prepare('SELECT value FROM backup_settings WHERE key_name=?'); $s->execute([$key]); $r = $s->fetch(); return $r ? $r['value'] : null; } function bset_save(string $key, ?string $value): void { global $pdo; $sql = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite' ? 'INSERT INTO backup_settings (key_name, value, updated_at) VALUES (?,?,?) ON CONFLICT(key_name) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at' : 'INSERT INTO backup_settings (key_name, value, updated_at) VALUES (?,?,?) ON DUPLICATE KEY UPDATE value=VALUES(value), updated_at=VALUES(updated_at)'; $pdo->prepare($sql)->execute([$key, $value, db_now()]); } // ── POST-Handler ───────────────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { verify_csrf(); $action = $_POST['action'] ?? ''; if ($action === 'create_backup') { $result = create_backup(); if ($result['ok']) { // FTP-Upload wenn konfiguriert $ftpHost = bget('ftp_host'); $ftpUser = bget('ftp_user'); $ftpPass = bget('ftp_pass'); $ftpPath = bget('ftp_path') ?: '/'; $ftpPort = (int)(bget('ftp_port') ?: 21); $ftpTls = bget('ftp_tls') === '1'; $ftpMsg = ''; if ($ftpHost && $ftpUser && function_exists('ftp_connect')) { $ftp = $ftpTls ? @ftp_ssl_connect($ftpHost, $ftpPort, 10) : @ftp_connect($ftpHost, $ftpPort, 10); if ($ftp && @ftp_login($ftp, $ftpUser, $ftpPass)) { ftp_pasv($ftp, true); $remote = rtrim($ftpPath, '/') . '/' . $result['filename']; $ftpMsg = ftp_put($ftp, $remote, $result['path'], FTP_BINARY) ? ' – FTP-Upload erfolgreich.' : ' – FTP-Upload fehlgeschlagen.'; ftp_close($ftp); } else { $ftpMsg = ' – FTP-Verbindung fehlgeschlagen.'; } } // Lokale Rotation $keep = (int)(bget('rotate_keep') ?: 0); if ($keep > 0) { $bdir = realpath(__DIR__ . '/../backups'); $files = array_merge(glob($bdir.'/backup_*.zip')?:[], glob($bdir.'/backup_*.tar.gz')?:[]); usort($files, fn($a,$b) => filemtime($b)-filemtime($a)); foreach (array_slice($files, $keep) as $old) @unlink($old); } $verified = backup_verify_and_record($result['path']); audit_log($staff['id'], "Manuelles Backup: {$result['filename']} ({$result['size']} MB)$ftpMsg"); flash($verified['ok'] ? 'success' : 'error', ($verified['ok'] ? "✅ Backup erstellt und geprüft: {$result['filename']} ({$result['size']} MB){$ftpMsg}" : "Backup erstellt, aber NICHT wiederherstellbar: " . implode(' ', $verified['errors']))); } else { flash('error', 'Backup fehlgeschlagen: ' . $result['error']); } redirect('/staff/backup.php'); } if ($action === 'verify_backup') { $path = backup_path_for((string) ($_POST['filename'] ?? '')); if (!$path) { flash('error', 'Backup nicht gefunden.'); redirect('/staff/backup.php'); } $v = backup_verify_and_record($path); $i = $v['info']; if ($v['ok']) { flash('success', '✅ ' . basename($path) . ' ist vollständig und wiederherstellbar (' . strtoupper((string) ($i['driver'] ?? '')) . '-Datenbank, ' . (int) ($i['tables'] ?? 0) . ' Tabellen' . (isset($i['tickets']) ? ', ' . $i['tickets'] . ' Tickets' : '') . ', ' . (int) ($i['files'] ?? 0) . ' Dateien).' . ($v['warnings'] ? ' Hinweis: ' . implode(' ', $v['warnings']) : '')); } else { flash('error', basename($path) . ' ist NICHT wiederherstellbar: ' . implode(' ', $v['errors'])); } redirect('/staff/backup.php'); } if ($action === 'restore_backup') { $path = backup_path_for((string) ($_POST['filename'] ?? '')); if (!$path) { flash('error', 'Backup nicht gefunden.'); redirect('/staff/backup.php'); } if (trim((string) ($_POST['confirm'] ?? '')) !== 'WIEDERHERSTELLEN') { flash('error', 'Zur Bestätigung bitte WIEDERHERSTELLEN eintippen. Es wurde nichts verändert.'); redirect('/staff/backup.php'); } $dirs = array_values(array_intersect(array_keys(backup_restorable_dirs()), (array) ($_POST['dirs'] ?? []))); $res = backup_restore($pdo, $path, ['database' => !empty($_POST['database']), 'dirs' => $dirs]); if ($res['ok']) { audit_log($staff['id'], 'Backup wiederhergestellt: ' . basename($path) . " ({$res['tables']} Tabellen, {$res['files']} Dateien, Sicherheits-Backup: " . ($res['safety'] ?? '–') . ')'); flash('success', '✅ Wiederhergestellt: ' . basename($path) . " ({$res['tables']} Tabellen, {$res['rows']} Datensätze, {$res['files']} Dateien). Der Stand von vorher steht im Sicherheits-Backup " . ($res['safety'] ?? '–') . '. ' . implode(' ', $res['notes']) . ' Falls du abgemeldet wirst: Der Mitarbeiter-Stand entspricht jetzt dem Backup, bitte neu anmelden.'); } else { flash('error', $res['error']); } redirect('/staff/backup.php'); } if ($action === 'delete_backup') { $file = basename($_POST['filename'] ?? ''); $bdir = realpath(__DIR__ . '/../backups'); $path = $bdir . '/' . $file; if ($file && $bdir && file_exists($path) && strpos(realpath($path), $bdir) === 0 && preg_match('/^backup_[\d\-_]+\.(zip|tar\.gz)$/', $file)) { unlink($path); audit_log($staff['id'], "Backup gelöscht: $file"); flash('success', "Backup $file gelöscht."); } else { flash('error', 'Backup nicht gefunden.'); } redirect('/staff/backup.php'); } if ($action === 'save_schedule') { bset_save('schedule', $_POST['schedule'] ?? 'disabled'); bset_save('rotate_keep', (string)(int)($_POST['rotate_keep'] ?? 0)); audit_log($staff['id'], 'Backup-Zeitplan gespeichert'); flash('success', 'Zeitplan gespeichert.'); redirect('/staff/backup.php#zeitplan'); } if ($action === 'save_ftp') { bset_save('ftp_host', trim($_POST['ftp_host'] ?? '') ?: null); bset_save('ftp_port', trim($_POST['ftp_port'] ?? '21')); bset_save('ftp_user', trim($_POST['ftp_user'] ?? '') ?: null); bset_save('ftp_tls', !empty($_POST['ftp_tls']) ? '1' : '0'); bset_save('ftp_path', trim($_POST['ftp_path'] ?? '/')); bset_save('ftp_keep', (string)(int)($_POST['ftp_keep'] ?? 5)); if (!empty($_POST['ftp_pass'])) { bset_save('ftp_pass', $_POST['ftp_pass']); } audit_log($staff['id'], 'FTP-Backup-Einstellungen gespeichert'); flash('success', 'FTP-Einstellungen gespeichert.'); redirect('/staff/backup.php#ftp'); } if ($action === 'test_ftp') { $host = bget('ftp_host'); $user = bget('ftp_user'); $pass = bget('ftp_pass'); $port = (int)(bget('ftp_port') ?: 21); $tls = bget('ftp_tls') === '1'; if (!$host || !$user) { flash('error', 'FTP nicht konfiguriert.'); } elseif (!function_exists('ftp_connect')) { flash('error', 'PHP FTP-Erweiterung nicht verfügbar.'); } else { $ftp = $tls ? @ftp_ssl_connect($host, $port, 10) : @ftp_connect($host, $port, 10); if ($ftp && @ftp_login($ftp, $user, $pass)) { ftp_close($ftp); flash('success', "✅ FTP-Verbindung zu $host erfolgreich."); } else { flash('error', "FTP-Verbindung zu $host fehlgeschlagen."); } } redirect('/staff/backup.php#ftp'); } } // ── Daten laden ─────────────────────────────────────────────────────────────── $backupDir = realpath(__DIR__ . '/../backups') ?: (dirname(__DIR__) . '/backups'); $backupFiles = []; if (is_dir($backupDir)) { foreach (array_merge(glob($backupDir.'/backup_*.zip')?:[], glob($backupDir.'/backup_*.tar.gz')?:[]) as $f) { $backupFiles[] = ['name'=>basename($f),'size'=>round(filesize($f)/1024/1024,2),'date'=>filemtime($f)]; } usort($backupFiles, fn($a,$b) => $b['date']-$a['date']); } $totalSize = array_sum(array_column($backupFiles, 'size')); $lastAutoRun = get_setting('cron_process_backups_last_run'); $lastAutoBkp = bget('last_auto_backup'); $pageTitle = 'Backup'; $activePage = 'backup'; require __DIR__ . '/_header.php'; ?>
Letzte Prüfung: = e(date('d.m.Y H:i', strtotime($lastCheck['at']))) ?> – = e($lastCheck['file']) ?>: = $lastCheck['ok'] ? 'wiederherstellbar' : 'nicht wiederherstellbar' ?>
| Dateiname | Größe | Erstellt | |
|---|---|---|---|
| = e($b['name']) ?> | = $b['size'] ?> MB | = date('d.m.Y H:i', $b['date']) ?> |
⬇️ Download
♻️ Wiederherstellen |
Cron-Job einrichten: php = e(dirname(__DIR__)) ?>/cron/process_backups.php
Letzter Lauf: = format_dt($lastAutoRun) ?>
Backups werden nach der Erstellung automatisch per FTP übertragen.
0 2 * * * php = e(dirname(__DIR__)) ?>/cron/process_backups.phpbackups/ und sind von außen nicht zugänglich