Skip to content

feat: daily encrypted DB backup and production health alerting - #110

Merged
abrichr merged 2 commits into
mainfrom
feat/db-backup-and-alerting
Aug 2, 2026
Merged

feat: daily encrypted DB backup and production health alerting#110
abrichr merged 2 commits into
mainfrom
feat/db-backup-and-alerting

Conversation

@abrichr

@abrichr abrichr commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes the two operational blockers for the hosted path: production DB has no backups (openadapt-cloud AUDIT.md finding (b): pitr_enabled=false, zero provider physical backups as of 2026-07-25), and there is no automated alerting.

Part A — daily encrypted logical backup (db-backup.yml)

  • Daily (07:23 UTC) supabase db dump of the exact triple the cloud data-safety runbook prescribes: roles.sql (--role-only), schema.sql, data.sql (--use-copy --data-only, excluding the storage vector tables). A single unflagged dump is schema-only and is not a backup.
  • Fail-closed: the run fails loudly when SUPABASE_DB_URL or the age recipient is missing, and refuses to upload an empty/schema-only dump. A red daily run is the reminder that prod still has no recovery point.
  • Encrypted with age to a public key committed in-repo (ops/backup/age-recipients.txt, currently comments-only — founder commits the real key). Private key is generated by the founder and stays offline/vault; nothing secret was generated in this PR. This repo is public, so artifacts are downloadable by any logged-in GitHub user — the encryption is load-bearing.
  • Uploaded as a workflow artifact, retention 90 days, with a manifest (plaintext + ciphertext SHA-256).
  • Restore drill with exact decrypt + scratch-restore commands (same psql --single-transaction order as the cloud runbook): ops/backup/RESTORE_DRILL.md.

Founder actions required (backup is dark until done)

# 1. Generate the keypair on a trusted machine; store the PRIVATE key in the vault + offline:
umask 077 && age-keygen -o ~/openadapt-db-backup.agekey
# 2. Commit ONLY the printed `age1...` public key as a line in ops/backup/age-recipients.txt (PR).
# 3. Set the DB secret (value: Supabase -> Project Settings -> Database -> connection string):
gh secret set SUPABASE_DB_URL --repo OpenAdaptAI/openadapt-ops

$0 vs paid — honest tradeoff (Supabase public pricing, checked 2026-08-02)

Option $/mo RPO Restore Covers
This PR: daily pg_dump, encrypted artifact, 90d retention $0 up to 24h manual, logical, drilled DB only
Supabase Pro (daily physical backups, 7d retention) $25 up to 24h dashboard physical restore; unlocks restore-to-new-project DB only
Pro + PITR add-on $25 + $100 per 7 days retention (+ ~$10-15 compute add-on net of Pro credit) minutes dashboard PITR to timestamp DB only (WAL)

Answer: yes, we reach a recovery point for $0 (once the founder does the 3 steps above). Recommendation: buy Supabase Pro ($25/mo) now — cheapest change that makes provider-status --require-recovery pass and gives provider-side physical restore; defer the PITR add-on (~$110-140/mo all-in) until at least one paying pilot (at N=0, 24h RPO with a drilled restore is defensible). Storage buckets are covered by neither option; bucket export stays with the cloud retention drill tooling.

Part B — health alerting (prod-health-alert.yml)

  • Every 30 min, probes https://app.openadapt.ai/api/health/ready — verified live today (200, "ready":true); this is the genuine dependency probe (src/lib/readiness.ts), not a static page.
  • 3 attempts over ~2 minutes (retry absorbs transient blips), prints per-component NOT READY detail, then fails the run loudly — GitHub emails on scheduled-workflow failure: the $0 pager.
  • Optional Telegram page using the existing crier bot secret names (TELEGRAM_BOT_TOKEN, TELEGRAM_OWNER_ID); neither exists in this repo today, so the step self-skips. To enable: gh secret set TELEGRAM_BOT_TOKEN --repo OpenAdaptAI/openadapt-ops and gh secret set TELEGRAM_OWNER_ID --repo OpenAdaptAI/openadapt-ops.
  • Caveats addressed in-file and in RESTORE_DRILL.md §6: GitHub auto-disables schedules after 60 days without repo activity (the daily docs sync.yml commit currently provides keepalive; if it stops, schedules die silently), and failure emails go to the last committer of the workflow file.

Cost discipline

openadapt-ops is public → Actions minutes and artifact storage are free. Backup: 1 run/day. Health: 48 runs/day × <1 min ≈ trivial even if metered. Both workflows pin actions by SHA and follow the repo's existing conventions (least-privilege permissions, concurrency groups, graceful degradation for optional secrets).

Validated with actionlint (pass); probe script smoke-tested against the live endpoint (healthy and failure branches).

🤖 Generated with Claude Code

abrichr and others added 2 commits August 2, 2026 18:27
Closes the zero-backup posture from openadapt-cloud AUDIT.md finding (b)
(pitr_enabled=false, zero provider physical backups as of 2026-07-25) with
a $0 recovery point:

- .github/workflows/db-backup.yml: daily supabase db dump of the runbook
  triple (roles/schema/data), fail-closed on missing config or a
  schema-only dump, encrypted with age to a public key committed in-repo,
  uploaded as a workflow artifact with 90-day retention. The repo is
  public, so artifacts are world-downloadable ciphertext; the private key
  stays offline with the founder.
- ops/backup/age-recipients.txt: recipient file (comments only until the
  founder commits the real public key; the workflow fails until then).
- ops/backup/RESTORE_DRILL.md: keypair generation, scratch-restore drill
  matching the cloud data-safety runbook, RPO/cost tradeoff table, and
  key-rotation/compromise procedure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
$0 pager: probes https://app.openadapt.ai/api/health/ready (the genuine
dependency probe from openadapt-cloud src/lib/readiness.ts), retries 3
times over ~2 minutes to absorb transient blips, prints per-component
not-ready detail, and fails the run loudly so GitHub emails on scheduled
failure. Optionally pages via Telegram when the crier bot secrets
(TELEGRAM_BOT_TOKEN/TELEGRAM_OWNER_ID) are configured as repo secrets.
Documents the 60-day scheduled-workflow auto-disable caveat (daily docs
sync currently provides keepalive activity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@abrichr
abrichr merged commit 74e8146 into main Aug 2, 2026
5 checks passed
@abrichr
abrichr deleted the feat/db-backup-and-alerting branch August 2, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant