mirror of
https://github.com/yellowcooln/meshcore-mqtt-live-map.git
synced 2026-04-20 23:23:36 +00:00
334 lines
9.5 KiB
Markdown
334 lines
9.5 KiB
Markdown
# How-To: MQTT Broker + Live Map
|
|
|
|
This guide covers two parts: stand up a MeshCore MQTT broker and point the live map at it.
|
|
Current version: `1.8.4` (see `VERSIONS.md`).
|
|
|
|
## 1) MQTT broker (meshcore-mqtt-broker)
|
|
|
|
Use the broker repo from Michael Hart.
|
|
|
|
Install dependencies:
|
|
|
|
```bash
|
|
apt install git curl npm
|
|
```
|
|
|
|
Clone the repo:
|
|
|
|
```bash
|
|
git clone https://github.com/michaelhart/meshcore-mqtt-broker
|
|
cd meshcore-mqtt-broker
|
|
```
|
|
|
|
Create the data folder:
|
|
|
|
```bash
|
|
mkdir data
|
|
```
|
|
|
|
Create your `.env`:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Edit `.env` and update the database path (use an absolute path that matches your checkout). While you are there, fill out the rest of the `.env` to your liking, and make sure `AUTH_EXPECTED_AUDIENCE` is set to the hostname of your MQTT server:
|
|
|
|
```bash
|
|
# Abuse Detection - Persistence
|
|
ABUSE_PERSISTENCE_PATH=/home/user/meshcore-mqtt-broker/data/abuse-detection.db
|
|
```
|
|
|
|
For the live map, the easiest broker auth setup is a dedicated read-only
|
|
subscriber account. Add at least one `SUBSCRIBER_N` entry to the broker
|
|
`.env`:
|
|
|
|
```bash
|
|
# Format: SUBSCRIBER_N=username:password:role
|
|
# Role 1 = admin (includes /internal + $SYS)
|
|
# Role 2 = full_access (recommended for most maps)
|
|
# Role 3 = limited (filtered payload fields)
|
|
SUBSCRIBER_1=meshmap:change-this-password:2
|
|
```
|
|
|
|
Recommended role:
|
|
- Use role `2` for most live maps.
|
|
- Use role `1` only if you explicitly want `/internal` topics or admin-only
|
|
broker visibility.
|
|
|
|
Important:
|
|
- The live map does **not** generate MeshCore signed auth tokens on its own.
|
|
- Do **not** put a node-style username like `v1_<PUBLIC_KEY>` in the map
|
|
unless you are also handling JWT token generation and rotation elsewhere.
|
|
- For normal map deployments against `meshcore-mqtt-broker`, use a
|
|
`SUBSCRIBER_N` username/password pair instead.
|
|
|
|
Build and run:
|
|
|
|
```bash
|
|
npm install
|
|
npm run build
|
|
npm start
|
|
```
|
|
|
|
---
|
|
|
|
### Run it as a systemd service (optional)
|
|
|
|
Create `/etc/systemd/system/meshcore-mqtt.service`:
|
|
|
|
```bash
|
|
[Unit]
|
|
Description=MeshCore MQTT Broker
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=user
|
|
WorkingDirectory=/home/user/meshcore-mqtt-broker
|
|
|
|
# Start the broker with node + tsx directly
|
|
ExecStart=/home/user/.nvm/versions/node/v22.21.1/bin/node /home/user/meshcore-mqtt-broker/node_modules/.bin/tsx src/server.ts
|
|
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=NODE_ENV=production
|
|
|
|
# Optional logging to files (you can drop these if you prefer journalctl only)
|
|
StandardOutput=append:/var/log/meshcore-mqtt.log
|
|
StandardError=append:/var/log/meshcore-mqtt-error.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Enable and start:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now meshcore-mqtt
|
|
```
|
|
|
|
---
|
|
|
|
### Cloudflare Tunnel for MQTT over TLS (optional)
|
|
|
|
Follow the broker docs for Cloudflare Tunnel setup:
|
|
|
|
https://github.com/michaelhart/meshcore-mqtt-broker/blob/main/docs/cloudflare-tunnels.md
|
|
|
|
Note: You can run plain TCP on port 1883, but using a tunnel (or another TLS front) avoids exposing the port on the host. If you do use TCP, make sure you lock it down.
|
|
|
|
## 2) Configure the live map
|
|
|
|
Clone the repo and enter it:
|
|
|
|
```bash
|
|
git clone https://github.com/yellowcooln/meshcore-mqtt-live-map
|
|
cd meshcore-mqtt-live-map
|
|
```
|
|
|
|
Copy `.env.example` to `.env` and update MQTT settings. The map expects MQTT over WebSockets by default:
|
|
|
|
```env
|
|
MQTT_HOST=your-mqtt-host
|
|
MQTT_PORT=443
|
|
MQTT_USERNAME=meshmap
|
|
MQTT_PASSWORD=change-this-password
|
|
MQTT_TRANSPORT=websockets
|
|
MQTT_WS_PATH=/
|
|
MQTT_TLS=true
|
|
# Comma-separated list supported, e.g. meshcore/BOS/#,meshcore/CON/#
|
|
MQTT_TOPIC=meshcore/#
|
|
|
|
# MQTT online presence tuning (v1.5+)
|
|
MQTT_ONLINE_SECONDS=300
|
|
MQTT_ONLINE_STATUS_TTL_SECONDS=300
|
|
MQTT_ONLINE_INTERNAL_TTL_SECONDS=300
|
|
MQTT_ACTIVITY_PACKETS_TTL_SECONDS=300
|
|
MQTT_STATUS_OFFLINE_VALUES=offline,disconnected
|
|
```
|
|
|
|
Those `MQTT_USERNAME` / `MQTT_PASSWORD` values should match the subscriber
|
|
account you created in the broker `.env`, for example:
|
|
|
|
```env
|
|
SUBSCRIBER_1=meshmap:change-this-password:2
|
|
```
|
|
|
|
Authentication summary:
|
|
- `meshcore-mqtt-broker` supports signed MeshCore node auth for publishers.
|
|
- This live map is normally a read-only subscriber, so it should use a broker
|
|
subscriber account instead.
|
|
- If you use subscriber role `3`, some metadata fields are filtered out.
|
|
Role `2` is the recommended default for maps.
|
|
|
|
Presence behavior:
|
|
- `/status` + `/internal` determine whether a node is shown as **MQTT online**.
|
|
- `/packets` is tracked as feed activity and does not, by itself, mark a node online.
|
|
|
|
Optional coordinate overrides (for fixed node placement):
|
|
|
|
```env
|
|
DEVICE_COORDS_FILE=/data/device_coords.json
|
|
```
|
|
|
|
Optional neighbor override controls (manual + auto):
|
|
|
|
```env
|
|
NEIGHBOR_OVERRIDES_FILE=/data/neighbor_overrides.json
|
|
AUTO_NEIGHBOR_OVERRIDES_ENABLED=false
|
|
AUTO_NEIGHBOR_OVERRIDES_FILE=/data/neighbor_overrides.auto.json
|
|
AUTO_NEIGHBOR_ACTIVE_DAYS=7
|
|
AUTO_NEIGHBOR_MIN_EDGE_COUNT=3
|
|
AUTO_NEIGHBOR_REFRESH_SECONDS=60
|
|
```
|
|
|
|
Optional channel secrets file (for decrypting sender names from supported group-text packets):
|
|
|
|
```env
|
|
CHANNEL_SECRETS_FILE=/data/channel_secrets.json
|
|
```
|
|
|
|
Copy `channel_secrets.example.json` to your chosen path and keep only the channels you want to ship.
|
|
|
|
Optional automatic backups:
|
|
|
|
```env
|
|
BACKUP_ENABLED=true
|
|
BACKUP_INTERVAL_SECONDS=43200
|
|
BACKUP_DIR=/backup
|
|
BACKUP_RETENTION_DAYS=7
|
|
```
|
|
|
|
Notes:
|
|
- backups are written as timestamped `.tar.gz` archives like `meshmap-backup-2026-03-28T21-15-00.tar.gz`
|
|
- only files that currently exist are included
|
|
- the default compose setup mounts `./backup:/backup`
|
|
- live state stays under `/data`; backups stay under `/backup`
|
|
|
|
Optional packet analyzer link base (used for Route Details hashes):
|
|
|
|
```env
|
|
PACKET_ANALYZER_URL=https://analyzer.letsmesh.net/packets?packet_hash=
|
|
QR_CODE_BUTTON_ENABLED=false
|
|
PEERS_DEFAULT_LIMIT=8
|
|
MAP_BOUNDARY_MODE=radius
|
|
MAP_BOUNDARY_FILE=/data/map_boundary.json
|
|
MAP_BOUNDARY_SHOW=false
|
|
LOS_CURVATURE_ENABLED=true
|
|
LOS_CURVATURE_FACTOR=1.333333
|
|
ROUTE_ALLOW_AMBIGUOUS_ONE_BYTE_FALLBACK=false
|
|
```
|
|
|
|
LOS note:
|
|
- The live map LOS tool now includes Earth curvature by default.
|
|
- Leave `LOS_CURVATURE_ENABLED` unset to keep the default `true`.
|
|
- Leave `LOS_CURVATURE_FACTOR` unset to keep the default `1.333333`.
|
|
|
|
Optional polygon boundary mode:
|
|
```env
|
|
MAP_BOUNDARY_MODE=polygon
|
|
MAP_BOUNDARY_FILE=/data/map_boundary.json
|
|
MAP_BOUNDARY_SHOW=true
|
|
```
|
|
|
|
Boundary files:
|
|
- Use `map_boundary.example.json` as the schema reference.
|
|
- Open `tools/map-boundary-builder.html` directly in a browser, or use the hosted copy at [https://yellowcooln.com/map-boundary-builder/](https://yellowcooln.com/map-boundary-builder/), to click out a polygon and export `map_boundary.json`.
|
|
|
|
Optional: enable the coverage layer by setting `COVERAGE_API_URL` (the Coverage button hides itself when blank):
|
|
|
|
```env
|
|
COVERAGE_API_URL=https://coverage.example.com
|
|
# The envs below are MeshMapper-only. The legacy coverage map does not use them.
|
|
# Optional for MeshMapper coverage.php requests
|
|
COVERAGE_API_KEY=
|
|
# Show only the last N days on the map. Default 30. Set 0 to disable age filtering.
|
|
COVERAGE_MAX_AGE_DAYS=30
|
|
# MeshMapper only: fallback cooldown after HTTP 429 if the API does not report resets_in_hours
|
|
COVERAGE_RATE_LIMIT_COOLDOWN_SECONDS=3600
|
|
# MeshMapper only: local file used to serve cached coverage to users
|
|
COVERAGE_CACHE_FILE=/data/coverage_cache.json
|
|
# MeshMapper only: server-side refresh interval; default hourly
|
|
COVERAGE_SYNC_INTERVAL_SECONDS=3600
|
|
#
|
|
# Routing note: on large meshes, ambiguous 1-byte prefixes are handled
|
|
# conservatively by default. If your network lost routes after v1.7.0 and you
|
|
# need the older behavior back, set:
|
|
# ROUTE_ALLOW_AMBIGUOUS_ONE_BYTE_FALLBACK=true
|
|
# That restores the legacy closest/time-based fallback for colliding 1-byte
|
|
# prefixes.
|
|
#
|
|
# MeshMapper uses the documented domain:
|
|
# COVERAGE_API_URL=https://meshmapper.net
|
|
WEATHER_RADAR_COUNTRY_BOUNDS_ENABLED=true
|
|
# Optional override. Default is /weather/radar/country-bounds on this app.
|
|
# This is an HTTP URL path, not a filesystem directory.
|
|
WEATHER_RADAR_COUNTRY_LOOKUP_URL=/weather/radar/country-bounds
|
|
WEATHER_WIND_ENABLED=true
|
|
WEATHER_WIND_API_URL=https://api.open-meteo.com/v1/forecast
|
|
WEATHER_WIND_GRID_SIZE=3
|
|
WEATHER_WIND_REFRESH_SECONDS=180
|
|
```
|
|
|
|
Optional: configure Weather (Radar + Wind) behavior:
|
|
|
|
```env
|
|
# Master radar feature flag.
|
|
WEATHER_RADAR_ENABLED=true
|
|
# Keep radar clipped to the active country bounds around map center.
|
|
WEATHER_RADAR_COUNTRY_BOUNDS_ENABLED=true
|
|
# Keep default unless you run your own lookup endpoint.
|
|
WEATHER_RADAR_COUNTRY_LOOKUP_URL=/weather/radar/country-bounds
|
|
|
|
# Enable/disable wind overlay in the Weather panel.
|
|
WEATHER_WIND_ENABLED=true
|
|
# Open-Meteo compatible wind API endpoint.
|
|
WEATHER_WIND_API_URL=https://api.open-meteo.com/v1/forecast
|
|
# Wind sampling density (1-5): higher = more arrows + more API load.
|
|
WEATHER_WIND_GRID_SIZE=3
|
|
# Wind refresh interval in seconds (minimum 30).
|
|
WEATHER_WIND_REFRESH_SECONDS=180
|
|
```
|
|
|
|
If both `WEATHER_RADAR_ENABLED=false` and `WEATHER_WIND_ENABLED=false`, the Weather button is hidden.
|
|
|
|
If you are using plain TCP MQTT, set:
|
|
|
|
```env
|
|
MQTT_TRANSPORT=tcp
|
|
MQTT_TLS=false
|
|
MQTT_PORT=1883
|
|
```
|
|
|
|
## 3) Run the map
|
|
|
|
Background (detached):
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Foreground (watch logs):
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
Open the UI at `http://localhost:8080`.
|
|
|
|
## 4) Verify data flow
|
|
|
|
- `/snapshot` should show devices once adverts arrive.
|
|
- `/stats` shows ingest totals.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
curl -s http://localhost:8080/stats | jq
|
|
```
|
|
|
|
Notes:
|
|
- The map only places nodes once it decodes adverts that include a valid location.
|
|
- If no nodes appear, verify the MQTT topic and that the broker is sending `/packets` data with raw hex payloads.
|