SPB Git
2.0 KB · 22 lines typescript
1/** Backup → restore into a throwaway database → compare row counts and content hashes → drop the temp DB. Proves recoverability without touching production.2 *  Requires DATABASE_URL_ADMIN (role that can CREATE DATABASE). */3import "./_env";4import postgres from "postgres";5import { runBackup, restoreInto } from "../src/lib/jobs/backup";6const admin = process.env.DATABASE_URL_ADMIN; if (!admin) { console.error("DATABASE_URL_ADMIN required"); process.exit(1); }7const pass = process.env.BACKUP_PASSPHRASE!; const tmpName = `tools_restore_drill_${Date.now()}`;8const a = postgres(admin, { max: 1, onnotice: () => {} });9const counts = async (url: string) => { const s = postgres(url, { max: 1, onnotice: () => {} }); const r = await s`SELECT (SELECT count(*)::int FROM tools) AS tools, (SELECT count(*)::int FROM tool_sources) AS sources, (SELECT count(*)::int FROM audit_log) AS audit, (SELECT count(*)::int FROM owner_notes) AS notes, (SELECT md5(string_agg(content_hash, ',' ORDER BY slug)) FROM tools) AS hash`; await s.end(); return r[0]; };10const t0 = Date.now();11const b = await runBackup(); console.log("backup:", b.file, b.bytes, "bytes");12await a.unsafe(`CREATE DATABASE "${tmpName}"`);13const tmpUrl = admin.replace(/\/[^/?]+(\?|$)/, `/${tmpName}$1`);14try {15  const t = postgres(tmpUrl, { max: 1, onnotice: () => {} }); await t`CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent;`.simple(); try { await t`CREATE EXTENSION IF NOT EXISTS vector`; } catch {} await t.end();16  await restoreInto(b.file, tmpUrl, pass);17  const [src, dst] = await Promise.all([counts(admin), counts(tmpUrl)]);18  const ok = JSON.stringify(src) === JSON.stringify(dst);19  console.log(JSON.stringify({ source: src, restored: dst, identical: ok, seconds: Math.round((Date.now() - t0) / 1000) }, null, 2));20  if (!ok) process.exitCode = 1;21} finally { await a.unsafe(`DROP DATABASE IF EXISTS "${tmpName}" WITH (FORCE)`); await a.end(); }22
Files · d0878ed8ec6f375df09d3e415bf35d941ff62d79
Menu

Theme