mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-03-28 17:42:56 +01:00
Add /metrics endpoint with Prometheus gauges for nodes, messages, advertisements, telemetry, trace paths, events, and members. Include per-node last_seen timestamps for alerting. Add Alertmanager service to Docker Compose metrics profile with default blackhole receiver. Add NodeNotSeen alert rule (48h threshold). Add 1h time window to all windowed metrics alongside existing 24h/7d/30d windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
387 lines
14 KiB
YAML
387 lines
14 KiB
YAML
services:
|
|
# ==========================================================================
|
|
# MQTT Broker - Eclipse Mosquitto (optional, use --profile mqtt)
|
|
# Most users will connect to an external MQTT broker instead
|
|
# ==========================================================================
|
|
mqtt:
|
|
image: eclipse-mosquitto:2
|
|
container_name: meshcore-mqtt
|
|
profiles:
|
|
- all
|
|
- mqtt
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${MQTT_EXTERNAL_PORT:-1883}:1883"
|
|
- "${MQTT_WS_PORT:-9001}:9001"
|
|
volumes:
|
|
# - ./etc/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
|
|
- mosquitto_data:/mosquitto/data
|
|
- mosquitto_log:/mosquitto/log
|
|
healthcheck:
|
|
test: ["CMD", "mosquitto_sub", "-t", "$$SYS/#", "-C", "1", "-i", "healthcheck", "-W", "3"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# ==========================================================================
|
|
# Interface Receiver - MeshCore device to MQTT bridge (events)
|
|
# ==========================================================================
|
|
interface-receiver:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-interface-receiver
|
|
profiles:
|
|
- all
|
|
- receiver
|
|
restart: unless-stopped
|
|
devices:
|
|
- "${SERIAL_PORT:-/dev/ttyUSB0}:${SERIAL_PORT:-/dev/ttyUSB0}"
|
|
user: root # Required for device access
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- MQTT_HOST=${MQTT_HOST:-mqtt}
|
|
- MQTT_PORT=${MQTT_PORT:-1883}
|
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
|
- MQTT_PREFIX=${MQTT_PREFIX:-meshcore}
|
|
- MQTT_TLS=${MQTT_TLS:-false}
|
|
- SERIAL_PORT=${SERIAL_PORT:-/dev/ttyUSB0}
|
|
- SERIAL_BAUD=${SERIAL_BAUD:-115200}
|
|
- NODE_ADDRESS=${NODE_ADDRESS:-}
|
|
command: ["interface", "receiver"]
|
|
healthcheck:
|
|
test: ["CMD", "meshcore-hub", "health", "interface"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# ==========================================================================
|
|
# Interface Sender - MQTT to MeshCore device bridge (commands)
|
|
# ==========================================================================
|
|
interface-sender:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-interface-sender
|
|
profiles:
|
|
- all
|
|
- sender
|
|
restart: unless-stopped
|
|
devices:
|
|
- "${SERIAL_PORT_SENDER:-/dev/ttyUSB1}:${SERIAL_PORT_SENDER:-/dev/ttyUSB1}"
|
|
user: root # Required for device access
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- MQTT_HOST=${MQTT_HOST:-mqtt}
|
|
- MQTT_PORT=${MQTT_PORT:-1883}
|
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
|
- MQTT_PREFIX=${MQTT_PREFIX:-meshcore}
|
|
- MQTT_TLS=${MQTT_TLS:-false}
|
|
- SERIAL_PORT=${SERIAL_PORT_SENDER:-/dev/ttyUSB1}
|
|
- SERIAL_BAUD=${SERIAL_BAUD:-115200}
|
|
- NODE_ADDRESS=${NODE_ADDRESS_SENDER:-}
|
|
command: ["interface", "sender"]
|
|
healthcheck:
|
|
test: ["CMD", "meshcore-hub", "health", "interface"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# ==========================================================================
|
|
# Interface Mock Receiver - For testing without real devices
|
|
# ==========================================================================
|
|
interface-mock-receiver:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-interface-mock-receiver
|
|
profiles:
|
|
- all
|
|
- mock
|
|
restart: unless-stopped
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- MQTT_HOST=${MQTT_HOST:-mqtt}
|
|
- MQTT_PORT=${MQTT_PORT:-1883}
|
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
|
- MQTT_PREFIX=${MQTT_PREFIX:-meshcore}
|
|
- MQTT_TLS=${MQTT_TLS:-false}
|
|
- MOCK_DEVICE=true
|
|
- NODE_ADDRESS=${NODE_ADDRESS:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
|
|
command: ["interface", "receiver", "--mock"]
|
|
healthcheck:
|
|
test: ["CMD", "meshcore-hub", "health", "interface"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# ==========================================================================
|
|
# Collector - MQTT subscriber and database storage
|
|
# ==========================================================================
|
|
collector:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-collector
|
|
profiles:
|
|
- all
|
|
- core
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
volumes:
|
|
- hub_data:/data
|
|
- ${SEED_HOME:-./seed}:/seed
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- MQTT_HOST=${MQTT_HOST:-mqtt}
|
|
- MQTT_PORT=${MQTT_PORT:-1883}
|
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
|
- MQTT_PREFIX=${MQTT_PREFIX:-meshcore}
|
|
- MQTT_TLS=${MQTT_TLS:-false}
|
|
- DATA_HOME=/data
|
|
- SEED_HOME=/seed
|
|
# Webhook configuration
|
|
- WEBHOOK_ADVERTISEMENT_URL=${WEBHOOK_ADVERTISEMENT_URL:-}
|
|
- WEBHOOK_ADVERTISEMENT_SECRET=${WEBHOOK_ADVERTISEMENT_SECRET:-}
|
|
- WEBHOOK_MESSAGE_URL=${WEBHOOK_MESSAGE_URL:-}
|
|
- WEBHOOK_MESSAGE_SECRET=${WEBHOOK_MESSAGE_SECRET:-}
|
|
- WEBHOOK_CHANNEL_MESSAGE_URL=${WEBHOOK_CHANNEL_MESSAGE_URL:-}
|
|
- WEBHOOK_CHANNEL_MESSAGE_SECRET=${WEBHOOK_CHANNEL_MESSAGE_SECRET:-}
|
|
- WEBHOOK_DIRECT_MESSAGE_URL=${WEBHOOK_DIRECT_MESSAGE_URL:-}
|
|
- WEBHOOK_DIRECT_MESSAGE_SECRET=${WEBHOOK_DIRECT_MESSAGE_SECRET:-}
|
|
- WEBHOOK_TIMEOUT=${WEBHOOK_TIMEOUT:-10.0}
|
|
- WEBHOOK_MAX_RETRIES=${WEBHOOK_MAX_RETRIES:-3}
|
|
- WEBHOOK_RETRY_BACKOFF=${WEBHOOK_RETRY_BACKOFF:-2.0}
|
|
# Data retention and cleanup configuration
|
|
- DATA_RETENTION_ENABLED=${DATA_RETENTION_ENABLED:-true}
|
|
- DATA_RETENTION_DAYS=${DATA_RETENTION_DAYS:-30}
|
|
- DATA_RETENTION_INTERVAL_HOURS=${DATA_RETENTION_INTERVAL_HOURS:-24}
|
|
- NODE_CLEANUP_ENABLED=${NODE_CLEANUP_ENABLED:-true}
|
|
- NODE_CLEANUP_DAYS=${NODE_CLEANUP_DAYS:-7}
|
|
command: ["collector"]
|
|
healthcheck:
|
|
test: ["CMD", "meshcore-hub", "health", "collector"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# ==========================================================================
|
|
# API Server - REST API for querying data and sending commands
|
|
# ==========================================================================
|
|
api:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-api
|
|
profiles:
|
|
- all
|
|
- core
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
collector:
|
|
condition: service_started
|
|
ports:
|
|
- "${API_PORT:-8000}:8000"
|
|
volumes:
|
|
- hub_data:/data
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- MQTT_HOST=${MQTT_HOST:-mqtt}
|
|
- MQTT_PORT=${MQTT_PORT:-1883}
|
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
|
- MQTT_PREFIX=${MQTT_PREFIX:-meshcore}
|
|
- MQTT_TLS=${MQTT_TLS:-false}
|
|
- DATA_HOME=/data
|
|
- API_HOST=0.0.0.0
|
|
- API_PORT=8000
|
|
- API_READ_KEY=${API_READ_KEY:-}
|
|
- API_ADMIN_KEY=${API_ADMIN_KEY:-}
|
|
- METRICS_ENABLED=${METRICS_ENABLED:-true}
|
|
- METRICS_CACHE_TTL=${METRICS_CACHE_TTL:-60}
|
|
command: ["api"]
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# ==========================================================================
|
|
# Web Dashboard - Web interface for network visualization
|
|
# ==========================================================================
|
|
web:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-web
|
|
profiles:
|
|
- all
|
|
- core
|
|
restart: unless-stopped
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
ports:
|
|
- "${WEB_PORT:-8080}:8080"
|
|
volumes:
|
|
- ${CONTENT_HOME:-./content}:/content:ro
|
|
environment:
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- API_BASE_URL=http://api:8000
|
|
# Use ADMIN key to allow write operations from admin interface
|
|
# Falls back to READ key if ADMIN key is not set
|
|
- API_KEY=${API_ADMIN_KEY:-${API_READ_KEY:-}}
|
|
- WEB_HOST=0.0.0.0
|
|
- WEB_PORT=8080
|
|
- WEB_THEME=${WEB_THEME:-dark}
|
|
- WEB_LOCALE=${WEB_LOCALE:-en}
|
|
- WEB_ADMIN_ENABLED=${WEB_ADMIN_ENABLED:-false}
|
|
- NETWORK_NAME=${NETWORK_NAME:-MeshCore Network}
|
|
- NETWORK_CITY=${NETWORK_CITY:-}
|
|
- NETWORK_COUNTRY=${NETWORK_COUNTRY:-}
|
|
- NETWORK_RADIO_CONFIG=${NETWORK_RADIO_CONFIG:-}
|
|
- NETWORK_CONTACT_EMAIL=${NETWORK_CONTACT_EMAIL:-}
|
|
- NETWORK_CONTACT_DISCORD=${NETWORK_CONTACT_DISCORD:-}
|
|
- NETWORK_CONTACT_GITHUB=${NETWORK_CONTACT_GITHUB:-}
|
|
- NETWORK_CONTACT_YOUTUBE=${NETWORK_CONTACT_YOUTUBE:-}
|
|
- NETWORK_WELCOME_TEXT=${NETWORK_WELCOME_TEXT:-}
|
|
- CONTENT_HOME=/content
|
|
- TZ=${TZ:-UTC}
|
|
# Feature flags (set to false to disable specific pages)
|
|
- FEATURE_DASHBOARD=${FEATURE_DASHBOARD:-true}
|
|
- FEATURE_NODES=${FEATURE_NODES:-true}
|
|
- FEATURE_ADVERTISEMENTS=${FEATURE_ADVERTISEMENTS:-true}
|
|
- FEATURE_MESSAGES=${FEATURE_MESSAGES:-true}
|
|
- FEATURE_MAP=${FEATURE_MAP:-true}
|
|
- FEATURE_MEMBERS=${FEATURE_MEMBERS:-true}
|
|
- FEATURE_PAGES=${FEATURE_PAGES:-true}
|
|
command: ["web"]
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
# ==========================================================================
|
|
# Database Migrations - Run Alembic migrations
|
|
# ==========================================================================
|
|
db-migrate:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-db-migrate
|
|
profiles:
|
|
- all
|
|
- core
|
|
- migrate
|
|
restart: "no"
|
|
volumes:
|
|
- hub_data:/data
|
|
environment:
|
|
- DATA_HOME=/data
|
|
command: ["db", "upgrade"]
|
|
|
|
# ==========================================================================
|
|
# Seed Data - Import node_tags.yaml and members.yaml from SEED_HOME
|
|
# NOTE: This is NOT run automatically. Use --profile seed to run explicitly.
|
|
# Since tags are now managed via the admin UI, automatic seeding would
|
|
# overwrite user changes.
|
|
# ==========================================================================
|
|
seed:
|
|
image: ghcr.io/ipnet-mesh/meshcore-hub:${IMAGE_VERSION:-latest}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
container_name: meshcore-seed
|
|
profiles:
|
|
- seed
|
|
restart: "no"
|
|
volumes:
|
|
- hub_data:/data
|
|
- ${SEED_HOME:-./seed}:/seed:ro
|
|
environment:
|
|
- DATA_HOME=/data
|
|
- SEED_HOME=/seed
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
# Imports both node_tags.yaml and members.yaml if they exist
|
|
command: ["collector", "seed"]
|
|
|
|
# ==========================================================================
|
|
# Prometheus - Metrics collection and monitoring (optional, use --profile metrics)
|
|
# ==========================================================================
|
|
prometheus:
|
|
image: prom/prometheus:latest
|
|
container_name: meshcore-prometheus
|
|
profiles:
|
|
- all
|
|
- metrics
|
|
restart: unless-stopped
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
ports:
|
|
- "${PROMETHEUS_PORT:-9090}:9090"
|
|
command:
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
- '--storage.tsdb.retention.time=30d'
|
|
volumes:
|
|
- ./etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
|
- ./etc/prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
|
|
- prometheus_data:/prometheus
|
|
|
|
# ==========================================================================
|
|
# Alertmanager - Alert routing and notifications (optional, use --profile metrics)
|
|
# ==========================================================================
|
|
alertmanager:
|
|
image: prom/alertmanager:latest
|
|
container_name: meshcore-alertmanager
|
|
profiles:
|
|
- all
|
|
- metrics
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${ALERTMANAGER_PORT:-9093}:9093"
|
|
volumes:
|
|
- ./etc/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
|
- alertmanager_data:/alertmanager
|
|
command:
|
|
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
|
- '--storage.path=/alertmanager'
|
|
|
|
# ==========================================================================
|
|
# Volumes
|
|
# ==========================================================================
|
|
volumes:
|
|
hub_data:
|
|
name: meshcore_hub_data
|
|
mosquitto_data:
|
|
name: meshcore_mosquitto_data
|
|
mosquitto_log:
|
|
name: meshcore_mosquitto_log
|
|
prometheus_data:
|
|
name: meshcore_prometheus_data
|
|
alertmanager_data:
|
|
name: meshcore_alertmanager_data
|