What Is This?

This is a self-hosted Docker stack that brings together everything you need to run a MeshCore bot on your local network — with a friendly web interface for monitoring, control, and configuration. No command-line experience required after the initial setup.

The stack consists of five services that work together:

Architecture

All services run as Docker containers on the same host and communicate over an internal Docker network:

Your MeshCore Radio (USB)
         │
   [serial-bridge]  — owns the USB port, broadcasts to TCP :5000
         │                    │
  [meshcore-bot]       [remoteterm]
   :8080 web UI         :8000 web UI
         │
  [config-editor]
   :8090 web UI

  [nginx dashboard]  :80  — landing page + API proxy for dashboard widgets
The serial bridge is the key to running multiple services with a single USB radio. It opens the port once and streams data to any number of TCP clients simultaneously.

Quick Start

  1. Plug your MeshCore radio into a USB port on the host machine.
  2. Make sure /dev/ttyUSB0 (or your device path) is visible: ls /dev/ttyUSB*
  3. Clone or copy this project to your server.
  4. Open the Config Editor at http://<your-ip>:8090 and fill in at minimum:
    • Your Bot Name
    • Your Timezone (e.g. America/Chicago)
    • Your Latitude & Longitude (for weather and location features)
    • The Channels you want to monitor
  5. Click Save Config then Restart Bot.
  6. Check the Bot Web Viewer at :8080 — the uptime counter should be running.

Dashboard :80

The page you're reading right now. It provides a single entry point to the whole stack — live stats pulled from the bot, quick links to all three service UIs, and this documentation. Stats refresh automatically every 30 seconds.

Use the moon/sun icon in the top-right to switch between dark and light mode. Your preference is saved in the browser.

Bot Web Viewer :8080

The main monitoring interface for the MeshCore bot. Key tabs:

The uptime counter on the dashboard shows "Bot Down?" if the bot lost its radio connection. Check the serial bridge logs first: docker logs serial-bridge

RemoteTerm :8000

A full-featured interactive terminal for MeshCore, built by jkingsman. Use it to:

Both the bot and RemoteTerm share the radio via the serial bridge. If you send a command in RemoteTerm while the bot is also responding to something, the bytes are serialised through a queue — so there's no corruption, but brief timing overlaps are possible on very busy networks.

Config Editor :8090

A point-and-click editor for the bot's config.ini file. Every setting has a description pulled from the config file itself, so you always know what you're changing.

Most settings take effect after a restart. The bot supports live config reload for some settings (channels, rate limits) via the !reload admin command on the mesh.

Serial Bridge internal :5000

A small Python service that owns /dev/ttyUSB0 and exposes it as a TCP server on port 5000 — internal to the Docker network only, never exposed to the host.

It works as a broadcast multiplexer:

To check which clients are connected and whether the port is open:

docker logs serial-bridge

Configuration Overview

The bot is configured via bot/data/config.ini — an INI-format file divided into sections. You can edit it directly or use the Config Editor.

Always click Restart Bot after saving changes. Some settings (like connection type and serial port) require a full restart — they can't be hot-reloaded.

Connection

Controls how the bot physically connects to the MeshCore radio.

SettingWhat It DoesDefault / Options
connection_type Transport to use. In this Docker stack, always set to tcp — the serial bridge handles the USB connection. serial · tcp · ble
hostname TCP host to connect to. In Docker, use the service name serial-bridge. serial-bridge
tcp_port TCP port the serial bridge listens on. 5000
serial_port USB device path. Only used when connection_type = serial. /dev/ttyUSB0
timeout Seconds to wait for a connection before giving up and retrying. 30

Bot Behavior

The main section — controls the bot's identity, location, rate limiting, and how it handles messages.

Identity

SettingWhat It DoesDefault
bot_nameThe name the bot uses to identify itself on the mesh. This is also set as the device name on startup if auto_update_device_name is enabled.MeshCoreBot
auto_update_device_nameAutomatically rename the radio device to match bot_name when the bot starts.true
node_idLeave empty for automatic assignment. Only set this if you need a fixed node ID.empty
command_prefixOptional prefix required before every command. E.g. ! makes commands like !ping. Leave empty to allow commands without a prefix.empty

Location & Time

SettingWhat It DoesExample
timezoneTimezone for scheduled messages and time-based features. Use standard tz names.America/Chicago
bot_latitudeLatitude of the bot's location. Used for weather lookups and proximity calculations.33.1788
bot_longitudeLongitude of the bot's location.-96.90622

Rate Limiting

These settings prevent the bot from flooding the mesh network with too many messages.

