Takt über public/cron.php: KAS-Cronjobs können nur URLs

Korrektur meiner eigenen Annahme von heute früh. Der Tarif hat SSH und
Cronjobs, aber das KAS-Formular kennt nur ein Feld "Protokoll / Pfad" mit
https:// — ein `php bin/…` ist dort nicht eintragbar. Der Endpoint, den ich
vorhin stillgelegt hatte, ist damit wieder der einzige Takt-Geber, und zwar in
einer besseren Form als geplant: Aufrufer ist der Hoster selbst, der Schlüssel
verlässt den Webspace nie, kein Dritter ist beteiligt.

Dritter Job 'logrotate' in der Whitelist, denn ohne ihn liefe die Logpflege nie
automatisch und die 2-MB-Grenze samt 90-Tage-Frist wäre toter Code. Er passt in
denselben Rahmen wie die Syncs: kein Parameter aus der URL, keine Verbindung nach
außen, schreibt nur in storage/logs. bin/log-rotate.php trägt dafür denselben
Riegel wie die Syncs (CRON_HTTP) und require_once. Eine vierte Ausnahme braucht
wieder denselben Aufwand an Begründung.

Geprüft: falscher Schlüssel 403, unbekannter Job 403, gültiger Aufruf 204 mit
leerem Body, sofortige Wiederholung 204 plus WARN, Logdateien unversehrt.

Zeitraster angepasst, weil das KAS Wochentage nicht mit Halbstunden kombiniert:
Matchcenter stündlich statt 2x/Tag plus Wochenende, Instagram als zwei
Tageseinträge, Logpflege täglich nachts. Die Abwägung steht in
config.example.php und docs/deploy.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
This commit is contained in:
2026-07-31 17:16:12 +02:00
parent 964ffb932b
commit 05c64c8f12
5 changed files with 133 additions and 94 deletions

View File

