33 lines
1.3 KiB
JavaScript
33 lines
1.3 KiB
JavaScript
import { access, mkdir, stat } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl?.startsWith('file:')) {
|
|
throw new Error('DATABASE_URL must use a SQLite file: URL');
|
|
}
|
|
|
|
const source = path.resolve(databaseUrl.slice('file:'.length));
|
|
const backupDirectory = path.resolve(process.env.BACKUP_PATH || path.join(process.cwd(), 'data', 'backups'));
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const destination = path.join(backupDirectory, `schulos-sqlite-${timestamp}.db`);
|
|
|
|
await access(source);
|
|
await mkdir(backupDirectory, { recursive: true });
|
|
|
|
const result = spawnSync('sqlite3', [source, `.backup '${destination.replaceAll("'", "''")}'`], {
|
|
encoding: 'utf8',
|
|
});
|
|
|
|
if (result.error || result.status !== 0) {
|
|
throw result.error ?? new Error(result.stderr || 'SQLite online backup failed');
|
|
}
|
|
|
|
const integrity = spawnSync('sqlite3', [destination, 'PRAGMA integrity_check;'], { encoding: 'utf8' });
|
|
if (integrity.error || integrity.status !== 0 || integrity.stdout.trim() !== 'ok') {
|
|
throw integrity.error ?? new Error(`Backup integrity check failed: ${integrity.stdout || integrity.stderr}`);
|
|
}
|
|
|
|
const backupStat = await stat(destination);
|
|
console.log(JSON.stringify({ backup: destination, bytes: backupStat.size, integrity: 'ok' }));
|