From 88c541c9ed33847aae0dc3f149f45c772b09eecc Mon Sep 17 00:00:00 2001 From: Benjamin Weinlich Date: Sat, 11 Apr 2026 22:26:05 +0200 Subject: [PATCH] docs(adr): mirror 6 ADRs from M7.1 into repo Adds the Architecture Decision Records that were written during the Forgejo deployment (M7.1) as part of moving docs from the iCloud folder into this versioned repository. Includes: - ADR-0001: Forgejo vs Gitea (non-profit stewardship) - ADR-0002: ai-apps placement (no separate VM) - ADR-0003: Native OIDC, not ForwardAuth - ADR-0004: Subdomain code.sdda.eu - ADR-0005: Volume mount on /data (lesson learned) - ADR-0006: Silent SSO via OAuth2 launch URL (lesson learned) Plus a docs/adr/README.md that explains the ADR format, lists the current ADRs, and provides a template for future entries. Refs OP#1118 --- docs/README.md | 56 +++++++++++++ docs/adr/0001-forgejo-vs-gitea.md | 39 +++++++++ docs/adr/0002-ai-apps-placement.md | 56 +++++++++++++ docs/adr/0003-native-oidc-not-forwardauth.md | 48 +++++++++++ docs/adr/0004-subdomain-code-sdda-eu.md | 38 +++++++++ .../adr/0005-volume-mount-data-not-var-lib.md | 59 +++++++++++++ docs/adr/0006-silent-sso-launch-url.md | 82 +++++++++++++++++++ docs/adr/README.md | 74 +++++++++++++++++ 8 files changed, 452 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/adr/0001-forgejo-vs-gitea.md create mode 100644 docs/adr/0002-ai-apps-placement.md create mode 100644 docs/adr/0003-native-oidc-not-forwardauth.md create mode 100644 docs/adr/0004-subdomain-code-sdda-eu.md create mode 100644 docs/adr/0005-volume-mount-data-not-var-lib.md create mode 100644 docs/adr/0006-silent-sso-launch-url.md create mode 100644 docs/adr/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..decafb8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,56 @@ +# docs/ + +Alle Dokumentation zur Electric-Horses-Infrastruktur. Jeder Unterordner hat seine eigene `README.md` mit Konventionen und Index. + +## Struktur + +``` +docs/ +├── README.md # diese Datei +│ +├── architecture/ # Topologie, Stack-Inventories, Diagramme +│ └── ai-apps-stacks.md # Stack-Snapshot auf ai-apps +│ +├── adr/ # Architecture Decision Records +│ ├── README.md # Format-Guide + Index +│ └── 0001-0006-*.md # 6 ADRs aus M7.1 +│ +├── runbooks/ # Schritt-für-Schritt Anleitungen für Ops +│ ├── README.md # Format + Index +│ ├── forgejo-admin-recovery.md +│ └── forgejo-backup-restore.md +│ +└── guides/ # Wiederverwendbare Setup-Anleitungen + ├── README.md # Format + Index + └── authentik-oauth2-provider.md +``` + +## Philosophie + +**Docs-as-Code:** Dokumentation lebt neben dem Code, im selben Git-Repo, mit derselben Change-History. Keine separaten Wikis (driften), keine Confluence (teuer, closed), keine Notion (lock-in). + +**Mermaid für Diagramme:** Forgejo rendert Mermaid-Blöcke im Web-UI direkt. Beispiel: +~~~mermaid +graph LR + Browser --> Nginx + Nginx --> Directus + Nginx --> Astro +~~~ + +**Markdown für alles andere:** Keine Word-Docs, keine PDFs (außer Anhänge). Alles in Plain-Text lesbar + commitbar + diffbar. + +## Was hierher gehört +- Architektur-Beschreibungen, Topologie-Diagramme +- ADRs (warum haben wir X gebaut wie wir es gebaut haben) +- Runbooks (wie debuggt / repariert man konkret) +- Guides (wie richtet man etwas Wiederkehrendes ein) +- How-Tos für Team-Onboarding (später, wenn das Team wächst) + +## Was NICHT hierher gehört +- Secrets / Credentials (niemals, Repo ist public!) +- Kundendaten / personenbezogene Daten (DSGVO) +- Marketing-Texte (gehört auf die Website) +- Lange Design-Specs in Rohform (lieber als ADR destillieren) + +## Navigation-Tipp +Im Forgejo-Web-UI kannst du die `README.md`-Dateien durchklicken — jede erklärt ihren Unterordner und verlinkt die einzelnen Inhalte. Das ersetzt ein Wiki-Interface. diff --git a/docs/adr/0001-forgejo-vs-gitea.md b/docs/adr/0001-forgejo-vs-gitea.md new file mode 100644 index 0000000..513b49b --- /dev/null +++ b/docs/adr/0001-forgejo-vs-gitea.md @@ -0,0 +1,39 @@ +# ADR-0001: Forgejo statt Gitea + +**Status:** Accepted +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment + +## Kontext +Wir brauchen ein selbst-gehostetes Git-Hosting für Code, Configs und Dokumentation. GitHub ist kommerziell und bei Microsoft; das passt nicht zum Souveränitäts-Prinzip unseres Stacks. Die naheliegende Open-Source-Alternative ist die Gitea-Familie. + +Gitea war lange die populärste FOSS-Option. Im August 2022 wurde die Entwicklung von einer kommerziellen Firma (Gitea Ltd.) übernommen. Das hat die Community gespalten: Ein Teil der ursprünglichen Maintainer gründete **Forgejo** als Community-Fork, gesteuert vom non-profit Verein **Codeberg e.V.** (Berlin). + +## Entscheidung +Wir nutzen **Forgejo** (nicht Gitea). + +## Konsequenzen + +### Positiv +- **Non-profit Stewardship:** Codeberg e.V. ist ein eingetragener Verein ohne kommerzielle Interessen. Keine Gefahr von rug-pulls oder "free tier" Beschränkungen. +- **Schnellere Security-Updates:** Forgejo hat einen dedizierten Security-Track. +- **API-kompatibel mit Gitea:** Migration zwischen Gitea und Forgejo in beide Richtungen ist trivial. Keine Lock-in. +- **Aktive Entwicklung:** Forgejo hat ein festes Release-Rhythmus und transparente Roadmap. +- **Passt zu unserem Prinzip:** Open Source > proprietär, Souveränität > Bequemlichkeit. + +### Negativ / Trade-off +- Kleinere Community als Gitea (noch). Dritt-Dokumentation findet sich öfter für Gitea — aber da APIs identisch sind, ist das selten ein Blocker. +- Integrations (z.B. mit CI-Tools) sind oft als "Gitea" beworben aber funktionieren meist auch für Forgejo. + +## Alternativen +- **Gitea:** Verworfen wegen kommerzieller Stewardship-Sorgen. +- **GitLab CE:** Zu schwergewichtig (>4 GB RAM, lange Installation), würde ai-apps sprengen. +- **Gogs:** Vorgänger von Gitea, Entwicklung eingeschlafen. Keine Zukunft. +- **SourceHut:** Minimalistisch, aber ungewöhnliches UI, kleine Community. +- **GitHub private:** Verworfen wegen Souveränitäts-Prinzip. + +## Referenzen +- [Forgejo Projekt](https://forgejo.org/) +- [Codeberg e.V.](https://join.codeberg.org/) +- [Warum Forgejo gegründet wurde](https://forgejo.org/2022-12-15-hello-forgejo/) diff --git a/docs/adr/0002-ai-apps-placement.md b/docs/adr/0002-ai-apps-placement.md new file mode 100644 index 0000000..631dd2c --- /dev/null +++ b/docs/adr/0002-ai-apps-placement.md @@ -0,0 +1,56 @@ +# ADR-0002: Forgejo auf ai-apps, nicht auf separater VM + +**Status:** Accepted +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment + +## Kontext +Forgejo kann überall laufen — auf einem eigenen Hetzner-Server, neben bestehenden Services, oder sogar on-prem. Wir müssen uns festlegen. + +Optionen geprüft: +1. **Auf ai-apps (Hetzner cx22)** neben n8n, eh-search, locosoft, etc. +2. **Auf Pegasus (Hetzner cx33)** neben dem Electric-Horses Stack +3. **Auf einem neuen separaten Hetzner cx22** nur für Forgejo +4. **On-premises im Autohaus** auf Hardware dort + +## Entscheidung +**Auf ai-apps.** Kein separater Server, kein Pegasus, kein on-prem. + +## Begründung + +### Warum ai-apps +- **Traefik 3.6.2 läuft schon** dort mit Let's Encrypt Resolver. Keine neue TLS-Infrastruktur nötig. +- **Stack-Pattern etabliert** (`/opt/ai-apps//docker-compose.yml`) — Forgejo reiht sich nahtlos ein. +- **Private-Network-Kommunikation** mit anderen Services verfügbar (Mailcow, Authentik, etc.). +- **Ressourcen reichen:** 7.6 GB RAM total, nach Verbrauch der bestehenden Services ~5.7 GB verfügbar (inkl. Cache). Forgejo + Postgres peak ~800 MB. +- **Operations-Pattern bekannt:** Docker Compose, Portainer, Cron-Backups — alles schon da. + +### Warum nicht Pegasus +Pegasus hostet den öffentlichen Webseiten-Stack (Astro, Directus, MariaDB, Nginx, Qdrant). Das ist kunden-facing Production mit hohen Verfügbarkeitsanforderungen. Wir wollen keine zusätzliche Last oder Neuerungen dort. Pegasus hat außerdem kein public IPv4 — wir müssten über Webmin proxyn, was den Pfad unnötig verkompliziert. + +### Warum nicht separater Server +Ein neuer cx22 würde ~4.50 €/Monat kosten und zusätzlich eigenen Traefik, eigene Let's Encrypt Config, eigene Backup-Infra, eigene Monitoring-Integration erfordern. Für einen einzelnen Service mit <1 GB RAM-Bedarf ist der Ops-Overhead nicht gerechtfertigt. **Re-evaluation:** Sobald Forgejo Actions (CI/CD runners) aktiviert werden, wird der Ressourcenbedarf steigen und ein Upgrade auf cx32 oder ein separater Runner-Host Sinn machen. + +### Warum nicht on-prem +Git-Hosting muss 24/7 von außen erreichbar sein (Home-Office, Mobil, von überall). Das Autohaus-LAN ist hinter Consumer-Internet und hat keinen stabilen Public-Pfad. Außerdem steht der on-prem Stack (Loco-Soft-Server, eh-sync Bridge) unter anderen Verfügbarkeitsbedingungen. + +## Konsequenzen + +### Positiv +- Schnelles Deployment (keine neue Infrastruktur) +- Keine Extra-Kosten +- Integration mit Authentik via Private Network (10.0.0.x) +- Konsolidiertes Monitoring (alle Stacks via Portainer sichtbar) + +### Negativ / Risiko +- **RAM-Druck:** Bei ~250 MB wirklich freiem RAM ist der Spielraum knapp. Forgejo + Postgres peak ~800 MB kommt nah an die Obergrenze. Mitigation: Monitoring, ggf. Upgrade auf cx32 (16 GB, +4 €/Monat) wenn nötig. +- **Single-Point-of-Failure ai-apps:** Wenn ai-apps ausfällt, sind n8n, eh-search, locosoft und Forgejo gleichzeitig weg. Mitigation: Hetzner Cloud Snapshots, Docker Volume Backups. +- **Keine CI-Runner möglich:** Forgejo Actions würde ai-apps sprengen. Wenn CI dazu kommt, separater Runner-Host (z.B. Woodpecker auf cx22). + +## Re-evaluation-Trigger +- RAM-Nutzung auf ai-apps dauerhaft > 85% +- Wunsch nach Forgejo Actions / CI-Runnern +- Mehrere gleichzeitige große Git-Pushes verlangsamen andere Services spürbar + +In diesen Fällen: Separater Hetzner-Server für Forgejo (oder cx32-Upgrade von ai-apps). diff --git a/docs/adr/0003-native-oidc-not-forwardauth.md b/docs/adr/0003-native-oidc-not-forwardauth.md new file mode 100644 index 0000000..e8c4fc7 --- /dev/null +++ b/docs/adr/0003-native-oidc-not-forwardauth.md @@ -0,0 +1,48 @@ +# ADR-0003: Natives OIDC statt Authentik ForwardAuth + +**Status:** Accepted +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment + +## Kontext +Authentik bietet zwei Integrations-Patterns: + +1. **ForwardAuth / Proxy-Provider:** Der Authentik Embedded Outpost macht Auth vor der App. Traefik leitet Requests zuerst an den Outpost, der User-Claims als HTTP-Header injectet. Die App weiß nichts von OIDC, sieht nur "authenticated" Requests. Unsere bestehenden Apps (n8n, locosoft-hilfe-system) nutzen dieses Pattern. + +2. **Nativer OIDC Client:** Die App spricht selbst OIDC mit Authentik. Sie macht den Authorization-Code-Flow, holt Tokens, parsed id_token und Userinfo. Kein Outpost als Middleware. + +Für Forgejo müssen wir wählen. + +## Entscheidung +**Nativer OIDC Client** in Forgejo. Forgejo macht den OAuth2-Handshake direkt mit Authentik. + +## Begründung + +### Pro natives OIDC +- **Forgejo ist ein echter OIDC-Client**. Es unterstützt OAuth2/OIDC als First-Class-Feature mit vollem Flow, Token-Exchange, Userinfo-Retrieval, Auto-Registration, Account-Linking via Email. +- **User-Identität kommt voll an:** Forgejo weiß wer der User ist (Name, Email, Avatar), kann ihn lokal anlegen und persistieren. Bei ForwardAuth sieht es nur "authenticated" plus Header — was für Gruppen-Zuordnung und persistente User-Records umständlich wäre. +- **Git-Commits signiert:** Forgejo kann Commits im Namen des Users signieren, was mit ForwardAuth und der Proxy-Layer komplizierter würde. +- **API-Access via Personal Access Tokens:** Entwickler können in Forgejo persönliche API-Tokens erstellen, ohne die Authentik-Credentials teilen zu müssen. Mit ForwardAuth müssten API-Requests durch den Outpost, was API-Keys-im-Header-Pattern bricht. +- **OIDC ist Standard:** Sollten wir irgendwann Authentik ersetzen (z.B. durch Zitadel, Keycloak), ist der Umstieg trivial, weil OIDC interoperabel ist. Bei ForwardAuth wäre der Umstieg viel schwerer. + +### Contra +- **Zweites Auth-Pattern in Authentik:** Bisher haben alle unsere Apps ForwardAuth genutzt. Jetzt kommt ein echter OIDC-Provider dazu. Mehr Komplexität in Authentik (aber: OIDC ist auch das universale Standard-Pattern, unsere ForwardAuth-Apps sind eigentlich die Ausnahme). +- **Migration bestehender Apps denkbar:** Mittelfristig könnten n8n und andere auch auf nativen OIDC migriert werden, um die Konsistenz wiederherzustellen. Das ist aber ein eigenes Projekt (siehe M7+). + +## Konsequenzen + +### Positiv +- Forgejo-Login-Seite zeigt einen sauberen "Sign in with authentik" Button. +- User werden automatisch beim ersten Login angelegt (via `ENABLE_AUTO_REGISTRATION=true`, `ACCOUNT_LINKING=auto`, `USERNAME=email`). +- Gruppen-Zugangskontrolle via Authentik Policy (nur Mitglieder von `forgejo-users`). +- Kein Traefik ForwardAuth Middleware nötig — Forgejo Docker-Labels sind simpel. + +### Zu beachten +- **Discovery-URL muss public sein.** Der Browser und der Forgejo-Backend-Call zum Token-Endpoint müssen dieselbe Issuer-URL benutzen, sonst scheitert die JWT-Issuer-Validation. Daher `https://welcome.sdda.eu/application/o/forgejo/` überall, nicht die private `10.0.0.7`. +- **OIDC-Provider-Config liegt in Postgres** (Tabelle `login_source`), nicht in `app.ini`. Muss via `forgejo admin auth add-oauth` CLI gesetzt werden, nicht über ENV-Variablen. +- **Client Credentials in .env nur als Referenz** — die echte Speicherung ist in der DB nach dem CLI-Call. + +## Follow-Up-Ideen +- Eine separate ADR für künftige Authentik-OIDC-Integrationen (OpenProject, Nextcloud, vielleicht Migration n8n/locosoft). +- `Authentik/howto-oauth2-provider.md` als Template für diese zukünftigen Apps schreiben (Teil M7.1). diff --git a/docs/adr/0004-subdomain-code-sdda-eu.md b/docs/adr/0004-subdomain-code-sdda-eu.md new file mode 100644 index 0000000..517d025 --- /dev/null +++ b/docs/adr/0004-subdomain-code-sdda-eu.md @@ -0,0 +1,38 @@ +# ADR-0004: Subdomain `code.sdda.eu` + +**Status:** Accepted +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment + +## Kontext +Forgejo braucht eine öffentliche Subdomain unter `sdda.eu`. Mehrere Varianten standen zur Wahl. + +## Entscheidung +**`code.sdda.eu`** + +## Alternativen + +| Option | Pro | Contra | +|---|---|---| +| `git.sdda.eu` | Kurz, Industriestandard (GitHub, GitLab, Bitbucket) | "Git" ist technisch, Fokus auf Protokoll statt Inhalt | +| **`code.sdda.eu`** ✓ | Sprechend, User-orientiert ("hier ist unser Code"), nicht protokoll-spezifisch | Nicht ganz so "Standard" wie git.* | +| `source.sdda.eu` | Neutral, betont Source-of-Truth | Ungewöhnlich | +| `forge.sdda.eu` | Bezieht sich auf Forgejo-Namen | Tool-spezifisch; bei Migration zu anderer Software irreführend | + +## Begründung +Benjamin hat `code.sdda.eu` gewählt, weil es: +- **User-zentriert** ist — "Code" ist näher am Geschäftszweck als "Git" (Protokoll) +- **Tool-agnostisch** ist — würden wir irgendwann auf GitLab / Gitea / whatever wechseln, passt der Name weiter +- **Kurz und merkbar** ist +- **Keine technischen Vorkenntnisse braucht** — ein Werkstatt-Mitarbeiter versteht "Code" schneller als "Git" + +## Konsequenzen +- DNS A-Record `code.sdda.eu → 91.98.226.44` auf Webmin/BIND +- AAAA-Record `code.sdda.eu → 2a01:4f8:1c1e:5113::1` (IPv6) +- Let's Encrypt Cert automatisch über Traefik +- Authentik Redirect URI: `https://code.sdda.eu/user/oauth2/authentik/callback` +- Forgejo ROOT_URL und DOMAIN konfiguriert auf `code.sdda.eu` + +## Nicht übersehen +Die Forgejo `SSH_DOMAIN` ist ebenfalls `code.sdda.eu` (Port 222) — git clone URLs sind dann `ssh://git@code.sdda.eu:222/user/repo.git`. diff --git a/docs/adr/0005-volume-mount-data-not-var-lib.md b/docs/adr/0005-volume-mount-data-not-var-lib.md new file mode 100644 index 0000000..423330f --- /dev/null +++ b/docs/adr/0005-volume-mount-data-not-var-lib.md @@ -0,0 +1,59 @@ +# ADR-0005: Volume-Mount auf `/data`, nicht `/var/lib/gitea` + +**Status:** Accepted (Lesson Learned) +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment + +## Kontext +Beim initialen Deployment wurde das Docker-Volume `forgejo-data` fälschlich auf `/var/lib/gitea` im Container gemountet. Grund: ältere Gitea/Forgejo Docs nennen teilweise `/var/lib/gitea` als APP_INI-Directory, und das wirkte als "Datenverzeichnis". + +Der Bug fiel auf, als das erste Backup nur 87 Bytes hatte (leeres tar) — weil das gemountete Volume leer war, während Forgejo seine Daten tatsächlich in `/data` im Container-Layer schrieb. + +## Entscheidung +Volume-Mount **`forgejo-data:/data`**, nicht `/var/lib/gitea`. + +## Was Forgejo tatsächlich wo speichert +Aus Forgejo Docker Image: +- **`/data`** — Haupt-Datenverzeichnis für ALLES: + - `/data/git/` — Git-Repositories + - `/data/gitea/conf/app.ini` — Config + - `/data/gitea/avatars/` — User-Avatars + - `/data/gitea/repo-avatars/` — Repo-Avatars + - `/data/gitea/lfs/` — LFS-Objekte + - `/data/gitea/attachments/` — Issue/PR-Anhänge + - `/data/gitea/packages/` — Package Registry + - `/data/gitea/actions_log/` — CI-Logs + - `/data/gitea/actions_artifacts/` — CI-Artefakte + - `/data/gitea/ssh/` — Forgejo SSH Host Keys (gitea.rsa etc.) +- `/var/lib/gitea` — existiert nicht im Forgejo-Image + +Die `FORGEJO__storage__*` ENV-Variablen erwarten relative Pfade unter `/data/gitea/`. + +## Konsequenzen + +### Mount korrigiert +```yaml +volumes: + - forgejo-data:/data # KORREKT + # - forgejo-data:/var/lib/gitea # FALSCH, Anfänger-Fehler +``` + +### Runtime-Impact +Beim Fix musste der Forgejo-Container mit `--force-recreate` neu erzeugt werden. Die initial im Container-Layer gespeicherten Daten (SSH Host Keys, leere Storage-Ordner) gingen verloren und wurden beim neuen Start frisch initialisiert. Das war akzeptabel weil: +- Postgres (eh_vehicles → forgejo-db) hatte alle User/OIDC-Daten → blieb erhalten +- SSH Host Keys werden automatisch neu generiert +- Keine Commits oder Repos existierten noch + +### Backup-Implikation +Das Backup-Script `backup.sh` tart jetzt korrekt das volle `/data`, nicht ein leeres Verzeichnis. Verifiziert: 12 KB statt 87 Bytes beim Test-Run. + +## Lesson Learned +**Beim Setup eines neuen Docker-Stacks:** +1. Nach `docker compose up -d` sofort checken: `docker run --rm -v :/data:ro alpine ls -la /data/` +2. Wenn das Volume leer aussieht, aber der Container offenbar Daten schreibt → Mount-Pfad falsch +3. Das offizielle Image-Documentation konsultieren, NICHT aus Memory copy-pasten + +## Related +- `Agent.md` — "Known Issues / Gotchas" Sektion erwähnt diesen Punkt als Warnung +- `runbook-backup-restore.md` — Restore-Prozedur nutzt den korrekten Pfad diff --git a/docs/adr/0006-silent-sso-launch-url.md b/docs/adr/0006-silent-sso-launch-url.md new file mode 100644 index 0000000..7f5ab08 --- /dev/null +++ b/docs/adr/0006-silent-sso-launch-url.md @@ -0,0 +1,82 @@ +# ADR-0006: Silent SSO via OAuth2-Initiate-Launch-URL + +**Status:** Accepted (Lesson Learned) +**Datum:** 2026-04-11 +**Entscheider:** Benjamin Weinlich +**Phase:** M7.1 — Forgejo Deployment (Post-Launch Fix) + +## Kontext +Nach dem Go-Live von Forgejo stellte Benjamin fest: Wenn man im Authentik-Dashboard (welcome.sdda.eu) auf die Forgejo-Kachel klickt, wird man auf `https://code.sdda.eu/` weitergeleitet — und muss dort **erneut auf "Anmelden mit authentik"** klicken. Das fühlt sich an wie "zweimal einloggen", auch wenn der zweite Klick technisch nur den OIDC-Flow triggert. + +Technisch korrekt, aber UX-Fail: Das Versprechen von SSO ist "einmal einloggen, überall drin sein". Ein Extra-Klick ist kein Single-Sign-On. + +## Root Cause +Die Application `forgejo` in Authentik hatte `meta_launch_url=https://code.sdda.eu/`. Das ist die Forgejo-Startseite. Wenn der User keine Forgejo-Session hat (erster Besuch), zeigt Forgejo die Login-Seite an — und der User muss manuell den OIDC-Button klicken. + +Forgejo hat **keine Config-Option** "redirect to OIDC statt Login-Seite zeigen" (wie z.B. GitLab's `auto_sign_in_with_provider`). Der OIDC-Flow wird nur bei Klick auf den Button oder bei direktem Aufruf der Route `/user/oauth2/` getriggert. + +## Entscheidung +**Launch-URL in Authentik auf `https://code.sdda.eu/user/oauth2/authentik` ändern.** + +Das ist die Forgejo-Route die den OIDC-Flow initiiert. Wenn der User schon eine Authentik-Session hat, läuft der Flow "silent" durch (ohne User-Interaktion) und landet direkt auf der Forgejo-Startseite. + +## Flow-Vergleich + +### Vorher (zwei Klicks) +``` +[Dashboard: Klick "Forgejo"] + ↓ GET https://code.sdda.eu/ +[Forgejo Login-Seite erscheint] + ↓ [Klick "Anmelden mit authentik"] + ↓ GET https://code.sdda.eu/user/oauth2/authentik + ↓ GET https://welcome.sdda.eu/application/o/authorize/ (Auth OK, silent) + ↓ GET https://code.sdda.eu/user/oauth2/authentik/callback?code=... +[Forgejo Startseite] +``` + +### Nachher (ein Klick) +``` +[Dashboard: Klick "Forgejo"] + ↓ GET https://code.sdda.eu/user/oauth2/authentik + ↓ GET https://welcome.sdda.eu/application/o/authorize/ (Auth OK, silent) + ↓ GET https://code.sdda.eu/user/oauth2/authentik/callback?code=... +[Forgejo Startseite] +``` + +Die Zwischenseite mit dem Login-Form fällt komplett weg. + +## Umsetzung +Via REST API (weil Authentik-MCP keine `update_application` hat): +```bash +TOKEN= +curl -X PATCH 'http://localhost:9000/api/v3/core/applications/forgejo/' \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"meta_launch_url": "https://code.sdda.eu/user/oauth2/authentik"}' +``` + +## Zusätzlich behoben: must_change_password +Bei der Gelegenheit fiel auf, dass Benjamins User (`bw`) den Flag `must_change_password=true` hatte. Grund: Ich hatte während des initialen Setups `forgejo admin user change-password --username bw --password ` aufgerufen, um den leeren Passwort-Slot zu füllen — Forgejo markiert das User-Konto dann als "muss Passwort wechseln". Für reine OIDC-User ist das unpassend, der User hat nie ein Passwort nötig. + +**Fix:** +```sql +UPDATE "user" +SET must_change_password = false, passwd = '', salt = '', passwd_hash_algo = '' +WHERE lower_name = 'bw'; +``` + +Damit ist bw ein echter password-less OIDC-User: Login geht ausschließlich via Authentik. + +## Konsequenzen +- **UX:** Authentik-Dashboard → Forgejo = ein Klick, keine Zwischenseite mehr. +- **Template aktualisiert:** `../Authentik/howto-oauth2-provider.md` hat jetzt eine explizite Warnung und Launch-URL-Beispiele für Forgejo, OpenProject, Nextcloud, Grafana. +- **Gilt für alle zukünftigen OIDC-Apps:** Die Launch-URL muss immer auf die OAuth2-Initiate-Route zeigen, nicht auf die App-Startseite. Merkhilfe: Wenn der Nutzer die App-Startseite ohne Login sieht und dort klicken muss, ist es falsch konfiguriert. + +## Gotcha: Direkter Zugriff via `code.sdda.eu` bleibt unverändert +Wer `https://code.sdda.eu/` direkt in den Browser eingibt (ohne den Umweg über Authentik-Dashboard), sieht weiter die Forgejo-Login-Seite mit dem manuellen OIDC-Button. Das ist OK so: Bookmark-User die direkt auf Forgejo gehen, bekommen das Login-UI — Dashboard-User kriegen silent SSO. + +Man kann das noch weiter optimieren (z.B. via `[service] DEFAULT_USER_VISIBILITY=limited` + Redirect-Magic), aber dafür gibt's keinen sauberen Konfig-Pfad in Forgejo. Current state ist guter Trade-off. + +## Related +- `0003-native-oidc-not-forwardauth.md` — Warum wir nativ OIDC machen +- `../Authentik/howto-oauth2-provider.md` — Template für zukünftige Apps (jetzt mit Launch-URL-Hinweis) diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..21e5b55 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,74 @@ +# Architecture Decision Records + +Dieses Verzeichnis enthält **ADRs** — kurze Markdown-Dokumente die einzelne Architektur-Entscheidungen festhalten. Das Pattern kommt von Michael Nygard und ist gemeinsamer Standard in vielen Open-Source-Projekten. + +## Warum ADRs? +- **Warum, nicht Wie:** Der Code ändert sich. Die Intention soll bleiben. Eine ADR erklärt warum etwas so gebaut wurde — in einem halben Jahr weiß niemand mehr sonst. +- **Referenz für AI-Sessions:** Wenn die AI morgen eine Änderung vorschlägt, die eine bestehende Entscheidung umkehrt, soll sie das bewusst tun und neue ADR schreiben, nicht versehentlich. +- **Audit-Trail:** Jede ADR hat Status, Datum und Entscheider. Das ist Compliance-ready. + +## Format +Jede ADR folgt grob diesem Schema: +```markdown +# ADR-NNNN: +Status: Proposed | Accepted | Superseded by ADR-NNNN | Deprecated +Datum: YYYY-MM-DD +Entscheider: +Phase: + +## Kontext +Was war das Problem? Welche Optionen standen zur Wahl? + +## Entscheidung +Was wurde gewählt? + +## Begründung +Warum genau diese Option? Welche Prinzipien waren leitend? + +## Konsequenzen +- **Positiv:** Was gewinnen wir? +- **Negativ/Trade-off:** Was geben wir auf? + +## Alternativen +Was wurde verworfen und warum? +``` + +## Nummerierung +Fortlaufend `NNNN` beginnend bei `0001`. **Niemals** alte ADRs ändern — superseden via neue ADR mit Verweis. Die Historie ist die Wahrheit. + +## Aktuelle ADRs (alle aus M7.1 — Forgejo Deployment) +| # | Titel | Status | +|---|---|---| +| 0001 | [Forgejo statt Gitea](0001-forgejo-vs-gitea.md) | Accepted | +| 0002 | [Forgejo auf ai-apps, nicht separate VM](0002-ai-apps-placement.md) | Accepted | +| 0003 | [Natives OIDC statt ForwardAuth](0003-native-oidc-not-forwardauth.md) | Accepted | +| 0004 | [Subdomain code.sdda.eu](0004-subdomain-code-sdda-eu.md) | Accepted | +| 0005 | [Volume-Mount auf /data](0005-volume-mount-data-not-var-lib.md) | Accepted (Lesson Learned) | +| 0006 | [Silent SSO via OAuth2-Initiate-Launch-URL](0006-silent-sso-launch-url.md) | Accepted (Lesson Learned) | + +## Vorlage für neue ADRs +Copy-and-paste Startpunkt: +```markdown +# ADR-NNNN: + +**Status:** Proposed +**Datum:** YYYY-MM-DD +**Entscheider:** +**Phase:** + +## Kontext + +## Entscheidung + +## Begründung + +## Konsequenzen + +### Positiv + +### Negativ / Trade-off + +## Alternativen + +## Referenzen +```