chore(docker): add missing backup-db.sh NAS DB backup script
Same gap as run-cron.sh: the crontab's 03:00 backup-db.sh entry pointed at a script that never actually existed, so there have been no automated DB backups since that entry was added. pg_dump via a throwaway postgres:17-alpine container (matches Supabase's server version; no postgresql-client on the NAS host), against DIRECT_URL (non-pooled) rather than the pgbouncer-fronted DATABASE_URL, custom format (-Fc). Writes to /srv/dev-disk-by-uuid-ad295d7f-a870-4f70-9de2-dfded1fabf7f/BACKUPALL/pawfeed/ with 30-day retention, logs to docker/backup.log for visibility (no local mail delivery configured on this NAS). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6cc697a696
commit
282ff4e741
2 files changed
+73
-1
No files matched your search
+2
-1
@@ -34,8 +34,9 @@ yarn-error.log*
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# NAS-side cron runtime log (docker/run-cron.sh), not app code
|
||||
# NAS-side cron runtime logs (docker/run-cron.sh, docker/backup-db.sh), not app code
|
||||
docker/cron.log
|
||||
docker/backup.log
|
||||
|
||||
# temp scaffold directory (removed after setup)
|
||||
/scaffold-tmp
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# Daily pg_dump backup of the production Supabase Postgres DB, run via NAS
|
||||
# crontab (03:00).
|
||||
#
|
||||
# Dumps via a throwaway postgres:17-alpine container - matches the Supabase
|
||||
# server's Postgres 17.6, and this NAS has no postgresql-client in its host
|
||||
# PATH (same reason Prisma/Node tooling here also runs in a throwaway
|
||||
# container - see CLAUDE.md). Uses DIRECT_URL (the non-pooled port-5432
|
||||
# connection); pg_dump against the pgbouncer-fronted DATABASE_URL is
|
||||
# unreliable. Custom format (-Fc): compressed, restorable selectively via
|
||||
# `pg_restore -j <n>` for parallelism.
|
||||
#
|
||||
# Retention: 30 days, enforced by deleting older *.dump files after each run.
|
||||
#
|
||||
# Every run is logged to docker/backup.log (this NAS has no local mail
|
||||
# delivery set up for cron failures - see docker/run-cron.sh for the same
|
||||
# issue/fix on the app-cron side).
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ENV_FILE="$SCRIPT_DIR/.env"
|
||||
LOG_FILE="$SCRIPT_DIR/backup.log"
|
||||
BACKUP_DIR="/srv/dev-disk-by-uuid-ad295d7f-a870-4f70-9de2-dfded1fabf7f/BACKUPALL/pawfeed"
|
||||
RETENTION_DAYS=30
|
||||
PG_IMAGE="postgres:17-alpine"
|
||||
|
||||
log() {
|
||||
printf '%s [backup-db] %s\n' "$(date -Iseconds)" "$1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
log "ERROR: $ENV_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIRECT_URL="$(grep -E '^DIRECT_URL=' "$ENV_FILE" | head -n1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//')"
|
||||
if [ -z "$DIRECT_URL" ]; then
|
||||
log "ERROR: DIRECT_URL not set in $ENV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
STAMP="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
FILE_NAME="pawfeed-$STAMP.dump"
|
||||
PART_NAME="$FILE_NAME.part"
|
||||
|
||||
if docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-e DB_URL="$DIRECT_URL" \
|
||||
-e FILE_NAME="$PART_NAME" \
|
||||
-v "$BACKUP_DIR:/backup" \
|
||||
"$PG_IMAGE" \
|
||||
sh -c 'pg_dump "$DB_URL" -Fc -f "/backup/$FILE_NAME"' \
|
||||
>> "$LOG_FILE" 2>&1
|
||||
then
|
||||
mv "$BACKUP_DIR/$PART_NAME" "$BACKUP_DIR/$FILE_NAME"
|
||||
SIZE="$(du -h "$BACKUP_DIR/$FILE_NAME" | cut -f1)"
|
||||
log "OK: $FILE_NAME ($SIZE)"
|
||||
else
|
||||
rm -f "$BACKUP_DIR/$PART_NAME"
|
||||
log "ERROR: pg_dump failed, see output above"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DELETED=0
|
||||
for old in $(find "$BACKUP_DIR" -name 'pawfeed-*.dump' -mtime "+$RETENTION_DAYS"); do
|
||||
rm -f "$old"
|
||||
DELETED=$((DELETED + 1))
|
||||
done
|
||||
log "retention: removed $DELETED backup(s) older than ${RETENTION_DAYS}d"
|
||||
Reference in new issue
Block a user