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 +```