new raceday and backup

This commit is contained in:
larssand
2026-09-01 07:07:23 +02:00
parent c2bfa97d30
commit f561dd8feb
21 changed files with 1042 additions and 60 deletions

84
scripts/restore-backup.js Normal file
View File

@@ -0,0 +1,84 @@
const fs = require("fs");
const path = require("path");
const Database = require("better-sqlite3");
const root = path.resolve(__dirname, "..");
const dataDir = process.env.RC_TIMING_DATA_DIR ? path.resolve(process.env.RC_TIMING_DATA_DIR) : path.join(root, "data");
const backupDir = path.join(dataDir, "backups");
const dbPath = path.join(dataDir, "rc_timing.sqlite");
const pidFile = process.env.RC_TIMING_PID_FILE || path.join(dataDir, "server.pid");
const fileName = String(process.argv[2] || "");
const confirmed = process.argv.includes("--confirm");
function fail(message) {
console.error(`[restore] ${message}`);
process.exit(1);
}
function isProcessRunning(pid) {
if (!Number.isInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch (error) {
return error?.code === "EPERM";
}
}
async function main() {
if (!fileName || path.basename(fileName) !== fileName || !/^[a-zA-Z0-9_.-]+\.sqlite$/.test(fileName)) {
fail("Usage: npm run backup:restore -- <backup-file.sqlite> --confirm");
}
if (!confirmed) fail("Add --confirm after the backup file to confirm the restore.");
if (fs.existsSync(pidFile)) {
const pid = Number(fs.readFileSync(pidFile, "utf8").trim());
if (isProcessRunning(pid)) fail(`RC RaceCore is running with PID ${pid}. Run npm stop first.`);
}
const sourcePath = path.join(backupDir, fileName);
if (!fs.existsSync(sourcePath)) fail(`Backup not found: ${sourcePath}`);
const sourceDb = new Database(sourcePath, { readonly: true, fileMustExist: true });
try {
const quickCheck = sourceDb.pragma("quick_check", { simple: true });
if (quickCheck !== "ok") fail(`Backup failed SQLite quick_check: ${quickCheck}`);
const state = sourceDb.prepare("SELECT state_json FROM app_state WHERE id = 1").get();
if (!state?.state_json) fail("Backup does not contain RC RaceCore application state.");
JSON.parse(state.state_json);
} finally {
sourceDb.close();
}
fs.mkdirSync(backupDir, { recursive: true });
let safetyPath = "";
if (fs.existsSync(dbPath)) {
const currentDb = new Database(dbPath);
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
safetyPath = path.join(backupDir, `${timestamp}__pre_restore__current.sqlite`);
try {
currentDb.pragma("wal_checkpoint(TRUNCATE)");
await currentDb.backup(safetyPath);
} finally {
currentDb.close();
}
fs.writeFileSync(safetyPath.replace(/\.sqlite$/, ".json"), JSON.stringify({
fileName: path.basename(safetyPath),
createdAt: new Date().toISOString(),
reason: "pre_restore",
sessionId: "",
eventId: "",
}, null, 2));
}
const temporaryPath = `${dbPath}.restore.tmp`;
fs.copyFileSync(sourcePath, temporaryPath);
fs.rmSync(dbPath, { force: true });
fs.renameSync(temporaryPath, dbPath);
fs.rmSync(`${dbPath}-wal`, { force: true });
fs.rmSync(`${dbPath}-shm`, { force: true });
console.log(`[restore] Restored: ${fileName}`);
if (safetyPath) console.log(`[restore] Previous database backup: ${safetyPath}`);
console.log("[restore] Start RC RaceCore with: npm start");
}
main().catch((error) => fail(error instanceof Error ? error.message : String(error)));