@@ -7,57 +7,49 @@
* config.php ist gitignored und liegt außerhalb des Webroots (zusätzlich per .htaccess gesperrt).
*
* ---------------------------------------------------------------------------
* TAKT DER SYNC-SKRIPTE — zwei Wege, je nach Tarif
* TAKT DER SYNC-SKRIPTE — über public/cron.php, aufgerufen vom KAS-Cron
* ---------------------------------------------------------------------------
* A) Der Tarif hat Cronjobs → Shell-Cronjobs unten verwenden. Sauberster Weg,
* kein Endpoint, kein Dritter im Spiel.
* ►►► DAS IST SEIT 31.07.2026 UNSER WEG: der Tarif hat SSH und Shell-Cronjobs.
* Deshalb steht `cron.key` unten leer und public/cron.php ist stillgelegt.
* B) Der Tarif hat KEINE Cronjobs (so war es bis 30.07.2026, das KAS meldete
* „in deinem Tarif nicht verfügbar“) → public/cron.php + externer Cron-Dienst.
* Nur noch als Rückweg dokumentiert, falls der Shell-Cron ausfällt.
* Dann `cron.key` unten setzen und beim Dienst diese URLs im gewünschten Takt
* aufrufen lassen (Schlüssel besser als Header X-Cron-Key als in der URL):
* Geprüft im KAS am 31.07.2026: der Tarif hat SSH und Cronjobs, aber die Cronjobs
* können ausschließlich **URLs** aufrufen. Ein `php bin/…` ist dort nicht eintragbar
* (das Formular hat nur ein Feld „Protokoll / Pfad" mit https://). Also läuft der Takt
* über public/cron.php, und der Aufrufer ist der Hoster selbst — kein Dritter, und der
* Schlüssel verlässt den Webspace nie.
*
* https://www.tsv08kulmbach.de/cron.php?job=matchcenter
* https://www.tsv08kulmbach.de/cron.php?job=instagram
* URLs (Schlüssel = cron.key unten):
* https://www.tsv08kulmbach.de/cron.php?job=matchcenter&key=<cron.key>
* https://www.tsv08kulmbach.de/cron.php?job=instagram&key=<cron.key>
* https://www.tsv08kulmbach.de/cron.php?job=logrotate&key=<cron.key>
*
* Takt wie unten bei den Cronjobs. Der Dienst muss keine Antwort auswerten:
* 204 = angenommen oder wegen Mindestabstand übersprungen, 403 = Schlüssel oder
* Job falsch (dann ist der Dienst falsch eingerichtet und es syncte nichts).
* Erwarteter Fehlerfall: Instagram sperrt Rechenzentrums-IPs. Einmal von Hand
* prüfen, ob der Scraper auf dem Server überhaupt durchkommt.
* Antwort ist immer leer: 204 = gelaufen oder wegen Mindestabstand übersprungen,
* 403 = Schlüssel oder Job falsch. Der KAS-Cron muss nichts auswerten. Das
* E-Mail-Feld im KAS kann leer bleiben; ob die Syncs laufen, sagt `php bin/logs.php`,
* und `php bin/preflight.php` warnt, wenn ein Datenstand zu alt wird.
*
* ---------------------------------------------------------------------------
* CRONJOBS (im KAS anlegen, Typ „eigener Cronjob“ / Shell) — nur Weg A
* DIE DREI CRONJOBS IM KAS (Zeitraster des KAS, nicht Crontab-Syntax)
* ---------------------------------------------------------------------------
* SITE=/www/htdocs/w01ca75d/tsv08kulmbach-website (echter Pfad, ermittelt 31.07.2026)
* PHP=/usr/bin/php (8.3.29; mit `which php` gegenprüfen)
* 1. Matchcenter — „stündlich", Minute 37
* Das KAS kann keine Wochentage mit Halbstunden-Takt kombinieren, wie es die
* ursprüngliche Crontab vorsah (Sa+So 13-21 Uhr halbstündlich). Stündlich rund um
* die Uhr ist der brauchbare Ersatz: am Spieltag stehen Ergebnis und Tabelle
* spätestens eine Stunde später. Kostet 24 Läufe/Tag statt 2 plus Wochenende.
* Wer den Verkehr zum BFV klein halten will, nimmt stattdessen zwei „täglich"-Jobs
* (07:37 und 19:37) und lebt damit, dass Samstagsergebnisse erst abends stehen.
*
* Die Fehlerausgabe geht bewusst nach php-errors.log und NICHT in die Kanal-Logs:
* die Skripte schreiben ihren Verlauf selbst über log_write() im Format
* `[Zeit] LEVEL Meldung`, und bin/logs.php wertet Kanäle nach Level aus. Eine rohe
* PHP-Warnung dazwischen bricht das Format. php-errors.log ist die Datei für PHPs
* eigene Ausgabe, und bin/logs.php behandelt sie schon als "eigenes Format".
* Nach /dev/null darf nur log-rotate: ein Fatal dort ist harmlos, ein Fatal in einem
* Sync (etwa fehlendes vendor/ nach einem halben Deploy) soll auffallen.
* 2. Instagram — ZWEI Jobs „täglich", 06:17 und 18:17
* Zwei Einträge, weil „täglich" nur einen Zeitpunkt kennt. Bewusst nicht stündlich:
* Instagram sperrt auffällige Zugriffsmuster deutlich schneller als der BFV.
*
* # Instagram-Feed: 2×/Tag (versetzte Minuten, damit die Läufe sich nicht überlappen)
* 17 6,18 * * * cd $SITE && $PHP bin/instagram-sync.php >> storage/logs/php-errors.log 2>&1
* 3. Logpflege — „täglich", 03:23 (`?job=logrotate`)
* Rotiert erst über 2 MB und löscht Einträge älter als 90 Tage, ein täglicher Lauf
* ist deshalb meistens ein No-Op. Wöchentlich genügt auch.
*
* # Matchcenter: 2×/Tag als Grundtakt
* 37 7,19 * * * cd $SITE && $PHP bin/matchcenter-sync.php >> storage/logs/php-errors.log 2>&1
* Die Minuten sind versetzt, damit sich zwei Läufe nicht überlappen (die Syncs sperren
* sich per Lock, sonst fiele einer aus).
*
* # Matchcenter am Spieltag-Nachmittag/Abend ½-stündlich (Sa+So 1321 Uhr),
* # damit Ergebnisse und Tabelle zeitnah stehen.
* 7,37 13-21 * * 6,0 cd $SITE && $PHP bin/matchcenter-sync.php >> storage/logs/php-errors.log 2>&1
*
* # Logpflege: sonntags nachts. Rotiert Dateien über 2 MB und löscht Einträge älter als 90 Tage.
* 23 3 * * 0 cd $SITE && $PHP bin/log-rotate.php > /dev/null 2>&1
*
* Alle Skripte sind CLI-only; die Syncs sperren sich per Lock gegen Parallelläufe und lassen
* bei jedem Fehler den letzten guten Cache stehen — ein fehlgeschlagener Lauf schadet nie.
* Erstlauf nach dem Deploy einmal von Hand anstoßen (füllt Cache + Bilder/Wappen).
* Alle Skripte sind CLI-only bzw. laufen über den authentifizierten Endpoint; die Syncs
* lassen bei jedem Fehler den letzten guten Cache stehen — ein fehlgeschlagener Lauf
* schadet nie. Erstlauf nach dem Deploy einmal per SSH anstoßen (füllt Cache + Bilder).
*
* ---------------------------------------------------------------------------
* NACH DEM DEPLOY
@@ -117,12 +109,12 @@ return [
'to' => 'info@tsv08kulmbach.de',
],
// Cron-Endpoint public/cron.php — nur nötig, wenn der Tarif keine Cronjobs hat
// (Weg B oben). Leer oder 'CHANGE_ME' lassen heißt: der Endpoint ist zu und
// antwortet auf jeden Aufruf mit 403.
// Cron-Endpoint public/cron.php — der Takt-Geber (siehe oben). Leer oder
// 'CHANGE_ME' heißt: der Endpoint ist zu und antwortet auf jeden Aufruf mit 403,
// dann läuft KEIN Sync mehr.
'cron' => [
// Langes Zufallsgeheimnis, NICHT app_secret wiederverwenden — der Schlüssel
// reist bei einem externen Dienst mit und darf dort nichts anderes aufschließen.
// Langes Zufallsgeheimnis, NICHT app_secret wiederverwenden: dieser Schlüssel
// steht im KAS in einer Cronjob-URL und darf nichts anderes aufschließen.
// Erzeugen: php -r 'echo bin2hex(random_bytes(24)), "\n";'
'key' => 'CHANGE_ME',
// Mindestabstand zwischen zwei Läufen desselben Jobs in Sekunden. Schützt bei