feat(backup): admin-tunable retention + BACKUP_KEY as a Komodo secret
Build desktop / desktop (push) Successful in 4m17s
Build & push images / images (push) Failing after 39s
CI / check (push) Successful in 39s

Retention (keep-last / keep-daily-days) is operational policy the on-site admin
should tune, not a server env var requiring a redeploy -- same reasoning that moved
the target directory to the UI.

- Migration 0017: site_config.backup_keep_last + backup_keep_daily_days (nullable;
  null = code default 7 / 30 per field).
- BackupService reads retention fresh each run; status() exposes keepLast +
  keepDailyDays. DEFAULT_BACKUP_RETENTION is now a pure code default (env reads gone).
- PUT /api/backup/config accepts keepLast / keepDailyDays (non-negative int, or null
  to reset to default; 400 on negative).
- UI: two retention fields on the Backup config card; one Save covers target +
  retention. i18n sq + en.

BACKUP_KEY wired into Komodo:
- komodo/resources.toml: BACKUP_KEY=[[park_buzi_backup_key]] (per-booth secret,
  alongside JWT / signing keys).
- komodo/.env.komodo.example: documents it as the ONLY backup env var -- escrow it
  offsite alongside EVENT_SIGNING_KEY (recovery needs both); target + retention are
  admin-chosen in the UI / DB, not env. Server .env.example trimmed to just BACKUP_KEY.

Also carries the small in-progress setup-intro i18n copy trim.

Tests: 218 server tests green, incl. retention persist / reset-to-default / reject-
negative and the updated status shape. Migration applies cleanly (needed a
statement-breakpoint between the two ALTERs). Wiki backup-recovery updated.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-29 12:52:18 +02:00
parent d5e41500a8
commit 84f00db48b
16 changed files with 234 additions and 44 deletions
+13
View File
@@ -29,6 +29,18 @@ EVENT_SIGNING_KEY=[[booth_<name>_event_signing_key]]
# without =0 the auth cookie never sends and operators CANNOT log in. Set 1 only behind TLS.
COOKIE_SECURE=0
# ════════════════════════════════════════════════════════════════════════════
# BACKUP (encrypted on-site DB backup — see wiki/concepts/backup-recovery.md)
# ════════════════════════════════════════════════════════════════════════════
# Dedicated backup-ENCRYPTION key. SEPARATE from EVENT_SIGNING_KEY (independent rotation;
# backups travel to the target, the signing key must not). Per booth + unique. openssl rand
# -hex 32. ESCROW it offsite alongside EVENT_SIGNING_KEY — disaster recovery needs BOTH, and
# neither is ever stored inside the backup it unlocks. Backups stay OFF until this key is set
# AND the admin picks a target directory in the UI. The key is the ONLY backup env var — the
# target directory and retention (keep-last / keep-daily) are admin-chosen in the UI (Setup →
# Backup) and stored in the DB, so changing them needs no redeploy.
BACKUP_KEY=[[booth_<name>_backup_key]]
# ════════════════════════════════════════════════════════════════════════════
# COMMONLY SET (have defaults, but you usually want these explicit on a booth)
# ════════════════════════════════════════════════════════════════════════════
@@ -103,5 +115,6 @@ WS_ALLOWED_ORIGINS=
# ════════════════════════════════════════════════════════════════════════════
# JWT_SECRET -> [[booth_<name>_jwt_secret]] (login)
# EVENT_SIGNING_KEY -> [[booth_<name>_event_signing_key]] (ledger signing — fraud root)
# BACKUP_KEY -> [[booth_<name>_backup_key]] (backup encryption — escrow offsite)
# periphery passkey -> [[periphery_passkey_booth_<name>]] (agent onboarding)
# registry account -> [[gitea_registry_account]] (image pull)
+3 -1
View File
@@ -15,7 +15,8 @@
#
# Secrets ([[park_buzi_jwt_secret]] etc.) are REFERENCES to Komodo Core's secret store —
# per-booth + unique, never inlined here (this file is in git). JWT_SECRET gates login;
# EVENT_SIGNING_KEY signs the append-only anti-fraud ledger.
# EVENT_SIGNING_KEY signs the append-only anti-fraud ledger; BACKUP_KEY encrypts on-site DB
# backups (separate from the signing key; escrow it offsite — recovery needs both).
#
# Deploys are MANUAL + PINNED in spirit: bump TAG to an immutable dev-<sha> before a
# production booth goes live (TAG=dev here is the moving tag, fine while staging). NO
@@ -48,4 +49,5 @@ VISION_ENABLED=1
WS_ALLOWED_ORIGINS=
JWT_SECRET=[[park_buzi_jwt_secret]]
EVENT_SIGNING_KEY=[[park_buzi_event_signing_key]]
BACKUP_KEY=[[park_buzi_backup_key]]
"""