echo6-docs/runbooks/meshtasticd-sim-nodes-runbook.md
Matt Johnson e9231ac24a Migration: consolidate Echo6 docs to cortex with full infrastructure cleanup sync
- Documents recent infrastructure cleanup (8 CTs destroyed, 35 DNS records removed, Headscale cleanup)
- Adds 24 new runbooks covering Authentik, PeerTube, Meshtastic, RECON, Proxmox, Mailcow, Internet Archive, GPU routing
- Adds project documentation for headscale, vaultwarden, peertube, matrix, mmud, advbbs, arr stack
- Updates services.md, environment.md, caddy.md, authentik.md to match live infrastructure
- Removes 4 deprecated runbook duplicates (canonical versions live in projects/)
- Adds .gitignore for binary archives and editor temp files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 06:02:16 +00:00

14 KiB
Raw Blame History

Meshtasticd SIM Node Runbook — LXC Deployment

Overview

This runbook covers deploying meshtasticd SIM (virtual) nodes inside LXC containers on Proxmox, each paired with a dedicated service (BBS, MeshSense, etc.). SIM nodes communicate with your real radio node over UDP and appear as normal nodes on the mesh — clients, maps, and other services can't tell the difference.

Design principle: One container = one SIM daemon + one service. Clean isolation, easy to snapshot, migrate, or tear down without affecting anything else.


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Proxmox Host                             │
│                                                                 │
│  ┌───────────────────┐                                          │
│  │  Real Radio Node   │  (LXC, bare metal, Pi, or USB device)   │
│  │  meshtasticd       │                                         │
│  │  Port 4403         │                                         │
│  │  /dev/ttyUSB0      │                                         │
│  │  UDP enabled       │                                         │
│  └────────┬──────────┘                                          │
│           │  UDP (vmbr0 or dedicated bridge)                    │
│           │                                                     │
│     ┌─────┴─────┬───────────────┐                               │
│     │           │               │                               │
│  ┌──▼────────┐ ┌▼────────────┐ ┌▼────────────┐                  │
│  │ LXC: BBS  │ │ LXC: Sense  │ │ LXC: Bot    │  ...more as     │
│  │           │ │             │ │             │  needed          │
│  │ mesht. sim│ │ mesht. sim  │ │ mesht. sim  │                  │
│  │ port 4403 │ │ port 4403   │ │ port 4403   │                  │
│  │ + BBS svc │ │ + MeshSense │ │ + bot svc   │                  │
│  └───────────┘ └─────────────┘ └─────────────┘                  │
└─────────────────────────────────────────────────────────────────┘

Since each SIM daemon is alone in its container, they can all use the default port (4403) internally. No port juggling needed.


Prerequisites

  • Proxmox host with LXC support
  • A working real radio meshtasticd instance somewhere on the network with UDP enabled
  • An LXC template (Ubuntu 22.04/24.04 or Debian 12 recommended)
  • Network bridge accessible to both the real radio node and LXC containers

Step 1: Create the LXC Container

From the Proxmox host CLI:

# Create an unprivileged container with static IP and TUN device for Tailscale
pct create <CTID> local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst \
  --hostname mesh-<service> \
  --memory 512 \
  --cores 1 \
  --rootfs local-lvm:4 \
  --net0 name=eth0,bridge=vmbr0,ip=192.168.1.<CTID>/24,gw=192.168.1.1 \
  --unprivileged 1 \
  --features nesting=1 \
  --start 0 \
  --password <from .ref/credentials>

# Add TUN device for Tailscale (must be done before first start)
cat >> /etc/pve/lxc/<CTID>.conf << EOF
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
EOF

pct start <CTID>

Adjust memory/cores/storage to taste. SIM daemons are lightweight — 512MB RAM and 1 core is plenty for the daemon plus most services.

Bootstrap standard packages

echo6-bootstrap-ct.sh <CTID>

If the script isn't on the Proxmox host, run echo6-onboard-node.sh first. See runbooks/proxmox-onboard-node.md.


Step 2: Install meshtasticd Inside the Container

Install from the Meshtastic OBS (OpenSUSE Build Service) repository:

pct exec <CTID> -- bash -c "
  # Add the Meshtastic beta repo (Debian 12)
  curl -fsSL https://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_12/Release.key | \
    gpg --dearmor -o /etc/apt/trusted.gpg.d/network_Meshtastic_beta.gpg
  echo 'deb http://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_12/ /' > \
    /etc/apt/sources.list.d/meshtasticd.list
  apt-get update -qq
  DEBIAN_FRONTEND=noninteractive apt-get install -y meshtasticd
