// backup.js — simple daily rotating backup of the shared data folder (fellakte.db + files/). // No external dependencies: plain folder copies under /backups//, // pruned to the most recent KEEP_BACKUPS. Runs once automatically per day (on app start) // and can also be triggered manually from Settings. "use strict"; const fs = require("fs"); const path = require("path"); const KEEP_BACKUPS = 30; function pad(n) { return String(n).padStart(2, "0"); } function todayStamp() { const d = new Date(); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } function backupsDir(dataFolder) { return path.join(dataFolder, "backups"); } function listBackups(dataFolder) { const dir = backupsDir(dataFolder); if (!fs.existsSync(dir)) return []; return fs .readdirSync(dir, { withFileTypes: true }) .filter((e) => e.isDirectory()) .map((e) => e.name) .sort(); } function pruneOldBackups(dataFolder) { const names = listBackups(dataFolder); if (names.length <= KEEP_BACKUPS) return; for (const name of names.slice(0, names.length - KEEP_BACKUPS)) { fs.rmSync(path.join(backupsDir(dataFolder), name), { recursive: true, force: true }); } } function copySnapshot(dataFolder, dest) { fs.mkdirSync(dest, { recursive: true }); const dbFile = path.join(dataFolder, "fellakte.db"); if (fs.existsSync(dbFile)) fs.copyFileSync(dbFile, path.join(dest, "fellakte.db")); const filesDir = path.join(dataFolder, "files"); if (fs.existsSync(filesDir)) fs.cpSync(filesDir, path.join(dest, "files"), { recursive: true }); } // Manual/"Jetzt sichern" backup. If today's daily backup already exists, this makes an // additional time-stamped one instead of silently doing nothing. function runBackupNow(dataFolder) { const stamp = todayStamp(); let dest = path.join(backupsDir(dataFolder), stamp); if (fs.existsSync(dest)) { const d = new Date(); dest = `${dest}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`; } copySnapshot(dataFolder, dest); pruneOldBackups(dataFolder); return dest; } // Safe to call on every app start — no-ops if today's backup already exists. function runDailyBackupIfNeeded(dataFolder) { try { const dest = path.join(backupsDir(dataFolder), todayStamp()); if (fs.existsSync(dest)) return { ran: false }; copySnapshot(dataFolder, dest); pruneOldBackups(dataFolder); return { ran: true, dest }; } catch (e) { console.error("Automatisches Backup fehlgeschlagen:", e); return { ran: false, error: String((e && e.message) || e) }; } } function backupInfo(dataFolder) { const names = listBackups(dataFolder); return { count: names.length, last: names.length ? names[names.length - 1] : null }; } // Restores fellakte.db + files/ from a named backup, overwriting the live data folder. // Takes a fresh safety-net backup of the current (about-to-be-overwritten) state first, // so a restore is itself always undoable. Caller is responsible for restarting the app // afterwards — the in-memory database must be reloaded from the restored file. function restoreBackup(dataFolder, backupName) { const src = path.join(backupsDir(dataFolder), backupName); if (!fs.existsSync(src)) throw new Error("Sicherung nicht gefunden: " + backupName); runBackupNow(dataFolder); const dbFile = path.join(dataFolder, "fellakte.db"); const srcDb = path.join(src, "fellakte.db"); if (fs.existsSync(srcDb)) fs.copyFileSync(srcDb, dbFile); const filesDir = path.join(dataFolder, "files"); fs.rmSync(filesDir, { recursive: true, force: true }); const srcFiles = path.join(src, "files"); if (fs.existsSync(srcFiles)) fs.cpSync(srcFiles, filesDir, { recursive: true }); else fs.mkdirSync(filesDir, { recursive: true }); } module.exports = { runBackupNow, runDailyBackupIfNeeded, restoreBackup, listBackups, backupInfo, backupsDir };