SettingWhat It DoesDefault
rate_limit_secondsMinimum seconds between any two bot transmissions globally.10
bot_tx_rate_limit_secondsMinimum seconds between bot-initiated messages (adverts, scheduled messages).1.0
per_user_rate_limit_secondsMinimum seconds between replies to the same user. Prevents one user from spamming the bot.5
per_user_rate_limit_enabledEnable or disable per-user rate limiting.true
tx_delay_msMilliseconds to wait before sending a reply. Helps avoid collisions on busy networks.250

Startup & Advertising

SettingWhat It DoesOptions
startup_advertWhether to announce the bot on startup. flood sends a network-wide advert; zero-hop is local only; false is silent.false · zero-hop · flood
advert_interval_hoursSend a periodic flood advert every N hours. Set to 0 to disable.0

Contact Management

SettingWhat It DoesOptions
auto_manage_contactsHow the bot handles new nodes it discovers. bot = bot automatically adds them; device = let the radio handle it; false = manual only.false · bot · device
max_channelsMaximum number of channels to fetch from the radio. MeshCore supports up to 40.12
dm_max_retriesHow many times to retry a failed direct message.3

General

SettingWhat It DoesDefault
enabledMaster switch. When false the bot listens but never responds.true
passive_modeWhen true the bot never transmits anything — pure listener mode.false
db_pathPath to the SQLite database file. Relative paths resolve from the config file's directory.meshcore_bot.db

Channels

SettingWhat It DoesExample
monitor_channelsComma-separated list of channel names the bot should watch and respond to. Must match the channel names on your radio exactly.Public,test,emergency
respond_to_dmsWhether the bot responds to direct messages sent to it.true
channel_keywordsOptional whitelist of commands that are allowed in channels. All commands are always available in DMs. Leave empty to allow all commands in channels.help,ping,wx

Banned Users

SettingWhat It DoesExample
banned_usersComma-separated list of sender names to completely ignore. Matching is prefix-based — BadUser also matches BadUser 🍆. Banned users get no responses in channels or DMs.SpamBot,TrollUser

Localization

SettingWhat It DoesOptions
languageLanguage code for bot responses. Falls back to English if a translation is missing.en · es · fr · de · ja · es-MX
translation_pathDirectory containing translation JSON files. Only change this if you're using a custom translations folder.translations/

Admin Access Control

Certain commands (like !reload and !repeater) are restricted to admin users only.

SettingWhat It DoesFormat
admin_pubkeysComma-separated list of 64-character hex public keys that have admin privileges. Leave blank to disable all admin commands. Find your public key in the Bot Web Viewer under Contacts.64-char hex, e.g. 957d4806…
admin_commandsWhich commands require admin access.repeater,webviewer,reload
To find your public key: open the Bot Web Viewer → Contacts tab → find your callsign → the key is shown in the contact detail.

Companion Purge

When the radio's contact list is nearly full, the bot can automatically remove contacts that haven't been heard from in a while.

SettingWhat It DoesDefault
companion_purge_enabledEnable automatic purging of inactive contacts. Disabled by default for safety.false
companion_dm_threshold_daysConsider a companion inactive if they haven't sent a DM in this many days.30

Bot Commands

These commands can be sent to the bot over the mesh — in a monitored channel or via DM.

CommandDescriptionAccess
pingBot replies with a pong and signal info.Everyone
helpLists all available commands.Everyone
wx <location>Current weather for a zip code or city (e.g. wx 75068 or wx dallas).Everyone
statsNetwork statistics — node count, active users, top paths.Everyone
timeCurrent time in the bot's configured timezone.Everyone
pathShow the routing path between nodes.Everyone
reloadReload config.ini without restarting the bot. Connection settings cannot be changed this way.Admin only
repeaterManage the contact/repeater list.Admin only
If you set a command_prefix in config (e.g. !), all commands must be prefixed — so ping becomes !ping.

Troubleshooting

Dashboard shows "Bot Down?" instead of uptime

The bot web viewer can't find the bot's start time in the database. Check the bot is actually running:

docker ps
docker logs meshcore-bot --tail 30

If it's crashing on startup, the most common causes are:

Bot connects but "Radio Disconnected" shows in dashboard

The serial bridge started but lost the USB device. Check:

docker logs serial-bridge
ls /dev/ttyUSB*

If /dev/ttyUSB0 is gone, the USB device was unplugged or the kernel dropped it. Re-plug the radio and restart the serial bridge:

docker restart serial-bridge

Config Editor says "Restart Bot" but uptime doesn't reset

The Docker socket permission may be wrong. Check:

docker logs config-editor --tail 20

You can manually restart the bot with:

docker restart meshcore-bot

RemoteTerm shows "Connecting…" forever

RemoteTerm connects via the serial bridge. If the bridge is healthy but RemoteTerm can't connect:

docker restart remoteterm

If that doesn't help, restart the whole stack:

cd ~/meshcore && docker compose down && docker compose up -d