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
@@ -0,0 +1,6 @@
-- Backup retention is OPERATIONAL POLICY the on-site admin tunes from the Backup screen, not a
-- server env var requiring a redeploy. Two additive, nullable columns on the single-row config;
-- null = fall back to the code default (keepLast 7, keepDailyDays 30). The backup ENGINE stays
-- parameterized; this just moves the source of truth env → DB. See wiki/concepts/backup-recovery.md.
ALTER TABLE `site_config` ADD `backup_keep_last` integer;--> statement-breakpoint
ALTER TABLE `site_config` ADD `backup_keep_daily_days` integer;
+7
View File
@@ -120,6 +120,13 @@
"when": 1781885900000,
"tag": "0016_backup_target_dir",
"breakpoints": true
},
{
"idx": 17,
"version": "6",
"when": 1781886000000,
"tag": "0017_backup_retention",
"breakpoints": true
}
]
}
+5
View File
@@ -269,6 +269,11 @@ export const siteConfig = sqliteTable("site_config", {
* screen; the encryption key (BACKUP_KEY) stays an env/Komodo secret and is NEVER stored
* here (a key must not live in the DB it backs up). See wiki/concepts/backup-recovery.md. */
backupTargetDir: text("backup_target_dir"),
/** Backup retention (admin-tunable policy, not env). Keep this many newest backups always.
* null ⇒ code default (7). See wiki/concepts/backup-recovery.md. */
backupKeepLast: integer("backup_keep_last"),
/** Beyond keepLast, keep one backup per day for this many days. null ⇒ code default (30). */
backupKeepDailyDays: integer("backup_keep_daily_days"),
updatedAt: text("updated_at")
.notNull()
.default(sql`(current_timestamp)`),