428 lines
14 KiB
Markdown
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*
|