Meshyface is a chat-first Meshtastic dashboard that runs as a single Python service and serves a single-page web UI over HTTP.
The current UI exposes:
- Chat plus direct-peer conversations
- Network workspace for map, topology, Top 10 rankings, node details, and on-demand history views
- Console workspace for live packet/log output
- Apps workspace with Games, plus BBS and Files tabs when those features are enabled
- Settings workspace with radio, device, connectivity, location, channels, tickers, lists, appearance, and about panes
- SQLite-backed history, search, rollups, theme persistence, and custom telemetry rule persistence
Network workspace views from a live Meshtastic session.
Map view with node locations, links, common paths, clusters, and signal heatmap.
History view for node counts, online status, new nodes, and position reports.
Topology view showing observed links from the selected root node.
Trace view for a source, destination, nearby links, and per-hop packet details.
Telemetry chart comparing sensor history across multiple nodes.
Top cards for radio activity, node counts, packets, links, battery, and channel use.
flowchart LR
Browser["Browser<br/>single-page UI"]
CDN["Vendored browser assets<br/>Leaflet + leaflet.heat"]
Tiles["Basemap provider<br/>OpenStreetMap tile service"]
Server["ThreadingHTTPServer<br/>HTML shell + JSON API"]
Assets["Python template assembly<br/>meshdash/html* + meshdash/assets/*"]
State["State loaders<br/>live snapshot + history readers"]
Services["Write services<br/>chat, settings, tools, games, optional BBS/files"]
Tracker["DashboardTracker<br/>live receive path + in-memory buffers"]
History["HistoryStore / SQLite (WAL)<br/>chat, packets, rollups, settings"]
Radio["Meshtastic interface<br/>serial or TCP"]
Mesh["Meshtastic radio / mesh"]
Atlas["Bundled offline atlas<br/>reference basemap fallback"]
Browser -->|GET /| Server
Browser -->|GET /api/*| Server
Browser -->|POST write APIs| Server
Browser --> CDN
Browser --> Tiles
Browser -->|fallback basemap data| Atlas
Server --> Assets
Server --> State
Server --> Services
State <--> Tracker
State <--> History
Services <--> History
Services <--> Radio
Radio <--> Mesh
Radio -->|receive callbacks| Tracker
- Linux host strongly recommended for server mode
- Debian-based hosts such as Raspberry Pi OS Bookworm work with the bundled deploy helper
- Python 3.11+
- Meshtastic-accessible radio over either:
- TCP (
--mesh-host+--mesh-tcp-port) - Serial USB (
--mesh-port)
- TCP (
- Python packages:
- install runtime packages with
python -m pip install -r requirements.txt - install test packages with
python -m pip install -r requirements-dev.txt
- install runtime packages with
- Browser access to:
https://tile.openstreetmap.org/...if you want online basemaps
For a fully air-gapped deployment, the vendored Leaflet assets still load locally, and the map uses the bundled offline atlas when online tile servers are unavailable.
git clone https://github.com/jaronmcd/meshyface.git meshyface
cd meshyface
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtpython mesh_dashboard.py \
--mesh-host meshtastic-radio.local \
--mesh-tcp-port 4403 \
--http-host 0.0.0.0 \
--http-port 8877 \
--refresh-ms 3000python mesh_dashboard.py \
--mesh-port /dev/ttyACM0 \
--http-host 0.0.0.0 \
--http-port 8877 \
--refresh-ms 3000Tip: use /dev/serial/by-id/... for a stable serial path when possible.
- Local:
http://127.0.0.1:8877 - LAN:
http://<host-ip>:8877
Run python mesh_dashboard.py --help for the authoritative runtime flag list.
--history-db is the final on-disk SQLite filename. The dashboard no longer
adds a connected-radio suffix, so any radio plugged into the dashboard
contributes to the same persisted packet, chat, node, and rollup history.
- Default mode persists chat, packets, connection events, node analytics, malformed-text records, environment metrics, and summary rollups to SQLite.
--no-historydisables the persistent store and keeps only live in-memory buffers.- Summary rollups are also sampled in the background while the dashboard is running when history is enabled.
- Theme preset selection persists to
mesh_dashboard_theme_settings.jsonby default, or the file supplied via--theme-settings-file. - Custom telemetry rules are stored in the history SQLite database.
- BBS host settings and local BBS posts are stored in the history SQLite database when BBS is enabled.
Operational commands that inspect or repair local dashboard data are documented in docs/maintenance.md.
The Links subview is a topology view, not a packet-route replay.
Historymode draws from the stored link history saved in SQLite.Livemode draws from current-session link observations only.- The numbered rings show shortest graph distance from the current root using a breadth-first search over the observed link graph.
- Those ring numbers are not literal Meshtastic forwarding hops and are not a real-time packet trace.
- Packet-hop metadata, when available, is still shown separately in node or edge details as packet-hop values.
The current root is the node the graph is centered around. Selecting a different node changes the root and recomputes the numbered distance rings from that node.
You have two common deployment models:
- Proxmox VM/LXC + radio reachable over LAN (TCP) - simplest and most stable
- Proxmox VM/LXC + USB radio passthrough (serial) - works, but needs device passthrough
If your radio is on Wi-Fi/Ethernet and exposes TCP (usually 4403), run the
dashboard in a VM or container and connect over network.
From this repo:
./scripts/deploy_meshyface.sh \
--target pi@meshyface.local \
--bootstrap \
--mesh-host meshtastic-radio.local \
--mesh-port 4403 \
--clean-app-dirThis installs the runtime, deploys app files, writes dashboard.env, and
restarts the service.
Bootstrap assumptions:
- target host has
apt-get,systemd,ssh, andsudo - target Python must be
3.11+after bootstrap; Raspberry Pi OS Bookworm is a good baseline - when you do not override
MESH_DASH_DEPLOY_ROOT, the deploy helper now uses the remote login user's home and installs under<remote-home>/mesh - the generated service unit uses the remote login user by default and keeps
dialoutas the default service group for serial-access-friendly installs
Important naming note:
- In the runtime CLI, the TCP radio port flag is
--mesh-tcp-port. - In
scripts/deploy_meshyface.shand the bundleddashboard.env,MESH_PORTis the TCP port for historical reasons.
./scripts/deploy_meshyface.sh \
--target pi@meshyface.local \
--mesh-host meshtastic-radio.local \
--mesh-port 4403 \
--clean-app-dirIf you want to remove the current Meshyface install on the target and rebuild it
from scratch in one step, use --wipe-remote-root. This removes the managed
systemd unit plus the deploy root, then bootstraps fresh:
./scripts/deploy_meshyface.sh \
--target pi@meshyface.local \
--wipe-remote-root \
--serial-path /dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0--wipe-remote-root implies --bootstrap.
If you want to remove Meshyface from the Pi and stop there:
./scripts/deploy_meshyface.sh \
--target pi@meshyface.local \
--uninstall \
--hard-rebootThat removes:
/etc/systemd/system/meshtastic-dashboard.service- the managed deploy root, which defaults to
/home/<ssh-user>/mesh - any managed app/config/log/venv/history paths that were explicitly configured outside the deploy root
--hard-reboot can also be used after a normal deploy if you want the host to
come back from a forced reboot instead of just restarting the service.
For a Raspberry Pi running Raspberry Pi OS Bookworm or newer, the same
bootstrap flow works as long as the SSH user has sudo access:
./scripts/deploy_meshyface.sh \
--target pi@raspberrypi.local \
--bootstrap \
--mesh-host meshtastic-radio.local \
--mesh-port 4403 \
--clean-app-dirThat will default to:
- app root:
/home/pi/mesh - service user:
pi - service group:
dialout
If the Pi has a radio attached over USB serial instead of TCP, use the stable
/dev/serial/by-id/... path:
./scripts/deploy_meshyface.sh \
--target pi@raspberrypi.local \
--bootstrap \
--serial-path /dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0 \
--clean-app-dir--mesh-host <ip-or-dns>: TCP radio host--mesh-tcp-port <port>: TCP radio port, default4403--mesh-port <path>: serial device path--default-gateway-host <host>: fallback TCP host if--mesh-hostis not provided and serial is still on the default path--default-gateway-port <port>: fallback TCP port for--default-gateway-host--no-default-gateway: force serial unless--mesh-hostis explicitly set
Related environment variables:
MESH_GATEWAY_HOSTMESH_GATEWAY_PORTMESH_DASH_MESH_PORTfor the default serial path
--http-host <host>: bind host, default0.0.0.0--http-port <port>: bind port, default8877--refresh-ms <ms>: browser poll interval, default3000--packet-limit <n>: recent live packet buffer size, default250--reset-ticker-scale-on-restart/--no-reset-ticker-scale-on-restart--show-secrets: reveal private keys/passwords/PSKs in raw JSON panels--debug-mode/--no-debug-mode: expose debug-only dashboard surfaces such as advanced network diagnostics--private-mode/--no-private-mode: strip public chat slices and block selected public endpoints--api-token <token>: require auth on write endpoints viaAuthorization: Bearer <token>orX-API-Token--bbs-enable/--no-bbs-enable: expose or hide the BBS/profile workspace when--accept-file-transfer-traffic-disclaimeris also set--games-enable/--no-games-enable: enable playable Zork console endpoints plus mesh bot replies
Related environment variables:
MESH_DASH_PRIVATE_MODEMESH_DASH_API_TOKENMESH_DASH_BBS_ENABLEMESH_DASH_GAMES_ENABLEMESH_DASH_VERSIONMESH_DASH_GIT_COMMIT
--history-db <path>: base SQLite DB path--history-max-rows <n>: default200000--history-retention-days <days>: default30, use0to disable age pruning--history-event-max-rows <n>: append-only packet event cap, default200000--history-event-retention-days <days>: default30--history-rollup-retention-days <days>: default365--no-history: memory-only mode--seed-from-node-db: bootstrap live tracker from the connected radio NodeDB--backfill-environment-rollups: rebuild environment rollups once and exit; see docs/maintenance.md--backfill-environment-rollups-reset: clear existing rollups before rebuild--node-history-hours <hours>: default selected-node window, default72--node-history-max-points <n>: max points returned by/api/history/node, default1440
Related environment variables:
MESH_DASH_HISTORY_DB
--theme-presets <json>: optional custom theme preset file--theme-preset <name>: selected preset name--theme-settings-file <json>: persisted runtime theme selection file
Built-in presets:
defaultbluecustom
Fresh installs default to custom unless a persisted theme settings file or
MESH_DASH_THEME_PRESET selects another preset.
Related environment variables:
MESH_DASH_THEME_PRESETSMESH_DASH_THEME_PRESETMESH_DASH_THEME_SETTINGS_FILE
- This dashboard is intended for trusted LAN/VPN environments.
- Do not expose it directly to the public internet without a reverse proxy and access control.
- Use
--private-modeand/or--api-tokenfor stricter write-path control. - The built-in
Join Meshyfacechannel preset uses an intentionally public shared Meshyface PSK for interoperability between users of this software. Do not use that public channel for private traffic. --show-secretsexposes sensitive values in raw JSON panels; do not enable it casually on shared displays.- BBS and file transfer can consume significant mesh airtime. Keep them disabled unless you have explicitly accepted that tradeoff.





