# Runbook: Admin-Plattform parallel zum Altbetrieb testen (ohne Merge, nur mit Demo-Tenant) Ziel: Die neue Admin-Plattform und den neuen Core **parallel** zu den laufenden Tenants betreiben – mit **einem** neuen Test-Tenant `demo-admin`. Nichts Bestehendes wird angefasst, es wird **nichts gemergt**, und jeder Schritt lässt sich einzeln zurücknehmen. --- ## 0. So läuft das Ganze parallel (und warum der Altbetrieb sicher ist) ``` ALTBETRIEB (unverändert) NEUBETRIEB (Test) ──────────────────────── ───────────────── Gitea: main / cortecs-llm-migration Gitea: Branch admin-platform-integration └ build-deploy.yml → :latest + deploy-all └ build-branch-image.yml → NUR Image : (kein :latest, kein Deploy) Mittwald: app-demo, app-rotary, app-sprinz, … Mittwald: app-demo-admin ← neuer, eigener Service + Volume + Ingress Domain: demo. … Domain: demo-admin. Daten: ins Image gebacken (tenants/) Daten: Bundle von der Admin-Plattform (TENANT_SOURCE=admin) OIDC-Client: von Hand angelegt OIDC-Client: automatisch über die Login-API Admin-Plattform auf Coolify (eigene Domain, eigene Postgres) ``` Schutzmechanismen (alle eingebaut und getestet): | Schutz | Wirkung | |---|---| | Branch-Trennung | `build-deploy.yml` läuft nur auf `main`/`cortecs-llm-migration` (auch nicht per „Run workflow“ vom Test-Branch). Der Test-Branch baut nur ein Image mit Tag = Git-SHA. | | Eigener Tenant | `demo-admin` hat eigenen Service `app-demo-admin`, eigenes Volume `vol-demo-admin`, eigenen Host und eigenen OIDC-Client. `app-demo` bleibt unberührt. | | `DEPLOY_ALLOWED_TENANTS=demo-admin` | Die Admin-Plattform darf **nur** diesen Tenant live anfassen (Mittwald **und** Login-API). Auch „Alle neu deployen“ trifft nur ihn. | | `DEPLOY_LIVE=false` zum Start | Erst nur Dry-Run (liest den Stack, schreibt nichts). | | Differenzielles PATCH + Guard | Es wird nur `app-demo-admin` geschrieben; danach prüft der Admin, dass alle fremden Services noch da sind. | | Default `TENANT_SOURCE=git` im Core | Der neue Core verhält sich ohne Env wie der alte. | Rückgängig machen: siehe **Abschnitt 11**. --- ## 1. Werte zusammentragen (einmalig) | Wert | Woher | Verwendung | |---|---|---| | `DOMAIN` | Gitea → Repo `transkriptipt-platform` → Einstellungen → Actions → **Variablen** | Basis-Domain in der Admin-Plattform | | `PROJECT_ID`, `STACK_ID` | dito | Mittwald-Ziel | | `REGISTRY` (z. B. `gitea.onyva.dev/onyva`) | dito | Registry in der Admin-Plattform | | `MITTWALD_API_TOKEN` | Gitea → **Secrets** (du kennst den Wert; derselbe Token) | Env der Admin-Plattform | | Weitere Variablen: `OIDC_ISSUER`, `AUTH_ENABLED`, `APP_COOKIE_SECURE`, `MAX_FILE_SIZE_MB`, `CORTECS_BASE_URL`, `LLM_MODEL`, `LLM_MAX_TOKENS`, `LLM_TEMPERATURE`, `SMTP_HOST/PORT/USER/FROM`, `MISTRAL_*`, `COST_*` | dito | in **Einstellungen** der Admin-Plattform (Abschnitt 6) | | `CORTECS_API_KEY`, `SMTP_PASSWORD` | `cd ~/PycharmProjects/transkript-platform && sops -d --extract '["CORTECS_API_KEY"]' secrets/shared.enc.yaml` (analog `SMTP_PASSWORD`) | Einstellungen → Geteilte Secrets | | `COST_INGEST_KEY` von `demo` | `sops -d --extract '["COST_INGEST_KEY"]' tenants/demo/secrets.enc.yaml` | Secret des Test-Tenants (Abschnitt 7, Punkt 7.4) | | **``** | du wählst: Domain, unter der Coolify die Admin-Plattform ausliefert, z. B. `admin.deine-domain.de` | muss **öffentlich per HTTPS** erreichbar sein (der Tenant-Container holt von dort sein Bundle) | | **``** | deine Login-E-Mail bei login.onyva.dev (Konto muss dort existieren) | Admin-Zugriff + Freigabe im Test-Tenant | Werte **nicht** in Chats oder Tickets kopieren (Keys, Secrets). --- ## 2. Core-Branch pushen (Image bauen lassen) ```bash cd ~/PycharmProjects/transkript-platform git branch --show-current # → admin-platform-integration git log --oneline -3 # 13055b8 CI: build-deploy nur von Produktions-Branches … git rev-parse HEAD # → voller SHA; z. B. 13055b811ca0030841f41ce1f10e4f30cba1a7db ← MERKEN (= CORE_SHA) git push -u origin admin-platform-integration ``` Was jetzt in Gitea passiert (Repo → **Actions**): | Workflow | Erwartung | |---|---| | `test` | grün (Schema ↔ Code ↔ Tenants, Bundle-Loader) | | `build-branch-image` | grün; Log-Zeile „Image gebaut: `/transkript-platform:`“ | | `build-deploy` | **läuft nicht** (Branch nicht gelistet) – wenn doch: sofort abbrechen und melden | Prüfen: Gitea → Repo → **Packages** → `transkript-platform` hat einen neuen Tag = voller SHA. Der Tag `latest` ist **unverändert** (Zeitstempel!). > ✅ Ergebnis: `CORE_SHA` (voller 40-stelliger SHA). Lege ihn in deine Notizen. --- ## 3. Admin-Plattform ins eigene Repo 1. Gitea → **Neues Repository**: Organisation `Onyva`, Name `onyscribe-admin`, **privat**, leer (ohne README/.gitignore/Lizenz). 2. Lokal: ```bash cd ~/PycharmProjects/onyscribe-admin git status --short | head # .env und .data dürfen NICHT erscheinen (sind ignoriert) git ls-files | grep -E '(^|/)\.env$|\.db$' || echo "ok: keine Secrets/DB im Index" git add -A git commit -m "Onyscribe Admin: Initialer Stand" git remote add origin https://gitea.onyva.dev/Onyva/onyscribe-admin.git git push -u origin main ``` Hinweis: `seed/tenants.seed.json.gz` enthält die Tenant-Konfigurationen inkl. Zusatzdateien (z. B. `namensliste.txt` – personenbezogen). Das liegt heute schon im Altrepo; Repo privat halten. --- ## 4. Login-System: Client + API-Key für die Admin-Plattform Im Login-Dashboard (login.onyva.dev), als Admin: 1. **API-Keys → neuer Key** („onyscribe-admin“, ggf. Ablaufdatum). Klartext `onyva_…` **einmalig** kopieren → später `ONYVA_LOGIN_API_KEY`. 2. Client für die Admin-Plattform selbst anlegen (Dashboard **oder** per API): ```bash KEY=onyva_… # der Key aus 1. curl -sX POST https://login.onyva.dev/api/v1/apps -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"name":"Onyscribe Admin","redirect_uris":["https:///auth/callback"],"auth_mode":"required","totp_mode":"disabled"}' # → client_id + client_secret SOFORT sichern (nur einmal sichtbar) curl -sX PUT https://login.onyva.dev/api/v1/apps//memberships -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"user_email":"","role":"admin"}' ``` Ergebnis: `client_id`, `client_secret` (Admin-Login), `ONYVA_LOGIN_API_KEY`. --- ## 5. Coolify: Admin-Plattform deployen **Neue Ressource → Docker Compose (aus Git)**: Repo `Onyva/onyscribe-admin`, Branch `main`, Compose-Datei `docker-compose.yml` (Zugriff auf das private Repo per Deploy-Key/Token einrichten). **Umgebungsvariablen** (Coolify → Ressource → Environment): | Variable | Wert | Phase | |---|---|---| | `POSTGRES_PASSWORD` | langes Zufallspasswort | immer | | `SECRET_KEY` | `python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` – **sichern! ohne ihn sind die Secrets in der DB unlesbar** | immer | | `AUTH_ENABLED` | `true` | immer | | `OIDC_ISSUER` | `https://login.onyva.dev` | immer | | `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | aus Abschnitt 4 (Punkt 2) | immer | | `OIDC_REDIRECT_URI` | `https:///auth/callback` | immer | | `OIDC_LOGOUT_REDIRECT_URI` | `https:///` | immer | | `APP_COOKIE_SECURE` | `true` | immer | | `ADMIN_ALLOWED_EMAILS` | `` (weitere kommagetrennt) | immer – ohne Regel wird jeder Login abgelehnt | | `MITTWALD_API_TOKEN` | derselbe Token wie in Gitea | immer | | `ONYVA_LOGIN_API_KEY` | aus Abschnitt 4 (Punkt 1) | immer | | `DEPLOY_ALLOWED_TENANTS` | `demo-admin` | immer | | `DEPLOY_LIVE` | **`false`** | Phase 1 (Dry-Run) → in Abschnitt 8 auf `true` | | `OIDC_SYNC_LIVE` | `false` | optional `true`, um den Login-Abgleich separat vor dem Deploy zu testen | | `CORE_RELEASE_TOKEN` | leer | erst in Abschnitt 10 | | `CORTECS_API_KEY` | leer (wird in der UI als Secret gesetzt) | optional | **Domain**: Service `admin` → Domain `https://:8300` (Port 8300 = Container-Port). **Persistente Daten**: Volume `pgdata` (Postgres) – Backup einrichten. Deploy starten. Der erste Build nutzt das normale `Dockerfile` (mitgelieferter Core-Snapshot, **keine Registry nötig**). Prüfen: - [ ] `https:///healthz` → `{"ok":true}` - [ ] `https:///` → Weiterleitung zu login.onyva.dev → Login → Dashboard mit **7 Tenants** (Seed). - [ ] Seitenleiste unten: „Deploy: gesperrt · Dry-Run“, „“. - [ ] `https:///auth/me` zeigt `"auth_enabled": true`. Steht dort `false` (Seitenleiste: rot „Login aus – offen!“), ist die Plattform für jeden offen: `AUTH_ENABLED=true` (ohne Anführungszeichen) und die OIDC-Variablen prüfen. Ohne Login sind Live-Aktionen automatisch gesperrt. - Probleme: „Kein Zugriff“ → `ADMIN_ALLOWED_EMAILS`; `redirect_uri`-Fehler → Redirect-URI im Login-Client **exakt** wie in `OIDC_REDIRECT_URI`. --- ## 6. Admin-Plattform konfigurieren (Menü „Einstellungen“) Felder (Spalte „Gitea“ = dieselbe Variable wie bisher), dann oben **„Alles speichern“**: | Feld in der Admin-Plattform | Wert / Gitea | |---|---| | Basis-Domain | `DOMAIN` | | Mittwald PROJECT_ID / STACK_ID | `PROJECT_ID` / `STACK_ID` | | Registry | `REGISTRY` | | Image-Name | `transkript-platform` | | **Öffentliche URL der Admin-Plattform** | `https://` | | **Core kann Bundle laden** | ✔ aktivieren (betrifft nur Tenants mit Quelle „Admin“, also nur `demo-admin`) | | Login (OIDC) & Limits | `OIDC_ISSUER`, `AUTH_ENABLED` (true), `APP_COOKIE_SECURE` (true), `MAX_FILE_SIZE_MB` | | LLM & Transkription | `CORTECS_BASE_URL`, `LLM_MODEL`, `LLM_MAX_TOKENS`, `LLM_TEMPERATURE`, `MISTRAL_*` | | E-Mail (SMTP) | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_FROM` | | Kostenerfassung | `COST_API_BASE`, `COST_TRACKING_ENABLED` | | „Weitere Core-Einstellungen“ (aus dem Schema) | nur ausfüllen, was du in Gitea auch gesetzt hast (`COST_LLM_*`, `COST_STT_PER_MINUTE`, `STT_*` …); leer = Standard des Cores | | Rollout → Canary-Tenants | `demo-admin` | | Geteilte Secrets → `CORTECS_API_KEY` | **Wert setzen** (aus `sops -d …`) | | Geteilte Secrets → `SMTP_PASSWORD` | **Wert setzen** (nur nötig, wenn der Test E-Mail-Versand prüfen soll) | Dann: Karte „Login-API“ → **„Verbindung testen“** → `✔ Verbindung ok – N App(s) im Login`. --- ## 7. Test-Tenant `demo-admin` anlegen und konfigurieren **7.1 Anlegen**: Dashboard → **+ Neuer Tenant** → Name `Onyva Demo (Admin-Test)`, ID `demo-admin`, Ausgangspunkt „Kopie von Onyva Demo“ → Anlegen. (Quelle ist automatisch „Admin“, OIDC-Verwaltung „managed“.) **7.2 Spracherkennung**: Tenant → *Funktion & STT* → Provider `cortecs`, Modell `voxtral-mini-transcribe-2602` (dann reicht der geteilte Cortecs-Key; `demo` nutzt sonst Mistral und bräuchte einen eigenen `MISTRAL_API_KEY`). **7.3 Optisch unterscheidbar machen**: *Marke & Farben* → Akzent z. B. `#E8590C` → „Töne aus Akzent ableiten“. Oben rechts **Prüfen & veröffentlichen** → Nachricht „Test-Tenant“ → **v2**. **7.4 Secrets**: Tenant → *Secrets* → `COST_INGEST_KEY` **Wert setzen** (der Wert von `demo`; Kosten laufen dann unter `demo`, für den Test okay). `SESSION_SECRET` und `ADMIN_BUNDLE_TOKEN` sind schon erzeugt. `OIDC_CLIENT_ID/SECRET` legt der Deploy automatisch an. **7.5 Login**: Tenant → *Domain & Login*: Subdomain `demo-admin`; OIDC-Verwaltung `managed`, Zugriff `required`; **Freigaben**: `` als `editor` (Nutzer muss im Login existieren) → *Speichern*. **„Gegen Login prüfen“** (nur lesend) → erwartet: „würde App ‚Onyscribe – Onyva Demo (Admin-Test)‘ anlegen (Redirect: https://demo-admin./auth/callback …)“ und „würde als editor freigeben“. **7.6 Core pinnen**: Tenant → *Übersicht* → Karte „Core-Version“ → Pin = **voller SHA aus Abschnitt 2** → *Pin setzen*. (Der Pin sorgt dafür, dass genau das Branch-Image läuft und der Health-Check die Core-Version prüfen kann.) **7.7 Vorflug-Check + Dry-Run**: Tenant → *Deploy* → **Vorflug-Check → Prüfen**. Erwartet in Phase 1: alles grün/gelb, **gelb** nur „DEPLOY_LIVE nicht aktiv“; keine roten Punkte. Typische rote Punkte und Fix stehen direkt dabei (z. B. Registry/PROJECT_ID/Token fehlen, `CORTECS_API_KEY`/`COST_INGEST_KEY` fehlen, „Core kann Bundle laden“ aus, Admin-URL nicht erreichbar). Dann **Dry-Run** → Log lesen: „geschützte bestehende Services (bleiben unangetastet): [app-demo, app-rotary, …]“, `[DRY-RUN] PATCH …` für Volume und Service, kein Schreibzugriff. --- ## 8. Live-Deploy des Test-Tenants 1. Coolify → Admin-Ressource → `DEPLOY_LIVE=true` setzen (`DEPLOY_ALLOWED_TENANTS=demo-admin` bleibt) → neu deployen (Daten bleiben). 2. Admin → `demo-admin` → *Deploy* → Vorflug-Check → muss **„Bereit“** zeigen → **Deployen**. Was passiert (Log): OIDC-Client im Login anlegen und Secret speichern → Pflicht-Secrets prüfen → Volume + Service per PATCH → Service neu erzeugen → Ingress `demo-admin.` → Health-Check (bis ~90 s Wartezeit für Start + Zertifikat). Abnahme-Checkliste: - [ ] Deploy-Log endet mit `Health-Check … ✔ · Core 13055b811c · Bundle v2` und `✔ Fertig` - [ ] mStudio: Service `app-demo-admin` läuft; **alle anderen Services unverändert** (Image-Tags wie vorher) - [ ] `https://demo-admin./health` → `core.version` = dein SHA, `bundle` = `{"source":"admin","version":2,"stale":false}` - [ ] Seite öffnen → Login → freigegebener Nutzer kommt rein → Oberfläche in der **neuen Farbe** - [ ] Protokoll erzeugen (kleines Transkript hochladen) → Zusammenfassung → PDF/Word → Aussehen = Vorschau in der Admin-Plattform - [ ] Admin → Dashboard → **Zustand prüfen**: `demo-admin` grün. Die Altbetrieb-Tenants zeigen „Core ohne Vertrag (alte Version)“ – **erwartet und harmlos** (sie laufen noch auf dem alten Image) - [ ] Altbetrieb: `demo.` und die anderen Kunden funktionieren unverändert --- ## 9. Änderung ohne Core-Änderung testen 1. Admin → `demo-admin` → *Marke & Farben*: Akzent ändern → **Prüfen & veröffentlichen** (v3) → *Deploy* → **Deployen** (nur Neustart, kein Image-Build). 2. Erwartung: Health-Check `Bundle v3 ✔`; Seite neu laden → neue Farbe. 3. Agent: *KI-Agent* → „Setze die Akzentfarbe auf #0B6E4F“ → Live-Vorschau beobachten → „In Entwurf übernehmen“ → veröffentlichen → deployen. 4. Ausfallsicherheit (nur `demo-admin`!): Coolify → Admin-Ressource **stoppen**; in mStudio den Container `app-demo-admin` **neu starten** → er startet mit gecachtem Bundle: `https://demo-admin./health` → `bundle.source = "cache"`, `stale = true`. Admin wieder starten → nächster Container-Neustart holt wieder `admin`. --- ## 10. (Optional) Den kompletten Core-Release-Weg testen 1. Coolify: `CORE_RELEASE_TOKEN` = langes Zufallstoken, Admin neu deployen. Gitea → Repo → Einstellungen → Actions: **Variable** `ADMIN_URL` = `https://`, **Secret** `ADMIN_RELEASE_TOKEN` = dasselbe Token. (Wirkt nur auf Workflows des Test-Branches; `main` kennt den `notify-admin`-Job nicht.) 2. Im Core-Repo eine Kleinigkeit committen und auf den Branch pushen → `build-branch-image` baut und meldet das Release. 3. Admin → Menü **Core**: neues Release, Badge **„Admin älter/anders“** (die Admin-Plattform enthält noch den alten Core-Snapshot). 4. Angleichen: `cd ~/PycharmProjects/onyscribe-admin && scripts/sync_core.sh && git add -A && git commit -m "Core-Snapshot aktualisiert" && git push` → Coolify deployt → Badge **„gleicher Core“**. *(Später ersetzt `Dockerfile.fromcore` diesen Schritt: Admin wird direkt auf dem Core-Image gebaut.)* 5. **Prüfen** → Matrix mit allen 7 Tenants (beim ersten Mal „neu (keine Baseline)“) → **Freigeben** → **Dry-Run** → *Tenant `demo-admin`: Pin aufheben* → **Ausrollen** → nur `demo-admin` wird live ausgerollt (Allowlist), Health-Check prüft die neue Core-SHA. 6. Rollback testen: **Zurückrollen** (stellt den vorherigen Tag wieder her). --- ## 11. Abbruch / Rückbau (jederzeit) | Was | Wie | |---|---| | Test-Tenant live entfernen | Admin → `demo-admin` → Übersicht → Gefahrenbereich → **Offboard-Dry-Run** prüfen → **Offboard (live)** (entfernt Service, Volume, Ingress) | | OIDC-Client des Test-Tenants | Gefahrenbereich → **OIDC-Client im Login löschen** | | Admin-Plattform | Coolify-Ressource stoppen/löschen; Login-Client „Onyscribe Admin“ und API-Key im Login löschen | | Branch/Image | Gitea: Branch `admin-platform-integration` löschen, Package-Tag (SHA) löschen | | Alles Live sperren | `DEPLOY_LIVE=false` (und/oder `DEPLOY_ALLOWED_TENANTS` leeren) → Admin neu deployen | Der Altbetrieb braucht in keinem dieser Fälle eine Änderung. --- ## 12. Fehlersuche | Symptom | Ursache / Fix | |---|---| | Vorflug: „Mittwald-Stack lesbar“ rot | Token-Rechte, `PROJECT_ID`/`STACK_ID` in Einstellungen prüfen (dieselben Werte wie Gitea) | | Deploy: „Pflicht-Secrets ohne Wert: …“ | `CORTECS_API_KEY` (Einstellungen → Geteilte Secrets), `COST_INGEST_KEY` (Tenant → Secrets), ggf. `MISTRAL_API_KEY` (nur bei STT-Provider mistral) | | Deploy: „… nicht für Live-Schreibzugriffe freigegeben“ | `DEPLOY_ALLOWED_TENANTS` enthält die Tenant-ID nicht | | Deploy: „Quelle ‚Admin‘ ist noch gesperrt“ | Einstellungen → „Core kann Bundle laden“ aktivieren | | Deploy: „OIDC-Abgleich fehlgeschlagen: API-Key ungültig (401)“ | `ONYVA_LOGIN_API_KEY` falsch/abgelaufen; Key im Login neu erzeugen | | Deploy: „… Freigaben: existiert im Login nicht“ (Warnung) | Nutzer im Login anlegen, dann *Domain & Login → Jetzt abgleichen* | | Health-Check: „Core X statt Y“ | Pin/Tag stimmt nicht mit dem Image überein oder Image fehlt in der Registry (mStudio-Container-Log) | | Health-Check: „Bundle cache v… (veraltet)“ | Container erreicht die Admin-URL nicht: Domain/HTTPS/Firewall, `admin_public_url`, Bundle-Token | | Container-Log: „Tenant-Bundle … nicht verfügbar“ | wie oben; zusätzlich `ADMIN_URL`/`ADMIN_BUNDLE_TOKEN` im Plan (Deploy → Service-Definition) prüfen | | Login der Tenant-Seite: `redirect_uri`-Fehler | *Domain & Login → Gegen Login prüfen → Jetzt abgleichen* (ergänzt die Redirect-URI) | | Login: `invalid_client` | `OIDC_CLIENT_SECRET` fehlt/ist veraltet → *Jetzt abgleichen* bzw. neu deployen | | Zertifikat/Ingress fehlt | Token braucht Domain/Ingress-Recht (Log: „Ingress … NICHT angelegt“); Service läuft trotzdem, nach Rechteänderung erneut deployen | | Admin „Zustand prüfen“: „nicht erreichbar“ | Tenant noch nicht deployt bzw. DNS/Zertifikat noch nicht fertig | --- ## 13. Danach (nicht Teil dieses Tests) Erst wenn der Test-Tenant mehrere Tage sauber läuft: Core-Branch reviewen und mergen (→ `build-deploy` baut dann den neuen Core für alle), Secrets der echten Tenants importieren (`python scripts/import_tenants.py --with-secrets`), pro Tenant die **Cut-over-Checkliste** abarbeiten und `DEPLOY_ALLOWED_TENANTS` schrittweise erweitern. Die echten Tenants stellst du **einzeln** um – Rückweg: Quelle zurück auf „Git“.