/** * 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 };