# Release deployment stack: wattetheria + wattswarm.
#
# This file is intended for published image deployment.
# It should not depend on sibling local repositories or local Docker builds.
#
# Usage:
#   export WATTSWARM_PG_PASSWORD='<strong-password>'
#   docker compose -f docker-compose.release.yml up -d
#
# Notes:
# - Use coordinated image tags for wattetheria and wattswarm.
# - Prefer pinning image digests for production-grade reproducibility.
# - Avoid `container_name` so multiple environments and rolling upgrades remain possible.

services:
  kernel:
    image: ${WATTETHERIA_KERNEL_IMAGE:-wattetheria/wattetheria-kernel:latest}
    restart: unless-stopped
    env_file:
      - ${WATTETHERIA_COMPOSE_ENV_FILE:-.env.release.local}
    depends_on:
      wattswarm-kernel:
        condition: service_started
    environment:
      WATTETHERIA_DATA_DIR: /var/lib/wattetheria
      WATTETHERIA_CONTROL_PLANE_BIND: 0.0.0.0:7777
      WATTETHERIA_RUNTIME_ENV_FILE: ${WATTETHERIA_RUNTIME_ENV_FILE:-/var/lib/wattetheria-deploy/.env.release.local}
      WATTETHERIA_WATTSWARM_UI_BASE_URL: ${WATTETHERIA_WATTSWARM_UI_BASE_URL:-http://wattswarm-kernel:7788}
      WATTETHERIA_WATTSWARM_SYNC_GRPC_ENDPOINT: ${WATTETHERIA_WATTSWARM_SYNC_GRPC_ENDPOINT:-http://wattswarm-kernel:7791}
      WATTETHERIA_WATTSWARM_AGENT_EVENT_CALLBACK_BASE_URL: ${WATTETHERIA_WATTSWARM_AGENT_EVENT_CALLBACK_BASE_URL:-http://kernel:7777}
      WATTETHERIA_AGENT_CONTROL_PLANE_ENDPOINT: ${WATTETHERIA_AGENT_CONTROL_PLANE_ENDPOINT:-http://127.0.0.1:7777}
      WATTETHERIA_AGENT_WATTSWARM_UI_BASE_URL: ${WATTETHERIA_AGENT_WATTSWARM_UI_BASE_URL:-http://127.0.0.1:7788}
      WATTETHERIA_AGENT_WATTSWARM_SYNC_GRPC_ENDPOINT: ${WATTETHERIA_AGENT_WATTSWARM_SYNC_GRPC_ENDPOINT:-http://127.0.0.1:7791}
      WATTETHERIA_AGENT_HOST_DATA_DIR: ${WATTETHERIA_AGENT_HOST_DATA_DIR:-/var/lib/wattetheria}
      WATTETHERIA_MCP_TOKEN_AUTH: ${WATTETHERIA_MCP_TOKEN_AUTH:-false}
      WATTETHERIA_BRAIN_PROVIDER_KIND: ${WATTETHERIA_BRAIN_PROVIDER_KIND:-rules}
      WATTETHERIA_BRAIN_BASE_URL: ${WATTETHERIA_BRAIN_BASE_URL:-}
      WATTETHERIA_BRAIN_MODEL: ${WATTETHERIA_BRAIN_MODEL:-}
      WATTETHERIA_BRAIN_API_KEY_ENV: ${WATTETHERIA_BRAIN_API_KEY_ENV:-}
      WATTETHERIA_BRAIN_API_KEY: ${WATTETHERIA_BRAIN_API_KEY:-}
      WATTETHERIA_BRAIN_RUNTIME_ADAPTER: ${WATTETHERIA_BRAIN_RUNTIME_ADAPTER:-}
      WATTETHERIA_BRAIN_SESSION_HEADER_NAME: ${WATTETHERIA_BRAIN_SESSION_HEADER_NAME:-}
      WATTETHERIA_BRAIN_SESSION_MODE: ${WATTETHERIA_BRAIN_SESSION_MODE:-stable}
      WATTETHERIA_GATEWAY_URLS: ${WATTETHERIA_GATEWAY_URLS:-}
      WATTETHERIA_GATEWAY_CONFIG_PATH: ${WATTETHERIA_GATEWAY_CONFIG_PATH:-/var/lib/wattswarm/startup_config.json}
      WATTETHERIA_AUTONOMY_ENABLED: ${WATTETHERIA_AUTONOMY_ENABLED:-false}
      WATTETHERIA_AUTONOMY_INTERVAL_SEC: ${WATTETHERIA_AUTONOMY_INTERVAL_SEC:-30}
    volumes:
      - ${WATTETHERIA_DEPLOY_DIR:-.}:/var/lib/wattetheria-deploy
      - ${WATTETHERIA_HOST_STATE_DIR:-./data/wattetheria}:/var/lib/wattetheria
      - ${WATTSWARM_HOST_STATE_DIR:-./data/wattswarm}:/var/lib/wattswarm:ro
    ports:
      - "${WATTETHERIA_CONTROL_PLANE_BIND_HOST:-127.0.0.1}:${WATTETHERIA_CONTROL_PLANE_PORT:-7777}:7777"
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:7777/supervision >/dev/null"]
      interval: 10s
      timeout: 3s
      retries: 12
      start_period: 10s
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - watt-internal
    entrypoint: ["/app/scripts/docker-kernel-entrypoint.sh"]

  wattswarm-postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${WATTSWARM_PG_DB:-wattswarm}
      POSTGRES_USER: ${WATTSWARM_PG_USER:-postgres}
      POSTGRES_PASSWORD: ${WATTSWARM_PG_PASSWORD:?WATTSWARM_PG_PASSWORD must be set}
    volumes:
      - wattswarm_pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${WATTSWARM_PG_USER:-postgres} -d ${WATTSWARM_PG_DB:-wattswarm}"]
      interval: 5s
      timeout: 3s
      retries: 20
    networks:
      - watt-internal

  wattswarm-runtime:
    image: ${WATTSWARM_RUNTIME_IMAGE:-wattetheria/wattswarm-runtime:latest}
    restart: unless-stopped
    entrypoint: ["/app/target/release/wattswarm-runtime"]
    command: ["--listen", "0.0.0.0:8787"]
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8787/health > /dev/null"]
      interval: 5s
      timeout: 3s
      retries: 20
      start_period: 5s
    networks:
      - watt-internal

  wattswarm-kernel:
    image: ${WATTSWARM_KERNEL_IMAGE:-wattetheria/wattswarm-kernel:latest}
    restart: unless-stopped
    depends_on:
      wattswarm-postgres:
        condition: service_healthy
      wattswarm-runtime:
        condition: service_healthy
    environment:
      WATTSWARM_PG_URL: postgres://${WATTSWARM_PG_USER:-postgres}:${WATTSWARM_PG_PASSWORD:?WATTSWARM_PG_PASSWORD must be set}@wattswarm-postgres:5432/${WATTSWARM_PG_DB:-wattswarm}
      WATTSWARM_STATE_DIR: /var/lib/wattswarm
      WATTSWARM_STORE_NAME: wattswarm.state
      WATTSWARM_UI_LISTEN: 0.0.0.0:7788
      WATTSWARM_WATTETHERIA_SYNC_GRPC_LISTEN: "0.0.0.0:7791"
      WATTSWARM_P2P_ENABLED: ${WATTSWARM_P2P_ENABLED:-true}
      WATTSWARM_P2P_MDNS: ${WATTSWARM_P2P_MDNS:-true}
      WATTSWARM_P2P_PORT: ${WATTSWARM_P2P_PORT:-4001}
      WATTSWARM_DISCOVERY_AGENT_CARD_ENABLED: ${WATTSWARM_DISCOVERY_AGENT_CARD_ENABLED:-true}
      WATTSWARM_DISCOVERY_AGENT_CARD_URL: ${WATTETHERIA_WATTSWARM_AGENT_EVENT_CALLBACK_BASE_URL:-http://kernel:7777}
      WATTSWARM_BOOTSTRAP_EXECUTOR_NAME: rt
      WATTSWARM_BOOTSTRAP_EXECUTOR_URL: http://wattswarm-runtime:8787
      WATTSWARM_UDP_ANNOUNCE_ENABLED: ${WATTSWARM_UDP_ANNOUNCE_ENABLED:-false}
      WATTSWARM_UDP_ANNOUNCE_MODE: ${WATTSWARM_UDP_ANNOUNCE_MODE:-multicast}
      WATTSWARM_UDP_ANNOUNCE_ADDR: ${WATTSWARM_UDP_ANNOUNCE_ADDR:-239.255.42.99}
      WATTSWARM_UDP_ANNOUNCE_PORT: ${WATTSWARM_UDP_ANNOUNCE_PORT:-37931}
      WATTSWARM_IROH_RELAY_URLS: ${WATTSWARM_IROH_RELAY_URLS:-https://relay.wattetheria.com}
      WATTSWARM_IROH_BIND_ADDR: ${WATTSWARM_IROH_BIND_ADDR:-0.0.0.0:4002}
      WATTSWARM_IROH_PUBLISH_DIRECT_ADDRS: ${WATTSWARM_IROH_PUBLISH_DIRECT_ADDRS:-false}
      WATTSWARM_IROH_DATA_PLANE_START_TIMEOUT_MS: ${WATTSWARM_IROH_DATA_PLANE_START_TIMEOUT_MS:-120000}
      WATTSWARM_PUBLIC_BOOTSTRAP_URLS: ${WATTSWARM_PUBLIC_BOOTSTRAP_URLS:-}
      WATTSWARM_PUBLIC_BOOTSTRAP_CONTACTS: ${WATTSWARM_PUBLIC_BOOTSTRAP_CONTACTS:-}
      WATTSWARM_PUBLIC_GATEWAY_URLS: ${WATTSWARM_PUBLIC_GATEWAY_URLS:-}
      WATTSWARM_PUBLIC_DISCOVERY_URLS: ${WATTSWARM_PUBLIC_DISCOVERY_URLS:-}
      WATTSWARM_NETWORK_JOIN_MANIFEST_URLS: ${WATTSWARM_NETWORK_JOIN_MANIFEST_URLS:-}
    volumes:
      - ${WATTSWARM_HOST_STATE_DIR:-./data/wattswarm}:/var/lib/wattswarm
      - ${WATTETHERIA_HOST_STATE_DIR:-./data/wattetheria}:/var/lib/wattetheria:ro
    ports:
      - "${WATTSWARM_UI_BIND_HOST:-127.0.0.1}:${WATTSWARM_UI_PORT:-7788}:7788"
      - "${WATTSWARM_SYNC_GRPC_BIND_HOST:-127.0.0.1}:${WATTSWARM_SYNC_GRPC_PORT:-7791}:7791"
      - "${WATTSWARM_P2P_HOST_PORT:-4001}:${WATTSWARM_P2P_PORT:-4001}"
      - "${WATTSWARM_IROH_HOST_PORT:-4002}:4002/udp"
      - "${WATTSWARM_UDP_ANNOUNCE_HOST_PORT:-37931}:${WATTSWARM_UDP_ANNOUNCE_PORT:-37931}/udp"
    networks:
      - watt-internal
    entrypoint: ["/app/scripts/docker-kernel-entrypoint.sh"]

  wattswarm-worker:
    image: ${WATTSWARM_WORKER_IMAGE:-wattetheria/wattswarm-worker:latest}
    restart: unless-stopped
    depends_on:
      wattswarm-postgres:
        condition: service_healthy
      wattswarm-runtime:
        condition: service_healthy
      wattswarm-kernel:
        condition: service_started
    environment:
      WATTSWARM_PG_URL: postgres://${WATTSWARM_PG_USER:-postgres}:${WATTSWARM_PG_PASSWORD:?WATTSWARM_PG_PASSWORD must be set}@wattswarm-postgres:5432/${WATTSWARM_PG_DB:-wattswarm}
      WATTSWARM_STATE_DIR: /var/lib/wattswarm
      WATTSWARM_STORE_NAME: wattswarm.state
      WATTSWARM_WORKER_CONCURRENCY: ${WATTSWARM_WORKER_CONCURRENCY:-16}
      WATTSWARM_WORKER_POLL_MS: ${WATTSWARM_WORKER_POLL_MS:-250}
      WATTSWARM_WORKER_LEASE_MS: ${WATTSWARM_WORKER_LEASE_MS:-30000}
      # Worker shares state_dir with wattswarm-kernel; kernel owns the iroh data
      # plane (gossip, backfill, agent-event delivery). Disabling P2P here keeps
      # the worker from trying to start a second iroh stack and contending on
      # iroh-blobs/blobs.db's exclusive flock, which would otherwise stall the
      # kernel's network bridge for the configured startup timeout.
      WATTSWARM_P2P_ENABLED: "false"
    volumes:
      - ${WATTSWARM_HOST_STATE_DIR:-./data/wattswarm}:/var/lib/wattswarm
    networks:
      - watt-internal
    entrypoint: ["/app/target/release/wattswarm"]
    command:
      - --state-dir
      - /var/lib/wattswarm
      - --store
      - wattswarm.state
      - run
      - --pg-url
      - postgres://${WATTSWARM_PG_USER:-postgres}:${WATTSWARM_PG_PASSWORD:?WATTSWARM_PG_PASSWORD must be set}@wattswarm-postgres:5432/${WATTSWARM_PG_DB:-wattswarm}
      - worker
      - --concurrency
      - "${WATTSWARM_WORKER_CONCURRENCY:-16}"
      - --poll-ms
      - "${WATTSWARM_WORKER_POLL_MS:-250}"
      - --lease-ms
      - "${WATTSWARM_WORKER_LEASE_MS:-30000}"

volumes:
  wattswarm_pg_data:

networks:
  watt-internal:
    driver: bridge
