# PAC 生产 compose — 单一真相在 apps/*/.env(跟 systemd 模式共用)
#
# 启动:
#   1. cp apps/pac-service/.env.example apps/pac-service/.env  并填好(POSTGRES_*/JWT_*/DEEPSEEK_*)
#   2. cp apps/pac-web/.env.example     apps/pac-web/.env      并填好(NEXT_PUBLIC_API_BASE_URL)
#   3. docker compose -f docker-compose.prod.yml \
#        --env-file apps/pac-service/.env \
#        --env-file apps/pac-web/.env \
#        up -d --build
#
#   说明:--env-file 给 compose CLI 做 ${VAR} 插值(POSTGRES_* / NEXT_PUBLIC_*);
#        env_file: 指令则把同一份文件注入容器内 process.env(JWT_* / DEEPSEEK_* 等)。
#        两者作用域不同,都要配。多个 --env-file 后者覆盖前者(本例无冲突 key)。
#
# 端口(宿主机):
#   postgres 5532 / redis 6479 / pac-service 3101 / pac-web 3100
#
# 内部网络:容器之间用服务名访问(postgres:5432 / redis:6379),容器内 port 用 image 默认

# ─── 日志驱动(所有服务共用)────────────────────────────────────────────
# 【为什么显式声明 json-file】docker 的默认就是 json-file,写不写行为一样;但 **podman
#   rootless 默认是 journald** —— 2026-08-04 新服务器实测:`docker logs` 只回一行兼容层提示,
#   `podman logs` 返回 0 行,而容器日志进了用户 journal、普通用户不在 systemd-journal 组读不到,
#   等于**线上出问题看不到日志**。显式声明让两种运行时行为一致。
#
# 【顺带治一个既有隐患】此前没有任何 rotate —— 当前生产 /var/lib/docker/containers 已 1.4 GB,
#   且只会一直涨。50m × 5 = 单容器上限 250MB,够排查近期问题,又不会把盘吃满
#   (2026-08-01 测试服就是被写满后 Postgres 崩溃重启的)。
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "5"

services:
  postgres:
    logging: *default-logging
    image: postgres:16-alpine
    restart: always
    env_file: ./apps/pac-service/.env   # 读 POSTGRES_USER/PASSWORD/DB
    # ⭐ Docker 给 /dev/shm 的默认只有 64MB,PG 的并行查询 worker 用共享内存段交换中间结果,
    #   一超就是 `could not resize shared memory segment ... No space left on device`。
    #   2026-07-27 测试服实测踩到:召回 selectHits 把 10 个子场景 Promise.all 并行跑,
    #   每个都是 2500 万 patient_facts 上带 LATERAL 的重查询、各自还起 parallel worker,
    #   并发的段远超 64MB → plan 全量重算当场崩。
    #   1g 是 PG 容器的常规配法(官方镜像文档亦如此建议);托管 DB 形态下本服务不启用,无影响。
    shm_size: 1g
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "127.0.0.1:5532:5432"           # 宿主仅本地访问(psql 进容器调试用)
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-pac}"]
      interval: 10s
      retries: 5

  redis:
    logging: *default-logging
    image: redis:7-alpine
    restart: always
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    ports:
      - "127.0.0.1:6479:6379"

  # One-shot migrator: applies pending Prisma migrations and exits.
  # Runs before pac-service so the schema is current before any traffic
  # hits the API. Safe to re-run — `migrate deploy` is idempotent.
  pac-migrate:
    logging: *default-logging
    build:
      context: .
      dockerfile: apps/pac-service/Dockerfile
      target: prod
    restart: "no"
    depends_on:
      postgres:
        condition: service_healthy
    env_file: ./apps/pac-service/.env
    environment:
      # 覆盖 .env 里 host=localhost 的连接串 → 走 docker 内部网络
      DATABASE_URL: postgresql://${POSTGRES_USER:-pac}:${POSTGRES_PASSWORD:-pac}@postgres:5432/${POSTGRES_DB:-pac}?schema=public
    working_dir: /app/apps/pac-service
    command: ["npx", "prisma", "migrate", "deploy"]

  pac-service:
    logging: *default-logging
    build:
      context: .
      dockerfile: apps/pac-service/Dockerfile
      target: prod
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
      pac-migrate:
        condition: service_completed_successfully
    env_file: ./apps/pac-service/.env
    environment:
      NODE_ENV: production
      PORT: 3101
      # 覆盖 .env 的 localhost URL → 走 docker 内部网络
      DATABASE_URL: postgresql://${POSTGRES_USER:-pac}:${POSTGRES_PASSWORD:-pac}@postgres:5432/${POSTGRES_DB:-pac}?schema=public
      REDIS_URL: redis://redis:6379
      # 语音听写 ASR(自部署 SenseVoice,内网)
      PAC_ASR_URL: http://pac-asr:8000
    ports:
      - "127.0.0.1:3101:3101"

  # 语音听写 ASR — SenseVoice-small(CPU)。模型不进镜像:
  #   服务器准备 ${PAC_MODELS_DIR:-../pac-models}/sensevoice/{model.int8.onnx,tokens.txt}
  # ⚠️ 可选服务,默认【不启】(profile=asr)。只有用语音助手才需要:
  #   启用 → docker compose --profile asr -f docker-compose.prod.yml up -d
  #   不启用时 pac-service 的 PAC_ASR_URL 指向的服务不在,transcribe 端点会失败(不影响其它功能/启动)。
  pac-asr:
    logging: *default-logging
    profiles: ["asr"]
    build:
      context: .
      dockerfile: apps/asr-sensevoice/Dockerfile
      args:
        # 服务器在阿里云 — 用国内镜像源提速
        PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple
        DEBIAN_MIRROR: mirrors.aliyun.com
    restart: always
    volumes:
      - ${PAC_MODELS_DIR:-../pac-models}/sensevoice:/models/sensevoice:ro
    # 不发布端口 — 仅 compose 内网供 pac-service 调用(无鉴权)
    cpus: 2
    mem_limit: 2g

  pac-web:
    logging: *default-logging
    build:
      context: .
      dockerfile: apps/pac-web/Dockerfile
      target: prod
      args:
        # build-time inline 进客户端 bundle,改了必须 --build
        # 来自 apps/pac-web/.env(启动时用 --env-file 指定那份)
        NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL}
        # Sentry:未设则空(前端 Sentry no-op),不阻塞构建
        NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN:-}
        NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${NEXT_PUBLIC_SENTRY_ENVIRONMENT:-}
        NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: ${NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE:-0.1}
    restart: always
    depends_on:
      - pac-service
    env_file: ./apps/pac-web/.env
    environment:
      NODE_ENV: production
      PORT: 3100
    ports:
      - "127.0.0.1:3100:3100"

  pac-docs:
    logging: *default-logging
    build:
      context: .
      dockerfile: apps/pac-docs/Dockerfile
      target: prod
      args:
        # OpenAPI 参考页 Scalar 的 server 地址(cURL/Send base);本地默认 localhost:3101,线上注入生产域名
        DOCS_API_URL: https://pac.jarvismedical.asia
    restart: always
    environment:
      NODE_ENV: production
      PORT: 3102
    ports:
      - "127.0.0.1:3102:3102"

volumes:
  postgres_data:
  redis_data:
