Files
meshcore-hub/.env.example
T
Louis King 385d1ab141 feat: add optional Redis caching layer for API endpoints
Add Redis-backed response caching for read-heavy API endpoints (nodes,
advertisements, messages, channels, dashboard, profiles) with configurable
TTL, key prefix isolation, and graceful fallback when Redis is unavailable.

New files:
- common/redis.py: CacheBackend, NullCache, RedisCacheBackend
- api/cache.py: @cached decorator, sorted_query_string helper
- tests/test_api/test_cache.py: 23 unit tests

Changes:
- pyproject.toml: add redis[hiredis] dependency
- common/config.py: 8 Redis settings on APISettings
- api/cli.py: Redis Click options + startup banner
- api/app.py: Redis lifespan init/cleanup, X-Cache middleware, health check
- 6 route files: apply @cached decorator to list endpoints
- docker-compose.yml: Redis service (cache profile), env vars
- docker-compose.dev.yml: Redis port exposure
- .env.example, README.md, AGENTS.md, docs/upgrading.md: documentation

Redis is disabled by default (REDIS_ENABLED=false). Enable with
--profile cache and REDIS_ENABLED=true.
2026-06-09 23:08:49 +01:00

505 lines
16 KiB
Bash

# MeshCore Hub - Environment Configuration
# Copy this file to .env and customize values
#
# Configuration is grouped by service. Most deployments only need:
# - Common Settings (always required)
# - MQTT Settings (always required)
# - Packet Capture Settings (for observer nodes)
#
# The Collector, API, and Web services typically run as a combined "core"
# profile and share the same data directory.
#
# -----------------------------------------------------------------------------
# QUICK START: Observer Node
# -----------------------------------------------------------------------------
# For an observer node capturing mesh traffic, you need:
#
# MQTT_HOST=your-mqtt-broker.example.com
# MQTT_PORT=1883
# MQTT_USERNAME=your_username
# MQTT_PASSWORD=your_password
# MQTT_TLS=false
# SERIAL_PORT=/dev/ttyUSB0
#
# Serial ports are typically /dev/ttyUSB[0-9] or /dev/ttyACM[0-9] on Linux.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# =============================================================================
# COMMON SETTINGS
# =============================================================================
# These settings apply to all services
# Docker Compose project name
# Used as a prefix for container names (e.g., hub-api) and volume names
# (e.g., hub_data). Change per instance when running multiple deployments
# on the same Docker host (e.g., hub-prod, hub-beta, hub-stg).
# For multi-instance setups, see the "Multi-Instance Deployment" section in README.md.
COMPOSE_PROJECT_NAME=hub
# Domain name for this instance (only needed for Traefik deployments)
# Used by docker-compose.traefik.yml to configure routing rules
# TRAEFIK_DOMAIN=meshcore.example.com
# Router priority for Traefik (higher = matched first)
# Use a higher value for more specific subdomain instances to avoid
# conflicts with wildcard routes from other instances on the same host.
# Production: 10 (default)
# Staging: 20
# MQTT broker: 30
# TRAEFIK_PRIORITY=10
# Docker image version tag to use
# Options: latest, main, v1.0.0, etc.
IMAGE_VERSION=latest
# Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
LOG_LEVEL=INFO
# Base directory for runtime data (database, etc.)
# Default: ./data (relative to docker-compose.yml location)
# Inside containers this is mapped to /data
#
# Structure:
# ${DATA_HOME}/
# └── collector/
# └── meshcore.db # SQLite database
DATA_HOME=./data
# Directory containing seed data files for import
# Default: ./seed (relative to docker-compose.yml location)
# Inside containers this is mapped to /seed
#
# Structure:
# ${SEED_HOME}/
# └── node_tags.yaml # Node tags for import
SEED_HOME=./seed
# =============================================================================
# MQTT SETTINGS
# =============================================================================
# MQTT broker connection settings for collector and API services
# Uses the MeshCore MQTT broker (WebSocket transport with subscriber auth)
# See: https://github.com/michaelhart/meshcore-mqtt-broker
# MQTT Broker host
# When using the local MQTT broker (--profile mqtt), use "mqtt"
# When using an external broker, set the hostname/IP
# For native (non-Docker) installs, use "localhost" (the Python default)
MQTT_HOST=mqtt
# MQTT Broker port
# Default: 1883 (local, plain WebSocket)
# Production behind reverse proxy: 8883 (TLS)
MQTT_PORT=1883
# MQTT subscriber authentication
# The broker uses subscriber accounts with roles:
# Role 1 (admin): full access including /internal topics
# Role 2 (full_access): all public topics, no filtering
# Role 3 (limited): filtered data (SNR, RSSI, etc. removed)
MQTT_USERNAME=
MQTT_PASSWORD=
# MQTT topic prefix for all MeshCore messages
MQTT_PREFIX=meshcore
# Enable TLS/SSL for MQTT connection
# Set to true when connecting via wss:// (e.g., behind a reverse proxy)
MQTT_TLS=false
# MQTT transport protocol
# The MeshCore MQTT broker uses WebSockets exclusively
MQTT_TRANSPORT=websockets
# MQTT WebSocket path (used when MQTT_TRANSPORT=websockets)
# Default: / (broker accepts any path)
# Production: can be set to /mqtt if reverse proxy rewrites paths
MQTT_WS_PATH=/
# JWT audience claim for packet capture authentication tokens
# Must match AUTH_EXPECTED_AUDIENCE on the broker
# Local default: mqtt.localhost
# Production: set to your broker's domain (e.g., mqtt.example.com)
MQTT_TOKEN_AUDIENCE=mqtt.localhost
# =============================================================================
# PACKET CAPTURE SETTINGS
# =============================================================================
# External packet capture service (ghcr.io/agessaman/meshcore-packet-capture)
# See https://github.com/agessaman/meshcore-packet-capture for documentation.
# Uses the "observer" compose profile.
# Publishes captured packets to MQTT in LetsMesh upload format, ingested by
# the collector.
# Serial port for the packet capture device
SERIAL_PORT=/dev/ttyUSB0
# Docker image version tag for the packet capture image
PACKETCAPTURE_IMAGE_VERSION=latest
# Connection timeout and retry settings
PACKETCAPTURE_TIMEOUT=30
PACKETCAPTURE_MAX_CONNECTION_RETRIES=5
PACKETCAPTURE_CONNECTION_RETRY_DELAY=5
PACKETCAPTURE_HEALTH_CHECK_INTERVAL=30
# IATA airport code identifier (used in Let's Mesh topic templates)
PACKETCAPTURE_IATA=LOC
# Device display name (optional, defaults to device name from meshcore connection)
# PACKETCAPTURE_ORIGIN=
# Send flood adverts at this interval in hours (0 = disabled)
PACKETCAPTURE_ADVERT_INTERVAL_HOURS=11
# RF data cache timeout in seconds
PACKETCAPTURE_RF_DATA_TIMEOUT=15.0
# -------------------
# Let's Mesh MQTT Brokers (opt-in)
# -------------------
# Enable to publish captured packets to the Let's Mesh cloud map
# Broker 1 - Let's Mesh US
PACKETCAPTURE_MQTT1_ENABLED=false
PACKETCAPTURE_MQTT1_SERVER=mqtt-us-v1.letsmesh.net
PACKETCAPTURE_MQTT1_PORT=443
PACKETCAPTURE_MQTT1_USE_TLS=true
PACKETCAPTURE_MQTT1_USE_AUTH_TOKEN=true
PACKETCAPTURE_MQTT1_TOKEN_AUDIENCE=mqtt-us-v1.letsmesh.net
PACKETCAPTURE_MQTT1_KEEPALIVE=120
# Broker 2 - Let's Mesh EU
PACKETCAPTURE_MQTT2_ENABLED=false
PACKETCAPTURE_MQTT2_SERVER=mqtt-eu-v1.letsmesh.net
PACKETCAPTURE_MQTT2_PORT=443
PACKETCAPTURE_MQTT2_USE_TLS=true
PACKETCAPTURE_MQTT2_USE_AUTH_TOKEN=true
PACKETCAPTURE_MQTT2_TOKEN_AUDIENCE=mqtt-eu-v1.letsmesh.net
PACKETCAPTURE_MQTT2_KEEPALIVE=120
# Broker 3 - Local MQTT (enabled by default, wired to hub's MQTT_* settings)
# Uses websockets and auth tokens by default (set in docker-compose.yml)
PACKETCAPTURE_MQTT3_ENABLED=true
PACKETCAPTURE_MQTT3_KEEPALIVE=60
# MQTT reconnection settings
PACKETCAPTURE_MAX_MQTT_RETRIES=5
PACKETCAPTURE_MQTT_RETRY_DELAY=5
PACKETCAPTURE_EXIT_ON_RECONNECT_FAIL=true
# =============================================================================
# COLLECTOR SETTINGS
# =============================================================================
# The collector subscribes to MQTT events and stores them in the database
# Refresh interval for reloading channel keys from the database (seconds).
# CHANNEL_REFRESH_INTERVAL_SECONDS=300
# -------------------
# Webhook Settings
# -------------------
# Webhooks forward mesh events to external HTTP endpoints as POST requests
# Webhook for advertisement events (node discovery)
WEBHOOK_ADVERTISEMENT_URL=
WEBHOOK_ADVERTISEMENT_SECRET=
# Webhook for all message events (channel and direct messages)
WEBHOOK_MESSAGE_URL=
WEBHOOK_MESSAGE_SECRET=
# Optional: Separate URLs for channel vs direct messages
# These override WEBHOOK_MESSAGE_URL if set
# WEBHOOK_CHANNEL_MESSAGE_URL=
# WEBHOOK_CHANNEL_MESSAGE_SECRET=
# WEBHOOK_DIRECT_MESSAGE_URL=
# WEBHOOK_DIRECT_MESSAGE_SECRET=
# Webhook behavior settings
WEBHOOK_TIMEOUT=10.0
WEBHOOK_MAX_RETRIES=3
WEBHOOK_RETRY_BACKOFF=2.0
# -------------------
# Data Retention Settings
# -------------------
# Automatic cleanup of old event data (advertisements, messages, telemetry, etc.)
# Enable automatic cleanup of old event data
DATA_RETENTION_ENABLED=true
# Number of days to retain event data
# Events older than this are deleted during cleanup
DATA_RETENTION_DAYS=30
# Hours between automatic cleanup runs
# Applies to both event data and node cleanup
DATA_RETENTION_INTERVAL_HOURS=24
# -------------------
# Node Cleanup Settings
# -------------------
# Automatic removal of inactive nodes
# Enable automatic cleanup of inactive nodes
# Nodes with last_seen=NULL (never seen on network) are NOT removed
NODE_CLEANUP_ENABLED=true
# Remove nodes not seen for this many days (based on last_seen field)
NODE_CLEANUP_DAYS=30
# =============================================================================
# API SETTINGS
# =============================================================================
# REST API for querying data
# External API port
API_PORT=8000
# API Keys for authentication
# Generate secure keys for production: openssl rand -hex 32
# Leave empty to disable authentication (not recommended for production)
API_READ_KEY=
API_ADMIN_KEY=
# -------------------
# Prometheus Metrics
# -------------------
# Prometheus metrics endpoint exposed at /metrics on the API service
# Enable Prometheus metrics endpoint
# Default: true
METRICS_ENABLED=true
# Seconds to cache metrics output (reduces database load)
# Default: 60
METRICS_CACHE_TTL=60
# CORS origins for the API server (comma-separated)
# Only needed when running the web dashboard on a different origin than the API
# Example: http://localhost:8080,http://localhost:3000
# CORS_ORIGINS=
# External Prometheus port (when using --profile metrics)
PROMETHEUS_PORT=9090
# -------------------
# Redis Cache
# -------------------
# Optional Redis cache for API response caching.
# Reduces database load for read-heavy endpoints (nodes, messages, dashboard).
# When disabled or unavailable, the API queries the database directly.
#
# Docker: Redis is included in the "cache" profile (--profile cache).
# REDIS_ENABLED defaults to false everywhere. To enable, set REDIS_ENABLED=true
# and start with --profile cache (or point REDIS_HOST at an external Redis).
# Bare-metal: Install Redis separately and set REDIS_ENABLED=true.
#
# For multi-instance setups sharing one Redis, use different REDIS_KEY_PREFIX
# values per instance (e.g., hub for prod, hub-stg for staging).
# Enable Redis caching (default: false outside Docker, true in Docker Compose)
# REDIS_ENABLED=false
# Redis server host (use "redis" in Docker Compose)
# REDIS_HOST=localhost
# Redis server port
# REDIS_PORT=6379
# Redis database number
# REDIS_DB=0
# Redis password (optional)
# REDIS_PASSWORD=
# Cache key prefix for multi-instance isolation
# REDIS_KEY_PREFIX=hub
# Default cache TTL in seconds (matches web auto-refresh interval)
# REDIS_CACHE_TTL=30
# Cache TTL for dashboard endpoints (seconds)
# REDIS_CACHE_TTL_DASHBOARD=30
# External Alertmanager port (when using --profile metrics)
ALERTMANAGER_PORT=9093
# =============================================================================
# WEB DASHBOARD SETTINGS
# =============================================================================
# Web interface for visualizing network status
# External web port
WEB_PORT=8080
# API endpoint URL for the web dashboard
# Default: http://localhost:8000
# API_BASE_URL=http://localhost:8000
# API key for web dashboard queries (optional)
# If API_READ_KEY is set on the API, provide it here
# API_KEY=
# Default theme for the web dashboard (dark or light)
# Users can override via the theme toggle; their preference is saved in localStorage
# Default: dark
# WEB_THEME=dark
# Locale/language for the web dashboard
# Default: en
# Supported: en, nl (see src/meshcore_hub/web/static/locales/ for available translations)
# WEB_LOCALE=en
# Locale used for date/time formatting in the web dashboard
# Controls date ordering only; 24-hour clock is still used by default
# Examples: en-US (MM/DD/YYYY), en-GB (DD/MM/YYYY)
# Default: en-US
# WEB_DATETIME_LOCALE=en-US
# Auto-refresh interval in seconds for list pages (nodes, advertisements, messages)
# Set to 0 to disable auto-refresh
# Default: 30
# WEB_AUTO_REFRESH_SECONDS=30
# Enable debug mode in the web dashboard
# Shows extra diagnostic info (e.g., raw user IDs in the user menu)
# Default: false
# WEB_DEBUG=false
# -------------------
# OIDC Authentication
# -------------------
# Enable OIDC/OAuth2 authentication for the web dashboard.
# When enabled, the admin interface (/a/) requires authenticated sessions.
# Requires an OIDC-compliant identity provider (e.g., LogTo, Keycloak).
# Enable OIDC authentication
# Default: false
# OIDC_ENABLED=false
# OIDC client ID (from your IdP)
# OIDC_CLIENT_ID=
# OIDC client secret (from your IdP)
# OIDC_CLIENT_SECRET=
# OIDC discovery base URL (.well-known/openid-configuration is appended automatically)
# OIDC_DISCOVERY_URL=
# OIDC callback URL (overrides auto-derivation from request)
# Example: https://hub.example.com/auth/callback
# OIDC_REDIRECT_URI=
# Post-logout redirect URI (must match Sign-out redirect URIs configured in IdP)
# Falls back to OIDC_REDIRECT_URI base or request.base_url if not set
# Example: https://hub.example.com/
# OIDC_POST_LOGOUT_REDIRECT_URI=
# OAuth scopes to request. The 'openid' scope is required for ID tokens
# and userinfo endpoint access. Quotes around the value are stripped automatically.
# Default: openid email profile
# OIDC_SCOPES="openid email profile"
# ID token claim name containing user roles
# Default: roles
# OIDC_ROLES_CLAIM=roles
# IdP role name that grants admin access
# Default: admin
# OIDC_ROLE_ADMIN=admin
# IdP role name for operator access (future use)
# Default: operator
# OIDC_ROLE_OPERATOR=operator
# IdP role name for member access
# Default: member
# OIDC_ROLE_MEMBER=member
# IdP role name for test users (excluded from public member views and counts)
# Default: test
# OIDC_ROLE_TEST=test
# Secret for signing session cookies (required when OIDC_ENABLED=true)
# Generate with: openssl rand -hex 32
# OIDC_SESSION_SECRET=
# Session cookie lifetime in seconds
# Default: 86400 (24 hours)
# OIDC_SESSION_MAX_AGE=86400
# HTTPS-only session cookies (enable in production behind TLS)
# Default: false
# OIDC_COOKIE_SECURE=false
# Timezone for displaying dates/times on the web dashboard
# Uses standard IANA timezone names (e.g., America/New_York, Europe/London)
# Default: UTC
TZ=UTC
# Directory containing custom content (pages/, media/)
# Default: ./content
# CONTENT_HOME=./content
# -------------------
# Network Information
# -------------------
# Displayed on the web dashboard homepage
# Network domain name (optional)
# NETWORK_DOMAIN=
# Network display name
NETWORK_NAME=MeshCore Network
# Network location
NETWORK_CITY=
NETWORK_COUNTRY=
# Radio configuration (six individual variables — no unit suffixes needed)
# Units (MHz, kHz, dBm) are applied automatically on display
NETWORK_RADIO_PROFILE=EU/UK Narrow
NETWORK_RADIO_FREQUENCY=869.618
NETWORK_RADIO_BANDWIDTH=62.5
NETWORK_RADIO_SPREADING_FACTOR=8
NETWORK_RADIO_CODING_RATE=8
NETWORK_RADIO_TX_POWER=22
# Welcome text displayed on the homepage (optional, plain text)
# If not set, a default welcome message is shown
NETWORK_WELCOME_TEXT=
# Flash banner announcement displayed on all pages (optional, Markdown supported)
# Supports bold, italic, links, inline code. Empty = no banner shown.
# Example: **Maintenance** scheduled for Saturday — see [details](https://example.com)
NETWORK_ANNOUNCEMENT=
# -------------------
# Feature Flags
# -------------------
# Control which pages are visible in the web dashboard
# Set to false to completely hide a page (nav, routes, sitemap, robots.txt)
# FEATURE_DASHBOARD=true
# FEATURE_NODES=true
# FEATURE_ADVERTISEMENTS=true
# FEATURE_MESSAGES=true
# FEATURE_MAP=true
# FEATURE_MEMBERS=true
# FEATURE_PAGES=true
# FEATURE_CHANNELS=true
# FEATURE_RADIO_CONFIG=true
# -------------------
# Contact Information
# -------------------
# Contact links displayed in the footer
NETWORK_CONTACT_EMAIL=
NETWORK_CONTACT_DISCORD=
NETWORK_CONTACT_GITHUB=
NETWORK_CONTACT_YOUTUBE=