162 lines
7.6 KiB
PHP
162 lines
7.6 KiB
PHP
<?php
|
||
// Direkter Web-Zugriff (Browser/URL) wird geblockt. Ausnahmen: echter CLI-Cronjob
|
||
// sowie Einbindung per require_once aus dem authentifizierten Backend (staff/settings.php),
|
||
// das vor dem require_once die Konstante DESKHUB_CRON_RUNNER definiert.
|
||
if (php_sapi_name() !== 'cli' && !defined('DESKHUB_CRON_RUNNER')) { http_response_code(403); exit('Kein Web-Zugriff.'); }
|
||
/**
|
||
* Automatischer Backup-Cron
|
||
* Crontab-Beispiele:
|
||
* Täglich 02:00: 0 2 * * * php /pfad/zu/deskhub/cron/process_backups.php
|
||
* Wöchentlich: 0 2 * * 1 php /pfad/zu/deskhub/cron/process_backups.php
|
||
* Monatlich: 0 2 1 * * php /pfad/zu/deskhub/cron/process_backups.php
|
||
*
|
||
* Kann außerdem manuell aus dem Backend (Einstellungen → Cron-Jobs) ausgeführt werden
|
||
* (dort wird der Zeitplan ignoriert und immer sofort ein Backup erstellt, $force=true).
|
||
*/
|
||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||
require_once __DIR__ . '/../includes/backup.php';
|
||
|
||
function bset(PDO $pdo, string $key): ?string {
|
||
$s = $pdo->prepare('SELECT value FROM backup_settings WHERE key_name=?');
|
||
$s->execute([$key]);
|
||
$r = $s->fetch();
|
||
return $r ? $r['value'] : null;
|
||
}
|
||
|
||
/**
|
||
* Führt das automatische Backup aus (falls laut Zeitplan fällig, oder immer bei $force=true).
|
||
* Kann vom Cron und manuell aus settings.php aufgerufen werden.
|
||
*
|
||
* @return array{ran: bool, ok: bool, log: string[]}
|
||
*/
|
||
function run_process_backups(PDO $pdo, bool $verbose = false, bool $force = false): array
|
||
{
|
||
$log = [];
|
||
|
||
// Zeitplan prüfen
|
||
$schedule = bset($pdo, 'schedule') ?: 'disabled';
|
||
$lastBackup = bset($pdo, 'last_auto_backup') ?: '1970-01-01';
|
||
$now = time();
|
||
|
||
$shouldRun = false;
|
||
switch ($schedule) {
|
||
case 'daily': $shouldRun = (strtotime($lastBackup) < strtotime('-1 day', $now)); break;
|
||
case 'weekly': $shouldRun = (strtotime($lastBackup) < strtotime('-7 days', $now)); break;
|
||
case 'monthly': $shouldRun = (strtotime($lastBackup) < strtotime('-30 days', $now)); break;
|
||
default: $shouldRun = false; // disabled
|
||
}
|
||
|
||
if ($force) {
|
||
$shouldRun = true;
|
||
}
|
||
|
||
if (!$shouldRun) {
|
||
if ($schedule === 'disabled' && !$force) {
|
||
$log[] = date('Y-m-d H:i:s') . ' – Automatische Backups sind deaktiviert (Zeitplan: disabled).';
|
||
} else {
|
||
$log[] = date('Y-m-d H:i:s') . " – Kein Backup nötig (Zeitplan: $schedule, letztes: $lastBackup)";
|
||
}
|
||
return ['ran' => false, 'ok' => true, 'log' => $log];
|
||
}
|
||
|
||
$log[] = date('Y-m-d H:i:s') . ' – Starte Backup...';
|
||
|
||
$result = create_backup();
|
||
|
||
if (!$result['ok']) {
|
||
$log[] = date('Y-m-d H:i:s') . " – FEHLER: {$result['error']}";
|
||
return ['ran' => true, 'ok' => false, 'log' => $log];
|
||
}
|
||
|
||
$log[] = date('Y-m-d H:i:s') . " – Backup erstellt: {$result['filename']} ({$result['size']} MB)";
|
||
|
||
// Sofort prüfen, ob sich das Backup wiederherstellen lässt (ohne etwas zu verändern)
|
||
require_once __DIR__ . '/../includes/backup_restore.php';
|
||
$check = $result['verify'] ?? backup_verify_and_record($result['path']); // create_backup() prüft jedes Backup automatisch
|
||
$log[] = date('Y-m-d H:i:s') . ($check['ok'] ? ' – Prüfung: Backup ist vollständig und wiederherstellbar.' : ' – WARNUNG, Backup nicht wiederherstellbar: ' . implode(' ', $check['errors']));
|
||
|
||
// Letztes Backup-Datum speichern
|
||
$backupTime = date('Y-m-d H:i:s');
|
||
$backupSettingSql = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite'
|
||
? "INSERT INTO backup_settings (key_name, value, updated_at) VALUES ('last_auto_backup', ?, ?)
|
||
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 ('last_auto_backup', ?, ?)
|
||
ON DUPLICATE KEY UPDATE value=VALUES(value), updated_at=VALUES(updated_at)";
|
||
$pdo->prepare($backupSettingSql)->execute([$backupTime, $backupTime]);
|
||
|
||
// ── FTP-Upload (optional) ────────────────────────────────────────────────────
|
||
$ftpHost = bset($pdo, 'ftp_host');
|
||
$ftpUser = bset($pdo, 'ftp_user');
|
||
$ftpPass = bset($pdo, 'ftp_pass');
|
||
$ftpPath = bset($pdo, 'ftp_path') ?: '/';
|
||
$ftpPort = (int)(bset($pdo, 'ftp_port') ?: 21);
|
||
$ftpTls = bset($pdo, 'ftp_tls') === '1';
|
||
|
||
if ($ftpHost && $ftpUser) {
|
||
$log[] = date('Y-m-d H:i:s') . " – FTP-Upload nach $ftpHost...";
|
||
|
||
if (!function_exists('ftp_connect')) {
|
||
$log[] = date('Y-m-d H:i:s') . ' – WARNUNG: PHP FTP-Erweiterung nicht verfügbar.';
|
||
} else {
|
||
$ftp = $ftpTls ? @ftp_ssl_connect($ftpHost, $ftpPort, 10) : @ftp_connect($ftpHost, $ftpPort, 10);
|
||
if ($ftp && @ftp_login($ftp, $ftpUser, $ftpPass)) {
|
||
ftp_pasv($ftp, true);
|
||
$remotePath = rtrim($ftpPath, '/') . '/' . $result['filename'];
|
||
if (ftp_put($ftp, $remotePath, $result['path'], FTP_BINARY)) {
|
||
$log[] = date('Y-m-d H:i:s') . " – FTP-Upload erfolgreich: $remotePath";
|
||
// FTP-Rotation
|
||
$ftpKeep = (int)(bset($pdo, 'ftp_keep') ?: 5);
|
||
$files = @ftp_nlist($ftp, $ftpPath);
|
||
if ($files) {
|
||
$backupFiles = array_filter($files, fn($f) => str_contains($f, 'backup_'));
|
||
rsort($backupFiles);
|
||
foreach (array_slice($backupFiles, $ftpKeep) as $old) {
|
||
@ftp_delete($ftp, $old);
|
||
$log[] = date('Y-m-d H:i:s') . " – FTP-Rotation: $old gelöscht";
|
||
}
|
||
}
|
||
} else {
|
||
$log[] = date('Y-m-d H:i:s') . ' – FTP-Upload fehlgeschlagen.';
|
||
}
|
||
ftp_close($ftp);
|
||
} else {
|
||
$log[] = date('Y-m-d H:i:s') . ' – FTP-Verbindung fehlgeschlagen.';
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Lokale Rotation ──────────────────────────────────────────────────────────
|
||
$keepLocal = (int)(bset($pdo, 'rotate_keep') ?: 0);
|
||
if ($keepLocal > 0) {
|
||
$backupDir = realpath(__DIR__ . '/../backups');
|
||
$files = array_merge(
|
||
glob($backupDir . '/backup_*.zip') ?: [],
|
||
glob($backupDir . '/backup_*.tar.gz') ?: []
|
||
);
|
||
usort($files, fn($a, $b) => filemtime($b) - filemtime($a));
|
||
foreach (array_slice($files, $keepLocal) as $old) {
|
||
unlink($old);
|
||
$log[] = date('Y-m-d H:i:s') . ' – Rotation: ' . basename($old) . ' gelöscht';
|
||
}
|
||
}
|
||
|
||
// Noch nicht oder lange nicht geprüfte Backups nachholen (zum Beispiel ältere oder von der NAS geholte)
|
||
$late = backup_verify_pending(3);
|
||
if ($late['checked']) $log[] = date('Y-m-d H:i:s') . ' – ' . $late['checked'] . ' weitere(s) Backup(s) geprüft' . ($late['bad'] ? ', davon ' . $late['bad'] . ' NICHT wiederherstellbar' : '') . '.';
|
||
|
||
set_setting('cron_process_backups_last_run', date('Y-m-d H:i:s'));
|
||
$log[] = date('Y-m-d H:i:s') . ' – Fertig.';
|
||
|
||
return ['ran' => true, 'ok' => true, 'log' => $log];
|
||
}
|
||
|
||
if (php_sapi_name() === 'cli') {
|
||
$result = run_process_backups(get_db(), true, false);
|
||
foreach ($result['log'] as $line) {
|
||
echo $line . "\n";
|
||
}
|
||
if (!$result['ok']) {
|
||
exit(1);
|
||
}
|
||
}
|