# ─────────────────────────────────────────────────────────────────
# SSO HA Infrastructure — PostgreSQL Patroni Cluster + PgBouncer + HAProxy
# Extends docker-compose.yml (requires postgres, mongo, redis, minio services)
#
# Usage:
#   docker compose -f docker-compose.yml -f docker-compose.ha.yml up -d
#
# Services added by this file:
#   etcd1, etcd2, etcd3  — distributed consensus store
#   postgres1, postgres2, postgres3 — Patroni-managed PostgreSQL cluster
#   haproxy              — routes write (5432) / read (5433)
#   pgbouncer            — connection pooling (transaction mode)
# ─────────────────────────────────────────────────────────────────

x-pool-common: &pool-common
  max_client_conn: 500
  default_pool_size: 25
  pool_mode: transaction
  server_lifetime: 3600
  server_idle_timeout: 600

services:
  # ── etcd cluster ────────────────────────────────────────────────
  etcd1:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: sso-etcd1
    hostname: etcd1
    environment:
      ETCD_NAME: etcd1
      ETCD_DATA_DIR: /etcd-data
      ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
      ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
      ETCD_INITIAL_CLUSTER: etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      ETCD_INITIAL_CLUSTER_STATE: new
      ETCD_INITIAL_CLUSTER_TOKEN: sso-ha-cluster
      ETCD_ADVERTISE_CLIENT_URLS: http://etcd1:2379
      ETCD_ENABLE_V2: true
    ports:
      - '2379:2379'
    volumes:
      - etcd1-data:/etcd-data
    networks:
      - sso-network
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'etcdctl', '--endpoints=http://localhost:2379', 'endpoint', 'health']
      interval: 10s
      timeout: 5s
      retries: 5

  etcd2:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: sso-etcd2
    hostname: etcd2
    environment:
      ETCD_NAME: etcd2
      ETCD_DATA_DIR: /etcd-data
      ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
      ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
      ETCD_INITIAL_CLUSTER: etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      ETCD_INITIAL_CLUSTER_STATE: new
      ETCD_INITIAL_CLUSTER_TOKEN: sso-ha-cluster
      ETCD_ADVERTISE_CLIENT_URLS: http://etcd2:2379
      ETCD_ENABLE_V2: true
    ports:
      - '2380:2379'
    volumes:
      - etcd2-data:/etcd-data
    networks:
      - sso-network
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'etcdctl', '--endpoints=http://localhost:2379', 'endpoint', 'health']
      interval: 10s
      timeout: 5s
      retries: 5

  etcd3:
    image: quay.io/coreos/etcd:v3.5.15
    container_name: sso-etcd3
    hostname: etcd3
    environment:
      ETCD_NAME: etcd3
      ETCD_DATA_DIR: /etcd-data
      ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
      ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
      ETCD_INITIAL_CLUSTER: etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      ETCD_INITIAL_CLUSTER_STATE: new
      ETCD_INITIAL_CLUSTER_TOKEN: sso-ha-cluster
      ETCD_ADVERTISE_CLIENT_URLS: http://etcd3:2379
      ETCD_ENABLE_V2: true
    volumes:
      - etcd3-data:/etcd-data
    networks:
      - sso-network
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'etcdctl', '--endpoints=http://localhost:2379', 'endpoint', 'health']
      interval: 10s
      timeout: 5s
      retries: 5

  # ── PostgreSQL Patroni cluster ─────────────────────────────────
  # NOTE: Requires custom Docker image with Patroni installed.
  # Build with: docker build -t sso-vietprodev-postgres -f Dockerfile.patroni .
  postgres1:
    image: sso-vietprodev-postgres:latest
    container_name: sso-postgres1
    hostname: postgres1
    user: postgres
    environment:
      PATRONI_SCOPE: sso-postgres
      PATRONI_NAME: postgres1
    volumes:
      - postgres1-data:/var/lib/postgresql/data
    networks:
      - sso-network
    restart: unless-stopped

  postgres2:
    image: sso-vietprodev-postgres:latest
    container_name: sso-postgres2
    hostname: postgres2
    user: postgres
    environment:
      PATRONI_SCOPE: sso-postgres
      PATRONI_NAME: postgres2
    volumes:
      - postgres2-data:/var/lib/postgresql/data
    networks:
      - sso-network
    restart: unless-stopped

  postgres3:
    image: sso-vietprodev-postgres:latest
    container_name: sso-postgres3
    hostname: postgres3
    user: postgres
    environment:
      PATRONI_SCOPE: sso-postgres
      PATRONI_NAME: postgres3
    volumes:
      - postgres3-data:/var/lib/postgresql/data
    networks:
      - sso-network
    restart: unless-stopped

  # ── HAProxy ──────────────────────────────────────────────────────
  # Routes: :5432 (write) → primary PG  |  :5433 (read) → all replicas
  haproxy:
    image: haproxy:2.9
    container_name: sso-haproxy
    hostname: haproxy
    ports:
      - '5433:5432'   # Write endpoint
      - '5434:5433'   # Read endpoint
      - '8404:8404'   # Stats UI
    volumes:
      - ./infrastructure/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    depends_on:
      postgres1:
        condition: service_started
      postgres2:
        condition: service_started
      postgres3:
        condition: service_started
    networks:
      - sso-network
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'haproxy', '-c', '-f', '/usr/local/etc/haproxy/haproxy.cfg']
      interval: 10s
      timeout: 5s
      retries: 3

  # ── PgBouncer ───────────────────────────────────────────────────
  # Connection pooling in transaction mode in front of HAProxy
  pgbouncer:
    image: edoburu/pgbouncer:latest
    container_name: sso-pgbouncer
    hostname: pgbouncer
    environment:
      DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@haproxy:5432/${DB_NAME:-sso}
      POOL_MODE: transaction
      MAX_CLIENT_CONN: 500
      DEFAULT_POOL_SIZE: 25
    ports:
      - '6432:5432'
    volumes:
      - ./infrastructure/pgbouncer/userlist.txt:/etc/pgbouncer/userlist.txt:ro
    depends_on:
      haproxy:
        condition: service_started
    networks:
      - sso-network
    restart: unless-stopped

volumes:
  etcd1-data:
  etcd2-data:
  etcd3-data:
  postgres1-data:
  postgres2-data:
  postgres3-data:
