Fix #209: analyse-nextcloud.sh prüft, ob das Importskript vorhanden und ladbar ist

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-09-20 17:33:07 +02:00
parent 69fcc84301
commit b7508fe401
4 changed files with 67 additions and 3 deletions

View file

@ -2,6 +2,11 @@
---
## 2026-09-20 – Analyse prüft das Importskript (#209)
- **`scripts/analyse-nextcloud.sh`:** neuer Abschnitt 6 prüft, ob `scripts/container/nextcloud-import.php` im Container existiert und syntaktisch ladbar ist (`php -l`, ohne Ausführung), und nennt den Pfad, den der Host-Cronjob verwenden muss. Fehlt das Skript, endet die Analyse mit Exit 1 – auch bei erfolgreicher Verbindung. Ein `--dry-run`-Schalter für den Import wurde nicht ergänzt.
- **`tests/75_AnalyseNextcloudSkriptTest.php`:** prüft die Existenzabfrage und dass das Importskript nie ausgeführt wird.
## 2026-09-20 – Überwachung des Nextcloud-Imports (#208)
- **`migrations/096_nextcloud_watchdog.sql`** (neu): `company_settings.nextcloud_last_run` (Zeitstempel des letzten Laufs) und `nextcloud_alarm_stunden` (Schwelle, Default 24, 0 = aus).

View file

@ -96,7 +96,7 @@ sudo usermod -aG docker $USER
### Verbindung analysieren
Meldet der Import „PROPFIND fehlgeschlagen" oder findet er keine Dateien, grenzt `scripts/analyse-nextcloud.sh` die Ursache ein. Das Skript wird auf dem Host aufgerufen (nicht im Container) und arbeitet rein lesend – es importiert, verschiebt und löscht nichts. Das Passwort wird nie im Klartext ausgegeben.
Meldet der Import „PROPFIND fehlgeschlagen" oder findet er keine Dateien, grenzt `scripts/analyse-nextcloud.sh` die Ursache ein. Das Skript wird auf dem Host aufgerufen (nicht im Container) und arbeitet rein lesend – es importiert, verschiebt und löscht nichts. Das Passwort wird nie im Klartext ausgegeben. Zum Schluss prüft die Analyse, ob `scripts/container/nextcloud-import.php` im Container vorhanden und ladbar ist (ohne es auszuführen), und nennt den Pfad, den der Host-Cronjob verwenden muss.
```bash
./scripts/analyse-nextcloud.sh # Konfiguration, DNS, Verbindungsversuch

View file

@ -7,13 +7,16 @@
# Schlägt der fehl, grenzt das Script die Ursache über Gegenproben ein:
# ohne IP-Pinning, nur IPv4, nur IPv6.
#
# Zuletzt wird geprüft, ob das Importskript im Container existiert und ladbar
# ist (der Cronjob muss denselben Pfad verwenden).
#
# Rein lesend – es werden keine Belege importiert, verschoben oder gelöscht.
# Das Passwort wird nie im Klartext ausgegeben.
#
# Usage:
# ./analyse-nextcloud.sh [--verbose]
#
# Exit-Code: 0 = Verbindung steht, 1 = Konfigurations- oder Verbindungsfehler
# Exit-Code: 0 = Verbindung steht und Importskript vorhanden, 1 = Konfigurations- oder Verbindungsfehler
# ════════════════════════════════════════════════════════════════════════════
set -euo pipefail
@ -145,6 +148,42 @@ function isOk(array $r): bool
return $r['errno'] === 0 && $r['code'] >= 200 && $r['code'] < 300;
}
/**
* Prüft, ob der Importlauf selbst startbar ist. Die Analyse läuft über die
* Standardeingabe und braucht das Importskript nicht – ein verschobenes oder
* fehlendes Skript bliebe sonst unbemerkt, obwohl der Cronjob ins Leere läuft.
* Das Skript wird nur auf Syntax geprüft (php -l), nicht ausgeführt.
*/
$checkImportScript = static function () use ($section, $green, $red, $bold): bool {
$section('6. Importskript (Cronjob-Ziel)');
$rel = 'scripts/container/nextcloud-import.php';
$path = '/var/www/html/' . $rel;
if (!is_file($path)) {
echo ' ' . $red('✗') . ' ' . $rel . ' fehlt im Container.' . PHP_EOL;
echo ' Der Cronjob findet das Skript nicht und der Import läuft nie.' . PHP_EOL;
echo ' Auch prüfen, ob der Bind-Mount ./scripts:/var/www/html/scripts den Image-Inhalt verdeckt.' . PHP_EOL;
return false;
}
echo ' ' . $green('✓') . ' ' . $rel . ' vorhanden' . PHP_EOL;
$output = [];
exec(escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($path) . ' 2>&1', $output, $code);
if ($code !== 0) {
echo ' ' . $red('✗') . ' Das Skript ist nicht ladbar: ' . implode(' ', $output) . PHP_EOL;
return false;
}
echo ' ' . $green('✓') . ' Syntax gültig' . PHP_EOL;
echo PHP_EOL . ' ' . $bold('Hinweis:') . ' Der Host-Cronjob muss genau diesen Pfad verwenden:' . PHP_EOL;
echo ' docker compose exec -T web php ' . $rel . PHP_EOL;
echo ' Mit `crontab -l` auf dem Host abgleichen.' . PHP_EOL;
return true;
};
// ── 1. Konfiguration ────────────────────────────────────────────────────────
$section('1. Konfiguration (Einstellungen → Nextcloud-Belegimport)');
@ -272,6 +311,7 @@ if (!isOk($result)) {
echo ' Netzwerkverbindung, Firewall und TLS-Zertifikat der Nextcloud prüfen.' . PHP_EOL;
}
$checkImportScript();
exit(1);
}
@ -328,6 +368,13 @@ if ($done !== null) {
}
}
$importOk = $checkImportScript();
if (!$importOk) {
echo PHP_EOL . ' ' . $red('Die Verbindung steht, aber der Importlauf ist nicht startbar.') . PHP_EOL . PHP_EOL;
exit(1);
}
echo PHP_EOL . ' ' . $green('Die Verbindung zur Nextcloud funktioniert.') . PHP_EOL . PHP_EOL;
exit(0);
PHPEOF

View file

@ -37,9 +37,21 @@ class AnalyseNextcloudSkriptTest extends TestCase
// Verweise auf NextcloudImportService im Kommentar sind erwünscht (die cURL-Optionen
// müssen dort synchron gehalten werden) – ausgeführt werden darf der Import nicht.
$this->assert(
!str_contains($content, 'nextcloud-import.php') && !str_contains($content, 'new NextcloudImportService'),
!str_contains($content, 'new NextcloudImportService'),
'Skript startet keinen Importlauf'
);
// Das Importskript wird nie ausgeführt – nur per Syntaxprüfung (php -l) angefasst.
$this->assert(
!preg_match('/\b(passthru|shell_exec|system|proc_open|include|require)\b[^;\n]*nextcloud-import/', $content)
&& str_contains($content, "' -l '"),
'Skript führt das Importskript nicht aus, sondern prüft nur die Syntax'
);
// Die Analyse muss die Existenz des Importskripts im Container prüfen.
$this->assert(
str_contains($content, 'scripts/container/nextcloud-import.php') && str_contains($content, 'is_file($path)'),
'Skript prüft, ob scripts/container/nextcloud-import.php im Container existiert'
);
// Das Passwort dient ausschließlich als cURL-Zugangsdatum. In Ausgaben darf
// es nur als Länge (strlen) auftauchen, nie im Klartext.