mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-03-28 17:42:56 +01:00
Operators can now disable specific pages (Dashboard, Nodes, Advertisements, Messages, Map, Members, Pages) via FEATURE_* environment variables. Disabled features are fully hidden: removed from navigation, return 404 on routes, and excluded from sitemap/robots.txt. Dashboard auto-disables when all of Nodes/Advertisements/Messages are off. Map auto-disables when Nodes is off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
337 lines
12 KiB
YAML
337 lines
12 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:-}
|
|
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_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"]
|
|
|
|
# ==========================================================================
|
|
# Volumes
|
|
# ==========================================================================
|
|
volumes:
|
|
hub_data:
|
|
name: meshcore_hub_data
|
|
mosquitto_data:
|
|
name: meshcore_mosquitto_data
|
|
mosquitto_log:
|
|
name: meshcore_mosquitto_log
|