rebreak-monorepo/ops/WEBHOOK_MIGRATION_PLAN.md
chahinebrini e76be7ee78 feat(profile): Profile-Page komplett + Header-Dropdown + UI-Pattern-Fixes
Profile (3 Iterationen):
- app/profile/index.tsx + components/profile/* (Header, StatsBar, Approved,
  Streak, UrgeStats, Demographics, DigaMissionBanner)
- echte Live-Daten via useMe-Hook (Avatar/Nickname/Plan/Email/Provider-Pill)
- Demographics mit echten Inputs (TextInput + Bottom-Sheet-Selects),
  debounced auto-save, Pro-Trial-Reward-Banner, Mikro-Why-Texte
- Approved Domains als plain integer (KEIN Plan-Slot/Cap)
- Friendly Hint-Text statt Progress-Bar (alignSelf:'stretch' Pattern)
- StatsBar zentriert mit 3 prominenten Cards (vertikale Dividers)
- Cooldown-Timeline als Liste mit 1px-Rail
- ApprovedDomainsList: Collapse-Chevron rechts in Title-Row (Pattern-Fix)
- Eigene vs fremde Profile-Ansicht streng getrennt (DSGVO/Anonymität)

Header-Dropdown (kein 3-Punkte-Icon):
- Avatar als Trigger im AppHeader (User-Wunsch)
- Custom-Modal beide Plattformen, Card-Style
- SOS prominent oben (nur Wort 'SOS' rot, Tagline 'wir sind für dich da' klein darunter)
- Profile/Settings/Games/Debug(__DEV__)/Logout
- Logout neutral (nicht rot — Recovery-tonal)
- AppHeader: neue showBack + title Props für Sub-Routes

Routes (Stub bis Phase C):
- app/profile/[userId].tsx — anonym (nur public-Stats)
- app/settings.tsx — Coming-Soon-Skeleton
- app/games.tsx — Standalone Games-Page mit GameCard-Grid
- app/debug.tsx — __DEV__-only

Game-Picker (Migration aus Nuxt):
- components/games/{GameCard, StarRating, GameRatingStars}
- 2x2 Grid, 56pt SVG-Icons (inline aus components/urge/gameSvgs.ts)
- Live-Backend /api/games/ratings (silent-fail)
- Re-use UrgeGames.tsx ohne TTS/Cooldown-Loop

UI-Pattern-Fixes (alle aus screenshot-User-Feedback 2026-05-07):
- Snake-Bug (food-pellet React-18-StrictMode-Reducer-double-call) gefixt
- Snake-Buttons platform-native (iOS-blue / Android-ripple)
- Tetris-Margins (16px paddingHorizontal)
- PostCard-Buttons Apple-44pt-Hit-Area (Image-Select, Image-Remove,
  Cancel, Share-Pill — via hitSlop)
- ProfileHeader Demographics-Hint: alignSelf:'stretch' Pattern
- ApprovedDomainsList Collapse: Title flex:1 + Chevron rechts
- ProtectionDetailsSheet FAQ-Items: alignSelf:'stretch' defensive
- AppHeader Back-Button: neue showBack-Prop + chevron-back

Memory + Plan-Docs:
- 17 Memory-Files dokumentieren System-Wissen + Patterns
- ops/{CUTOVER, UI_MIGRATION, PROFILE_PAGE, WEBHOOK, GAMES_1V1,
  RELEASE_READINESS, TESTING_STATE, MAESTRO_HOSTING}_*.md

Backend bleibt unverändert (Tier-LLM + Nickname + sort:latency
sind seit gestern deployed).
2026-05-07 18:22:58 +02:00

12 KiB

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 /webhookproxy_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)

  1. 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)

  1. 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).
  2. Auf GitHub: zweite Webhook-Konfig anlegen, beide aktiv. Beide verifizieren mit demselben Secret.
  3. Logs beide Streams beobachten — wenn neuer Endpoint zuverlässig deployt, alter wird redundant.

Phase 4: Cutover

  1. GitHub-Webhook umstellen: alten URL-Webhook deaktivieren, nur neuer /api/webhook/github aktiv.
  2. nginx: location /webhookproxy_pass http://127.0.0.1:9000 ENTFERNEN (oder auf Backend umrouten).
  3. pm2 stop rebreak-webhook && pm2 delete rebreak-webhook (NUR auf User-Approval — destruktiv).
  4. scripts/deploy-webhook/ archivieren (nicht löschen — Reference).
  5. 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

  1. Healthcheck-Endpoint hinzufügen: backend/server/api/webhook/health.get.ts → returns { ok, lastDeploy, queueDepth }.
  2. Persistent Deploy-Log: in /var/log/rebreak/deploys.jsonl schreiben (statt nur pm2-stream).
  3. 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.