Add pg_dump/pg_restore backup and restore scripts

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 <noreply@anthropic.com>
This commit is contained in:
Thorsten
2026-09-03 13:04:00 +02:00
parent a58730fce7
commit fb19795fdb
4 changed files with 65 additions and 0 deletions
+43
View File
@@ -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."