Documents the JWT-to-sb_secret key migration, deployment touchpoints (local .env, Portainer Stack 115, Gitea workflow), and the fresh-clone .env setup needed after machine migration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.1 KiB
Supabase Credential Migration — Deployment Notes
Hintergrund
Das Tippspiel-Repo hatte versehentlich Secrets im Git-History (alter .gitea/workflows/build.yml). Im Zuge der Bereinigung wurden:
- DB-Passwort rotiert
- Service Role Key vom Legacy-JWT-Format auf das neue Supabase-Schlüsselformat migriert
Die alten Werte sind ungültig — Deployments mit alter Konfiguration funktionieren nicht mehr.
Supabase Key-System: was sich geändert hat
Supabase hat das API-Key-System umgestellt:
| Alt (Legacy, JWT) | Neu |
|---|---|
anon (Browser/Public) |
sb_publishable_... |
service_role (Backend) |
sb_secret_... |
Wichtige Unterschiede:
- Legacy-Keys sind JWTs (lange Strings beginnend mit
eyJ...), neue Keys sind Tokens beginnend mitsb_secret_...bzw.sb_publishable_... - Legacy-Keys können nur als Gruppe rotiert werden (per JWT-Secret-Reset, invalidiert alle User-Sessions)
- Neue Keys sind einzeln rotierbar
- Das Supabase-SDK akzeptiert beide Formate — kein Code-Change nötig, nur die Env-Variable austauschen
Wir nutzen jetzt den neuen sb_secret_...-Key unter dem Variablennamen SUPABASE_SERVICE_ROLE_KEY.
Betroffene Variablen
| Variable | Wo zu finden |
|---|---|
DATABASE_URL |
Supabase Dashboard → Project Settings → Database → Connection string (Transaction Pooler, Port 6543) |
SUPABASE_URL |
Supabase Dashboard → Project Settings → API (unverändert) |
SUPABASE_SERVICE_ROLE_KEY |
Supabase Dashboard → Project Settings → API Keys → Secret keys Tab → sb_secret_... |
Wichtig: Bei
DATABASE_URLden Transaction Pooler (Port 6543, Hostaws-0-eu-west-1.pooler.supabase.com, Userpostgres.<project-ref>) verwenden — nicht Direct Connection (Port 5432, Hostdb.<project-ref>.supabase.co). Direct Connection läuft nur über IPv6 und scheitert in vielen Umgebungen mitENOTFOUND.
Korrektes URL-Format (Pooler):
postgresql://postgres.<project-ref>:<password>@aws-0-eu-west-1.pooler.supabase.com:6543/postgres
Deployment-Stellen
Die Variablen müssen an drei Stellen synchron gehalten werden:
1. Lokales Dev-Environment
Datei: backend/.env (gitignored, nicht im Repo).
Wichtig bei frischem Clone / Maschinenwechsel: backend/.env ist bewusst nicht in Git — Secrets gehören niemals ins Repo. Stattdessen liegt backend/.env.example als Template mit Platzhaltern bei. Auf jeder neuen Maschine einmalig:
cd backend
cp .env.example .env
# dann .env öffnen und Platzhalter durch echte Werte ersetzen
Mindestens benötigte Werte für lauffähiges Dev-Setup:
DATABASE_URL=postgresql://postgres.<project-ref>:<password>@aws-0-eu-west-1.pooler.supabase.com:6543/postgres
SUPABASE_URL=https://<project-ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=sb_secret_...
FOOTBALL_API_KEY=...
ANTHROPIC_API_KEY und ELEVENLABS_API_KEY aus .env.example werden aktuell nicht benötigt (Features wurden entfernt).
Empfehlung: Werte in einem Passwort-Manager (1Password, Bitwarden o.ä.) hinterlegen, damit sie beim nächsten Umzug sofort zur Hand sind. Niemals per E-Mail/Slack/Chat verschicken.
2. Portainer Stack wm2026-tippspiel (Stack 115)
Portainer UI → Stack → Environment variables → Werte ersetzen → Update the stack
Häufiger Fehler: Beim Pasten nicht den Variablennamen mitkopieren. Ins Value-Feld kommt nur der reine Wert, nicht
DATABASE_URL=postgresql://.... Sonst entsteht eine doppelte Präfix-Verschachtelung und der Container kann keine DB-Verbindung aufbauen.
3. CI/CD Workflow (.gitea/workflows/build.yml)
Der Workflow holt die Env-Vars dynamisch aus der Portainer Stack-Konfig (Schritt 2). Im Repo selbst stehen keine Secrets, sondern nur Referenzen wie ${{ secrets.PORTAINER_TOKEN }} und ${{ secrets.DEPLOY_TOKEN }}. Diese beiden Gitea-Secrets bleiben unverändert.
Migration-Checklist für neue Umgebung
- Aktuelles DB-Passwort aus Supabase abrufen (oder neu setzen unter Project Settings → Database)
- Aktuellen
sb_secret_...-Key aus Supabase Dashboard kopieren (API Keys → Secret keys) - Connection String aus Pooler-Tab kopieren,
[YOUR-PASSWORD]ersetzen - In Ziel-Stack die drei Variablen setzen:
DATABASE_URL,SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY - Stack redeployen
- Smoke-Test:
curl http://<host>:<port>/api/matches→ soll JSON mitmatches-Array liefern (kein 500) - Falls 500: Container-Logs prüfen auf
ENOTFOUND(→ Direct Connection statt Pooler) oder28P01 invalid_password(→ Passwort falsch oder Pooler-Cache braucht ~30s)
Code-relevante Stellen
- DB-Pool wird konfiguriert in
backend/src/db/client.ts— liestDATABASE_URL - SSL ist nur in
NODE_ENV=productionaktiv, mitrejectUnauthorized: false - Pool-Settings: max 10 Verbindungen, 5s Connection-Timeout, 30s Idle-Timeout
Referenzen
- Supabase API Keys Doku: https://supabase.com/docs/guides/getting-started/api-keys
- Supabase Connection Pooler: https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler