monitor/README_UPDATE.md
fds2610-bot 6bad9b470c Auto-Update per Push-Webhook fuer Produktionsserver einrichten
deploy-webhook.py laeuft auf dem Host, prueft eingehende Codeberg-Webhooks per
HMAC-Signatur (analog zu POST /api/v1/webhooks/codeberg) und stoesst bei einem
Push auf main - also nach jedem manuell gemergten PR - das bereits vorhandene
update.sh an. README_UPDATE.md dokumentiert die dafuer noetigen manuellen
Vorarbeiten auf dem Server (Checkout, Docker, systemd-Unit, Webhook-Einrichtung).

Schliesst #36
2026-08-04 12:20:41 +02:00

94 lines
5.6 KiB
Markdown

# 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](https://codeberg.org/fds2610/Agent) 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. Kann jederzeit auch manuell aufgerufen werden, siehe
Kommentar am Kopf der Datei fuer die Umgebungsvariablen `UPDATE_REMOTE`, `UPDATE_BRANCH`,
`UPDATE_RUN_TESTS`.
- **`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](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. Der Arbeitsbaum muss beim Update sauber
sein - `update.sh` bricht kontrolliert ab, wenn lokale Aenderungen vorhanden sind, statt sie
zu ueberschreiben.
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:**
```bash
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.
## Testen
Zustellung testen (Codeberg schickt beim Anlegen automatisch ein `ping`-Event, sichtbar unter
"Letzte Zustellungen" im Webhook selbst). Manuell mit korrekter Signatur:
```bash
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.