backups: a schedule, a location, and a timer that survives a restart
Restore goes. What it would read from was never decided, and a disabled button with a tooltip explaining it is not built is worse than no button. In its place, backups. `cron` was already a dependency and used nowhere, so the schedule is a real cron expression — a backup wants to happen at an HOUR, and an interval counted from whenever somebody pressed save drifts across the clock until it runs mid-afternoon. Cron alone is not enough, and this is the part worth writing down: it does not run a job it was down for. A daily backup at three on a machine that restarts overnight is skipped silently — the schedule is still there, the next fire is still in the future, and a month later there is one file. So `lastBackupAt` is in the database, and both the startup and a fifteen-minute tick compare it against the schedule's own period, computed from two successive fires. A backup that should already have happened happens now. The job handle is module-level and stopped before a new one is made. Two saves leaving two jobs is a bug that surfaces as backups arriving in pairs, weeks later, with nothing connecting it to a settings form. Back up now exists because a scheduled job nobody has ever run by hand is a guess — it is how you learn the location is writable and pg_dump can reach the databases without waiting until three to find out it cannot. The list underneath is a readdir of the location rather than a record of what should be there: a schedule that has been failing quietly looks exactly like a working one until you count the files. pg_dump per database, not pg_dumpall — a cluster dump would also carry Officer's own, which is not this plugin's to copy around. And a failed run still records the timestamp, or one undumpable database would have the catch-up tick retrying every fifteen minutes for the rest of the week. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,30 @@ export const fofosAdminSettings = pgTable('fofos_admin_settings', {
|
||||
dbProduction: text('db_production'),
|
||||
dbDevelopment: text('db_development'),
|
||||
|
||||
/**
|
||||
* Backups: when, and where they land.
|
||||
*
|
||||
* A cron expression, because a backup wants to happen at an HOUR — "every day at three" — and an
|
||||
* interval measured from whenever somebody last pressed save drifts across the clock until it is
|
||||
* running at two in the afternoon. The panel offers the handful of schedules anybody wants, and the
|
||||
* expression is validated on the way in so a malformed one cannot be stored and then fail at a time
|
||||
* nobody is watching.
|
||||
*
|
||||
* Null in either means backups are not set up. Both are needed, so one without the other is not a
|
||||
* half-configured state to interpret — it is off.
|
||||
*/
|
||||
backupCron: text('backup_cron'),
|
||||
backupDir: text('backup_dir'),
|
||||
|
||||
/**
|
||||
* When the last one finished, which is what makes the schedule survive a restart.
|
||||
*
|
||||
* The timer does not measure elapsed time from when the process started; it asks whether a backup is
|
||||
* DUE, against this. A daily backup on a machine that restarts every few hours would otherwise never
|
||||
* fire once — the interval would be reset before it ever elapsed, and nothing would look wrong.
|
||||
*/
|
||||
lastBackupAt: timestamp('last_backup_at', { withTimezone: true }),
|
||||
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user