# VYNDR Backup Runbook (security follow-up item 2) Supabase free tier has **zero** backups (no scheduled, no PITR). `scripts/backup-db.sh` is the safety net: a nightly full-database `pg_dump`, 14 days kept locally, a weekly copy pushed off-box, ntfy alert on any failure. ## What Kev needs to set (one env var) **`SUPABASE_DB_URL`** — the Supabase **direct** connection string (session mode). Supabase → Project → Settings → Database → **Connection string → URI**, the `db..supabase.co:5432` one (NOT the `:6543` transaction pooler — `pg_dump` needs a real session). Paste it in Coolify as an env var; never commit it. Optional: `BACKUP_REMOTE` (off-box rsync target for the weekly copy — see below). ## Install the cron (on the Hetzner box) ```bash # 1. Ensure the client is present sudo apt-get install -y postgresql-client rsync # 2. Nightly at 03:10 UTC. Pass the env the script needs (or source an env file). sudo crontab -e # add: 10 3 * * * SUPABASE_DB_URL='postgresql://...' BACKUP_REMOTE='u123456@u123456.your-storagebox.de:vyndr-backups/' /path/to/vyndr/scripts/backup-db.sh >> /var/log/vyndr-backup.log 2>&1 ``` (If the cron runs inside the Coolify container instead, the env vars are already present — just schedule `scripts/backup-db.sh`.) ## Off-box target (simplest reliable pick) **Hetzner Storage Box** over `rsync`/SSH — you're already on Hetzner, it's ~€3/mo for 1TB, and needs no extra tooling. Create one, add the box's SSH key to it, set `BACKUP_REMOTE=u@u.your-storagebox.de:vyndr-backups/`. The script pushes the latest dump every Sunday. (Alternative: Backblaze B2 via `rclone` if you'd rather keep it off Hetzner entirely — swap the `rsync` line for `rclone copy`.) ## Restore / FINGERPRINT (proves it's a real backup, not just a file) A dump only counts once a restore of it succeeds. Load one into a scratch DB: ```bash # spin a throwaway local postgres, restore the newest dump, count a known table docker run -d --name vyndr-restore-test -e POSTGRES_PASSWORD=x -p 55432:5432 postgres:15 sleep 5 newest=$(ls -t /var/backups/vyndr/vyndr-*.dump | head -1) pg_restore --no-owner --no-privileges -d "postgresql://postgres:x@localhost:55432/postgres" "$newest" psql "postgresql://postgres:x@localhost:55432/postgres" -c "select count(*) from public.ledger_entries;" docker rm -f vyndr-restore-test ``` A non-zero `ledger_entries` count from the restored dump = the backup is real and restorable. Record the date + row count as the fingerprint. ## Alerting Any hard failure (missing env, `pg_dump` error, empty/undersized dump) pages `ntfy` topic `vyndr-backups-kev2026` at urgent priority. The weekly off-box push failing (or `BACKUP_REMOTE` unset) pages at high priority but does not fail the run — the local dump still succeeded. Subscribe the phone to that topic.