feat(infra): Gitea Actions CI/CD-Pipelines, Container Registry und Branch-Protection #212

Closed
opened 2026-07-03 19:46:30 +02:00 by niboer · 2 comments
Owner

Ziel

Automatisierte CI/CD-Pipelines via Gitea Actions, Bereitstellung der Images in der Gitea-Container-Registry (nur bei Versions-Tags) und Schutz des main-Branches.

Wichtig: Kein automatischer Close

Dieser Issue enthaelt Aufgaben (Branch-Protection, Secrets, Actions-Runner), die manuell in den Gitea-Einstellungen gesetzt werden muessen. Er wird nicht durch einen Commit geschlossen (closes #212 ist untersagt). Schliessung erfolgt manuell nach Erledigung aller Tasks.

Voraussetzung

  • #205 (E2E-Tests mit Playwright) — die hier verwendeten Test-Features (auth-Fixture, Mailhog-Helper, Spec-Struktur)
  • #206 (Unified Build Script) — build.mjs muss fuer --registry/--version/--push bereit sein
  • Gitea Actions Runner muss auf dem Server registriert sein
  • Container Registry muss in Gitea aktiviert sein (Settings → Packages → Enable)

Trigger-Strategie

Event Workflow Jobs
Push main ci.yml Lint + Unit-Tests
PR gegen main ci.yml + e2e.yml Lint + Unit-Tests + Conventional Commit + E2E-Tests (#205)
Tag v* cd.yml Build + Push aller Images zur Registry

Teil 1: CI-Pipeline (Code-Qualitaet)

Datei: .gitea/workflows/ci.yml

Trigger: Push auf main + PR gegen main

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint-backend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm --prefix backend ci
      - run: npm --prefix backend run lint

  lint-frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm --prefix frontend ci
      - run: npm --prefix frontend run lint

  test-backend:
    needs: [lint-backend]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm --prefix backend ci
      - run: npm --prefix backend test

  test-frontend:
    needs: [lint-frontend]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24 }
      - run: npm --prefix frontend ci
      - run: npm --prefix frontend test

  conventional-commit:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Check PR title
        run: |
          TITLE="${{ github.event.pull_request.title }}"
          PATTERN='^(feat|fix|chore|docs|refactor|test|style|perf|ci|build)(\(.+\))?: .+'
          if ! echo "$TITLE" | grep -qE "$PATTERN"; then
            echo "❌ PR-Titel entspricht nicht Conventional Commits"
            echo "Erwartet: type(scope): description"
            exit 1
          fi
          echo "✅ Conventional Commit: $TITLE"

Teil 2: E2E-Pipeline (Integrationstests via #205)

Datei: .gitea/workflows/e2e.yml

Trigger: Nur bei PRs gegen main

Nutzt die Features aus #205:

  • Playwright-Specs aus e2e/specs/phase-1/ und e2e/specs/phase-2/
  • Auth-Fixture (e2e/fixtures/auth.ts) fuer Keycloak-Login
  • Mailhog-Helper (e2e/helpers/mailhog.ts) fuer E-Mail-Pruefung
  • prepare-tokens.mjs stellt sicher dass shared/design-tokens/tokens.css in frontend/ + keycloak/ verfuegbar ist
name: E2E
on:
  pull_request:
    branches: [main]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Prepare tokens
        run: node scripts/prepare-tokens.mjs

      - name: Build all images
        run: CONTAINER_CMD=docker node scripts/build.mjs all

      - name: Start full stack
        run: docker compose --profile integration-test up -d

      - name: Wait for backend health
        run: |
          for i in $(seq 1 15); do
            curl -sf http://localhost:3000/health && break
            sleep 10
          done

      - name: Wait for frontend health
        run: |
          for i in $(seq 1 10); do
            curl -sf http://localhost:5173/health && break
            sleep 10
          done

      - name: Run Playwright tests
        run: docker compose --profile integration-test exec -T e2e-runner npx playwright test

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: e2e/results/

      - name: Cleanup
        if: always()
        run: |
          docker compose --profile integration-test down -v --rmi local
          docker builder prune -f

Hinweis: Der frontend- und keycloak-Service in docker-compose.yml muessen vor dem Start via build.mjs gebaut sein (passiert in Schritt "Build all images"). Der e2e-runner-Container laeuft dauerhaft via entrypoint: ["sleep", "infinity"] — die Tests werden via docker compose exec gestartet.

Teil 3: CD-Pipeline (Build + Registry)

Datei: .gitea/workflows/cd.yml

Trigger: Nur bei Versions-Tags (v1.0.0, v0.2.0, v2026.07.0 etc.)

name: CD
on:
  push:
    tags: ['v*']

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Extract version
        id: version
        run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Login to Gitea Container Registry
        run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.gs-woa.hopto.org -u niboer --password-stdin

      - name: Build and push all images
        run: |
          REGISTRY="git.gs-woa.hopto.org/niboer"
          VERSION="${{ steps.version.outputs.VERSION }}"
          node scripts/build.mjs all --registry "$REGISTRY" --version "$VERSION" --push

      - name: Cleanup
        if: always()
        run: |
          docker compose down -v --rmi local
          docker builder prune -f

Registry-Login: Token-Setup (manuell)

Der Workflow loggt sich via docker login in die Gitea Container Registry ein. Dafuer wird ein Repository-Secret benoetigt:

  1. Persoenliches Access Token erzeugen:

    • Gitea → SettingsApplicationsGenerate New Token
    • Scope: write:packages (ausreichend zum Pushen von Images)
    • Token kopieren (wird nur einmal angezeigt)
  2. Als Repository-Secret anlegen:

    • Gitea → FamilienFeierPlanerSettingsActionsSecretsAdd Secret
    • Name: REGISTRY_TOKEN
    • Value: das kopierte Token
  3. Im Workflow verwenden:

    - name: Login to Gitea Container Registry
      run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.gs-woa.hopto.org -u niboer --password-stdin
    

Resultierende Images in der Registry:

Registry-Pfad Tags
git.gs-woa.hopto.org/niboer/familienfeierplaner-backend v1.0.0, latest
git.gs-woa.hopto.org/niboer/familienfeierplaner-frontend v1.0.0, latest
git.gs-woa.hopto.org/niboer/familienfeierplaner-keycloak v1.0.0, latest

Teil 4: Build.mjs --registry / --version / --push

scripts/build.mjs bekommt neue Optionen fuer den CD-Workflow:

node scripts/build.mjs all --registry git.gs-woa.hopto.org/niboer --version 1.0.0 --push

Ablauf je Komponente:

function buildAndPush(component, { registry, version, push }) {
  const localTag = `familienfeierplaner-${component}`;
  const remoteTag = `${registry}/${localTag}`;

  buildComponent(component);
  tag(localTag, remoteTag + ':' + version);
  tag(localTag, remoteTag + ':latest');
  if (push) {
    push(remoteTag + ':' + version);
    push(remoteTag + ':latest');
  }
}

Teil 5: Branch-Protection fuer main

Manuell in Gitea Settings → Branches → Branch Protection → main:

  • Block regular commits — Nur Pull Requests erlaubt
  • Required approvals — 1
  • Dismiss stale approvals — Aktivieren
  • Status checks are required — Folgende Checks muessen bei PRs gruen sein:
    • CI / lint-backend
    • CI / lint-frontend
    • CI / test-backend
    • CI / test-frontend
    • CI / conventional-commit
    • E2E / e2e

Hinweis: Branch-Protection muss manuell via Gitea-Oberflaeche gesetzt werden. Der Agent hat keinen Admin-Zugriff auf die Repository-Einstellungen.

Tasks

  • Gitea Actions Runner auf Server registrieren (manuell/Admin)
  • Persoenliches Access Token erzeugen (Settings → Applications, Scope: write:packages)
  • REGISTRY_TOKEN-Secret im Repository anlegen (Settings → Actions → Secrets)
  • .gitea/workflows/ci.yml anlegen (Lint + Tests + Conventional Commit)
  • .gitea/workflows/e2e.yml anlegen (E2E-Tests via #205, nur bei PRs)
  • .gitea/workflows/cd.yml anlegen (Build + Push bei Tags)
  • scripts/build.mjs um --registry, --version, --push erweitern
  • Branch-Protection fuer main einrichten (Settings → Branches)
  • Test-Tag erstellen → CD-Pipeline muss Images bauen + pushen
  • Test-PR erstellen → CI + E2E muessen laufen, Conventional Commit wird geprueft
  • Test-PR mit falschem Conventional Commit → muss abgewiesen werden

Dateien (neu)

.gitea/workflows/ci.yml
.gitea/workflows/e2e.yml
.gitea/workflows/cd.yml

Geaenderte Dateien

scripts/build.mjs             # + --registry, --version, --push
## Ziel Automatisierte CI/CD-Pipelines via Gitea Actions, Bereitstellung der Images in der Gitea-Container-Registry (nur bei Versions-Tags) und Schutz des main-Branches. ## Wichtig: Kein automatischer Close Dieser Issue enthaelt Aufgaben (Branch-Protection, Secrets, Actions-Runner), die manuell in den Gitea-Einstellungen gesetzt werden muessen. Er wird **nicht** durch einen Commit geschlossen (`closes #212` ist untersagt). Schliessung erfolgt manuell nach Erledigung aller Tasks. ## Voraussetzung - #205 (E2E-Tests mit Playwright) — die hier verwendeten Test-Features (auth-Fixture, Mailhog-Helper, Spec-Struktur) - #206 (Unified Build Script) — `build.mjs` muss fuer `--registry`/`--version`/`--push` bereit sein - Gitea Actions Runner muss auf dem Server registriert sein - Container Registry muss in Gitea aktiviert sein (Settings → Packages → Enable) ## Trigger-Strategie | Event | Workflow | Jobs | |-------|----------|------| | **Push main** | `ci.yml` | Lint + Unit-Tests | | **PR gegen main** | `ci.yml` + `e2e.yml` | Lint + Unit-Tests + Conventional Commit + **E2E-Tests (#205)** | | **Tag `v*`** | `cd.yml` | Build + Push aller Images zur Registry | ## Teil 1: CI-Pipeline (Code-Qualitaet) **Datei:** `.gitea/workflows/ci.yml` **Trigger:** Push auf main + PR gegen main ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: lint-backend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 24 } - run: npm --prefix backend ci - run: npm --prefix backend run lint lint-frontend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 24 } - run: npm --prefix frontend ci - run: npm --prefix frontend run lint test-backend: needs: [lint-backend] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 24 } - run: npm --prefix backend ci - run: npm --prefix backend test test-frontend: needs: [lint-frontend] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 24 } - run: npm --prefix frontend ci - run: npm --prefix frontend test conventional-commit: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Check PR title run: | TITLE="${{ github.event.pull_request.title }}" PATTERN='^(feat|fix|chore|docs|refactor|test|style|perf|ci|build)(\(.+\))?: .+' if ! echo "$TITLE" | grep -qE "$PATTERN"; then echo "❌ PR-Titel entspricht nicht Conventional Commits" echo "Erwartet: type(scope): description" exit 1 fi echo "✅ Conventional Commit: $TITLE" ``` ## Teil 2: E2E-Pipeline (Integrationstests via #205) **Datei:** `.gitea/workflows/e2e.yml` **Trigger:** Nur bei PRs gegen main **Nutzt die Features aus #205:** - Playwright-Specs aus `e2e/specs/phase-1/` und `e2e/specs/phase-2/` - Auth-Fixture (`e2e/fixtures/auth.ts`) fuer Keycloak-Login - Mailhog-Helper (`e2e/helpers/mailhog.ts`) fuer E-Mail-Pruefung - `prepare-tokens.mjs` stellt sicher dass `shared/design-tokens/tokens.css` in frontend/ + keycloak/ verfuegbar ist ```yaml name: E2E on: pull_request: branches: [main] jobs: e2e: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Prepare tokens run: node scripts/prepare-tokens.mjs - name: Build all images run: CONTAINER_CMD=docker node scripts/build.mjs all - name: Start full stack run: docker compose --profile integration-test up -d - name: Wait for backend health run: | for i in $(seq 1 15); do curl -sf http://localhost:3000/health && break sleep 10 done - name: Wait for frontend health run: | for i in $(seq 1 10); do curl -sf http://localhost:5173/health && break sleep 10 done - name: Run Playwright tests run: docker compose --profile integration-test exec -T e2e-runner npx playwright test - name: Upload test report if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: e2e/results/ - name: Cleanup if: always() run: | docker compose --profile integration-test down -v --rmi local docker builder prune -f ``` **Hinweis:** Der `frontend`- und `keycloak`-Service in docker-compose.yml muessen vor dem Start via `build.mjs` gebaut sein (passiert in Schritt "Build all images"). Der `e2e-runner`-Container laeuft dauerhaft via `entrypoint: ["sleep", "infinity"]` — die Tests werden via `docker compose exec` gestartet. ## Teil 3: CD-Pipeline (Build + Registry) **Datei:** `.gitea/workflows/cd.yml` **Trigger:** Nur bei Versions-Tags (`v1.0.0`, `v0.2.0`, `v2026.07.0` etc.) ```yaml name: CD on: push: tags: ['v*'] jobs: build-and-push: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Extract version id: version run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT - name: Login to Gitea Container Registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.gs-woa.hopto.org -u niboer --password-stdin - name: Build and push all images run: | REGISTRY="git.gs-woa.hopto.org/niboer" VERSION="${{ steps.version.outputs.VERSION }}" node scripts/build.mjs all --registry "$REGISTRY" --version "$VERSION" --push - name: Cleanup if: always() run: | docker compose down -v --rmi local docker builder prune -f ``` ## Registry-Login: Token-Setup (manuell) Der Workflow loggt sich via `docker login` in die Gitea Container Registry ein. Dafuer wird ein **Repository-Secret** benoetigt: 1. **Persoenliches Access Token erzeugen:** - Gitea → `Settings` → `Applications` → `Generate New Token` - Scope: `write:packages` (ausreichend zum Pushen von Images) - Token kopieren (wird nur einmal angezeigt) 2. **Als Repository-Secret anlegen:** - Gitea → `FamilienFeierPlaner` → `Settings` → `Actions` → `Secrets` → `Add Secret` - Name: `REGISTRY_TOKEN` - Value: das kopierte Token 3. **Im Workflow verwenden:** ```yaml - name: Login to Gitea Container Registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.gs-woa.hopto.org -u niboer --password-stdin ``` **Resultierende Images in der Registry:** | Registry-Pfad | Tags | |---------------|------| | `git.gs-woa.hopto.org/niboer/familienfeierplaner-backend` | `v1.0.0`, `latest` | | `git.gs-woa.hopto.org/niboer/familienfeierplaner-frontend` | `v1.0.0`, `latest` | | `git.gs-woa.hopto.org/niboer/familienfeierplaner-keycloak` | `v1.0.0`, `latest` | ## Teil 4: Build.mjs --registry / --version / --push `scripts/build.mjs` bekommt neue Optionen fuer den CD-Workflow: ```bash node scripts/build.mjs all --registry git.gs-woa.hopto.org/niboer --version 1.0.0 --push ``` Ablauf je Komponente: ```javascript function buildAndPush(component, { registry, version, push }) { const localTag = `familienfeierplaner-${component}`; const remoteTag = `${registry}/${localTag}`; buildComponent(component); tag(localTag, remoteTag + ':' + version); tag(localTag, remoteTag + ':latest'); if (push) { push(remoteTag + ':' + version); push(remoteTag + ':latest'); } } ``` ## Teil 5: Branch-Protection fuer main Manuell in Gitea Settings → Branches → Branch Protection → main: - **Block regular commits** — Nur Pull Requests erlaubt - **Required approvals** — 1 - **Dismiss stale approvals** — Aktivieren - **Status checks are required** — Folgende Checks muessen bei PRs gruen sein: - `CI / lint-backend` - `CI / lint-frontend` - `CI / test-backend` - `CI / test-frontend` - `CI / conventional-commit` - `E2E / e2e` **Hinweis:** Branch-Protection muss manuell via Gitea-Oberflaeche gesetzt werden. Der Agent hat keinen Admin-Zugriff auf die Repository-Einstellungen. ## Tasks - [x] Gitea Actions Runner auf Server registrieren (manuell/Admin) - [x] Persoenliches Access Token erzeugen (`Settings → Applications`, Scope: `write:packages`) - [x] `REGISTRY_TOKEN`-Secret im Repository anlegen (Settings → Actions → Secrets) - [x] `.gitea/workflows/ci.yml` anlegen (Lint + Tests + Conventional Commit) - [x] `.gitea/workflows/e2e.yml` anlegen (E2E-Tests via #205, nur bei PRs) - [x] `.gitea/workflows/cd.yml` anlegen (Build + Push bei Tags) - [x] `scripts/build.mjs` um `--registry`, `--version`, `--push` erweitern - [x] Branch-Protection fuer main einrichten (Settings → Branches) - [x] Test-Tag erstellen → CD-Pipeline muss Images bauen + pushen - [x] Test-PR erstellen → CI + E2E muessen laufen, Conventional Commit wird geprueft - [x] Test-PR mit falschem Conventional Commit → muss abgewiesen werden ## Dateien (neu) ``` .gitea/workflows/ci.yml .gitea/workflows/e2e.yml .gitea/workflows/cd.yml ``` ## Geaenderte Dateien ``` scripts/build.mjs # + --registry, --version, --push ```
niboer added this to the MS-INTEGRATION milestone 2026-07-03 19:46:30 +02:00
niboer self-assigned this 2026-07-03 19:46:30 +02:00
Author
Owner

Ergaenzung: Builder-Cache auch im CI/CD-Cleanup praegen

Das Multi-Stage-Dockerfile (backend/Dockerfile) produziert Intermediate-Images der builder-Stage, die von --rmi local nicht erfasst werden (siehe #206, Kommentar zur Analyse).

Massnahme: In den Cleanup-Schritten von e2e.yml und cd.yml nach docker compose down -v --rmi local zusaetzlich docker builder prune -f ausfuehren:

.gitea/workflows/e2e.yml

- name: Cleanup
  if: always()
  run: |
    docker compose --profile integration-test down -v --rmi local
    docker builder prune -f

.gitea/workflows/cd.yml

- name: Cleanup
  if: always()
  run: |
    docker compose down -v --rmi local
    docker builder prune -f

Begruendung: docker builder prune -f entfernt gezielt nur den Build-Cache (intermediate Stages), ohne benannte/getaggte Images anzutasten. docker image prune -f waere zu aggressiv und koennte noch benoetigte Cache-Images entfernen.

## Ergaenzung: Builder-Cache auch im CI/CD-Cleanup praegen Das Multi-Stage-Dockerfile (`backend/Dockerfile`) produziert Intermediate-Images der `builder`-Stage, die von `--rmi local` nicht erfasst werden (siehe #206, Kommentar zur Analyse). **Massnahme:** In den Cleanup-Schritten von `e2e.yml` und `cd.yml` nach `docker compose down -v --rmi local` zusaetzlich `docker builder prune -f` ausfuehren: ### `.gitea/workflows/e2e.yml` ```yaml - name: Cleanup if: always() run: | docker compose --profile integration-test down -v --rmi local docker builder prune -f ``` ### `.gitea/workflows/cd.yml` ```yaml - name: Cleanup if: always() run: | docker compose down -v --rmi local docker builder prune -f ``` **Begruendung:** `docker builder prune -f` entfernt gezielt nur den Build-Cache (intermediate Stages), ohne benannte/getaggte Images anzutasten. `docker image prune -f` waere zu aggressiv und koennte noch benoetigte Cache-Images entfernen.
Author
Owner

Automatisierte Tasks abgeschlossen (Commit 264993b):

  • .gitea/workflows/ci.yml — Lint + Tests + Conventional Commit
  • .gitea/workflows/e2e.yml — Playwright-Tests (nur PR)
  • .gitea/workflows/cd.yml — Build + Push Backend/Frontend/Keycloak (Tags v*)
  • scripts/build.mjs--registry, --version, --push CLI-Optionen
  • docs/design/06-betrieb-entwicklung.md — CI/CD-Sektion ergaenzt

Manuell noch ausstehend:

  1. Gitea Actions Runner auf Server registrieren
  2. Persoenliches Access Token erzeugen (Settings → Applications, Scope: write:packages)
  3. REGISTRY_TOKEN-Secret im Repository anlegen (Settings → Actions → Secrets)
  4. Branch-Protection fuer main einrichten (Settings → Branches): Block regular commits, 1 Approval, Required checks: CI/*, E2E/e2e
  5. Test-Tag erstellen → CD-Pipeline testen
  6. Test-PR erstellen → CI + E2E testen
Automatisierte Tasks abgeschlossen (Commit 264993b): - `.gitea/workflows/ci.yml` — Lint + Tests + Conventional Commit - `.gitea/workflows/e2e.yml` — Playwright-Tests (nur PR) - `.gitea/workflows/cd.yml` — Build + Push Backend/Frontend/Keycloak (Tags v*) - `scripts/build.mjs` — `--registry`, `--version`, `--push` CLI-Optionen - `docs/design/06-betrieb-entwicklung.md` — CI/CD-Sektion ergaenzt **Manuell noch ausstehend:** 1. [x] Gitea Actions Runner auf Server registrieren 2. [x] Persoenliches Access Token erzeugen (Settings → Applications, Scope: `write:packages`) 3. [x] `REGISTRY_TOKEN`-Secret im Repository anlegen (Settings → Actions → Secrets) 4. [x] Branch-Protection fuer `main` einrichten (Settings → Branches): Block regular commits, 1 Approval, Required checks: CI/*, E2E/e2e 5. [x] Test-Tag erstellen → CD-Pipeline testen 6. [x] Test-PR erstellen → CI + E2E testen
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
WompSchmiede/FamilienFeierPlaner#212
No description provided.