Skip to content

Manage remote devices over the Meshtastic mesh. Define command aliases, execute them remotely via private channels, and get output back in chunks

Notifications You must be signed in to change notification settings

Dark-Alex-17/meshexec

MeshExec - Remote command execution over Meshtastic mesh networks

License crates.io link Release Crate.io downloads GitHub Downloads

MeshExec lets you execute commands on remote serially-connected Meshtastic nodes by listening for messages in a private channel. Define command aliases with arguments and flags in a YAML config, send a message like !myip over the mesh, and get the output back — no internet required!

How It Works

  1. MeshExec connects to a Meshtastic device via serial port
  2. It listens for messages prefixed with ! on a configured private channel
  3. When a matching command alias is received, it executes the corresponding shell command
  4. The output is chunked to fit within Meshtastic's message size limits and sent back over the mesh

This makes it ideal for managing remote devices in off-grid, decentralized, or IoT deployments where traditional network access isn't available.

Screenshots

chat config logs

Prerequisites

  • A Meshtastic device connected via serial (USB)
  • A private channel configured on the device
  • Rust 1.89.0+ (for building from source)

Installation

Cargo

If you have Cargo installed, then you can install MeshExec from Crates.io:

cargo install meshexec

# If you encounter issues installing, try installing with '--locked'
cargo install --locked meshexec

Homebrew (Mac and Linux)

To install MeshExec from Homebrew, install the MeshExec tap. Then you'll be able to install MeshExec:

brew tap Dark-Alex-17/meshexec
brew install meshexec

# If you need to be more specific, use the following:
brew install Dark-Alex-17/meshexec/meshexec

To upgrade to a newer version of MeshExec:

brew upgrade meshexec

Manual

Binaries are available on the releases page.

Linux/macOS Instructions

  1. Download the latest binary for your OS and architecture.
  2. cd to the directory where you downloaded the binary.
  3. Extract the binary with tar -C /usr/local/bin -xzf meshexec-<arch>.tar.gz (Note: This may require sudo)
  4. Now you can run meshexec!

Usage

MeshExec has three subcommands:

meshexec serve

Starts the runner server that listens for commands on the mesh network:

# Config file 'config.yml' is in current directory
meshexec serve

# Config file 'config.yml' is in another directory
meshexec --config-file /opt/meshexec/config.yml serve

meshexec tail-logs

Tails the MeshExec log file with optional colored output:

meshexec tail-logs

# Disable colored output
meshexec tail-logs --no-color

meshexec config-path

Prints the default configuration file path for your system:

meshexec config-path

This is useful for finding where to place your configuration file. The output varies by operating system:

  • Linux: ~/.config/meshexec/config.yaml
  • macOS: ~/Library/Application Support/meshexec/config.yaml
  • Windows: C:\Users\<User>\AppData\Roaming\meshexec\config.yaml

Global Options

Flag Short Env Var Description
--config-file <PATH> -c MESHEXEC_CONFIG_FILE Specify the config file (if not set, searches current directory then system config directory; see Configuration File Location)
--log-level <LEVEL> -l MESHEXEC_LOG_LEVEL Set the logging level: off, error, warn, info (default), debug, trace

Sending Commands Over the Mesh

Once MeshExec is running, send messages prefixed with ! on the configured private channel from any node on the mesh:

!help                    # List all available commands
!myip                    # Run the 'myip' command
!network check-port 8080 # Run a subcommand with an argument
!loki --help             # Show help for a specific command

Configuration

MeshExec is configured via a YAML file. You can specify an explicit path with --config-file, or let MeshExec automatically find for a configuration file.

Configuration File Location

MeshExec searches for a configuration file in the following order:

  1. Explicit path: If --config-file or MESHEXEC_CONFIG_FILE is set, that path is used directly
  2. Current directory: ./config.yaml or ./config.yml
  3. System config directory: The standard configuration directory for your operating system

To find the system config directory for your platform, run:

meshexec config-path

If no configuration file is found in any of these locations, MeshExec will display an error listing all searched paths.

Example Configuration

device: /dev/ttyUSB0
channel: 1
baud: null
shell: bash
shell_args:
  - -lc
max_text_bytes: 200
chunk_delay: 10000
max_content_bytes: 180
commands:
  - import: network_commands.yml

  - name: loki
    help: Ask Loki something
    args:
      - name: question
        help: Your prompt for Loki
        greedy: true
    command: loki "${question}"

  - name: list-disk-space
    help: List disk space for all mounted filesystems
    args:
      - name: servarr
        help: The servarr to hit
    flags:
      - long: --servarr-name
        short: -s
        arg: servarr_name
        help: The name of the servarr instance
    command: |
      # Can define scripts inline
      declare -a flags=()
      if [[ -n $servarr_name ]]; then
        flags+=("--servarr-name $servarr_name")
      fi
      managarr $servarr "${flags[@]}"

