49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
|
|
/**
|
||
|
|
* Produce a full disaster-recovery bundle (db.dump + every blob + manifest.json)
|
||
|
|
* to a local file. Same code path as the admin "Download full backup" button
|
||
|
|
* (`createFullBackupTar`), minus the HTTP layer — for headless/ops use and for
|
||
|
|
* rehearsing the restore runbook (docs/backup-restore-runbook.md).
|
||
|
|
*
|
||
|
|
* pnpm tsx scripts/create-full-backup.ts [outfile.tar]
|
||
|
|
*
|
||
|
|
* Defaults the output name to ./pn-crm-backup-<timestamp>.tar in the CWD.
|
||
|
|
*/
|
||
|
|
import 'dotenv/config';
|
||
|
|
|
||
|
|
import { copyFile } from 'node:fs/promises';
|
||
|
|
import path from 'node:path';
|
||
|
|
|
||
|
|
import { createFullBackupTar } from '@/lib/services/backup-export.service';
|
||
|
|
import { logger } from '@/lib/logger';
|
||
|
|
|
||
|
|
async function main(): Promise<void> {
|
||
|
|
const { tarPath, filename, manifest, cleanup } = await createFullBackupTar();
|
||
|
|
try {
|
||
|
|
const dest = path.resolve(process.argv[2] ?? filename);
|
||
|
|
await copyFile(tarPath, dest);
|
||
|
|
logger.info(
|
||
|
|
{
|
||
|
|
dest,
|
||
|
|
storageBackend: manifest.storageBackend,
|
||
|
|
dbDumpBytes: manifest.database.sizeBytes,
|
||
|
|
blobs: manifest.counts.blobs,
|
||
|
|
blobBytes: manifest.counts.blobBytes,
|
||
|
|
skipped: manifest.counts.skipped,
|
||
|
|
},
|
||
|
|
'Full backup written',
|
||
|
|
);
|
||
|
|
if (manifest.skipped.length) {
|
||
|
|
logger.warn({ skipped: manifest.skipped }, 'Some referenced blobs were missing in storage');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
await cleanup();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main()
|
||
|
|
.then(() => process.stdout.write('', () => process.exit(0)))
|
||
|
|
.catch((err) => {
|
||
|
|
logger.error({ err }, 'Full backup failed');
|
||
|
|
process.stderr.write('', () => process.exit(1));
|
||
|
|
});
|