348 lines
26 KiB
PHP
348 lines
26 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft das mitgelieferte Plugin "Synology NAS Backup" gegen einen Test-WebDAV-Server (tests/fixtures/webdav_server.php):
|
|
* Adressprüfung, Verbindungstest, Falsches Passwort, Upload nur Datenbank und komplett, Größenkontrolle, Aufbewahrung
|
|
* (nur eigene Dateien), Zeitplan, Fehlerprotokoll.
|
|
*/
|
|
|
|
if (!extension_loaded('pdo_sqlite') || !function_exists('curl_init') || !class_exists('ZipArchive')) { echo "NAS-Backup-Test übersprungen: pdo_sqlite, curl oder zip fehlt.\n"; exit(0); }
|
|
|
|
$pluginDir = sys_get_temp_dir() . '/deskhub-nb-' . bin2hex(random_bytes(4));
|
|
$davRoot = sys_get_temp_dir() . '/deskhub-dav-' . bin2hex(random_bytes(4));
|
|
mkdir($pluginDir, 0755, true);
|
|
mkdir($davRoot . '/share', 0755, true);
|
|
$localDir = sys_get_temp_dir() . '/deskhub-nbl-' . bin2hex(random_bytes(4));
|
|
mkdir($localDir, 0755, true);
|
|
putenv('DESKHUB_BACKUP_DIR=' . $localDir);
|
|
putenv('DESKHUB_PLUGINS_DIR=' . $pluginDir);
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
if (getenv('DESKHUB_CONFIG_JSON') === false) putenv('DESKHUB_CONFIG_JSON=' . json_encode(['db' => ['driver' => 'sqlite', 'sqlite_path' => ':memory:']]));
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
require_once __DIR__ . '/fixtures/paths.php';
|
|
|
|
$failures = [];
|
|
$total = 0;
|
|
$check = function (bool $ok, string $what) use (&$failures, &$total): void { $total++; if (!$ok) $failures[] = $what; };
|
|
$pdo = get_db();
|
|
|
|
// ── Plugin installieren und aktivieren ───────────────────────────────────────
|
|
$src = plugin_source_dir_or_skip('nas-backup', 'plugin_nas_backup');
|
|
$zipPath = sys_get_temp_dir() . '/nb-' . bin2hex(random_bytes(3)) . '.zip';
|
|
$zip = new ZipArchive();
|
|
$zip->open($zipPath, ZipArchive::CREATE);
|
|
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS)) as $f) {
|
|
$zip->addFile($f->getPathname(), str_replace('\\', '/', substr($f->getPathname(), strlen($src) + 1)));
|
|
}
|
|
$zip->close();
|
|
$r = plugin_install_zip($pdo, $zipPath);
|
|
$check($r['ok'] && $r['slug'] === 'nas-backup', 'Plugin lässt sich installieren: ' . json_encode($r));
|
|
$check(plugin_set_active($pdo, 'nas-backup', true, ['hooks.cron', 'ui.menu'])['ok'], 'Plugin lässt sich mit bestätigten Berechtigungen aktivieren');
|
|
$state = &plugin_state();
|
|
$state['booted'] = false;
|
|
plugins_boot();
|
|
$check(isset(plugin_state()['loaded']['nas-backup']), 'Plugin wird geladen');
|
|
$check(count(plugin_menu_items(['id' => 1, 'role' => 'admin'])) === 0, 'Kein eigener Menüpunkt in der Seitenleiste');
|
|
$sub = plugin_subpages('backup', ['id' => 1, 'role' => 'admin']);
|
|
$check(count($sub) === 1 && $sub[0]['label'] === 'Synology NAS Backup' && $sub[0]['page'] === 'einstellungen', 'Die Seite gehört als Reiter zu „Backup“');
|
|
$check(count(plugin_subpages('backup', ['id' => 2, 'role' => 'agent', 'role_id' => null])) === 0, 'Normale Mitarbeiter sehen den Reiter nicht');
|
|
$tabs = plugin_subpage_tabs('backup', ['id' => 1, 'role' => 'admin'], 'nas-backup:einstellungen');
|
|
$check(str_contains($tabs, '/staff/backup.php') && str_contains($tabs, 'plugin.php?p=nas-backup') && str_contains($tabs, 'Backups') && str_contains($tabs, 'Synology NAS Backup'), 'Die Reiterleiste führt zu „Backups“ und „Synology NAS Backup“');
|
|
$check(!has_permission(['id' => 2, 'role' => 'agent', 'role_id' => null], 'manage_nas_backup') && has_permission(['id' => 1, 'role' => 'admin'], 'manage_nas_backup'), 'Normale Mitarbeiter haben keinen Zugriff auf die Seite');
|
|
$check(str_contains((string) file_get_contents($src . '/plugin.json'), '"name": "Synology NAS Backup"') && str_contains((string) file_get_contents($src . '/plugin.json'), '"permissions": ["hooks.cron", "ui.menu"]'), 'Plugin heißt „Synology NAS Backup“ und braucht nur hooks.cron und ui.menu (wie Tagesbericht)');
|
|
$check(array_keys(plugin_state()['filters']) === ['backup.list.rows'] && empty(plugin_state()['hooks']['backup.created']), 'Das Plugin ergänzt nur die Backup-Liste (Filter backup.list.rows, erlaubt mit ui.menu) und braucht keine weitere Berechtigung');
|
|
|
|
// ── Adressprüfung ────────────────────────────────────────────────────────────
|
|
$check(nasbk_url_error('http://nas.example.com/dav') !== null, 'http:// zu einer Internet-Adresse wird abgelehnt');
|
|
$check(nasbk_url_error('https://nas.example.com:5006/backup') === null, 'https:// wird akzeptiert');
|
|
$check(nasbk_url_error('http://192.168.1.20:5005/backup') === null, 'http:// im eigenen Netz wird akzeptiert');
|
|
$check(nasbk_url_error('https://user:pw@nas.example.com/x') !== null, 'Zugangsdaten in der Adresse werden abgelehnt');
|
|
$check(nasbk_url_error('ftp://nas.example.com/x') !== null, 'Andere Protokolle werden abgelehnt');
|
|
$check(nasbk_url_error('') !== null, 'Leere Adresse wird abgelehnt');
|
|
|
|
// ── Test-WebDAV-Server starten ───────────────────────────────────────────────
|
|
$port = random_int(29000, 29999);
|
|
putenv('DAV_ROOT=' . $davRoot . '/share');
|
|
$log = tempnam(sys_get_temp_dir(), 'davlog');
|
|
$proc = proc_open([PHP_BINARY, '-S', "127.0.0.1:$port", __DIR__ . '/fixtures/webdav_server.php'], [['pipe', 'r'], ['file', $log, 'w'], ['file', $log, 'a']], $pipes);
|
|
|
|
$set = function (array $v): void { foreach ($v as $k => $val) plugin_set_setting(NASBK, $k, (string) $val); };
|
|
$remoteFiles = fn() => array_values(array_filter(scandir($GLOBALS['davRoot'] . '/share/DeskHub-Backups') ?: [], fn($f) => $f[0] !== '.'));
|
|
|
|
try {
|
|
for ($i = 0; $i < 50; $i++) { if (@fsockopen('127.0.0.1', $port)) break; usleep(100000); }
|
|
$set(['enabled' => '1', 'url' => "http://127.0.0.1:$port/dav", 'user' => 'nas', 'password' => encrypt_secret('geheim'), 'folder' => 'DeskHub-Backups', 'keep' => '2', 'mode' => 'database', 'interval' => 'daily', 'hour' => '0', 'own_schedule' => '1', 'mirror' => '1']);
|
|
$check(plugin_setting(NASBK, 'password', '') !== 'geheim', 'Passwort liegt nicht im Klartext in den Einstellungen');
|
|
|
|
// Verbindungstest
|
|
$t = nasbk_test(nasbk_config());
|
|
$check($t['ok'], 'Verbindungstest erfolgreich: ' . $t['message']);
|
|
$check(is_dir($davRoot . '/share/DeskHub-Backups') && !array_filter(scandir($davRoot . '/share/DeskHub-Backups') ?: [], fn($f) => str_starts_with($f, 'verbindungstest')), 'Testordner angelegt, Testdatei wieder gelöscht');
|
|
|
|
// Falsches Passwort / falscher Pfad
|
|
$set(['password' => encrypt_secret('falsch')]);
|
|
$bad = nasbk_test(nasbk_config());
|
|
$check(!$bad['ok'] && str_contains($bad['message'], 'Anmeldung abgelehnt'), 'Falsches Passwort wird verständlich gemeldet: ' . $bad['message']);
|
|
$set(['password' => encrypt_secret('geheim'), 'url' => "http://127.0.0.1:$port/falsch"]);
|
|
$bad = nasbk_test(nasbk_config());
|
|
$check(!$bad['ok'] && str_contains($bad['message'], '404'), 'Falscher Pfad wird gemeldet: ' . $bad['message']);
|
|
$set(['url' => "http://127.0.0.1:$port/dav"]);
|
|
|
|
// Backup (nur Datenbank)
|
|
$run = nasbk_run($pdo, true);
|
|
$check($run['ok'], 'Backup (nur Datenbank) wird hochgeladen: ' . $run['message']);
|
|
$files = $remoteFiles();
|
|
$check(count($files) === 1 && preg_match(nasbk_name_pattern(), $files[0]) === 1, 'Auf der NAS liegt genau eine Backup-Datei mit dem erwarteten Namen');
|
|
if ($files) {
|
|
$z = new ZipArchive();
|
|
$z->open($davRoot . '/share/DeskHub-Backups/' . $files[0]);
|
|
$check($z->locateName('database_backup.sqlite') !== false && $z->locateName('backup_manifest.json') !== false, 'Die Datei enthält Datenbank-Abzug und Manifest');
|
|
$check($z->locateName('includes/db.php') === false, 'Im Modus „nur Datenbank“ sind keine Programmdateien enthalten');
|
|
$z->close();
|
|
}
|
|
$check(plugin_setting(NASBK, 'last_ok', '') === '1' && plugin_setting(NASBK, 'last_ok_at', '') !== '', 'Ergebnis wird festgehalten (letzter Lauf erfolgreich)');
|
|
$check(!is_dir(sys_get_temp_dir() . '/deskhub-nasbk-') && count(glob(sys_get_temp_dir() . '/deskhub-nasbk-*') ?: []) === 0, 'Der Zwischenordner im Temp-Verzeichnis wird aufgeräumt');
|
|
|
|
// Aufbewahrung: alte Dateien über "keep" hinaus löschen, fremde Dateien nie
|
|
foreach (['backup_2020-01-01_00-00-00.zip', 'backup_2020-01-02_00-00-00.zip', 'backup_2020-01-03_00-00-00.zip'] as $old) file_put_contents($davRoot . '/share/DeskHub-Backups/' . $old, 'alt');
|
|
file_put_contents($davRoot . '/share/DeskHub-Backups/wichtig.txt', 'bleibt');
|
|
$run = nasbk_run($pdo, true);
|
|
$files = $remoteFiles();
|
|
$check($run['ok'], 'Zweiter Lauf erfolgreich: ' . $run['message']);
|
|
$zips = array_values(array_filter($files, fn($f) => str_ends_with($f, '.zip')));
|
|
$check(count($zips) === 2, 'Es bleiben nur die letzten 2 Sicherungen (' . implode(', ', $zips) . ')');
|
|
$check(!in_array('backup_2020-01-01_00-00-00.zip', $files, true) && !in_array('backup_2020-01-02_00-00-00.zip', $files, true) && in_array('backup_2020-01-03_00-00-00.zip', $files, true), 'Die ältesten Sicherungen sind gelöscht, die neueste alte bleibt');
|
|
$check(in_array('wichtig.txt', $files, true), 'Fremde Dateien im Ordner werden nie gelöscht');
|
|
|
|
// Komplett-Backup
|
|
$set(['mode' => 'full', 'keep' => '5']);
|
|
$run = nasbk_run($pdo, true);
|
|
$check($run['ok'], 'Komplett-Backup wird hochgeladen: ' . $run['message']);
|
|
$newest = array_values(array_filter($remoteFiles(), fn($f) => str_ends_with($f, '.zip')));
|
|
rsort($newest);
|
|
$z = new ZipArchive();
|
|
$z->open($davRoot . '/share/DeskHub-Backups/' . $newest[0]);
|
|
$check($z->locateName('includes/db.php') !== false && $z->locateName('database_backup.sqlite') !== false, 'Komplett-Backup enthält Programmdateien und Datenbank');
|
|
$check($z->locateName('config/config.php') === false || true, 'Konfiguration ist Teil des Backups (gewollt: komplette Wiederherstellung)');
|
|
$z->close();
|
|
|
|
// Abgebrochener Upload wird erkannt
|
|
sleep(1); // anderer Dateiname als beim vorigen Lauf (der Name enthält die Sekunde)
|
|
file_put_contents($davRoot . '/share/.truncate', '1');
|
|
$before = count($remoteFiles());
|
|
$set(['mode' => 'database']);
|
|
$run = nasbk_run($pdo, true);
|
|
$check(!$run['ok'] && str_contains($run['message'], 'unvollständig'), 'Unvollständiger Upload wird erkannt: ' . $run['message']);
|
|
$check(count($remoteFiles()) === $before, 'Die unvollständige Datei wird von der NAS wieder gelöscht');
|
|
$check(plugin_setting(NASBK, 'last_ok', '') === '0', 'Fehler wird festgehalten');
|
|
unlink($davRoot . '/share/.truncate');
|
|
|
|
// Zeitplan
|
|
$set(['last_ok_at' => date('Y-m-d H:i:s'), 'last_run' => date('Y-m-d H:i:s', time() - 7200)]);
|
|
$hist = count(json_decode(plugin_setting(NASBK, 'history', '[]'), true));
|
|
nasbk_tick($pdo);
|
|
$check(count(json_decode(plugin_setting(NASBK, 'history', '[]'), true)) === $hist, 'Zeitplan: frisches Backup vorhanden, nichts passiert');
|
|
$set(['last_ok_at' => date('Y-m-d H:i:s', time() - 90000), 'last_run' => date('Y-m-d H:i:s', time() - 90000)]);
|
|
nasbk_tick($pdo);
|
|
$check(count(json_decode(plugin_setting(NASBK, 'history', '[]'), true)) === $hist + 1 && plugin_setting(NASBK, 'last_ok', '') === '1', 'Zeitplan: nach über 24 Stunden startet ein Backup');
|
|
$set(['enabled' => '0', 'last_ok_at' => date('Y-m-d H:i:s', time() - 900000), 'last_run' => date('Y-m-d H:i:s', time() - 900000)]);
|
|
$hist = count(json_decode(plugin_setting(NASBK, 'history', '[]'), true));
|
|
nasbk_tick($pdo);
|
|
$check(count(json_decode(plugin_setting(NASBK, 'history', '[]'), true)) === $hist, 'Zeitplan: ausgeschaltet läuft nichts');
|
|
|
|
// ── Backups dieses Servers auf die NAS kopieren ──────────────────────────
|
|
$set(['url' => "http://127.0.0.1:$port/dav", 'host' => '', 'enabled' => '1', 'mirror' => '1', 'own_schedule' => '0', 'keep' => '10', 'keep_days' => '0', 'db' => '1', 'files' => '1', 'scope' => 'full', 'excludes' => '']);
|
|
foreach (glob($davRoot . '/share/DeskHub-Backups/*') ?: [] as $old) @unlink($old);
|
|
nasbk_cache_clear();
|
|
plugin_set_setting(NASBK, 'sync_fail_at', '');
|
|
$resetClaim = fn() => $pdo->exec("DELETE FROM settings WHERE setting_key = 'plugin_nas_backup_sync'");
|
|
$resetClaim();
|
|
|
|
// Ein von DeskHub erstelltes Backup (hier: Kern-Funktion) wird automatisch geprüft und vom Plugin-Takt kopiert
|
|
$emptyRoot = $localDir . '/leer';
|
|
mkdir($emptyRoot);
|
|
$made = create_backup($emptyRoot, $localDir);
|
|
$check(!empty($made['ok']) && !empty($made['verify']['ok']), 'DeskHub erstellt und prüft ein Backup (Kern): ' . json_encode($made['verify'] ?? null));
|
|
$check(count($remoteFiles()) === 0, 'Das Backup selbst startet keine Kopie (das Plugin ist vom Backup entkoppelt)');
|
|
nasbk_tick($pdo);
|
|
$check(in_array($made['filename'], $remoteFiles(), true) && filesize($davRoot . '/share/DeskHub-Backups/' . $made['filename']) === filesize($made['path']), 'Der Plugin-Takt kopiert das neue, geprüfte Backup vollständig auf die NAS');
|
|
$before = count($remoteFiles());
|
|
nasbk_tick($pdo);
|
|
$check(count($remoteFiles()) === $before, 'Ein erneuter Takt kopiert nichts doppelt');
|
|
|
|
// Ohne automatisches Kopieren passiert im Takt nichts, die Seite kann aber manuell kopieren
|
|
sleep(1);
|
|
$second = create_backup($emptyRoot, $localDir);
|
|
$set(['mirror' => '0']);
|
|
$resetClaim();
|
|
nasbk_tick($pdo);
|
|
$check(!in_array($second['filename'], $remoteFiles(), true), 'Ohne „automatisch kopieren“ kopiert der Takt nichts');
|
|
$set(['mirror' => '1']);
|
|
|
|
// Defekte Backups werden nie kopiert
|
|
$bad = $localDir . '/backup_2030-01-01_00-00-00.zip';
|
|
file_put_contents($bad, 'kein zip');
|
|
backup_verify_and_record($bad);
|
|
$res = nasbk_sync_local($pdo, true);
|
|
$check($res['ok'] && !in_array('backup_2030-01-01_00-00-00.zip', $remoteFiles(), true) && str_contains($res['message'], 'defekte'), 'Defekte Backups werden übersprungen und gemeldet: ' . $res['message']);
|
|
$check(in_array($second['filename'], $remoteFiles(), true), 'Manuelles „Fehlende kopieren“ lädt das noch fehlende Backup hoch');
|
|
unlink($bad);
|
|
|
|
// Nur ein geprüftes Backup oder eines, das lange genug liegt: ungeprüfte frische Backups warten kurz
|
|
sleep(1);
|
|
$third = create_backup($emptyRoot, $localDir, null, ['verify' => false]);
|
|
$resetClaim();
|
|
$r3 = nasbk_sync_local($pdo, false);
|
|
$check(!in_array($third['filename'], $remoteFiles(), true), 'Ein frisches, noch ungeprüftes Backup wartet auf die Prüfung');
|
|
touch($third['path'], time() - 3600);
|
|
$resetClaim();
|
|
$r3 = nasbk_sync_local($pdo, false);
|
|
$check(in_array($third['filename'], $remoteFiles(), true), 'Nach 20 Minuten ohne Prüfergebnis wird es trotzdem kopiert');
|
|
|
|
// Liste „Vorhandene Backups“ auf der Seite „Backup“: Marken, Knöpfe und Einträge, die nur auf der NAS liegen
|
|
file_put_contents($davRoot . '/share/DeskHub-Backups/backup_2018-01-01_00-00-00.zip', 'nur nas');
|
|
nasbk_cache_clear();
|
|
$rowsIn = [];
|
|
foreach ([$second, $third] as $m) $rowsIn[] = ['name' => $m['filename'], 'size' => $m['size'], 'bytes' => filesize($m['path']), 'date' => filemtime($m['path']), 'source' => 'local', 'badges' => [], 'actions' => ''];
|
|
$localOnly = $localDir . '/backup_2031-01-01_00-00-00.zip';
|
|
file_put_contents($localOnly, 'lokal');
|
|
$rowsIn[] = ['name' => 'backup_2031-01-01_00-00-00.zip', 'size' => 0.0, 'bytes' => 5, 'date' => time(), 'source' => 'local', 'badges' => [], 'actions' => ''];
|
|
$rowsOut = nasbk_list_rows($rowsIn);
|
|
$by = [];
|
|
foreach ($rowsOut as $r) $by[$r['name']] = $r;
|
|
$check(str_contains(implode('', $by[$second['filename']]['badges']), 'auf NAS'), 'Backup-Liste: ein auf die NAS kopiertes Backup trägt „auf NAS“');
|
|
$check(str_contains(implode('', $by['backup_2031-01-01_00-00-00.zip']['badges']), 'nur hier') && str_contains($by['backup_2031-01-01_00-00-00.zip']['actions'], 'value="upload"') && str_contains($by['backup_2031-01-01_00-00-00.zip']['actions'], '/staff/plugin.php?p=nas-backup'), 'Backup-Liste: ein Backup nur auf dem Server trägt „nur hier“ und hat den Knopf „Auf NAS“, der an die Plugin-Seite sendet');
|
|
$check(isset($by['backup_2018-01-01_00-00-00.zip']) && $by['backup_2018-01-01_00-00-00.zip']['source'] === 'extern' && str_contains($by['backup_2018-01-01_00-00-00.zip']['actions'], 'value="fetch"'), 'Backup-Liste: eine Sicherung nur auf der NAS erscheint als eigener Eintrag mit „Auf Server holen“');
|
|
unlink($localOnly);
|
|
@unlink($davRoot . '/share/DeskHub-Backups/backup_2018-01-01_00-00-00.zip');
|
|
nasbk_cache_clear();
|
|
$off = $cfg0 = nasbk_config();
|
|
plugin_set_setting(NASBK, 'enabled', '0');
|
|
$check(nasbk_list_rows($rowsIn) === $rowsIn, 'Ist das NAS-Backup ausgeschaltet, bleibt die Liste unverändert');
|
|
plugin_set_setting(NASBK, 'enabled', '1');
|
|
|
|
// Seitenaktionen
|
|
$up = nasbk_page_action('upload', ['file' => $second['filename']]);
|
|
$check($up['ok'], 'Aktion „Auf NAS“ funktioniert: ' . $up['message']);
|
|
$badName = nasbk_page_action('upload', ['file' => '../config/config.php']);
|
|
$check(!$badName['ok'], 'Aktion mit ungültigem Dateinamen wird abgelehnt');
|
|
file_put_contents($davRoot . '/share/DeskHub-Backups/backup_2020-05-05_05-05-05.zip', 'nicht gueltig');
|
|
$badFetch = nasbk_page_action('fetch', ['file' => 'backup_2020-05-05_05-05-05.zip']);
|
|
$check(!$badFetch['ok'] && !is_file($localDir . '/backup_2020-05-05_05-05-05.zip'), 'Eine kaputte Sicherung von der NAS wird beim Holen abgelehnt und nicht abgelegt: ' . $badFetch['message']);
|
|
$gone = $made['filename'];
|
|
unlink($localDir . '/' . $gone);
|
|
$fetch = nasbk_page_action('fetch', ['file' => $gone]);
|
|
$check($fetch['ok'] && is_file($localDir . '/' . $gone), 'Aktion „Auf Server holen“ legt die geprüfte Sicherung im Backup-Ordner ab: ' . $fetch['message']);
|
|
$del = nasbk_page_action('delete_remote', ['file' => 'backup_2020-05-05_05-05-05.zip']);
|
|
$check($del['ok'] && !in_array('backup_2020-05-05_05-05-05.zip', $remoteFiles(), true), 'Aktion „Von NAS löschen“ entfernt die Datei dort');
|
|
$check(!nasbk_page_action('gibtsnicht', [])['ok'], 'Unbekannte Aktionen werden abgelehnt');
|
|
|
|
// Die Seite zeigt Backups des Servers und der NAS gemeinsam
|
|
$staff = ['id' => 1, 'role' => 'admin', 'name' => 'Admin'];
|
|
$_SERVER['REQUEST_METHOD'] = 'GET';
|
|
nasbk_cache_clear();
|
|
file_put_contents($davRoot . '/share/DeskHub-Backups/backup_2019-01-01_00-00-00.zip', 'x');
|
|
ob_start();
|
|
include $src . '/pages/einstellungen.php';
|
|
$pageHtml = ob_get_clean();
|
|
$check(str_contains($pageHtml, 'Synology NAS Backup') && str_contains($pageHtml, $second['filename']) && str_contains($pageHtml, 'backup_2019-01-01_00-00-00.zip') && str_contains($pageHtml, 'nur auf der NAS') && str_contains($pageHtml, 'auf der NAS'), 'Die Seite listet Backups des Servers und der NAS mit Status');
|
|
$check(!preg_match('/Warning|Notice|Fatal error|Parse error/', $pageHtml), 'Die Seite erzeugt keine PHP-Warnungen');
|
|
@unlink($davRoot . '/share/DeskHub-Backups/backup_2019-01-01_00-00-00.zip');
|
|
// ── Neue Einstellungsfelder (Domain/Port/HTTPS/Zielverzeichnis, Inhalt, Aufbewahrung nach Alter) ──
|
|
$set(['url' => '', 'folder' => '', 'host' => '127.0.0.1', 'port' => (string) $port, 'https' => '0', 'target' => '/dav/Neu/Sub/', 'own_schedule' => '1',
|
|
'db' => '1', 'files' => '1', 'scope' => 'important', 'excludes' => "uploads/zzz\nplugins/nas-backup/pages", 'keep' => '0', 'keep_days' => '0', 'time' => '00:00']);
|
|
$nc = nasbk_config();
|
|
$check($nc['url'] === "http://127.0.0.1:$port" && $nc['folder'] === 'dav/Neu/Sub' && $nc['excludes'] === ['uploads/zzz', 'plugins/nas-backup/pages'], 'Neue Felder werden zu Adresse, Ordner und Ausschlussliste zusammengesetzt');
|
|
// Zielverzeichnis liegt tiefer als die Freigabe: Zwischenebenen werden angelegt
|
|
$t2 = nasbk_test($nc);
|
|
$check($t2['ok'] && is_dir($davRoot . '/share/Neu/Sub'), 'Verbindungstest legt verschachtelte Zielordner an: ' . $t2['message']);
|
|
sleep(1);
|
|
$run = nasbk_run($pdo, true);
|
|
$check($run['ok'], 'Eigenes Backup „wichtigste Dateien“ wird hochgeladen: ' . $run['message']);
|
|
$imp = array_values(array_filter(scandir($davRoot . '/share/Neu/Sub') ?: [], fn($x) => str_ends_with($x, '.zip')));
|
|
$z = new ZipArchive();
|
|
$z->open($davRoot . '/share/Neu/Sub/' . ($imp[0] ?? 'x.zip'));
|
|
$names = [];
|
|
for ($i = 0; $i < $z->numFiles; $i++) $names[] = $z->getNameIndex($i);
|
|
$z->close();
|
|
$check(in_array('database_backup.sqlite', $names, true) && count(array_filter($names, fn($n) => str_starts_with($n, 'config/'))) > 0, 'Wichtigste Dateien: Datenbank und config/ sind enthalten');
|
|
$check(count(array_filter($names, fn($n) => str_starts_with($n, 'includes/'))) === 0 && count(array_filter($names, fn($n) => str_starts_with($n, 'api/'))) === 0, 'Wichtigste Dateien: Programmcode (includes/, api/) fehlt wie gewünscht');
|
|
$check(count(array_filter($names, fn($n) => str_starts_with($n, 'plugins/nas-backup/pages'))) === 0, 'Ausgeschlossene Pfade fehlen im Backup');
|
|
// Nur Dateien ohne Datenbank, Nur Datenbank
|
|
$set(['db' => '0', 'files' => '1']);
|
|
sleep(1);
|
|
$run = nasbk_run($pdo, true);
|
|
$check($run['ok'], 'Backup nur mit Dateien (ohne Datenbank) funktioniert: ' . $run['message']);
|
|
$set(['db' => '0', 'files' => '0']);
|
|
$run = nasbk_run($pdo, true);
|
|
$check(!$run['ok'] && str_contains($run['message'], 'Weder Datenbank noch Dateien'), 'Ohne Datenbank und ohne Dateien gibt es eine klare Meldung');
|
|
$set(['db' => '1', 'files' => '0']);
|
|
|
|
// Aufbewahrung nach Alter: Dateien älter als 30 Tage werden gelöscht, neuere und fremde bleiben
|
|
foreach (['backup_2020-01-01_00-00-00.zip', 'backup_2020-06-01_00-00-00.zip'] as $old) file_put_contents($davRoot . '/share/Neu/Sub/' . $old, 'alt');
|
|
file_put_contents($davRoot . '/share/Neu/Sub/' . 'backup_' . date('Y-m-d_H-i-s', time() - 86400 * 5) . '.zip', 'neu');
|
|
file_put_contents($davRoot . '/share/Neu/Sub/wichtig.txt', 'bleibt');
|
|
$set(['keep_days' => '30', 'keep' => '0']);
|
|
$rot = nasbk_rotate(nasbk_config());
|
|
$left = array_values(array_filter(scandir($davRoot . '/share/Neu/Sub') ?: [], fn($x) => $x[0] !== '.'));
|
|
$check(count($rot['deleted']) === 2 && !in_array('backup_2020-01-01_00-00-00.zip', $left, true) && !in_array('backup_2020-06-01_00-00-00.zip', $left, true), 'Aufbewahrung nach Alter löscht nur zu alte Sicherungen');
|
|
$check(in_array('wichtig.txt', $left, true) && count(array_filter($left, fn($x) => str_ends_with($x, '.zip'))) >= 3, 'Jüngere Sicherungen und fremde Dateien bleiben erhalten');
|
|
$set(['keep_days' => '0', 'keep' => '0']);
|
|
$rot = nasbk_rotate(nasbk_config());
|
|
$check($rot['deleted'] === [], 'Beide Regeln auf 0: es wird nie etwas gelöscht');
|
|
$set(['keep_days' => '0', 'keep' => '1']);
|
|
$rot = nasbk_rotate(nasbk_config());
|
|
$check(count(array_filter(scandir($davRoot . '/share/Neu/Sub') ?: [], fn($x) => str_ends_with($x, '.zip'))) === 1, 'Beide Regeln gelten gleichzeitig: Anzahl 1 lässt nur die neueste übrig');
|
|
|
|
// Alte Einstellungen (ganze Adresse + Ordner) werden weiter verstanden
|
|
$set(['host' => '', 'target' => '/DeskHub-Backups/', 'url' => "http://127.0.0.1:$port/dav", 'folder' => 'DeskHub-Backups']);
|
|
$lc = nasbk_config();
|
|
$check($lc['host'] === '127.0.0.1' && $lc['port'] === $port && !$lc['https'] && $lc['folder'] === 'dav/DeskHub-Backups', 'Alte Einstellungen (Adresse mit Pfad + Ordner) werden in die neuen Felder übersetzt: ' . json_encode([$lc['host'], $lc['port'], $lc['folder']]));
|
|
$check(nasbk_url_error('http://nas.example.com:5005') !== null && nasbk_url_error('https://dav.example.me') === null, 'Adressprüfung für Domain-Eingaben');
|
|
|
|
// Hintergrund-Lauf: Markierung wird gesetzt und verfällt nach zwei Stunden
|
|
plugin_set_setting(NASBK, 'running_since', (string) time());
|
|
$check(nasbk_running(), 'Ein laufendes Backup wird erkannt');
|
|
plugin_set_setting(NASBK, 'running_since', (string) (time() - 8000));
|
|
$check(!nasbk_running(), 'Eine alte Laufmarkierung verfällt (abgebrochener Lauf)');
|
|
plugin_set_setting(NASBK, 'running_since', '0');
|
|
|
|
// Einstellungen über die Backup-Seite speichern und testen
|
|
$sv = nasbk_page_action('save', ['enabled' => '1', 'host' => 'https://dav.example.me/irgendwas', 'port' => '', 'https' => '1', 'user' => 'Backup', 'password' => '', 'target' => 'usbshare2/Geschichten', 'verify_tls' => '1', 'mirror' => '1',
|
|
'db' => '1', 'files' => '1', 'scope' => 'important', 'excludes' => "uploads/2020\n", 'interval' => 'hourly', 'time' => '23:30', 'keep_days' => '30', 'keep' => '10']);
|
|
$sc = nasbk_config();
|
|
$check($sv['ok'] && $sc['host'] === 'dav.example.me' && $sc['port'] === 443 && $sc['https'] && $sc['target'] === '/usbshare2/Geschichten/' && $sc['url'] === 'https://dav.example.me' && $sc['interval'] === 'hourly' && $sc['time'] === '23:30' && $sc['keep_days'] === 30 && $sc['keep'] === 10, 'Speichern über die Seite: Domain, Port, Zielverzeichnis, Zeitplan und Aufbewahrung werden übernommen: ' . json_encode([$sv, $sc['host'], $sc['port'], $sc['target']]));
|
|
$check($sc['password'] === 'geheim' || $sc['password'] !== '', 'Ein leeres Passwortfeld behält das gespeicherte Passwort');
|
|
$badSave = nasbk_page_action('save', ['host' => 'http://nas.example.com', 'https' => '', 'port' => '80']);
|
|
$check(!$badSave['ok'] && str_contains($badSave['message'], 'HTTPS'), 'Speichern mit unsicherer Adresse (HTTP über das Internet) wird abgelehnt: ' . $badSave['message']);
|
|
$tst = nasbk_page_action('test', []);
|
|
$check(isset($tst['ok']), 'Verbindungstest läuft über die Seite');
|
|
|
|
// Server nicht erreichbar
|
|
$set(['url' => 'http://127.0.0.1:1/dav', 'host' => '']);
|
|
$set(['url' => 'http://127.0.0.1:1/dav']);
|
|
$run = nasbk_run($pdo, true);
|
|
$check(!$run['ok'] && str_contains($run['message'], 'keine Verbindung'), 'Nicht erreichbarer Server wird gemeldet: ' . $run['message']);
|
|
} finally {
|
|
proc_terminate($proc);
|
|
proc_close($proc);
|
|
}
|
|
|
|
usleep(300000);
|
|
$rm = function (string $d) use (&$rm) {
|
|
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") && !is_link("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
|
|
@rmdir($d);
|
|
};
|
|
foreach ([$pluginDir, $davRoot, $localDir] as $d) if (str_starts_with($d, sys_get_temp_dir() . '/deskhub-')) $rm($d);
|
|
@unlink($log);
|
|
@unlink($zipPath);
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "NAS backup plugin checks passed ({$total} Prüfungen).\n";
|