# =============================================================================
# Sentry Self-Hosted - 崩溃收集与错误追踪
# =============================================================================
#
# 功能：
#   - 崩溃收集（Flutter + Android/iOS 原生层）
#   - 错误追踪与符号化
#   - 性能监控
#   - Session Replay
#   - 设备兼容性分析
#
# 使用方法:
#   ./deploy.sh up        # 首次启动（会自动初始化）
#   ./deploy.sh down      # 停止
#   ./deploy.sh logs      # 查看日志
#
# 系统要求:
#   - Docker 20.10+
#   - Docker Compose v2+
#   - 最低 4GB 内存，推荐 8GB+
#   - 约 20GB 磁盘空间
#
# =============================================================================

services:
  # ===========================================================================
  # Redis - 缓存与消息队列
  # ===========================================================================
  sentry-redis:
    image: redis:7-alpine
    container_name: sentry-redis
    volumes:
      - sentry_redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # PostgreSQL - 主数据库
  # ===========================================================================
  sentry-postgres:
    image: postgres:15-alpine
    container_name: sentry-postgres
    environment:
      POSTGRES_USER: sentry
      POSTGRES_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      POSTGRES_DB: sentry
    volumes:
      - sentry_postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sentry"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # Kafka + Zookeeper - 事件流处理
  # ===========================================================================
  sentry-zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: sentry-zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    volumes:
      - sentry_zookeeper_data:/var/lib/zookeeper/data
      - sentry_zookeeper_log:/var/lib/zookeeper/log
    healthcheck:
      test: ["CMD", "nc", "-z", "localhost", "2181"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - sentry

  sentry-kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: sentry-kafka
    depends_on:
      sentry-zookeeper:
        condition: service_healthy
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: sentry-zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://sentry-kafka:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
    volumes:
      - sentry_kafka_data:/var/lib/kafka/data
    healthcheck:
      test: ["CMD", "kafka-broker-api-versions", "--bootstrap-server", "localhost:9092"]
      interval: 10s
      timeout: 10s
      retries: 5
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # ClickHouse - 事件存储（高性能分析）
  # ===========================================================================
  sentry-clickhouse:
    image: clickhouse/clickhouse-server:23.8-alpine
    container_name: sentry-clickhouse
    volumes:
      - sentry_clickhouse_data:/var/lib/clickhouse
      - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/sentry.xml:ro
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8123/ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # Snuba - 事件搜索与聚合
  # ===========================================================================
  sentry-snuba-api:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-api
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: api
    restart: unless-stopped
    networks:
      - sentry

  sentry-snuba-consumer:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-consumer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: consumer --storage errors --auto-offset-reset=latest --max-batch-time-ms 750
    restart: unless-stopped
    networks:
      - sentry

  sentry-snuba-outcomes-consumer:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-outcomes-consumer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: consumer --storage outcomes_raw --auto-offset-reset=earliest --max-batch-time-ms 750
    restart: unless-stopped
    networks:
      - sentry

  sentry-snuba-sessions-consumer:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-sessions-consumer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: consumer --storage sessions_raw --auto-offset-reset=latest --max-batch-time-ms 750
    restart: unless-stopped
    networks:
      - sentry

  sentry-snuba-transactions-consumer:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-transactions-consumer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: consumer --storage transactions --auto-offset-reset=latest --max-batch-time-ms 750
    restart: unless-stopped
    networks:
      - sentry

  sentry-snuba-replacer:
    image: getsentry/snuba:24.1.0
    container_name: sentry-snuba-replacer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-clickhouse:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SNUBA_SETTINGS: docker
      CLICKHOUSE_HOST: sentry-clickhouse
      REDIS_HOST: sentry-redis
      KAFKA_BROKER_HOST: sentry-kafka
    command: replacer --storage errors --auto-offset-reset=latest --max-batch-size 3
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # Sentry Core Services
  # ===========================================================================
  sentry-web:
    image: getsentry/sentry:24.1.0
    container_name: sentry-web
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-postgres:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
      sentry-snuba-api:
        condition: service_started
    ports:
      - "${SENTRY_PORT:-9000}:9000"
    environment:
      SENTRY_SECRET_KEY: ${SENTRY_SECRET_KEY:-please_change_this_to_a_random_string_at_least_50_chars}
      SENTRY_POSTGRES_HOST: sentry-postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      SENTRY_REDIS_HOST: sentry-redis
      SNUBA: http://sentry-snuba-api:1218
      SENTRY_KAFKA_HOST: sentry-kafka
      # 邮件配置（可选）
      SENTRY_EMAIL_HOST: ${SENTRY_EMAIL_HOST:-}
      SENTRY_EMAIL_PORT: ${SENTRY_EMAIL_PORT:-587}
      SENTRY_EMAIL_USER: ${SENTRY_EMAIL_USER:-}
      SENTRY_EMAIL_PASSWORD: ${SENTRY_EMAIL_PASSWORD:-}
      SENTRY_EMAIL_USE_TLS: ${SENTRY_EMAIL_USE_TLS:-true}
      SENTRY_SERVER_EMAIL: ${SENTRY_SERVER_EMAIL:-sentry@localhost}
      # 系统配置
      SENTRY_SINGLE_ORGANIZATION: "true"
    volumes:
      - sentry_data:/data
      - ./sentry.conf.py:/etc/sentry/sentry.conf.py:ro
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:9000/_health/ || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    restart: unless-stopped
    networks:
      - sentry

  sentry-worker:
    image: getsentry/sentry:24.1.0
    container_name: sentry-worker
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-postgres:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SENTRY_SECRET_KEY: ${SENTRY_SECRET_KEY:-please_change_this_to_a_random_string_at_least_50_chars}
      SENTRY_POSTGRES_HOST: sentry-postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      SENTRY_REDIS_HOST: sentry-redis
      SNUBA: http://sentry-snuba-api:1218
      SENTRY_KAFKA_HOST: sentry-kafka
    command: run worker
    volumes:
      - sentry_data:/data
      - ./sentry.conf.py:/etc/sentry/sentry.conf.py:ro
    restart: unless-stopped
    networks:
      - sentry

  sentry-cron:
    image: getsentry/sentry:24.1.0
    container_name: sentry-cron
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-postgres:
        condition: service_healthy
    environment:
      SENTRY_SECRET_KEY: ${SENTRY_SECRET_KEY:-please_change_this_to_a_random_string_at_least_50_chars}
      SENTRY_POSTGRES_HOST: sentry-postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      SENTRY_REDIS_HOST: sentry-redis
      SNUBA: http://sentry-snuba-api:1218
      SENTRY_KAFKA_HOST: sentry-kafka
    command: run cron
    volumes:
      - sentry_data:/data
      - ./sentry.conf.py:/etc/sentry/sentry.conf.py:ro
    restart: unless-stopped
    networks:
      - sentry

  sentry-ingest-consumer:
    image: getsentry/sentry:24.1.0
    container_name: sentry-ingest-consumer
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-postgres:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SENTRY_SECRET_KEY: ${SENTRY_SECRET_KEY:-please_change_this_to_a_random_string_at_least_50_chars}
      SENTRY_POSTGRES_HOST: sentry-postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      SENTRY_REDIS_HOST: sentry-redis
      SNUBA: http://sentry-snuba-api:1218
      SENTRY_KAFKA_HOST: sentry-kafka
    command: run ingest-consumer --all-consumer-types
    volumes:
      - sentry_data:/data
      - ./sentry.conf.py:/etc/sentry/sentry.conf.py:ro
    restart: unless-stopped
    networks:
      - sentry

  sentry-post-process-forwarder:
    image: getsentry/sentry:24.1.0
    container_name: sentry-post-process-forwarder
    depends_on:
      sentry-redis:
        condition: service_healthy
      sentry-postgres:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    environment:
      SENTRY_SECRET_KEY: ${SENTRY_SECRET_KEY:-please_change_this_to_a_random_string_at_least_50_chars}
      SENTRY_POSTGRES_HOST: sentry-postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: ${SENTRY_DB_PASSWORD:-sentry_secret_password}
      SENTRY_REDIS_HOST: sentry-redis
      SNUBA: http://sentry-snuba-api:1218
      SENTRY_KAFKA_HOST: sentry-kafka
    command: run post-process-forwarder --commit-batch-size 1
    volumes:
      - sentry_data:/data
      - ./sentry.conf.py:/etc/sentry/sentry.conf.py:ro
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # Relay - 事件接收网关
  # ===========================================================================
  sentry-relay:
    image: getsentry/relay:24.1.0
    container_name: sentry-relay
    depends_on:
      sentry-web:
        condition: service_healthy
      sentry-kafka:
        condition: service_healthy
    ports:
      - "${SENTRY_RELAY_PORT:-3000}:3000"
    volumes:
      - ./relay/config.yml:/work/.relay/config.yml:ro
      - ./relay/credentials.json:/work/.relay/credentials.json:ro
    restart: unless-stopped
    networks:
      - sentry

  # ===========================================================================
  # Symbolicator - 符号化服务（崩溃堆栈解析）
  # ===========================================================================
  sentry-symbolicator:
    image: getsentry/symbolicator:24.1.0
    container_name: sentry-symbolicator
    volumes:
      - sentry_symbolicator_data:/data
      - ./symbolicator/config.yml:/etc/symbolicator/config.yml:ro
    command: run -c /etc/symbolicator/config.yml
    restart: unless-stopped
    networks:
      - sentry

# =============================================================================
# Volumes
# =============================================================================
volumes:
  sentry_redis_data:
    driver: local
  sentry_postgres_data:
    driver: local
  sentry_kafka_data:
    driver: local
  sentry_zookeeper_data:
    driver: local
  sentry_zookeeper_log:
    driver: local
  sentry_clickhouse_data:
    driver: local
  sentry_data:
    driver: local
  sentry_symbolicator_data:
    driver: local

# =============================================================================
# Networks
# =============================================================================
networks:
  sentry:
    driver: bridge
    name: sentry
