/** * TASKMATE - Backup System * ======================== * Automatische Datenbank-Backups */ const fs = require('fs'); const path = require('path'); const logger = require('./logger'); 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 */ 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 backupPath = path.join(BACKUP_DIR, backupName); // Datenbank kopieren fs.copyFileSync(DB_FILE, backupPath); // WAL-Datei auch 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 */ function cleanupOldBackups(keepCount = 30) { try { const files = fs.readdirSync(BACKUP_DIR) .filter(f => f.startsWith('backup_') && f.endsWith('.db')) .sort() .reverse(); const toDelete = files.slice(keepCount); toDelete.forEach(file => { const filePath = path.join(BACKUP_DIR, file); fs.unlinkSync(filePath); // WAL-Datei auch löschen falls vorhanden const walPath = filePath + '-wal'; if (fs.existsSync(walPath)) { fs.unlinkSync(walPath); } logger.info(`Altes Backup gelöscht: ${file}`); }); } catch (error) { logger.error('Fehler beim Aufräumen alter Backups:', { error: error.message }); } } /** * Backup wiederherstellen */ function restoreBackup(backupName) { try { const backupPath = path.join(BACKUP_DIR, backupName); if (!fs.existsSync(backupPath)) { throw new Error(`Backup nicht gefunden: ${backupName}`); } // Aktuelles DB sichern bevor überschrieben wird if (fs.existsSync(DB_FILE)) { const safetyBackup = DB_FILE + '.before-restore'; fs.copyFileSync(DB_FILE, safetyBackup); } // Backup wiederherstellen fs.copyFileSync(backupPath, DB_FILE); // WAL-Datei auch wiederherstellen falls vorhanden const walBackup = backupPath + '-wal'; if (fs.existsSync(walBackup)) { fs.copyFileSync(walBackup, DB_FILE + '-wal'); } logger.info(`Backup wiederhergestellt: ${backupName}`); return true; } catch (error) { logger.error('Restore-Fehler:', { error: error.message }); throw error; } } /** * Liste aller Backups */ function listBackups() { try { const files = fs.readdirSync(BACKUP_DIR) .filter(f => f.startsWith('backup_') && f.endsWith('.db')) .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 };