bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

restore-data.js (3905B)


      1 const fs = require("fs");
      2 const path = require("path");
      3 
      4 const ROOT_DIR = path.join(__dirname, "..");
      5 const DATA_DIR = path.resolve(process.env.DATA_DIR || path.join(ROOT_DIR, "data"));
      6 const BACKUP_ROOT = path.resolve(process.env.BACKUP_DIR || path.join(DATA_DIR, "backups"));
      7 const TARGETS = ["users.json", "posts.json", "reports.json", "mod-log.json", "sessions.json", "uploads"];
      8 
      9 function pad2(value) {
     10   return String(value).padStart(2, "0");
     11 }
     12 
     13 function timestamp() {
     14   const d = new Date();
     15   return [
     16     d.getFullYear(),
     17     pad2(d.getMonth() + 1),
     18     pad2(d.getDate()),
     19     "-",
     20     pad2(d.getHours()),
     21     pad2(d.getMinutes()),
     22     pad2(d.getSeconds())
     23   ].join("");
     24 }
     25 
     26 function parseArgs(argv) {
     27   const out = { yes: false, list: false, name: "" };
     28   for (let i = 0; i < argv.length; i += 1) {
     29     const token = argv[i];
     30     if (token === "--yes") out.yes = true;
     31     else if (token === "--list") out.list = true;
     32     else if (token === "--name") out.name = String(argv[i + 1] || "");
     33   }
     34   return out;
     35 }
     36 
     37 function listBackups() {
     38   if (!fs.existsSync(BACKUP_ROOT)) return [];
     39   return fs
     40     .readdirSync(BACKUP_ROOT, { withFileTypes: true })
     41     .filter((e) => e.isDirectory())
     42     .map((e) => {
     43       const full = path.join(BACKUP_ROOT, e.name);
     44       const stat = fs.statSync(full);
     45       return { name: e.name, full, mtimeMs: Number(stat.mtimeMs || 0) };
     46     })
     47     .sort((a, b) => b.mtimeMs - a.mtimeMs);
     48 }
     49 
     50 function createPreRestoreSnapshot() {
     51   fs.mkdirSync(DATA_DIR, { recursive: true });
     52   fs.mkdirSync(BACKUP_ROOT, { recursive: true });
     53   const name = `pre-restore-${timestamp()}`;
     54   const destDir = path.join(BACKUP_ROOT, name);
     55   fs.mkdirSync(destDir, { recursive: false });
     56   const copied = [];
     57   for (const rel of TARGETS) {
     58     const src = path.join(DATA_DIR, rel);
     59     if (!fs.existsSync(src)) continue;
     60     fs.cpSync(src, path.join(destDir, rel), { recursive: true, force: true });
     61     copied.push(rel);
     62   }
     63   fs.writeFileSync(
     64     path.join(destDir, "manifest.json"),
     65     JSON.stringify(
     66       {
     67         version: 1,
     68         createdAt: Date.now(),
     69         createdAtIso: new Date().toISOString(),
     70         kind: "pre-restore",
     71         copied
     72       },
     73       null,
     74       2
     75     ) + "\n",
     76     "utf8"
     77   );
     78   return destDir;
     79 }
     80 
     81 function resetTarget(dest) {
     82   if (!fs.existsSync(dest)) return;
     83   fs.rmSync(dest, { recursive: true, force: true });
     84 }
     85 
     86 function main() {
     87   const args = parseArgs(process.argv.slice(2));
     88   const backups = listBackups();
     89 
     90   if (args.list) {
     91     if (!backups.length) {
     92       console.log("No backups found.");
     93       return;
     94     }
     95     for (const b of backups) console.log(b.name);
     96     return;
     97   }
     98 
     99   if (!backups.length) {
    100     console.error(`No backups found in ${BACKUP_ROOT}`);
    101     process.exit(1);
    102   }
    103 
    104   const selected = args.name ? backups.find((b) => b.name === args.name) : backups[0];
    105   if (!selected) {
    106     console.error(`Backup not found: ${args.name}`);
    107     process.exit(1);
    108   }
    109 
    110   if (!args.yes) {
    111     console.error("Restore requires --yes (it overwrites current runtime data).");
    112     console.error(`Try: node scripts/restore-data.js --name ${selected.name} --yes`);
    113     process.exit(1);
    114   }
    115 
    116   fs.mkdirSync(DATA_DIR, { recursive: true });
    117   const preRestorePath = createPreRestoreSnapshot();
    118 
    119   const restored = [];
    120   for (const rel of TARGETS) {
    121     const from = path.join(selected.full, rel);
    122     const to = path.join(DATA_DIR, rel);
    123     resetTarget(to);
    124     if (!fs.existsSync(from)) continue;
    125     fs.cpSync(from, to, { recursive: true, force: true });
    126     restored.push(rel);
    127   }
    128 
    129   console.log(`Restored backup: ${selected.full}`);
    130   console.log(`Pre-restore snapshot: ${preRestorePath}`);
    131   if (restored.length) console.log(`Restored targets: ${restored.join(", ")}`);
    132   else console.log("Selected backup had no data targets.");
    133 }
    134 
    135 try {
    136   main();
    137 } catch (e) {
    138   console.error("Restore failed:", e?.message || e);
    139   process.exit(1);
    140 }