feat(backup): full DR bundle export + admin-configurable offsite destinations
Backend-agnostic disaster-recovery backup engine that runs on the current storage backend (no storage cutover required): - Full-bundle export: db.dump (pg_dump custom) + every storage blob + manifest.json with per-object SHA-256, streamed as a tar. Entry points: admin UI download, GET /api/v1/admin/backup/export, scripts/create-full-backup.ts. - Admin-configurable push destinations (backup_destinations table, migration 0091): SFTP/SSH, S3-compatible (reuses the minio client), and mounted path/NAS behind one transport interface (test/push/prune). Secrets AES-GCM at rest; API returns only *IsSet markers. - Opt-in per-destination AES-256 bundle encryption (scrypt KDF, streamed) + scripts/decrypt-backup.ts for restore. - Wired the previously-dead database-backup cron to runScheduledBackupPush (push to enabled destinations, prune to retention, alert super-admins on failure). Tests: 1608 unit/integration pass; tsc + lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,17 @@ export const maintenanceWorker = new Worker(
|
||||
await refreshRates();
|
||||
break;
|
||||
}
|
||||
case 'database-backup': {
|
||||
// Scheduled full-bundle backup pushed to every enabled destination.
|
||||
// No-op until an admin turns the schedule on AND enables a destination
|
||||
// (`backup_schedule` setting + `backup_destinations`). Replaces the
|
||||
// previous silent no-op (this case did not exist before).
|
||||
const { runScheduledBackupPush } =
|
||||
await import('@/lib/services/backup-destinations.service');
|
||||
const summary = await runScheduledBackupPush();
|
||||
logger.info(summary, 'Scheduled backup push complete');
|
||||
break;
|
||||
}
|
||||
case 'form-expiry-check': {
|
||||
const result = await db
|
||||
.update(formSubmissions)
|
||||
|
||||
Reference in New Issue
Block a user