Files
momentum-system/deploy/docker/backup_db.sh
T
momentum devandClaude Fable 5 caee1d5655 backup: nightly pg_dump service on the postgres:18 image
Production Postgres had NO automated backup (pg_dump appeared only in the
one-time migration script). New db-backup service in both compose files fires
20:10 ET Mon-Fri: pg_dump | gzip with pipefail + gunzip -t + tmp-then-rename
(a mid-stream death can't masquerade as a backup), prunes past
BACKUP_KEEP_DAYS, writes last_backup_ok on success. postgres:18 image =
version-matched client (app image has none). TrueNAS host is pull-only, so
its copy inlines the script. Restore: gunzip -c dump | psql $DATABASE_URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:39:26 -04:00

31 lines
1.2 KiB
Bash

#!/usr/bin/env bash
# Nightly logical backup of the momentum Postgres. Runs inside the db-backup
# compose service — the postgres:18 image, so pg_dump always matches the
# server major (the app image has NO pg_dump, and a Debian-stock client is an
# older major that refuses to dump a newer server).
#
# pg_dump | gzip -> $BACKUP_DIR/momentum-YYYY-MM-DD.sql.gz
# prune anything older than BACKUP_KEEP_DAYS (default 14)
# write $BACKUP_DIR/last_backup_ok on success (freshness marker)
#
# Restore: gunzip -c momentum-<date>.sql.gz | psql "$DATABASE_URL"
#
# Run with bash (not sh): pipefail is what catches a mid-stream pg_dump death
# that would otherwise leave a truncated-but-valid gzip behind.
set -euo pipefail
BACKUP_DIR="${BACKUP_DIR:-/backups}"
BACKUP_KEEP_DAYS="${BACKUP_KEEP_DAYS:-14}"
: "${DATABASE_URL:?DATABASE_URL is required}"
mkdir -p "$BACKUP_DIR"
out="$BACKUP_DIR/momentum-$(date +%F).sql.gz"
echo "[backup] $(date) -> $out"
pg_dump "$DATABASE_URL" | gzip > "$out.tmp"
gunzip -t "$out.tmp"
mv "$out.tmp" "$out"
find "$BACKUP_DIR" -name 'momentum-*.sql.gz*' -mtime +"$BACKUP_KEEP_DAYS" -delete # *: also sweep stale .tmp from failed runs
date > "$BACKUP_DIR/last_backup_ok"
echo "[backup] OK $(du -h "$out" | cut -f1)"