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 }