# sundynix-agentix · 生产/独立部署编排（应用 + 基建一把拉起）
#
#   cp .env.example .env   # 然后按提示填密钥（见 DEPLOY.md）
#   docker compose -f docker-compose.prod.yml up -d --build
#
# 设计：
# - 应用经服务名互连（postgres:5432 等），不依赖 127.0.0.1；基建端口不对宿主暴露（仅内网）。
# - 仅暴露 admin 控制台(:${HTTP_PORT}) 与 gateway(:${GATEWAY_PORT}，供桌面端直连) + Jaeger UI（可选）。
# - 引导密钥从 .env 注入（SUNDYNIX_SECRET_KEY/JWT_SECRET/各库密码）；模型 API key 仍在控制台配、加密存 PG。
# - APP_ENV=production → 强制校验密钥、锁 CORS、启用管理员白名单。

x-secret-key: &secret-key ${SUNDYNIX_SECRET_KEY:?需在 .env 设置（openssl rand -base64 32）}

services:
  # ---------- 应用层 ----------
  gateway:
    build: { context: ., dockerfile: sundynix-gateway/Dockerfile }
    image: sundynix/gateway:${TAG:-latest}
    restart: unless-stopped
    environment:
      APP_ENV: production
      GATEWAY_ADDR: ":8080"
      NATS_URL: nats://nats:4222
      POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable
      REDIS_ADDR: redis:6379
      MINIO_ENDPOINT: minio:9000
      MINIO_ACCESS_KEY: minioadmin   # minio 仅 compose 内网可达，不对宿主暴露
      MINIO_SECRET_KEY: minioadmin
      SUNDYNIX_SECRET_KEY: *secret-key
      JWT_SECRET: ${JWT_SECRET:?需在 .env 设置（openssl rand -base64 32）}
      ADMIN_USER_IDS: ${ADMIN_USER_IDS:-}
      CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN:-*}
      OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
    ports: ["${GATEWAY_PORT:-8080}:8080"]
    depends_on:
      postgres: { condition: service_healthy }
      redis: { condition: service_started }
      nats: { condition: service_started }

  dispatcher:
    build: { context: ., dockerfile: sundynix-dispatcher/Dockerfile }
    image: sundynix/dispatcher:${TAG:-latest}
    restart: unless-stopped
    environment:
      APP_ENV: production
      NATS_URL: nats://nats:4222
      SUNDYNIX_SECRET_KEY: *secret-key
      OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
      DISPATCHER_CONCURRENCY: ${DISPATCHER_CONCURRENCY:-8}
      TASK_TOKEN_BUDGET: ${TASK_TOKEN_BUDGET:-200000}
    depends_on:
      nats: { condition: service_started }

  mcp-go:
    build: { context: ., dockerfile: sundynix-mcp-go/Dockerfile }
    image: sundynix/mcp-go:${TAG:-latest}
    restart: unless-stopped
    environment:
      APP_ENV: production
      NATS_URL: nats://nats:4222
      POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable
      REDIS_ADDR: redis:6379
      MILVUS_ADDR: milvus:19530
      NEO4J_URI: neo4j://neo4j:7687
      NEO4J_USER: neo4j
      NEO4J_PASS: ${NEO4J_PASSWORD:?}
      SUNDYNIX_SECRET_KEY: *secret-key
      OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
    depends_on:
      nats: { condition: service_started }
      milvus: { condition: service_healthy }   # mcp-go 须在 Milvus 后起，否则工具 no responders

  mcp-py:
    build: { context: sundynix-mcp-py, dockerfile: Dockerfile }
    image: sundynix/mcp-py:${TAG:-latest}
    restart: unless-stopped
    environment:
      NATS_URL: nats://nats:4222
    # secure_sandbox(代码隔离执行)需访问宿主 Docker 拉起一次性容器。
    # 安全敏感：等于把宿主 Docker 暴露给该服务；不需要代码执行工具可注释掉这行。
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      nats: { condition: service_started }

  admin:
    build: { context: sundynix-admin, dockerfile: Dockerfile }
    image: sundynix/admin:${TAG:-latest}
    restart: unless-stopped
    ports: ["${HTTP_PORT:-80}:80"]   # 运维控制台 + 反代网关 API（一个 URL 访问整套）
    depends_on:
      gateway: { condition: service_started }

  # ---------- 基础设施（端口不对宿主暴露，仅 compose 内网） ----------
  nats:
    image: nats:2-alpine
    command: ["-c", "/etc/nats/nats-server.conf"]
    restart: unless-stopped
    volumes:
      - ./deploy/nats/nats-server.conf:/etc/nats/nats-server.conf:ro
      - nats_data:/data/jetstream

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: sundynix
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
      POSTGRES_DB: sundynix
    volumes: ["pg_data:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sundynix"]
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes: ["redis_data:/data"]

  jaeger:
    image: jaegertracing/all-in-one:1.60
    restart: unless-stopped
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    ports: ["${JAEGER_UI_PORT:-16686}:16686"]   # 链路追踪 UI（演示可看 span 瀑布图；不需要可去掉）

  milvus-etcd:
    image: quay.io/coreos/etcd:v3.5.14
    restart: unless-stopped
    environment:
      ETCD_AUTO_COMPACTION_MODE: revision
      ETCD_AUTO_COMPACTION_RETENTION: "1000"
      ETCD_QUOTA_BACKEND_BYTES: "4294967296"
      ETCD_SNAPSHOT_COUNT: "50000"
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    volumes: ["milvus_etcd:/etcd"]

  minio:
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    restart: unless-stopped
    environment:
      MINIO_ACCESS_KEY: minioadmin   # 内网专用（不对宿主暴露端口），与 milvus/gateway 默认对齐
      MINIO_SECRET_KEY: minioadmin
    command: minio server /minio_data
    volumes: ["minio_data:/minio_data"]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  milvus:
    image: milvusdb/milvus:v2.4.13
    command: ["milvus", "run", "standalone"]
    restart: unless-stopped
    environment:
      ETCD_ENDPOINTS: milvus-etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes: ["milvus_data:/var/lib/milvus"]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 15s
      timeout: 10s
      retries: 10
    depends_on: [milvus-etcd, minio]

  neo4j:
    image: neo4j:5-community
    restart: unless-stopped
    environment:
      NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:?}
    volumes: ["neo4j_data:/data"]

volumes:
  nats_data:
  pg_data:
  redis_data:
  milvus_etcd:
  minio_data:
  milvus_data:
  neo4j_data:
