# Webhook-Migration: Standalone Listener → Nitro-Endpoint (Trucko-Pattern) **Owner:** Backyard **Erstellt:** 2026-05-07 **Status:** PLAN — keine Implementierung in dieser Session. User-Direktive: „langsam starten". **Ziel:** Migration des GitHub-Webhook-Receivers vom standalone `scripts/deploy-webhook/server.mjs` zu einem Nitro-Endpoint im Backend (`backend/server/api/webhook/github.post.ts`) — analog zum Trucko-Monorepo. --- ## 1. Status quo (Stand 2026-05-07) ### 1.1 Aktuelle Pipeline ``` GitHub push → nginx (staging.rebreak.org/webhook) → 127.0.0.1:9000 (pm2: rebreak-webhook) → scripts/deploy-webhook/server.mjs (HMAC-Verify + Queue) → spawn bash scripts/deploy.sh → git fetch + reset --hard origin/main → pnpm install --frozen-lockfile (workspace-root) → cd backend && pnpm --filter rebreak-backend build → .output → .output-staging (atomisch via tmp) → pm2 restart rebreak-staging --update-env → pm2 restart rebreak-imap-staging / rebreak-idle-staging / dns-* (best-effort) ``` ### 1.2 Beteiligte Dateien | Datei | Rolle | |------|-------| | `/srv/rebreak/scripts/deploy-webhook/server.mjs` | Standalone Node-HTTP-Server auf Port 9000, HMAC-Verify, spawn deploy.sh | | `/srv/rebreak/scripts/deploy.sh` | Pull → install → build → atomic-deploy → pm2 restart | | `/srv/rebreak/ecosystem.config.js` | pm2-Service `rebreak-webhook` (cluster mode, 1 instance) | | `/etc/nginx/sites-enabled/staging.rebreak.org` | `location /webhook` → `proxy_pass http://127.0.0.1:9000/webhook` | | `/etc/environment` | `GITHUB_WEBHOOK_SECRET` (via Infisical-bootstrap), `INFISICAL_CLIENT_ID/SECRET` | ### 1.3 Beobachtete Probleme 1. **Push-Detection unzuverlässig:** Manche Pushes triggern Deploy nicht (Listener empfängt Event nicht oder schweigt) — Symptom unklar (timing/network/hängender vorheriger spawn?). 2. **deploy.sh exit code 1:** Vergangene Logs zeigen `cd apps/rebreak: No such file or directory` (alter Pre-Cutover-Pfad in alten Logs; aktueller deploy.sh ist auf `backend/` korrekt umgestellt — Symptom nicht mehr reproduzierbar, aber Vertrauensverlust). 3. **Kein Healthcheck:** `/webhook` antwortet nur auf POST mit valider Sig. Kein `GET /webhook/health` o.ä. — manuelles Debug nur via `pm2 logs rebreak-webhook`. 4. **Kein Retry:** Wenn `git fetch` netzwerkbedingt failt, bleibt Deploy hängen / aborts ohne Rescheduling. 5. **Logs verstreut:** Webhook-Logs in `pm2 logs rebreak-webhook`, Deploy-Output in dieselben Logs gestreamt aber mit `[deploy] /[deploy:err]`-Prefixen — kein strukturiertes Logging, kein File-Persistence über pm2-Restart hinweg. 6. **Single-Point-of-Failure:** Wenn `rebreak-webhook` pm2-Prozess crashed, gehen Pushes ins Leere — keine GitHub-Redelivery-Automatik (manuell via GitHub UI nötig). --- ## 2. Trucko-Pattern (das Vorbild) **Quelldatei:** `~/mono/trucko-monorepo/backend/server/api/webhook/github.post.ts` (957 Zeilen) **Test-Coverage:** `~/mono/trucko-monorepo/backend/tests/unit/webhook-hmac.test.ts` (HMAC-Verify isoliert testbar) ### 2.1 Architektur-Eigenschaften - **Webhook ist ein Nitro-API-Endpoint im Backend selbst** — kein separater Listener. Pfad: `backend/server/api/webhook/github.post.ts` → wird über das Backend-pm2 (`rebreak-staging`) bedient. - **HMAC-Sig-Verify** identisch wie unsere `server.mjs` (sha256, `timingSafeEqual`). - **Reads `useRuntimeConfig()`** für `GITHUB_WEBHOOK_SECRET`, `GITHUB_USERNAME`, `GITHUB_TOKEN` — Secrets kommen via Infisical → Nuxt/Nitro runtimeConfig (kein extra `/etc/environment`-Parsing). - **Affected-Detection:** Liest `payload.commits[].added/modified/removed`, normalisiert Pfade, mapped auf eine Tabelle `affected = { rebreak: bool, backend: bool, nginx: bool, ... }`. Nur betroffene Apps werden gebaut. - **Queue-Mechanismus mit Merge:** `queueDeployment()` mergt `affected`-Flags wenn ein zweiter Push während eines laufenden Builds reinkommt → kein Doppel-Build, aber kein Verlust. - **Inline-Build-Steps via `executeCommand()`-Helper:** Jeder Step (git fetch / install / build / pm2 restart) ist ein einzelner spawned bash mit Timeout + structured stdout/stderr-Capture. Kein externes deploy.sh. - **Token-basierter Pull:** Nutzt `GITHUB_USERNAME` + `GITHUB_TOKEN` für `https://...@github.com/...` — kein SSH-Deploy-Key-Handling im Webhook. - **Atomisches Output-Swap** für rebreak: `cp -r .output .output-staging-new && rm -rf .output-staging && mv .output-staging-new .output-staging` (identisch zu unserem deploy.sh). - **Logs in pm2-stream:** Alles via `console.log("[Webhook] ...")` — landet in `pm2 logs backend`, zentral. - **Healthcheck implizit:** Wenn das Backend lebt, lebt der Webhook. pm2 zeigt `backend online` → Webhook funktioniert. ### 2.2 Was das Trucko-Pattern besser macht | Aspekt | Standalone-Listener (Status quo) | Nitro-Endpoint (Trucko) | |--------|-----------------------------------|-------------------------| | Single Source of Truth | 2 pm2-Services (webhook + app) | 1 pm2-Service (Backend allein) | | Secret-Handling | `/etc/environment` parsing manuell | `useRuntimeConfig()` via Infisical | | Affected-Logik | Nicht vorhanden, baut immer alles | Granular, nur betroffene Apps | | Logs | 2 verschiedene pm2-Streams | 1 zentraler Stream | | Test-Coverage | Keine Tests | `webhook-hmac.test.ts` (vitest) | | Code-Lokation | `scripts/` (separater Code-Pfad) | `backend/server/api/` (gleiche TS-Codebase) | | Crash-Resilience | Eigener Prozess kann sterben ohne app | Wenn Backend lebt, lebt Webhook | --- ## 3. Migrations-Schritte (für nächste Session — NICHT jetzt) ### Phase 1: Endpoint anlegen (Mac, kein Server-Change) 1. Datei `backend/server/api/webhook/github.post.ts` erstellen — kopiere Trucko-Vorlage, passe an: - Repo-Pfad: `/srv/rebreak` (statt `/srv/trucko-monorepo`) - `affected`-Map: nur `rebreak-native`, `backend`, `nginx`, `dns`, `imap` (kein `pizzabox`/`driver`/etc.) - Build-Steps: nur Backend-Build (`pnpm --filter rebreak-backend build`) + atomic-output-swap + pm2 restart `rebreak-staging` - Entferne Trucko-spezifische nginx-conf-Listen (die unsere ops/nginx-Files haben andere Namen) 2. `nitro.config.ts`: `runtimeConfig` um `githubWebhookSecret`, `githubUsername`, `githubToken` ergänzen (analog Trucko). 3. Test-File `backend/tests/unit/webhook-hmac.test.ts` mitmigrieren (vitest setup falls nötig). 4. Lokal `pnpm --filter rebreak-backend build` → checken dass Endpoint im `.output/server/chunks/routes/api/webhook/github.post.mjs` landet. ### Phase 2: Infisical-Secrets setzen (User-Eskalation) 5. Infisical Project rebreak-staging: - `GITHUB_WEBHOOK_SECRET` (bereits vorhanden in /etc/environment, in Infisical spiegeln) - `GITHUB_USERNAME` (z.B. `chahinebrini`) - `GITHUB_TOKEN` (PAT mit `repo`-Scope für git-pull über HTTPS) → diese Secrets werden via `start-staging.sh` als `NUXT_*` env vars dem Backend bereitgestellt. ### Phase 3: Parallel-Betrieb (Failsafe) 6. **Beide Endpoints aktiv halten:** - GitHub-Webhook bleibt auf `https://staging.rebreak.org/webhook` (alter Listener auf Port 9000). - Neuer Endpoint zusätzlich erreichbar unter `https://staging.rebreak.org/api/webhook/github` (Backend-Routing). 7. Auf GitHub: **zweite Webhook-Konfig** anlegen, beide aktiv. Beide verifizieren mit demselben Secret. 8. Logs beide Streams beobachten — wenn neuer Endpoint zuverlässig deployt, alter wird redundant. ### Phase 4: Cutover 9. GitHub-Webhook umstellen: alten URL-Webhook deaktivieren, nur neuer `/api/webhook/github` aktiv. 10. nginx: `location /webhook` → `proxy_pass http://127.0.0.1:9000` ENTFERNEN (oder auf Backend umrouten). 11. `pm2 stop rebreak-webhook && pm2 delete rebreak-webhook` (NUR auf User-Approval — destruktiv). 12. `scripts/deploy-webhook/` archivieren (nicht löschen — Reference). 13. `scripts/deploy.sh` entweder: - **a)** behalten als Fallback-Tool für manuelles `bash deploy.sh` auf SSH (empfohlen), oder - **b)** Code-Logik ins Backend-Endpoint inline-spawnen wie Trucko es tut (mehr Code, aber 100% Single-Source). ### Phase 5: Hardening 14. Healthcheck-Endpoint hinzufügen: `backend/server/api/webhook/health.get.ts` → returns `{ ok, lastDeploy, queueDepth }`. 15. Persistent Deploy-Log: in `/var/log/rebreak/deploys.jsonl` schreiben (statt nur pm2-stream). 16. Slack/Discord-Notifier on deploy-fail (optional). --- ## 4. Risk-Assessment ### Top 3 Risks bei Migration 1. **Self-Deploy-Loop / Race-Condition beim Restart:** Der Webhook lebt im Backend-Prozess. Wenn der Webhook `pm2 restart rebreak-staging` aufruft, killt der Prozess sich selbst, BEVOR die HTTP-Response gesendet ist. → GitHub bekommt timeout, retry-storm. **Mitigation:** Trucko macht's auch, und es funktioniert dort. Schlüssel: pm2 restart ist async + `setTimeout(2000)` vor dem restart, response wurde schon vor Build-Start gesendet (`return { ok }` BEVOR `queueDeployment()` läuft im Background). Muss bei uns **gleich** strukturiert werden. 2. **Build-Failure crashed Webhook-Capability:** Wenn ein Bad-Push das Backend-Build kaputt macht, hat der nächste Push keinen Webhook-Endpoint mehr (Backend offline). → Stuck-State, manuelles SSH nötig zum Recovery. **Mitigation:** Atomic-Output-Swap (.output → .output-staging via mv) bleibt. Alter Build überlebt im `.output-staging`, Backend bleibt online auch wenn neuer Build failed. **PLUS:** Listener als Failsafe parallel erstmal behalten (Phase 3). 3. **Infisical-Secret-Loading-Lücke:** Wenn `useRuntimeConfig()` zur Build-Zeit aufgerufen wird (statt Runtime), landen die Secrets nicht im Endpoint. Trucko nutzt `runtimeConfig` → wird per `NUXT_*`-env bei Start aufgelöst. Unsere `start-staging.sh` muss `NUXT_GITHUB_WEBHOOK_SECRET`, `NUXT_GITHUB_USERNAME`, `NUXT_GITHUB_TOKEN` setzen — sonst 401 für jeden Push. **Mitigation:** Phase 2 explizit verifizieren via `curl https://staging.rebreak.org/api/webhook/github -X POST -H "x-hub-signature-256: sha256=..." -d '...'` mit Test-Payload BEVOR GitHub-Webhook umgestellt wird. ### Weitere Risiken (lower-priority) - **GitHub-Token-Lifetime:** PAT läuft ab → silent fail. Mitigation: Token mit langer Lifetime, ggf. Rotation-Reminder. - **Body-Parsing-Reihenfolge:** Nitro liest body normal als JSON, aber HMAC braucht raw-body. Trucko liest manuell `event.req.on("data")` — Nitro-Middleware darf body nicht vorher konsumiert haben. Ggf. Plugin-Order checken. - **OOM während Build:** 4 GB RAM, `NODE_OPTIONS=--max-old-space-size=1536` reicht heute. Wenn Backend-Build wächst → OOM-kill mid-build → nicht-atomar. --- ## 5. Fallback-Plan Wenn Migration scheitert: 1. **Listener bleibt aktiv** während Migration (Phase 3 ist parallel-Betrieb). 2. **Rollback-Path:** - GitHub-Webhook URL zurück auf `/webhook` (alter Listener). - `pm2 restart rebreak-webhook` (falls gestoppt). - Neuer Endpoint im Backend bleibt vorhanden, antwortet aber nicht mehr aus GitHub-Pushes — kein Schaden. 3. **Total-Rollback (nur falls Backend-Endpoint Backend-Builds blockiert):** - File `backend/server/api/webhook/github.post.ts` löschen, neu builden, deployen. - Cleanup: `runtimeConfig`-Einträge in `nitro.config.ts` zurückrollen. --- ## 6. Empfohlener erster Schritt (für nächste Session) **Read-only Investigation:** Prüfen welche Infisical-Secrets aktuell im Backend-Runtime verfügbar sind (`pm2 logs rebreak-staging | grep -i webhook` + Infisical-CLI `infisical secrets --env=staging | grep GITHUB`). Damit ist klar ob Phase 2 trivial ist (Secrets bereits da) oder neue Secrets nötig sind. **Erst danach** Phase 1 (Endpoint anlegen) auf Mac, ohne deployen. --- ## 7. Was NICHT in Scope - Code-Änderungen am App-Code (Nitro-Routes außerhalb `/webhook/`). - Mail-Stack-Touchups (Mo's Scope). - nginx-Routing-Änderung jetzt — erst nach Cutover-Phase 4. - Force-Push / git history-rewrite.