# Local development stack for the Galaxy UI.
#
# Brings up postgres + redis + mailpit + backend + gateway so the UI
# Vite dev server (run on the host with `pnpm -C ui/frontend dev`) can
# talk to a real authenticated stack without any cloud dependency.
#
# Browser:        http://localhost:8080  (gateway public REST + Connect-Web)
# Mailpit UI:     http://localhost:8025
# Postgres:       localhost:5433 (host-mapped)
# Redis:          localhost:6380 (host-mapped)
#
# Bring up:    make -C tools/local-dev up
# Tear down:   make -C tools/local-dev down
# Wipe state:  make -C tools/local-dev clean
#
# The backend reads `BACKEND_AUTH_DEV_FIXED_CODE=123456` from the
# `.env` file alongside this compose; ConfirmEmailCode accepts that
# literal in addition to the real bcrypt-verified code, so a developer
# can log in without touching Mailpit. Real codes still arrive in
# Mailpit; both paths coexist.

name: galaxy-local-dev

services:
  postgres:
    image: postgres:16-alpine
    container_name: galaxy-local-dev-postgres
    restart: unless-stopped
    labels:
      galaxy.stack: local-dev
    environment:
      POSTGRES_USER: galaxy
      POSTGRES_PASSWORD: galaxy
      POSTGRES_DB: galaxy_backend
    ports:
      - "${LOCAL_DEV_POSTGRES_PORT:-5433}:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - galaxy-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U galaxy -d galaxy_backend"]
      interval: 3s
      timeout: 3s
      retries: 30
      start_period: 5s

  redis:
    image: redis:7-alpine
    container_name: galaxy-local-dev-redis
    restart: unless-stopped
    labels:
      galaxy.stack: local-dev
    command:
      - redis-server
      - --requirepass
      - galaxy-dev
      - --appendonly
      - "no"
      - --save
      - ""
    ports:
      - "${LOCAL_DEV_REDIS_PORT:-6380}:6379"
    networks:
      - galaxy-net
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "galaxy-dev", "PING"]
      interval: 3s
      timeout: 3s
      retries: 30
      start_period: 3s

  mailpit:
    image: axllent/mailpit:v1.21
    container_name: galaxy-local-dev-mailpit
    restart: unless-stopped
    labels:
      galaxy.stack: local-dev
    ports:
      - "${LOCAL_DEV_MAILPIT_PORT:-8025}:8025"
    networks:
      - galaxy-net
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://localhost:8025/livez"]
      interval: 3s
      timeout: 3s
      retries: 30
      start_period: 3s

  backend:
    build:
      context: ../..
      dockerfile: tools/local-dev/backend.Dockerfile
    image: galaxy/backend:local-dev
    container_name: galaxy-local-dev-backend
    restart: unless-stopped
    labels:
      galaxy.stack: local-dev
    user: "0:0"
    depends_on:
      postgres:
        condition: service_healthy
      mailpit:
        condition: service_healthy
    environment:
      BACKEND_LOGGING_LEVEL: debug
      BACKEND_HTTP_LISTEN_ADDR: ":8080"
      BACKEND_GRPC_PUSH_LISTEN_ADDR: ":8081"
      BACKEND_POSTGRES_DSN: "postgres://galaxy:galaxy@postgres:5432/galaxy_backend?search_path=backend&sslmode=disable"
      BACKEND_SMTP_HOST: mailpit
      BACKEND_SMTP_PORT: "1025"
      BACKEND_SMTP_FROM: "galaxy-backend@galaxy.local"
      BACKEND_SMTP_TLS_MODE: none
      BACKEND_DOCKER_NETWORK: galaxy-local-dev-net
      BACKEND_STACK_LABEL: local-dev
      BACKEND_GAME_STATE_ROOT: /tmp/galaxy-game-state
      BACKEND_GEOIP_DB_PATH: /var/lib/galaxy/geoip.mmdb
      BACKEND_NOTIFICATION_ADMIN_EMAIL: admin@galaxy.local
      BACKEND_AUTH_CHALLENGE_THROTTLE_MAX: "100"
      BACKEND_MAIL_WORKER_INTERVAL: 500ms
      BACKEND_NOTIFICATION_WORKER_INTERVAL: 500ms
      BACKEND_OTEL_TRACES_EXPORTER: none
      BACKEND_OTEL_METRICS_EXPORTER: none
      BACKEND_AUTH_DEV_FIXED_CODE: ${BACKEND_AUTH_DEV_FIXED_CODE:-}
      BACKEND_DEV_SANDBOX_EMAIL: ${BACKEND_DEV_SANDBOX_EMAIL:-}
      BACKEND_DEV_SANDBOX_ENGINE_IMAGE: ${BACKEND_DEV_SANDBOX_ENGINE_IMAGE:-}
      BACKEND_DEV_SANDBOX_ENGINE_VERSION: ${BACKEND_DEV_SANDBOX_ENGINE_VERSION:-}
      BACKEND_DEV_SANDBOX_PLAYER_COUNT: ${BACKEND_DEV_SANDBOX_PLAYER_COUNT:-}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      # Per-game state directories live under the same absolute path
      # both inside the backend container and on the Docker daemon
      # host (colima VM), so the bind-mount source the backend hands
      # to the daemon resolves correctly when spawning engine
      # containers. See backend/internal/runtime/service.go:454.
      - type: bind
        source: /tmp/galaxy-game-state
        target: /tmp/galaxy-game-state
        bind:
          create_host_path: true
      - ../../pkg/geoip/test-data/test-data/GeoIP2-Country-Test.mmdb:/var/lib/galaxy/geoip.mmdb:ro
    networks:
      - galaxy-net
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"]
      interval: 3s
      timeout: 3s
      retries: 60
      start_period: 10s

  gateway:
    build:
      context: ../..
      dockerfile: tools/local-dev/gateway.Dockerfile
    image: galaxy/gateway:local-dev
    container_name: galaxy-local-dev-gateway
    restart: unless-stopped
    labels:
      galaxy.stack: local-dev
    depends_on:
      backend:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      GATEWAY_LOG_LEVEL: debug
      GATEWAY_PUBLIC_HTTP_ADDR: ":8080"
      GATEWAY_AUTHENTICATED_GRPC_ADDR: ":9090"
      GATEWAY_BACKEND_HTTP_URL: "http://backend:8080"
      GATEWAY_BACKEND_GRPC_PUSH_URL: "backend:8081"
      GATEWAY_BACKEND_GATEWAY_CLIENT_ID: local-dev-gateway-1
      GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH: /run/secrets/gateway-response.pem
      GATEWAY_REDIS_MASTER_ADDR: "redis:6379"
      GATEWAY_REDIS_PASSWORD: galaxy-dev
      # Loosen anti-abuse so a developer hammering the form does not
      # rate-limit themselves between cycles.
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_BURST: "1000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST: "1000"
      # public_misc class wraps the authenticated EdgeGateway gRPC
      # endpoints (ExecuteCommand, SubscribeEvents). The gateway's
      # default for this class is 0 bytes, which rejects every
      # non-empty body with HTTP 413; override with a generous limit
      # so browser-side commands carrying signed envelopes go through.
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_MAX_BODY_BYTES: "131072"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_BURST: "1000"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_MAX_BODY_BYTES: "65536"
      GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_MAX_BODY_BYTES: "65536"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_BURST: "1000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_BURST: "1000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_BURST: "1000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_REQUESTS: "10000"
      GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_BURST: "1000"
    ports:
      - "${LOCAL_DEV_GATEWAY_REST_PORT:-8080}:8080"
      # Authenticated EdgeGateway connect-web/gRPC listener. The
      # browser reaches it via the Vite dev proxy in
      # ui/frontend/vite.config.ts.
      - "${LOCAL_DEV_GATEWAY_GRPC_PORT:-9090}:9090"
    volumes:
      - ./keys/gateway-response.pem:/run/secrets/gateway-response.pem:ro
    networks:
      - galaxy-net
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://localhost:8080/healthz"]
      interval: 3s
      timeout: 3s
      retries: 30
      start_period: 5s

networks:
  galaxy-net:
    name: galaxy-local-dev-net

# See note in tools/dev-deploy/docker-compose.yml — labels live only
# on services (containers), not on volumes or networks, to keep the
# compose config-hash for stateful resources stable across deploys.
volumes:
  postgres-data:
    name: galaxy-local-dev-postgres-data
