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).
178 lines
12 KiB
Markdown
178 lines
12 KiB
Markdown
# 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.
|