# ==============================================
# Mosaic Stack — Coolify Core Deployment
# ==============================================
#
# Core services only. For Matrix, speech, and other optional
# services, deploy them as separate Coolify services or extend
# this file.
#
# Usage (Coolify):
#   1. New Resource -> Docker Compose
#   2. Paste this file
#   3. Set environment variables in Coolify UI
#   4. Configure domains for web + api in Coolify UI
#   5. Deploy
#
# NOTE: Traefik labels are NOT included here. Coolify manages
# routing and TLS via its own proxy integration. Configure
# domains in the Coolify service settings.
#
# ==============================================

services:
  # ======================
  # PostgreSQL Database
  # ======================
  postgres:
    image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_SHARED_BUFFERS=${POSTGRES_SHARED_BUFFERS:-256MB}
      - POSTGRES_EFFECTIVE_CACHE_SIZE=${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
      - POSTGRES_MAX_CONNECTIONS=${POSTGRES_MAX_CONNECTIONS:-100}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - internal

  # ======================
  # Valkey Cache
  # ======================
  valkey:
    image: valkey/valkey:8-alpine
    restart: unless-stopped
    command:
      - valkey-server
      - --maxmemory ${VALKEY_MAXMEMORY:-256mb}
      - --maxmemory-policy noeviction
      - --appendonly yes
    volumes:
      - valkey_data:/data
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - internal

  # ======================
  # Mosaic API
  # ======================
  api:
    image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      # Coolify domain assignment (magic variable — tells Coolify this service gets a domain on port 3001)
      - SERVICE_FQDN_API_3001
      - NODE_ENV=production
      - PORT=${API_PORT:-3001}
      - API_HOST=${API_HOST:-0.0.0.0}
      # Database
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      # Cache
      - VALKEY_URL=redis://valkey:6379
      # Auth (external Authentik — optional)
      - OIDC_ENABLED=${OIDC_ENABLED:-false}
      - OIDC_ISSUER=${OIDC_ISSUER:-}
      - OIDC_CLIENT_ID=${OIDC_CLIENT_ID:-}
      - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET:-}
      - OIDC_REDIRECT_URI=${OIDC_REDIRECT_URI:-}
      # JWT
      - JWT_SECRET=${JWT_SECRET}
      - JWT_EXPIRATION=${JWT_EXPIRATION:-24h}
      # Better Auth
      - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
      - BETTER_AUTH_URL=${BETTER_AUTH_URL:-}
      - CSRF_SECRET=${CSRF_SECRET}
      - COOKIE_DOMAIN=${COOKIE_DOMAIN:-}
      # Encryption
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
      # External services (optional — leave empty to disable)
      - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-}
      - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.2}
      - OPENBAO_ADDR=${OPENBAO_ADDR:-}
      # Knowledge module
      - KNOWLEDGE_CACHE_ENABLED=${KNOWLEDGE_CACHE_ENABLED:-true}
      - KNOWLEDGE_CACHE_TTL=${KNOWLEDGE_CACHE_TTL:-300}
      - SEMANTIC_SEARCH_SIMILARITY_THRESHOLD=${SEMANTIC_SEARCH_SIMILARITY_THRESHOLD:-0.5}
      # Rate limiting
      - RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60}
      - RATE_LIMIT_GLOBAL_LIMIT=${RATE_LIMIT_GLOBAL_LIMIT:-100}
      - RATE_LIMIT_STORAGE=${RATE_LIMIT_STORAGE:-redis}
      # Speech services (disabled — not in core stack)
      - STT_ENABLED=${STT_ENABLED:-false}
      - TTS_ENABLED=${TTS_ENABLED:-false}
      # Matrix bridge (disabled — not in core stack)
      - MATRIX_ACCESS_TOKEN=${MATRIX_ACCESS_TOKEN:-}
      # Telemetry (disabled by default)
      - MOSAIC_TELEMETRY_ENABLED=${MOSAIC_TELEMETRY_ENABLED:-false}
      - MOSAIC_TELEMETRY_SERVER_URL=${MOSAIC_TELEMETRY_SERVER_URL:-}
      - MOSAIC_TELEMETRY_API_KEY=${MOSAIC_TELEMETRY_API_KEY:-}
      - MOSAIC_TELEMETRY_INSTANCE_ID=${MOSAIC_TELEMETRY_INSTANCE_ID:-}
      - MOSAIC_TELEMETRY_DRY_RUN=${MOSAIC_TELEMETRY_DRY_RUN:-false}
      # Frontend URLs (for CORS and auth redirects)
      - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
      - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
      - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-}
    depends_on:
      postgres:
        condition: service_healthy
      valkey:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - internal

  # ======================
  # Mosaic Web
  # ======================
  web:
    image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      # Coolify domain assignment (magic variable — tells Coolify this service gets a domain on port 3000)
      - SERVICE_FQDN_WEB_3000
      - NODE_ENV=production
      - PORT=${WEB_PORT:-3000}
      - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
      - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
      - NEXT_PUBLIC_ORCHESTRATOR_URL=${NEXT_PUBLIC_ORCHESTRATOR_URL:-}
      - NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE:-real}
      # Server-side orchestrator proxy (API routes forward to orchestrator service over internal network)
      - ORCHESTRATOR_URL=http://orchestrator:3001
      - ORCHESTRATOR_API_KEY=${ORCHESTRATOR_API_KEY:-}
    depends_on:
      api:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - internal

  # ======================
  # Mosaic Coordinator
  # ======================
  coordinator:
    image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      - GITEA_WEBHOOK_SECRET=${GITEA_WEBHOOK_SECRET:-}
      - GITEA_URL=${GITEA_URL:-}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
      - LOG_LEVEL=${LOG_LEVEL:-info}
      - HOST=0.0.0.0
      - PORT=8000
      - COORDINATOR_POLL_INTERVAL=${COORDINATOR_POLL_INTERVAL:-5.0}
      - COORDINATOR_MAX_CONCURRENT_AGENTS=${COORDINATOR_MAX_CONCURRENT_AGENTS:-10}
      - COORDINATOR_ENABLED=${COORDINATOR_ENABLED:-true}
      # Telemetry
      - MOSAIC_TELEMETRY_ENABLED=${MOSAIC_TELEMETRY_ENABLED:-false}
      - MOSAIC_TELEMETRY_SERVER_URL=${MOSAIC_TELEMETRY_SERVER_URL:-}
      - MOSAIC_TELEMETRY_API_KEY=${MOSAIC_TELEMETRY_API_KEY:-}
      - MOSAIC_TELEMETRY_INSTANCE_ID=${MOSAIC_TELEMETRY_INSTANCE_ID:-}
      - MOSAIC_TELEMETRY_DRY_RUN=${MOSAIC_TELEMETRY_DRY_RUN:-false}
    healthcheck:
      test:
        [
          "CMD",
          "python",
          "-c",
          "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s
    networks:
      - internal

  # ======================
  # Mosaic Orchestrator
  # ======================
  orchestrator:
    image: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest}
    restart: unless-stopped
    user: "1000:1000"
    environment:
      - NODE_ENV=production
      - ORCHESTRATOR_PORT=3001
      # Bind to all interfaces so the web container can reach it over Docker networking
      - HOST=0.0.0.0
      - AI_PROVIDER=${AI_PROVIDER:-ollama}
      - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-}
      - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.2}
      - VALKEY_URL=redis://valkey:6379
      - VALKEY_HOST=valkey
      - VALKEY_PORT=6379
      - CLAUDE_API_KEY=${CLAUDE_API_KEY:-}
      - ORCHESTRATOR_API_KEY=${ORCHESTRATOR_API_KEY:-}
      - DOCKER_SOCKET=/var/run/docker.sock
      - GIT_USER_NAME=Mosaic Orchestrator
      - GIT_USER_EMAIL=orchestrator@mosaicstack.dev
      - KILLSWITCH_ENABLED=true
      - SANDBOX_ENABLED=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - orchestrator_workspace:/workspace
    depends_on:
      valkey:
        condition: service_healthy
      api:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - internal
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp:noexec,nosuid,size=100m

# ======================
# Volumes
# ======================
volumes:
  postgres_data:
  valkey_data:
  orchestrator_workspace:

# ======================
# Networks
# ======================
networks:
  internal:
    driver: bridge
