#!/usr/bin/env node 'use strict'; // Targeted recovery snapshot, not a library backup. Caller must prove all writers // are stopped first. Never run this concurrently with the reader or scraper. const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto'); if (process.argv[2] !== '--writers-stopped') throw new Error('First verify that every writer is stopped, then pass --writers-stopped'); const root = path.resolve(__dirname, '..'), library = path.join(root, 'library'), data = path.join(root, 'data'); const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z'); const backup = path.join(data, 'scraper', 'backups', stamp + '-compose-cutover'); fs.mkdirSync(backup, { recursive: true }); const files = []; function copy(source, relative) { if (!fs.existsSync(source)) return; if (fs.statSync(source).isDirectory()) { for (const name of fs.readdirSync(source)) copy(path.join(source, name), path.join(relative, name)); return; } const target = path.join(backup, relative); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.copyFileSync(source, target); files.push({ path: relative, bytes: fs.statSync(source).size, sha256: crypto.createHash('sha256').update(fs.readFileSync(source)).digest('hex') }); } for (const name of ['db.json', 'scan-cache.json', 'catalog.json', 'collection-covers', 'runtime-reader.json']) copy(path.join(data, name), path.join('data', name)); for (const name of ['queue.json', 'runtime.json', 'daemon.lock', 'supervisor.lock']) copy(path.join(data, 'scraper', name), path.join('data', 'scraper', name)); for (const name of fs.readdirSync(library)) { copy(path.join(library, name, name.startsWith('.mcscraper-work-') ? 'job.json' : 'manga.json'), path.join('library', name, name.startsWith('.mcscraper-work-') ? 'job.json' : 'manga.json')); } fs.writeFileSync(path.join(backup, 'snapshot.json'), JSON.stringify({ created: new Date().toISOString(), kind: 'targeted-state-not-image-backup', files }, null, 2)); console.log(JSON.stringify({ backup, files: files.length, imageFilesCopied: false }));