import { spawn } from 'child_process'; export function triggerBackup(): void { runBackup().catch((e) => console.error('[backup] Fehler:', e)); } async function runBackup(): Promise { const sshUrl = process.env.BACKUP_SSH_URL; if (!sshUrl) return; const match = sshUrl.match(/^([^:]+):(.+)$/); if (!match) { console.error('[backup] BACKUP_SSH_URL muss das Format user@host:/pfad haben'); return; } const [, sshHost, remotePath] = match; const rawKeyPath = process.env.BACKUP_SSH_KEY_PATH || ''; const keyPath = rawKeyPath.startsWith('~') ? rawKeyPath.replace('~', process.env.HOME || '/root') : rawKeyPath; const ts = new Date().toISOString().replace('T', '_').replace(/:/g, '-').slice(0, 19); const filename = `sternwarte_${ts}.sql.gz`; const dbHost = process.env.DB_HOST || 'db'; const dbPort = process.env.DB_PORT || '3306'; const dbUser = process.env.DB_USER || ''; const dbPass = process.env.DB_PASS || ''; const dbName = process.env.DB_NAME || 'sternwarte'; const sshOpts = [ ...(keyPath ? ['-i', keyPath] : []), '-o', 'StrictHostKeyChecking=no', '-o', 'BatchMode=yes', ]; const dumpBin = process.env.BACKUP_DUMP_CMD || 'mariadb-dump'; await new Promise((resolve, reject) => { const dump = spawn(dumpBin, [ `-h${dbHost}`, `-P${dbPort}`, `-u${dbUser}`, '--skip-ssl', `--ignore-table=${dbName}.beos`, dbName, ], { env: { ...process.env, MYSQL_PWD: dbPass } }); const gzip = spawn('gzip'); const ssh = spawn('ssh', [...sshOpts, sshHost, `cat > ${remotePath}/${filename}`]); dump.stdout.pipe(gzip.stdin); gzip.stdout.pipe(ssh.stdin); let dumpErr = ''; let sshErr = ''; dump.stderr.on('data', (d: Buffer) => { dumpErr += d.toString(); }); ssh.stderr.on('data', (d: Buffer) => { sshErr += d.toString(); }); dump.on('error', reject); gzip.on('error', reject); ssh.on('error', reject); dump.on('close', (code) => { if (code !== 0) gzip.stdin.end(); }); gzip.on('close', () => ssh.stdin.end()); ssh.on('close', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`ssh exit ${code}${sshErr ? ': ' + sshErr.trim() : ''}${dumpErr ? ' | dump: ' + dumpErr.trim() : ''}`)); } }); }); // Backups älter als 30 Tage löschen await new Promise((resolve) => { const ssh = spawn('ssh', [ ...sshOpts, sshHost, `find ${remotePath} -name 'sternwarte_*.sql.gz' -mtime +30 -delete`, ]); ssh.on('error', (e) => { console.error('[backup] Cleanup spawn-Fehler:', e.message); resolve(); }); ssh.on('close', (code) => { if (code !== 0) console.error('[backup] Cleanup fehlgeschlagen (exit ' + code + ')'); resolve(); }); }); console.log(`[backup] ${filename} → ${sshHost}:${remotePath}`); }