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
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
33 lines
1.1 KiB
TypeScript
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) };
|
|
}
|