/tsv08kulmbach (echten Pfad einsetzen: pwd auf dem Server) * PHP=/usr/bin/php (All-Inkl ggf. /usr/local/bin/php8.2 — `which php8.2` prüft es) * * # 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/instagram.log 2>&1 * * # Matchcenter: 2×/Tag als Grundtakt * 37 7,19 * * * cd $SITE && $PHP bin/matchcenter-sync.php >> storage/logs/matchcenter.log 2>&1 * * # Matchcenter am Spieltag-Nachmittag/Abend ½-stündlich (Sa+So 13–21 Uhr), * # damit Ergebnisse und Tabelle zeitnah stehen. * 7,37 13-21 * * 6,0 cd $SITE && $PHP bin/matchcenter-sync.php >> storage/logs/matchcenter.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). * * --------------------------------------------------------------------------- * 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 — 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' => [ // Langes Zufallsgeheimnis, NICHT app_secret wiederverwenden — der Schlüssel // reist bei einem externen Dienst mit und darf dort 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'], ], ], ];