dorfteich/apps/backup/src/scheduler.ts
Claude Fable 5 8dbff86537
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m9s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m47s
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 5m25s
CI / Import/export fidelity gate (push) Successful in 45s
Add backup sidecar: nightly dump, volume archive, prune, status, restore (#83)
New apps/backup service (ADR 0015): nightly pg_dump -Fc plus one tar of the
uploads/plugins volumes as a consistent restore set on a new backups volume,
retention prune that never removes the newest complete set, atomic
status.json for the readiness/admin consumers (#85/#86), and a failure mail
sent directly via nodemailer (the api may be the broken part) with de/en
texts in the shared mails catalog. BACKUP_RUN_ONCE=1 gives the on-demand
path; deploy/backup/restore.sh automates the documented restore runbook.
The pure secret-store helpers moved to @dorfteich/shared so the sidecar
resolves the wizard-written SMTP relay exactly like the api.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-11 17:29:15 +02:00

33 lines
1.1 KiB
TypeScript

/**
* Daily scheduling without a cron dependency: one `setTimeout` to the next
* HH:MM occurrence (container-local time via TZ), re-armed after every run.
* A run that crosses midnight simply shifts the next one — backups are
* hours apart, drift by seconds is irrelevant.
*/
/** Milliseconds from `now` to the next local-time occurrence of `time` (HH:MM). */
export function msUntilNext(now: Date, time: string): number {
const [hours = 0, minutes = 0] = time.split(':').map(Number);
const next = new Date(now);
next.setHours(hours, minutes, 0, 0);
if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
return next.getTime() - now.getTime();
}
export function scheduleDaily(
time: string,
task: () => Promise<void>,
log: { info(details: object, message: string): void },
): { stop(): void } {
let timer: NodeJS.Timeout;
const arm = (): void => {
const delay = msUntilNext(new Date(), time);
log.info({ nextRunInMs: delay }, 'next backup run scheduled');
timer = setTimeout(() => {
void task().finally(arm);
}, delay);
};
arm();
return { stop: () => clearTimeout(timer) };
}