From fb19795fdbf1c305b13fdd0143e9d11ab913be36 Mon Sep 17 00:00:00 2001 From: Thorsten Date: Thu, 3 Sep 2026 13:04:00 +0200 Subject: [PATCH] Add pg_dump/pg_restore backup and restore scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For moving this project to a new server: git tracks code, not the Postgres data directory, and raw volume/data-dir copying is fragile across Postgres versions or architectures (this repo already hit a collation-version mismatch once from just changing the Postgres image). pg_dump/pg_restore is the portable way to move the actual data instead. scripts/backup-db.sh dumps the running postgres container's database in pg_dump's custom format into ./backups/ (gitignored — dumps contain real user data and don't belong in git history). It reads POSTGRES_USER/POSTGRES_DB from the container's own environment rather than re-parsing .env, so it can't drift out of sync with the actual running config. scripts/restore-db.sh restores a dump into the running container (--clean --if-exists --single-transaction, so a failure rolls back atomically instead of leaving a half-restored database), with a confirmation prompt since it's destructive, skippable via -y. Added .gitattributes forcing LF on *.sh — Windows git would otherwise check these out as CRLF, breaking the bash shebang on the Linux server they're meant to run on. Verified end to end: took a real backup of the live database, restored it back over itself, and confirmed exact row-count matches across every table plus that pgvector similarity search still works on the restored embeddings (not just that the bytes round-tripped). Co-Authored-By: Claude Sonnet 5 --- .gitattributes | 2 ++ .gitignore | 1 + scripts/backup-db.sh | 19 +++++++++++++++++++ scripts/restore-db.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 .gitattributes create mode 100644 scripts/backup-db.sh create mode 100644 scripts/restore-db.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..efdba87 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.sh text eol=lf diff --git a/.gitignore b/.gitignore index a2422fd..e65d728 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ build/ caddy_data/ caddy_config/ pgdata/ +backups/ frontend/dist/ *.tsbuildinfo .DS_Store diff --git a/scripts/backup-db.sh b/scripts/backup-db.sh new file mode 100644 index 0000000..b0e842f --- /dev/null +++ b/scripts/backup-db.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Dumps the Postgres database from the running "postgres" container into ./backups/, +# in pg_dump's custom format (compressed, restorable with pg_restore — see restore-db.sh). +# +# Usage: scripts/backup-db.sh +set -euo pipefail + +cd "$(dirname "$0")/.." + +mkdir -p backups +timestamp="$(date +%Y%m%d_%H%M%S)" +out="backups/dnd_backup_${timestamp}.dump" +trap 'rm -f "${out}"' ERR + +echo "Dumping database into ${out} ..." +docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -F c' > "${out}" + +size="$(du -h "${out}" | cut -f1)" +echo "Done: ${out} (${size})" diff --git a/scripts/restore-db.sh b/scripts/restore-db.sh new file mode 100644 index 0000000..c37f377 --- /dev/null +++ b/scripts/restore-db.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Restores a dump produced by backup-db.sh into the running "postgres" container. +# Destructive: drops and recreates every object in the target database before restoring. +# +# Usage: scripts/restore-db.sh path/to/dnd_backup_20260101_120000.dump [-y] +# -y skip the confirmation prompt (for non-interactive/scripted use) +set -euo pipefail + +cd "$(dirname "$0")/.." + +skip_confirm=false +file="" +for arg in "$@"; do + if [ "$arg" = "-y" ]; then + skip_confirm=true + else + file="$arg" + fi +done + +if [ -z "$file" ]; then + echo "Usage: $0 path/to/backup.dump [-y]" >&2 + exit 1 +fi +if [ ! -f "$file" ]; then + echo "Error: file not found: $file" >&2 + exit 1 +fi + +if [ "$skip_confirm" = false ]; then + read -r -p "This will DROP and recreate every table in the target database. Continue? [y/N] " reply + case "$reply" in + [yY]|[yY][eE][sS]) ;; + *) echo "Aborted."; exit 1 ;; + esac +fi + +echo "Restoring ${file} ..." +docker compose exec -T postgres sh -c \ + 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner --no-privileges --single-transaction' \ + < "$file" + +echo "Done."