diff --git a/.env.coolify.example b/.env.coolify.example new file mode 100644 index 0000000..ca87373 --- /dev/null +++ b/.env.coolify.example @@ -0,0 +1,72 @@ +# ========================================================= +# BreakPilot Lehrer — Coolify Environment Variables +# ========================================================= +# Copy these into Coolify's environment variable UI +# for the breakpilot-lehrer Docker Compose resource. +# ========================================================= + +# --- Database (shared with Core) --- +POSTGRES_USER=breakpilot +POSTGRES_PASSWORD=CHANGE_ME_SAME_AS_CORE +POSTGRES_DB=breakpilot_db + +# --- Security --- +JWT_SECRET=CHANGE_ME_SAME_AS_CORE + +# --- MinIO (from Core) --- +MINIO_ROOT_USER=breakpilot +MINIO_ROOT_PASSWORD=CHANGE_ME_SAME_AS_CORE +MINIO_BUCKET=breakpilot-rag + +# --- Session --- +SESSION_TTL_HOURS=24 + +# --- SMTP (Real mail server) --- +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USERNAME=noreply@breakpilot.ai +SMTP_PASSWORD=CHANGE_ME_SMTP_PASSWORD +SMTP_FROM_NAME=BreakPilot +SMTP_FROM_ADDR=noreply@breakpilot.ai + +# --- LLM / Ollama (optional) --- +OLLAMA_BASE_URL= +OLLAMA_URL= +OLLAMA_ENABLED=false +OLLAMA_DEFAULT_MODEL= +OLLAMA_VISION_MODEL= +OLLAMA_CORRECTION_MODEL= +OLLAMA_TIMEOUT=120 + +# --- Anthropic (optional) --- +ANTHROPIC_API_KEY= + +# --- vast.ai GPU (optional) --- +VAST_API_KEY= +VAST_INSTANCE_ID= + +# --- Game Settings --- +GAME_USE_DATABASE=true +GAME_REQUIRE_AUTH=true +GAME_REQUIRE_BILLING=true +GAME_LLM_MODEL= + +# --- Frontend URLs (build args) --- +NEXT_PUBLIC_API_URL=https://api-lehrer.breakpilot.ai +NEXT_PUBLIC_KLAUSUR_SERVICE_URL=https://klausur.breakpilot.ai +NEXT_PUBLIC_VOICE_SERVICE_URL=wss://voice.breakpilot.ai +NEXT_PUBLIC_BILLING_API_URL=https://api-core.breakpilot.ai +NEXT_PUBLIC_APP_URL=https://app.breakpilot.ai +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= + +# --- Edu Search --- +EDU_SEARCH_URL= +EDU_SEARCH_API_KEY= +OPENSEARCH_PASSWORD=CHANGE_ME_OPENSEARCH_PASSWORD + +# --- Misc --- +CONTROL_API_KEY= +ALERTS_AGENT_ENABLED=false +PADDLEOCR_SERVICE_URL= +TROCR_SERVICE_URL= +CAMUNDA_URL= diff --git a/.gitea/workflows/deploy-coolify.yml b/.gitea/workflows/deploy-coolify.yml new file mode 100644 index 0000000..461ad38 --- /dev/null +++ b/.gitea/workflows/deploy-coolify.yml @@ -0,0 +1,32 @@ +name: Deploy to Coolify + +on: + push: + branches: + - coolify + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Wait for Core deployment + run: | + echo "Waiting 30s for Core services to stabilize..." + sleep 30 + + - name: Deploy via Coolify API + run: | + echo "Deploying breakpilot-lehrer to Coolify..." + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST \ + -H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{"uuid": "${{ secrets.COOLIFY_RESOURCE_UUID }}", "force_rebuild": true}' \ + "${{ secrets.COOLIFY_BASE_URL }}/api/v1/deploy") + + echo "HTTP Status: $HTTP_STATUS" + if [ "$HTTP_STATUS" -ne 200 ] && [ "$HTTP_STATUS" -ne 201 ]; then + echo "Deployment failed with status $HTTP_STATUS" + exit 1 + fi + echo "Deployment triggered successfully!" diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml new file mode 100644 index 0000000..99ede3d --- /dev/null +++ b/docker-compose.coolify.yml @@ -0,0 +1,321 @@ +# ========================================================= +# BreakPilot Lehrer — KI-Lehrerplattform (Coolify) +# ========================================================= +# Requires: breakpilot-core must be running +# Deployed via Coolify. SSL termination handled by Traefik. +# ========================================================= + +networks: + breakpilot-network: + external: true + name: breakpilot-network + +volumes: + klausur_uploads: + eh_uploads: + ocr_labeling: + paddle_models: + lehrer_backend_data: + opensearch_data: + +services: + + # ========================================================= + # FRONTEND + # ========================================================= + admin-lehrer: + build: + context: ./admin-lehrer + dockerfile: Dockerfile + args: + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api-lehrer.breakpilot.ai} + NEXT_PUBLIC_OLD_ADMIN_URL: ${NEXT_PUBLIC_OLD_ADMIN_URL:-} + NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} + NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} + container_name: bp-lehrer-admin + expose: + - "3000" + volumes: + - lehrer_backend_data:/app/data + environment: + NODE_ENV: production + BACKEND_URL: http://backend-lehrer:8001 + CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 + KLAUSUR_SERVICE_URL: http://klausur-service:8086 + OLLAMA_URL: ${OLLAMA_URL:-} + depends_on: + backend-lehrer: + condition: service_started + labels: + - "traefik.enable=true" + - "traefik.http.routers.admin-lehrer.rule=Host(`admin-lehrer.breakpilot.ai`)" + - "traefik.http.routers.admin-lehrer.entrypoints=https" + - "traefik.http.routers.admin-lehrer.tls=true" + - "traefik.http.routers.admin-lehrer.tls.certresolver=letsencrypt" + - "traefik.http.services.admin-lehrer.loadbalancer.server.port=3000" + restart: unless-stopped + networks: + - breakpilot-network + + studio-v2: + build: + context: ./studio-v2 + dockerfile: Dockerfile + args: + NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} + NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} + container_name: bp-lehrer-studio-v2 + expose: + - "3001" + environment: + NODE_ENV: production + BACKEND_URL: http://backend-lehrer:8001 + depends_on: + - backend-lehrer + labels: + - "traefik.enable=true" + - "traefik.http.routers.studio.rule=Host(`app.breakpilot.ai`)" + - "traefik.http.routers.studio.entrypoints=https" + - "traefik.http.routers.studio.tls=true" + - "traefik.http.routers.studio.tls.certresolver=letsencrypt" + - "traefik.http.services.studio.loadbalancer.server.port=3001" + restart: unless-stopped + networks: + - breakpilot-network + + website: + build: + context: ./website + dockerfile: Dockerfile + args: + NEXT_PUBLIC_BILLING_API_URL: ${NEXT_PUBLIC_BILLING_API_URL:-https://api-core.breakpilot.ai} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-https://app.breakpilot.ai} + NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://klausur.breakpilot.ai} + NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://voice.breakpilot.ai} + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-} + container_name: bp-lehrer-website + expose: + - "3000" + environment: + NODE_ENV: production + VAST_API_KEY: ${VAST_API_KEY:-} + CONTROL_API_KEY: ${CONTROL_API_KEY:-} + BACKEND_URL: http://backend-lehrer:8001 + CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 + EDU_SEARCH_URL: ${EDU_SEARCH_URL:-} + EDU_SEARCH_API_KEY: ${EDU_SEARCH_API_KEY:-} + depends_on: + - backend-lehrer + labels: + - "traefik.enable=true" + - "traefik.http.routers.website.rule=Host(`www.breakpilot.ai`)" + - "traefik.http.routers.website.entrypoints=https" + - "traefik.http.routers.website.tls=true" + - "traefik.http.routers.website.tls.certresolver=letsencrypt" + - "traefik.http.services.website.loadbalancer.server.port=3000" + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # BACKEND + # ========================================================= + backend-lehrer: + build: + context: ./backend-lehrer + dockerfile: Dockerfile + container_name: bp-lehrer-backend + user: "0:0" + expose: + - "8001" + volumes: + - lehrer_backend_data:/app/data + environment: + PORT: 8001 + DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB}?options=-csearch_path%3Dlehrer,core,public + JWT_SECRET: ${JWT_SECRET} + ENVIRONMENT: production + CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 + KLAUSUR_SERVICE_URL: http://klausur-service:8086 + TROCR_SERVICE_URL: ${TROCR_SERVICE_URL:-} + CAMUNDA_URL: ${CAMUNDA_URL:-} + VALKEY_URL: redis://bp-core-valkey:6379/0 + SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + DEBUG: "false" + ALERTS_AGENT_ENABLED: ${ALERTS_AGENT_ENABLED:-false} + VAST_API_KEY: ${VAST_API_KEY:-} + VAST_INSTANCE_ID: ${VAST_INSTANCE_ID:-} + CONTROL_API_KEY: ${CONTROL_API_KEY:-} + OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-} + OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false} + OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-} + OLLAMA_VISION_MODEL: ${OLLAMA_VISION_MODEL:-} + OLLAMA_CORRECTION_MODEL: ${OLLAMA_CORRECTION_MODEL:-} + OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-120} + GAME_USE_DATABASE: ${GAME_USE_DATABASE:-true} + GAME_REQUIRE_AUTH: ${GAME_REQUIRE_AUTH:-true} + GAME_REQUIRE_BILLING: ${GAME_REQUIRE_BILLING:-true} + GAME_LLM_MODEL: ${GAME_LLM_MODEL:-} + SMTP_HOST: ${SMTP_HOST} + SMTP_PORT: ${SMTP_PORT:-587} + SMTP_USERNAME: ${SMTP_USERNAME} + SMTP_PASSWORD: ${SMTP_PASSWORD} + SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot} + SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-noreply@breakpilot.ai} + RAG_SERVICE_URL: http://bp-core-rag-service:8097 + labels: + - "traefik.enable=true" + - "traefik.http.routers.backend-lehrer.rule=Host(`api-lehrer.breakpilot.ai`)" + - "traefik.http.routers.backend-lehrer.entrypoints=https" + - "traefik.http.routers.backend-lehrer.tls=true" + - "traefik.http.routers.backend-lehrer.tls.certresolver=letsencrypt" + - "traefik.http.services.backend-lehrer.loadbalancer.server.port=8001" + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # MICROSERVICES + # ========================================================= + klausur-service: + build: + context: ./klausur-service + dockerfile: Dockerfile + container_name: bp-lehrer-klausur-service + expose: + - "8086" + volumes: + - klausur_uploads:/app/uploads + - eh_uploads:/app/eh-uploads + - ocr_labeling:/app/ocr-labeling + - paddle_models:/root/.paddlex + environment: + JWT_SECRET: ${JWT_SECRET} + BACKEND_URL: http://backend-lehrer:8001 + SCHOOL_SERVICE_URL: http://school-service:8084 + ENVIRONMENT: production + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + EMBEDDING_SERVICE_URL: http://bp-core-embedding-service:8087 + QDRANT_URL: http://bp-core-qdrant:6333 + MINIO_ENDPOINT: bp-core-minio:9000 + MINIO_ACCESS_KEY: ${MINIO_ROOT_USER} + MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD} + MINIO_BUCKET: ${MINIO_BUCKET:-breakpilot-rag} + MINIO_SECURE: "false" + PADDLEOCR_SERVICE_URL: ${PADDLEOCR_SERVICE_URL:-} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-} + OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false} + OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-} + OLLAMA_VISION_MODEL: ${OLLAMA_VISION_MODEL:-} + OLLAMA_CORRECTION_MODEL: ${OLLAMA_CORRECTION_MODEL:-} + RAG_SERVICE_URL: http://bp-core-rag-service:8097 + depends_on: + school-service: + condition: service_started + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8086/health"] + interval: 30s + timeout: 30s + retries: 3 + start_period: 10s + labels: + - "traefik.enable=true" + - "traefik.http.routers.klausur.rule=Host(`klausur.breakpilot.ai`)" + - "traefik.http.routers.klausur.entrypoints=https" + - "traefik.http.routers.klausur.tls=true" + - "traefik.http.routers.klausur.tls.certresolver=letsencrypt" + - "traefik.http.services.klausur.loadbalancer.server.port=8086" + restart: unless-stopped + networks: + - breakpilot-network + + school-service: + build: + context: ./school-service + dockerfile: Dockerfile + container_name: bp-lehrer-school-service + expose: + - "8084" + environment: + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + JWT_SECRET: ${JWT_SECRET} + PORT: 8084 + ENVIRONMENT: production + ALLOWED_ORIGINS: "*" + LLM_GATEWAY_URL: http://backend-lehrer:8001/llm + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # EDU SEARCH + # ========================================================= + opensearch: + image: opensearchproject/opensearch:2.11.1 + container_name: bp-lehrer-opensearch + environment: + - cluster.name=edu-search-cluster + - node.name=opensearch-node1 + - discovery.type=single-node + - bootstrap.memory_lock=true + - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" + - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD:-Admin123!} + - plugins.security.disabled=true + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + volumes: + - opensearch_data:/usr/share/opensearch/data + healthcheck: + test: ["CMD-SHELL", "curl -s http://localhost:9200 >/dev/null || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + restart: unless-stopped + networks: + - breakpilot-network + + edu-search-service: + build: + context: ./edu-search-service + dockerfile: Dockerfile + container_name: bp-lehrer-edu-search + expose: + - "8088" + environment: + PORT: 8088 + OPENSEARCH_URL: http://opensearch:9200 + OPENSEARCH_USERNAME: admin + OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD:-Admin123!} + INDEX_NAME: bp_documents_v1 + EDU_SEARCH_API_KEY: ${EDU_SEARCH_API_KEY:-} + USER_AGENT: "BreakpilotEduCrawler/1.0 (+contact: security@breakpilot.com)" + RATE_LIMIT_PER_SEC: "0.2" + MAX_DEPTH: "4" + MAX_PAGES_PER_RUN: "500" + DB_HOST: bp-core-postgres + DB_PORT: "5432" + DB_USER: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_NAME: ${POSTGRES_DB} + DB_SSLMODE: disable + STAFF_CRAWLER_EMAIL: crawler@breakpilot.de + depends_on: + opensearch: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8088/v1/health"] + interval: 30s + timeout: 3s + start_period: 10s + retries: 3 + restart: unless-stopped + networks: + - breakpilot-network