diff --git a/abra.sh b/abra.sh new file mode 100644 index 0000000..0975f1e --- /dev/null +++ b/abra.sh @@ -0,0 +1 @@ +export PG_BACKUP_VERSION=v1 diff --git a/compose.yml b/compose.yml index a308f36..5e10561 100644 --- a/compose.yml +++ b/compose.yml @@ -67,6 +67,21 @@ services: - postgres:/var/lib/postgresql/data networks: - backend + deploy: + labels: + backupbot.backup: "${ENABLE_BACKUPS:-true}" + backupbot.backup.pre-hook: "/pg_backup.sh backup" + backupbot.backup.volumes.postgres.path: "backup.sql" + backupbot.restore.post-hook: "/pg_backup.sh restore" + configs: + - source: pg_backup + target: /pg_backup.sh + mode: 0555 + +configs: + pg_backup: + name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION} + file: pg_backup.sh secrets: db_password: diff --git a/pg_backup.sh b/pg_backup.sh new file mode 100755 index 0000000..70a8bc6 --- /dev/null +++ b/pg_backup.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Postgres backup/restore hook for the immich `database` service (VectorChord/pgvecto.rs image). +# Invoked by backupbot-two via the deploy labels: +# backupbot.backup.pre-hook = "/pg_backup.sh backup" +# backupbot.backup.volumes.postgres.path = "backup.sql" +# backupbot.restore.post-hook = "/pg_backup.sh restore" +# Backup dumps the immich DB to backup.sql (gzip) inside the postgres volume; backupbot then +# archives that file. Restore reads it back and reimports. immich-server keeps TCP connections +# open to the DB, so restore must terminate them and FORCE-drop before recreating (the matrix-synapse +# pg_hba "local trust" trick does not cover networked connections). + +set -e + +BACKUP_FILE='/var/lib/postgresql/data/backup.sql' +export PGPASSWORD=$(cat "${POSTGRES_PASSWORD_FILE:-/run/secrets/db_password}") +DB_USER="${POSTGRES_USER:-postgres}" +DB_NAME="${POSTGRES_DB:-immich}" + +function backup { + pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE" +} + +function restore { + # immich-server holds connections to the DB; drop them so DROP DATABASE can proceed. + psql -U "$DB_USER" -d postgres -c \ + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='${DB_NAME}' AND pid<>pg_backend_pid();" + psql -U "$DB_USER" -d postgres -c "DROP DATABASE ${DB_NAME} WITH (FORCE);" + createdb -U "$DB_USER" "$DB_NAME" + gunzip -c "$BACKUP_FILE" | psql -U "$DB_USER" -d "$DB_NAME" -1 -v ON_ERROR_STOP=1 -f - +} + +$@