From 6570e818500d7b66dbda21c9da07ec1615fce640 Mon Sep 17 00:00:00 2001 From: Benjamin Weinlich Date: Sat, 11 Apr 2026 22:26:24 +0200 Subject: [PATCH] docs(runbooks,guides): mirror runbooks and Authentik OIDC guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/guides/README.md | 26 +++ docs/guides/authentik-oauth2-provider.md | 224 +++++++++++++++++++++++ docs/runbooks/README.md | 31 ++++ docs/runbooks/forgejo-admin-recovery.md | 96 ++++++++++ docs/runbooks/forgejo-backup-restore.md | 138 ++++++++++++++ 5 files changed, 515 insertions(+) create mode 100644 docs/guides/README.md create mode 100644 docs/guides/authentik-oauth2-provider.md create mode 100644 docs/runbooks/README.md create mode 100644 docs/runbooks/forgejo-admin-recovery.md create mode 100644 docs/runbooks/forgejo-backup-restore.md diff --git a/docs/guides/README.md b/docs/guides/README.md new file mode 100644 index 0000000..ed1404c --- /dev/null +++ b/docs/guides/README.md @@ -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). diff --git a/docs/guides/authentik-oauth2-provider.md b/docs/guides/authentik-oauth2-provider.md new file mode 100644 index 0000000..bd7383f --- /dev/null +++ b/docs/guides/authentik-oauth2-provider.md @@ -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:///user/oauth2//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="-users") +``` + +Oder via REST API: +```bash +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= + +# 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\": \" 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\": \"\"} + ], + \"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": "", + "slug": "", + "provider": , + "meta_launch_url": "https:///user/oauth2/", + "meta_description": "", + "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//.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/` + +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= +GROUP_PK= + +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=, user_id=) +``` + +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": }' +``` + +## 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//.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 "" \ + --secret "" \ + --auto-discover-url "https://welcome.sdda.eu/application/o//.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) diff --git a/docs/runbooks/README.md b/docs/runbooks/README.md new file mode 100644 index 0000000..7e1414a --- /dev/null +++ b/docs/runbooks/README.md @@ -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:** `-.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. diff --git a/docs/runbooks/forgejo-admin-recovery.md b/docs/runbooks/forgejo-admin-recovery.md new file mode 100644 index 0000000..12f9458 --- /dev/null +++ b/docs/runbooks/forgejo-admin-recovery.md @@ -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 diff --git a/docs/runbooks/forgejo-backup-restore.md b/docs/runbooks/forgejo-backup-restore.md new file mode 100644 index 0000000..d439102 --- /dev/null +++ b/docs/runbooks/forgejo-backup-restore.md @@ -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-.sql.gz` — pg_dump der Postgres DB + - `forgejo-data-.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-.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-.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-.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-.tar.gz | head -30 + +# Einzelne Datei extrahieren +mkdir -p /tmp/forgejo-restore +tar -xzf /opt/backups/forgejo/forgejo-data-.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)