Files
gold_land_server/scripts/sqlite-restore.mjs
T
safavimohadeseh78andCursor dfb20ca92c
Backend CI / type-check, build, tests (push) Successful in 1m0s
feat: prepare six-lesson trial backend with reliable rewards and operations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 14:35:42 +03:30

110 lines
2.9 KiB
JavaScript

/**
* Restore backup to a target path (service must be stopped).
* Usage: node scripts/sqlite-restore.mjs <backup.db> <target.db>
*/
import Database from 'better-sqlite3'
import {
copyFileSync,
existsSync,
mkdirSync,
renameSync,
rmSync,
statSync,
} from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { validateAppSqliteFile } from './sqlite-app-schema.mjs'
const backupPath = resolve(process.argv[2] ?? '')
const targetPath = resolve(process.argv[3] ?? '')
if (!backupPath || !targetPath) {
console.error('Usage: node scripts/sqlite-restore.mjs <backup.db> <target.db>')
process.exit(1)
}
if (!existsSync(backupPath)) {
console.error(`Backup not found: ${backupPath}`)
process.exit(1)
}
const activeDb = resolve(process.env.DB_PATH ?? join(process.cwd(), 'data', 'app.db'))
const targetExists = existsSync(targetPath)
if (targetPath === activeDb) {
console.error('Restore onto active DB_PATH is not allowed while it is configured as the live database.')
console.error('Stop the backend service, point DB_PATH elsewhere, or restore to a temp file first.')
process.exit(1)
}
if (targetExists && process.env.RESTORE_ACK_STOPPED !== 'YES') {
console.error(
'Target database file already exists. Stop the backend service and set RESTORE_ACK_STOPPED=YES to continue.',
)
process.exit(1)
}
const schema = validateAppSqliteFile(backupPath)
if (!schema.ok) {
console.error(`Backup rejected: ${schema.reason}`)
process.exit(1)
}
const staging = `${targetPath}.restore-staging`
const stagingWal = `${staging}-wal`
const stagingShm = `${staging}-shm`
function cleanupStaging() {
for (const p of [staging, stagingWal, stagingShm]) {
try {
if (existsSync(p)) rmSync(p)
} catch {
// ignore
}
}
}
cleanupStaging()
try {
copyFileSync(backupPath, staging)
const stagedCheck = validateAppSqliteFile(staging)
if (!stagedCheck.ok) {
console.error(`Staging validation failed: ${stagedCheck.reason}`)
cleanupStaging()
process.exit(1)
}
const verify = new Database(staging, { readonly: true })
try {
const integrity = verify.pragma('integrity_check', { simple: true })
if (integrity !== 'ok') {
console.error(`Staging integrity check failed: ${integrity}`)
cleanupStaging()
process.exit(1)
}
} finally {
verify.close()
}
for (const sidecar of [`${targetPath}-wal`, `${targetPath}-shm`]) {
try {
if (existsSync(sidecar)) rmSync(sidecar)
} catch {
// ignore
}
}
if (targetExists) {
const backupOfTarget = `${targetPath}.pre-restore-${Date.now()}.bak`
renameSync(targetPath, backupOfTarget)
console.log(`Existing target moved to ${backupOfTarget}`)
}
renameSync(staging, targetPath)
console.log(`Restored ${backupPath} -> ${targetPath}`)
} catch (error) {
cleanupStaging()
console.error(error instanceof Error ? error.message : error)
process.exit(1)
}