* https://www.tsv08kulmbach.de/cron.php?job=instagram&key= * https://www.tsv08kulmbach.de/cron.php?job=logrotate&key= * * 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. * * --------------------------------------------------------------------------- * DIE DREI CRONJOBS IM KAS (Zeitraster des KAS, nicht Crontab-Syntax) * --------------------------------------------------------------------------- * 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. * * 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. * * 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. * * Die Minuten sind versetzt, damit sich zwei Läufe nicht überlappen (die Syncs sperren * sich per Lock, sonst fiele einer aus). * * 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 * --------------------------------------------------------------------------- * php bin/preflight.php Config, Secrets, Schreibrechte, vendor/, PHP-Version prüfen * php bin/smtp-test.php SMTP-Login testen (ohne Mailversand) * danach je eine Testmail über /kontakt, /mitglied-werden und /sportheimbuchung * * IM BETRIEB * php bin/logs.php Läuft alles? Fehler der letzten 7 Tage + Stand aller Kanäle * * Für die Dateiablage /dateien zusätzlich: * storage/dateien/uploads und storage/dateien/system müssen beschreibbar sein * php bin/filesgallery-update.php --check (Patch + self-hosted Assets prüfen) * php bin/dateien-init.php Ordnerstruktur Jahr → Abteilung anlegen */ return [ // Kanonische Basis-URL ohne trailing slash (für Canonical, OG, Sitemap). 'base_url' => 'https://www.tsv08kulmbach.de', // 'production' oder 'development' (steuert Fehleranzeige). 'env' => 'production', // Geheimer Schlüssel für die HMAC-Time-Trap der Formulare und den IP-Hash im Spam-Log. // Generieren mit: php -r "echo bin2hex(random_bytes(32));" // Für Produktion einen EIGENEN Wert erzeugen — nie den aus der lokalen Dev-Config // übernehmen. Solange hier CHANGE_ME steht, weisen die Formular-Actions jeden // Versand ab (config_problems()), damit die Time-Trap nie mit einem bekannten // Schlüssel läuft. 'app_secret' => 'CHANGE_ME', // All-Inkl SMTP (Postfach im KAS anlegen; Host = .kasserver.com). // Verbindung testen ohne Mailversand: php bin/smtp-test.php // // „SMTP Error: Could not authenticate" auf dem Server, obwohl dieselben Daten vom // Entwicklungsrechner angenommen werden? Am 31.07.2026 war das VORÜBERGEHEND: // derselbe Weg lief Minuten später fehlerfrei, ohne Änderung an der Config // (vermutlich der Brute-Force-Schutz des Mailservers). Erst `php bin/smtp-test.php --probe` // laufen lassen, bevor irgendetwas hier geändert wird — und nicht in Serie // wiederholen, jeder Fehlversuch ist ein fehlgeschlagener Login. // // Bekannter RÜCKFALLWEG auf diesem Webspace, falls die Anmeldung öfter kippt: // host 'localhost', port 25, ohne Verschlüsselung und OHNE Anmeldung — der lokale // Relay des Hosters nimmt uns an (am 31.07.2026 nachgewiesen). Dann kann eine // Anmeldung gar nicht mehr fehlschlagen. Bewusst NICHT der Standard: mit // 'localhost' liefen Dev und Produktion auseinander (lokal gibt es keinen // Mailserver), und ein Codepfad ist leichter zu pflegen als zwei. 'smtp' => [ 'host' => 'w01ca75d.kasserver.com', 'port' => 587, // STARTTLS 'username' => 'noreply@tsv08kulmbach.de', // volle Postfach-Adresse 'password' => 'POSTFACH_PASSWORT', // Absender: das authentifizierte Postfach (gleiche Domain, sonst lehnt All-Inkl ab). 'from' => 'noreply@tsv08kulmbach.de', 'from_name' => 'TSV 08 Kulmbach', // Empfänger der Formular-Mails. 'to' => 'info@tsv08kulmbach.de', ], // 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: 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 // geleaktem Link davor, dass BFV oder Instagram unsere Server-IP sperren. // Muss kleiner sein als der engste gewünschte Takt (½-stündlich = 1800 s). 'min_interval' => 240, ], // Instagram-Scraper (bin/instagram-sync.php). 'instagram' => [ 'username' => 'tsv08kulmbach', 'max_posts' => 9, ], // Zentrale News-Seite (app/pages/news.php) — mischt data/news.json, // Instagram-Cache und Matchcenter-Ergebnisse; hier nur die Obergrenze für // die Gesamtliste (News allein könnte sonst unbegrenzt wachsen). 'news' => [ 'max_items' => 20, ], // Dateiablage /dateien (Files Gallery, vendored unter public/dateien/) — der Verein // lädt dort Fotos und Videos hoch, eingearbeitet werden sie danach von Hand. // Konfiguriert wird die App in public/dateien/_filesconfig.php; hier stehen nur die // Geheimnisse. Passwort-Hash erzeugen mit: // php -r "echo password_hash('DAS-PASSWORT', PASSWORD_DEFAULT), PHP_EOL;" // Klartext-Passwörter würden auch funktionieren (index.php:507) — aber nicht in einer Datei. // Solange hier CHANGE_ME steht, ist der Zugang gesperrt (kein Passwort = kein Login). 'dateien' => [ 'license_key' => 'CHANGE_ME', // Vollversion-Schlüssel von files.gallery 'users' => [ // Gemeinsames Konto zum Hochladen — Passwort im Verein weitergeben. 'verein' => ['password_hash' => 'CHANGE_ME'], // Verwaltung (Aufräumen, Sortieren, Löschen). 'admin' => ['password_hash' => 'CHANGE_ME'], ], ], // Matchcenter-Scraper (bin/matchcenter-sync.php) — Spielplan/Ergebnisse/Tabelle // aus der öffentlichen BFV-Widget-JSON-API. Die IDs sind keine Geheimnisse // (sie steckten schon im alten BFV-Widget-Embed) und dürfen hier stehen. 'matchcenter' => [ // Referer für die BFV-Requests (gegen HTTP 418). 'referer' => 'https://widget-prod.bfv.de/', 'max_upcoming' => 5, // anstehende Spiele pro Team + im globalen Slider 'max_results' => 5, // letzte Ergebnisse pro Team // key = Schlüssel in data/teams.json (für kuratierten Namen/Liga/Slug), // team_id = BFV permanentId (saison-stabil). compound_id = Wettbewerbs-ID // der Tabelle — nur Fallback: der Scraper leitet die aktuelle compoundId // automatisch aus der Matches-API ab, sodass eine neue Saison von selbst // übernommen wird. compound_id nur pflegen, falls die Auto-Erkennung mal fehlt. 'teams' => [ ['key' => 'erste-mannschaft', 'team_id' => '016PKHIPIO000000VV0AG811VUDIC8D7', 'compound_id' => '02TBKEVREG000006VS5489BTVVQ0O654-G'], // Zweite = Spielgemeinschaft „(SG1) TSV Melkendorf III/ TSV 08 Kulmbach II". // team_id ist die Saison-26/27-ID (reaktiviert 29.07.2026 — die alte ID // 016PCRVRJC… lieferte ab 26/27 nur noch den TSV Melkendorf ohne uns; bei // SG-Umbauten zur neuen Saison die ID von der BFV-Mannschaftsseite prüfen). ['key' => 'zweite-mannschaft', 'team_id' => '030RNM1V6K000000VS5489BRVVPSQ1LR'], ['key' => 'damen', 'team_id' => '01HLQ7QVES000000VV0AG80NVVKQLCU9', 'compound_id' => '02TK5AGFE0000004VS5489BUVVPGA9RV-G'], ], ], ];