version: "3.9"

services:
  mesh-controller:
    image: ghcr.io/community-mesh/controller:2.4.1
    restart: unless-stopped
    environment:
      MESH_DOMAIN: mesh.example.net
      DATABASE_URL: postgresql://mesh:${DB_PASSWORD}@postgres:5432/mesh
      REDIS_URL: redis://redis:6379/0
      TELEMETRY_RETENTION_DAYS: "90"
      NODE_HEARTBEAT_TIMEOUT: "180s"
      FIRMWARE_CHANNEL: stable
    ports:
      - "8443:8443"
    volumes:
      - controller-data:/var/lib/mesh-controller
      - ./config/controller.yaml:/etc/mesh/controller.yaml:ro
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - management

  routing-observer:
    image: ghcr.io/community-mesh/routing-observer:1.8.0
    restart: unless-stopped
    command:
      - "--protocols=batman-adv,babel"
      - "--poll-interval=30s"
      - "--controller=https://mesh-controller:8443"
    environment:
      CONTROLLER_TOKEN_FILE: /run/secrets/observer_token
    secrets:
      - observer_token
    cap_add:
      - NET_ADMIN
      - NET_RAW
    network_mode: host
    depends_on:
      - mesh-controller

  radius:
    image: freeradius/freeradius-server:3.2.6
    restart: unless-stopped
    volumes:
      - ./config/radius:/etc/freeradius/3.0:ro
    ports:
      - "1812:1812/udp"
      - "1813:1813/udp"
    networks:
      - management

  prometheus:
    image: prom/prometheus:v2.54.1
    restart: unless-stopped
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.retention.time=30d"
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "127.0.0.1:9090:9090"
    networks:
      - management

  grafana:
    image: grafana/grafana:11.2.0
    restart: unless-stopped
    environment:
      GF_SERVER_ROOT_URL: https://status.mesh.example.net
      GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin_password
      GF_AUTH_ANONYMOUS_ENABLED: "false"
    secrets:
      - grafana_admin_password
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "127.0.0.1:3000:3000"
    networks:
      - management
    depends_on:
      - prometheus

  postgres:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: mesh
      POSTGRES_USER: mesh
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    secrets:
      - postgres_password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mesh -d mesh"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - management

  redis:
    image: redis:7.4-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb"]
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - management

networks:
  management:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.40.0/24

volumes:
  controller-data:
  prometheus-data:
  grafana-data:
  postgres-data:
  redis-data:

secrets:
  observer_token:
    file: ./secrets/observer_token
  grafana_admin_password:
    file: ./secrets/grafana_admin_password
  postgres_password:
    file: ./secrets/postgres_password