#!/usr/bin/env bash # scripts/backup-db.sh — nightly Postgres dump for loreal-prod-tracker # # Runs on the HOST (not inside the app container), driving pg_dump # through the db service's compose project. Writes a gzipped SQL # file to $BACKUP_DIR and prunes anything older than $RETAIN_DAYS. # # Intended to run from cron at midnight — see DEPLOY.md for the # crontab entry. Safe to run manually at any time. # # Env vars (all optional, sensible defaults for optical-dev): # BACKUP_DIR where to write dumps (default /srv/backups/loreal-prod-tracker) # RETAIN_DAYS days to keep dumps (default 30) # COMPOSE_PROJECT compose project name (default loreal-prod-tracker) # COMPOSE_DIR dir containing docker-compose.yml (default /opt/loreal-prod-tracker) # PGDATABASE database name (default loreal_prod_tracker) # PGUSER postgres user (default postgres) # # Exit codes: 0 ok, non-zero = failure (cron will mail root on non-zero). set -euo pipefail BACKUP_DIR="${BACKUP_DIR:-/srv/backups/loreal-prod-tracker}" RETAIN_DAYS="${RETAIN_DAYS:-30}" COMPOSE_PROJECT="${COMPOSE_PROJECT:-loreal-prod-tracker}" COMPOSE_DIR="${COMPOSE_DIR:-/opt/loreal-prod-tracker}" PGDATABASE="${PGDATABASE:-loreal_prod_tracker}" PGUSER="${PGUSER:-postgres}" TIMESTAMP=$(date +%Y-%m-%d_%H%M%S) OUT_FILE="${BACKUP_DIR}/loreal-prod-tracker_${TIMESTAMP}.sql.gz" mkdir -p "$BACKUP_DIR" echo "[backup] $(date -Iseconds) starting dump → $OUT_FILE" # Stream pg_dump output straight through gzip to disk without buffering # the whole dump in memory. `exec -T` keeps the TTY disabled so this # works from cron. We `cd` into the compose dir because compose reads # its project definition from the working dir. ( cd "$COMPOSE_DIR" docker compose -p "$COMPOSE_PROJECT" exec -T db \ pg_dump -U "$PGUSER" --format=plain --clean --if-exists "$PGDATABASE" ) | gzip > "$OUT_FILE" SIZE=$(du -h "$OUT_FILE" | cut -f1) echo "[backup] wrote $OUT_FILE ($SIZE)" # Prune anything older than RETAIN_DAYS. `-mtime +N` is "modified more # than N days ago" — older dumps get deleted. Fails gracefully if the # dir is empty. find "$BACKUP_DIR" -maxdepth 1 -type f -name 'loreal-prod-tracker_*.sql.gz' \ -mtime +"$RETAIN_DAYS" -print -delete || true echo "[backup] $(date -Iseconds) done"