Compare commits
No commits in common. "20025814e8c1edb8dc3100870c60e54ccdb67f56" and "b3813ed6ac9fb4478fa67538c0b6211d6b3d8c6a" have entirely different histories.
20025814e8
...
b3813ed6ac
15 changed files with 9 additions and 979 deletions
17
Agent.md
17
Agent.md
|
|
@ -89,14 +89,11 @@ Das bedeutet:
|
|||
- M7: Operations & Documentation Foundation
|
||||
- M7.1: Forgejo Deployment (#1119) — abgeschlossen
|
||||
- M7.2: Erstes Infra-Repo (dieses Repo!)
|
||||
- M7.3: ADRs/Runbooks/Guides gespiegelt (dieser Commit)
|
||||
- M7.4+: Mirror zu GitHub, rclone backup, weitere Apps
|
||||
- M7.3+: ADRs/Runbooks spiegeln, Mirror zu GitHub, etc.
|
||||
|
||||
## Related Docs (im Repo)
|
||||
Ab M7.3 lebt die Doku primär in diesem Repo:
|
||||
- [`docs/adr/`](docs/adr/) — Architecture Decision Records (6 ADRs aus M7.1)
|
||||
- [`docs/runbooks/`](docs/runbooks/) — Ops-Runbooks
|
||||
- [`docs/guides/`](docs/guides/) — Wiederverwendbare Setup-Anleitungen
|
||||
- [`docs/architecture/`](docs/architecture/) — Topologie + Stack-Inventories
|
||||
|
||||
Der lokale iCloud-Ordner `AI/IT & Infrastruktur/` ist ab jetzt sekundär — dort leben nur noch Credentials-Hinweise und Lose-Notizen, die nicht in ein public Repo gehören.
|
||||
## Related External Docs
|
||||
Außerhalb dieses Repos lebt Doku in:
|
||||
- `/Users/benjaminweinlich/Library/Mobile Documents/com~apple~CloudDocs/AI/IT & Infrastruktur/`
|
||||
- `Forgejo/` — ADRs + Runbooks für den Forgejo-Stack (werden in M7.3 hier hin gespiegelt)
|
||||
- `Authentik/howto-oauth2-provider.md` — Template für OIDC-Integrationen
|
||||
- `Hetzner/ai-apps-stacks.md` — Cloud-Inventory
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# 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/)
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# 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/<name>/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).
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# 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).
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# 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`.
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
# 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 <volume_name>:/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
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
# 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/<name>` 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=<akadmin-api>
|
||||
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 <random>` 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)
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
# 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: <Titel>
|
||||
Status: Proposed | Accepted | Superseded by ADR-NNNN | Deprecated
|
||||
Datum: YYYY-MM-DD
|
||||
Entscheider: <Name>
|
||||
Phase: <OpenProject Phase oder Sprint>
|
||||
|
||||
## 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: <Titel>
|
||||
|
||||
**Status:** Proposed
|
||||
**Datum:** YYYY-MM-DD
|
||||
**Entscheider:** <Name>
|
||||
**Phase:** <OpenProject>
|
||||
|
||||
## Kontext
|
||||
|
||||
## Entscheidung
|
||||
|
||||
## Begründung
|
||||
|
||||
## Konsequenzen
|
||||
|
||||
### Positiv
|
||||
|
||||
### Negativ / Trade-off
|
||||
|
||||
## Alternativen
|
||||
|
||||
## Referenzen
|
||||
```
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Guides
|
||||
|
||||
**Guides** sind Anleitungen für Setup- und Konfigurations-Aufgaben die wiederholt vorkommen — aber nicht notfall-getrieben wie Runbooks. Sie sind das **"How do we do X here"** der Firma.
|
||||
|
||||
## Unterschied zu Runbooks
|
||||
- **Runbook:** "Der Loco-Sync ist kaputt — was jetzt?" → reagiert auf Probleme
|
||||
- **Guide:** "Wie integriere ich eine neue App mit Authentik OIDC?" → proaktive Einrichtung
|
||||
|
||||
## Unterschied zu ADRs
|
||||
- **ADR:** "Warum haben wir diese Entscheidung getroffen?"
|
||||
- **Guide:** "Wie setze ich diese Entscheidung für einen neuen Service um?"
|
||||
|
||||
Typisch: Eine ADR legt das Pattern fest, der Guide zeigt wie man es anwendet.
|
||||
|
||||
## Aktuelle Guides
|
||||
|
||||
### Authentik
|
||||
- **[authentik-oauth2-provider.md](authentik-oauth2-provider.md)** — Wie richtet man einen nativen OIDC-Provider in Authentik für eine neue App ein? Template-based, wurde zum ersten Mal für Forgejo genutzt, ist jetzt wiederverwendbar für OpenProject, Nextcloud, Grafana, etc.
|
||||
|
||||
## Wann neue Guides schreiben
|
||||
- Wenn eine Setup-Sequenz mehr als einmal vorkommen wird
|
||||
- Wenn sie mehr als 5 Schritte hat
|
||||
- Wenn die korrekte Reihenfolge nicht offensichtlich ist
|
||||
- Wenn es Gotchas gibt die man schnell vergessen würde
|
||||
|
||||
Wenn es nur einmalig ist und nie wieder vorkommt: Kein Guide, sondern direkt als ADR dokumentieren (falls eine Entscheidung drinsteckt).
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
# How-To: OAuth2/OIDC Provider in Authentik einrichten
|
||||
|
||||
**Stand:** 2026-04-11
|
||||
**Erstanwendung:** M7.1 — Forgejo OIDC Setup
|
||||
**Zielgruppe:** AI-Agents und Benjamin — wiederverwendbares Template für künftige OIDC-Integrationen
|
||||
|
||||
## Wann dieses Pattern nutzen
|
||||
Wenn eine Applikation OIDC (OpenID Connect) als First-Class-Client unterstützt (z.B. Forgejo, OpenProject, Nextcloud, GitLab, Grafana, Mattermost). In diesem Fall ist nativer OIDC besser als ForwardAuth weil:
|
||||
- User-Identität (Name, Email, Avatar, Gruppen) kommt direkt in der App an
|
||||
- Auto-Registration / Account-Linking möglich
|
||||
- API-Tokens der App bleiben unabhängig
|
||||
- Migration zu anderer IdP-Software trivial
|
||||
|
||||
Für Apps **ohne** OIDC-Support (oder zu komplex zu integrieren): ForwardAuth via Embedded Outpost — siehe das LocoSoft/n8n Pattern.
|
||||
|
||||
## Voraussetzungen
|
||||
- Authentik läuft und ist über `welcome.sdda.eu` erreichbar
|
||||
- SSH-Zugang `authentik-sso` verfügbar
|
||||
- MCP Tools `authentik_*` verfügbar ODER API-Token für akadmin
|
||||
- Die App (z.B. Forgejo) ist bereits live oder zumindest geplant — du brauchst die **Redirect URI** (meist `https://<app-domain>/user/oauth2/<name>/callback` oder ähnlich)
|
||||
|
||||
## Schritt 1: Gruppe für Zugangskontrolle (optional aber empfohlen)
|
||||
|
||||
Statt "jeder in Authentik" → eigene Gruppe pro App, damit User-Anlage und -Abschaltung granular funktioniert.
|
||||
|
||||
```python
|
||||
# Via MCP Tool
|
||||
mcp.authentik_create_group(name="<app-slug>-users")
|
||||
```
|
||||
|
||||
Oder via REST API:
|
||||
```bash
|
||||
TOKEN=<akadmin-api-token>
|
||||
curl -X POST 'http://localhost:9000/api/v3/core/groups/' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name": "forgejo-users", "is_superuser": false}'
|
||||
```
|
||||
|
||||
Notiere die zurückgegebene `pk` (UUID).
|
||||
|
||||
## Schritt 2: OAuth2/OpenID Provider anlegen
|
||||
|
||||
```bash
|
||||
# Vorbereitung: Flow-IDs und Scope-Mapping-IDs holen
|
||||
TOKEN=<akadmin-api-token>
|
||||
|
||||
# Authorization Flow (Default: explicit consent)
|
||||
AUTH_FLOW="9f7af462-90cf-44d8-99fa-916f8459ba13"
|
||||
|
||||
# Invalidation Flow (für logout)
|
||||
INVAL_FLOW="697e1b4c-db65-4c21-a458-dd7df2bf51f2"
|
||||
|
||||
# Scope Property Mappings (diese sind bei uns fest)
|
||||
SCOPE_OPENID="f878f335-46dd-4104-8dfb-fe72f21dda22"
|
||||
SCOPE_PROFILE="04294271-539c-44e6-824a-5ab5c1e4613d"
|
||||
SCOPE_EMAIL="602c1e0a-0c6d-48ef-bada-97d4c6e45f3c"
|
||||
|
||||
# Signing Certificate (RS256 statt HMAC)
|
||||
CERT="663c97fa-f336-49fe-8809-118d3745547a" # authentik Self-signed
|
||||
|
||||
# Eigenes Client Secret generieren
|
||||
CLIENT_SECRET=$(openssl rand -hex 48)
|
||||
|
||||
# Provider anlegen
|
||||
curl -X POST 'http://localhost:9000/api/v3/providers/oauth2/' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"name\": \"<App-Name> OIDC\",
|
||||
\"authorization_flow\": \"$AUTH_FLOW\",
|
||||
\"invalidation_flow\": \"$INVAL_FLOW\",
|
||||
\"client_type\": \"confidential\",
|
||||
\"client_secret\": \"$CLIENT_SECRET\",
|
||||
\"access_code_validity\": \"minutes=1\",
|
||||
\"access_token_validity\": \"hours=24\",
|
||||
\"refresh_token_validity\": \"days=30\",
|
||||
\"include_claims_in_id_token\": true,
|
||||
\"signing_key\": \"$CERT\",
|
||||
\"sub_mode\": \"user_email\",
|
||||
\"issuer_mode\": \"per_provider\",
|
||||
\"redirect_uris\": [
|
||||
{\"matching_mode\": \"strict\", \"url\": \"<REDIRECT_URI>\"}
|
||||
],
|
||||
\"property_mappings\": [
|
||||
\"$SCOPE_OPENID\",
|
||||
\"$SCOPE_PROFILE\",
|
||||
\"$SCOPE_EMAIL\"
|
||||
]
|
||||
}"
|
||||
```
|
||||
|
||||
Die Response enthält:
|
||||
- `pk` — die ID des Providers (numerisch)
|
||||
- `client_id` — auto-generiert, brauchst du für die App-Config
|
||||
- `client_secret` — das das du mitgegeben hast (bestätigt)
|
||||
|
||||
**Notiere client_id und client_secret sofort.** Das Secret wird in der Web-UI später maskiert.
|
||||
|
||||
## Schritt 3: Application anlegen
|
||||
|
||||
Die Application verbindet Provider + Namen + Zugangs-Policies.
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:9000/api/v3/core/applications/' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"name": "<App-Name>",
|
||||
"slug": "<app-slug>",
|
||||
"provider": <PROVIDER_PK>,
|
||||
"meta_launch_url": "https://<app-domain>/user/oauth2/<auth-name>",
|
||||
"meta_description": "<Beschreibung>",
|
||||
"meta_publisher": "Richter & Zech",
|
||||
"policy_engine_mode": "any"
|
||||
}'
|
||||
```
|
||||
|
||||
Die Response enthält die App-ID (UUID).
|
||||
|
||||
**Wichtig:** Der `slug` bestimmt die Discovery-URL: `https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration`
|
||||
|
||||
**KRITISCH — Silent SSO:** `meta_launch_url` muss auf die **OAuth2-Initiate-Route** der App zeigen, nicht auf die App-Startseite. Nur so wird beim Klick im Authentik-Dashboard der OIDC-Flow automatisch getriggert ohne dass der User nochmal "Sign in with..." klicken muss. Beispiele:
|
||||
- **Forgejo:** `https://code.sdda.eu/user/oauth2/authentik` (der `authentik` Teil ist der Auth-Source-Name aus `forgejo admin auth add-oauth --name`)
|
||||
- **OpenProject:** `https://openproject.sdda.eu/auth/oidc_authentik/callback`
|
||||
- **Grafana:** `https://grafana.sdda.eu/login/generic_oauth`
|
||||
- **Nextcloud:** `https://app.sdda.eu/apps/user_oidc/login/<provider_id>`
|
||||
|
||||
Wenn man die nackte App-URL setzt (`https://code.sdda.eu/`), zeigt die App ihre eigene Login-Seite an, und der User muss nochmal klicken. Das ist keine silent SSO — das ist eine Frustration.
|
||||
|
||||
## Schritt 4: Policy Binding (Gruppen-Zugangskontrolle)
|
||||
|
||||
Wenn du in Schritt 1 eine Gruppe angelegt hast:
|
||||
|
||||
```bash
|
||||
APP_PK=<uuid-aus-schritt-3>
|
||||
GROUP_PK=<uuid-aus-schritt-1>
|
||||
|
||||
curl -X POST 'http://localhost:9000/api/v3/policies/bindings/' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"target\": \"$APP_PK\",
|
||||
\"group\": \"$GROUP_PK\",
|
||||
\"enabled\": true,
|
||||
\"order\": 0,
|
||||
\"timeout\": 30
|
||||
}"
|
||||
```
|
||||
|
||||
Damit können nur Mitglieder der Gruppe die App nutzen. User die nicht drin sind, bekommen beim OIDC-Login "Access Denied".
|
||||
|
||||
## Schritt 5: User zur Gruppe hinzufügen
|
||||
|
||||
```python
|
||||
# Via MCP
|
||||
mcp.authentik_add_user_to_group(group_id=<UUID>, user_id=<INT>)
|
||||
```
|
||||
|
||||
Oder REST:
|
||||
```bash
|
||||
curl -X POST "http://localhost:9000/api/v3/core/groups/$GROUP_PK/add_user/" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pk": <USER_ID>}'
|
||||
```
|
||||
|
||||
## Schritt 6: Discovery-URL verifizieren
|
||||
|
||||
Bevor du in der App den Provider konfigurierst, check dass Authentik die Discovery-URL korrekt ausliefert:
|
||||
|
||||
```bash
|
||||
curl -s 'https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration' | jq .
|
||||
```
|
||||
|
||||
Erwartete Felder:
|
||||
- `issuer` — muss mit dem Issuer matchen den die App erwartet
|
||||
- `authorization_endpoint`
|
||||
- `token_endpoint`
|
||||
- `userinfo_endpoint`
|
||||
- `jwks_uri`
|
||||
|
||||
## Schritt 7: Client in der App konfigurieren
|
||||
|
||||
Je nach App unterschiedlich. Bei **Forgejo** z.B. via CLI:
|
||||
```bash
|
||||
docker exec -u git forgejo forgejo admin auth add-oauth \
|
||||
--name authentik \
|
||||
--provider openidConnect \
|
||||
--key "<CLIENT_ID>" \
|
||||
--secret "<CLIENT_SECRET>" \
|
||||
--auto-discover-url "https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration" \
|
||||
--scopes "openid profile email" \
|
||||
--skip-local-2fa
|
||||
```
|
||||
|
||||
Bei **OpenProject**: `config/settings.yml` oder ENV. Bei **Nextcloud**: App "user_oidc" installieren, dann UI. Bei **Grafana**: `grafana.ini` [auth.generic_oauth]. Immer die App-Docs konsultieren.
|
||||
|
||||
## Schritt 8: Test-Login
|
||||
|
||||
1. Browser: App-URL → Login-Button "Sign in with authentik"
|
||||
2. Redirect zu welcome.sdda.eu → Login
|
||||
3. Evtl. Consent-Screen bestätigen
|
||||
4. Redirect zurück → User ist eingeloggt
|
||||
|
||||
## Gotchas
|
||||
- **Issuer-URL muss public sein** (nicht `http://10.0.0.7:9000`). JWT-Signatur enthält den Issuer — wenn App und Authentik unterschiedliche Issuer sehen, schlägt die Validation fehl.
|
||||
- **Subject-Mode `user_email`** funktioniert gut wenn alle User einzigartige Email-Adressen haben. Bei gemeinsamen Mailboxen (info@ etc.) lieber `hashed_user_id`.
|
||||
- **Include Claims in id_token = true** ist wichtig für Apps die `email`/`name` aus dem id_token lesen statt separaten userinfo-Call.
|
||||
- **Strict Redirect URIs:** Immer `"matching_mode": "strict"`, niemals Regex — Security.
|
||||
- **Policy Timeout = 30s** gibt Authentik Luft bei langsamen LDAP/SCIM-Backends.
|
||||
|
||||
## Template-Reference
|
||||
Als konkretes funktionierendes Beispiel für alle oben genannten Schritte siehe:
|
||||
- `../Forgejo/adr/0003-native-oidc-not-forwardauth.md`
|
||||
- Die Forgejo-`.env` auf ai-apps enthält die konkreten Werte
|
||||
- Die Authentik-Admin-UI unter `welcome.sdda.eu/if/admin/#/core/applications` zeigt die "Forgejo" Application als lebendes Beispiel
|
||||
|
||||
## Nächste Apps die dieses Template nutzen könnten
|
||||
- **OpenProject** — hat nativen OIDC-Client, aktuell noch ForwardAuth nicht genutzt
|
||||
- **Nextcloud** — braucht `user_oidc` App-Installation
|
||||
- **Grafana** — falls wir Monitoring einführen
|
||||
- **Mattermost** — falls wir Chat einführen
|
||||
- Potenzielle Migration von n8n und locosoft-hilfe-system von ForwardAuth auf OIDC (großer Eingriff, nicht dringlich)
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# Runbooks
|
||||
|
||||
**Runbooks** sind Schritt-für-Schritt-Anleitungen für wiederkehrende operative Situationen — meist Notfälle oder seltene Wartungs-Aufgaben.
|
||||
|
||||
## Warum Runbooks?
|
||||
- **Für dich selbst in 3 Monaten:** Wenn der Loco-Sync wieder kaputt ist und du dich nicht mehr an die Debug-Schritte erinnerst.
|
||||
- **Für die AI:** Eine AI-Session kann ein Runbook **lesen und ausführen**, ohne dass du jede Zeile diktieren musst.
|
||||
- **Für den Vertretungsfall:** Wenn du im Urlaub bist und jemand anderes dringend muss.
|
||||
|
||||
## Format
|
||||
Jedes Runbook beschreibt:
|
||||
1. **Wann anwenden** — Symptome, Trigger
|
||||
2. **Voraussetzungen** — Zugänge, Tools, Secrets
|
||||
3. **Schritte** — nummeriert, kopierbar (Bash-Blöcke)
|
||||
4. **Verification** — wie man erkennt dass der Fix greift
|
||||
5. **Was NIE machen** — typische Fallen
|
||||
|
||||
## Aktuelle Runbooks
|
||||
|
||||
### Forgejo
|
||||
- **[forgejo-admin-recovery.md](forgejo-admin-recovery.md)** — Was tun wenn Authentik down ist und niemand mehr ins Forgejo kommt. Nutzt den lokalen `admin-local` Fallback-User.
|
||||
- **[forgejo-backup-restore.md](forgejo-backup-restore.md)** — Backup-Format, Restore-Szenarien (komplett / nur DB / einzelne Datei), Disaster-Recovery auf neuem Server.
|
||||
|
||||
## Convention
|
||||
- **Dateiname:** `<stack>-<action>.md`, z.B. `forgejo-backup-restore.md`, `loco-sync-debug.md`
|
||||
- **Immer Bash-Blöcke** mit echten Kommandos, nicht Pseudo-Code
|
||||
- **Erwartete Outputs** hinschreiben, damit man weiß wann's geklappt hat
|
||||
- **Destruktive Aktionen fett markieren** ("⚠️ löscht ALLE Daten!")
|
||||
|
||||
## Neue Runbooks hinzufügen
|
||||
Wenn du ein Problem zum zweiten Mal hast, wird es ein Runbook. Die AI kann dir das im Moment des Fixes schreiben — du reviewst im Commit.
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
# Runbook — Admin-Recovery wenn Authentik down ist
|
||||
|
||||
**Kontext:** Forgejo nutzt Authentik OIDC als Primär-Auth. Wenn Authentik ausfällt, kommt niemand mehr per Login rein. Für diesen Fall gibt es einen lokalen Fallback-User `admin-local`.
|
||||
|
||||
## Wann dieses Runbook anwenden
|
||||
- `welcome.sdda.eu` gibt 500er / nicht erreichbar
|
||||
- OIDC-Callback zu Forgejo bricht ab
|
||||
- Authentik-Docker-Container crasht
|
||||
- authentik-sso VM ist down
|
||||
|
||||
## Voraussetzung
|
||||
- **SSH-Zugang zu ai-apps** (`ssh ai-apps`)
|
||||
- **admin-local Passwort** aus Password-Manager / Vault
|
||||
|
||||
## Recovery-Schritte
|
||||
|
||||
### 1. PROHIBIT_LOGIN lösen
|
||||
Normalerweise ist `admin-local.prohibit_login = true` gesetzt, damit niemand das Passwort bruteforcen kann. Wir müssen es temporär deaktivieren.
|
||||
|
||||
```bash
|
||||
ssh ai-apps
|
||||
docker exec forgejo-db psql -U forgejo -d forgejo \
|
||||
-c "UPDATE \"user\" SET prohibit_login = false WHERE lower_name = 'admin-local';"
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
UPDATE 1
|
||||
```
|
||||
|
||||
### 2. Einloggen
|
||||
Im Browser: https://code.sdda.eu/user/login
|
||||
- Username: `admin-local`
|
||||
- Passwort: aus Password-Manager
|
||||
|
||||
Du siehst jetzt das Forgejo Dashboard mit Admin-Rechten.
|
||||
|
||||
### 3. Was du während des Authentik-Ausfalls machen kannst
|
||||
- Repos anlegen / ändern
|
||||
- User manuell anlegen (fallback wenn jemand dringend Zugang braucht)
|
||||
- System-Settings anpassen
|
||||
- Backups prüfen
|
||||
|
||||
**Achtung:** Andere normale User können sich auch nicht einloggen während Authentik down ist. Das ist bewusst so. Wenn ein User dringend arbeiten muss, können sie:
|
||||
- Von dir ein Personal Access Token bekommen (Settings → Applications)
|
||||
- git clone via HTTPS mit diesem Token
|
||||
|
||||
### 4. Authentik wieder hochbringen
|
||||
```bash
|
||||
ssh authentik-sso
|
||||
cd /opt/authentik
|
||||
docker compose ps # Was läuft?
|
||||
docker compose logs --tail 50 # Warum down?
|
||||
docker compose restart # Versuch 1
|
||||
# Wenn restart nicht hilft: docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
Bei Datenbank-Problemen:
|
||||
```bash
|
||||
docker compose logs authentik-db # DB ok?
|
||||
# Im Worst Case: Restore aus Authentik-Backup
|
||||
```
|
||||
|
||||
### 5. Nach der Wiederherstellung: PROHIBIT_LOGIN zurücksetzen
|
||||
**WICHTIG:** Sobald Authentik wieder läuft, sofort den Notfall-Fallback wieder sperren:
|
||||
|
||||
```bash
|
||||
ssh ai-apps
|
||||
docker exec forgejo-db psql -U forgejo -d forgejo \
|
||||
-c "UPDATE \"user\" SET prohibit_login = true WHERE lower_name = 'admin-local';"
|
||||
```
|
||||
|
||||
Aus dem Forgejo Admin-UI deine bw-Session ausloggen, dann via Authentik neu einloggen — sicherstellen dass OIDC wieder funktioniert.
|
||||
|
||||
## Warum dieses Pattern?
|
||||
- **admin-local hat keinen Zugang über Authentik** (nicht in Gruppe `forgejo-users`, und nicht mal ein OIDC-linked User).
|
||||
- **admin-local ist per default gesperrt** (`prohibit_login=true`).
|
||||
- Zum Aktivieren braucht man DB-Zugang → bedeutet SSH-Zugang zu ai-apps → bedeutet physischen Zugang zu deinem Rechner / SSH-Key.
|
||||
- Nach Recovery sofort zurück in den gesperrten Zustand — kein dauerhaftes Bypass.
|
||||
|
||||
## Was du NIE machen solltest
|
||||
- **admin-local Passwort aus dem Password-Manager entfernen.** Das ist deine einzige Notfall-Tür.
|
||||
- **admin-local Passwort in Klartext irgendwo committen.** Das ist wie GH-Secret-in-Public-Repo, nur schlimmer.
|
||||
- **PROHIBIT_LOGIN dauerhaft auf false lassen nach Recovery.** Das bricht das Sicherheitsprinzip.
|
||||
|
||||
## Alternative für sehr lange Authentik-Ausfälle
|
||||
Wenn Authentik auf Dauer nicht mehr funktioniert (z.B. Lizenz-Issue, Migration nötig):
|
||||
1. admin-local temporär entsperren
|
||||
2. OIDC-Auth-Source in Forgejo deaktivieren: `docker exec -u git forgejo sh -c 'cd / && forgejo admin auth update-oauth --id 1 --not-enabled'`
|
||||
3. Lokale User bestehen bleibt (bw kann admin-local-Passwort bekommen oder eigenes lokales Passwort setzen)
|
||||
4. Neuer Auth-Provider später einrichten
|
||||
5. Doku in neue ADR schreiben
|
||||
|
||||
## Related
|
||||
- `Agent.md` — Stack-Overview
|
||||
- `../Authentik/howto-oauth2-provider.md` — Wie OIDC-Provider neu aufgesetzt wird
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
# Runbook — Forgejo Backup und Restore
|
||||
|
||||
## Backup-Architektur
|
||||
- **Script:** `/opt/ai-apps/forgejo/backup.sh`
|
||||
- **Cron:** `0 3 * * *` (täglich 03:00 UTC auf ai-apps)
|
||||
- **Ziel:** `/opt/backups/forgejo/`
|
||||
- **Format:**
|
||||
- `forgejo-db-<YYYYMMDD-HHMMSS>.sql.gz` — pg_dump der Postgres DB
|
||||
- `forgejo-data-<YYYYMMDD-HHMMSS>.tar.gz` — tar des Forgejo `/data` Volumes
|
||||
- **Retention:** 14 Tage (ältere Dateien werden via `find -mtime +14 -delete` entfernt)
|
||||
|
||||
## Manueller Backup-Run
|
||||
```bash
|
||||
ssh ai-apps
|
||||
bash /opt/ai-apps/forgejo/backup.sh
|
||||
ls -lh /opt/backups/forgejo/
|
||||
```
|
||||
|
||||
Erwartete Ausgabe:
|
||||
```
|
||||
[2026-04-11T19:39:11+00:00] backup start
|
||||
db dump: 26K
|
||||
data tar: 12K
|
||||
[2026-04-11T19:39:12+00:00] backup complete
|
||||
```
|
||||
|
||||
## Restore-Szenarien
|
||||
|
||||
### Szenario A: Komplett-Restore aus Backup (Disaster Recovery)
|
||||
|
||||
Voraussetzung: Backup-Files sind vorhanden (entweder in `/opt/backups/forgejo/` oder aus Offsite-Kopie).
|
||||
|
||||
```bash
|
||||
# 1. Stack stoppen
|
||||
cd /opt/ai-apps/forgejo
|
||||
docker compose down
|
||||
|
||||
# 2. Volumes löschen (ACHTUNG: unwiderruflich!)
|
||||
docker volume rm forgejo_forgejo-data forgejo_forgejo-db-data
|
||||
|
||||
# 3. Stack wieder starten — leere Volumes werden neu angelegt
|
||||
docker compose up -d forgejo-db
|
||||
|
||||
# 4. Warten bis Postgres healthy
|
||||
sleep 20
|
||||
docker ps --filter name=forgejo-db --format '{{.Status}}' # Up (healthy)
|
||||
|
||||
# 5. DB restoren
|
||||
gunzip -c /opt/backups/forgejo/forgejo-db-<TIMESTAMP>.sql.gz | \
|
||||
docker exec -i forgejo-db psql -U forgejo -d forgejo
|
||||
|
||||
# 6. Data volume restoren
|
||||
docker run --rm \
|
||||
-v forgejo_forgejo-data:/data \
|
||||
-v /opt/backups/forgejo:/backup:ro \
|
||||
alpine \
|
||||
sh -c "cd /data && tar -xzf /backup/forgejo-data-<TIMESTAMP>.tar.gz"
|
||||
|
||||
# 7. Forgejo starten
|
||||
docker compose up -d forgejo
|
||||
|
||||
# 8. Verifikation
|
||||
sleep 30
|
||||
curl -s https://code.sdda.eu/api/healthz
|
||||
curl -s https://code.sdda.eu/api/v1/version
|
||||
docker exec -u git forgejo sh -c 'cd / && forgejo admin user list'
|
||||
```
|
||||
|
||||
### Szenario B: Nur DB zurückspielen (z.B. nach Korruption durch Update)
|
||||
|
||||
```bash
|
||||
cd /opt/ai-apps/forgejo
|
||||
docker compose stop forgejo # nur Forgejo, nicht DB
|
||||
|
||||
# DB mit -c wipen und neu importieren
|
||||
docker exec forgejo-db psql -U forgejo -d postgres \
|
||||
-c "DROP DATABASE forgejo;"
|
||||
docker exec forgejo-db psql -U forgejo -d postgres \
|
||||
-c "CREATE DATABASE forgejo OWNER forgejo;"
|
||||
|
||||
gunzip -c /opt/backups/forgejo/forgejo-db-<TIMESTAMP>.sql.gz | \
|
||||
docker exec -i forgejo-db psql -U forgejo -d forgejo
|
||||
|
||||
docker compose start forgejo
|
||||
```
|
||||
|
||||
### Szenario C: Einzelne Datei / Config aus Data-Volume wiederherstellen
|
||||
|
||||
```bash
|
||||
# Tar-Inhalt inspizieren
|
||||
tar -tzf /opt/backups/forgejo/forgejo-data-<TIMESTAMP>.tar.gz | head -30
|
||||
|
||||
# Einzelne Datei extrahieren
|
||||
mkdir -p /tmp/forgejo-restore
|
||||
tar -xzf /opt/backups/forgejo/forgejo-data-<TIMESTAMP>.tar.gz \
|
||||
-C /tmp/forgejo-restore gitea/conf/app.ini
|
||||
|
||||
# Ins laufende Volume kopieren
|
||||
docker cp /tmp/forgejo-restore/gitea/conf/app.ini forgejo:/data/gitea/conf/app.ini
|
||||
docker restart forgejo
|
||||
```
|
||||
|
||||
## Disaster-Recovery: Kompletter Neuaufbau auf neuem Server
|
||||
|
||||
1. Neuen Host aufsetzen (Docker + Compose)
|
||||
2. `/opt/ai-apps/forgejo/` Stack-Verzeichnis kopieren (inkl. `.env`, docker-compose.yml, backup.sh)
|
||||
3. Backup-Files aus Offsite-Quelle holen (Nextcloud / Hetzner Snapshot)
|
||||
4. Szenario A durchführen
|
||||
5. DNS ggf. auf neue IP anpassen
|
||||
|
||||
## Verification nach Restore
|
||||
```bash
|
||||
# Health
|
||||
curl -s https://code.sdda.eu/api/healthz
|
||||
|
||||
# Auth-Sources
|
||||
docker exec -u git forgejo sh -c 'cd / && forgejo admin auth list'
|
||||
# Erwartet: 1 authentik OAuth2 true
|
||||
|
||||
# User
|
||||
docker exec -u git forgejo sh -c 'cd / && forgejo admin user list'
|
||||
# Erwartet: admin-local + bw + alle anderen
|
||||
|
||||
# Repo-Zähler
|
||||
docker exec forgejo-db psql -U forgejo -d forgejo \
|
||||
-c "SELECT count(*) FROM repository;"
|
||||
```
|
||||
|
||||
## Offsite-Backup-Strategie (zukünftig, M7.5)
|
||||
- **Tier 2:** rclone → Nextcloud, zweiter Cron 03:30
|
||||
- **Tier 3:** Hetzner Cloud Snapshot, manuell wöchentlich
|
||||
|
||||
Beides noch nicht eingerichtet — solange lokale 14-Tage-Retention + Hetzner-VM-Stabilität unser Sicherheitsnetz.
|
||||
|
||||
## Was NIE machen
|
||||
- Backup-Files niemals in Git einchecken (können sensible Repo-Daten enthalten)
|
||||
- Restore auf laufender Prod ohne vorheriges Backup (einfach `docker compose down` macht keine DB-Kopie!)
|
||||
- `docker volume prune` wenn Forgejo noch läuft (wird Daten verlieren)
|
||||
|
|
@ -56,9 +56,9 @@ docker exec forgejo-db psql -U forgejo -d forgejo \
|
|||
- Retention: 14 Tage
|
||||
- Format: `forgejo-db-<ts>.sql.gz` + `forgejo-data-<ts>.tar.gz`
|
||||
- Offsite: noch nicht (Tier 2 via rclone → Nextcloud geplant M7.5)
|
||||
- Restore-Prozedur: siehe [`../../docs/runbooks/forgejo-backup-restore.md`](../../docs/runbooks/forgejo-backup-restore.md)
|
||||
- Restore-Prozedur: siehe `docs/runbooks/forgejo-backup-restore.md` (wird in M7.3 hinzugefügt)
|
||||
|
||||
## Related
|
||||
- `../../Agent.md` — Repo-weites Briefing
|
||||
- `../../docs/architecture/ai-apps-stacks.md` — Server-Kontext
|
||||
- ADRs 0001-0006 in [`../../docs/adr/`](../../docs/adr/) (gespiegelt in M7.3)
|
||||
- ADRs 0001-0006 (in iCloud-Ordner, Spiegelung hier in M7.3)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue