From fe86d763add451acc2e6a5b98781d6bc7a32e940 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 29 Apr 2026 13:33:28 +0200 Subject: [PATCH] refactor: enhance environment configuration and CI workflow for improved flexibility - Updated .env.example to provide clearer instructions and examples for production and development environments. - Refactored docker-compose files to utilize environment variables for database and application settings, improving configurability. - Enhanced .gitea/workflows/test.yml to streamline E2E testing setup, ensuring consistent handling of environment variables across different stages. --- .env.example | 39 +++++++++++-------- .gitea/workflows/test.yml | 79 +++++++++++++++++++++----------------- docker-compose.dev-env.yml | 38 +++++++++--------- docker-compose.yml | 11 +++--- playwright.config.js | 2 +- 5 files changed, 91 insertions(+), 78 deletions(-) diff --git a/.env.example b/.env.example index 60dfe75..cce781b 100644 --- a/.env.example +++ b/.env.example @@ -1,49 +1,56 @@ -# === Deploy (.env auf dem Host) ============================================ -# Kopiere diese Datei nach `.env` im SELBEN Verzeichnis wie `docker-compose.yml` (Pi: ~/docker/shinkan). -# Docker Compose liest `.env` beim Start — dadurch wird z. B. SMTP_HOST=${SMTP_HOST} gefüllt. -# Ist die Datei weg/leer oder steht SMTP_PASS nicht darin → im Container keine SMTP-Daten ([SMTP] nicht konfiguriert). +# === .env neben der Jeweiligen docker-compose.*.yml kopieren ==================== +# Docker Compose ersetzt ${VARIABLE} beim Start. +# +# Pro Umgebung eigene Datei (z. B. ~/docker/shinkan/.env für Prod, +# ~/docker/shinkan-dev/.env für Dev) — dieselben SCHLÜSSEL, unterschiedliche Werte. +# Kein separates DEV_APP_URL vs APP_URL: immer APP_URL, ALLOWED_ORIGINS, DB_*, … -# Database +# ─── Typische Werte PROD (docker-compose.yml) ───────────────────────────────── +# DB_NAME=shinkan +# DB_USER=shinkan_user +# DB_PASSWORD=… +# APP_URL=https://shinkan.jinkendo.de +# ALLOWED_ORIGINS=https://shinkan.jinkendo.de +# ENVIRONMENT=production + +# ─── Typische Werte DEV (docker-compose.dev-env.yml) ───────────────────────── +# DB_NAME=shinkan_dev +# DB_USER=shinkan_dev +# DB_PASSWORD=dev_password +# APP_URL=https://dev.shinkan.jinkendo.de +# ALLOWED_ORIGINS=https://dev.shinkan.jinkendo.de,http://192.168.2.49:3098 +# ENVIRONMENT=development + +# ─── Ab hier: eine ausfüllbare Vorlage (bei uns meist Prod-Defaults) ─────────── DB_HOST=postgres DB_PORT=5432 DB_NAME=shinkan DB_USER=shinkan_user DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD -# OpenRouter (KI - optional) OPENROUTER_API_KEY=your_api_key_here OPENROUTER_MODEL=anthropic/claude-sonnet-4 -# SMTP (E-Mail) SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=noreply@jinkendo.de SMTP_PASS=your_smtp_password SMTP_FROM=noreply@jinkendo.de -# Port 465 oft SSL; dann z. B. SMTP_SSL=true und SMTP_STARTTLS=false SMTP_SSL= SMTP_STARTTLS= -# Bootstrap: erste Registrierung (leere Nutzerliste) erhält Rolle admin; oder feste Admin-Mails: AUTO_ADMIN_FIRST_USER=true ADMIN_BOOTSTRAP_EMAILS= -# App APP_URL=https://shinkan.jinkendo.de -# Kommasepariert (ohne Leerzeichen um die Kommas ist am sichersten). Für Dev mehrere Origins nötig (HTTPS + LAN). ALLOWED_ORIGINS=https://shinkan.jinkendo.de ENVIRONMENT=production -# Nur docker-compose.dev-env.yml (optional): DEV_APP_URL, DEV_ALLOWED_ORIGINS - -# Media Storage MEDIA_DIR=/app/media -# MediaWiki Import (Semantic MediaWiki) MEDIAWIKI_API_URL=https://karatetrainer.net/api.php MEDIAWIKI_USER=Jinkendo MEDIAWIKI_PASSWORD=CHANGE_ME -# Kategorienamen im Wiki (echte Namen von karatetrainer.net) MEDIAWIKI_CATEGORY_EXERCISES=Übungen MEDIAWIKI_CATEGORY_SKILLS=Fähigkeitsbeschreibung MEDIAWIKI_CATEGORY_METHODS=Methodenbeschreibung diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 6df831a..1d42718 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -57,9 +57,11 @@ jobs: playwright-tests: if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest - # Lokal: docker-compose.dev-env + COMPOSE_PROJECT_NAME. Nach „Deploy Production“: HTTPS-Prod (kein Docker). + # Kein zusätzlicher docker-compose im Job: keine zweiten Container/Host-Ports. + # Dev: Tests gegen bereits deployte URL (HTTPS, Reverse-Proxy). Prod: gleicher Ablauf. env: - COMPOSE_PROJECT_NAME: shinkan-e2e-${{ github.run_id }} + # Öffentliche Dev-Basis — muss ALLOWED_ORIGINS / Nginx entsprechen; bei anderer Domain Workflow anpassen. + E2E_TARGET_URL: https://dev.shinkan.jinkendo.de steps: - name: Checkout repository uses: actions/checkout@v4 @@ -69,41 +71,39 @@ jobs: with: node-version: '20' - - name: E2E-Ziel wählen (lokal vs. Production) + - 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://shinkan.jinkendo.de" >> $GITHUB_OUTPUT - echo "→ Playwright gegen Production. Login: Secrets E2E_PROD_TEST_EMAIL / E2E_PROD_TEST_PASSWORD." + echo "→ Prod. Secrets: E2E_PROD_TEST_EMAIL / E2E_PROD_TEST_PASSWORD." else - echo "mode=local" >> $GITHUB_OUTPUT - echo "base_url=http://127.0.0.1:3098" >> $GITHUB_OUTPUT - echo "→ Playwright gegen lokalen Stack (docker-compose.dev-env.yml)." + 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: Start dev stack for E2E (nur lokal) - if: ${{ steps.e2e.outputs.mode == 'local' }} - env: - DEV_ALLOWED_ORIGINS: http://127.0.0.1:3098,http://localhost:3098,http://host.docker.internal:3098,https://dev.shinkan.jinkendo.de + - name: Dev /health abwarten + if: ${{ steps.e2e.outputs.mode == 'dev' }} run: | - docker compose -f docker-compose.dev-env.yml up -d --build - echo "Warte auf Frontend + API (/health) …" + BASE="${{ steps.e2e.outputs.base_url }}" + echo "Warte auf $BASE/health …" for i in $(seq 1 90); do - if curl -sf http://127.0.0.1:3098/health >/dev/null 2>&1; then + if curl -sf "$BASE/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done - echo "Timeout: /health nicht erreichbar" - curl -v http://127.0.0.1:3098/health || true - docker compose -f docker-compose.dev-env.yml logs --tail=120 + echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen." + curl -v "$BASE/health" || true exit 1 - - name: Prod /health abwarten (nach Deploy) + - name: Prod /health abwarten if: ${{ steps.e2e.outputs.mode == 'prod' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" @@ -119,16 +119,21 @@ jobs: curl -v "$BASE/health" || true exit 1 - - name: Seed E2E-User (nur lokaler Stack, frische DB) - if: ${{ steps.e2e.outputs.mode == 'local' }} + - name: Testnutzer registrieren (Dev, nur wenn möglich) + if: ${{ steps.e2e.outputs.mode == 'dev' }} env: - TEST_EMAIL: lars@stommer.com - TEST_PASSWORD: 12345678 + E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }} + E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }} run: | - curl -sf -X POST "http://127.0.0.1:3098/api/auth/register" \ + 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\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASSWORD}\",\"name\":\"Playwright CI\"}" \ - || echo "(Registrierung übersprungen — Nutzer existiert evtl. schon)" + -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: | @@ -136,33 +141,35 @@ jobs: 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 PLAYWRIGHT_BASE_URL="$BASE_URL" 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: Für Prod-E2E Repository-Secrets E2E_PROD_TEST_EMAIL und E2E_PROD_TEST_PASSWORD setzen (Testnutzer mit Login auf Prod)." + echo "Fehler: E2E_PROD_TEST_EMAIL und E2E_PROD_TEST_PASSWORD setzen." exit 1 fi else - export PLAYWRIGHT_BASE_URL="$BASE_URL" - export TEST_EMAIL="lars@stommer.com" - export TEST_PASSWORD="12345678" + 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: Stop dev stack - if: ${{ always() && steps.e2e.outputs.mode == 'local' }} - run: | - docker compose -f docker-compose.dev-env.yml down --remove-orphans - echo "Gestoppt unter Projekt: ${COMPOSE_PROJECT_NAME}" - - name: Upload test screenshots if: failure() uses: actions/upload-artifact@v4 diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 03eb6b0..1b5cc8f 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -1,15 +1,15 @@ version: '3.8' -# Keine festen container_name: Namen sind hostweit eindeutig und kollidieren bei -# erneuten Deploys / anderem Compose-Projektprefix. Compose vergibt z. B. -postgres-1. +# Keine festen container_name — Compose-Namen haben Projektprefix (-postgres-1). +# Gleiche Variablennamen wie docker-compose.yml; andere Werte in einer eigenen .env neben dieser Datei. services: postgres: image: postgres:16-alpine environment: - POSTGRES_DB: shinkan_dev - POSTGRES_USER: shinkan_dev - POSTGRES_PASSWORD: dev_password + POSTGRES_DB: "${DB_NAME:-shinkan_dev}" + POSTGRES_USER: "${DB_USER:-shinkan_dev}" + POSTGRES_PASSWORD: "${DB_PASSWORD:-dev_password}" volumes: - dev-shinkan-db-data:/var/lib/postgresql/data ports: @@ -25,9 +25,9 @@ services: environment: DB_HOST: postgres DB_PORT: 5432 - DB_NAME: shinkan_dev - DB_USER: shinkan_dev - DB_PASSWORD: dev_password + DB_NAME: "${DB_NAME:-shinkan_dev}" + DB_USER: "${DB_USER:-shinkan_dev}" + DB_PASSWORD: "${DB_PASSWORD:-dev_password}" OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} OPENROUTER_MODEL: ${OPENROUTER_MODEL} SMTP_HOST: ${SMTP_HOST} @@ -35,18 +35,16 @@ services: SMTP_USER: ${SMTP_USER} SMTP_PASS: ${SMTP_PASS} SMTP_FROM: ${SMTP_FROM} - # Öffentliche Dev-URL (E-Mail-Links); lokaler Zugriff per IP bleibt über ALLOWED_ORIGINS möglich - APP_URL: "${DEV_APP_URL:-https://dev.shinkan.jinkendo.de}" - # Login/Register vom Browser: HTTPS-Subdomain und optional LAN-IP (Compose überschreibbar per .env) - ALLOWED_ORIGINS: "${DEV_ALLOWED_ORIGINS:-https://dev.shinkan.jinkendo.de,http://192.168.2.49:3098}" - ENVIRONMENT: development - MEDIAWIKI_API_URL: https://karatetrainer.net/api.php - MEDIAWIKI_USER: Jinkendo - MEDIAWIKI_PASSWORD: Jinkendo6970 - MEDIAWIKI_CATEGORY_EXERCISES: Übungen - MEDIAWIKI_CATEGORY_SKILLS: Fähigkeitsbeschreibung - MEDIAWIKI_CATEGORY_METHODS: Methodenbeschreibung - MEDIAWIKI_CATEGORY_MODELS: Reifegradmodelle + APP_URL: "${APP_URL:-https://dev.shinkan.jinkendo.de}" + ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.shinkan.jinkendo.de,http://192.168.2.49:3098}" + ENVIRONMENT: "${ENVIRONMENT:-development}" + 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}" volumes: - dev-shinkan-media:/app/media ports: diff --git a/docker-compose.yml b/docker-compose.yml index f20b602..89fbd47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,8 +5,8 @@ services: image: postgres:16-alpine container_name: shinkan-db-prod environment: - POSTGRES_DB: shinkan - POSTGRES_USER: shinkan_user + POSTGRES_DB: "${DB_NAME:-shinkan}" + POSTGRES_USER: "${DB_USER:-shinkan_user}" POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - shinkan-db-data:/var/lib/postgresql/data @@ -40,9 +40,10 @@ services: # Erste Self-Registration → Admin; oder ADMIN_BOOTSTRAP_EMAILS=mail@…,weitere@… AUTO_ADMIN_FIRST_USER: "${AUTO_ADMIN_FIRST_USER:-true}" ADMIN_BOOTSTRAP_EMAILS: "${ADMIN_BOOTSTRAP_EMAILS:-}" - APP_URL: https://shinkan.jinkendo.de - ALLOWED_ORIGINS: https://shinkan.jinkendo.de - ENVIRONMENT: production + # Werte wie in .env (APP_URL, ALLOWED_ORIGINS, ENVIRONMENT) — keine zweite „Wahrheit“ in YAML + APP_URL: "${APP_URL:-https://shinkan.jinkendo.de}" + ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://shinkan.jinkendo.de}" + ENVIRONMENT: "${ENVIRONMENT:-production}" volumes: - shinkan-media:/app/media ports: diff --git a/playwright.config.js b/playwright.config.js index c8eff35..37bdd62 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,4 +1,4 @@ -// CI: PLAYWRIGHT_BASE_URL — lokal docker dev-env (:3098); nach „Deploy Production“ https://shinkan.jinkendo.de +// CI: PLAYWRIGHT_BASE_URL = öffentliche Dev-/Prod-URL (HTTPS über Reverse Proxy), nicht localhost // Lokal: export PLAYWRIGHT_BASE_URL=http://192.168.x.x:3098 const rawBase =