SPB Git
2.4 KB · 19 lines typescript
1import "./_env";2import { execFile } from "node:child_process";3import { promisify } from "node:util";4import { promises as fs } from "node:fs";5import { join } from "node:path";6import postgres from "postgres";7const run = promisify(execFile);8/** Restore drill: decrypts the latest backup, restores the dump into a scratch database, compares row counts with production, then drops the scratch database. */9const dir = process.env.BACKUP_DIR!; const pass = process.env.BACKUP_PASSPHRASE!; const pgBin = process.env.PG_BIN ?? "/opt/homebrew/opt/postgresql@17/bin";10const files = (await fs.readdir(dir)).filter((f) => /^wp-\d+T\d+\.dump\.enc$/.test(f)).sort(); const latest = files[files.length - 1]; if (!latest) { console.error("no backup found in " + dir); process.exit(1); }11const tmp = join(dir, "restore-drill.dump"); await run("openssl", ["enc", "-d", "-aes-256-cbc", "-pbkdf2", "-in", join(dir, latest), "-out", tmp, "-pass", "env:WP_BK"], { env: { ...process.env, WP_BK: pass } });12const scratch = "wp_restore_drill"; const admin = postgres({ path: "/tmp/.s.PGSQL.5432", database: "postgres", username: process.env.USER, max: 1, onnotice: () => {} });13await admin.unsafe(`DROP DATABASE IF EXISTS ${scratch}`); await admin.unsafe(`CREATE DATABASE ${scratch}`); await admin.end();14await run(join(pgBin, "pg_restore"), ["--no-owner", "--no-privileges", "-d", `postgres://${process.env.USER}@/${scratch}?host=/tmp`, tmp]);15const a = postgres({ path: "/tmp/.s.PGSQL.5432", database: scratch, username: process.env.USER, max: 1, onnotice: () => {} }); const b = postgres(process.env.DATABASE_URL!, { max: 1, onnotice: () => {} });16let ok = true; for (const t of ["papers", "paper_versions", "files", "authors", "topics", "paper_chunks", "audit_log"]) { const [[x], [y]] = await Promise.all([a.unsafe(`SELECT count(*)::int AS n FROM ${t}`), b.unsafe(`SELECT count(*)::int AS n FROM ${t}`)]); const same = Math.abs(x.n - y.n) <= Math.max(2, y.n * 0.02); console.log(`${same ? "OK " : "DIFF"} ${t}: backup ${x.n} / live ${y.n}`); if (!same && t !== "audit_log" && t !== "paper_chunks") ok = false; }17await a.end(); await b.end(); const admin2 = postgres({ path: "/tmp/.s.PGSQL.5432", database: "postgres", username: process.env.USER, max: 1, onnotice: () => {} }); await admin2.unsafe(`DROP DATABASE ${scratch}`); await admin2.end(); await fs.unlink(tmp);18console.log(ok ? `restore drill OK (${latest})` : "restore drill FAILED"); process.exit(ok ? 0 : 1);19
Files · main
Menu

Theme