2026-07-31 16:13:14 +02:00
|
|
|
# Deploy auf All-Inkl (Shared Hosting, SSH + Cronjobs)
|
|
|
|
|
|
|
|
|
|
Schritt für Schritt vom leeren Webspace zur Live-Seite. Die Reihenfolge ist Absicht:
|
|
|
|
|
**die alte CMS-Seite bleibt online, bis die neue auf einer geschützten Test-Subdomain
|
|
|
|
|
nachweislich läuft.** Umgeschaltet wird erst am Ende, und das Umschalten ist in einer
|
|
|
|
|
Minute rückgängig.
|
|
|
|
|
|
|
|
|
|
Der Kern des Plans: die Test-Subdomain läuft mit **derselben** `config.php`, die
|
|
|
|
|
später live geht. Es gibt keinen Config-Wechsel beim Umschalten, also auch keinen
|
|
|
|
|
Fehler, der sich dabei einschleichen kann. Möglich ist das, weil die
|
|
|
|
|
Host-Weiterleitung in `public/.htaccess` nur beim exakten Host `tsv08kulmbach.de`
|
|
|
|
|
greift; eine Subdomain läuft unbehelligt durch. Einzige Abweichung: Canonical, OG
|
|
|
|
|
und Sitemap nennen auf der Test-Subdomain schon die echte Domain. Genau richtig für
|
|
|
|
|
den Umschaltmoment, und dank Verzeichnisschutz sieht es ohnehin keine Suchmaschine.
|
|
|
|
|
|
|
|
|
|
Legende: `[lokal]` auf deinem Mac, `[SSH]` in der Server-Shell, `[KAS]` im
|
|
|
|
|
All-Inkl-Kundenmenü.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Teil A — Vorbereitung lokal
|
|
|
|
|
|
|
|
|
|
### A1 [lokal] Die zwei offenen Werte in `config/config.production.php` setzen
|
|
|
|
|
|
|
|
|
|
`php bin/preflight.php` bricht sonst auf dem Server ab. Beides betrifft nur die
|
|
|
|
|
Dateiablage `/dateien`, nicht die Website selbst.
|
|
|
|
|
|
|
|
|
|
**Passwörter für die zwei Zugänge.** Als Hash, nicht als Klartext:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
php -r 'echo password_hash("DEIN-PASSWORT", PASSWORD_DEFAULT), "\n";'
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Ausgabe (beginnt mit `$2y$…`) in `dateien.users.verein.password_hash` bzw.
|
|
|
|
|
`dateien.users.admin.password_hash` eintragen. Zwei verschiedene Passwörter: `verein`
|
|
|
|
|
gibt der Verein weiter, `admin` behältst du.
|
|
|
|
|
|
|
|
|
|
**Lizenzschlüssel** der Files Gallery in `dateien.license_key`. Fehlt er, läuft die
|
|
|
|
|
Ablage trotzdem, zeigt aber den Hinweis der unlizenzierten Version (nur eine Warnung
|
|
|
|
|
in `preflight`, kein Blocker).
|
|
|
|
|
|
|
|
|
|
Bereits erledigt: `cron.key` ist auf `''` gesetzt und der Endpoint `public/cron.php`
|
|
|
|
|
damit zu. Der Takt kommt ab jetzt aus echten Cronjobs (Teil D), der Endpoint war nur
|
|
|
|
|
der Ersatz für den fehlenden Cron.
|
|
|
|
|
|
|
|
|
|
### A2 [lokal] Alles gepusht?
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git status --porcelain # muss leer sein
|
|
|
|
|
git push origin main
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Teil B — Server einrichten
|
|
|
|
|
|
|
|
|
|
### B1 [SSH] Umgebung feststellen und notieren
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 16:28:58 +02:00
|
|
|
ssh ssh-w01ca75d@SERVER # SERVER = Hostname oder IP aus dem KAS
|
2026-07-31 16:21:09 +02:00
|
|
|
pwd # das Verzeichnis, in dem SSH startet
|
2026-07-31 16:13:14 +02:00
|
|
|
which php; php -v # PHP muss >= 8.1 sein
|
|
|
|
|
git --version
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-31 16:19:22 +02:00
|
|
|
**Ergebnis vom 31.07.2026 auf diesem Webspace:**
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|---|---|
|
2026-07-31 16:21:09 +02:00
|
|
|
| Webspace-Wurzel | `/www/htdocs/w01ca75d` |
|
2026-07-31 16:19:22 +02:00
|
|
|
| SITE | `/www/htdocs/w01ca75d/tsv08kulmbach-website` |
|
|
|
|
|
| PHP (CLI, für Cron) | `/usr/bin/php` — 8.3.29 |
|
|
|
|
|
| Composer | 2.9.8, global vorhanden |
|
|
|
|
|
| Alte CMS-Seite (Rückweg) | `/www/htdocs/w01ca75d/tsv08kulmbach.de` |
|
|
|
|
|
|
2026-07-31 16:21:09 +02:00
|
|
|
**Falle: `~` ist hier `/`, nicht der Webspace.** Das SSH-Konto hat die Systemwurzel
|
|
|
|
|
als Home-Verzeichnis, `cd ~` führt also aus dem Webspace heraus und `~/…`-Pfade
|
|
|
|
|
existieren nicht. Alle Pfade in dieser Anleitung sind deshalb absolut — auch das
|
|
|
|
|
`scp`-Ziel und die Cron-Zeilen.
|
|
|
|
|
|
|
|
|
|
All-Inkl legt pro Domain einen Ordner unter `/www/htdocs/<user>/` an, dieses
|
|
|
|
|
Verzeichnis selbst ist also kein Docroot. Der Ordnername `tsv08kulmbach.de` ist von der alten Seite belegt, deshalb
|
2026-07-31 16:19:22 +02:00
|
|
|
heißt der neue `tsv08kulmbach-website`. Die anderen TSV-Ordner
|
|
|
|
|
(`2024.`, `hall-of-fame.`, `kabinendienst.`, `kreisliga-reise.`) sind eigene
|
|
|
|
|
Subdomains und vom Umschalten nicht betroffen.
|
|
|
|
|
|
|
|
|
|
Achtung: das ist die **CLI**-Version. Welches PHP der Webserver nimmt, stellt KAS pro
|
|
|
|
|
Domain getrennt ein (Schritt C1).
|
2026-07-31 16:13:14 +02:00
|
|
|
|
|
|
|
|
### B2 [SSH] Repo klonen
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 16:21:09 +02:00
|
|
|
cd /www/htdocs/w01ca75d
|
2026-07-31 16:19:22 +02:00
|
|
|
git clone https://onion.breadcrumb-online.de/fs/tsv08kulmbach-website.git tsv08kulmbach-website
|
|
|
|
|
cd tsv08kulmbach-website && pwd # diesen Pfad notieren: das ist SITE
|
2026-07-31 16:13:14 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Der Klon lädt gut 460 MB (Website plus das Vereinsmaterial der Dateiablage), das
|
|
|
|
|
dauert ein paar Minuten. Die alte CMS-Seite liegt in einem anderen Verzeichnis und
|
|
|
|
|
wird nicht angefasst.
|
|
|
|
|
|
|
|
|
|
### B3 [SSH] `config.php` platzieren
|
|
|
|
|
|
|
|
|
|
Die Datei ist gitignored, also nicht im Klon. Vom Mac hochladen:
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 16:28:58 +02:00
|
|
|
# [lokal], in einem zweiten Terminal.
|
|
|
|
|
# SERVER = derselbe Host wie beim ssh-Login (hier die Server-IP des Webspace).
|
2026-07-31 16:13:14 +02:00
|
|
|
scp config/config.production.php \
|
2026-07-31 16:28:58 +02:00
|
|
|
ssh-w01ca75d@SERVER:/www/htdocs/w01ca75d/tsv08kulmbach-website/config/config.php
|
2026-07-31 16:13:14 +02:00
|
|
|
```
|
|
|
|
|
|
2026-07-31 16:28:58 +02:00
|
|
|
Platzhalter bewusst ohne spitze Klammern: `<host>` liest die zsh als
|
|
|
|
|
Eingabe-Umleitung und bricht mit „no such file or directory" ab.
|
|
|
|
|
|
2026-07-31 16:13:14 +02:00
|
|
|
Wichtig: Ziel heißt `config.php`, nicht `config.production.php`. Danach [SSH]:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
chmod 600 config/config.php # enthält SMTP-Passwort und app_secret
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### B4 [SSH] Composer-Abhängigkeiten
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 16:21:09 +02:00
|
|
|
cd /www/htdocs/w01ca75d/tsv08kulmbach-website
|
2026-07-31 16:13:14 +02:00
|
|
|
composer install --no-dev --optimize-autoloader
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Kennt die Shell `composer` nicht, hol ihn dir lokal ins Verzeichnis:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
curl -sS https://getcomposer.org/installer | php
|
|
|
|
|
php composer.phar install --no-dev --optimize-autoloader
|
|
|
|
|
rm composer.phar
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Es wird nur PHPMailer installiert.
|
|
|
|
|
|
2026-07-31 16:36:03 +02:00
|
|
|
### B4b [SSH] Git-Identität für den Server setzen
|
|
|
|
|
|
|
|
|
|
Ohne sie bricht **jeder** Commit auf dem Server ab („Author identity unknown"),
|
|
|
|
|
und ohne Commits funktioniert der Update-Ablauf aus F1 nicht.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd /www/htdocs/w01ca75d/tsv08kulmbach-website
|
|
|
|
|
git config --local user.name "TSV 08 Server"
|
|
|
|
|
git config --local user.email "fs@breadcrumb-solutions.de"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`--local` gilt nur für dieses Repo — die anderen Projekte auf dem Webspace bleiben
|
|
|
|
|
unberührt. Der Name macht in `git log` auf einen Blick sichtbar, welche Commits vom
|
|
|
|
|
Server stammen (Uploads, Sync-Stände) und welche vom Mac (Code, Inhalte).
|
|
|
|
|
|
|
|
|
|
Wird ein Push mit `! [rejected] … (fetch first)` abgewiesen, hat der Mac inzwischen
|
|
|
|
|
gepusht: `git pull --rebase origin main` und nochmal pushen. Rebase statt Merge, damit
|
|
|
|
|
die Historie linear bleibt.
|
|
|
|
|
|
2026-07-31 16:13:14 +02:00
|
|
|
### B5 [SSH] Schreibrechte prüfen
|
|
|
|
|
|
|
|
|
|
PHP läuft unter deinem Benutzer, meist passt es also schon. `preflight` sagt es dir
|
|
|
|
|
in Teil C4 verbindlich. Falls dort etwas fehlt:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
chmod -R u+rwX storage
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Schreiben muss die Seite in `storage/logs`, `storage/cache`, `storage/ratelimit`,
|
|
|
|
|
`storage/dateien/uploads` und `storage/dateien/system`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Teil C — Auf der Test-Subdomain prüfen
|
|
|
|
|
|
|
|
|
|
### C1 [KAS] Subdomain anlegen und schützen
|
|
|
|
|
|
|
|
|
|
1. **Domains** → Subdomain `neu.tsv08kulmbach.de` anlegen.
|
2026-07-31 16:19:22 +02:00
|
|
|
2. Als Verzeichnis **`/tsv08kulmbach-website/public`** eintragen, nicht das Repo-Root.
|
2026-07-31 16:13:14 +02:00
|
|
|
Damit liegen `app/`, `config/`, `data/`, `storage/` und `vendor/` außerhalb des
|
|
|
|
|
Docroots und sind grundsätzlich nicht per URL erreichbar. (Die Root-`.htaccess`
|
|
|
|
|
ist nur ein Notnagel für Hoster, die das nicht können.)
|
|
|
|
|
3. **PHP-Version** für die Subdomain auf 8.1 oder neuer stellen.
|
|
|
|
|
4. **SSL** aktivieren (Let's Encrypt). Ohne Zertifikat läuft die Seite in eine
|
|
|
|
|
Endlosweiterleitung, denn `public/.htaccess` erzwingt HTTPS.
|
2026-07-31 16:39:39 +02:00
|
|
|
5. **Verzeichnisschutz** (Basic Auth) — empfohlen, aber kein Blocker. Er hält die
|
|
|
|
|
Testseite komplett aus dem Netz. Ohne ihn ist das Risiko klein, weil jede Seite
|
|
|
|
|
ein Canonical auf `www.tsv08kulmbach.de` trägt: Google konsolidiert darauf,
|
|
|
|
|
doppelter Inhalt entsteht praktisch nicht. Es kann lediglich die Test-URL bis zum
|
|
|
|
|
Umschalten in einem Suchergebnis auftauchen. Am 31.07.2026 bewusst weggelassen.
|
|
|
|
|
Wer es ohne Basic Auth sauber will: `X-Robots-Tag: noindex` in
|
|
|
|
|
`public/.htaccess`, host-bedingt nur für Namen, die mit `neu.` beginnen.
|
2026-07-31 16:13:14 +02:00
|
|
|
|
|
|
|
|
### C2 Erreichbarkeit
|
|
|
|
|
|
|
|
|
|
`https://neu.tsv08kulmbach.de` aufrufen. Nach der Passwortabfrage muss die
|
|
|
|
|
Startseite kommen. Weißes Blatt oder 500er? Dann [SSH]
|
|
|
|
|
`tail -30 storage/logs/php-errors.log`.
|
|
|
|
|
|
|
|
|
|
### C3 [SSH] Datenstände erzeugen
|
|
|
|
|
|
|
|
|
|
`data/matchcenter.json` und `data/instagram.json` sind bewusst nicht im Repo, die
|
|
|
|
|
holen die Syncs. Vor dem ersten Lauf fehlen Tabellen, Spiele und der
|
|
|
|
|
Instagram-Block (die Seite bricht deswegen nicht, sie zeigt Ersatzinhalte).
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-31 16:21:09 +02:00
|
|
|
cd /www/htdocs/w01ca75d/tsv08kulmbach-website
|
2026-07-31 16:13:14 +02:00
|
|
|
php bin/matchcenter-sync.php # Spiele, Ergebnisse, Tabellen, Wappen
|
|
|
|
|
php bin/instagram-sync.php # Posts und Bilder
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Der Instagram-Lauf ist der Wackelkandidat des ganzen Deploys.** Instagram sperrt
|
|
|
|
|
Rechenzentrums-IPs; bisher lief der Scraper nur über einen privaten Anschluss.
|
|
|
|
|
Schlägt er hier fehl, ist das kein Fehler im Deploy: dann bleibt der Weg, ihn von
|
|
|
|
|
zu Hause laufen zu lassen und `data/instagram.json` samt Bildern hochzuladen, oder
|
|
|
|
|
der Block wird redaktionell gepflegt. Die Website funktioniert in jedem Fall.
|
|
|
|
|
|
|
|
|
|
### C4 [SSH] `preflight` bis null Fehler
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
php bin/preflight.php; echo "Exit: $?"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Ziel ist **Exit 0**. Warnungen darfst du bewerten, Fehler nicht. Erwartbare
|
|
|
|
|
Warnungen hier: der Hinweis, dass `cron.key` leer ist (richtig so, siehe A1) und
|
|
|
|
|
die Uploadgrenze, die erst ein echter Upload beweist (C6).
|
|
|
|
|
|
|
|
|
|
### C5 Durchklicken
|
|
|
|
|
|
|
|
|
|
Die Seiten, die eigene Datenquellen haben:
|
|
|
|
|
|
|
|
|
|
- [ ] Startseite: Banner-Slider, Termin-Karte im Hero, Instagram-Block
|
|
|
|
|
- [ ] `/matchcenter`: Tabellen und nächste Spiele mit Wappen
|
|
|
|
|
- [ ] `/termine`, `/news`, `/veranstaltungen` plus eine Detailseite
|
|
|
|
|
- [ ] `/stadionzeitung` und die Ausgabe durchswipen — **alle Wappen und
|
|
|
|
|
Instagram-Bilder da?** (Sie liegen im Repo, sollten also stimmen.)
|
|
|
|
|
- [ ] `/stadionzeitung/aktuell` landet auf der neuesten Ausgabe
|
|
|
|
|
- [ ] Die Druckfassung einer Ausgabe (`?druck=1`)
|
|
|
|
|
- [ ] `/impressum`: Webadresse zeigt `www.tsv08kulmbach.de`, nicht die Subdomain
|
|
|
|
|
|
|
|
|
|
### C6 Dateiablage und Mailversand
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
php bin/dateien-init.php # Jahr/Abteilung-Ordner, MUSS auf dem Server laufen
|
|
|
|
|
php bin/smtp-test.php # nur Verbindung und Login, kein Versand
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`dateien-init.php` gehört auf den Server, weil Umlaute per FTP zu Doppelordnern
|
|
|
|
|
führen (macOS NFD gegen Linux NFC).
|
|
|
|
|
|
|
|
|
|
- [ ] `/dateien` mit beiden Zugängen anmelden
|
|
|
|
|
- [ ] Eine große Datei hochladen (>10 MB) — beweist die 250-MB-Grenze
|
|
|
|
|
- [ ] Ein Formular abschicken (z. B. `/kontakt`) und die Mail im Postfach prüfen
|
|
|
|
|
- [ ] `php bin/logs.php` zeigt keine Fehler
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Teil D — Cronjobs
|
|
|
|
|
|
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
2026-07-31 17:16:12 +02:00
|
|
|
**Die KAS-Cronjobs können nur URLs aufrufen, keine Shell-Befehle** (geprüft 31.07.2026:
|
|
|
|
|
das Formular hat ein Feld „Protokoll / Pfad" mit `https://`, kein Kommandofeld). Der
|
|
|
|
|
Takt läuft deshalb über `public/cron.php`. Aufrufer ist der Hoster selbst — der
|
|
|
|
|
Schlüssel verlässt den Webspace nie, kein Dritter ist beteiligt.
|
|
|
|
|
|
|
|
|
|
Voraussetzung: `cron.key` muss in `config/config.php` gesetzt sein (48 Zeichen,
|
|
|
|
|
nicht `app_secret`). Steht er leer, antwortet der Endpoint mit 403 und **nichts** synct.
|
|
|
|
|
`bin/preflight.php` prüft Länge und Verwechslung.
|
|
|
|
|
|
|
|
|
|
[KAS] → **Cronjobs** → Anlegen, drei bis vier Einträge. `KEY` durch den Wert aus
|
|
|
|
|
`config.php` ersetzen:
|
|
|
|
|
|
|
|
|
|
| # | Pfad | Zeitpunkt |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| 1 | `www.tsv08kulmbach.de/cron.php?job=matchcenter&key=KEY` | stündlich, Minute 37 |
|
|
|
|
|
| 2 | `www.tsv08kulmbach.de/cron.php?job=instagram&key=KEY` | täglich, 06:17 |
|
|
|
|
|
| 3 | `www.tsv08kulmbach.de/cron.php?job=instagram&key=KEY` | täglich, 18:17 |
|
|
|
|
|
| 4 | `www.tsv08kulmbach.de/cron.php?job=logrotate&key=KEY` | täglich, 03:23 |
|
|
|
|
|
|
|
|
|
|
Vier Dinge dahinter sind Absicht:
|
|
|
|
|
|
|
|
|
|
- **Matchcenter stündlich statt halbstündlich am Spieltag.** Das KAS kann Wochentage
|
|
|
|
|
nicht mit einem Halbstunden-Takt kombinieren, wie es die ursprüngliche Crontab vorsah.
|
|
|
|
|
Stündlich rund um die Uhr ist der brauchbare Ersatz: am Spieltag steht das Ergebnis
|
|
|
|
|
spätestens eine Stunde später. Kostet 24 Läufe/Tag. Wer den Verkehr zum BFV klein
|
|
|
|
|
halten will, nimmt zwei „täglich"-Jobs (07:37 und 19:37) und lebt damit, dass
|
|
|
|
|
Samstagsergebnisse erst abends stehen.
|
|
|
|
|
- **Instagram zwei Einträge**, weil „täglich" nur einen Zeitpunkt kennt. Bewusst nicht
|
|
|
|
|
stündlich: Instagram sperrt auffällige Zugriffsmuster schneller als der BFV.
|
|
|
|
|
- **Versetzte Minuten**, damit sich zwei Läufe nie überlappen (die Syncs sperren sich
|
|
|
|
|
per Lock, sonst fällt einer aus).
|
|
|
|
|
- **Das E-Mail-Feld im KAS bleibt leer.** Die Antwort ist immer leer (204 = gelaufen oder
|
|
|
|
|
wegen Mindestabstand übersprungen, 403 = Schlüssel oder Job falsch), es gäbe also
|
|
|
|
|
nichts zu mailen. Ob der Takt läuft, sagt `php bin/logs.php`; `php bin/preflight.php`
|
|
|
|
|
warnt, wenn ein Datenstand zu alt wird.
|
|
|
|
|
|
|
|
|
|
**Am Tag nach dem ersten Lauf** einmal `php bin/logs.php` ansehen. Läuft nichts, ist fast
|
|
|
|
|
immer der Schlüssel in der URL falsch oder abgeschnitten. Gegenprobe von Hand:
|
2026-07-31 16:13:14 +02:00
|
|
|
|
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
2026-07-31 17:16:12 +02:00
|
|
|
```bash
|
|
|
|
|
curl -s -o /dev/null -w '%{http_code}\n' \
|
|
|
|
|
'https://www.tsv08kulmbach.de/cron.php?job=matchcenter&key=KEY'
|
2026-07-31 16:13:14 +02:00
|
|
|
```
|
|
|
|
|
|
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
2026-07-31 17:16:12 +02:00
|
|
|
204 = angenommen, 403 = Schlüssel oder Job falsch. Direkt danach ein zweites Mal
|
|
|
|
|
aufgerufen muss es ebenfalls 204 geben — dann hat der Mindestabstand gegriffen und der
|
|
|
|
|
Lauf wurde übersprungen, was im Log als WARN steht.
|
2026-07-31 16:13:14 +02:00
|
|
|
|
|
|
|
|
## Teil E — Umschalten auf die Hauptdomain
|
|
|
|
|
|
|
|
|
|
Ab hier ist die Seite öffentlich. Alles davor muss sitzen.
|
|
|
|
|
|
|
|
|
|
### E1 Sicherheitsnetz
|
|
|
|
|
|
|
|
|
|
Notiere, auf welches Verzeichnis `www.tsv08kulmbach.de` **jetzt** zeigt (die alte
|
|
|
|
|
CMS-Seite). Das ist dein Rückweg. Verzeichnis und Datenbank der alten Seite bleiben
|
|
|
|
|
unangetastet, nicht löschen.
|
|
|
|
|
|
|
|
|
|
### E2 [KAS] Domain umstellen
|
|
|
|
|
|
|
|
|
|
**Domains** → `tsv08kulmbach.de` und `www.tsv08kulmbach.de` auf
|
2026-07-31 16:19:22 +02:00
|
|
|
**`/tsv08kulmbach-website/public`** zeigen lassen. PHP-Version prüfen, SSL muss für beide
|
2026-07-31 16:13:14 +02:00
|
|
|
aktiv sein.
|
|
|
|
|
|
|
|
|
|
### E3 Sofort danach prüfen
|
|
|
|
|
|
|
|
|
|
- [ ] `https://www.tsv08kulmbach.de` lädt
|
|
|
|
|
- [ ] `http://tsv08kulmbach.de` (ohne www, ohne s) landet per 301 auf
|
|
|
|
|
`https://www.tsv08kulmbach.de`
|
|
|
|
|
- [ ] `/sitemap.xml` und `/robots.txt` erreichbar
|
|
|
|
|
- [ ] `curl -sI https://www.tsv08kulmbach.de | head` — Statuszeile 200, kein
|
|
|
|
|
Redirect-Loop
|
|
|
|
|
- [ ] Ein Formular live abschicken
|
|
|
|
|
- [ ] **Einen gedruckten QR-Code mit dem Handy scannen.** Der Code der
|
|
|
|
|
Stadionzeitung zeigt auf `www.tsv08kulmbach.de/stadionzeitung/aktuell`.
|
|
|
|
|
Scannen ist die einzige echte Prüfung: die QR-Bibliothek arbeitet nicht
|
|
|
|
|
reproduzierbar, ein Bildvergleich beweist nichts.
|
|
|
|
|
|
|
|
|
|
### E4 Verzeichnisschutz der Test-Subdomain lassen
|
|
|
|
|
|
|
|
|
|
Nicht entfernen. Sonst steht die Seite zweimal im Netz und Google sieht doppelten
|
|
|
|
|
Inhalt. Wenn du die Subdomain nicht mehr brauchst, in KAS löschen.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Teil F — Danach
|
|
|
|
|
|
|
|
|
|
### F1 Der Update-Ablauf ab jetzt
|
|
|
|
|
|
|
|
|
|
**Die Reihenfolge ist nicht verhandelbar**, weil `storage/dateien/uploads/` im Repo
|
|
|
|
|
liegt und andere Menschen dort hochladen:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 1. [SSH] auf dem Server: neue Uploads sichern
|
2026-07-31 16:21:09 +02:00
|
|
|
cd /www/htdocs/w01ca75d/tsv08kulmbach-website
|
2026-07-31 16:13:14 +02:00
|
|
|
git add -A storage/dateien/uploads && git commit -m "Uploads vom Server" && git push
|
|
|
|
|
|
|
|
|
|
# 2. [lokal] holen, dann entwickeln
|
|
|
|
|
git pull
|
|
|
|
|
|
|
|
|
|
# 3. [lokal] fertig, gepusht → [SSH] ausrollen
|
2026-07-31 16:21:09 +02:00
|
|
|
cd /www/htdocs/w01ca75d/tsv08kulmbach-website && git pull && php bin/preflight.php
|
2026-07-31 16:13:14 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Nie in der anderen Reihenfolge. Details und Begründung in CLAUDE.md, Abschnitt
|
|
|
|
|
Dateiablage.
|
|
|
|
|
|
|
|
|
|
Ändert sich `composer.json`, gehört ein `composer install --no-dev
|
|
|
|
|
--optimize-autoloader` dazu. Neue Formularfelder heißen: Datenschutzerklärung
|
|
|
|
|
mitziehen.
|
|
|
|
|
|
|
|
|
|
### F2 Rückweg, falls etwas Grundlegendes bricht
|
|
|
|
|
|
|
|
|
|
[KAS] Domain zurück auf das alte Verzeichnis aus E1. Nach ein paar Minuten ist die
|
|
|
|
|
alte Seite wieder da. Kein Datenverlust, weil die neue Seite in einem eigenen
|
|
|
|
|
Verzeichnis liegt und die alte Datenbank nicht anfasst.
|
|
|
|
|
|
|
|
|
|
### F3 Offen, nach dem Livegang
|
|
|
|
|
|
|
|
|
|
- **301-Weiterleitungen der alten URLs.** Die neue Seite hat andere Slugs; alte
|
|
|
|
|
Google-Treffer und fremde Links laufen ins Leere. Die alten Adressen stehen im
|
|
|
|
|
DB-Dump unter `.context_db`. Aufwand lohnt für die paar wichtigen Seiten, nicht
|
|
|
|
|
für alle.
|
|
|
|
|
- **Google Search Console:** Property anlegen, `sitemap.xml` einreichen.
|
|
|
|
|
- **Instagram-Sync** beobachten, siehe C3.
|
|
|
|
|
- Nach dem Sportfest (ab 07.09.2026) fallen zwei Anzeigen per `bis`-Datum aus dem
|
|
|
|
|
Bestand, die Stadionzeitung hat dann 38 Seiten statt 40 und ist nicht mehr durch
|
|
|
|
|
4 teilbar. `bin/stadionzeitung-add.php` warnt beim nächsten Lauf.
|