import fs from 'node:fs/promises'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { DatabaseSync } from 'node:sqlite'; import { paths } from '../src/env.mjs'; export async function createBackup({ dataDir = paths.data, now = () => new Date(), retention = 14 } = {}) { const stamp = now().toISOString().slice(0, 10); const backupsDir = path.join(dataDir, 'backups'); const target = path.join(backupsDir, `maquina-pessoal-${stamp}`); const dbPath = dataDir === paths.data ? paths.db : path.join(dataDir, 'maquina-pessoal.db'); const authPath = dataDir === paths.data ? paths.whatsappAuth : path.join(dataDir, 'runtime', 'whatsapp-auth'); await fs.mkdir(target, { recursive: true }); await copyDatabaseBackup(dbPath, path.join(target, 'maquina-pessoal.db')); await fs.cp(authPath, path.join(target, 'whatsapp-auth'), { recursive: true }); await pruneBackups(backupsDir, retention); return { file: target }; } async function copyDatabaseBackup(source, destination) { try { const db = new DatabaseSync(source, { readOnly: true }); try { db.exec(`VACUUM INTO ${sqlString(destination)};`); return; } finally { db.close(); } } catch { await copyDatabaseFiles(source, destination); } } async function copyDatabaseFiles(source, destination) { await fs.copyFile(source, destination); for (const suffix of ['-wal', '-shm']) { try { await fs.copyFile(`${source}${suffix}`, `${destination}${suffix}`); } catch (error) { if (error?.code !== 'ENOENT') { throw error; } } } } function sqlString(value) { return `'${String(value).replaceAll("'", "''")}'`; } async function pruneBackups(backupsDir, retention) { const keep = Math.max(0, retention); const entries = await fs.readdir(backupsDir, { withFileTypes: true }); const backupDirs = entries .filter((entry) => entry.isDirectory() && entry.name.startsWith('maquina-pessoal-')) .map((entry) => entry.name) .sort(); for (const name of backupDirs.slice(0, Math.max(0, backupDirs.length - keep))) { await fs.rm(path.join(backupsDir, name), { recursive: true, force: true }); } } export function isCliEntryPoint(moduleUrl = import.meta.url, argvPath = process.argv[1]) { return Boolean(argvPath) && moduleUrl === pathToFileURL(argvPath).href; } if (isCliEntryPoint()) { createBackup().then((result) => console.log(`backup criado: ${result.file}`)).catch((error) => { console.error(`backup falhou: ${error.message}`); process.exit(1); }); }