102 lines
7.2 KiB
PHP
102 lines
7.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft, dass der echte Server-Cronjob (cron/run_plugins.php) die Hintergrundaufgaben der Plugins auslöst (Hook "cron.run"),
|
|
* nicht doppelt läuft, im Crontab-Block steht und im eingebauten Hintergrund-Betrieb als Job geführt wird.
|
|
*/
|
|
|
|
$pluginDir = sys_get_temp_dir() . '/deskhub-cj-' . bin2hex(random_bytes(4));
|
|
$db = sys_get_temp_dir() . '/deskhub-cj-' . bin2hex(random_bytes(4)) . '.sqlite';
|
|
$marker = sys_get_temp_dir() . '/deskhub-cj-marker-' . bin2hex(random_bytes(4)) . '.txt';
|
|
mkdir($pluginDir, 0755, true);
|
|
foreach (['DESKHUB_PLUGINS_DIR' => $pluginDir, 'DESKHUB_TESTING' => '1', 'DESKHUB_CATALOG_DEFAULTS' => __DIR__ . '/fixtures/no-catalog.php', 'DESKHUB_SQLITE_PATH' => $db, 'DESKHUB_CJ_MARKER' => $marker] as $k => $v) putenv("{$k}={$v}");
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
|
|
// Kleines Plugin, das bei jedem Lauf eine Zeile in eine Datei schreibt
|
|
$zipPath = sys_get_temp_dir() . '/cj-' . bin2hex(random_bytes(3)) . '.zip';
|
|
$zip = new ZipArchive();
|
|
$zip->open($zipPath, ZipArchive::CREATE);
|
|
$zip->addFromString('cronprobe/plugin.json', json_encode(['slug' => 'cronprobe', 'name' => 'Cron-Probe', 'version' => '1.0.0', 'permissions' => ['hooks.cron']]));
|
|
$zip->addFromString('cronprobe/plugin.php', "<?php\nplugin_add_hook('cron.run', function () { file_put_contents(getenv('DESKHUB_CJ_MARKER'), 'lauf' . PHP_EOL, FILE_APPEND); });\nplugin_add_hook('cron.minute', function () { file_put_contents(getenv('DESKHUB_CJ_MARKER'), 'minute' . PHP_EOL, FILE_APPEND); });\n");
|
|
$zip->close();
|
|
$check(plugin_install_zip($pdo, $zipPath)['ok'], 'Test-Plugin lässt sich installieren');
|
|
$check(plugin_set_active($pdo, 'cronprobe', true, ['hooks.cron'])['ok'], 'Test-Plugin lässt sich aktivieren');
|
|
|
|
$php = escapeshellarg(PHP_BINARY);
|
|
$script = escapeshellarg(dirname(__DIR__) . '/cron/run_plugins.php');
|
|
exec("{$php} {$script} 2>&1", $out, $code);
|
|
$check($code === 0 && str_contains(implode("\n", $out), '1 aktive'), 'Cronjob löst die Plugin-Aufgaben aus: ' . implode(' | ', $out));
|
|
$n = fn(string $w) => substr_count((string) @file_get_contents($marker), $w);
|
|
$check(is_file($marker) && $n('lauf') === 1 && $n('minute') === 1, 'beide Hooks des Plugins wurden genau einmal ausgeführt (Minute und 5-Minuten-Lauf)');
|
|
unset($out);
|
|
exec("{$php} {$script} 2>&1", $out, $code);
|
|
$check($code === 0 && str_contains(implode("\n", $out), 'Übersprungen') && $n('lauf') === 1 && $n('minute') === 1, 'direkt danach läuft er nicht doppelt');
|
|
$last = (string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'cron_run_plugins_last_run'")->fetchColumn();
|
|
$check($last !== '' && strtotime($last) > time() - 60, 'letzter Lauf ist vermerkt (Anzeige unter Einstellungen → Cron-Jobs)');
|
|
$srcRow = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('cron_run_plugins_source','cron_run_plugins_last_cli')")->fetchAll(PDO::FETCH_KEY_PAIR);
|
|
$check(($srcRow['cron_run_plugins_source'] ?? '') === 'cron' && strtotime((string) ($srcRow['cron_run_plugins_last_cli'] ?? '')) > time() - 60, 'der Lauf wird als „echter Cronjob“ vermerkt (Quelle und Zeit)');
|
|
|
|
// Der eingebaute Hintergrund-Betrieb hat die Minute schon belegt: Der Lauf wird übersprungen, der echte Cronjob gilt trotzdem als lebendig
|
|
$pdo->prepare("DELETE FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->execute();
|
|
unset($out);
|
|
exec("{$php} {$script} 2>&1", $out, $code);
|
|
$lastCli = (string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->fetchColumn();
|
|
$check(str_contains(implode("
|
|
", $out), 'Übersprungen') && $lastCli !== '' && strtotime($lastCli) > time() - 60, 'auch ein übersprungener Cron-Lauf wird als „echter Cronjob lebt“ vermerkt');
|
|
|
|
// Eine Minute später: das Minuten-Ereignis kommt wieder, der 5-Minuten-Lauf noch nicht
|
|
$setAgo = fn(string $key, int $sec) => $pdo->prepare('UPDATE settings SET setting_value = ? WHERE setting_key = ?')->execute([date('Y-m-d H:i:s', time() - $sec), $key]);
|
|
$setAgo('cron_run_plugins_last_run', 70);
|
|
unset($out);
|
|
exec("{$php} {$script} 2>&1", $out, $code);
|
|
$check($n('minute') === 2 && $n('lauf') === 1, 'nach einer Minute: Minuten-Ereignis ja, 5-Minuten-Ereignis noch nicht');
|
|
// Fünf Minuten später: beide
|
|
$setAgo('cron_run_plugins_last_run', 70);
|
|
$setAgo('cron_plugins_run_last', 310);
|
|
unset($out);
|
|
exec("{$php} {$script} 2>&1", $out, $code);
|
|
$check($n('minute') === 3 && $n('lauf') === 2, 'nach fünf Minuten: beide Ereignisse');
|
|
|
|
// Der Webzugriff bleibt gesperrt
|
|
$src = (string) file_get_contents(dirname(__DIR__) . '/cron/run_plugins.php');
|
|
$check(str_contains($src, "php_sapi_name() !== 'cli' && !defined('DESKHUB_CRON_RUNNER')") && str_contains($src, 'Kein Web-Zugriff'), 'Webzugriff auf das Skript ist gesperrt');
|
|
|
|
// Eintrag im Crontab-Block und im eingebauten Hintergrund-Betrieb
|
|
require_once dirname(__DIR__) . '/includes/crontab_installer.php';
|
|
$block = crontab_marker_block();
|
|
$check(preg_match('#^\* \* \* \* \* .*/cron/run_plugins\.php#m', $block) === 1, 'Crontab-Block enthält den Plugin-Job jede Minute');
|
|
$defs = pseudo_cron_job_definitions();
|
|
$check(isset($defs['run_plugins']) && $defs['run_plugins']['fn'] === 'run_plugins_cron' && $defs['run_plugins']['interval'] === 60 && is_file(dirname(__DIR__) . '/cron/' . $defs['run_plugins']['file']), 'eingebauter Hintergrund-Betrieb führt den Job (jede Minute)');
|
|
$settingsSrc = (string) file_get_contents(dirname(__DIR__) . '/staff/settings.php');
|
|
// Jeder Job der Statusliste braucht eine Anzeige „letzter Lauf“ (sonst bleibt die Zeit leer)
|
|
preg_match('/\$cronJobs = \[(.*?)\n\];/s', $settingsSrc, $mj);
|
|
preg_match('/\$cronLastRun = \[(.*?)\n \];/s', $settingsSrc, $ml);
|
|
preg_match_all("/^\s*'([a-z_]+)'\s*=>/m", $mj[1] ?? '', $jobKeys);
|
|
preg_match_all("/^\s*'([a-z_]+)'\s*=>/m", $ml[1] ?? '', $lastKeys);
|
|
$check(count($jobKeys[1]) >= 10 && array_diff($jobKeys[1], $lastKeys[1]) === [], 'jeder Job der Statusliste hat eine Anzeige des letzten Laufs (fehlt: ' . implode(', ', array_diff($jobKeys[1], $lastKeys[1])) . ')');
|
|
$check(str_contains($settingsSrc, "get_setting('cron_run_plugins_last_run')"), 'der Plugin-Job liest seinen letzten Lauf aus der Einstellung, die das Skript schreibt');
|
|
$check(str_contains($settingsSrc, "'run_plugins'") && str_contains($settingsSrc, 'run_plugins_cron'), 'Job steht in der Statusliste unter Einstellungen → Cron-Jobs');
|
|
$check(!str_contains((string) file_get_contents(dirname(__DIR__) . '/includes/pseudo_cron.php'), "plugin_hook('cron.run'"), 'Plugin-Aufgaben werden nicht noch ein zweites Mal im Hintergrund-Betrieb angestoßen');
|
|
$check(plugin_hook_permission('cron.minute') === 'hooks.cron' && plugin_hook_permission('cron.run') === 'hooks.cron', 'das Minuten-Ereignis braucht dieselbe Berechtigung wie cron.run');
|
|
|
|
foreach (scandir($pluginDir) ?: [] as $entry) {
|
|
if ($entry === '.' || $entry === '..') continue;
|
|
is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry);
|
|
}
|
|
@rmdir($pluginDir);
|
|
@unlink($zipPath);
|
|
@unlink($db);
|
|
@unlink($marker);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo 'Plugin cron job checks passed.' . "\n";
|