feat(ci): Add Hetzner deployment for Core services
All checks were successful
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
CI / deploy-hetzner (push) Successful in 3m29s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
All checks were successful
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 34s
CI / deploy-hetzner (push) Successful in 3m29s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
- docker-compose.hetzner.yml: Override for x86_64 (platform, ports, Ollama container for CPU embeddings, mailpit dummy, disabled services) - CI: deploy-hetzner job using helper-container pattern - Services: postgres, valkey, qdrant, ollama, backend-core, consent-service, rag-service, embedding-service, health-aggregator Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -138,3 +138,116 @@ jobs:
|
|||||||
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
||||||
pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-asyncio
|
pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-asyncio
|
||||||
python -m pytest tests/bqas/ -v --tb=short || true
|
python -m pytest tests/bqas/ -v --tb=short || true
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Build & Deploy auf Hetzner (nur main, kein PR)
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
deploy-hetzner:
|
||||||
|
runs-on: docker
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs:
|
||||||
|
- test-go-consent
|
||||||
|
container: docker:27-cli
|
||||||
|
steps:
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
DEPLOY_DIR="/opt/breakpilot-core"
|
||||||
|
COMPOSE_FILES="-f docker-compose.yml -f docker-compose.hetzner.yml"
|
||||||
|
COMMIT_SHA="${GITHUB_SHA:-unknown}"
|
||||||
|
SHORT_SHA="${COMMIT_SHA:0:8}"
|
||||||
|
REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
|
||||||
|
|
||||||
|
# Services die deployed werden
|
||||||
|
SERVICES="postgres valkey qdrant minio ollama mailpit embedding-service rag-service backend-core consent-service health-aggregator"
|
||||||
|
|
||||||
|
echo "=== BreakPilot Core Deploy ==="
|
||||||
|
echo "Commit: ${SHORT_SHA}"
|
||||||
|
echo "Deploy Dir: ${DEPLOY_DIR}"
|
||||||
|
echo "Services: ${SERVICES}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Repo auf dem Host erstellen/aktualisieren via Helper-Container
|
||||||
|
echo "=== Updating code on host ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v "${DEPLOY_DIR}:${DEPLOY_DIR}" \
|
||||||
|
--entrypoint sh \
|
||||||
|
alpine/git:latest \
|
||||||
|
-c "
|
||||||
|
if [ ! -d '${DEPLOY_DIR}/.git' ]; then
|
||||||
|
echo 'Erstmaliges Klonen nach ${DEPLOY_DIR}...'
|
||||||
|
git clone '${REPO_URL}' '${DEPLOY_DIR}'
|
||||||
|
else
|
||||||
|
cd '${DEPLOY_DIR}'
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
echo "Code aktualisiert auf ${SHORT_SHA}"
|
||||||
|
|
||||||
|
# 2. .env sicherstellen
|
||||||
|
docker run --rm -v "${DEPLOY_DIR}:${DEPLOY_DIR}" alpine \
|
||||||
|
sh -c "
|
||||||
|
if [ ! -f '${DEPLOY_DIR}/.env' ]; then
|
||||||
|
echo 'WARNUNG: ${DEPLOY_DIR}/.env fehlt!'
|
||||||
|
echo 'Erstelle .env aus .env.example mit Defaults...'
|
||||||
|
if [ -f '${DEPLOY_DIR}/.env.example' ]; then
|
||||||
|
cp '${DEPLOY_DIR}/.env.example' '${DEPLOY_DIR}/.env'
|
||||||
|
echo '.env aus .env.example erstellt'
|
||||||
|
else
|
||||||
|
echo 'Kein .env.example gefunden — Services starten mit Defaults'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo '.env vorhanden'
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
|
||||||
|
# 3. Build + Deploy via Helper-Container
|
||||||
|
echo ""
|
||||||
|
echo "=== Building + Deploying ==="
|
||||||
|
docker run --rm \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v "${DEPLOY_DIR}:${DEPLOY_DIR}" \
|
||||||
|
-w "${DEPLOY_DIR}" \
|
||||||
|
docker:27-cli \
|
||||||
|
sh -c "
|
||||||
|
COMPOSE_FILES='-f docker-compose.yml -f docker-compose.hetzner.yml'
|
||||||
|
SERVICES='postgres valkey qdrant minio ollama mailpit embedding-service rag-service backend-core consent-service health-aggregator'
|
||||||
|
|
||||||
|
echo '=== Building Docker Images ==='
|
||||||
|
docker compose \${COMPOSE_FILES} build --parallel \
|
||||||
|
backend-core consent-service rag-service embedding-service health-aggregator
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo '=== Starting infrastructure ==='
|
||||||
|
docker compose \${COMPOSE_FILES} up -d postgres valkey qdrant minio mailpit
|
||||||
|
|
||||||
|
echo 'Warte auf DB + Cache...'
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo '=== Starting Ollama + pulling bge-m3 ==='
|
||||||
|
docker compose \${COMPOSE_FILES} up -d ollama
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# bge-m3 Modell pullen (nur beim ersten Mal ~670MB)
|
||||||
|
echo 'Pulling bge-m3 model (falls noch nicht vorhanden)...'
|
||||||
|
docker exec bp-core-ollama ollama pull bge-m3 2>&1 || echo 'WARNUNG: bge-m3 pull fehlgeschlagen (wird spaeter nachgeholt)'
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo '=== Starting application services ==='
|
||||||
|
docker compose \${COMPOSE_FILES} up -d \
|
||||||
|
embedding-service rag-service backend-core consent-service health-aggregator
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo '=== Health Checks ==='
|
||||||
|
sleep 15
|
||||||
|
for svc in bp-core-postgres bp-core-valkey bp-core-qdrant bp-core-ollama bp-core-embedding-service bp-core-rag-service bp-core-backend bp-core-consent-service bp-core-health; do
|
||||||
|
STATUS=\$(docker inspect --format='{{.State.Status}}' \"\${svc}\" 2>/dev/null || echo 'not found')
|
||||||
|
echo \"\${svc}: \${STATUS}\"
|
||||||
|
done
|
||||||
|
"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deploy abgeschlossen: ${SHORT_SHA} ==="
|
||||||
|
|||||||
165
docker-compose.hetzner.yml
Normal file
165
docker-compose.hetzner.yml
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# =========================================================
|
||||||
|
# BreakPilot Core — Hetzner Override (x86_64)
|
||||||
|
# =========================================================
|
||||||
|
# Verwendung:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.hetzner.yml up -d \
|
||||||
|
# postgres valkey qdrant ollama embedding-service rag-service \
|
||||||
|
# backend-core consent-service health-aggregator
|
||||||
|
#
|
||||||
|
# Aenderungen gegenueber Basis (docker-compose.yml):
|
||||||
|
# - platform: linux/amd64 (statt arm64)
|
||||||
|
# - Ollama Container fuer CPU-Embeddings (bge-m3)
|
||||||
|
# - Mailpit ersetzt durch Dummy (kein Mail-Dev-Server noetig)
|
||||||
|
# - Vault, Nginx, Gitea etc. deaktiviert via Profile
|
||||||
|
# - Netzwerk: auto-create (nicht external)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
networks:
|
||||||
|
breakpilot-network:
|
||||||
|
external: false
|
||||||
|
name: breakpilot-network
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# NEUE SERVICES
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# Ollama fuer Embeddings (CPU-only, bge-m3)
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
container_name: bp-core-ollama
|
||||||
|
platform: linux/amd64
|
||||||
|
volumes:
|
||||||
|
- ollama_models:/root/.ollama
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:11434/api/tags || exit 1"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- breakpilot-network
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# PLATFORM OVERRIDES (arm64 → amd64)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
backend-core:
|
||||||
|
platform: linux/amd64
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}?options=-csearch_path%3Dcore,public
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-production}
|
||||||
|
VALKEY_URL: redis://valkey:6379/0
|
||||||
|
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24}
|
||||||
|
CONSENT_SERVICE_URL: http://consent-service:8081
|
||||||
|
USE_VAULT_SECRETS: "false"
|
||||||
|
SMTP_HOST: ${SMTP_HOST:-smtp.example.com}
|
||||||
|
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.app}
|
||||||
|
|
||||||
|
consent-service:
|
||||||
|
platform: linux/amd64
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgres://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
|
||||||
|
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:-your-refresh-secret}
|
||||||
|
PORT: 8081
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-production}
|
||||||
|
ALLOWED_ORIGINS: "*"
|
||||||
|
VALKEY_URL: redis://valkey:6379/0
|
||||||
|
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24}
|
||||||
|
SMTP_HOST: ${SMTP_HOST:-smtp.example.com}
|
||||||
|
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.app}
|
||||||
|
FRONTEND_URL: ${FRONTEND_URL:-https://admin-dev.breakpilot.ai}
|
||||||
|
|
||||||
|
billing-service:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
rag-service:
|
||||||
|
platform: linux/amd64
|
||||||
|
ports:
|
||||||
|
- "8097:8097"
|
||||||
|
environment:
|
||||||
|
PORT: 8097
|
||||||
|
QDRANT_URL: http://qdrant:6333
|
||||||
|
MINIO_ENDPOINT: nbg1.your-objectstorage.com
|
||||||
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-T18RGFVXXG2ZHQ5404TP}
|
||||||
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-KOUU4WO6wh07cQjNgh0IZHkeKQrVfBz6hnIGpNss}
|
||||||
|
MINIO_BUCKET: ${MINIO_BUCKET:-breakpilot-rag}
|
||||||
|
MINIO_SECURE: "true"
|
||||||
|
EMBEDDING_SERVICE_URL: http://embedding-service:8087
|
||||||
|
OLLAMA_URL: http://ollama:11434
|
||||||
|
OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-bge-m3}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
|
||||||
|
ENVIRONMENT: ${ENVIRONMENT:-production}
|
||||||
|
|
||||||
|
embedding-service:
|
||||||
|
platform: linux/amd64
|
||||||
|
ports:
|
||||||
|
- "8087:8087"
|
||||||
|
|
||||||
|
health-aggregator:
|
||||||
|
platform: linux/amd64
|
||||||
|
environment:
|
||||||
|
PORT: 8099
|
||||||
|
CHECK_SERVICES: "postgres:5432,valkey:6379,qdrant:6333,backend-core:8000,rag-service:8097,embedding-service:8087"
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# DUMMY-ERSATZ FUER ABHAENGIGKEITEN
|
||||||
|
# =========================================================
|
||||||
|
# backend-core + consent-service haengen von mailpit ab
|
||||||
|
# (depends_on merged bei compose override, kann nicht entfernt werden)
|
||||||
|
# → Mailpit durch leichtgewichtigen Dummy ersetzen
|
||||||
|
|
||||||
|
mailpit:
|
||||||
|
image: alpine:3.19
|
||||||
|
entrypoint: ["sh", "-c", "echo 'Mailpit dummy on Hetzner' && tail -f /dev/null"]
|
||||||
|
volumes: []
|
||||||
|
ports: []
|
||||||
|
environment: {}
|
||||||
|
|
||||||
|
# minio: rag-service haengt davon ab (depends_on)
|
||||||
|
# Lokal laufen lassen, aber rag-service nutzt externe Hetzner Object Storage
|
||||||
|
# minio bleibt unveraendert (klein, ~50MB RAM)
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# DEAKTIVIERTE SERVICES (via profiles)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
vault:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
vault-init:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
vault-agent:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
gitea:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
gitea-runner:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
night-scheduler:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
admin-core:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
pitch-deck:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
levis-holzbau:
|
||||||
|
profiles: ["disabled"]
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ollama_models:
|
||||||
Reference in New Issue
Block a user