quality / changes (push) Successful in 6s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 28s
quality / web (push) Successful in 57s
quality / api (push) Successful in 1m35s
quality / linux-agent-smoke (push) Successful in 24s
quality / docker-check (push) Failing after 3m20s
CD / quality (push) Failing after 6m56s
CD / publish (push) Skipped
42 lines
2.1 KiB
JavaScript
42 lines
2.1 KiB
JavaScript
import { createRequire } from 'node:module'
|
|
import { resolve, dirname } from 'node:path'
|
|
import { existsSync, mkdirSync, renameSync, rmSync, realpathSync } from 'node:fs'
|
|
|
|
// Resolve through the production workspace package; pnpm isolates transitive deps.
|
|
const require = createRequire(realpathSync(resolve('node_modules/@evofw/db/package.json')))
|
|
const Database = require('better-sqlite3')
|
|
const [mode, input, output, offline] = process.argv.slice(2)
|
|
if (!['backup', 'restore', 'verify'].includes(mode) || !input || (mode !== 'verify' && !output)) {
|
|
throw new Error('Usage: sqlite-backup.mjs backup|verify|restore INPUT [OUTPUT] [--offline]')
|
|
}
|
|
if (mode === 'restore' && offline !== '--offline') throw new Error('Restore requires a stopped volume and --offline')
|
|
if (output && resolve(input) === resolve(output)) throw new Error('Input and output must differ')
|
|
if (mode === 'backup' && existsSync(output)) throw new Error('Backup destination already exists')
|
|
const source = new Database(input, { readonly: true, fileMustExist: true })
|
|
let temporary
|
|
try {
|
|
if (source.pragma('integrity_check', { simple: true }) !== 'ok') throw new Error('Source integrity_check failed')
|
|
if (mode !== 'verify') {
|
|
mkdirSync(dirname(output), { recursive: true })
|
|
temporary = `${output}.partial-${process.pid}`
|
|
// SQLite online backup includes committed WAL pages while the API runs.
|
|
await source.backup(temporary)
|
|
const check = new Database(temporary, { readonly: true, fileMustExist: true })
|
|
try {
|
|
if (check.pragma('integrity_check', { simple: true }) !== 'ok') throw new Error('Backup integrity_check failed')
|
|
if (check.pragma('foreign_key_check').length) throw new Error('Backup foreign_key_check failed')
|
|
} finally { check.close() }
|
|
if (mode === 'restore') {
|
|
// Only safe after the caller has excluded every process using this volume.
|
|
rmSync(`${output}-wal`, { force: true })
|
|
rmSync(`${output}-shm`, { force: true })
|
|
}
|
|
renameSync(temporary, output)
|
|
temporary = undefined
|
|
}
|
|
console.log(JSON.stringify({ mode, input, output, integrity: 'ok' }))
|
|
} finally {
|
|
source.close()
|
|
if (temporary) rmSync(temporary, { force: true })
|
|
}
|