fix(backup): persist last-success/error status; wall-clock-based schedule
BackupService tracked last-success/last-error as plain in-process fields and scheduled the daily backup via setInterval measured from process start — so any server restart (deploy/crash/OOM/reboot, routine under `restart: always`) silently reset the admin UI to "last successful backup: Never" and drifted the actual cadence, independent of whether backups were writing correctly to disk (they were — a real field incident at park-buzi showed 7 valid rotating backups on disk with the status stuck on "Never"). Persist last-success/error to new site_config columns (migration 0025) and add BackupService.isDue(), computed from the persisted timestamp instead of process uptime; server.ts now polls every 15 min and lets isDue() gate the actual run. No API/UI contract change. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
@@ -288,6 +288,20 @@ export const siteConfig = sqliteTable("site_config", {
|
||||
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"),
|
||||
/** ISO timestamp of the last backup that actually completed successfully. Persisted here
|
||||
* (not just in-process memory) so the admin UI's "last successful backup" survives a
|
||||
* server restart — before this column existed, a restart silently reset that status to
|
||||
* "Never" even with valid backups already on disk. null = no successful run recorded yet.
|
||||
* See wiki/concepts/backup-recovery.md. */
|
||||
backupLastSuccessAt: text("backup_last_success_at"),
|
||||
/** JSON-encoded { path, bytes, prunedFiles } of the last successful run, for the same
|
||||
* restart-durability reason as backupLastSuccessAt. null = none recorded yet. */
|
||||
backupLastResultJson: text("backup_last_result_json"),
|
||||
/** ISO timestamp of the last FAILED scheduled/manual backup attempt, persisted for the same
|
||||
* reason. null = no failure recorded (or none since the last success). */
|
||||
backupLastErrorAt: text("backup_last_error_at"),
|
||||
/** Error message of the last failed attempt. Cleared (set null) on the next success. */
|
||||
backupLastError: text("backup_last_error"),
|
||||
updatedAt: text("updated_at")
|
||||
.notNull()
|
||||
.default(sql`(current_timestamp)`),
|
||||
|
||||
Reference in New Issue
Block a user