150 lines
6.6 KiB
PHP
150 lines
6.6 KiB
PHP
<?php
|
||
/**
|
||
* includes/crontab_installer.php
|
||
*
|
||
* Installiert/entfernt die 6 DeskHub-Cronjobs direkt in der System-Crontab des
|
||
* aktuellen Benutzers (crontab -l / crontab -). Nur sinnvoll auf einem eigenen
|
||
* (v)Server mit Shell-Zugriff für den Web-User – auf klassischem Shared-Hosting
|
||
* ist exec()/proc_open() fast immer gesperrt, siehe crontab_capability_check().
|
||
*
|
||
* Sicherheitsprinzip: Es wird NIE ein von außen beeinflussbarer String als
|
||
* Shell-Befehl ausgeführt. Die Crontab-Zeilen sind fest im Code hinterlegt
|
||
* (nur der erkannte PHP-Pfad und der DeskHub-Installationspfad sind variabel,
|
||
* beide serverseitig ermittelt, nicht aus einer Nutzereingabe). Nur der klar
|
||
* markierte "# DESKHUB-CRON-…"-Block wird verwaltet, alle anderen vorhandenen
|
||
* Crontab-Einträge des Nutzers bleiben unangetastet.
|
||
*/
|
||
|
||
const DESKHUB_CRON_MARKER_START = '# DESKHUB-CRON-START (automatisch verwaltet von DeskHub – Einstellungen → Cron-Jobs. Manuelle Änderungen hier werden beim erneuten Installieren überschrieben.)';
|
||
const DESKHUB_CRON_MARKER_END = '# DESKHUB-CRON-END';
|
||
|
||
/** Prüft, ob dieser Server eine automatische Installation überhaupt technisch zulässt. */
|
||
function crontab_capability_check(): array
|
||
{
|
||
// Crontab ist ein Unix-Mechanismus; auf Windows (z.B. lokale Entwicklung) von vornherein ausschließen,
|
||
// statt uns auf lokalisierte/unvorhersehbare Fehlertexte von cmd.exe verlassen zu müssen.
|
||
if (PHP_OS_FAMILY === 'Windows') {
|
||
return ['available' => false, 'reason' => 'Windows-Server werden nicht unterstützt (Crontab ist ein Unix/Linux-Mechanismus).'];
|
||
}
|
||
|
||
$disabled = array_map('trim', explode(',', (string) ini_get('disable_functions')));
|
||
foreach (['proc_open', 'shell_exec'] as $fn) {
|
||
if (!function_exists($fn) || in_array($fn, $disabled, true)) {
|
||
return ['available' => false, 'reason' => "PHP-Funktion \"$fn\" ist auf diesem Server gesperrt (typisch für Shared-Hosting)."];
|
||
}
|
||
}
|
||
|
||
$out = @shell_exec('crontab -l 2>&1');
|
||
if ($out === null) {
|
||
return ['available' => false, 'reason' => 'Der Befehl "crontab" konnte nicht ausgeführt werden.'];
|
||
}
|
||
// "no crontab for <user>" ist der normale Zustand, wenn noch keine Crontab existiert – das ist OK.
|
||
if (stripos($out, 'not found') !== false || stripos($out, 'command not found') !== false) {
|
||
return ['available' => false, 'reason' => 'Das Programm "crontab" ist auf diesem Server nicht installiert.'];
|
||
}
|
||
if (stripos($out, 'permission denied') !== false) {
|
||
return ['available' => false, 'reason' => 'Der Web-Server-Benutzer hat keine Berechtigung für crontab.'];
|
||
}
|
||
|
||
return ['available' => true, 'reason' => ''];
|
||
}
|
||
|
||
/** Versucht, den echten CLI-PHP-Pfad zu ermitteln (PHP_BINARY zeigt unter Apache/FPM auf den falschen Binary). */
|
||
function detect_php_cli_binary(): string
|
||
{
|
||
$which = trim((string) @shell_exec('command -v php 2>/dev/null'));
|
||
if ($which !== '' && is_executable($which)) {
|
||
return $which;
|
||
}
|
||
foreach (['/usr/bin/php', '/usr/local/bin/php', '/opt/php/bin/php'] as $candidate) {
|
||
if (is_executable($candidate)) {
|
||
return $candidate;
|
||
}
|
||
}
|
||
return 'php'; // Fallback: hoffen, dass PATH im Cron-Kontext passt
|
||
}
|
||
|
||
/** Baut den verwalteten DeskHub-Block mit den Job-Zeilen (fest hinterlegte Kommandos, keine Nutzereingabe). */
|
||
function crontab_marker_block(): string
|
||
{
|
||
$php = detect_php_cli_binary();
|
||
$root = realpath(__DIR__ . '/..');
|
||
$lines = [
|
||
DESKHUB_CRON_MARKER_START,
|
||
"*/5 * * * * $php $root/cron/fetch_emails.php >> $root/cron/fetch_emails.log 2>&1",
|
||
"* * * * * $php $root/cron/run_plugins.php >> $root/cron/run_plugins.log 2>&1",
|
||
"*/15 * * * * $php $root/cron/check_sla.php >> $root/cron/check_sla.log 2>&1",
|
||
"0 * * * * $php $root/cron/check_escalations.php >> $root/cron/check_escalations.log 2>&1",
|
||
"0 6 * * * $php $root/cron/process_recurring.php >> $root/cron/process_recurring.log 2>&1",
|
||
"0 2 * * * $php $root/cron/process_backups.php >> $root/cron/process_backups.log 2>&1",
|
||
"0 3 * * * $php $root/cron/anonymize_data.php >> $root/cron/anonymize_data.log 2>&1",
|
||
DESKHUB_CRON_MARKER_END,
|
||
];
|
||
return implode("\n", $lines);
|
||
}
|
||
|
||
function crontab_get_current(): string
|
||
{
|
||
$out = @shell_exec('crontab -l 2>/dev/null');
|
||
return $out !== null ? $out : '';
|
||
}
|
||
|
||
function crontab_is_installed(): bool
|
||
{
|
||
return str_contains(crontab_get_current(), DESKHUB_CRON_MARKER_START);
|
||
}
|
||
|
||
/** Entfernt einen ggf. vorhandenen DeskHub-Block aus einem Crontab-Text (Hilfsfunktion für install+remove). */
|
||
function crontab_strip_marker_block(string $content): string
|
||
{
|
||
$pattern = '/^' . preg_quote(DESKHUB_CRON_MARKER_START, '/') . '.*?^' . preg_quote(DESKHUB_CRON_MARKER_END, '/') . '\n?/ms';
|
||
return preg_replace($pattern, '', $content) ?? $content;
|
||
}
|
||
|
||
/** Schreibt einen kompletten neuen Crontab-Inhalt für den aktuellen Benutzer. */
|
||
function crontab_write(string $content): array
|
||
{
|
||
$descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
|
||
$proc = @proc_open('crontab -', $descriptors, $pipes);
|
||
if (!is_resource($proc)) {
|
||
return ['ok' => false, 'error' => 'crontab-Prozess konnte nicht gestartet werden.'];
|
||
}
|
||
fwrite($pipes[0], $content);
|
||
fclose($pipes[0]);
|
||
$stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
|
||
$stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
|
||
$code = proc_close($proc);
|
||
if ($code !== 0) {
|
||
return ['ok' => false, 'error' => trim($stderr) ?: trim($stdout) ?: "crontab beendete mit Fehlercode $code"];
|
||
}
|
||
return ['ok' => true, 'error' => ''];
|
||
}
|
||
|
||
/** Installiert/aktualisiert den DeskHub-Block in der Crontab des aktuellen Benutzers. */
|
||
function crontab_install(): array
|
||
{
|
||
$check = crontab_capability_check();
|
||
if (!$check['available']) {
|
||
return ['ok' => false, 'error' => $check['reason']];
|
||
}
|
||
$current = crontab_strip_marker_block(crontab_get_current());
|
||
$current = rtrim($current, "\n");
|
||
$new = ($current !== '' ? $current . "\n\n" : '') . crontab_marker_block() . "\n";
|
||
return crontab_write($new);
|
||
}
|
||
|
||
/** Entfernt den DeskHub-Block aus der Crontab, lässt alle anderen Einträge unangetastet. */
|
||
function crontab_uninstall(): array
|
||
{
|
||
$check = crontab_capability_check();
|
||
if (!$check['available']) {
|
||
return ['ok' => false, 'error' => $check['reason']];
|
||
}
|
||
$current = crontab_get_current();
|
||
if (!str_contains($current, DESKHUB_CRON_MARKER_START)) {
|
||
return ['ok' => true, 'error' => '']; // war gar nicht installiert
|
||
}
|
||
$new = crontab_strip_marker_block($current);
|
||
return crontab_write($new);
|
||
}
|