Skip to content

Backup and Recovery

The backup strategy has three tiers: hourly local dumps on Caroline, daily rsync to Companion-Cube (the Synology NAS), and encrypted offsite to Cloudflare R2. An untested backup is a hypothesis. A tested restore procedure exists; run it.

scripts/backup-hourly.sh runs on Caroline as a systemd timer. It produces a timestamped pg_dump of the primary database and includes WAL cleanup.

It also handles the Authelia users.yml backup (Phase 1b). This file is excluded from GitOps checkout because Authelia rehashes passwords on login, making any version restored from git invalid. The hourly backup is the only reliable source for users.yml recovery.

Output directory: ~/backups/ on Caroline.

scripts/maintenance/daily-backup.sh and scripts/backup/rsync-to-nas.sh rsync backup data from Caroline to Companion-Cube (the Synology NAS) for a second physical copy.

scripts/backup/push-offsite.sh encrypts and pushes database dumps to Cloudflare R2 storage. This provides geographic redundancy and a recovery point independent of the local network.

scripts/backup/dr-drill.sh runs restore drills against the R2 backups to verify they are actually recoverable. Results are logged to dr_drill_log.

  1. SSH to Caroline
  2. Stop dependent containers: docker compose stop n8n authelia grafana dashboard-api mcp-proxy-postgres mcp-proxy-memory discord-bot
  3. Restore the database from the backup dump using pg_restore as the superuser role
  4. Restart services: docker compose up -d
  5. Verify: make pi-status and check container health
  1. SSH to Caroline
  2. Copy the backup from ~/backups/authelia-users-<timestamp>.yml to authelia/users.yml in the project directory
  3. Restart Authelia: docker compose restart authelia
  4. Verify login works before closing the session
  1. Run scripts/backup/dr-drill.sh — it documents the full restore procedure and tests it.
  2. Retrieve the encrypted dump from R2 using the credentials in .env.
  3. Decrypt and restore per the drill script output.

scripts/backup/verify-restore.sh tests that a backup file can actually be restored into a temporary database. Run this periodically or after any change to the backup pipeline.

Terminal window
# Check backup log for recent runs
make pi-db-shell
# In psql:
SELECT created_at, status, size_bytes, notes
FROM backup_log
ORDER BY created_at DESC
LIMIT 10;
ScriptPurpose
scripts/backup-hourly.shHourly pg_dump + Authelia users.yml backup on Caroline
scripts/backup/rsync-to-nas.shRsync backups to NAS
scripts/backup/push-offsite.shEncrypt and push to Cloudflare R2
scripts/backup/dr-drill.shFull disaster recovery drill
scripts/backup/local-pg-dump.shManual local Postgres dump
scripts/backup/verify-restore.shVerify a backup file is restorable
scripts/backup/retention-local.shPrune old local backups
scripts/backup/setup-wal-archive.shWAL archiving setup