54 lines
1.6 KiB
JavaScript
54 lines
1.6 KiB
JavaScript
/**
|
|||
|
|
* WAL-safe SQLite backup via better-sqlite3 backup API (not a raw file copy).
|
||
|
|
* Usage: DB_PATH=./data/app.db BACKUP_DIR=./backups node scripts/sqlite-backup.mjs
|
||
|
|
*/
|
||
|
|
import Database from 'better-sqlite3'
|
||
|
|
import { mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from 'node:fs'
|
||
|
|
import { join } from 'node:path'
|
||
|
|
|
||
|
|
const srcPath = process.env.DB_PATH ?? join(process.cwd(), 'data', 'app.db')
|
||
|
|
const backupDir = process.env.BACKUP_DIR ?? join(process.cwd(), 'backups')
|
||
|
|
const retentionDays = Number(process.env.BACKUP_RETENTION_DAYS ?? '0')
|
||
|
|
|
||
|
|
if (!existsSync(srcPath)) {
|
||
|
|
console.error(`Source database not found: ${srcPath}`)
|
||
|
|
process.exit(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
mkdirSync(backupDir, { recursive: true })
|
||
|
|
|
||
|
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||
|
|
const destPath = join(backupDir, `app-${stamp}.db`)
|
||
|
|
|
||
|
|
const src = new Database(srcPath, { readonly: true })
|
||
|
|
try {
|
||
|
|
await src.backup(destPath)
|
||
|
|
} finally {
|
||
|
|
src.close()
|
||
|
|
}
|
||
|
|
|
||
|
|
const verify = new Database(destPath, { readonly: true })
|
||
|
|
try {
|
||
|
|
const integrity = verify.pragma('integrity_check', { simple: true })
|
||
|
|
if (integrity !== 'ok') {
|
||
|
|
console.error(`Backup integrity check failed: ${integrity}`)
|
||
|
|
process.exit(1)
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
verify.close()
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`Backup written: ${destPath}`)
|
||
|
|
|
||
|
|
if (retentionDays > 0) {
|
||
|
|
const cutoff = Date.now() - retentionDays * 86_400_000
|
||
|
|
for (const name of readdirSync(backupDir)) {
|
||
|
|
if (!name.startsWith('app-') || !name.endsWith('.db')) continue
|
||
|
|
const full = join(backupDir, name)
|
||
|
|
if (statSync(full).mtimeMs < cutoff) {
|
||
|
|
unlinkSync(full)
|
||
|
|
console.log(`Removed old backup: ${name}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|