Sites-Konfiguration aus dem CMS nachziehen (Pull-Modell) #2

Open
keksdestodes wants to merge 0 commits from feat/cms-config-sync into main
Owner

Damit das CMS Sites automatisch anbinden kann, ohne dass jemand die sites.yaml von Hand pflegt.

Warum Pull und nicht ein Schreib-Endpunkt

Naheliegend wäre POST /api/sites am Sammler. Dagegen sprechen zwei Dinge:

  • config/sites.yaml ist read-only gemountet — ein Schreib-Endpunkt bräuchte einen rw-Mount an genau der Datei, in der CORS-Domains und Auth-Keys stehen.
  • Ein Dienst, der im Internet steht und sonst nur liest, bekäme damit einen authentifizierten Schreibpfad in seine eigene Sicherheitskonfiguration.

Stattdessen holt der Sammler ab. Es gibt hier ohnehin schon einen 5-Minuten-Cron für die Traefik-Config; der neue Sync läuft direkt davor, damit neue Sites ohne zusätzliche Verzögerung ihr Zertifikat bekommen.

Was drin ist

  • cron/sync-sites.php — holt, validiert, schreibt atomar (tmp + rename) nach /app/data/sites-remote.yaml. Das Daten-Volume ist beschreibbar, also kein Mount-Umbau nötig.
  • Config::mergeRemote()config/sites.yaml gewinnt immer. Ein von Hand gepflegter Eintrag wird nie vom Sync überschrieben; Sites außerhalb des CMS bleiben so möglich.
  • Cron-Eintrag, docker-compose.yml-Env, .env.example, README-Abschnitt.

Fehlerverhalten

Bewusst konservativ — der Sammler soll nie Sites verlieren, nur weil das CMS hustet:

Fall Verhalten
CMS nicht erreichbar / HTTP ≠ 200 Meldung nach stderr, letzter Stand bleibt
Unerwartetes Format oder falsche version verworfen, letzter Stand bleibt
Antwort ohne gültige Site verworfen (häufigste Ursache: falscher Endpunkt)
Inhalt unverändert keine Schreiboperation
CMS_SYNC_URL/CMS_SYNC_SECRET leer Sync aus, no-op

Site-IDs und Keys werden vor dem Schreiben gegen ein Format geprüft; Keys ohne zugehörige Site fallen raus.

Getestet

Gegen den echten CMS-Endpunkt gelaufen (PHP 8.3): 4 Sites samt Domains, CNAMEs und Keys korrekt übernommen, die erzeugte YAML ist gültig. php -l sauber für beide geänderten Dateien. Der Merge-Vorrang ist bisher nur gelesen, nicht im laufenden Container geprüft — das würde ich vor dem Deploy noch einmal live sehen wollen.

Vor dem Deploy

CMS_SYNC_URL und CMS_SYNC_SECRET in die .env (Secret = TRACKER_SYNC_SECRET im CMS). Ohne die beiden ändert sich nichts am Verhalten.

Damit das CMS Sites automatisch anbinden kann, ohne dass jemand die `sites.yaml` von Hand pflegt. ## Warum Pull und nicht ein Schreib-Endpunkt Naheliegend wäre `POST /api/sites` am Sammler. Dagegen sprechen zwei Dinge: - `config/sites.yaml` ist **read-only gemountet** — ein Schreib-Endpunkt bräuchte einen rw-Mount an genau der Datei, in der CORS-Domains und Auth-Keys stehen. - Ein Dienst, der im Internet steht und sonst nur liest, bekäme damit einen authentifizierten Schreibpfad in seine eigene Sicherheitskonfiguration. Stattdessen holt der Sammler ab. Es gibt hier ohnehin schon einen 5-Minuten-Cron für die Traefik-Config; der neue Sync läuft direkt davor, damit neue Sites ohne zusätzliche Verzögerung ihr Zertifikat bekommen. ## Was drin ist - **`cron/sync-sites.php`** — holt, validiert, schreibt atomar (tmp + rename) nach `/app/data/sites-remote.yaml`. Das Daten-Volume ist beschreibbar, also **kein Mount-Umbau nötig**. - **`Config::mergeRemote()`** — `config/sites.yaml` gewinnt **immer**. Ein von Hand gepflegter Eintrag wird nie vom Sync überschrieben; Sites außerhalb des CMS bleiben so möglich. - Cron-Eintrag, `docker-compose.yml`-Env, `.env.example`, README-Abschnitt. ## Fehlerverhalten Bewusst konservativ — der Sammler soll nie Sites verlieren, nur weil das CMS hustet: | Fall | Verhalten | |---|---| | CMS nicht erreichbar / HTTP ≠ 200 | Meldung nach stderr, letzter Stand bleibt | | Unerwartetes Format oder falsche `version` | verworfen, letzter Stand bleibt | | Antwort ohne gültige Site | verworfen (häufigste Ursache: falscher Endpunkt) | | Inhalt unverändert | keine Schreiboperation | | `CMS_SYNC_URL`/`CMS_SYNC_SECRET` leer | Sync aus, no-op | Site-IDs und Keys werden vor dem Schreiben gegen ein Format geprüft; Keys ohne zugehörige Site fallen raus. ## Getestet Gegen den echten CMS-Endpunkt gelaufen (PHP 8.3): 4 Sites samt Domains, CNAMEs und Keys korrekt übernommen, die erzeugte YAML ist gültig. `php -l` sauber für beide geänderten Dateien. Der Merge-Vorrang ist bisher nur gelesen, nicht im laufenden Container geprüft — das würde ich vor dem Deploy noch einmal live sehen wollen. ## Vor dem Deploy `CMS_SYNC_URL` und `CMS_SYNC_SECRET` in die `.env` (Secret = `TRACKER_SYNC_SECRET` im CMS). Ohne die beiden ändert sich nichts am Verhalten.
Statt jede Site von Hand in die sites.yaml zu schreiben, holt sich der
Sammler Sites, Domains, CNAMEs und API-Keys alle 5 Minuten beim CMS ab.

