From 2ec340c64b98c8b24d8e942f7b670069d3a7e87d Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Wed, 25 Feb 2026 10:43:04 +0100 Subject: [PATCH 01/14] feat: add Coolify deployment configuration Add docker-compose.coolify.yml (8 services), .env.coolify.example, and Gitea Action workflow for Coolify API deployment. Removes core-health-check and docs. Adds Traefik labels for *.breakpilot.ai domain routing with Let's Encrypt SSL. Co-Authored-By: Claude Opus 4.6 --- .env.coolify.example | 57 ++++++ .gitea/workflows/deploy-coolify.yml | 32 ++++ docker-compose.coolify.yml | 257 ++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 .env.coolify.example create mode 100644 .gitea/workflows/deploy-coolify.yml create mode 100644 docker-compose.coolify.yml diff --git a/.env.coolify.example b/.env.coolify.example new file mode 100644 index 0000000..f5074de --- /dev/null +++ b/.env.coolify.example @@ -0,0 +1,57 @@ +# ========================================================= +# BreakPilot Compliance — Coolify Environment Variables +# ========================================================= +# Copy these into Coolify's environment variable UI +# for the breakpilot-compliance 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 + +# --- Session --- +SESSION_TTL_HOURS=24 + +# --- SMTP (Real mail server) --- +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USERNAME=compliance@breakpilot.ai +SMTP_PASSWORD=CHANGE_ME_SMTP_PASSWORD +SMTP_FROM_NAME=BreakPilot Compliance +SMTP_FROM_ADDR=compliance@breakpilot.ai + +# --- LLM Configuration --- +COMPLIANCE_LLM_PROVIDER=anthropic +SELF_HOSTED_LLM_URL= +SELF_HOSTED_LLM_MODEL= +COMPLIANCE_LLM_MAX_TOKENS=4096 +COMPLIANCE_LLM_TEMPERATURE=0.3 +COMPLIANCE_LLM_TIMEOUT=120 +ANTHROPIC_API_KEY=CHANGE_ME_ANTHROPIC_KEY +ANTHROPIC_DEFAULT_MODEL=claude-sonnet-4-5-20250929 + +# --- Ollama (optional) --- +OLLAMA_URL= +OLLAMA_DEFAULT_MODEL= +COMPLIANCE_LLM_MODEL= + +# --- LLM Fallback --- +LLM_FALLBACK_PROVIDER= + +# --- PII & Audit --- +PII_REDACTION_ENABLED=true +PII_REDACTION_LEVEL=standard +AUDIT_RETENTION_DAYS=365 +AUDIT_LOG_PROMPTS=true + +# --- Frontend URLs (build args) --- +NEXT_PUBLIC_API_URL=https://api-compliance.breakpilot.ai +NEXT_PUBLIC_SDK_URL=https://sdk.breakpilot.ai diff --git a/.gitea/workflows/deploy-coolify.yml b/.gitea/workflows/deploy-coolify.yml new file mode 100644 index 0000000..4949666 --- /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-compliance 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..b6ed2d8 --- /dev/null +++ b/docker-compose.coolify.yml @@ -0,0 +1,257 @@ +# ========================================================= +# BreakPilot Compliance — Compliance SDK Platform (Coolify) +# ========================================================= +# Requires: breakpilot-core must be running +# Deployed via Coolify. SSL termination handled by Traefik. +# ========================================================= + +networks: + breakpilot-network: + external: true + name: breakpilot-network + +volumes: + dsms_data: + +services: + + # ========================================================= + # FRONTEND + # ========================================================= + admin-compliance: + build: + context: ./admin-compliance + dockerfile: Dockerfile + args: + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api-compliance.breakpilot.ai} + NEXT_PUBLIC_SDK_URL: ${NEXT_PUBLIC_SDK_URL:-https://sdk.breakpilot.ai} + container_name: bp-compliance-admin + expose: + - "3000" + environment: + NODE_ENV: production + BACKEND_URL: http://backend-compliance:8002 + CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 + SDK_URL: http://ai-compliance-sdk:8090 + OLLAMA_URL: ${OLLAMA_URL:-} + COMPLIANCE_LLM_MODEL: ${COMPLIANCE_LLM_MODEL:-} + depends_on: + backend-compliance: + condition: service_started + labels: + - "traefik.enable=true" + - "traefik.http.routers.admin-compliance.rule=Host(`admin-compliance.breakpilot.ai`)" + - "traefik.http.routers.admin-compliance.entrypoints=https" + - "traefik.http.routers.admin-compliance.tls=true" + - "traefik.http.routers.admin-compliance.tls.certresolver=letsencrypt" + - "traefik.http.services.admin-compliance.loadbalancer.server.port=3000" + restart: unless-stopped + networks: + - breakpilot-network + + developer-portal: + build: + context: ./developer-portal + dockerfile: Dockerfile + container_name: bp-compliance-developer-portal + expose: + - "3000" + environment: + NODE_ENV: production + labels: + - "traefik.enable=true" + - "traefik.http.routers.developer-portal.rule=Host(`developer.breakpilot.ai`)" + - "traefik.http.routers.developer-portal.entrypoints=https" + - "traefik.http.routers.developer-portal.tls=true" + - "traefik.http.routers.developer-portal.tls.certresolver=letsencrypt" + - "traefik.http.services.developer-portal.loadbalancer.server.port=3000" + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # BACKEND + # ========================================================= + backend-compliance: + build: + context: ./backend-compliance + dockerfile: Dockerfile + container_name: bp-compliance-backend + expose: + - "8002" + environment: + PORT: 8002 + DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB}?options=-csearch_path%3Dcompliance,core,public + JWT_SECRET: ${JWT_SECRET} + ENVIRONMENT: production + CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 + VALKEY_URL: redis://bp-core-valkey:6379/0 + SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24} + COMPLIANCE_LLM_PROVIDER: ${COMPLIANCE_LLM_PROVIDER:-anthropic} + SELF_HOSTED_LLM_URL: ${SELF_HOSTED_LLM_URL:-} + SELF_HOSTED_LLM_MODEL: ${SELF_HOSTED_LLM_MODEL:-} + COMPLIANCE_LLM_MAX_TOKENS: ${COMPLIANCE_LLM_MAX_TOKENS:-4096} + COMPLIANCE_LLM_TEMPERATURE: ${COMPLIANCE_LLM_TEMPERATURE:-0.3} + COMPLIANCE_LLM_TIMEOUT: ${COMPLIANCE_LLM_TIMEOUT:-120} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + SMTP_HOST: ${SMTP_HOST} + SMTP_PORT: ${SMTP_PORT:-587} + SMTP_USERNAME: ${SMTP_USERNAME} + SMTP_PASSWORD: ${SMTP_PASSWORD} + SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot Compliance} + SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-compliance@breakpilot.ai} + RAG_SERVICE_URL: http://bp-core-rag-service:8097 + labels: + - "traefik.enable=true" + - "traefik.http.routers.backend-compliance.rule=Host(`api-compliance.breakpilot.ai`)" + - "traefik.http.routers.backend-compliance.entrypoints=https" + - "traefik.http.routers.backend-compliance.tls=true" + - "traefik.http.routers.backend-compliance.tls.certresolver=letsencrypt" + - "traefik.http.services.backend-compliance.loadbalancer.server.port=8002" + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # SDK SERVICES + # ========================================================= + ai-compliance-sdk: + build: + context: ./ai-compliance-sdk + dockerfile: Dockerfile + container_name: bp-compliance-ai-sdk + expose: + - "8090" + environment: + PORT: 8090 + ENVIRONMENT: production + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + JWT_SECRET: ${JWT_SECRET} + LLM_PROVIDER: ${COMPLIANCE_LLM_PROVIDER:-anthropic} + LLM_FALLBACK_PROVIDER: ${LLM_FALLBACK_PROVIDER:-} + OLLAMA_URL: ${OLLAMA_URL:-} + OLLAMA_DEFAULT_MODEL: ${OLLAMA_DEFAULT_MODEL:-} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + ANTHROPIC_DEFAULT_MODEL: ${ANTHROPIC_DEFAULT_MODEL:-claude-sonnet-4-5-20250929} + PII_REDACTION_ENABLED: ${PII_REDACTION_ENABLED:-true} + PII_REDACTION_LEVEL: ${PII_REDACTION_LEVEL:-standard} + AUDIT_RETENTION_DAYS: ${AUDIT_RETENTION_DAYS:-365} + AUDIT_LOG_PROMPTS: ${AUDIT_LOG_PROMPTS:-true} + ALLOWED_ORIGINS: "*" + TTS_SERVICE_URL: http://compliance-tts-service:8095 + QDRANT_HOST: bp-core-qdrant + QDRANT_PORT: "6333" + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8090/health"] + interval: 30s + timeout: 3s + start_period: 10s + retries: 3 + labels: + - "traefik.enable=true" + - "traefik.http.routers.ai-sdk.rule=Host(`sdk.breakpilot.ai`)" + - "traefik.http.routers.ai-sdk.entrypoints=https" + - "traefik.http.routers.ai-sdk.tls=true" + - "traefik.http.routers.ai-sdk.tls.certresolver=letsencrypt" + - "traefik.http.services.ai-sdk.loadbalancer.server.port=8090" + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # TTS SERVICE (Piper TTS + FFmpeg) + # ========================================================= + compliance-tts-service: + build: + context: ./compliance-tts-service + dockerfile: Dockerfile + container_name: bp-compliance-tts + expose: + - "8095" + environment: + MINIO_ENDPOINT: bp-core-minio:9000 + MINIO_ACCESS_KEY: ${MINIO_ROOT_USER} + MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD} + PIPER_MODEL_PATH: /app/models/de_DE-thorsten-high.onnx + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8095/health')"] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # DATA SOVEREIGNTY + # ========================================================= + dsms-node: + build: + context: ./dsms-node + dockerfile: Dockerfile + container_name: bp-compliance-dsms-node + expose: + - "4001" + - "5001" + - "8080" + volumes: + - dsms_data:/data/ipfs + environment: + IPFS_PROFILE: server + healthcheck: + test: ["CMD-SHELL", "ipfs id"] + interval: 30s + timeout: 10s + start_period: 30s + retries: 3 + restart: unless-stopped + networks: + - breakpilot-network + + dsms-gateway: + build: + context: ./dsms-gateway + dockerfile: Dockerfile + container_name: bp-compliance-dsms-gateway + expose: + - "8082" + environment: + IPFS_API_URL: http://dsms-node:5001 + IPFS_GATEWAY_URL: http://dsms-node:8080 + JWT_SECRET: ${JWT_SECRET} + depends_on: + dsms-node: + condition: service_healthy + restart: unless-stopped + networks: + - breakpilot-network + + # ========================================================= + # DOCUMENT CRAWLER & AUTO-ONBOARDING + # ========================================================= + document-crawler: + build: + context: ./document-crawler + dockerfile: Dockerfile + container_name: bp-compliance-document-crawler + expose: + - "8098" + environment: + PORT: 8098 + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + LLM_GATEWAY_URL: http://ai-compliance-sdk:8090 + DSMS_GATEWAY_URL: http://dsms-gateway:8082 + CRAWL_BASE_PATH: /data/crawl + MAX_FILE_SIZE_MB: 50 + volumes: + - /tmp/breakpilot-crawl-data:/data/crawl:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8098/health"] + interval: 30s + timeout: 10s + start_period: 15s + retries: 3 + restart: unless-stopped + networks: + - breakpilot-network -- 2.49.1 From 99f3180ffc2d90fb1de6c99f7bb3aabc1c9826c5 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 3 Mar 2026 09:23:22 +0100 Subject: [PATCH 02/14] refactor(coolify): externalize postgres, qdrant, S3 - Replace bp-core-postgres with POSTGRES_HOST env var - Replace bp-core-qdrant with QDRANT_HOST env var - Replace bp-core-minio with S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY Co-Authored-By: Claude Opus 4.6 --- .env.coolify.example | 15 +++++++++++---- docker-compose.coolify.yml | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.env.coolify.example b/.env.coolify.example index f5074de..1db4a2b 100644 --- a/.env.coolify.example +++ b/.env.coolify.example @@ -5,7 +5,9 @@ # for the breakpilot-compliance Docker Compose resource. # ========================================================= -# --- Database (shared with Core) --- +# --- External PostgreSQL (Coolify-managed, same as Core) --- +POSTGRES_HOST= +POSTGRES_PORT=5432 POSTGRES_USER=breakpilot POSTGRES_PASSWORD=CHANGE_ME_SAME_AS_CORE POSTGRES_DB=breakpilot_db @@ -13,9 +15,14 @@ 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 +# --- External S3 Storage (same as Core) --- +S3_ENDPOINT= +S3_ACCESS_KEY=CHANGE_ME_SAME_AS_CORE +S3_SECRET_KEY=CHANGE_ME_SAME_AS_CORE + +# --- External Qdrant (Coolify-managed, same as Core) --- +QDRANT_HOST= +QDRANT_PORT=6333 # --- Session --- SESSION_TTL_HOURS=24 diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index b6ed2d8..2d3fb50 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -3,6 +3,8 @@ # ========================================================= # Requires: breakpilot-core must be running # Deployed via Coolify. SSL termination handled by Traefik. +# External services (managed separately in Coolify): +# - PostgreSQL, Qdrant, S3-compatible storage # ========================================================= networks: @@ -81,7 +83,7 @@ services: - "8002" environment: PORT: 8002 - DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB}?options=-csearch_path%3Dcompliance,core,public + DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB}?options=-csearch_path%3Dcompliance,core,public JWT_SECRET: ${JWT_SECRET} ENVIRONMENT: production CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 @@ -125,7 +127,7 @@ services: environment: PORT: 8090 ENVIRONMENT: production - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} JWT_SECRET: ${JWT_SECRET} LLM_PROVIDER: ${COMPLIANCE_LLM_PROVIDER:-anthropic} LLM_FALLBACK_PROVIDER: ${LLM_FALLBACK_PROVIDER:-} @@ -139,8 +141,8 @@ services: AUDIT_LOG_PROMPTS: ${AUDIT_LOG_PROMPTS:-true} ALLOWED_ORIGINS: "*" TTS_SERVICE_URL: http://compliance-tts-service:8095 - QDRANT_HOST: bp-core-qdrant - QDRANT_PORT: "6333" + QDRANT_HOST: ${QDRANT_HOST} + QDRANT_PORT: ${QDRANT_PORT:-6333} healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8090/health"] interval: 30s @@ -169,9 +171,9 @@ services: expose: - "8095" environment: - MINIO_ENDPOINT: bp-core-minio:9000 - MINIO_ACCESS_KEY: ${MINIO_ROOT_USER} - MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD} + MINIO_ENDPOINT: ${S3_ENDPOINT} + MINIO_ACCESS_KEY: ${S3_ACCESS_KEY} + MINIO_SECRET_KEY: ${S3_SECRET_KEY} PIPER_MODEL_PATH: /app/models/de_DE-thorsten-high.onnx healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8095/health')"] @@ -239,7 +241,7 @@ services: - "8098" environment: PORT: 8098 - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@bp-core-postgres:5432/${POSTGRES_DB} + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} LLM_GATEWAY_URL: http://ai-compliance-sdk:8090 DSMS_GATEWAY_URL: http://dsms-gateway:8082 CRAWL_BASE_PATH: /data/crawl -- 2.49.1 From 998d427c3c851ba6469f060495f38ce5852af054 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 6 Mar 2026 22:09:01 +0100 Subject: [PATCH 03/14] fix: update alpine base to 3.21 for ai-compliance-sdk Alpine 3.19 apk mirrors failing during Coolify build. Co-Authored-By: Claude Opus 4.6 --- ai-compliance-sdk/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai-compliance-sdk/Dockerfile b/ai-compliance-sdk/Dockerfile index ff9c684..4e27e62 100644 --- a/ai-compliance-sdk/Dockerfile +++ b/ai-compliance-sdk/Dockerfile @@ -17,7 +17,7 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /ai-compliance-sdk ./cmd/server # Runtime stage -FROM alpine:3.19 +FROM alpine:3.21 WORKDIR /app -- 2.49.1 From a3d0024d39eee8cb71e16798cfe6b508ccf38c91 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 6 Mar 2026 22:38:31 +0100 Subject: [PATCH 04/14] fix: use Alpine-compatible addgroup/adduser flags in Dockerfiles Replace --system/--gid/--uid (Debian syntax) with -S/-g/-u (BusyBox/Alpine). Coolify ARG injection causes exit code 255 with Debian-style flags. Co-Authored-By: Claude Opus 4.6 --- admin-compliance/Dockerfile | 4 ++-- developer-portal/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin-compliance/Dockerfile b/admin-compliance/Dockerfile index 61494a6..b98962a 100644 --- a/admin-compliance/Dockerfile +++ b/admin-compliance/Dockerfile @@ -37,8 +37,8 @@ WORKDIR /app ENV NODE_ENV=production # Create non-root user -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs +RUN addgroup -S -g 1001 nodejs +RUN adduser -S -u 1001 -G nodejs nextjs # Copy built assets COPY --from=builder /app/public ./public diff --git a/developer-portal/Dockerfile b/developer-portal/Dockerfile index 3dd000e..21c0326 100644 --- a/developer-portal/Dockerfile +++ b/developer-portal/Dockerfile @@ -27,8 +27,8 @@ WORKDIR /app ENV NODE_ENV=production # Create non-root user -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs +RUN addgroup -S -g 1001 nodejs +RUN adduser -S -u 1001 -G nodejs nextjs # Copy built assets COPY --from=builder /app/public ./public -- 2.49.1 From d542dbbacd2d23c131208aa826ed5a4a4bbe4efa Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 6 Mar 2026 22:53:49 +0100 Subject: [PATCH 05/14] fix: ensure public dir exists in developer-portal build Next.js standalone COPY fails when no public directory exists in source. Co-Authored-By: Claude Opus 4.6 --- developer-portal/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-portal/Dockerfile b/developer-portal/Dockerfile index 21c0326..2dae055 100644 --- a/developer-portal/Dockerfile +++ b/developer-portal/Dockerfile @@ -12,7 +12,7 @@ RUN npm install # Copy source code COPY . . -# Ensure public directory exists +# Ensure public directory exists (may not have static assets) RUN mkdir -p public # Build the application -- 2.49.1 From ffd256d4201aae2291802161e4a9158342ec177d Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Sat, 7 Mar 2026 23:23:55 +0100 Subject: [PATCH 06/14] Sync coolify compose with main: use COMPLIANCE_DATABASE_URL, QDRANT_URL - Switch to ${COMPLIANCE_DATABASE_URL} for admin-compliance, backend, SDK, crawler - Add DATABASE_URL to admin-compliance environment - Switch ai-compliance-sdk from QDRANT_HOST/PORT to QDRANT_URL + QDRANT_API_KEY - Add MINIO_SECURE to compliance-tts-service - Update .env.coolify.example with new variable patterns Co-Authored-By: Claude Opus 4.6 --- .env.coolify.example | 13 +++++-------- docker-compose.coolify.yml | 12 +++++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.env.coolify.example b/.env.coolify.example index 1db4a2b..f448ae8 100644 --- a/.env.coolify.example +++ b/.env.coolify.example @@ -6,11 +6,7 @@ # ========================================================= # --- External PostgreSQL (Coolify-managed, same as Core) --- -POSTGRES_HOST= -POSTGRES_PORT=5432 -POSTGRES_USER=breakpilot -POSTGRES_PASSWORD=CHANGE_ME_SAME_AS_CORE -POSTGRES_DB=breakpilot_db +COMPLIANCE_DATABASE_URL=postgresql://breakpilot:CHANGE_ME@:5432/breakpilot_db # --- Security --- JWT_SECRET=CHANGE_ME_SAME_AS_CORE @@ -19,10 +15,11 @@ JWT_SECRET=CHANGE_ME_SAME_AS_CORE S3_ENDPOINT= S3_ACCESS_KEY=CHANGE_ME_SAME_AS_CORE S3_SECRET_KEY=CHANGE_ME_SAME_AS_CORE +S3_SECURE=true -# --- External Qdrant (Coolify-managed, same as Core) --- -QDRANT_HOST= -QDRANT_PORT=6333 +# --- External Qdrant --- +QDRANT_URL=https:// +QDRANT_API_KEY=CHANGE_ME_QDRANT_API_KEY # --- Session --- SESSION_TTL_HOURS=24 diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 2d3fb50..6e5a96d 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -32,6 +32,7 @@ services: - "3000" environment: NODE_ENV: production + DATABASE_URL: ${COMPLIANCE_DATABASE_URL} BACKEND_URL: http://backend-compliance:8002 CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 SDK_URL: http://ai-compliance-sdk:8090 @@ -83,7 +84,7 @@ services: - "8002" environment: PORT: 8002 - DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB}?options=-csearch_path%3Dcompliance,core,public + DATABASE_URL: ${COMPLIANCE_DATABASE_URL} JWT_SECRET: ${JWT_SECRET} ENVIRONMENT: production CONSENT_SERVICE_URL: http://bp-core-consent-service:8081 @@ -127,7 +128,7 @@ services: environment: PORT: 8090 ENVIRONMENT: production - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} + DATABASE_URL: ${COMPLIANCE_DATABASE_URL} JWT_SECRET: ${JWT_SECRET} LLM_PROVIDER: ${COMPLIANCE_LLM_PROVIDER:-anthropic} LLM_FALLBACK_PROVIDER: ${LLM_FALLBACK_PROVIDER:-} @@ -141,8 +142,8 @@ services: AUDIT_LOG_PROMPTS: ${AUDIT_LOG_PROMPTS:-true} ALLOWED_ORIGINS: "*" TTS_SERVICE_URL: http://compliance-tts-service:8095 - QDRANT_HOST: ${QDRANT_HOST} - QDRANT_PORT: ${QDRANT_PORT:-6333} + QDRANT_URL: ${QDRANT_URL} + QDRANT_API_KEY: ${QDRANT_API_KEY:-} healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8090/health"] interval: 30s @@ -174,6 +175,7 @@ services: MINIO_ENDPOINT: ${S3_ENDPOINT} MINIO_ACCESS_KEY: ${S3_ACCESS_KEY} MINIO_SECRET_KEY: ${S3_SECRET_KEY} + MINIO_SECURE: ${S3_SECURE:-true} PIPER_MODEL_PATH: /app/models/de_DE-thorsten-high.onnx healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8095/health')"] @@ -241,7 +243,7 @@ services: - "8098" environment: PORT: 8098 - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB} + DATABASE_URL: ${COMPLIANCE_DATABASE_URL} LLM_GATEWAY_URL: http://ai-compliance-sdk:8090 DSMS_GATEWAY_URL: http://dsms-gateway:8082 CRAWL_BASE_PATH: /data/crawl -- 2.49.1 From 0c01f1c96cff2453d3e9a183811fc602c33019c9 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Sun, 8 Mar 2026 00:05:28 +0100 Subject: [PATCH 07/14] =?UTF-8?q?Remove=20Traefik=20labels=20from=20coolif?= =?UTF-8?q?y=20compose=20=E2=80=94=20Coolify=20handles=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- docker-compose.coolify.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 6e5a96d..b85d2df 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -41,13 +41,6 @@ services: depends_on: backend-compliance: condition: service_started - labels: - - "traefik.enable=true" - - "traefik.http.routers.admin-compliance.rule=Host(`admin-compliance.breakpilot.ai`)" - - "traefik.http.routers.admin-compliance.entrypoints=https" - - "traefik.http.routers.admin-compliance.tls=true" - - "traefik.http.routers.admin-compliance.tls.certresolver=letsencrypt" - - "traefik.http.services.admin-compliance.loadbalancer.server.port=3000" restart: unless-stopped networks: - breakpilot-network @@ -61,13 +54,6 @@ services: - "3000" environment: NODE_ENV: production - labels: - - "traefik.enable=true" - - "traefik.http.routers.developer-portal.rule=Host(`developer.breakpilot.ai`)" - - "traefik.http.routers.developer-portal.entrypoints=https" - - "traefik.http.routers.developer-portal.tls=true" - - "traefik.http.routers.developer-portal.tls.certresolver=letsencrypt" - - "traefik.http.services.developer-portal.loadbalancer.server.port=3000" restart: unless-stopped networks: - breakpilot-network @@ -104,13 +90,6 @@ services: SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot Compliance} SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-compliance@breakpilot.ai} RAG_SERVICE_URL: http://bp-core-rag-service:8097 - labels: - - "traefik.enable=true" - - "traefik.http.routers.backend-compliance.rule=Host(`api-compliance.breakpilot.ai`)" - - "traefik.http.routers.backend-compliance.entrypoints=https" - - "traefik.http.routers.backend-compliance.tls=true" - - "traefik.http.routers.backend-compliance.tls.certresolver=letsencrypt" - - "traefik.http.services.backend-compliance.loadbalancer.server.port=8002" restart: unless-stopped networks: - breakpilot-network @@ -150,13 +129,6 @@ services: timeout: 3s start_period: 10s retries: 3 - labels: - - "traefik.enable=true" - - "traefik.http.routers.ai-sdk.rule=Host(`sdk.breakpilot.ai`)" - - "traefik.http.routers.ai-sdk.entrypoints=https" - - "traefik.http.routers.ai-sdk.tls=true" - - "traefik.http.routers.ai-sdk.tls.certresolver=letsencrypt" - - "traefik.http.services.ai-sdk.loadbalancer.server.port=8090" restart: unless-stopped networks: - breakpilot-network -- 2.49.1 From 005fb9d2196f1651b037e711f2ea165c330e6e4f Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Sun, 8 Mar 2026 00:46:20 +0100 Subject: [PATCH 08/14] Add healthchecks to admin-compliance, developer-portal, backend-compliance Traefik may require healthchecks to route traffic to containers. Co-Authored-By: Claude Opus 4.6 --- docker-compose.coolify.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index b85d2df..70d5434 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -41,6 +41,12 @@ services: depends_on: backend-compliance: condition: service_started + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"] + interval: 30s + timeout: 10s + start_period: 30s + retries: 3 restart: unless-stopped networks: - breakpilot-network @@ -54,6 +60,12 @@ services: - "3000" environment: NODE_ENV: production + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"] + interval: 30s + timeout: 10s + start_period: 30s + retries: 3 restart: unless-stopped networks: - breakpilot-network @@ -90,6 +102,12 @@ services: SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot Compliance} SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-compliance@breakpilot.ai} RAG_SERVICE_URL: http://bp-core-rag-service:8097 + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8002/health"] + interval: 30s + timeout: 10s + start_period: 15s + retries: 3 restart: unless-stopped networks: - breakpilot-network -- 2.49.1 From 033fa52e5b2596f5ee2609069124f6477b267619 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Sun, 8 Mar 2026 00:57:32 +0100 Subject: [PATCH 09/14] Add healthcheck to dsms-gateway Co-Authored-By: Claude Opus 4.6 --- docker-compose.coolify.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 70d5434..d2eaf12 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -217,6 +217,12 @@ services: depends_on: dsms-node: condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8082/health"] + interval: 30s + timeout: 10s + start_period: 15s + retries: 3 restart: unless-stopped networks: - breakpilot-network -- 2.49.1 From 86588aff09c625a495464914bff0e56fedbd0f7e Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 10 Mar 2026 13:56:38 +0100 Subject: [PATCH 10/14] Fix SQLAlchemy 2.x compatibility: wrap raw SQL in text() SQLAlchemy 2.x requires raw SQL strings to be explicitly wrapped in text(). Fixed 16 instances across 5 route files. Co-Authored-By: Claude Opus 4.6 --- .../compliance/api/compliance_scope_routes.py | 13 ++++++------ .../compliance/api/import_routes.py | 19 +++++++++-------- .../compliance/api/screening_routes.py | 21 ++++++++++--------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/backend-compliance/compliance/api/compliance_scope_routes.py b/backend-compliance/compliance/api/compliance_scope_routes.py index 408ed3d..1b94173 100644 --- a/backend-compliance/compliance/api/compliance_scope_routes.py +++ b/backend-compliance/compliance/api/compliance_scope_routes.py @@ -15,6 +15,7 @@ from typing import Any, Optional from fastapi import APIRouter, HTTPException, Header from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -75,13 +76,13 @@ async def get_compliance_scope( db = SessionLocal() try: row = db.execute( - """SELECT tenant_id, + text("""SELECT tenant_id, state->'compliance_scope' AS scope, created_at, updated_at FROM sdk_states WHERE tenant_id = :tid - AND state ? 'compliance_scope'""", + AND state ? 'compliance_scope'"""), {"tid": tid}, ).fetchone() @@ -106,22 +107,22 @@ async def upsert_compliance_scope( db = SessionLocal() try: db.execute( - """INSERT INTO sdk_states (tenant_id, state) + text("""INSERT INTO sdk_states (tenant_id, state) VALUES (:tid, jsonb_build_object('compliance_scope', :scope::jsonb)) ON CONFLICT (tenant_id) DO UPDATE SET state = sdk_states.state || jsonb_build_object('compliance_scope', :scope::jsonb), - updated_at = NOW()""", + updated_at = NOW()"""), {"tid": tid, "scope": scope_json}, ) db.commit() row = db.execute( - """SELECT tenant_id, + text("""SELECT tenant_id, state->'compliance_scope' AS scope, created_at, updated_at FROM sdk_states - WHERE tenant_id = :tid""", + WHERE tenant_id = :tid"""), {"tid": tid}, ).fetchone() diff --git a/backend-compliance/compliance/api/import_routes.py b/backend-compliance/compliance/api/import_routes.py index 14d8044..25ab62d 100644 --- a/backend-compliance/compliance/api/import_routes.py +++ b/backend-compliance/compliance/api/import_routes.py @@ -15,6 +15,7 @@ from typing import Optional import httpx from fastapi import APIRouter, File, Form, Header, UploadFile, HTTPException from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -291,11 +292,11 @@ async def analyze_document( db = SessionLocal() try: db.execute( - """INSERT INTO compliance_imported_documents + text("""INSERT INTO compliance_imported_documents (id, tenant_id, filename, file_type, file_size, detected_type, detection_confidence, extracted_text, extracted_entities, recommendations, status, analyzed_at) VALUES (:id, :tenant_id, :filename, :file_type, :file_size, :detected_type, :confidence, - :text, :entities::jsonb, :recommendations::jsonb, 'analyzed', NOW())""", + :text, :entities::jsonb, :recommendations::jsonb, 'analyzed', NOW())"""), { "id": doc_id, "tenant_id": tenant_id, @@ -313,9 +314,9 @@ async def analyze_document( if total_gaps > 0: import json db.execute( - """INSERT INTO compliance_gap_analyses + text("""INSERT INTO compliance_gap_analyses (tenant_id, document_id, total_gaps, critical_gaps, high_gaps, medium_gaps, low_gaps, gaps, recommended_packages) - VALUES (:tenant_id, :document_id, :total, :critical, :high, :medium, :low, :gaps::jsonb, :packages::jsonb)""", + VALUES (:tenant_id, :document_id, :total, :critical, :high, :medium, :low, :gaps::jsonb, :packages::jsonb)"""), { "tenant_id": tenant_id, "document_id": doc_id, @@ -358,7 +359,7 @@ async def get_gap_analysis( db = SessionLocal() try: result = db.execute( - "SELECT * FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid", + text("SELECT * FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ).fetchone() if not result: @@ -374,11 +375,11 @@ async def list_documents(tenant_id: str = "default"): db = SessionLocal() try: result = db.execute( - """SELECT id, filename, file_type, file_size, detected_type, detection_confidence, + text("""SELECT id, filename, file_type, file_size, detected_type, detection_confidence, extracted_entities, recommendations, status, analyzed_at, created_at FROM compliance_imported_documents WHERE tenant_id = :tenant_id - ORDER BY created_at DESC""", + ORDER BY created_at DESC"""), {"tenant_id": tenant_id}, ) rows = result.fetchall() @@ -424,11 +425,11 @@ async def delete_document( try: # Delete gap analysis first (FK dependency) db.execute( - "DELETE FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid", + text("DELETE FROM compliance_gap_analyses WHERE document_id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ) result = db.execute( - "DELETE FROM compliance_imported_documents WHERE id = :doc_id AND tenant_id = :tid", + text("DELETE FROM compliance_imported_documents WHERE id = :doc_id AND tenant_id = :tid"), {"doc_id": document_id, "tid": tid}, ) db.commit() diff --git a/backend-compliance/compliance/api/screening_routes.py b/backend-compliance/compliance/api/screening_routes.py index 307ca67..9b9ee16 100644 --- a/backend-compliance/compliance/api/screening_routes.py +++ b/backend-compliance/compliance/api/screening_routes.py @@ -17,6 +17,7 @@ from typing import Optional import httpx from fastapi import APIRouter, File, Form, UploadFile, HTTPException from pydantic import BaseModel +from sqlalchemy import text from database import SessionLocal @@ -366,13 +367,13 @@ async def scan_dependencies( db = SessionLocal() try: db.execute( - """INSERT INTO compliance_screenings + text("""INSERT INTO compliance_screenings (id, tenant_id, status, sbom_format, sbom_version, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, sbom_data, started_at, completed_at) VALUES (:id, :tenant_id, 'completed', 'CycloneDX', '1.5', :total_components, :total_issues, :critical, :high, :medium, :low, - :sbom_data::jsonb, :started_at, :completed_at)""", + :sbom_data::jsonb, :started_at, :completed_at)"""), { "id": screening_id, "tenant_id": tenant_id, @@ -391,11 +392,11 @@ async def scan_dependencies( # Persist security issues for issue in issues: db.execute( - """INSERT INTO compliance_security_issues + text("""INSERT INTO compliance_security_issues (id, screening_id, severity, title, description, cve, cvss, affected_component, affected_version, fixed_in, remediation, status) VALUES (:id, :screening_id, :severity, :title, :description, :cve, :cvss, - :component, :version, :fixed_in, :remediation, :status)""", + :component, :version, :fixed_in, :remediation, :status)"""), { "id": issue["id"], "screening_id": screening_id, @@ -486,10 +487,10 @@ async def get_screening(screening_id: str): db = SessionLocal() try: result = db.execute( - """SELECT id, status, sbom_format, sbom_version, + text("""SELECT id, status, sbom_format, sbom_version, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, sbom_data, started_at, completed_at - FROM compliance_screenings WHERE id = :id""", + FROM compliance_screenings WHERE id = :id"""), {"id": screening_id}, ) row = result.fetchone() @@ -498,9 +499,9 @@ async def get_screening(screening_id: str): # Fetch issues issues_result = db.execute( - """SELECT id, severity, title, description, cve, cvss, + text("""SELECT id, severity, title, description, cve, cvss, affected_component, affected_version, fixed_in, remediation, status - FROM compliance_security_issues WHERE screening_id = :id""", + FROM compliance_security_issues WHERE screening_id = :id"""), {"id": screening_id}, ) issues_rows = issues_result.fetchall() @@ -566,12 +567,12 @@ async def list_screenings(tenant_id: str = "default"): db = SessionLocal() try: result = db.execute( - """SELECT id, status, total_components, total_issues, + text("""SELECT id, status, total_components, total_issues, critical_issues, high_issues, medium_issues, low_issues, started_at, completed_at, created_at FROM compliance_screenings WHERE tenant_id = :tenant_id - ORDER BY created_at DESC""", + ORDER BY created_at DESC"""), {"tenant_id": tenant_id}, ) rows = result.fetchall() -- 2.49.1 From f6b22820ce7b99f3f993eacb0b1dfb90795efe6a Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 10 Mar 2026 15:13:24 +0100 Subject: [PATCH 11/14] Add coolify network to externally-routed services Traefik routes traffic via the 'coolify' bridge network, so services that need public domain access must be on both breakpilot-network (for inter-service communication) and coolify (for Traefik routing). Co-Authored-By: Claude Opus 4.6 --- docker-compose.coolify.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index d2eaf12..9710073 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -11,6 +11,9 @@ networks: breakpilot-network: external: true name: breakpilot-network + coolify: + external: true + name: coolify volumes: dsms_data: @@ -50,6 +53,7 @@ services: restart: unless-stopped networks: - breakpilot-network + - coolify developer-portal: build: @@ -69,6 +73,7 @@ services: restart: unless-stopped networks: - breakpilot-network + - coolify # ========================================================= # BACKEND @@ -111,6 +116,7 @@ services: restart: unless-stopped networks: - breakpilot-network + - coolify # ========================================================= # SDK SERVICES @@ -150,6 +156,7 @@ services: restart: unless-stopped networks: - breakpilot-network + - coolify # ========================================================= # TTS SERVICE (Piper TTS + FFmpeg) -- 2.49.1 From a101426dba6cb5b68d2c5e90d80b0ea52e48fd8f Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 10 Mar 2026 16:30:04 +0100 Subject: [PATCH 12/14] Add traefik.docker.network label to fix routing Containers are on multiple networks (breakpilot-network, coolify, gokocgws...). Without traefik.docker.network, Traefik randomly picks a network and may choose breakpilot-network where it has no access. This label forces Traefik to always use the coolify network. Co-Authored-By: Claude Opus 4.6 --- docker-compose.coolify.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 9710073..8e1d0fb 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -31,6 +31,8 @@ services: NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api-compliance.breakpilot.ai} NEXT_PUBLIC_SDK_URL: ${NEXT_PUBLIC_SDK_URL:-https://sdk.breakpilot.ai} container_name: bp-compliance-admin + labels: + - "traefik.docker.network=coolify" expose: - "3000" environment: @@ -60,6 +62,8 @@ services: context: ./developer-portal dockerfile: Dockerfile container_name: bp-compliance-developer-portal + labels: + - "traefik.docker.network=coolify" expose: - "3000" environment: @@ -83,6 +87,8 @@ services: context: ./backend-compliance dockerfile: Dockerfile container_name: bp-compliance-backend + labels: + - "traefik.docker.network=coolify" expose: - "8002" environment: @@ -126,6 +132,8 @@ services: context: ./ai-compliance-sdk dockerfile: Dockerfile container_name: bp-compliance-ai-sdk + labels: + - "traefik.docker.network=coolify" expose: - "8090" environment: -- 2.49.1 From 559d7960a2f4b40e87b890be6637f6436b94d37e Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 13 Mar 2026 10:39:12 +0100 Subject: [PATCH 13/14] Replace deploy-hetzner with Coolify webhook deploy Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/ci.yaml | 100 ++++----------------------------------- 1 file changed, 10 insertions(+), 90 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 9cc229d..d706806 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -7,7 +7,7 @@ # Node.js: admin-compliance, developer-portal # # Workflow: -# Push auf main → Tests → Build → Deploy (Hetzner) +# Push auf main → Tests → Deploy (Coolify) # Pull Request → Lint + Tests (kein Deploy) name: CI/CD @@ -186,10 +186,11 @@ jobs: python scripts/validate-controls.py # ======================================== - # Build & Deploy auf Hetzner (nur main, kein PR) + # Deploy via Coolify (nur main, kein PR) # ======================================== - deploy-hetzner: + deploy-coolify: + name: Deploy runs-on: docker if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: @@ -198,92 +199,11 @@ jobs: - test-python-document-crawler - test-python-dsms-gateway - validate-canonical-controls - container: docker:27-cli + container: + image: alpine:latest steps: - - name: Deploy + - name: Trigger Coolify deploy run: | - set -euo pipefail - DEPLOY_DIR="/opt/breakpilot-compliance" - 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" - - echo "=== BreakPilot Compliance Deploy ===" - echo "Commit: ${SHORT_SHA}" - echo "Deploy Dir: ${DEPLOY_DIR}" - echo "" - - # Der Runner laeuft in einem Container mit Docker-Socket-Zugriff, - # hat aber KEINEN direkten Zugriff auf das Host-Dateisystem. - # Loesung: Alpine-Helper-Container mit Host-Bind-Mount fuer Git-Ops. - - # 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 (muss einmalig manuell angelegt werden) - docker run --rm -v "${DEPLOY_DIR}:${DEPLOY_DIR}" alpine \ - sh -c " - if [ ! -f '${DEPLOY_DIR}/.env' ]; then - echo 'WARNUNG: ${DEPLOY_DIR}/.env fehlt!' - echo 'Bitte einmalig auf dem Host anlegen.' - echo 'Deploy wird fortgesetzt (Services starten ggf. mit Defaults).' - else - echo '.env vorhanden' - fi - " - - # 3. Build + Deploy via Helper-Container mit Docker-Socket + Deploy-Dir - # docker compose muss die YAML-Dateien lesen koennen, daher - # alles in einem Container mit beiden Mounts ausfuehren. - 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' - - echo '=== Building Docker Images ===' - docker compose \${COMPOSE_FILES} build --parallel \ - admin-compliance \ - backend-compliance \ - ai-compliance-sdk \ - developer-portal - - echo '' - echo '=== Starting containers ===' - docker compose \${COMPOSE_FILES} up -d --remove-orphans \ - admin-compliance \ - backend-compliance \ - ai-compliance-sdk \ - developer-portal - - echo '' - echo '=== Health Checks ===' - sleep 10 - for svc in bp-compliance-admin bp-compliance-backend bp-compliance-ai-sdk bp-compliance-developer-portal; do - STATUS=\$(docker inspect --format='{{.State.Status}}' \"\${svc}\" 2>/dev/null || echo 'not found') - echo \"\${svc}: \${STATUS}\" - done - " - - echo "" - echo "=== Deploy abgeschlossen: ${SHORT_SHA} ===" + apk add --no-cache curl + curl -sf "${{ secrets.COOLIFY_WEBHOOK }}" \ + -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" -- 2.49.1 From 1dfea5191980ca2a8942c65a764c3ea00b06e684 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Fri, 13 Mar 2026 11:26:31 +0100 Subject: [PATCH 14/14] =?UTF-8?q?Remove=20standalone=20deploy-coolify.yml?= =?UTF-8?q?=20=E2=80=94=20deploy=20is=20handled=20in=20ci.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/deploy-coolify.yml | 32 ----------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .gitea/workflows/deploy-coolify.yml diff --git a/.gitea/workflows/deploy-coolify.yml b/.gitea/workflows/deploy-coolify.yml deleted file mode 100644 index 4949666..0000000 --- a/.gitea/workflows/deploy-coolify.yml +++ /dev/null @@ -1,32 +0,0 @@ -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-compliance 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!" -- 2.49.1