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, overview, links, routes, sensors, Top 10 rankings, node details, and on-demand history
- Console workspace for live packet/log output
- Apps workspace with Scripts (Alpha), Games, and a Files tab when file transfer is 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
The Console workspace is a terminal-style control surface for packet traffic, history search, and mesh utility commands.
- Type a command name to open autocomplete suggestions. Use
TaborEnterto accept,ArrowRightto accept the ghosted suffix, andArrowUp/ArrowDownto move through the popup. - Commands that take a node target show rich node suggestions with name, ID,
emoji, tag, status, hops, GPS, last-heard state, ports, and node number.
Normal text narrows by node names and tags;
!narrows by node ID. livestreams packet traffic untilCtrl+Corq. Uselive grep <text>,live rg <text>,live filter=<text>, or barelive <text>to stream only matching live packet groups. Layer and verbosity filters still apply, for examplelive rg TEXT_MESSAGE_APP -vv --layer=2.grep <text>andrg <text>search retained packet/chat history with context windows, limits, packet/chat source filters, and summary/packet scope filters./search <text>filters the visible console output from the prompt without starting a retained-history search.
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.
Themes can reshape the full dashboard with custom colors, gradients, transparency, blur, fonts, particles, or a live-map background. These example palettes are custom themes; select any preview to open its full-resolution screenshot.
| Signal Green | Neon Magenta | Aurora Relay |
|---|---|---|
![]() |
![]() |
![]() |
Meshyface nodes can share compact appearance packets containing a node-theme
recipe and an optional ghost or watermark. Configure and preview your node in
Settings > Appearance > Node Appearance, then use Broadcast appearance to
publish it. Share node appearance is on by default for fresh installs.
- Appearance packets use the configured Profiles send channel and inherit the radio's hop-limit setting; Meshyface does not impose a separate hop limit.
- Received appearances can style node identities in chat, rosters, node details, maps, graphs, sensors, and tickers without replacing the receiving dashboard's own theme.
- Turning Share node appearance off disables both sides of the feature. It stops broadcasts and bypasses the profile receive path before incoming appearance payloads are decoded, cached, or persisted; cached shared appearances are cleared and no received appearance is rendered.
- When history is enabled, received profiles and the local sharing preference persist in the history SQLite database.
- Shared appearances are cosmetic metadata. A received appearance does not change radio settings, channels, or the dashboard's saved package theme.
flowchart LR
Browser["Browser<br/>single-page UI"]
CDN["Vendored browser assets<br/>Leaflet + leaflet.heat + particles.js"]
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 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
Start with prerequisites and dependencies, then choose one install path:
- Manual foreground run: clone anywhere and run the dashboard directly.
- Recommended systemd service: clone into
/opt/meshyfacefor a persistent GitHub-updatable host. - Docker: run the same app entrypoint in a container
with
/datamounted for persistent state. - Workstation push deployment: copy a local checkout to a target over SSH.
- Proxmox runtime topology: choose TCP or USB radio placement for a VM/LXC install.
- Offline map packs: add detailed local map data for air-gapped or low-connectivity dashboards.
--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.
Operational commands that inspect or repair local dashboard data are documented in docs/maintenance.md.
Administrator-installed Python plugins run in a spawned worker that is shown by
default.
Each plugin currently exports one Script with bounded dispatch, handler
timeouts, durable JSON state, direct-message sessions, validated chat actions,
node/location lookups, and host-managed file jobs. New local plugins still start
disabled until an administrator enables them. Script Python is trusted code,
not sandboxed code. Its management surface is Apps → Scripts (Alpha); it
shows plugin runtime and enablement status but does not edit or upload code.
See Plugins and Script API (Alpha) for the in-repository
reference plugins, one-restart enablement flow, package format, API names, trust
model, and troubleshooting.
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.
--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 for external API-style write clients viaAuthorization: Bearer <token>orX-API-Token. The dashboard UI uses same-origin browser checks instead of a separate key prompt. PreferMESH_DASH_API_TOKENon shared hosts because command-line tokens may appear in process listings and shell history--allow-tokenless-raw-packet-download/--no-allow-tokenless-raw-packet-download: permit raw-packet DB downloads without a token for loopback/private-LAN clients; enabled by default--games-enable/--no-games-enable: enable playable standalone Zork console endpoints
Related environment variables:
MESH_DASH_PRIVATE_MODEMESH_DASH_API_TOKENMESH_DASH_GAMES_ENABLEMESH_DASH_VERSIONMESH_DASH_GIT_COMMITMESH_DASH_PR_NUMBER
Runtime identity comes from the 12-character git commit, followed by an
optional pull-request number, for example abc123456789 · PR #43. The Software
panel displays this single revision instead of a package version. GitHub
merge/squash commit subjects are detected automatically; unmerged preview
deployments can set MESH_DASH_PR_NUMBER explicitly.
/api/revision is the canonical identity endpoint. Its revision and
build_ref fields report the runtime revision, while commit and pr_number
remain available as structured fields. /api/version remains as a legacy
endpoint, and its version field retains release/package metadata.
MESH_DASH_VERSION is likewise reserved for explicit release packaging and is
not shown in the dashboard.
/api/healthreturns service status plus aperformanceblock. It has per-poll-profile/api/stateserver times (p50, p95, and max over recent full responses), the share of304 Not Modifiedpolls, and the latest and largest response sizes. It also reports process memory./metricsexposes the same values in Prometheus text format:meshdash_state_response_ms,meshdash_state_responses_total,meshdash_state_body_bytes, andmeshdash_process_resident_memory_bytes. It adds known and omitted node counts when the poll node window is active.- When recent full
/api/stateresponses exceed 500 ms at p95 or 1.5 MB, the service logs aPerformance warning:line, at most once per 15 minutes per profile.
--file-transfer-enable: enable the Files app; requires--accept-file-transfer-traffic-disclaimer--file-transfer-max-bytes <bytes>: per-file limit, default65536and constrained to1024-524288
Transfers use the beta MF_FILE_V2 protocol on private port 258 and are not
compatible with the former text-message transport. When a destination has a
usable detected hop count, Meshyface uses that count plus one, capped by the
configured radio hop limit. Hop data older than one hour falls back to the
configured limit. The Files app displays the selected limit and its source.
Related environment variables:
MESH_DASH_FILE_TRANSFER_ENABLEMESH_DASH_FILE_TRANSFER_MAX_BYTESMESH_DASH_ACCEPT_FILE_TRANSFER_TRAFFIC_DISCLAIMER
--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_DBMESH_DASH_STATE_NODE_WINDOW_DAYS: default14. Routine dashboard polls include nodes heard within this many days, plus favorites, the local node, and nodes referenced by current chat, packets, or links. Older nodes stay in history and appear in node search through/api/nodes/search. Use0to send every known node.
--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:
default(safe Meshyface blue, particles disabled)custom(showcase theme with particles and background effects enabled)
Fresh installs default to default 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-mode, a reverse proxy, or an API token for stricter external API-client write control. - Prefer
MESH_DASH_API_TOKENover--api-tokenon shared or multi-user hosts. A command-line token can be visible in process listings and retained in shell history. - 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.- Raw-packet database downloads can expose message and telemetry contents. On
shared hosts, require an API token and use
--no-allow-tokenless-raw-packet-download. - File transfer can consume significant mesh airtime. Keep it disabled unless you have explicitly accepted that tradeoff.
Run the normal test suite:
python -m pytestRun Ruff the same way CI does:
scripts/run_ruff_local.shRun the advisory app coverage report:
python -m pytest \
--cov=meshdash \
--cov=mesh_dashboard \
--cov=mesh_connection \
--cov-report=termRun the local coverage gate with the stricter 85% minimum:
scripts/run_coverage_local.shRun the local GUI responsiveness benchmark before PRs:
scripts/run_gui_responsiveness_local.shCoverage intentionally excludes the ported Zork engine package from scoring, but standalone Zork and routing tests still run. GitHub Actions publishes the same coverage report as an advisory PR comment and artifact. CI fails below 80%. The local gate stays 5 percentage points higher than CI.








