Files
famapp/deploy/backups/README.md
T
ginnoirandClaude Sonnet 4.6 285a460eb8 Implement tasks 60, 61, 62: backups, rate limiting, structured logging
Task 60 — Postgres backups:
- deploy/backups/: backup.sh (pg_dump -Fc nightly), retain.sh (14/8/6 tiers),
  restore.sh, entrypoint.sh, crontab
- famapp-backup Alpine service + backups volume added to deploy/compose.yaml
- Restore procedure in deploy/backups/README.md

Task 61 — Rate limiting on share links:
- src/lib/rate-limit.ts: Edge-compatible sliding-window counter (50/min, LRU eviction)
  with consume(), isRateLimited(), recordFailure() exports
- middleware.ts: enforces 429 with Retry-After: 60 for /s/[token] (IP + token prefix)
- /s/[token]/page.tsx: tracks only failed resolveShareToken calls via recordFailure()

Task 62 — Structured logging:
- pino + pino-pretty installed; serverExternalPackages added to next.config.ts
- src/lib/logger.ts: JSON in production, pretty in dev, level from LOG_LEVEL env
- middleware.ts: structured JSON request log (method, path, status, ms, authenticated)
- _core/push.ts, notify.ts, reminders.ts: console.error/log → logger.error/info

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:23:29 -05:00

2.7 KiB
Raw Blame History

Backups

The famapp-backup service performs nightly compressed pg_dump of both famapp-db and authentik-db at 02:00 server time.

Storage layout

/backups/           (named Docker volume: famapp_backups)
  famapp/
    daily/          ← last 14 days
    weekly/         ← last 8 Sundays
    monthly/        ← last 6 first-of-month dumps
  authentik/
    daily/
    weekly/
    monthly/

Dump files are named YYYY-MM-DD.dump in custom (-Fc) format (internal compression, ~35× smaller than plain SQL).

Retention

Tier Kept Trigger
daily 14 every night
weekly 8 Sunday night
monthly 6 1st of the month

Retention is enforced by retain.sh at the end of each backup.sh run.

Restore procedure

1. Identify the dump

# List available dumps
docker exec famapp-backup-1 ls /backups/famapp/daily/
docker exec famapp-backup-1 /scripts/restore.sh \
  /backups/famapp/daily/2024-06-01.dump \
  postgres://famapp:SECRET@famapp-db:5432/famapp

Replace SECRET with the value of FAMAPP_DB_PASSWORD in your .env file. For authentik:

docker exec famapp-backup-1 /scripts/restore.sh \
  /backups/authentik/daily/2024-06-01.dump \
  postgres://authentik:SECRET@authentik-db:5432/authentik

2b. Restore to a separate database (safe — non-destructive)

Create a fresh target database first, then restore into it:

# Create the target DB
docker exec famapp-db-1 createdb \
  -U "$FAMAPP_DB_USER" famapp_restore

# Restore
docker exec famapp-backup-1 /scripts/restore.sh \
  /backups/famapp/daily/2024-06-01.dump \
  postgres://famapp:SECRET@famapp-db:5432/famapp_restore

2c. Restore on a fresh host (disaster recovery)

# Copy the dump file out of the volume
docker cp famapp-backup-1:/backups/famapp/daily/2024-06-01.dump ./

# Spin up a temporary Postgres container and restore
docker run --rm \
  -e PGPASSWORD=SECRET \
  -v "$(pwd)/2024-06-01.dump:/dump.dump:ro" \
  postgres:16-alpine \
  pg_restore -h <new-db-host> -U famapp -d famapp \
    --no-owner --no-acl /dump.dump

Off-site replication

The backups live in the famapp_backups Docker named volume. To copy them to another host, rsync the volume's data directory periodically (e.g. from a host cron job):

# On the Docker host, add to /etc/cron.d/famapp-rsync:
30 3 * * * root rsync -a --delete \
  /var/lib/docker/volumes/famapp_backups/_data/ \
  user@offsite-server:/opt/famapp-backups/

Encryption at rest is handled at the disk/filesystem layer (e.g. LUKS).