backup.sh now also streams the fiscal-hub container's release binaries
(dist tarball, never in Postgres) into BACKUP_DIR alongside the pg_dump,
with the same 14-backup retention; restore_releases.sh is the companion
restore. Verified with a real restore smoke test in production: the
restored dump's agent_releases row and the restored tarball's exe both
match the published sha256 exactly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GTI terminates TLS in its Nginx Proxy Manager (10.0.10.100), not on the
Hub host, so the Hub must be reachable on the LAN — by the proxy only.
- docker-compose.prod.yml passes FORWARDED_ALLOW_IPS (uvicorn then trusts
X-Forwarded-For/-Proto from the proxy only: real client IPs in audit and
session records, https scheme) and WEB_COOKIE_SECURE. Defaults keep the
previous behaviour.
- restrict_hub_port.sh + gti-fiscal-hub-port.service: Docker-published
ports bypass UFW, so the source restriction lives in DOCKER-USER,
matched on the conntrack original destination with --ctdir ORIGINAL,
and is re-applied whenever docker.service starts (the chain is not
persisted across reboots).
- *.service forced to LF like *.sh; tests cover both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by running deploy/hub/backup.sh on the production VM:
- With core.autocrlf=true, `git archive` on the Windows release machine
shipped the .sh files with CRLF and `sh` aborted on line 4
("set: Illegal option -"): backup, restore and seed never ran.
.gitattributes now forces *.sh to LF.
- Docker creates the bind-mounted backups/ directory as root, so the
host-side retention `rm` would fail once the 15th dump existed.
Retention now runs inside the postgres container.
tests/test_deploy_scripts.py covers both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>