"

For Ubuntu 24.04 containers, replace Debian_12 with Debian_13 in both URLs above.

Verify it's installed:

pct exec <CTID> -- meshtasticd --version

Step 3: Configure SIM Mode

Create or edit the config file. Since this is a dedicated container, you can use the default paths.

/etc/meshtasticd/config.yaml

Lora:
  Module: sim

General:
  # Prevent loading any default config.d overrides
  # ConfigDirectory: /etc/meshtasticd/config.d/

  # REQUIRED: Set a unique MAC address for this SIM node
  # Last 3 hex pairs = node color in client apps
  MACAddress: "DE:AD:00:FF:00:01"

MAC Address Guidelines

  • Every SIM node must have a unique MAC. If two nodes share a MAC, you'll get node ID collisions and unpredictable behavior.
  • The last 3 byte pairs map to a hex color code displayed in client apps.
  • Pick a scheme that makes sense for your deployment, e.g.:
    • DE:AD:00:FF:00:01 — SIM node 1 (BBS)
    • DE:AD:00:00:FF:02 — SIM node 2 (MeshSense)
    • DE:AD:00:FF:FF:03 — SIM node 3 (bot)

Step 4: Enable UDP Bridging

UDP is what connects SIM nodes to the rest of your mesh. Every meshtasticd instance — real radio and all SIM nodes — needs UDP enabled and must be able to reach each other on the network.

Add to your SIM node's config:

Networking:
  EnableUDP: true

Also ensure your real radio node has UDP enabled in its own config.

Network Considerations

For UDP mesh traffic to flow between LXC containers and your real radio node:

  • All containers and the real radio host must be on the same Layer 2 network (same bridge, same subnet) — UDP broadcast/multicast needs to reach all instances.
  • If your real radio runs on a different host (e.g., a Raspberry Pi), make sure it's on the same VLAN/subnet as the LXC bridge.
  • Proxmox's default vmbr0 bridge works fine if everything is on the same network.
  • If you're running the real radio in its own LXC and passing through USB, the same bridge rules apply.

Firewall note: If you have Proxmox firewall or iptables rules on the host, ensure UDP traffic between containers is not blocked. Meshtasticd uses UDP broadcast by default — verify your bridge allows broadcast forwarding.


Step 5: Configure the systemd Service

The meshtasticd package likely installs a default unit file. If you need to customize it:

sudo systemctl edit meshtasticd --full

Or create/verify /etc/systemd/system/meshtasticd.service:

[Unit]
Description=Meshtastic Daemon - SIM Node
After=network.target

[Service]
Type=simple
User=meshtasticd
Group=meshtasticd
ExecStart=/usr/bin/meshtasticd -d /var/lib/meshtasticd/vfs -c /etc/meshtasticd/config.yaml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Since this is the only meshtasticd instance in the container, you don't need custom port flags — the default port is fine.

Ensure directory ownership:

sudo mkdir -p /var/lib/meshtasticd/vfs
sudo chown -R meshtasticd:meshtasticd /var/lib/meshtasticd
sudo chown -R meshtasticd:meshtasticd /etc/meshtasticd

Start and enable:

sudo systemctl daemon-reload
sudo systemctl enable --now meshtasticd
sudo systemctl status meshtasticd

Step 5b: Install Tailscale and Register with Headscale

Install Tailscale inside the container:

pct exec <CTID> -- bash -c "
  echo nameserver 1.1.1.1 > /etc/resolv.conf
  echo nameserver 8.8.8.8 >> /etc/resolv.conf
  curl -fsSL https://tailscale.com/install.sh | sh
"

Generate a preauth key on Contabo (user ID 1 = echo6):

ssh root@100.64.0.1 'docker exec headscale headscale preauthkeys create --user 1 --reusable --expiration 1h'

Register the node:

pct exec <CTID> -- tailscale up --login-server https://vpn.echo6.co --authkey <PREAUTH_KEY> --hostname mesh-<service>

# Verify
pct exec <CTID> -- tailscale status

Note: The TUN device must already be configured in the container config (done in Step 1). If Tailscale fails to start, verify /dev/net/tun exists inside the container.


Step 6: Configure the SIM Node

With the daemon running and no service connected yet, configure it via CLI:

# Install the Meshtastic Python CLI
pip install meshtastic

# Check that the node is up
meshtastic --host localhost --info

