monitor/README_UPDATE.md
fds2610-bot e8d19534a6 update.sh: api und sync bei jedem Update neu erzeugen (#115)
Die rote Fahne aus #113 blieb nach dem automatischen Update aus. Code-Pfad und Daten sind
in Ordnung (NC-Gantt #333 ist offen und stammt von "tinko"), aber der sync-Container bindet
./api nur als Volume ein. Bei unveraendertem Image und unveraenderter Compose-Konfiguration
liess "docker compose up -d" ihn deshalb einfach weiterlaufen. Der --watch-Prozess hatte
seine Klassen noch vom alten Stand geladen, uebergab also nie einen author, und
countExternalOpenTickets() kam immer auf 0.

update.sh erzeugt api und sync deshalb jetzt mit --force-recreate neu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:36:36 +02:00

6.8 KiB

Auto-Update auf einem Produktionsserver

Dieses Dokument beschreibt, wie ein bereits laufendes monitor auf einem Produktionsserver sich selbst aktualisiert, sobald der Repo-Besitzer einen Pull Request nach main mergt. Anders als im parallelen Agent-Projekt gibt es hier keine Trennung zwischen einem unveraenderlichen "Bootstrap"-Updater und dem eigentlichen Projekt-Code - update.sh gehoert direkt zum Projekt und wird bei jedem Update mit aktualisiert.

Bausteine

  • update.sh (Projekt-Wurzel) - holt die neueste Version des konfigurierten Branches per git pull --ff-only, baut die Docker-Images neu, startet api, web und sync neu und fuehrt (per Default) die Testsuiten aus. api und sync werden dabei immer neu erzeugt (--force-recreate), weil sie den Code nur per Volume einbinden und der langlebige sync-Prozess neuen PHP-Code sonst erst nach einem manuellen Neustart sieht (Issue #115). Kann jederzeit auch manuell aufgerufen werden, siehe Kommentar am Kopf der Datei fuer die Umgebungsvariablen UPDATE_REMOTE, UPDATE_BRANCH, UPDATE_RUN_TESTS. Nach dem Neustart meldet das Skript ein Event "Monitor: Update eingespielt ()" per POST /api/v1/events/system (Admin-Key aus der .env); schlaegt das fehl, bleibt das Update gueltig.
  • deploy-webhook.py (Projekt-Wurzel) - kleiner HTTP-Server (nur Python-Standardbibliothek, keine Abhaengigkeiten) auf Basis von http.server. Prueft eingehende Codeberg-Webhooks per HMAC-Signatur (X-Forgejo-Signature/X-Gitea-Signature, analog zum bestehenden POST /api/v1/webhooks/codeberg aus API.md) und ruft bei einem Push auf den konfigurierten Branch update.sh auf. Laeuft auf dem Host, nicht im Docker-Compose-Stack
    • update.sh selbst ruft docker compose auf dem Host auf, das waere aus einem Container heraus nur mit riskantem Zugriff auf den Docker-Socket moeglich.
  • deploy/monitor-deploy-webhook.service - systemd-Unit-Vorlage, um deploy-webhook.py dauerhaft im Hintergrund laufen zu lassen.

Manuelle Vorarbeiten auf dem Server

Diese Schritte sind einmalig und nicht Teil dieses Repos (unterschiedlich je nach Server), daher hier nur als Checkliste:

  1. Repo auskecken. git clone https://codeberg.org/fds2610/monitor.git (oder per SSH, falls Push-Zugangsdaten fuer git pull als Deploy-Key/SSH-Key eingerichtet werden) nach z.B. /opt/monitor, Branch main ausgecheckt. Versionierte Dateien duerfen dort nicht lokal veraendert werden - update.sh bricht kontrolliert ab, statt die Aenderungen zu ueberschreiben (Notausgang: UPDATE_ALLOW_DIRTY=1). Unversionierte Dateien neben dem Repo (.env, hosts.conf, monitor-api.conf, credentials.conf) stoeren dagegen nicht, sie bleiben beim Update unangetastet.
  2. Docker + Docker Compose installieren (docker compose version muss funktionieren). Der Benutzer, unter dem deploy-webhook.py laeuft, braucht Mitgliedschaft in der docker-Gruppe (oder aequivalente Rechte), um docker compose ohne sudo aufzurufen.
  3. Python 3 installieren (Standardbibliothek genuegt, keine weiteren Pakete noetig) - mail_fds.py im selben Repo setzt das ohnehin schon voraus.
  4. .env anlegen aus .env.example, insbesondere MONITOR_ADMIN_API_KEY, MONITOR_API_BASE und die neuen Variablen MONITOR_DEPLOY_WEBHOOK_SECRET, MONITOR_DEPLOY_WEBHOOK_PORT, MONITOR_DEPLOY_WEBHOOK_BRANCH (Details siehe dort). Das Secret z.B. mit openssl rand -hex 32 erzeugen.
  5. systemd-Unit einrichten:
    sudo cp deploy/monitor-deploy-webhook.service /etc/systemd/system/
    sudo $EDITOR /etc/systemd/system/monitor-deploy-webhook.service   # Pfade/User/Group anpassen
    sudo systemctl daemon-reload
    sudo systemctl enable --now monitor-deploy-webhook
    sudo systemctl status monitor-deploy-webhook
    
  6. Port/Proxy freischalten. deploy-webhook.py lauscht auf MONITOR_DEPLOY_WEBHOOK_PORT (Default 9099) auf allen Interfaces. Analog zum bestehenden Codeberg-Sync-Webhook muss Proxy/Firewall eingehende Anfragen von Codeberg an diesen Port (bzw. an einen dahinter liegenden Reverse-Proxy-Pfad) durchlassen - alle anderen Dienste (api, web, sync) brauchen dafuer keine Aenderung.
  7. Webhook in Codeberg einrichten (macht der Repo-Besitzer manuell, siehe Issue #36): Repo-Einstellungen -> Webhooks -> Forgejo, Ziel-URL auf deploy-webhook.py (Port aus Schritt 6, ggf. per Proxy auf https://... gemappt), Content-Type application/json, Secret wie in .env (MONITOR_DEPLOY_WEBHOOK_SECRET), Ereignis nur "Push". Ein Merge erzeugt immer einen Push auf den Ziel-Branch, ein eigenes "Pull Request merged"-Event ist daher nicht noetig.
  8. Fallback bei verpasstem Webhook (automatisch, keine Einrichtung): Sinkt im sync-Container die Zahl offener PRs (Merge oder Schliessen), legt er api/data/deploy-requested an. deploy-webhook.py bemerkt die Datei, holt origin/<Branch> und startet update.sh nur, wenn der Stand abweicht. Pfad aenderbar ueber MONITOR_DEPLOY_TRIGGER_FILE. Greift spaetestens beim naechsten Sync (MONITOR_SYNC_INTERVAL), keine zusaetzliche Codeberg-Verbindung. Trifft ein Ausloeser waehrend eines laufenden Updates ein, laeuft danach ein Folgelauf.

Testen

Zustellung testen (Codeberg schickt beim Anlegen automatisch ein ping-Event, sichtbar unter "Letzte Zustellungen" im Webhook selbst). Manuell mit korrekter Signatur:

SECRET=<MONITOR_DEPLOY_WEBHOOK_SECRET aus .env>
BODY='{"ref":"refs/heads/main"}'
SIG=$(python3 -c "import hmac,hashlib,sys; print(hmac.new(sys.argv[1].encode(), sys.argv[2].encode(), hashlib.sha256).hexdigest())" "$SECRET" "$BODY")
curl -i -X POST "http://<server>:9099/" \
  -H "X-Forgejo-Event: push" \
  -H "X-Forgejo-Signature: $SIG" \
  -d "$BODY"

Antwort 202 {"status": "update gestartet"} bedeutet, dass update.sh im Hintergrund laeuft. Der Verlauf landet in MONITOR_DEPLOY_LOG_FILE (Default: deploy-webhook.log neben deploy-webhook.py) sowie im systemd-Journal (journalctl -u monitor-deploy-webhook -f).

Sicherheit

Der Endpunkt ist wie POST /api/v1/webhooks/codeberg ohne Secret grundsaetzlich geschlossen (401) und akzeptiert nur Anfragen mit gueltiger HMAC-Signatur. Er loest im Erfolgsfall lediglich denselben git pull --ff-only + Rebuild aus, den ein Server-Betreiber auch manuell per ./update.sh anstossen wuerde - er bringt also keinen Code auf den Server, der nicht bereits reviewt und manuell nach main gemergt wurde. Diese manuelle Merge-Hürde ist laut Repo-Besitzer (Issue #36) als alleinige Sicherheitsmassnahme ausreichend; zusaetzliche Branch-Protection-Einstellungen im Codeberg-Repo (z.B. kein direkter Push nach main) bleiben trotzdem empfehlenswert, sind aber eine manuelle Repo-Einstellung und nicht Teil dieses Skripts.