rebreak-monorepo/docs/internal/CI_CD_DEPLOYMENT_WORKFLOW.md

428 lines
14 KiB
Markdown

# CI/CD Deployment Workflow
Diese Dokumentation beschreibt den kompletten Deployment-Workflow für das `rebreak-monorepo` nach der Migration weg von GitHub Actions hin zu einem selbstgehosteten Setup mit **Gitea** und **Woodpecker CI**.
## TL;DR
- **Code-Hosting**: https://git.rebreak.org/chahine/rebreak-monorepo (Gitea)
- **CI/CD**: https://ci.rebreak.org/chahine/rebreak-monorepo (Woodpecker)
- **Build-Server**: `raynis-builder` / `api.trucko.org` / `128.140.47.53`
- **Staging-Ziel**: `staging.rebreak.org` (`91.99.225.223`)
- **Pipeline-Definition**: `.woodpecker.yml` im Repo
- **Legacy-Fallback**: Gitea-Webhook → `scripts/deploy-webhook/server.mjs``scripts/deploy.sh`
---
## Infrastruktur-Übersicht
```
┌─────────────────┐ push ┌──────────────────┐
│ Entwickler │ ────────────► │ Gitea │
│ (lokal) │ │ git.rebreak.org │
└─────────────────┘ └────────┬─────────┘
Webhook (push) │ Woodpecker CI
│ (pull + build)
┌─────────────────────┐
│ Woodpecker Server │
│ ci.rebreak.org │
└──────────┬──────────┘
│ gRPC
┌─────────────────────┐
│ Woodpecker Agent │
│ raynis-builder │
│ 128.140.47.53 │
└──────────┬──────────┘
│ scp + ssh
┌─────────────────────┐
│ Staging-Server │
│ staging.rebreak.org│
└─────────────────────┘
```
### Server
| Server | IP / Domain | Rolle |
|--------|-------------|-------|
| `raynis-builder` | `128.140.47.53`, `api.trucko.org` | Gitea, Woodpecker, Build-Agent |
| `staging.rebreak.org` | `91.99.225.223` | Ziel für Staging-Deploys, Webhook-Listener |
### Dienste auf raynis-builder
| Dienst | Container | Port (intern) | Extern |
|--------|-----------|---------------|--------|
| Gitea | `gitea` | `3000` | https://git.rebreak.org |
| Woodpecker Server | `woodpecker-server` | `8000` | https://ci.rebreak.org |
| Woodpecker Agent | `woodpecker-agent` | — | — |
| Postgres | `gitea-db` | `5432` | — |
### Pfade auf raynis-builder
- Gitea + Woodpecker Stack: `/mnt/HC_Volume_103985481/gitea/`
- Deploy-SSH-Key: `/home/runner/.ssh/rebreak-deploy`
- Gitea-Daten: `/mnt/HC_Volume_103985481/gitea/data/`
- Woodpecker-Daten: `/mnt/HC_Volume_103985481/gitea/woodpecker-server/`
---
## Gitea
### Zugang
- URL: https://git.rebreak.org
- Admin-User: `chahine`
- Repos:
- `chahine/hello-ci` (Test-Repo)
- `chahine/rebreak-monorepo` (Hauptrepo)
### SSH-Zugriff
Gitea-SSH läuft auf **Port 2222**:
```bash
git clone ssh://git@git.rebreak.org:2222/chahine/rebreak-monorepo.git
```
Für HTTPS:
```bash
git clone https://git.rebreak.org/chahine/rebreak-monorepo.git
```
### Deploy-Key
- Auf dem Staging-Server liegt `/home/runner/.ssh/rebreak-deploy`
- Der Public Key (`rebreak-deploy.pub`) ist in Gitea als Deploy-Key für `chahine/rebreak-monorepo` hinterlegt
- Wird vom Staging-Server verwendet, um Code-Updates von Gitea zu pullen
---
## Woodpecker CI
### Zugang
- URL: https://ci.rebreak.org
- Login über Gitea-OAuth
- Nur der Admin-User `chahine` ist automatisch Admin
### Pipeline-Definition
Die Pipeline ist in `.woodpecker.yml` definiert:
```yaml
when:
- event: push
branch: main
- event: pull_request
steps:
install:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- pnpm install --frozen-lockfile
build-backend:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- cd backend && NODE_OPTIONS=--max-old-space-size=4096 pnpm build
depends_on: [install]
build-admin:
image: node:24-slim
commands:
- corepack enable
- corepack prepare pnpm@10.23.0 --activate
- cd apps/admin && pnpm build
depends_on: [install]
deploy-backend:
image: alpine:3.21
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- tar czf backend-output.tar.gz -C backend/.output .
- scp -i ~/.ssh/id_ed25519 backend-output.tar.gz root@staging.rebreak.org:/srv/rebreak/backend/.output-incoming.tar.gz
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-from-artifact.sh'
depends_on: [build-backend]
when:
- event: push
branch: main
deploy-admin:
image: alpine:3.21
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- tar czf admin-output.tar.gz -C apps/admin/.output .
- scp -i ~/.ssh/id_ed25519 admin-output.tar.gz root@staging.rebreak.org:/srv/rebreak/apps/admin/.output-incoming.tar.gz
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-admin-from-artifact.sh'
depends_on: [build-admin]
when:
- event: push
branch: main
```
### Wichtige Details
- **Build** läuft in `node:24-slim`-Containern auf dem Woodpecker-Agent.
- **Deploy** läuft in `alpine:3.21`-Containern.
- Der SSH-Key wird über `WOODPECKER_BACKEND_DOCKER_VOLUMES` in jeden Pipeline-Container gemountet:
- Host-Pfad: `/home/runner/.ssh`
- Container-Pfad: `/root/ssh-keys`
- Deploy-Steps laufen nur bei `push` auf `main`.
- Pull Requests werden gebaut, aber nicht deployed.
### Agent-Konfiguration
Wichtige Env-Variablen in `/mnt/HC_Volume_103985481/gitea/docker-compose.yml`:
```yaml
woodpecker-agent:
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_BACKEND_DOCKER_VOLUMES=/home/runner/.ssh:/root/ssh-keys:ro
```
`WOODPECKER_BACKEND_DOCKER_VOLUMES` mountet den Host-SSH-Key in **alle** Pipeline-Container.
---
## Staging-Deploy-Mechanismus
### Primärer Pfad: Woodpecker CI
1. Push auf `main` in Gitea
2. Gitea benachrichtigt Woodpecker
3. Woodpecker startet die Pipeline
4. Pipeline baut Backend und Admin
5. Pipeline kopiert Artifacts per `scp` auf `staging.rebreak.org`
6. Auf dem Staging-Server werden diese Scripts ausgeführt:
- `/srv/rebreak/scripts/deploy-from-artifact.sh` (Backend)
- `/srv/rebreak/scripts/deploy-admin-from-artifact.sh` (Admin)
7. Die Scripts extrahieren das Artifact und starten die pm2-Prozesse neu
### Legacy-Fallback: Gitea-Webhook
Ein zusätzlicher Webhook in Gitea triggert den alten Webhook-Listener auf dem Staging-Server:
- **Webhook-URL**: `https://staging.rebreak.org/webhook`
- **Listener**: `scripts/deploy-webhook/server.mjs` (Port 9000, reverse-proxied via Nginx)
- **Deploy-Script**: `scripts/deploy.sh`
- **Funktion**:
- Validiert HMAC-SHA256-Signatur
- Prüft, ob Branch `main` ist
- Startet `scripts/deploy.sh`
- Führt `git pull`, `pnpm install`, `prisma migrate deploy` (falls nötig), Build und pm2-Restart aus
#### Warum zwei Pfade?
- **Woodpecker** ist der bevorzugte Pfad: Build auf leistungsfähigem Server, Artifact-Deploy.
- **Webhook** ist ein Legacy-Fallback, der z.B. Migrationen direkt auf dem Staging-Server ausführt.
- Der `.deploy-ga.lock` verhindert, dass beide gleichzeitig laufen.
#### Signatur-Validierung
Der Listener unterstützt beide Formate:
- **GitHub**: `x-hub-signature-256: sha256=...`
- **Gitea**: `x-gitea-signature: ...` (nur Hex, kein `sha256=` Präfix)
Secret: `GITHUB_WEBHOOK_SECRET` aus `/etc/environment` auf dem Staging-Server.
---
## SSH-Key / Secrets
### `rebreak-deploy` Key
- **Privater Key**: `/home/runner/.ssh/rebreak-deploy` auf raynis-builder
- **Public Key**: `/home/runner/.ssh/rebreak-deploy.pub`
- **Fingerprint**: `SHA256:Wkw1O4YGEM9q++dbCd3+CjAfILtjhPasE71wG5wnH4Q`
- **Verwendung**:
- Woodpecker-Deploy-Steps verwenden ihn für `scp`/`ssh` zum Staging-Server
- Staging-Server pullt damit Code von Gitea (Deploy-Key im Repo)
- **Auf Staging-Server authorisiert**: Ja, in `~root/.ssh/authorized_keys`
### Gitea-OAuth-App für Woodpecker
- Name: `Woodpecker CI`
- Type: `confidential_client`
- Redirect URI: `https://ci.rebreak.org/authorize`
- Scopes: Standard (keine expliziten nötig)
### Woodpecker Secrets
- `staging_deploy_key` wurde ursprünglich in Woodpecker hinterlegt, wird aber **nicht mehr verwendet**.
- Der Key wird stattdessen über `WOODPECKER_BACKEND_DOCKER_VOLUMES` gemountet.
---
## Wichtige Dateien
| Datei | Zweck |
|-------|-------|
| `.woodpecker.yml` | Pipeline-Definition für Woodpecker |
| `scripts/deploy-webhook/server.mjs` | Webhook-Listener auf Staging-Server |
| `scripts/deploy.sh` | Legacy Deploy-Script (inkl. Prisma-Migrationen) |
| `scripts/deploy-from-artifact.sh` | Artifact-Deploy für Backend |
| `scripts/deploy-admin-from-artifact.sh` | Artifact-Deploy für Admin |
| `ecosystem.config.js` | pm2-Konfiguration auf Staging-Server |
| `/mnt/HC_Volume_103985481/gitea/docker-compose.yml` | Gitea + Woodpecker Stack |
---
## Prisma-Migrationen
Migrationen werden an zwei Stellen geprüft:
1. **In `scripts/deploy-from-artifact.sh`** (Woodpecker-Deploy):
- Vergleicht `.last-deployed-sha` mit HEAD
- Führt `prisma migrate deploy` aus, wenn sich etwas unter `backend/prisma/migrations/` oder `backend/prisma/schema.prisma` geändert hat
2. **In `scripts/deploy.sh`** (Webhook-Deploy):
- Gleicher Mechanismus
- Wird als Fallback ausgeführt
> ⚠️ Wichtig: Migrationen werden **nur** ausgeführt, wenn sich die Prisma-Dateien seit dem letzten Deploy geändert haben. Bei manuellem Eingriff muss ggf. `.last-deployed-sha` gelöscht werden.
---
## Betrieb: Pipelines ansehen
### Woodpecker-Logs
```bash
ssh root@128.140.47.53
docker logs woodpecker-server --tail 100
docker logs woodpecker-agent --tail 100
```
### Pipeline-Status in DB
```bash
sqlite3 /mnt/HC_Volume_103985481/gitea/woodpecker-server/woodpecker.sqlite \
"SELECT id, repo_id, number, status, message FROM pipelines WHERE repo_id=2 ORDER BY id DESC LIMIT 5;"
```
### Webhook-Logs auf Staging
```bash
ssh root@staging.rebreak.org
pm2 logs rebreak-webhook --lines 50
```
---
## Troubleshooting
### Pipeline-Clone schlägt fehl mit Submodule-Fehler
Ursache: Verwaister Submodule-Eintrag ohne `.gitmodules`.
Lösung:
```bash
git rm --cached <pfad-zum-submodule>
echo '<pfad-zum-submodule>/' >> .gitignore
git commit -m "Remove broken submodule entry"
```
### Deploy-Step: "Load key ... error in libcrypto"
Ursache: Alpine-OpenSSH konnte das Key-Format nicht lesen.
Lösung: `image: alpine:3.21` statt `image: alpine` verwenden.
### "Permission denied (publickey)" beim Deploy
Ursache: SSH-Key nicht im Container verfügbar oder nicht auf Staging authorisiert.
Prüfung:
```bash
# Ist der Key auf Staging authorisiert?
ssh -i /home/runner/.ssh/rebreak-deploy root@staging.rebreak.org 'echo OK'
# Ist WOODPECKER_BACKEND_DOCKER_VOLUMES gesetzt?
docker inspect woodpecker-agent --format '{{json .Config.Env}}'
```
### Webhook gibt 401
Ursache: Signatur stimmt nicht.
Prüfung:
- Ist `GITHUB_WEBHOOK_SECRET` in `/etc/environment` auf Staging gesetzt?
- Stimmt das Secret im Gitea-Webhook?
- Verwendet Gitea den richtigen Signatur-Header? Der Listener akzeptiert sowohl `x-hub-signature-256` (GitHub) als auch `x-gitea-signature` (Gitea).
### Woodpecker zeigt keine Repos
Ursache: OAuth-App falsch konfiguriert oder User nicht angemeldet.
Lösung:
- Auf https://ci.rebreak.org ausloggen und wieder einloggen
- In Gitea unter Settings → Applications prüfen, ob die OAuth-App existiert
- OAuth-App muss `confidential_client: true` sein
### Zwei Deploys kollidieren
Der `.deploy-ga.lock` verhindert parallele Deploys. Wenn ein Deploy hängt:
```bash
ssh root@staging.rebreak.org
rm -f /srv/rebreak/.deploy-ga.lock
```
---
## Migration von GitHub
### Was wurde gemacht?
1. Repo auf Gitea erstellt
2. Code von GitHub nach Gitea gepusht
3. Woodpecker mit Gitea verbunden
4. `.woodpecker.yml` erstellt
5. Staging-Server remote auf Gitea umgestellt
6. Gitea-Webhook für Legacy-Deploy eingerichtet
7. Webhook-Listener für Gitea-Signaturen angepasst
### Was noch zu tun ist?
- Lokale Entwickler-Repos sollten den `origin`-Remote auf Gitea umstellen:
```bash
git remote set-url origin https://git.rebreak.org/chahine/rebreak-monorepo.git
# oder SSH:
git remote set-url origin ssh://git@git.rebreak.org:2222/chahine/rebreak-monorepo.git
```
- Falls GitHub komplett abgeschaltet werden soll, den GitHub-Remote entfernen und keinen `git push origin main` mehr ausführen.
- Der `staging_deploy_key`-Secret in Woodpecker kann gelöscht werden (wird nicht mehr verwendet).
---
## Kontakte / Verantwortlichkeiten
- Infrastruktur: `raynis-builder` (Hetzner VPS)
- Gitea / Woodpecker Admin: `chahine`
- Staging-Server: `staging.rebreak.org`
---
*Letzte Aktualisierung: 2026-06-18*