Add daily encrypted borgmatic backup service
- /docker/backup: borgmatic container (daily 03:00 Berlin via BACKUP_CRON) - Backs up /docker (ro) to local encrypted repo /var/backups/borg (repokey-blake2) - Postgres prod+dev captured via pg_dumpall; live PG data/logs/build artifacts excluded - Retention: keep_daily 7, keep_weekly 4, keep_monthly 6; runs prune/compact/create/check - Secrets in backup/.env (gitignored, chmod 600)
This commit is contained in:
@@ -18,3 +18,6 @@
|
|||||||
/dev/redis/data/
|
/dev/redis/data/
|
||||||
/dev/stripe-stub/.env
|
/dev/stripe-stub/.env
|
||||||
/dev/bandwidth-worker/.env
|
/dev/bandwidth-worker/.env
|
||||||
|
|
||||||
|
# Backup service secrets (DB passwords + borg passphrase)
|
||||||
|
/backup/.env
|
||||||
|
|||||||
@@ -49,3 +49,47 @@ After editing a function, `cd /docker/supabase && docker compose restart functio
|
|||||||
- `supabase-edge-functions` reads `REDIS_URL` from `/docker/supabase/.env`.
|
- `supabase-edge-functions` reads `REDIS_URL` from `/docker/supabase/.env`.
|
||||||
- bandwidth-worker polls `http://frps:7500/api/proxy/http` every 60s; deltas go
|
- bandwidth-worker polls `http://frps:7500/api/proxy/http` every 60s; deltas go
|
||||||
to `public.usage_samples` and `public.tunnels.bytes_used` via PostgREST.
|
to `public.usage_samples` and `public.tunnels.bytes_used` via PostgREST.
|
||||||
|
|
||||||
|
## Backups
|
||||||
|
|
||||||
|
Daily encrypted backups via **borgmatic** (`/docker/backup`).
|
||||||
|
|
||||||
|
| Service | Compose dir | Container | Schedule | Image |
|
||||||
|
|-----------|-----------------|-----------|---------------------|------------------------------------------------|
|
||||||
|
| borgmatic | /docker/backup | borgmatic | daily 03:00 (Berlin)| ghcr.io/borgmatic-collective/borgmatic:latest |
|
||||||
|
|
||||||
|
- **Source:** `/docker` (mounted read-only). **Repository:** `/var/backups/borg`
|
||||||
|
(local host disk for v1; remote planned later). **Encryption:** repokey-blake2.
|
||||||
|
- **Databases:** prod (`supabase-db`) + dev (`supabase-dev-db`) captured via
|
||||||
|
`pg_dumpall` (roles + all DBs), streamed into the archive. The container is
|
||||||
|
attached to `supabase_default` + `supabase-dev_default` to reach them.
|
||||||
|
- **Excluded:** live PG data dirs (`supabase/volumes/db/data`), logs,
|
||||||
|
`caddy/.../autosave.json`, `.remediation-backup`, `node_modules`, `.next`.
|
||||||
|
- **Retention:** keep_daily 7, keep_weekly 4, keep_monthly 6.
|
||||||
|
- **Schedule:** built-in cron via `BACKUP_CRON` env (`0 3 * * *`), running
|
||||||
|
`borgmatic --stats -v 0` (default action set: prune, compact, create, check).
|
||||||
|
- **Secrets:** `/docker/backup/.env` (chmod 600, gitignored) holds
|
||||||
|
`POSTGRES_PASSWORD_PROD`, `POSTGRES_PASSWORD_DEV`, `BORG_PASSPHRASE`.
|
||||||
|
**Store `BORG_PASSPHRASE` offsite — losing it makes the repo unrecoverable.**
|
||||||
|
|
||||||
|
### Operations
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/backup
|
||||||
|
docker compose up -d # start the scheduler
|
||||||
|
docker compose run --rm --entrypoint borgmatic borgmatic list # archives
|
||||||
|
docker compose run --rm --entrypoint borgmatic borgmatic create --stats # manual backup
|
||||||
|
docker compose run --rm --entrypoint borgmatic borgmatic check # verify integrity
|
||||||
|
```
|
||||||
|
|
||||||
|
Restore a database dump:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /docker/backup
|
||||||
|
ARCH=$(docker compose run --rm --entrypoint borgmatic borgmatic list --json \
|
||||||
|
| python3 -c "import sys,json;print(json.load(sys.stdin)[0][\"archives\"][-1][\"archive\"])")
|
||||||
|
docker compose run --rm -v /tmp/restore:/restore --workdir /restore \
|
||||||
|
--entrypoint borgmatic borgmatic extract --archive "$ARCH" \
|
||||||
|
--path "borgmatic/postgresql_databases/supabase-db:5432/all"
|
||||||
|
# then: psql -U postgres -f /tmp/restore/borgmatic/postgresql_databases/supabase-db:5432/all
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Never commit secrets.
|
||||||
|
.env
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# borgmatic configuration for linumiq.net — local encrypted daily backup (v1).
|
||||||
|
# borgmatic 2.x flat schema. Secrets are interpolated from the container
|
||||||
|
# environment (see ../.env): POSTGRES_PASSWORD_PROD, POSTGRES_PASSWORD_DEV,
|
||||||
|
# BORG_PASSPHRASE.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# What to back up (files)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
source_directories:
|
||||||
|
- /docker
|
||||||
|
|
||||||
|
# Repository (bind-mounted from host /var/backups/borg).
|
||||||
|
repositories:
|
||||||
|
- path: /repository
|
||||||
|
label: linumiq-local
|
||||||
|
|
||||||
|
# Encryption passphrase (repokey-blake2). Set in ../.env, kept offsite too.
|
||||||
|
encryption_passphrase: ${BORG_PASSPHRASE}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Exclusions: raw live PG data (captured via logical dumps below), transient
|
||||||
|
# logs, regenerable build artifacts, and remediation snapshots.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
exclude_patterns:
|
||||||
|
- /docker/supabase/volumes/db/data
|
||||||
|
- /docker/dev/supabase/volumes/db/data
|
||||||
|
- /docker/supabase/volumes/logs
|
||||||
|
- /docker/dev/supabase/volumes/logs
|
||||||
|
- /docker/caddy/config/caddy/autosave.json
|
||||||
|
- sh:/docker/**/.remediation-backup
|
||||||
|
- sh:/docker/**/node_modules
|
||||||
|
- sh:/docker/**/.next
|
||||||
|
exclude_caches: true
|
||||||
|
exclude_if_present:
|
||||||
|
- .nobackup
|
||||||
|
|
||||||
|
# Keep the archive listing manageable.
|
||||||
|
one_file_system: false
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Postgres logical dumps (streamed into the archive, not written to disk).
|
||||||
|
# name "all" => pg_dumpall (roles + all databases + extensions).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
postgresql_databases:
|
||||||
|
- name: all
|
||||||
|
hostname: supabase-db
|
||||||
|
port: 5432
|
||||||
|
username: postgres
|
||||||
|
password: ${POSTGRES_PASSWORD_PROD}
|
||||||
|
- name: all
|
||||||
|
hostname: supabase-dev-db
|
||||||
|
port: 5432
|
||||||
|
username: postgres
|
||||||
|
password: ${POSTGRES_PASSWORD_DEV}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Retention
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
keep_daily: 7
|
||||||
|
keep_weekly: 4
|
||||||
|
keep_monthly: 6
|
||||||
|
|
||||||
|
# Tag archives so prune groups them correctly.
|
||||||
|
archive_name_format: "linumiq-{now:%Y-%m-%dT%H:%M:%S}"
|
||||||
|
match_archives: "sh:linumiq-*"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Consistency checks (run as part of the default daily action set).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
checks:
|
||||||
|
- name: repository
|
||||||
|
- name: archives
|
||||||
|
|
||||||
|
# Surface command output/errors in logs.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: backup
|
||||||
|
services:
|
||||||
|
borgmatic:
|
||||||
|
image: ghcr.io/borgmatic-collective/borgmatic:latest
|
||||||
|
container_name: borgmatic
|
||||||
|
hostname: linumiq-borgmatic
|
||||||
|
restart: unless-stopped
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
environment:
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
# Daily backup at 03:00 server-local time. The image writes this to
|
||||||
|
# /etc/crontabs/root and runs: borgmatic --stats -v 0
|
||||||
|
BACKUP_CRON: "0 3 * * *"
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
|
volumes:
|
||||||
|
# Backup source (read-only — borgmatic never writes to /docker).
|
||||||
|
- /docker:/docker:ro
|
||||||
|
# Local borg repository (host-side, same disk; remote planned later).
|
||||||
|
- /var/backups/borg:/repository
|
||||||
|
# borgmatic configuration.
|
||||||
|
- ./borgmatic.d:/etc/borgmatic.d:ro
|
||||||
|
# Persist borg cache + security dir across container recreation so borg
|
||||||
|
# does not re-scan the whole repo or warn about an "unknown" repository.
|
||||||
|
- borg_cache:/root/.cache/borg
|
||||||
|
- borg_security:/root/.config/borg
|
||||||
|
networks:
|
||||||
|
# Reach the prod + dev Postgres containers (supabase-db / supabase-dev-db)
|
||||||
|
# by container name for the pg_dumpall hook.
|
||||||
|
- supabase_default
|
||||||
|
- supabase-dev_default
|
||||||
|
volumes:
|
||||||
|
borg_cache:
|
||||||
|
borg_security:
|
||||||
|
networks:
|
||||||
|
supabase_default:
|
||||||
|
external: true
|
||||||
|
supabase-dev_default:
|
||||||
|
external: true
|
||||||
Reference in New Issue
Block a user