feat(stacks/forgejo): add self-hosted Git stack
First stack mirrored 1:1 from /opt/ai-apps/forgejo/ on the server. Includes docker-compose.yml (forgejo + postgres 16), .env.example template (NO real secrets), backup.sh (nightly pg_dump + tar), plus Agent.md and README.md. Known gotchas documented in Agent.md: - Volume mount on /data not /var/lib/gitea - SSH port 2222 in container (system sshd occupies 22) - OIDC config lives in DB table login_source, not app.ini Refs OP#1119
This commit is contained in:
parent
723ee00388
commit
8ba375caaa
5 changed files with 277 additions and 0 deletions
18
stacks/forgejo/.env.example
Normal file
18
stacks/forgejo/.env.example
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Forgejo Stack — environment template
|
||||
# Copy to .env and fill in actual values. NEVER commit .env to Git.
|
||||
|
||||
# Postgres password (generate: openssl rand -hex 32)
|
||||
DB_PASSWORD=<openssl rand -hex 32>
|
||||
|
||||
# Forgejo internal crypto
|
||||
FORGEJO_SECRET_KEY=<openssl rand -hex 32>
|
||||
FORGEJO_INTERNAL_TOKEN=<openssl rand -hex 64>
|
||||
|
||||
# SMTP via Mailcow
|
||||
SMTP_USER=electric-horses@sdda.eu
|
||||
SMTP_PASSWORD=<aus Mailcow>
|
||||
|
||||
# OIDC (Authentik) — applied via CLI after first boot, reference only here
|
||||
OIDC_CLIENT_ID=<from Authentik Application 'Forgejo'>
|
||||
OIDC_CLIENT_SECRET=<from Authentik Application 'Forgejo'>
|
||||
OIDC_DISCOVERY_URL=https://welcome.sdda.eu/application/o/forgejo/.well-known/openid-configuration
|
||||
64
stacks/forgejo/Agent.md
Normal file
64
stacks/forgejo/Agent.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Agent Briefing — Forgejo Stack
|
||||
|
||||
Du arbeitest am Forgejo-Git-Hosting. Lies erst `../../Agent.md` am Repo-Root für globale Konventionen.
|
||||
|
||||
## Live auf
|
||||
- **URL:** https://code.sdda.eu
|
||||
- **SSH:** `ssh://git@code.sdda.eu:222/<user>/<repo>.git`
|
||||
- **Server:** ai-apps (Hetzner cx22, 10.0.0.8)
|
||||
- **Pfad auf Server:** `/opt/ai-apps/forgejo/`
|
||||
- **Live seit:** 2026-04-11
|
||||
- **Version:** Forgejo 10 (`codeberg.org/forgejo/forgejo:10`)
|
||||
|
||||
## Authentifizierung
|
||||
- Primär: **Nativer OIDC via Authentik** (nicht ForwardAuth)
|
||||
- Application in Authentik: `forgejo` auf `welcome.sdda.eu`
|
||||
- Zugangskontrolle: Gruppen-Policy `forgejo-users` erforderlich
|
||||
- Launch-URL: `https://code.sdda.eu/user/oauth2/authentik` (für Silent SSO aus Authentik-Dashboard)
|
||||
- Fallback: lokaler `admin-local` User mit `prohibit_login=true` (Emergency)
|
||||
- Siehe ADR-0003 (OIDC statt ForwardAuth), ADR-0006 (Silent-SSO-Launch-URL)
|
||||
|
||||
## Kritische Gotchas
|
||||
1. **Volume-Mount auf `/data`**, NICHT `/var/lib/gitea`. Forgejo schreibt alles nach `/data`. Siehe ADR-0005.
|
||||
2. **SSH-Port-Kollision:** Forgejo-Image hat system-sshd auf 22, deshalb Forgejos eigener Server auf Container-Port 2222 → Host-Port 222.
|
||||
3. **OIDC-Config lebt in der Postgres-DB** (Tabelle `login_source`), NICHT in `app.ini`. Zum Ändern: `docker exec -u git forgejo sh -c 'cd / && forgejo admin auth update-oauth --id 1 ...'`
|
||||
4. **`forgejo admin` CLI** braucht `-u git` und `cd /`: `docker exec -u git forgejo sh -c 'cd / && forgejo admin user list'`
|
||||
5. **User-Promotion zum Admin** via SQL am saubersten: `UPDATE "user" SET is_admin = true WHERE lower_name = 'NAME';`
|
||||
|
||||
## Ops-Kommandos
|
||||
```bash
|
||||
ssh ai-apps
|
||||
cd /opt/ai-apps/forgejo
|
||||
|
||||
# Status
|
||||
docker compose ps
|
||||
docker logs forgejo --tail 50
|
||||
|
||||
# Restart
|
||||
docker compose restart forgejo
|
||||
|
||||
# Update (Major-Version manuell, Tag auf :10 gepinnt)
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# Backup manuell
|
||||
bash backup.sh
|
||||
|
||||
# User-Verwaltung
|
||||
docker exec -u git forgejo sh -c 'cd / && forgejo admin user list'
|
||||
docker exec forgejo-db psql -U forgejo -d forgejo \
|
||||
-c "UPDATE \"user\" SET is_admin = true WHERE lower_name = 'NAME';"
|
||||
```
|
||||
|
||||
## Backup
|
||||
- Script: `backup.sh` in diesem Ordner (spiegelt `/opt/ai-apps/forgejo/backup.sh`)
|
||||
- Cron: `0 3 * * *` auf ai-apps → `/opt/backups/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` (wird in M7.3 hinzugefügt)
|
||||
|
||||
## Related
|
||||
- `../../Agent.md` — Repo-weites Briefing
|
||||
- `../../docs/architecture/ai-apps-stacks.md` — Server-Kontext
|
||||
- ADRs 0001-0006 (in iCloud-Ordner, Spiegelung hier in M7.3)
|
||||
63
stacks/forgejo/README.md
Normal file
63
stacks/forgejo/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Forgejo — Self-Hosted Git
|
||||
|
||||
**Live:** https://code.sdda.eu
|
||||
**Git SSH:** `ssh://git@code.sdda.eu:222/<user>/<repo>.git`
|
||||
|
||||
## Was ist das?
|
||||
Unser selbst-gehostetes Git-Hosting, basierend auf [Forgejo](https://forgejo.org/) — einem non-profit Community-Fork von Gitea, gesteuert von Codeberg e.V. Hier leben Code, Infrastructure-as-Code (wie dieses Repo!), Dokumentation und alle künftigen internen Tools.
|
||||
|
||||
## Schnelles Setup auf neuem Host
|
||||
|
||||
```bash
|
||||
# Vorausgesetzt: Traefik mit Let's Encrypt läuft bereits, DNS ist gesetzt,
|
||||
# Authentik OIDC-Provider ist konfiguriert (Client-ID + Secret bereit)
|
||||
|
||||
cp .env.example .env
|
||||
# Secrets generieren
|
||||
sed -i '' -e "s|<openssl rand -hex 32>|$(openssl rand -hex 32)|g" .env # macOS
|
||||
# ODER: sed -i -e "s|...|...|g" .env auf Linux
|
||||
# SMTP + OIDC Credentials manuell eintragen
|
||||
|
||||
docker compose up -d
|
||||
sleep 30
|
||||
|
||||
# Admin-Fallback anlegen
|
||||
docker exec -u git forgejo forgejo admin user create \
|
||||
--username admin-local \
|
||||
--email admin-local@sdda.eu \
|
||||
--password "<strong-password>" \
|
||||
--admin --must-change-password=false
|
||||
|
||||
# OIDC einrichten
|
||||
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/forgejo/.well-known/openid-configuration" \
|
||||
--scopes "openid profile email" \
|
||||
--skip-local-2fa
|
||||
|
||||
# admin-local sperren (Notfall-Reserve)
|
||||
docker exec forgejo-db psql -U forgejo -d forgejo \
|
||||
-c "UPDATE \"user\" SET prohibit_login = true WHERE lower_name = 'admin-local';"
|
||||
```
|
||||
|
||||
## Zugang
|
||||
- **Normal (empfohlen):** Via Authentik OIDC. `code.sdda.eu/user/login` → "Anmelden mit authentik" — oder direkt aus dem Authentik-Dashboard (Silent SSO).
|
||||
- **Voraussetzung:** Mitglied der Authentik-Gruppe `forgejo-users`
|
||||
- **Emergency-Fallback:** `admin-local` User (in Runbook, nicht hier)
|
||||
|
||||
## Files in diesem Ordner
|
||||
- `docker-compose.yml` — Stack-Definition
|
||||
- `.env.example` — Template, **niemals echte `.env` committen**
|
||||
- `backup.sh` — Nightly-Backup-Script (pg_dump + tar)
|
||||
- `Agent.md` — AI-Briefing für Sessions an diesem Stack
|
||||
- `README.md` — diese Datei
|
||||
|
||||
## Live vs. Repo
|
||||
Dieses Repo ist die **versionierte Wahrheit** des Stacks. Wenn du auf ai-apps eine Änderung machst (z.B. `docker-compose.yml` anpasst), **bitte auch hier einchecken**. Andernfalls driftet das Repo vom Server weg und verliert seinen Wert.
|
||||
|
||||
## OpenProject
|
||||
- M7.1 — Forgejo Deployment mit Authentik SSO (abgeschlossen)
|
||||
- M7.2 — Repo initialisiert (dieser Commit)
|
||||
29
stacks/forgejo/backup.sh
Executable file
29
stacks/forgejo/backup.sh
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/bash
|
||||
# Forgejo Backup — nightly cron
|
||||
# Runs pg_dump + tar of /data, retention 14 days.
|
||||
# Schedule: 0 3 * * * /opt/ai-apps/forgejo/backup.sh >> /opt/ai-apps/forgejo/backup.log 2>&1
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TS=$(date +%Y%m%d-%H%M%S)
|
||||
DEST=/opt/backups/forgejo
|
||||
mkdir -p "$DEST"
|
||||
|
||||
echo "[$(date -Iseconds)] backup start"
|
||||
|
||||
# Postgres dump
|
||||
docker exec forgejo-db pg_dump -U forgejo forgejo | gzip > "$DEST/forgejo-db-$TS.sql.gz"
|
||||
echo " db dump: $(ls -lh $DEST/forgejo-db-$TS.sql.gz | awk '{print $5}')"
|
||||
|
||||
# Data volume tar
|
||||
docker run --rm \
|
||||
-v forgejo_forgejo-data:/data:ro \
|
||||
-v "$DEST":/backup \
|
||||
alpine \
|
||||
tar -czf "/backup/forgejo-data-$TS.tar.gz" -C /data .
|
||||
echo " data tar: $(ls -lh $DEST/forgejo-data-$TS.tar.gz | awk '{print $5}')"
|
||||
|
||||
# Retention: 14 days
|
||||
find "$DEST" -type f -mtime +14 -delete
|
||||
|
||||
echo "[$(date -Iseconds)] backup complete"
|
||||
103
stacks/forgejo/docker-compose.yml
Normal file
103
stacks/forgejo/docker-compose.yml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Forgejo Stack — self-hosted Git hosting with Authentik SSO
|
||||
# Part of M7.1 (Operations & Documentation Foundation)
|
||||
# Network: traefik-public (public via Traefik) + forgejo-internal (service ↔ DB)
|
||||
|
||||
services:
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:10
|
||||
container_name: forgejo
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
USER_UID: "1000"
|
||||
USER_GID: "1000"
|
||||
FORGEJO__database__DB_TYPE: postgres
|
||||
FORGEJO__database__HOST: forgejo-db:5432
|
||||
FORGEJO__database__NAME: forgejo
|
||||
FORGEJO__database__USER: forgejo
|
||||
FORGEJO__database__PASSWD: ${DB_PASSWORD}
|
||||
FORGEJO__server__DOMAIN: code.sdda.eu
|
||||
FORGEJO__server__ROOT_URL: https://code.sdda.eu/
|
||||
FORGEJO__server__SSH_DOMAIN: code.sdda.eu
|
||||
FORGEJO__server__SSH_PORT: "222"
|
||||
FORGEJO__server__START_SSH_SERVER: "true"
|
||||
FORGEJO__server__SSH_LISTEN_PORT: "2222"
|
||||
FORGEJO__server__HTTP_PORT: "3000"
|
||||
FORGEJO__server__LFS_START_SERVER: "true"
|
||||
FORGEJO__security__INSTALL_LOCK: "true"
|
||||
FORGEJO__security__SECRET_KEY: ${FORGEJO_SECRET_KEY}
|
||||
FORGEJO__security__INTERNAL_TOKEN: ${FORGEJO_INTERNAL_TOKEN}
|
||||
FORGEJO__service__DISABLE_REGISTRATION: "true"
|
||||
FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true"
|
||||
FORGEJO__service__SHOW_REGISTRATION_BUTTON: "false"
|
||||
FORGEJO__service__ENABLE_NOTIFY_MAIL: "true"
|
||||
FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE: "true"
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNIN: "false"
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNUP: "false"
|
||||
FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
|
||||
FORGEJO__oauth2_client__USERNAME: email
|
||||
FORGEJO__oauth2_client__UPDATE_AVATAR: "true"
|
||||
FORGEJO__oauth2_client__ACCOUNT_LINKING: "auto"
|
||||
FORGEJO__mailer__ENABLED: "true"
|
||||
FORGEJO__mailer__PROTOCOL: smtp
|
||||
FORGEJO__mailer__SMTP_ADDR: 10.0.0.2
|
||||
FORGEJO__mailer__SMTP_PORT: "587"
|
||||
FORGEJO__mailer__FROM: "Forgejo <forgejo@sdda.eu>"
|
||||
FORGEJO__mailer__USER: ${SMTP_USER}
|
||||
FORGEJO__mailer__PASSWD: ${SMTP_PASSWORD}
|
||||
FORGEJO__log__LEVEL: Info
|
||||
volumes:
|
||||
- forgejo-data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
networks:
|
||||
- traefik-public
|
||||
- forgejo-internal
|
||||
ports:
|
||||
- "222:2222"
|
||||
depends_on:
|
||||
forgejo-db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:3000/api/healthz"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.http.services.forgejo.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.forgejo.rule=Host(`code.sdda.eu`)"
|
||||
- "traefik.http.routers.forgejo.entrypoints=websecure"
|
||||
- "traefik.http.routers.forgejo.tls=true"
|
||||
- "traefik.http.routers.forgejo.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.forgejo.service=forgejo"
|
||||
|
||||
forgejo-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: forgejo-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: forgejo
|
||||
POSTGRES_USER: forgejo
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- forgejo-db-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- forgejo-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U forgejo -d forgejo"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
forgejo-data:
|
||||
forgejo-db-data:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
forgejo-internal:
|
||||
driver: bridge
|
||||
Loading…
Add table
Reference in a new issue