104 lines
3.8 KiB
JavaScript
104 lines
3.8 KiB
JavaScript
// backup.js — simple daily rotating backup of the shared data folder (fellakte.db + files/).
|
|
// No external dependencies: plain folder copies under <dataFolder>/backups/<YYYY-MM-DD>/,
|
|
// 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 };
|