#!/usr/bin/env sh # PostgreSQL + release-storage backup of the GTI Fiscal Hub (restorable with # restore.sh + restore_releases.sh). Run from deploy/hub. # ./backup.sh -> ./backups/gti_fiscal_YYYYmmdd_HHMMSS.dump (Postgres, last 14 kept) # -> ./backups/gti_fiscal_releases_YYYYmmdd_HHMMSS.tgz (release binaries + AgentRelease metadata is IN the dump above) set -eu COMPOSE="docker compose -f docker-compose.prod.yml --env-file .env" # BACKUP_DIR (like every other setting) lives in .env, but --env-file only # feeds the container — a plain shell script never gets it unless it loads # the file itself. Without this, BACKUP_DIR silently falls back to ./backups # here while the Hub's containers still read the real value from .env. if [ -f .env ]; then set -a; . ./.env; set +a; fi DIR="${BACKUP_DIR:-./backups}" mkdir -p "$DIR" STAMP="$(date +%Y%m%d_%H%M%S)" FILE="gti_fiscal_${STAMP}.dump" $COMPOSE exec -T postgres pg_dump -U gti -d gti_fiscal -Fc -f "/backups/${FILE}" [ -s "$DIR/$FILE" ] || { echo "backup empty or missing: $DIR/$FILE" >&2; exit 1; } # Retention runs inside the container: Docker creates the bind-mounted # backup directory as root, so a host-side rm would fail from the 15th dump. $COMPOSE exec -T postgres sh -c 'ls -1t /backups/gti_fiscal_*.dump | tail -n +15 | xargs -r rm -f' echo "backup ok: $DIR/$FILE" # Release storage (central/app/release_storage.py: every published Agent # package zip/exe, never in Postgres — only its metadata is, in the dump # above). Streamed out of the fiscal-hub container's stdout so this works # regardless of the compose project/container name. RELEASES_FILE="gti_fiscal_releases_${STAMP}.tgz" $COMPOSE exec -T fiscal-hub tar -C /app/data -cz releases > "$DIR/$RELEASES_FILE" [ -s "$DIR/$RELEASES_FILE" ] || { echo "release backup empty or missing: $DIR/$RELEASES_FILE" >&2; exit 1; } find "$DIR" -maxdepth 1 -name 'gti_fiscal_releases_*.tgz' -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | tail -n +15 | cut -d' ' -f2- | xargs -r rm -f echo "release storage backup ok: $DIR/$RELEASES_FILE"