feat(backup): admin-tunable retention + BACKUP_KEY as a Komodo secret
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:
+13
-3
@@ -234,6 +234,9 @@ export interface BackupStatus {
|
||||
configured: boolean;
|
||||
/** Admin-chosen target directory (null = not set). */
|
||||
targetDir: string | null;
|
||||
/** Admin-tuned retention (resolved value: DB or code default). */
|
||||
keepLast: number;
|
||||
keepDailyDays: number;
|
||||
/** Whether the env encryption key is present (a missing key is flagged distinctly). */
|
||||
keyPresent: boolean;
|
||||
running: boolean;
|
||||
@@ -247,9 +250,16 @@ export async function fetchBackupStatus(): Promise<BackupStatus> {
|
||||
return apiFetch("/api/backup/status");
|
||||
}
|
||||
|
||||
/** Set (or clear, with "") the admin-chosen target directory. Returns the new status. */
|
||||
export async function setBackupTarget(targetDir: string | null): Promise<BackupStatus> {
|
||||
return apiFetch("/api/backup/config", { method: "PUT", body: JSON.stringify({ targetDir }) });
|
||||
export interface BackupConfigPatch {
|
||||
/** "" clears the target. Omit a field to leave it unchanged; null resets retention to default. */
|
||||
targetDir?: string | null;
|
||||
keepLast?: number | null;
|
||||
keepDailyDays?: number | null;
|
||||
}
|
||||
|
||||
/** Update backup config (target dir and/or retention). Returns the new status. */
|
||||
export async function setBackupConfig(patch: BackupConfigPatch): Promise<BackupStatus> {
|
||||
return apiFetch("/api/backup/config", { method: "PUT", body: JSON.stringify(patch) });
|
||||
}
|
||||
|
||||
export interface TargetCheck {
|
||||
|
||||
Reference in New Issue
Block a user