The overall health badge on route cards now shows the rolling 7-day average tier instead of the latest window-hours snapshot, so flapping routes that are currently up still appear marginal/failing if the week's mean warrants it. Same averaging drives the dashboard Route Health widget's summary dot, the routes page summary strip counts, and (already) the Route Trends chart line colors. - compute_average_quality() in collector/routes.py (0/1/2 mean, thresholds 1.5/0.75, empty-history fallback) — kept in sync with the averageRouteTier JS helper in charts.js - RouteRead / RouteDetail gain a 'quality_avg' field - list/get/update handlers compute it per route; create skips (no meaningful history yet) and the frontend falls back to route_result.quality for brand-new routes - diagnosis tooltip unchanged (still current-snapshot state text)
MeshCore Hub
Python 3.14+ platform for managing and orchestrating MeshCore mesh networks.
Warning
DEPRECATION NOTICE — v0.14 adds PostgreSQL support (
DATABASE_BACKEND=postgres); SQLite remains the zero-config default. SQLite support will be maintained for at least the next few releases (~3 months), then removed in favour of PostgreSQL-only. See docs/database.md to switch backends and docs/upgrading.md to migrate.
Overview
MeshCore Hub provides a complete solution for monitoring, collecting, and interacting with MeshCore mesh networks. Data ingestion is handled by meshcore-packet-capture, which observes MeshCore RF traffic and publishes decoded packets to MQTT. It consists of multiple components that work together:
| Component | Description |
|---|---|
| Collector | Subscribes to MQTT events and persists them to a database |
| API | REST API for querying data |
| Web Dashboard | Single Page Application (SPA) for visualizing network status |
MeshCore Hub Networks
Local mesh communities that are using MeshCore Hub:
Architecture
flowchart LR
subgraph Devices["MeshCore Devices"]
D1["Device 1"]
D2["Device 2"]
D3["Device 3"]
end
PCAP["meshcore-packet-capture"]
D1 -.->|RF| PCAP
D2 -.->|RF| PCAP
D3 -.->|RF| PCAP
PCAP -->|Publish| MQTT["MQTT Broker"]
subgraph Backend["Backend Services"]
Collector --> Database --> API
end
MQTT --> Collector
API --> Web["Web Dashboard"]
style Devices fill:none,stroke:#0288d1,stroke-width:2px
style PCAP fill:none,stroke:#f57c00,stroke-width:2px
style Backend fill:none,stroke:#388e3c,stroke-width:2px
style MQTT fill:none,stroke:#7b1fa2,stroke-width:3px
style Collector fill:none,stroke:#388e3c,stroke-width:2px
style Database fill:none,stroke:#c2185b,stroke-width:2px
style API fill:none,stroke:#1976d2,stroke-width:2px
style Web fill:none,stroke:#ffa000,stroke-width:2px
Features
- Event Persistence: Store messages, advertisements, telemetry, and trace data
- Raw Packet Inspection: Capture, browse, and search raw wire packets; a deduplicated packet view shows every observer reception and routing path, with clickable path-hash badges that look up the matching nodes
- Route Health Monitoring: Define multi-hop mesh routes and track each link's health over time with traffic-light status, per-day history, and recent-match attribution
- REST API: Query historical data with filtering and pagination
- Node Tagging: Add custom metadata to nodes for organization
- Web Dashboard: Visualize network status, node locations, and message history
- Internationalization: Full i18n support with composable translation patterns
- Docker Ready: Single image with all components, easy deployment
Getting Started
Docker Compose Profiles
Docker Compose uses profiles to select which services to run. The configuration is split across multiple files:
| File | Purpose |
|---|---|
docker-compose.yml |
Base shared config (services, profiles, healthchecks, environment) |
docker-compose.dev.yml |
Development overrides (port mappings for direct access) |
docker-compose.prod.yml |
Production overrides (external proxy network, no exposed ports) |
docker-compose.traefik.yml |
Optional Traefik auto-discovery labels |
All docker compose commands require explicit file selection with -f:
# Development (default — exposes ports for local access)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile all up -d
# Production (generic reverse proxy — nginx, caddy, etc.)
docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile all up -d
# Production (Traefik)
docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.traefik.yml --profile all up -d
Service profiles:
| Profile | Services | Use Case |
|---|---|---|
all |
mqtt, observer, migrate, collector, api, web | Everything on one host |
core |
migrate, collector, api, web | Central server infrastructure |
mqtt |
meshcore-mqtt-broker | Local MQTT broker (optional) |
observer |
packet capture observer | Observes RF traffic and publishes to MQTT |
seed |
seed | One-time seed data import |
migrate |
migrate | One-time database migration |
Note: Most deployments connect to an external MQTT broker. Add --profile mqtt only if you need a local broker. The observer profile runs meshcore-packet-capture to observe MeshCore RF traffic and publish decoded packets to MQTT.
Simple Self-Hosted Setup
The quickest way to get started is running the entire stack on a single machine with a connected LoRa radio.
Prerequisites:
- A compatible LoRa radio (e.g., Heltec V3, T-Beam) connected via serial
Steps:
# Create a directory, download the Docker Compose files and
# example environment configuration file
mkdir meshcore-hub
cd meshcore-hub
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/docker-compose.yml
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/docker-compose.dev.yml
wget https://raw.githubusercontent.com/ipnet-mesh/meshcore-hub/refs/heads/main/.env.example
# Copy and configure environment
cp .env.example .env
# Edit .env: set PACKETCAPTURE_IATA to your 3-letter airport code
# set SERIAL_PORT if not /dev/ttyUSB0
# Start the entire stack with local MQTT broker and packet capture
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile mqtt --profile core --profile observer up -d
# View the web dashboard
open http://localhost:8080
This starts all services: MQTT broker, collector, API, web dashboard, and packet capture. The observer profile runs meshcore-packet-capture to observe MeshCore RF traffic and publish decoded packets to MQTT.
Deployment
For production deployments (reverse-proxy setup, Traefik overrides, multi-instance routing on a shared Docker host, and API scaling with API_WORKERS), see docs/deployment.md.
Adding Remote Observers
Other operators can run their own meshcore-packet-capture instance and publish decoded packets to your MeshCore Hub, with optional contribution to the LetsMesh and MeshRank networks. For the ready-made Docker Compose setup and the PACKETCAPTURE_* configuration reference, see docs/observer.md.
Backup & Restore
For backing up and restoring Docker volumes (Makefile and shell variants), see docs/maintenance.md.
Configuration
All components are configured via environment variables. Copy .env.example to .env and override what you need. The full variable reference — grouped by feature (Common, Database, Caching, Collector, Webhooks, Auth, Data Retention, API, Web Dashboard, Feature Flags, Traefik, Prometheus & Alertmanager) — lives in docs/configuration.md; each section there links to the relevant feature doc for setup and operational details.
Seed Data
The database can be seeded with node tags from YAML files. See docs/seeding.md for format details, directory structure, and running the seed process.
API Documentation
When running, the API provides interactive documentation at:
- Swagger UI: http://localhost:8000/api/docs
- ReDoc: http://localhost:8000/api/redoc
- OpenAPI JSON: http://localhost:8000/api/openapi.json
Health check endpoints are also available:
- Health: http://localhost:8000/health
- Ready: http://localhost:8000/health/ready (includes database check)
- Metrics: http://localhost:8000/metrics (Prometheus format — point your Prometheus scraper here)
Authentication
The API supports optional bearer token authentication:
# Read-only access
curl -H "Authorization: Bearer <API_READ_KEY>" http://localhost:8000/api/v1/nodes
# Admin access
curl -H "Authorization: Bearer <API_ADMIN_KEY>" http://localhost:8000/api/v1/members
The web dashboard supports OIDC/OAuth2 authentication for admin access. When enabled, users must authenticate with an identity provider and have the admin role assigned. See docs/auth.md for setup instructions and IdP-specific guides.
Example Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/nodes |
List all known nodes |
| GET | /api/v1/nodes/{public_key} |
Get node details |
| GET | /api/v1/nodes/prefix/{prefix} |
Get node by public key prefix |
| GET | /api/v1/nodes/{public_key}/tags |
Get node tags |
| POST | /api/v1/nodes/{public_key}/tags |
Create node tag |
| GET | /api/v1/messages |
List messages with filters |
| GET | /api/v1/advertisements |
List advertisements |
| GET | /api/v1/telemetry |
List telemetry data |
| GET | /api/v1/trace-paths |
List trace paths |
| GET | /api/v1/members |
List network members |
| GET | /api/v1/dashboard/stats |
Get network statistics |
| GET | /api/v1/dashboard/activity |
Get daily advertisement activity |
| GET | /api/v1/dashboard/message-activity |
Get daily message activity |
| GET | /api/v1/dashboard/node-count |
Get cumulative node count history |
Development
Setup
# Clone and setup
git clone https://github.com/ipnet-mesh/meshcore-hub.git
cd meshcore-hub
# Install frontend dependencies and build static assets (requires Node.js 22+ LTS)
npm install
npm run build
# Setup Python environment
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
# Run database migrations
meshcore-hub db upgrade
# Start components (in separate terminals)
meshcore-hub collector
meshcore-hub api
meshcore-hub web
Note:
npm run buildcompiles Tailwind CSS and copies vendor libraries (lit-html, Leaflet, Chart.js, QRCode.js) intosrc/meshcore_hub/web/static/vendor/. This step is required before the web dashboard will render correctly. In Docker, this happens automatically during the build.
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=meshcore_hub --cov-report=html
# Run specific test file
pytest tests/test_api/test_nodes.py
# Run tests matching pattern
pytest -k "test_list"
Code Quality
# Run all code quality checks (formatting, linting, type checking)
pre-commit run --all-files
Creating Database Migrations
# Auto-generate migration from model changes
meshcore-hub db revision --autogenerate -m "Add new field to nodes"
# Create empty migration
meshcore-hub db revision -m "Custom migration"
# Apply migrations
meshcore-hub db upgrade
Project Structure
meshcore-hub/
├── src/meshcore_hub/ # Main package
│ ├── common/ # Shared code (models, schemas, config)
│ ├── collector/ # MQTT event collector
│ ├── api/ # REST API
│ └── web/ # Web dashboard
│ ├── templates/ # Jinja2 templates (SPA shell)
│ └── static/
│ ├── css/ # Stylesheets (app.css, input.css, built tailwind.css)
│ ├── vendor/ # Vendored JS/CSS libraries (built by npm run build)
│ ├── js/spa/ # SPA frontend (ES modules, lit-html)
│ └── locales/ # Translation files (en.json)
├── tests/ # Test suite
├── alembic/ # Database migrations
├── etc/ # Configuration files (MQTT, Prometheus, Alertmanager)
├── example/ # Example files for reference
│ ├── seed/ # Example seed data files
│ │ └── node_tags.yaml # Example node tags
│ └── content/ # Example custom content
│ ├── pages/ # Example custom pages
│ │ └── join.md # Example join page
│ └── media/ # Example media files
│ └── images/ # Custom images
├── seed/ # Seed data directory (SEED_HOME, copy from example/seed/)
├── content/ # Custom content directory (CONTENT_HOME, optional)
│ ├── pages/ # Custom markdown pages
│ └── media/ # Custom media files
│ └── images/ # Custom images (logo.svg/png/jpg/jpeg/webp replace default logo)
├── data/ # Runtime data directory (DATA_HOME, created at runtime)
├── Dockerfile # Docker build configuration (multi-stage: Node.js frontend + Python)
├── package.json # Frontend build dependencies (Tailwind, DaisyUI, lit-html, etc.)
├── build.js # Frontend build script (Tailwind CLI + vendor copy)
├── docker-compose.yml # Docker Compose base config
├── docker-compose.dev.yml # Development overrides (port mappings)
├── docker-compose.prod.yml # Production overrides (proxy network)
├── docker-compose.traefik.yml # Optional Traefik labels
├── docs/ # Documentation
│ ├── images/ # Screenshots and images
│ ├── hosting/ # Reverse proxy hosting guides
│ ├── configuration.md # Single source of truth for environment variables
│ ├── content.md # Custom content setup guide
│ ├── database.md # Database backends (SQLite/PostgreSQL) reference
│ ├── deployment.md # Production setup, scaling, Redis, multi-instance
│ ├── i18n.md # Translation reference guide
│ ├── letsmesh.md # LetsMesh packet decoding details
│ ├── maintenance.md # Backup and restore procedures
│ ├── observer.md # Remote observers and PACKETCAPTURE_* reference
│ ├── routes.md # Route health monitoring
│ ├── seeding.md # Seed data format and import guide
│ ├── upgrading.md # Upgrade guide for breaking changes
│ └── webhooks.md # Webhook configuration reference
├── SCHEMAS.md # Event schema documentation
└── AGENTS.md # AI assistant guidelines
Documentation
- SCHEMAS.md - MeshCore event schemas
- docs/configuration.md - Single source of truth for environment variables
- docs/deployment.md - Production setup, reverse proxy, multi-instance, API scaling, Redis
- docs/observer.md - Remote packet-capture observers and
PACKETCAPTURE_*reference - docs/routes.md - Route health monitoring and link status
- docs/maintenance.md - Backup and restore procedures
- docs/database.md - Database backends (SQLite/PostgreSQL) and migration
- docs/upgrading.md - Upgrade guide for breaking changes
- docs/letsmesh.md - LetsMesh packet decoding details
- docs/seeding.md - Seed data format and import guide
- docs/i18n.md - Translation reference guide
- docs/content.md - Custom content setup guide
- docs/auth.md - OIDC authentication setup and configuration
- docs/webhooks.md - Webhook configuration reference
- AGENTS.md - Guidelines for AI coding assistants
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and quality checks (
pytest && pre-commit run --all-files) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Important
Help Translate MeshCore Hub 🌍
We need volunteers to translate the web dashboard! Currently only English is available. Check out the Translation Guide to contribute a language pack. Partial translations welcome!
License
This project is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See LICENSE for details.
Acknowledgments
- MeshCore - The mesh networking protocol
- meshcore - Python library for MeshCore devices
- meshcore-packet-capture - RF packet capture and MQTT publisher for data ingestion
- meshcore-mqtt-broker - WebSocket MQTT broker with MeshCore public key authentication. The Docker image (
ghcr.io/ipnet-mesh/meshcore-mqtt-broker) is built and published by a GitHub Action in this repository that clones the upstream source, as the upstream project does not currently provide a public Docker image (although a PR has been submitted to add this).
