diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml new file mode 100644 index 0000000..0b95c44 --- /dev/null +++ b/.gitea/workflows/deploy-dev.yml @@ -0,0 +1,35 @@ +name: Deploy Development + +on: + push: + branches: [develop] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Development + run: | + set -e + echo "=== Deploying Kairo to DEVELOPMENT ===" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo-dev" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b develop "$REPO" . + else + git fetch origin develop + git checkout develop + git reset --hard origin/develop + fi + docker compose -f docker-compose.dev-env.yml build --no-cache + docker compose -f docker-compose.dev-env.yml up -d --wait + if ! curl -sf http://localhost:8097/api/health; then + echo "✗ DEV API nicht erreichbar — Backend-Logs:" + docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true + exit 1 + fi + echo "✓ DEV API /api/health OK" + curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" + echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml new file mode 100644 index 0000000..a8f8889 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml @@ -0,0 +1,30 @@ +name: Deploy Production + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Production + run: | + set -e + echo "=== Deploying Kairo to PRODUCTION ===" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b main "$REPO" . + else + git fetch origin main + git checkout main + git reset --hard origin/main + fi + docker compose build --no-cache + docker compose up -d --wait + curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" + curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" + echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 0d9128f..60f22b6 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,88 +1,125 @@ name: Test Suite -# Push develop/main: deploy → pytest, k6, Playwright (needs deploy, gleicher Workflow) -# Pull Request: compose-smoke auf isolierten Ports (kein Konflikt mit kairo-dev) +# develop: push/PR → Tests gegen deployte Dev-Instanz (wartet auf Container). +# main: kein push/PR — Prod-Tests via workflow_run nach Deploy Production. +# compose-smoke (PR): eigener Stack auf CI-Ports 18197/13197 (kein Konflikt mit kairo-dev). on: push: - branches: [develop, main] + branches: [develop] pull_request: branches: [develop] + workflow_run: + workflows: ["Deploy Development", "Deploy Production"] + types: [completed] jobs: + pytest-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Backend pytest im deployten Container + run: | + set -e + EVENT_NAME="${{ github.event_name }}" + REF_NAME="${{ github.ref_name }}" + BASE_REF="${{ github.base_ref }}" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + APP_DIR="/home/lars/docker/kairo" + COMPOSE_FILE="docker-compose.yml" + + if [ "$EVENT_NAME" = "workflow_run" ]; then + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + COMPOSE_FILE="docker-compose.dev-env.yml" + fi + elif [ "$REF_NAME" = "develop" ] || [ "$BASE_REF" = "develop" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + COMPOSE_FILE="docker-compose.dev-env.yml" + fi + + cd "$APP_DIR" + echo "Warte auf stabilen backend-Container …" + for i in $(seq 1 60); do + if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then + echo "Backend bereit (Versuch $i)" + break + fi + if [ "$i" -eq 60 ]; then + echo "Timeout: backend-Container nicht bereit" + docker compose -f "$COMPOSE_FILE" ps || true + docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true + exit 1 + fi + sleep 5 + done + + docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " + pip install -q -r requirements-dev.txt && + python -m pytest tests -ra -vv --tb=short + " + echo "✓ pytest OK" + lint-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout repository + if: github.event_name != 'workflow_run' uses: actions/checkout@v4 - - name: Check backend syntax + - name: Check backend syntax (Checkout) + if: github.event_name != 'workflow_run' run: | python3 -m py_compile backend/main.py backend/run_migrations.py backend/db.py echo "✓ Backend syntax OK" + - name: Check backend syntax (Deploy-Pfad) + if: github.event_name == 'workflow_run' + run: | + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + else + APP_DIR="/home/lars/docker/kairo" + fi + python3 -m py_compile "$APP_DIR/backend/main.py" + echo "✓ Backend syntax OK" + build-frontend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout repository + if: github.event_name != 'workflow_run' uses: actions/checkout@v4 - name: Setup Node.js + if: github.event_name != 'workflow_run' uses: actions/setup-node@v4 with: node-version: "20" - - name: Build frontend + - name: Build frontend (Checkout) + if: github.event_name != 'workflow_run' run: | cd frontend npm install npm run build echo "✓ Frontend build OK" - deploy: - if: github.event_name == 'push' - runs-on: ubuntu-latest - steps: - - name: Deploy to target environment + - name: Build frontend (Deploy-Pfad) + if: github.event_name == 'workflow_run' run: | - set -e - REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" - - if [ "${{ github.ref_name }}" = "main" ]; then - TARGET="/home/lars/docker/kairo" - BRANCH="main" - COMPOSE="docker-compose.yml" - API_PORT=8004 - UI_PORT=3004 - echo "=== Deploy PRODUCTION ===" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" else - TARGET="/home/lars/docker/kairo-dev" - BRANCH="develop" - COMPOSE="docker-compose.dev-env.yml" - API_PORT=8097 - UI_PORT=3097 - echo "=== Deploy DEVELOPMENT ===" + APP_DIR="/home/lars/docker/kairo" fi - - mkdir -p "$TARGET" - cd "$TARGET" - if [ ! -d .git ]; then - git clone -b "$BRANCH" "$REPO" . - else - git fetch origin "$BRANCH" - git checkout "$BRANCH" - git reset --hard "origin/$BRANCH" - fi - - docker compose -f "$COMPOSE" build --no-cache - docker compose -f "$COMPOSE" up -d --wait - - curl -sf "http://localhost:${API_PORT}/api/health" && echo "✓ API /api/health OK" - curl -sf "http://localhost:${UI_PORT}/api/health" && echo "✓ Frontend-Proxy /api/health OK" - - echo "DEPLOY_TARGET=$TARGET" >> "$GITHUB_ENV" - echo "DEPLOY_COMPOSE=$COMPOSE" >> "$GITHUB_ENV" - echo "DEPLOY_API_PORT=$API_PORT" >> "$GITHUB_ENV" - echo "DEPLOY_UI_PORT=$UI_PORT" >> "$GITHUB_ENV" + cd "$APP_DIR/frontend" + npm install + npm run build + echo "✓ Frontend build OK" compose-smoke: if: github.event_name == 'pull_request' @@ -96,133 +133,112 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Compose Stack bauen und testen (isolierte CI-Ports) + - name: Isolierter Stack (CI-Ports, kein Konflikt mit kairo-dev) run: | set -e COMPOSE="docker-compose.dev-env.yml" API_PORT="${KAIRO_BACKEND_PORT}" UI_PORT="${KAIRO_FRONTEND_PORT}" - + echo "compose-smoke auf localhost:${API_PORT} / ${UI_PORT}" docker compose -f "$COMPOSE" up -d --build --wait curl -sf "http://localhost:${API_PORT}/api/health" curl -sf "http://localhost:${UI_PORT}/api/health" docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short docker compose -f "$COMPOSE" down -v - echo "✓ PR compose-smoke + pytest OK" - - pytest-backend: - if: github.event_name == 'push' - needs: [deploy] - runs-on: ubuntu-latest - steps: - - name: Backend pytest im deployten Container - run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - COMPOSE="docker-compose.yml" - else - APP_DIR="/home/lars/docker/kairo-dev" - COMPOSE="docker-compose.dev-env.yml" - fi - - cd "$APP_DIR" - for i in $(seq 1 60); do - if docker compose -f "$COMPOSE" exec -T backend true 2>/dev/null; then - echo "Backend bereit (Versuch $i)" - break - fi - if [ "$i" -eq 60 ]; then - docker compose -f "$COMPOSE" ps || true - docker compose -f "$COMPOSE" logs backend --tail 80 || true - exit 1 - fi - sleep 5 - done - - docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt - docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short - echo "✓ pytest OK" + echo "✓ compose-smoke OK" k6-health-baseline: name: k6 /api/health Baseline - if: github.event_name == 'push' - needs: [deploy] + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: k6 gegen deployte Instanz (localhost) + - name: Checkout repository + uses: actions/checkout@v4 + + - name: k6-Ziel wählen + id: k6 run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - BASE_URL="http://localhost:8004" + EVENT="${{ github.event_name }}" + WF_NAME="${{ github.event.workflow_run.name }}" + if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then + echo "base_url=http://localhost:8004" >> $GITHUB_OUTPUT else - APP_DIR="/home/lars/docker/kairo-dev" - BASE_URL="http://localhost:8097" + echo "base_url=http://localhost:8097" >> $GITHUB_OUTPUT fi - for i in $(seq 1 30); do - if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then - break - fi - if [ "$i" -eq 30 ]; then - curl -v "$BASE_URL/api/health" || true - exit 1 + - name: /api/health abwarten + run: | + BASE="${{ steps.k6.outputs.base_url }}" + for i in $(seq 1 90); do + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then + echo "Health OK (Versuch $i)" + exit 0 fi sleep 2 done + curl -v "$BASE/api/health" || true + exit 1 - cd "$APP_DIR" + - name: Install k6 + run: | + set -e K6_VER="v0.55.0" - if ! command -v k6 >/dev/null 2>&1; then - ARCH=$(uname -m) - case "$ARCH" in - x86_64) K6_ARCH=amd64 ;; - aarch64|arm64) K6_ARCH=arm64 ;; - *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; - esac - curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz - tar -xzf /tmp/k6.tgz -C /tmp - sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - fi + ARCH=$(uname -m) + case "$ARCH" in + x86_64) K6_ARCH=amd64 ;; + aarch64|arm64) K6_ARCH=arm64 ;; + *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; + esac + curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz + tar -xzf /tmp/k6.tgz -C /tmp + sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - BASE_URL="$BASE_URL" k6 run scripts/load/k6-health-baseline.js - echo "✓ k6 OK" + - name: k6 Health-Baseline + env: + BASE_URL: ${{ steps.k6.outputs.base_url }} + run: k6 run scripts/load/k6-health-baseline.js playwright-smoke: - if: github.event_name == 'push' - needs: [deploy] + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: Playwright smoke gegen deployte Instanz + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Playwright-Ziel wählen + id: pw run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - BASE_URL="http://localhost:3004" + EVENT="${{ github.event_name }}" + WF_NAME="${{ github.event.workflow_run.name }}" + if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then + echo "base_url=http://localhost:3004" >> $GITHUB_OUTPUT else - APP_DIR="/home/lars/docker/kairo-dev" - BASE_URL="http://localhost:3097" + echo "base_url=http://localhost:3097" >> $GITHUB_OUTPUT fi - for i in $(seq 1 30); do - if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then - break - fi - if [ "$i" -eq 30 ]; then - exit 1 + - name: /api/health abwarten + run: | + BASE="${{ steps.pw.outputs.base_url }}" + for i in $(seq 1 90); do + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then + exit 0 fi sleep 2 done + exit 1 - cd "$APP_DIR" - if ! command -v node >/dev/null 2>&1; then - echo "Fehler: Node.js auf dem Runner erforderlich für Playwright." - exit 1 - fi - - npm install --no-audit --no-fund + - name: Install Playwright + run: | + npm install npx playwright install --with-deps chromium - PLAYWRIGHT_BASE_URL="$BASE_URL" npx playwright test tests/smoke-health.spec.js - echo "✓ Playwright smoke OK" + + - name: Run Playwright smoke + env: + PLAYWRIGHT_BASE_URL: ${{ steps.pw.outputs.base_url }} + run: npx playwright test tests/smoke-health.spec.js diff --git a/README.md b/README.md index ed44039..60b0c4d 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,15 @@ Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. -## Deployment +## Deployment & CI -Auto-Deploy und Tests laufen im Workflow **Test Suite** (`.gitea/workflows/test.yml`): +Zwei getrennte Gitea-Workflows (wie Shinkan): -``` -develop/main push → lint ∥ build → deploy → pytest ∥ k6 ∥ Playwright -``` +| Workflow | Branch | +|----------|--------| +| **Deploy Development** | `develop` | +| **Deploy Production** | `main` | +| **Test Suite** | nach Deploy + bei Push/PR `develop` | Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 24a53d2..90f04a9 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -1,5 +1,6 @@ # Keine festen container_name — Compose-Namen haben Projektprefix (-postgres-1). -# Medien: aktuell nicht vorgesehen. Bei Bedarf NAS-Mount + docker-compose.override.yml (siehe docs/DEPLOYMENT.md). +# Host-Ports: KAIRO_BACKEND_PORT (Default 8097), KAIRO_FRONTEND_PORT (Default 3097). +# CI compose-smoke: 18197/13197 — siehe infra/ci-smoke.env.example services: postgres: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 6ed2554..6b66524 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -47,12 +47,16 @@ cp .env.example .env 4. Merge `main` → `deploy-prod.yml` → `curl http://localhost:8004/api/health` 5. Nach Deploy: `test.yml` (workflow_run) — pytest, k6, Playwright smoke gegen **localhost** -| Workflow | Trigger | Jobs | -|----------|---------|------| -| `test.yml` | Push `develop` / `main` | lint, build → **deploy** → **pytest**, **k6**, **Playwright** | -| `test.yml` | Pull Request | lint, build, compose-smoke (+ pytest) | +| Workflow | Trigger | Zweck | +|----------|---------|--------| +| `deploy-dev.yml` | Push `develop` | Deploy nach `/home/lars/docker/kairo-dev` | +| `deploy-prod.yml` | Push `main` | Deploy nach `/home/lars/docker/kairo` | +| `test.yml` | Push/PR `develop`, nach Deploy (`workflow_run`) | pytest, k6, Playwright gegen deployte Instanz | +| `test.yml` → `compose-smoke` | Pull Request | Eigener Stack auf **CI-Ports 18197/13197** | -Alle Integrationstests laufen im **selben** Workflow via `needs: deploy` — kein separates Deploy-Workflow mehr, kein `workflow_run`. +Struktur wie Shinkan: **Deploy** und **Test Suite** sind getrennte Workflows in Gitea. + +Bei Push auf `develop` starten Deploy und Test Suite parallel; pytest wartet auf den Backend-Container der deployten Instanz (Ports 8097/3097). Nach Deploy Production feuert `workflow_run` die Prod-Tests (Ports 8004/3004). --- diff --git a/infra/ci-smoke.env.example b/infra/ci-smoke.env.example new file mode 100644 index 0000000..3d0a94b --- /dev/null +++ b/infra/ci-smoke.env.example @@ -0,0 +1,7 @@ +# CI-Ports für isolierte compose-smoke-Läufe (Pull Requests). +# Deploy kairo-dev nutzt 8097/3097 — keine Überschneidung mit 18197/13197. + +COMPOSE_PROJECT_NAME=kairo-ci-smoke +KAIRO_BACKEND_PORT=18197 +KAIRO_FRONTEND_PORT=13197 +DB_PASSWORD=ci_smoke_password