docs(runbooks,guides): mirror runbooks and Authentik OIDC guide
Two Forgejo runbooks plus the Authentik OAuth2 provider guide, mirrored from the iCloud folder into the versioned repo. Runbooks: - forgejo-admin-recovery.md — fallback login when Authentik is down using the local admin-local user (prohibit_login reset via SQL). - forgejo-backup-restore.md — backup format, restore scenarios (full / DB-only / single file), disaster recovery on new host. Guides: - authentik-oauth2-provider.md — reusable template for adding native OIDC integrations in Authentik. First applied for Forgejo, ready to reuse for OpenProject, Nextcloud, Grafana. Includes the important launch-URL gotcha from ADR-0006. Each category folder has a README.md with format conventions and an index of the current documents. Refs OP#1118
This commit is contained in:
parent
88c541c9ed
commit
6570e81850
5 changed files with 515 additions and 0 deletions
26
docs/guides/README.md
Normal file
26
docs/guides/README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# 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).
|
||||
224
docs/guides/authentik-oauth2-provider.md
Normal file
224
docs/guides/authentik-oauth2-provider.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# 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)
|
||||
31
docs/runbooks/README.md
Normal file
31
docs/runbooks/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# 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.
|
||||
96
docs/runbooks/forgejo-admin-recovery.md
Normal file
96
docs/runbooks/forgejo-admin-recovery.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# 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
|
||||
138
docs/runbooks/forgejo-backup-restore.md
Normal file
138
docs/runbooks/forgejo-backup-restore.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# 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)
|
||||
Loading…
Add table
Reference in a new issue