See the examples/ directory for a full configuration example (i.e. with subcommands).

Configuration Reference

Top-Level Fields

Field Type Required Description
device string Yes Serial device path (e.g. /dev/ttyUSB0, /dev/tty.usbserial-0001)
channel integer Yes Meshtastic channel number to listen on (must be a private channel)
baud integer No Baud rate for the serial connection (uses the Meshtastic default if null)
shell string Yes Shell to execute commands with (e.g. bash, sh, zsh)
shell_args list[string] No Arguments to pass to the shell (e.g. ["-lc"] for a login shell with command)
max_text_bytes integer Yes Maximum bytes per Meshtastic text message (device-dependent, typically ~200)
chunk_delay integer Yes Delay in milliseconds between sending chunks (prevents flooding the mesh)
max_content_bytes integer Yes Maximum content bytes per chunk before footer (should be less than max_text_bytes to leave room for [1/N] footers)
commands list Yes List of command definitions and/or imports

Commands

Commands can be either leaf commands (execute a shell command) or group commands (contain subcommands). They can also be imported from external YAML files, enabling more complex configuration structures.

Leaf Command
- name: myip
  help: Show the current system's public IP address
  command: curl -s checkip.amazonaws.com
Field Type Required Description
name string Yes The alias name (used after ! prefix, e.g. !myip)
help string No Help text shown when the user sends !<command> --help
command string Yes (for leaf) Shell command to execute. Use ${var_name} to interpolate arg/flag values
args list[Arg] No Positional arguments
flags list[Flag] No Named flags
Group Command

Group commands organize subcommands under a namespace:

# network_commands.yml
- name: network
  help: Network commands
  commands:
    - name: myip
      command: curl -s checkip.amazonaws.com
    - name: check-port
      args:
        - name: port
          help: The port number to check
      command: 'sudo lsof -i :${port}'
Field Type Required Description
name string Yes The group name
help string No Help text for the group
commands list Yes (for group) Nested subcommands and/or imports (recursive)

A command cannot have both command and commands — it must be one or the other. Group commands cannot have args or flags.

Importing Commands

Commands can be split across multiple YAML files using imports:

commands:
  - import: network_commands.yml
  - import: monitoring_commands.yml
  - name: inline-command
    command: echo "I'm defined inline"

The imported file can contain either a single command object or a list of commands. Circular imports are detected and will produce an error.

Nested Subcommand Imports

Imports can also be used inside group commands, enabling deeply nested command hierarchies organized across multiple files:

---
# config.yml
commands:
  - import: network_commands.yml

---
# network_commands.yml
name: network
help: Network commands
commands:
  - import: docker_commands.yml    # Imports can be nested inside groups
  - name: myip
    command: curl -s checkip.amazonaws.com

---
# docker_commands.yml
name: docker
help: Docker commands
commands:
  - name: hello
    command: docker run hello-world

This creates a command hierarchy where you can run:

  • !network myip — Show public IP
  • !network docker hello — Run the Docker hello-world container

Import paths are always relative to the file containing the import directive.

Args (Positional Arguments)

Field Type Required Description
name string Yes Argument name (used as the environment variable name; hyphens become underscores)
help string Yes Help text shown in --help output
default string No Default value if not provided (if omitted, the argument is required)
greedy bool No If true, consumes all remaining tokens. Must be the last arg. Default: false

Flags

Field Type Required Description
long string Yes Long flag name (must start with --, e.g. --verbose)
short string No Short flag alias (must be - followed by a single character, e.g. -v)
help string No Help text shown in --help output
arg string No If present, the flag takes a value (the string is the env var name). If absent, the flag is boolean
required bool No If true, the flag must be provided. Default: false
default string No Default value when the flag is not provided
greedy bool No If true, consumes all remaining tokens as the value. Requires arg to be set. Must be the last flag. Default: false

Greedy Behavior

Only one arg or flag in a command can be greedy, and it must be the last in its respective list. A greedy arg/flag consumes all remaining whitespace-separated tokens as a single value. This is useful for free-text inputs:

- name: ask
  help: Ask a question
  args:
    - name: question
      help: Your question
      greedy: true
  command: echo "${question}"

Sending !ask what is the weather today would set question to "what is the weather today".

Environment Variables

Variable Description Equivalent Flag
MESHEXEC_CONFIG_FILE Path to the config file --config-file
MESHEXEC_LOG_LEVEL Logging level (off, error, warn, info, debug, trace) --log-level

Contributing

See the CONTRIBUTING.md for details on how to contribute to this project.

Dependencies

  • meshtastic - Meshtastic protocol library for Rust
  • clap - Command line argument parsing
  • tokio - Async runtime
  • serde - Serialization/deserialization framework
  • log4rs - Logging framework

Creator

About

Manage remote devices over the Meshtastic mesh. Define command aliases, execute them remotely via private channels, and get output back in chunks

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages