Infrastruktur-Grundgeruest fuer Kairo Jinkendo: Docker, Gitea Actions, Deployment-Doku (ohne App-Code).
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Failing after 1s
Test Suite / pytest-backend (push) Has been cancelled
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Failing after 1s
Test Suite / pytest-backend (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
848452f837
70
.env.example
Normal file
70
.env.example
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# === .env neben der jeweiligen docker-compose.*.yml kopieren ====================
|
||||||
|
# Docker Compose ersetzt ${VARIABLE} beim Start.
|
||||||
|
#
|
||||||
|
# Pro Umgebung eigene Datei (z. B. ~/docker/kairo/.env für Prod,
|
||||||
|
# ~/docker/kairo-dev/.env für Dev) — dieselben SCHLÜSSEL, unterschiedliche Werte.
|
||||||
|
# Kein separates DEV_APP_URL vs APP_URL: immer APP_URL, ALLOWED_ORIGINS, DB_*, …
|
||||||
|
|
||||||
|
# ─── Typische Werte PROD (docker-compose.yml) ─────────────────────────────────
|
||||||
|
# DB_NAME=kairo
|
||||||
|
# DB_USER=kairo_user
|
||||||
|
# DB_PASSWORD=…
|
||||||
|
# APP_URL=https://kairo.jinkendo.de
|
||||||
|
# ALLOWED_ORIGINS=https://kairo.jinkendo.de
|
||||||
|
# ENVIRONMENT=production
|
||||||
|
# KAIRO_MEDIA_HOST=/kairo-media
|
||||||
|
# MEDIA_ROOT=/app/media
|
||||||
|
|
||||||
|
# ─── Typische Werte DEV (docker-compose.dev-env.yml) ─────────────────────────
|
||||||
|
# DB_NAME=kairo_dev
|
||||||
|
# DB_USER=kairo_dev
|
||||||
|
# DB_PASSWORD=dev_password
|
||||||
|
# APP_URL=https://dev.kairo.jinkendo.de
|
||||||
|
# ALLOWED_ORIGINS=https://dev.kairo.jinkendo.de,http://192.168.2.49:3097
|
||||||
|
# ENVIRONMENT=development
|
||||||
|
# KAIRO_MEDIA_HOST=/kairo-media/dev
|
||||||
|
# MEDIA_ROOT=/app/media
|
||||||
|
|
||||||
|
# ─── Ab hier: eine ausfüllbare Vorlage (bei uns meist Prod-Defaults) ───────────
|
||||||
|
DB_HOST=postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=kairo
|
||||||
|
DB_USER=kairo_user
|
||||||
|
DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD
|
||||||
|
|
||||||
|
OPENROUTER_API_KEY=your_api_key_here
|
||||||
|
OPENROUTER_MODEL=anthropic/claude-sonnet-4
|
||||||
|
|
||||||
|
# Vereins-Kontingente hart blockieren (KI-Kosten!). Nur 1, true oder yes aktivieren.
|
||||||
|
CLUB_FEATURE_ENFORCE=1
|
||||||
|
|
||||||
|
# KI-Debug (Docker): KAIRO_AI_DEBUG in docker-compose*.yml angebunden — 1 = ausführliche WARN-Logs.
|
||||||
|
# KAIRO_AI_DEBUG=1
|
||||||
|
|
||||||
|
SMTP_HOST=smtp.example.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=noreply@jinkendo.de
|
||||||
|
SMTP_PASS=your_smtp_password
|
||||||
|
SMTP_FROM=noreply@jinkendo.de
|
||||||
|
SMTP_SSL=
|
||||||
|
SMTP_STARTTLS=
|
||||||
|
|
||||||
|
AUTO_ADMIN_FIRST_USER=true
|
||||||
|
ADMIN_BOOTSTRAP_EMAILS=
|
||||||
|
|
||||||
|
APP_URL=https://kairo.jinkendo.de
|
||||||
|
ALLOWED_ORIGINS=https://kairo.jinkendo.de
|
||||||
|
ENVIRONMENT=production
|
||||||
|
|
||||||
|
# Medien (Docker Compose): KAIRO_MEDIA_HOST = Verzeichnis auf dem Host (Bind-Mount),
|
||||||
|
# MEDIA_ROOT = gleicher Pfad im Container (muss mit dem Mount-Ziel übereinstimmen).
|
||||||
|
KAIRO_MEDIA_HOST=/kairo-media
|
||||||
|
MEDIA_ROOT=/app/media
|
||||||
|
|
||||||
|
MEDIAWIKI_API_URL=https://karatetrainer.net/api.php
|
||||||
|
MEDIAWIKI_USER=Jinkendo
|
||||||
|
MEDIAWIKI_PASSWORD=CHANGE_ME
|
||||||
|
MEDIAWIKI_CATEGORY_EXERCISES=Übungen
|
||||||
|
MEDIAWIKI_CATEGORY_SKILLS=Fähigkeitsbeschreibung
|
||||||
|
MEDIAWIKI_CATEGORY_METHODS=Methodenbeschreibung
|
||||||
|
MEDIAWIKI_CATEGORY_MODELS=Reifegradmodelle
|
||||||
28
.gitea/workflows/deploy-dev.yml
Normal file
28
.gitea/workflows/deploy-dev.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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 ==="
|
||||||
|
cd /home/lars/docker/kairo-dev
|
||||||
|
git fetch origin develop || git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git .
|
||||||
|
git reset --hard origin/develop
|
||||||
|
docker compose -f docker-compose.dev-env.yml build --no-cache
|
||||||
|
docker compose -f docker-compose.dev-env.yml up -d
|
||||||
|
sleep 5
|
||||||
|
if ! curl -sf http://localhost:8097/api/version; then
|
||||||
|
echo "✗ DEV API nicht erreichbar — Backend-Logs (Migration/Startup):"
|
||||||
|
docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✓ DEV API healthy"
|
||||||
|
curl -sf http://localhost:3097/api/version && echo "✓ DEV über Frontend-Nginx (wie Browser) healthy"
|
||||||
|
echo "=== Kairo DEV Deploy complete ==="
|
||||||
23
.gitea/workflows/deploy-prod.yml
Normal file
23
.gitea/workflows/deploy-prod.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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 ==="
|
||||||
|
cd /home/lars/docker/kairo
|
||||||
|
git fetch origin main || git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git .
|
||||||
|
git reset --hard origin/main
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
sleep 5
|
||||||
|
curl -sf http://localhost:8004/api/version && echo "✓ PROD API (direkt) healthy"
|
||||||
|
curl -sf http://localhost:3004/api/version && echo "✓ PROD API über Frontend-Nginx (wie Browser) healthy"
|
||||||
|
echo "=== Kairo PROD Deploy complete ==="
|
||||||
312
.gitea/workflows/test.yml
Normal file
312
.gitea/workflows/test.yml
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
name: Test Suite
|
||||||
|
|
||||||
|
# develop: push/PR → Tests gegen Dev (parallel oder vor Deploy Development).
|
||||||
|
# main: kein push/PR-Trigger — vermeidet doppelten Dev-Lauf beim Merge develop→main;
|
||||||
|
# Prod-Tests nur via workflow_run nach erfolgreichem Deploy Production.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
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 -r /app/requirements-dev.txt &&
|
||||||
|
cd /app &&
|
||||||
|
ACCESS_LAYER_STRICT=1 python scripts/check_access_layer_hints.py &&
|
||||||
|
python scripts/security_release_checks.py &&
|
||||||
|
ACCESS_LAYER_INTEGRATION=1 SKIP_DB_MIGRATE=1 python -m pytest tests -m 'not slow' -ra -vv --tb=short
|
||||||
|
"
|
||||||
|
|
||||||
|
lint-backend:
|
||||||
|
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check backend syntax
|
||||||
|
run: |
|
||||||
|
EVENT_NAME="${{ github.event_name }}"
|
||||||
|
REF_NAME="${{ github.ref_name }}"
|
||||||
|
RUN_WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
|
APP_DIR="/home/lars/docker/kairo"
|
||||||
|
|
||||||
|
if [ "$EVENT_NAME" = "workflow_run" ]; then
|
||||||
|
if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then
|
||||||
|
APP_DIR="/home/lars/docker/kairo-dev"
|
||||||
|
fi
|
||||||
|
elif [ "$REF_NAME" = "develop" ]; then
|
||||||
|
APP_DIR="/home/lars/docker/kairo-dev"
|
||||||
|
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: Build frontend
|
||||||
|
run: |
|
||||||
|
EVENT_NAME="${{ github.event_name }}"
|
||||||
|
REF_NAME="${{ github.ref_name }}"
|
||||||
|
RUN_WORKFLOW="${{ github.event.workflow_run.name }}"
|
||||||
|
APP_DIR="/home/lars/docker/kairo"
|
||||||
|
|
||||||
|
if [ "$EVENT_NAME" = "workflow_run" ]; then
|
||||||
|
if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then
|
||||||
|
APP_DIR="/home/lars/docker/kairo-dev"
|
||||||
|
fi
|
||||||
|
elif [ "$REF_NAME" = "develop" ]; then
|
||||||
|
APP_DIR="/home/lars/docker/kairo-dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$APP_DIR/frontend"
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
echo "✓ Frontend build OK"
|
||||||
|
|
||||||
|
k6-health-baseline:
|
||||||
|
name: k6 /health Baseline
|
||||||
|
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
E2E_TARGET_URL: https://dev.kairo.jinkendo.de
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: E2E-Ziel wählen (Dev über Proxy vs. Production)
|
||||||
|
id: e2e
|
||||||
|
run: |
|
||||||
|
EVENT="${{ github.event_name }}"
|
||||||
|
WF_NAME="${{ github.event.workflow_run.name }}"
|
||||||
|
DEV_BASE="${{ env.E2E_TARGET_URL }}"
|
||||||
|
if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then
|
||||||
|
echo "mode=prod" >> $GITHUB_OUTPUT
|
||||||
|
echo "base_url=https://kairo.jinkendo.de" >> $GITHUB_OUTPUT
|
||||||
|
echo "→ k6 gegen Prod-Basis."
|
||||||
|
else
|
||||||
|
echo "mode=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT
|
||||||
|
echo "→ k6 gegen Dev (${DEV_BASE})."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Dev /health abwarten
|
||||||
|
if: ${{ steps.e2e.outputs.mode == 'dev' }}
|
||||||
|
run: |
|
||||||
|
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
echo "Warte auf $BASE/health …"
|
||||||
|
for i in $(seq 1 90); do
|
||||||
|
if curl -sf "$BASE/health" >/dev/null 2>&1; then
|
||||||
|
echo "Health OK (Versuch $i)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen."
|
||||||
|
curl -v "$BASE/health" || true
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Prod /health abwarten
|
||||||
|
if: ${{ steps.e2e.outputs.mode == 'prod' }}
|
||||||
|
run: |
|
||||||
|
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
echo "Warte auf $BASE/health …"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
if curl -sf "$BASE/health" >/dev/null 2>&1; then
|
||||||
|
echo "Health OK (Versuch $i)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo "Timeout: Prod /health nicht erreichbar"
|
||||||
|
curl -v "$BASE/health" || true
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Install k6
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
K6_VER="v0.55.0"
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64) K6_ARCH=amd64 ;;
|
||||||
|
aarch64|arm64) K6_ARCH=arm64 ;;
|
||||||
|
*) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
echo "Installing k6 ${K6_VER} linux-${K6_ARCH}"
|
||||||
|
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
|
||||||
|
k6 version
|
||||||
|
|
||||||
|
- name: k6 Health-Baseline (parallele /health)
|
||||||
|
env:
|
||||||
|
BASE_URL: ${{ steps.e2e.outputs.base_url }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
echo "k6 gegen BASE_URL=$BASE_URL"
|
||||||
|
k6 run scripts/load/k6-health-baseline.js
|
||||||
|
echo "✓ k6 Health-Baseline passed"
|
||||||
|
|
||||||
|
playwright-tests:
|
||||||
|
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
E2E_TARGET_URL: https://dev.kairo.jinkendo.de
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: E2E-Ziel wählen (Dev über Proxy vs. Production)
|
||||||
|
id: e2e
|
||||||
|
run: |
|
||||||
|
EVENT="${{ github.event_name }}"
|
||||||
|
WF_NAME="${{ github.event.workflow_run.name }}"
|
||||||
|
DEV_BASE="${{ env.E2E_TARGET_URL }}"
|
||||||
|
if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then
|
||||||
|
echo "mode=prod" >> $GITHUB_OUTPUT
|
||||||
|
echo "base_url=https://kairo.jinkendo.de" >> $GITHUB_OUTPUT
|
||||||
|
echo "→ Prod. Secrets: E2E_PROD_TEST_EMAIL / E2E_PROD_TEST_PASSWORD."
|
||||||
|
else
|
||||||
|
echo "mode=dev" >> $GITHUB_OUTPUT
|
||||||
|
echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT
|
||||||
|
echo "→ Deployte Dev-Umgebung (${DEV_BASE}). Secrets: E2E_DEV_TEST_EMAIL / E2E_DEV_TEST_PASSWORD."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Dev /health abwarten
|
||||||
|
if: ${{ steps.e2e.outputs.mode == 'dev' }}
|
||||||
|
run: |
|
||||||
|
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
echo "Warte auf $BASE/health …"
|
||||||
|
for i in $(seq 1 90); do
|
||||||
|
if curl -sf "$BASE/health" >/dev/null 2>&1; then
|
||||||
|
echo "Health OK (Versuch $i)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen."
|
||||||
|
curl -v "$BASE/health" || true
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Prod /health abwarten
|
||||||
|
if: ${{ steps.e2e.outputs.mode == 'prod' }}
|
||||||
|
run: |
|
||||||
|
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
echo "Warte auf $BASE/health …"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
if curl -sf "$BASE/health" >/dev/null 2>&1; then
|
||||||
|
echo "Health OK (Versuch $i)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo "Timeout: Prod /health nicht erreichbar"
|
||||||
|
curl -v "$BASE/health" || true
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Testnutzer registrieren (Dev, nur wenn möglich)
|
||||||
|
if: ${{ steps.e2e.outputs.mode == 'dev' }}
|
||||||
|
env:
|
||||||
|
E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }}
|
||||||
|
E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
if [ -z "$E2E_DEV_TEST_EMAIL" ] || [ -z "$E2E_DEV_TEST_PASSWORD" ]; then
|
||||||
|
echo "(Registrierung übersprungen — Secrets E2E_DEV_* nicht gesetzt.)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
curl -sf -X POST "$BASE/api/auth/register" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"email\":\"${E2E_DEV_TEST_EMAIL}\",\"password\":\"${E2E_DEV_TEST_PASSWORD}\",\"name\":\"Playwright CI\"}" \
|
||||||
|
|| echo "(Register evtl. schon erfolgt oder Limits — Login-Test gilt trotzdem.)"
|
||||||
|
|
||||||
|
- name: Install Playwright
|
||||||
|
run: |
|
||||||
|
npm ci || npm install
|
||||||
|
npx playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: Run Playwright tests
|
||||||
|
env:
|
||||||
|
E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }}
|
||||||
|
E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
MODE="${{ steps.e2e.outputs.mode }}"
|
||||||
|
BASE_URL="${{ steps.e2e.outputs.base_url }}"
|
||||||
|
export PLAYWRIGHT_BASE_URL="$BASE_URL"
|
||||||
|
|
||||||
|
if [ "$MODE" = "prod" ]; then
|
||||||
|
export TEST_EMAIL="${{ secrets.E2E_PROD_TEST_EMAIL }}"
|
||||||
|
export TEST_PASSWORD="${{ secrets.E2E_PROD_TEST_PASSWORD }}"
|
||||||
|
if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then
|
||||||
|
echo "Fehler: E2E_PROD_TEST_EMAIL und E2E_PROD_TEST_PASSWORD setzen."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
export TEST_EMAIL="$E2E_DEV_TEST_EMAIL"
|
||||||
|
export TEST_PASSWORD="$E2E_DEV_TEST_PASSWORD"
|
||||||
|
if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then
|
||||||
|
echo "Fehler: E2E_DEV_TEST_EMAIL und E2E_DEV_TEST_PASSWORD setzen (Playwright soll gegen Dev einloggen)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p screenshots
|
||||||
|
npx playwright test
|
||||||
|
echo "✓ Playwright tests passed"
|
||||||
|
|
||||||
|
- name: Upload test screenshots
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: playwright-screenshots
|
||||||
|
path: screenshots/
|
||||||
|
retention-days: 7
|
||||||
76
.gitignore
vendored
Normal file
76
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
frontend/dist/
|
||||||
|
|
||||||
|
# Data (NEVER commit database or user data)
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
data/
|
||||||
|
photos/
|
||||||
|
uploads/
|
||||||
|
media/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/settings.json
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Docker overrides
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# SSL certificates (never commit)
|
||||||
|
nginx/ssl/
|
||||||
|
nginx/certbot/
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.csr
|
||||||
|
|
||||||
|
# Pytest
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Claude: nur ausgewählte Bereiche versionieren
|
||||||
|
.claude/**
|
||||||
|
!.claude/README.md
|
||||||
|
!.claude/docs/
|
||||||
|
!.claude/docs/**/*
|
||||||
|
!.claude/rules/
|
||||||
|
!.claude/rules/**/*
|
||||||
|
!.claude/commands/
|
||||||
|
!.claude/commands/**/*
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# Cursor MCP
|
||||||
|
.cursor/mcp.json
|
||||||
|
frontend/package-lock.json
|
||||||
59
CLAUDE.md
Normal file
59
CLAUDE.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Kairo Jinkendo – Entwickler-Kontext
|
||||||
|
|
||||||
|
## Projekt-Übersicht
|
||||||
|
|
||||||
|
**Kairo Jinkendo** (回廊 Jinkendo) — Schwesterprodukt in der **Jinkendo**-App-Familie (人拳道).
|
||||||
|
Domains: kairo.jinkendo.de · dev.kairo.jinkendo.de
|
||||||
|
|
||||||
|
**Status:** Infrastruktur (Docker, Gitea Actions, Deployment-Doku) ist eingerichtet. Anwendungscode folgt.
|
||||||
|
|
||||||
|
## Tech-Stack (geplant)
|
||||||
|
|
||||||
|
| Komponente | Technologie |
|
||||||
|
|-----------|-------------|
|
||||||
|
| Frontend | React 18 + Vite + PWA (Node 20) |
|
||||||
|
| Backend | FastAPI Python 3.12 |
|
||||||
|
| Datenbank | PostgreSQL 16 Alpine |
|
||||||
|
| Container | Docker + Docker Compose |
|
||||||
|
| Auth | Token-basiert + bcrypt (Familien-Standard) |
|
||||||
|
|
||||||
|
**Ports:** Prod 3004/8004 · Dev 3097/8097 — nicht ändern ohne explizite Freigabe (Reverse Proxy/Fritz!Box).
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet → Fritz!Box (privat.stommer.com) → Synology NAS → Raspberry Pi 5 (192.168.2.49)
|
||||||
|
|
||||||
|
Git Workflow:
|
||||||
|
develop → Auto-Deploy → dev.kairo.jinkendo.de (kairo-dev/, Port 3097/8097)
|
||||||
|
main → Auto-Deploy → kairo.jinkendo.de (kairo/, Port 3004/8004)
|
||||||
|
|
||||||
|
Gitea: http://192.168.2.144:3000/Lars/Kairo-Jinkendo
|
||||||
|
Runner: Raspberry Pi (/home/lars/gitea-runner/) — gemeinsam mit Shinkan/Mitai
|
||||||
|
|
||||||
|
Manuell:
|
||||||
|
cd /home/lars/docker/kairo[-dev]
|
||||||
|
docker compose -f docker-compose[.dev-env].yml build --no-cache && up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verzeichnisstruktur (Zielbild)
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/ # FastAPI — noch anzulegen
|
||||||
|
frontend/ # React + Vite — noch anzulegen
|
||||||
|
.gitea/workflows/ # CI/CD (deploy + test)
|
||||||
|
docs/ # Deployment-Doku
|
||||||
|
scripts/load/ # k6 Health-Baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
## Referenz
|
||||||
|
|
||||||
|
Deployment-Muster und Familien-Standards: Schwesterprojekt **shinkan-jinkendo** (`c:\Dev\shinkan-jinkendo`).
|
||||||
|
|
||||||
|
## Jinkendo-Familie
|
||||||
|
|
||||||
|
```
|
||||||
|
mitai.jinkendo.de → Körper-Tracker (身体)
|
||||||
|
shinkan.jinkendo.de → Trainingsplanung (真観)
|
||||||
|
kairo.jinkendo.de → (回廊 — Produktdefinition folgt)
|
||||||
|
```
|
||||||
63
README.md
Normal file
63
README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Kairo Jinkendo (回廊)
|
||||||
|
|
||||||
|
**Produktfamilie Jinkendo (人拳道) — Schwesterprodukt zu Shinkan und Mitai**
|
||||||
|
|
||||||
|
> Infrastruktur-Setup: Deployment, Docker und Gitea Actions sind vorbereitet.
|
||||||
|
> Anwendungscode (Backend/Frontend) folgt in einem separaten Schritt.
|
||||||
|
|
||||||
|
## Deployment-Übersicht
|
||||||
|
|
||||||
|
| Umgebung | Branch | Server-Pfad | Host-Ports (UI/API) | Domain |
|
||||||
|
|----------|--------|-------------|---------------------|--------|
|
||||||
|
| **Development** | `develop` | `/home/lars/docker/kairo-dev` | 3097 / 8097 | https://dev.kairo.jinkendo.de |
|
||||||
|
| **Production** | `main` | `/home/lars/docker/kairo` | 3004 / 8004 | https://kairo.jinkendo.de |
|
||||||
|
|
||||||
|
Auto-Deploy via Gitea Actions auf dem Raspberry Pi (gleicher Runner wie Shinkan/Mitai).
|
||||||
|
|
||||||
|
**Gitea:** http://192.168.2.144:3000/Lars/Kairo-Jinkendo
|
||||||
|
|
||||||
|
## Tech-Stack (geplant)
|
||||||
|
|
||||||
|
- **Frontend:** React 18 + Vite + PWA
|
||||||
|
- **Backend:** FastAPI (Python 3.12)
|
||||||
|
- **Datenbank:** PostgreSQL 16
|
||||||
|
- **Container:** Docker + Docker Compose
|
||||||
|
|
||||||
|
## Lokales Setup (wenn App-Code vorhanden)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git
|
||||||
|
cd Kairo-Jinkendo
|
||||||
|
cp .env.example .env
|
||||||
|
# .env anpassen
|
||||||
|
|
||||||
|
# Development
|
||||||
|
docker compose -f docker-compose.dev-env.yml up --build
|
||||||
|
|
||||||
|
# Production (lokal)
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Frontend (Dev): http://localhost:3097
|
||||||
|
Backend (Dev): http://localhost:8097
|
||||||
|
|
||||||
|
## Server-Einrichtung
|
||||||
|
|
||||||
|
Siehe [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) für Verzeichnisse, `.env`-Dateien und Reverse-Proxy-Hinweise auf dem Pi.
|
||||||
|
|
||||||
|
## Git-Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
develop → Auto-Deploy → kairo-dev (Dev)
|
||||||
|
main → Auto-Deploy → kairo (Prod)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
- **Deployment:** [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)
|
||||||
|
- **Entwickler-Kontext:** [CLAUDE.md](CLAUDE.md)
|
||||||
|
- **Schwesterprojekt (Referenz):** [shinkan-jinkendo](http://192.168.2.144:3000/Lars/shinkan-jinkendo)
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Proprietary – Lars Stommer
|
||||||
14
backend/README.md
Normal file
14
backend/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Backend
|
||||||
|
|
||||||
|
FastAPI-Anwendung — noch anzulegen.
|
||||||
|
|
||||||
|
Erwartete Struktur (analog shinkan-jinkendo):
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── Dockerfile
|
||||||
|
├── main.py
|
||||||
|
├── requirements.txt
|
||||||
|
├── migrations/
|
||||||
|
└── routers/
|
||||||
|
```
|
||||||
79
docker-compose.dev-env.yml
Normal file
79
docker-compose.dev-env.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Keine festen container_name — Compose-Namen haben Projektprefix (<projekt>-postgres-1).
|
||||||
|
# Medien: In .env KAIRO_MEDIA_HOST (Host-Pfad) und optional MEDIA_ROOT (Container-Pfad) setzen.
|
||||||
|
# Default Host /kairo-media/dev — Verzeichnis ggf. anlegen oder Compose legt es an.
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${DB_NAME:-kairo_dev}"
|
||||||
|
POSTGRES_USER: "${DB_USER:-kairo_dev}"
|
||||||
|
POSTGRES_PASSWORD: "${DB_PASSWORD:-dev_password}"
|
||||||
|
volumes:
|
||||||
|
- dev-kairo-db-data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5437:5432"
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- dev-kairo-network
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: "${DB_NAME:-kairo_dev}"
|
||||||
|
DB_USER: "${DB_USER:-kairo_dev}"
|
||||||
|
DB_PASSWORD: "${DB_PASSWORD:-dev_password}"
|
||||||
|
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
|
||||||
|
OPENROUTER_MODEL: ${OPENROUTER_MODEL}
|
||||||
|
KAIRO_AI_DEBUG: "${KAIRO_AI_DEBUG:-}"
|
||||||
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
|
SMTP_USER: ${SMTP_USER}
|
||||||
|
SMTP_PASS: ${SMTP_PASS}
|
||||||
|
SMTP_FROM: ${SMTP_FROM}
|
||||||
|
APP_URL: "${APP_URL:-https://dev.kairo.jinkendo.de}"
|
||||||
|
ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.kairo.jinkendo.de,http://192.168.2.49:3097}"
|
||||||
|
ENVIRONMENT: "${ENVIRONMENT:-development}"
|
||||||
|
CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}"
|
||||||
|
MEDIAWIKI_API_URL: "${MEDIAWIKI_API_URL:-https://karatetrainer.net/api.php}"
|
||||||
|
MEDIAWIKI_USER: "${MEDIAWIKI_USER:-Jinkendo}"
|
||||||
|
MEDIAWIKI_PASSWORD: "${MEDIAWIKI_PASSWORD:-CHANGE_ME}"
|
||||||
|
MEDIAWIKI_CATEGORY_EXERCISES: "${MEDIAWIKI_CATEGORY_EXERCISES:-Übungen}"
|
||||||
|
MEDIAWIKI_CATEGORY_SKILLS: "${MEDIAWIKI_CATEGORY_SKILLS:-Fähigkeitsbeschreibung}"
|
||||||
|
MEDIAWIKI_CATEGORY_METHODS: "${MEDIAWIKI_CATEGORY_METHODS:-Methodenbeschreibung}"
|
||||||
|
MEDIAWIKI_CATEGORY_MODELS: "${MEDIAWIKI_CATEGORY_MODELS:-Reifegradmodelle}"
|
||||||
|
MEDIA_ROOT: "${MEDIA_ROOT:-/app/media}"
|
||||||
|
volumes:
|
||||||
|
- ${KAIRO_MEDIA_HOST:-/kairo-media/dev}:${MEDIA_ROOT:-/app/media}
|
||||||
|
ports:
|
||||||
|
- "8097:8000"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- dev-kairo-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
VITE_API_URL: ""
|
||||||
|
ports:
|
||||||
|
- "3097:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- dev-kairo-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
dev-kairo-db-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dev-kairo-network:
|
||||||
|
driver: bridge
|
||||||
82
docker-compose.yml
Normal file
82
docker-compose.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: kairo-db-prod
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${DB_NAME:-kairo}"
|
||||||
|
POSTGRES_USER: "${DB_USER:-kairo_user}"
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- kairo-db-data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:5436:5432"
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- kairo-network
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: kairo-api
|
||||||
|
environment:
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: kairo
|
||||||
|
DB_USER: kairo_user
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
|
||||||
|
OPENROUTER_MODEL: ${OPENROUTER_MODEL}
|
||||||
|
KAIRO_AI_DEBUG: "${KAIRO_AI_DEBUG:-}"
|
||||||
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
|
SMTP_USER: ${SMTP_USER}
|
||||||
|
SMTP_PASS: ${SMTP_PASS}
|
||||||
|
SMTP_FROM: ${SMTP_FROM}
|
||||||
|
SMTP_SSL: ${SMTP_SSL:-}
|
||||||
|
SMTP_STARTTLS: ${SMTP_STARTTLS:-}
|
||||||
|
AUTO_ADMIN_FIRST_USER: "${AUTO_ADMIN_FIRST_USER:-true}"
|
||||||
|
ADMIN_BOOTSTRAP_EMAILS: "${ADMIN_BOOTSTRAP_EMAILS:-}"
|
||||||
|
APP_URL: "${APP_URL:-https://kairo.jinkendo.de}"
|
||||||
|
ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://kairo.jinkendo.de}"
|
||||||
|
ENVIRONMENT: "${ENVIRONMENT:-production}"
|
||||||
|
CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}"
|
||||||
|
MEDIAWIKI_API_URL: "${MEDIAWIKI_API_URL:-https://karatetrainer.net/api.php}"
|
||||||
|
MEDIAWIKI_USER: "${MEDIAWIKI_USER:-}"
|
||||||
|
MEDIAWIKI_PASSWORD: "${MEDIAWIKI_PASSWORD:-}"
|
||||||
|
MEDIAWIKI_CATEGORY_EXERCISES: "${MEDIAWIKI_CATEGORY_EXERCISES:-Übungen}"
|
||||||
|
MEDIAWIKI_CATEGORY_SKILLS: "${MEDIAWIKI_CATEGORY_SKILLS:-Fähigkeitsbeschreibung}"
|
||||||
|
MEDIAWIKI_CATEGORY_METHODS: "${MEDIAWIKI_CATEGORY_METHODS:-Methodenbeschreibung}"
|
||||||
|
MEDIAWIKI_CATEGORY_MODELS: "${MEDIAWIKI_CATEGORY_MODELS:-Reifegradmodelle}"
|
||||||
|
MEDIA_ROOT: "${MEDIA_ROOT:-/app/media}"
|
||||||
|
volumes:
|
||||||
|
- ${KAIRO_MEDIA_HOST:-/kairo-media}:${MEDIA_ROOT:-/app/media}
|
||||||
|
ports:
|
||||||
|
- "8004:8000"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- kairo-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
VITE_API_URL: ""
|
||||||
|
container_name: kairo-ui
|
||||||
|
ports:
|
||||||
|
- "3004:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- kairo-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
kairo-db-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
kairo-network:
|
||||||
|
driver: bridge
|
||||||
127
docs/DEPLOYMENT.md
Normal file
127
docs/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
# Deployment – Kairo Jinkendo
|
||||||
|
|
||||||
|
**Stand:** 2026-07-04
|
||||||
|
**Server:** Raspberry Pi 5 (`192.168.2.49`) — gleicher Host wie Shinkan/Mitai
|
||||||
|
**Runner:** Gitea Actions (`/home/lars/gitea-runner/`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Port- und Pfad-Übersicht
|
||||||
|
|
||||||
|
| | Production | Development |
|
||||||
|
|---|------------|-------------|
|
||||||
|
| **Git-Branch** | `main` | `develop` |
|
||||||
|
| **Server-Verzeichnis** | `/home/lars/docker/kairo` | `/home/lars/docker/kairo-dev` |
|
||||||
|
| **Frontend-Port** | 3004 | 3097 |
|
||||||
|
| **Backend-Port** | 8004 | 8097 |
|
||||||
|
| **PostgreSQL (localhost)** | 5436 | 5437 |
|
||||||
|
| **Domain** | kairo.jinkendo.de | dev.kairo.jinkendo.de |
|
||||||
|
| **Medien-Host-Pfad** | `/kairo-media` | `/kairo-media/dev` |
|
||||||
|
|
||||||
|
**Familien-Referenz (bereits belegt):**
|
||||||
|
|
||||||
|
| App | Prod UI/API | Dev UI/API |
|
||||||
|
|-----|-------------|------------|
|
||||||
|
| Mitai | 3002 / 8002 | 3099 / 8099 |
|
||||||
|
| Shinkan | 3003 / 8003 | 3098 / 8098 |
|
||||||
|
| **Kairo** | **3004 / 8004** | **3097 / 8097** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Einmalige Server-Einrichtung
|
||||||
|
|
||||||
|
Auf dem Pi als User `lars` ausführen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verzeichnisse anlegen
|
||||||
|
mkdir -p /home/lars/docker/kairo
|
||||||
|
mkdir -p /home/lars/docker/kairo-dev
|
||||||
|
mkdir -p /kairo-media /kairo-media/dev
|
||||||
|
|
||||||
|
# Production
|
||||||
|
cd /home/lars/docker/kairo
|
||||||
|
git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git .
|
||||||
|
git checkout main
|
||||||
|
cp .env.example .env
|
||||||
|
# .env bearbeiten (DB_PASSWORD, SMTP, OPENROUTER, …)
|
||||||
|
|
||||||
|
# Development
|
||||||
|
cd /home/lars/docker/kairo-dev
|
||||||
|
git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git .
|
||||||
|
git checkout develop
|
||||||
|
cp .env.example .env
|
||||||
|
# .env mit Dev-Werten bearbeiten (siehe Kommentare in .env.example)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Hinweis:** Erstes `docker compose up` funktioniert erst, wenn `backend/` und `frontend/` mit Dockerfiles vorhanden sind.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reverse Proxy (Synology / Fritz!Box)
|
||||||
|
|
||||||
|
Analog zu Shinkan — neue Hostnamen im Proxy eintragen:
|
||||||
|
|
||||||
|
| Hostname | Ziel (Pi) |
|
||||||
|
|----------|-----------|
|
||||||
|
| `kairo.jinkendo.de` | `http://192.168.2.49:3004` |
|
||||||
|
| `dev.kairo.jinkendo.de` | `http://192.168.2.49:3097` |
|
||||||
|
|
||||||
|
SSL/TLS wie bei den Schwester-Apps (Let's Encrypt über Synology).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gitea Actions
|
||||||
|
|
||||||
|
Workflows unter `.gitea/workflows/`:
|
||||||
|
|
||||||
|
| Workflow | Trigger | Aktion |
|
||||||
|
|----------|---------|--------|
|
||||||
|
| `deploy-dev.yml` | Push auf `develop` | Build + Deploy nach `kairo-dev/` |
|
||||||
|
| `deploy-prod.yml` | Push auf `main` | Build + Deploy nach `kairo/` |
|
||||||
|
| `test.yml` | PR/Push `develop`, nach Deploy | pytest, Lint, Frontend-Build, k6, Playwright |
|
||||||
|
|
||||||
|
Health-Checks nach Deploy:
|
||||||
|
|
||||||
|
- Dev: `curl http://localhost:8097/api/version` und `http://localhost:3097/api/version`
|
||||||
|
- Prod: `curl http://localhost:8004/api/version` und `http://localhost:3004/api/version`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gitea Secrets (für E2E-Tests)
|
||||||
|
|
||||||
|
In Gitea unter Repository → Settings → Actions → Secrets (analog Shinkan):
|
||||||
|
|
||||||
|
| Secret | Verwendung |
|
||||||
|
|--------|------------|
|
||||||
|
| `E2E_DEV_TEST_EMAIL` | Playwright Dev-Login |
|
||||||
|
| `E2E_DEV_TEST_PASSWORD` | Playwright Dev-Login |
|
||||||
|
| `E2E_PROD_TEST_EMAIL` | Playwright Prod-Login |
|
||||||
|
| `E2E_PROD_TEST_PASSWORD` | Playwright Prod-Login |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manuelles Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
cd /home/lars/docker/kairo-dev
|
||||||
|
git fetch origin develop && git reset --hard origin/develop
|
||||||
|
docker compose -f docker-compose.dev-env.yml build --no-cache
|
||||||
|
docker compose -f docker-compose.dev-env.yml up -d
|
||||||
|
|
||||||
|
# Production
|
||||||
|
cd /home/lars/docker/kairo
|
||||||
|
git fetch origin main && git reset --hard origin/main
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nächster Schritt (App-Code)
|
||||||
|
|
||||||
|
1. `backend/` mit FastAPI-Skeleton (main.py, Dockerfile, migrations/)
|
||||||
|
2. `frontend/` mit React/Vite-Skeleton (Dockerfile, nginx.conf)
|
||||||
|
3. Erster Push auf `develop` → Auto-Deploy Dev
|
||||||
|
4. Reverse-Proxy-Einträge für Domains aktivieren
|
||||||
|
5. Gitea E2E-Secrets setzen
|
||||||
13
frontend/README.md
Normal file
13
frontend/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Frontend
|
||||||
|
|
||||||
|
React + Vite SPA — noch anzulegen.
|
||||||
|
|
||||||
|
Erwartete Struktur (analog shinkan-jinkendo):
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/
|
||||||
|
├── Dockerfile
|
||||||
|
├── nginx.conf
|
||||||
|
├── package.json
|
||||||
|
└── src/
|
||||||
|
```
|
||||||
11
package.json
Normal file
11
package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "kairo-jinkendo",
|
||||||
|
"private": true,
|
||||||
|
"description": "Workspace-Metadaten für E2E (Playwright). Frontend: frontend/",
|
||||||
|
"scripts": {
|
||||||
|
"test:e2e": "playwright test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.49.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
playwright.config.js
Normal file
19
playwright.config.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// CI: PLAYWRIGHT_BASE_URL = öffentliche Dev-/Prod-URL (HTTPS über Reverse Proxy), nicht localhost
|
||||||
|
// Lokal: export PLAYWRIGHT_BASE_URL=http://192.168.x.x:3097
|
||||||
|
|
||||||
|
const rawBase =
|
||||||
|
process.env.PLAYWRIGHT_BASE_URL ||
|
||||||
|
process.env.BASE_URL ||
|
||||||
|
'http://127.0.0.1:3097';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testDir: './tests',
|
||||||
|
timeout: 30000,
|
||||||
|
use: {
|
||||||
|
headless: true,
|
||||||
|
viewport: { width: 390, height: 844 },
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
baseURL: rawBase.replace(/\/$/, ''),
|
||||||
|
},
|
||||||
|
reporter: 'list',
|
||||||
|
};
|
||||||
16
scripts/load/README.md
Normal file
16
scripts/load/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# k6 Load-Tests (Phase 0)
|
||||||
|
|
||||||
|
Parallele `/health`-Requests gegen Dev oder Prod.
|
||||||
|
|
||||||
|
## Lokal / CI
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:BASE_URL="https://dev.kairo.jinkendo.de"
|
||||||
|
k6 run scripts/load/k6-health-baseline.js
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE_URL=https://dev.kairo.jinkendo.de k6 run scripts/load/k6-health-baseline.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Im Gitea-Workflow `test.yml` (Job `k6-health-baseline`) wird k6 automatisch installiert und ausgeführt.
|
||||||
32
scripts/load/k6-health-baseline.js
Normal file
32
scripts/load/k6-health-baseline.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Phase-0-Baseline: parallele GET /health (kein Auth).
|
||||||
|
* BASE_URL optional, z. B. https://dev.kairo.jinkendo.de
|
||||||
|
*/
|
||||||
|
import http from 'k6/http'
|
||||||
|
import { check } from 'k6'
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
scenarios: {
|
||||||
|
health: {
|
||||||
|
executor: 'constant-vus',
|
||||||
|
vus: 10,
|
||||||
|
duration: '30s',
|
||||||
|
gracefulStop: '5s',
|
||||||
|
tags: { scenario: 'health' },
|
||||||
|
exec: 'health',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.05'],
|
||||||
|
'http_req_duration{scenario:health}': ['p(95)<3000'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE = (__ENV.BASE_URL || 'https://dev.kairo.jinkendo.de').replace(/\/$/, '')
|
||||||
|
|
||||||
|
export function health() {
|
||||||
|
const res = http.get(`${BASE}/health`, { tags: { scenario: 'health' } })
|
||||||
|
check(res, {
|
||||||
|
'health 2xx': (r) => r.status >= 200 && r.status < 300,
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user