219 Zeilen
6.0 KiB
JavaScript
219 Zeilen
6.0 KiB
JavaScript
/**
|
|
* TASKMATE - Backup System
|
|
* ========================
|
|
* Automatische Datenbank-Backups
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const logger = require('./logger');
|
|
const { encryptFile, decryptFile, secureDelete } = require('./encryption');
|
|
|
|
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data');
|
|
const BACKUP_DIR = process.env.BACKUP_DIR || path.join(__dirname, '..', 'backups');
|
|
const DB_FILE = path.join(DATA_DIR, 'taskmate.db');
|
|
|
|
// Backup-Verzeichnis erstellen falls nicht vorhanden
|
|
if (!fs.existsSync(BACKUP_DIR)) {
|
|
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
}
|
|
|
|
|
|
/**
|
|
* Backup erstellen (mit einfacher Verschlüsselung)
|
|
*/
|
|
function createBackup() {
|
|
try {
|
|
if (!fs.existsSync(DB_FILE)) {
|
|
logger.warn('Keine Datenbank zum Sichern gefunden');
|
|
return null;
|
|
}
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const backupName = `backup_${timestamp}.db`;
|
|
const encryptedName = `backup_${timestamp}.db.enc`;
|
|
const backupPath = path.join(BACKUP_DIR, backupName);
|
|
const encryptedPath = path.join(BACKUP_DIR, encryptedName);
|
|
|
|
// 1. Normales Backup erstellen (für Kompatibilität)
|
|
fs.copyFileSync(DB_FILE, backupPath);
|
|
|
|
// 2. Verschlüsseltes Backup erstellen (zusätzlich)
|
|
if (process.env.ENCRYPTION_KEY) {
|
|
try {
|
|
if (encryptFile(DB_FILE, encryptedPath)) {
|
|
logger.info(`Verschlüsseltes Backup erstellt: ${encryptedName}`);
|
|
} else {
|
|
logger.warn('Verschlüsselung fehlgeschlagen, nur normales Backup erstellt');
|
|
}
|
|
} catch (encError) {
|
|
logger.warn('Verschlüsselung fehlgeschlagen, nur normales Backup erstellt');
|
|
}
|
|
}
|
|
|
|
// 3. WAL-Datei sichern falls vorhanden
|
|
const walFile = DB_FILE + '-wal';
|
|
if (fs.existsSync(walFile)) {
|
|
fs.copyFileSync(walFile, backupPath + '-wal');
|
|
}
|
|
|
|
logger.info(`Backup erstellt: ${backupName}`);
|
|
|
|
// Alte Backups aufräumen (behalte nur die letzten 30)
|
|
cleanupOldBackups(30);
|
|
|
|
return backupPath;
|
|
} catch (error) {
|
|
logger.error('Backup-Fehler:', { error: error.message });
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alte Backups löschen (verschlüsselte)
|
|
*/
|
|
function cleanupOldBackups(keepCount = 30) {
|
|
try {
|
|
const files = fs.readdirSync(BACKUP_DIR)
|
|
.filter(f => f.startsWith('backup_') && f.endsWith('.db.enc'))
|
|
.sort()
|
|
.reverse();
|
|
|
|
const toDelete = files.slice(keepCount);
|
|
|
|
toDelete.forEach(file => {
|
|
const filePath = path.join(BACKUP_DIR, file);
|
|
secureDelete(filePath);
|
|
|
|
// Verschlüsselte WAL-Datei auch löschen falls vorhanden
|
|
const walPath = filePath + '-wal';
|
|
if (fs.existsSync(walPath)) {
|
|
secureDelete(walPath);
|
|
}
|
|
|
|
logger.info(`Altes Backup sicher gelöscht: ${file}`);
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Aufräumen alter Backups:', { error: error.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Backup wiederherstellen (entschlüsselt)
|
|
*/
|
|
function restoreBackup(backupName) {
|
|
try {
|
|
const encryptedBackupPath = path.join(BACKUP_DIR, backupName);
|
|
|
|
if (!fs.existsSync(encryptedBackupPath)) {
|
|
throw new Error(`Backup nicht gefunden: ${backupName}`);
|
|
}
|
|
|
|
// Aktuelles DB verschlüsselt sichern bevor überschrieben wird
|
|
if (fs.existsSync(DB_FILE)) {
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const safetyBackupPath = DB_FILE + `.before-restore-${timestamp}.enc`;
|
|
if (!encryptFile(DB_FILE, safetyBackupPath)) {
|
|
logger.warn('Sicherheitsbackup vor Wiederherstellung fehlgeschlagen');
|
|
}
|
|
}
|
|
|
|
// Temporäre entschlüsselte Datei
|
|
const tempRestorePath = path.join(BACKUP_DIR, `temp_restore_${Date.now()}.db`);
|
|
|
|
// Backup entschlüsseln
|
|
if (!decryptFile(encryptedBackupPath, tempRestorePath)) {
|
|
throw new Error('Backup-Entschlüsselung fehlgeschlagen');
|
|
}
|
|
|
|
// Entschlüsselte DB kopieren
|
|
fs.copyFileSync(tempRestorePath, DB_FILE);
|
|
|
|
// WAL-Datei auch wiederherstellen falls vorhanden
|
|
const encryptedWalBackup = encryptedBackupPath + '-wal';
|
|
if (fs.existsSync(encryptedWalBackup)) {
|
|
const tempWalPath = tempRestorePath + '-wal';
|
|
if (decryptFile(encryptedWalBackup, tempWalPath)) {
|
|
fs.copyFileSync(tempWalPath, DB_FILE + '-wal');
|
|
secureDelete(tempWalPath);
|
|
}
|
|
}
|
|
|
|
// Temporäre entschlüsselte Dateien sicher löschen
|
|
secureDelete(tempRestorePath);
|
|
|
|
logger.info(`Verschlüsseltes Backup wiederhergestellt: ${backupName}`);
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('Restore-Fehler:', { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Liste aller verschlüsselten Backups
|
|
*/
|
|
function listBackups() {
|
|
try {
|
|
const files = fs.readdirSync(BACKUP_DIR)
|
|
.filter(f => f.startsWith('backup_') && f.endsWith('.db.enc'))
|
|
.map(f => {
|
|
const filePath = path.join(BACKUP_DIR, f);
|
|
const stats = fs.statSync(filePath);
|
|
return {
|
|
name: f,
|
|
size: stats.size,
|
|
created: stats.birthtime
|
|
};
|
|
})
|
|
.sort((a, b) => b.created - a.created);
|
|
|
|
return files;
|
|
} catch (error) {
|
|
logger.error('Fehler beim Auflisten der Backups:', { error: error.message });
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Backup-Scheduler starten
|
|
*/
|
|
let schedulerInterval = null;
|
|
|
|
function startScheduler() {
|
|
const intervalHours = parseInt(process.env.BACKUP_INTERVAL_HOURS) || 24;
|
|
const intervalMs = intervalHours * 60 * 60 * 1000;
|
|
|
|
// Erstes Backup nach 1 Minute
|
|
setTimeout(() => {
|
|
createBackup();
|
|
}, 60 * 1000);
|
|
|
|
// Regelmäßige Backups
|
|
schedulerInterval = setInterval(() => {
|
|
createBackup();
|
|
}, intervalMs);
|
|
|
|
logger.info(`Backup-Scheduler gestartet (alle ${intervalHours} Stunden)`);
|
|
}
|
|
|
|
/**
|
|
* Backup-Scheduler stoppen
|
|
*/
|
|
function stopScheduler() {
|
|
if (schedulerInterval) {
|
|
clearInterval(schedulerInterval);
|
|
schedulerInterval = null;
|
|
logger.info('Backup-Scheduler gestoppt');
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
createBackup,
|
|
restoreBackup,
|
|
listBackups,
|
|
startScheduler,
|
|
stopScheduler,
|
|
cleanupOldBackups
|
|
};
|