Cross-platform configurator for experimental Meshtastic firmware builds that expose ModuleConfig.wireguard. Runs on Windows, macOS, and Linux.
This tool lets users import a standard single-peer WireGuard .conf, connect over serial or the Meshtastic TCP API, push the config to a device, confirm readback, tune basic network settings, and monitor basic tunnel health from a gui.
- Install Python 3.
- Clone or download this repo.
- Run the one-time setup:
bin\setup-wireguard-gui.cmd- Launch the GUI:
bin\wireguard-gui.cmdOn macOS or Linux, use the shell-script launchers instead — see Running on macOS and Linux.
In the GUI, choose Serial for USB-connected devices or Network for devices reachable through the Meshtastic TCP API. The default TCP API port is 4403. Use the top menu to switch between the WireGuard and Network sections.
The GUI shows progress for each device operation, including network ping status, connection open, request sent, waiting for response, and confirmed response. Use Cancel if a device operation is stuck or the wrong IP/port was entered; the app will ignore late results from the cancelled operation and allow a new action. The detailed log is collapsed by default and can be expanded when troubleshooting.
The WireGuard section keeps device actions at the top, followed by the selected .conf file and device health. Each node should use a unique WireGuard client config; do not reuse the same private key or tunnel address across multiple devices.
The Network section can read and apply common Meshtastic network settings:
- Wi-Fi enabled state, SSID, and password
- Wi-Fi/Bluetooth conflict handling for devices that cannot reliably run both radios together
- NTP server
- Rsyslog server
- Ethernet enabled state
- IPv6 enabled state
- Bluetooth enabled state, pairing mode, and fixed PIN
Read the current device config before applying changes. Leaving the Wi-Fi password blank keeps the device's existing password. Some ESP-based devices cannot reliably keep Wi-Fi and Bluetooth enabled at the same time, so the Network section includes explicit options to disable Bluetooth before enabling Wi-Fi, or disable Wi-Fi before enabling Bluetooth. The Wi-Fi-off-before-Bluetooth option is disabled when the configurator is connected over Network/TCP because it would break the active management path before Bluetooth can be confirmed. Radio conflict warnings are shown inside the app instead of as separate system dialogs. Verify readback after the device reconnects.
On startup, the GUI checks the latest GitHub Release. When a newer configurator version is available, it shows a small update banner with options to download the new Windows EXE or open the release notes. The app does not replace itself while running; close the current configurator before launching a downloaded update.
Note: The macOS/Linux launchers and build path have not yet been tested on real macOS or Linux hardware — the code is platform-neutral by construction, but treat this path as experimental and please report any issues.
The engine and GUI are pure Python (the GUI uses tkinter), so they run on macOS
and Linux as well. Use the .sh launchers instead of the .cmd ones:
# One-time setup
bin/setup-wireguard-gui.sh
# Launch the GUI
bin/wireguard-gui.shRequirements:
- Python 3 with tkinter. tkinter ships with the python.org installers on
macOS and Windows, but on many Linux distributions it is a separate package —
e.g.
sudo apt install python3-tk(Debian/Ubuntu) orsudo dnf install python3-tkinter(Fedora). - Serial access on Linux usually requires your user to be in the
dialout(oruucp) group so the device shows up as/dev/ttyUSB*//dev/ttyACM*.
Everything else — CLI usage, RPC mode, and the protobuf setup profiles below — works identically; just swap the Windows path separators in the examples.
To package a single-file executable for the current platform:
bin\build-wireguard-gui-exe.cmdbin/build-wireguard-gui-exe.shPyInstaller is not a cross-compiler, so the build produces a binary for whatever OS you run it on:
dist\MeshtasticWireGuardConfigurator.exe # Windows
dist/MeshtasticWireGuardConfigurator.app # macOS
dist/MeshtasticWireGuardConfigurator # Linux
Generated dist, build, .spec, and .wireguard-gui-venv files are local artifacts and should not be committed.
Import a WireGuard config file over serial:
python bin\wireguard-config.py --port COM12 set --config wg0.conf --enableImport over the Meshtastic TCP API:
python bin\wireguard-config.py --host 192.168.1.50 set --config wg0.conf --enableUse a non-default TCP API port:
python bin\wireguard-config.py --host 192.168.1.50 --tcp-port 4403 getUse a shorter or longer network timeout:
python bin\wireguard-config.py --host 192.168.1.50 --timeout 5 getYou can also include the port in the host value:
python bin\wireguard-config.py --host 192.168.1.50:4403 getRead the saved device config and runtime status over serial:
python bin\wireguard-config.py --port COM12 getDisable automatic startup without erasing saved keys:
python bin\wireguard-config.py --port COM12 disableList available serial ports as JSON (no device connection required):
python bin\wireguard-config.py list-portsValidate a WireGuard .conf and preview the parsed fields without connecting to a
device (useful for UI validation; secrets redacted unless --show-secrets):
python bin\wireguard-config.py parse-conf --config wg0.confAll commands emit JSON on stdout and report errors on stderr with a non-zero exit code, so the tool can be driven as a subprocess by another front-end.
Pass the global --rpc flag to switch stdout from a single pretty-printed result
to a stream of newline-delimited JSON events. This is the contract a front-end
(e.g. the planned Electron app) binds to when running the engine as a subprocess:
python bin\wireguard-config.py --rpc --port COM12 getEach line is one self-contained JSON object carrying the schema version v:
{"v":1,"type":"progress","message":"Opening device connection."}
{"v":1,"type":"progress","message":"Connected to device."}
{"v":1,"type":"result","data":{ "...the same payload the command prints by default..." }}Exactly one terminal event is emitted per run: result (exit 0) or, on failure,
an error event (exit 1) on stdout instead of stderr:
{"v":1,"type":"error","message":"WireGuard config is missing an [Interface] section.","kind":"parse_error"}The kind lets a front-end react programmatically instead of parsing English:
kind |
Cause |
|---|---|
parse_error |
Invalid arguments or .conf (missing section, bad endpoint/address, --port and --host together) |
connection_error |
Cannot reach the device — socket/host resolution failure, or meshtastic-python/pyserial not installed |
timeout |
TCP connect or config readback timed out |
cancelled |
The operation was cancelled (host-cancel path) |
internal |
Any other unexpected error |
--rpc is additive and opt-in; without it the default single-JSON / stderr
behavior is unchanged. The v field is the event-contract version (currently
1); a non-additive change to the envelope bumps it so a client can reject an
incompatible engine.
The importer reads:
Interface.AddressInterface.PrivateKeyPeer.PublicKeyPeer.PresharedKeyPeer.Endpoint
CLI flags override imported values. Private and preshared keys are redacted from output unless --show-secrets is passed.
When configuring over the network, the app pings the selected host before opening the Meshtastic TCP API connection. A failed ping is logged, but the app still tries TCP because some networks block ICMP. Enabling or changing a WireGuard tunnel can alter routing or briefly interrupt the management connection. If the write succeeds but readback disconnects, reconnect over serial or the pre-existing network path and verify the saved config.
Likely next additions:
- Rebuild onto Electron
- Web-based client
- Batch/Fleet deployment and maintenance: load a CSV or JSON device list, apply one config per device, and export a success/failure report.
- Fallback configuration (FIRMWARE DEPENDENT): save the last confirmed working VPN config before remote writes and restore it if post-change verification fails.
- Release automation: build and attach
MeshtasticWireGuardConfigurator.exeto GitHub Releases from CI. - Full self-update flow: download, verify, replace the running EXE through a helper process, and restart.
- Firmware Maintenance to support flashing and updating WireGuard-capable firmware on verified ESP and Linux based nodes (compatible with Batch mode and ability to inject custom Linux patches)
For any future batch deployment workflow, each node must receive its own unique WireGuard client configuration. Reusing the same WireGuard private key or tunnel address across multiple nodes will cause routing and identity conflicts.
bin\setup-wireguard-gui.py downloads the Meshtastic protobuf sources from the experimental WireGuard firmware branch, generates Python bindings, and overlays them into the local Python environment so the configurator can use ModuleConfig.wireguard before upstream Meshtastic clients support it natively.
The default setup profile targets the current experimental WireGuard branch. For trial firmware based on Meshtastic 2.8 development protos, rebuild the local environment with:
python bin\setup-wireguard-gui.py --recreate --proto-profile 2.8-wireguard-trialTo test against a local 2.8 firmware checkout, pass both the profile and local protobuf directory:
python bin\setup-wireguard-gui.py --recreate --proto-profile 2.8-wireguard-trial --proto-dir C:\path\to\Meshtastic\protobufs\meshtasticDuring each device read or push, the configurator asks the device for metadata first. Firmware versions 2.8.0 and newer are labeled with the 2.8-wireguard-trial protobuf profile in CLI output and the GUI health panel.
To use a local protobuf checkout instead of downloading:
python bin\setup-wireguard-gui.py --proto-dir C:\path\to\Meshtastic\protobufs\meshtasticRegardless of which proto source is used (firmware branch download, upstream 2.8
protos plus overlay, or a local --proto-dir), setup ends by verifying the
generated Python protobufs against a single declared contract
(WIREGUARD_CONTRACT in bin/setup-wireguard-gui.py):
ModuleConfig.wireguard= field 17LocalModuleConfig.wireguard= field 18AdminMessage.ModuleConfigType.WIREGUARD_CONFIG= 16WireGuardConfigfields 1-9:address,server_addr,server_port,private_key,public_key,preshared_key,enabled,status,last_error
These tag numbers must match the firmware's generated nanopb headers
(src/mesh/generated/meshtastic/*.pb.h). If any tag has drifted, setup fails
with a precise diff instead of silently producing a client that is
wire-incompatible with the device. This guards against the scenario where
upstream Meshtastic assigns one of these (currently unreserved) tags to a
different module. See discussion #10716.
Once WireGuard configuration lands in official Meshtastic protobufs and
meshtastic-python — ideally with these tags reserved upstream — this
compatibility layer can be simplified or removed.
The target device must run a Meshtastic firmware build that includes the WireGuard module config fields and firmware support for runtime WireGuard configuration.
GPL-3.0, matching the Meshtastic firmware repository this tool was split from.