const cron = require('node-cron'); const archiver = require('archiver'); const path = require('path'); const fs = require('fs'); const dataDir = path.join(__dirname, '../data'); const backupsDir = path.join(dataDir, 'backups'); const uploadsDir = path.join(__dirname, '../uploads'); const settingsFile = path.join(dataDir, 'backup-settings.json'); const CRON_EXPRESSIONS = { hourly: '0 * * * *', daily: '0 2 * * *', weekly: '0 2 * * 0', monthly: '0 2 1 * *', }; const VALID_INTERVALS = Object.keys(CRON_EXPRESSIONS); let currentTask = null; function loadSettings() { try { if (fs.existsSync(settingsFile)) { return JSON.parse(fs.readFileSync(settingsFile, 'utf8')); } } catch (e) {} return { enabled: false, interval: 'daily', keep_days: 7 }; } function saveSettings(settings) { if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true }); fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2)); } async function runBackup() { if (!fs.existsSync(backupsDir)) fs.mkdirSync(backupsDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const filename = `auto-backup-${timestamp}.zip`; const outputPath = path.join(backupsDir, filename); try { // Flush WAL to main DB file before archiving try { const { db } = require('./db/database'); db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch (e) {} await new Promise((resolve, reject) => { const output = fs.createWriteStream(outputPath); const archive = archiver('zip', { zlib: { level: 9 } }); output.on('close', resolve); archive.on('error', reject); archive.pipe(output); const dbPath = path.join(dataDir, 'travel.db'); if (fs.existsSync(dbPath)) archive.file(dbPath, { name: 'travel.db' }); if (fs.existsSync(uploadsDir)) archive.directory(uploadsDir, 'uploads'); archive.finalize(); }); console.log(`[Auto-Backup] Erstellt: ${filename}`); } catch (err) { console.error('[Auto-Backup] Fehler:', err.message); if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath); return; } const settings = loadSettings(); if (settings.keep_days > 0) { cleanupOldBackups(settings.keep_days); } } function cleanupOldBackups(keepDays) { try { const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1000; const files = fs.readdirSync(backupsDir).filter(f => f.endsWith('.zip')); for (const file of files) { const filePath = path.join(backupsDir, file); const stat = fs.statSync(filePath); if (stat.birthtimeMs < cutoff) { fs.unlinkSync(filePath); console.log(`[Auto-Backup] Altes Backup gelöscht: ${file}`); } } } catch (err) { console.error('[Auto-Backup] Bereinigungsfehler:', err.message); } } function start() { if (currentTask) { currentTask.stop(); currentTask = null; } const settings = loadSettings(); if (!settings.enabled) { console.log('[Auto-Backup] Deaktiviert'); return; } const expression = CRON_EXPRESSIONS[settings.interval] || CRON_EXPRESSIONS.daily; currentTask = cron.schedule(expression, runBackup); console.log(`[Auto-Backup] Geplant: ${settings.interval} (${expression}), Aufbewahrung: ${settings.keep_days === 0 ? 'immer' : settings.keep_days + ' Tage'}`); } // Demo mode: hourly reset of demo user data let demoTask = null; function startDemoReset() { if (demoTask) { demoTask.stop(); demoTask = null; } if (process.env.DEMO_MODE !== 'true') return; demoTask = cron.schedule('0 * * * *', () => { try { const { resetDemoUser } = require('./demo/demo-reset'); resetDemoUser(); } catch (err) { console.error('[Demo Reset] Error:', err.message); } }); console.log('[Demo] Hourly reset scheduled (at :00 every hour)'); } function stop() { if (currentTask) { currentTask.stop(); currentTask = null; } if (demoTask) { demoTask.stop(); demoTask = null; } } module.exports = { start, stop, startDemoReset, loadSettings, saveSettings, VALID_INTERVALS };