Bewusst Pull und nicht Push: so bleibt config/sites.yaml read-only
gemountet und der Sammler bekommt keinen Schreibpfad in seine eigene
Sicherheitskonfiguration (CORS-Domains + Auth-Keys). Das CMS ist ohnehin
die Quelle der Wahrheit fuer Sites und Domains.

- cron/sync-sites.php: holt, validiert, schreibt atomar nach
  /app/data/sites-remote.yaml (Volume, kein Mount-Umbau noetig).
  Unerreichbares CMS oder unplausible Antwort ⇒ letzter Stand bleibt.
- Config::mergeRemote(): config/sites.yaml gewinnt IMMER, damit manuelle
  Eintraege nicht vom naechsten Sync ueberschrieben werden.
- Cron alle 5 Min, direkt vor der Traefik-Generierung — neue Sites
  bekommen ihr Zertifikat damit ohne zusaetzliche Verzoegerung.
- Ohne CMS_SYNC_URL/CMS_SYNC_SECRET passiert nichts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Liefert das CMS `domains` als String statt als Liste, warf array_filter()
einen TypeError und der komplette Sync-Lauf starb. Kein Datenverlust (der
letzte Stand bleibt liegen), aber eine einzelne fehlerhafte Site sollte
uebersprungen werden, nicht alle mitreissen.

In PHP 8 ist der Zugriff auf einen String-Offset ein TypeError statt eines
Warnings — deshalb Guards auf allen vier Ebenen: sites, das einzelne
Site-Objekt, dessen domains und die apiKeys-Metadaten. Auch name wird jetzt
geprueft, statt (string) auf ein Array anzuwenden.

Gegen ein Mock-CMS verifiziert (PHP 8.3): domains-als-String crasht vorher
mit "array_filter(): Argument #1 must be of type array", nachher wird die
kaputte Site uebersprungen und die heile daneben normal uebernommen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
supercronic startet Jobs mit gleichem Schedule parallel — die Datei-
Reihenfolge garantiert keine Ausfuehrungsreihenfolge. Der Kommentar
("laeuft davor") stimmte also nicht: eine neue CMS-Site haette ihr
Zertifikat je nach Scheduling erst einen Zyklus spaeter bekommen.
Jetzt beide Schritte in einer Crontab-Zeile, mit `;` statt `&&`, damit
die Traefik-Config auch bei fehlgeschlagenem oder deaktiviertem Sync
nachgezogen wird.

README: Erst-Rollout braucht `docker compose up -d --build` —
Cron-Script und Crontab stecken im Image, ohne Rebuild laeuft der
Sync kommentarlos nie.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This branch is already included in the target branch. There is nothing to merge.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/cms-config-sync:feat/cms-config-sync
git switch feat/cms-config-sync

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff feat/cms-config-sync
git switch feat/cms-config-sync
git rebase main
git switch main
git merge --ff-only feat/cms-config-sync
git switch feat/cms-config-sync
git rebase main
git switch main
git merge --no-ff feat/cms-config-sync
git switch main
git merge --squash feat/cms-config-sync
git switch main
git merge --ff-only feat/cms-config-sync
git switch main
git merge feat/cms-config-sync
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
keksdestodes/intraday-analytics!2
No description provided.