# Set a descriptive name
meshtastic --host localhost --set-owner "BBS Node"
meshtastic --host localhost --set-owner-short "BBS"

# Optionally set a position (makes it appear on mesh maps)
meshtastic --host localhost --setlat XX.XXXX --setlon -XXX.XXXX --setalt XXXX

# Set the node role as appropriate
meshtastic --host localhost --set device.role CLIENT

If you don't set a position, the node still functions on the mesh but won't appear on maps.


Step 7: Install and Connect Your Service

Now install whichever service this container is dedicated to and point it at localhost:4403 (or whatever the default meshtasticd API port is).

Example: BBS

# Install your BBS software of choice
# Point it at the local meshtasticd instance
# BBS_CONFIG: host=localhost, port=4403

Example: MeshSense

# MeshSense supports non-default ports
# Configure it to connect to localhost:4403

Reminder: One client API connection per daemon. Once the service is connected, don't also try to connect a client app to the same instance. If you need to reconfigure the node, stop the service first.


Provisioning Additional Containers

For each new service, repeat steps 17 with:

  1. A new container ID and hostname (e.g., mesh-sense, mesh-bot)
  2. A unique MAC address in config.yaml
  3. The specific service installed alongside meshtasticd

Quick Clone Approach

Once you have one container fully set up, you can clone it in Proxmox and just change:

  • Container hostname
  • MAC address in /etc/meshtasticd/config.yaml
  • The service installed/configured
  • Node owner name via meshtastic --host localhost --set-owner "NewName"
# Clone from Proxmox CLI
pct clone 201 202 --hostname mesh-sense --full
pct start 202
pct enter 202

# Update the MAC address
nano /etc/meshtasticd/config.yaml  # change MACAddress

# Restart meshtasticd to pick up new MAC
systemctl restart meshtasticd

# Reconfigure node identity
meshtastic --host localhost --set-owner "MeshSense"

USB Passthrough for Real Radio Container

If you want your real radio node in an LXC too (rather than bare metal), you need to pass the USB device through to the container.

On the Proxmox host, find the device:

ls -la /dev/serial/by-id/
# or
lsusb

Add to the container config (/etc/pve/lxc/<CTID>.conf):

lxc.cgroup2.devices.allow: c 188:* rwm
lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file

Adjust the device path and cgroup major number as needed for your hardware. The container will also need a config.yaml with the real LoRa module config instead of Module: sim.


Troubleshooting

SIM nodes not seeing the real radio (or each other)

  • Verify UDP is enabled on all instances
  • Confirm all containers are on the same bridge/subnet
  • Check for firewall rules blocking UDP broadcast between containers
  • Test basic connectivity: ping between containers

Node ID collisions / "things get weird"

  • Every SIM node must have a unique MAC address — check each container's config.yaml
  • After changing a MAC, restart meshtasticd and verify with meshtastic --host localhost --info

Service can't connect to meshtasticd

  • Is meshtasticd actually running? systemctl status meshtasticd
  • Is another client already connected? Only one API connection per instance.
  • Check the port: ss -tlnp | grep 4403

Config not taking effect

  • Make sure ConfigDirectory is commented out or pointed somewhere empty so default config.d files don't override your settings
  • Restart after config changes: systemctl restart meshtasticd

Permission errors

  • VFS and config directories must be owned by the meshtasticd user
    chown -R meshtasticd:meshtasticd /var/lib/meshtasticd
    chown -R meshtasticd:meshtasticd /etc/meshtasticd
    

Quick Reference

# Inside any SIM container:
systemctl status meshtasticd          # Check daemon status
journalctl -u meshtasticd -f          # Follow logs
meshtastic --host localhost --info    # Node info (only if no service is connected)

# From Proxmox host:
pct list                              # List all containers
pct enter <CTID>                      # Shell into container
pct exec <CTID> -- systemctl status meshtasticd  # Check without entering

Container Inventory Template

Track your deployment:

CTID Hostname MAC Address Service Port Notes
200 mesh-radio (hardware) Real radio 4403 USB passthrough
201 mesh-bbs DE:AD:00:FF:00:01 BBS 4403
202 mesh-sense DE:AD:00:00:FF:02 MeshSense 4403
203 mesh-bot DE:AD:00:FF:FF:03 Bot 4403

Credits

Procedure sourced from a community discussion between pdxlocs, tedward, and wehooper4 regarding multi-daemon meshtasticd deployments with SIM mode. Adapted for LXC/Proxmox deployment.