---
title: Meshtastic Sidecar Node — Modular Deployment Runbook
type: runbook
tags:
  - mesh
aliases: []
related:
  - [[idahomesh-vpn-device-setup]]
  - [[meshtastic-headscale-runbook]]
  - [[idahomesh-bridge-setup]]
  - [[headscale-onboard-node]]
  - [[advbbs-project]]
updated: 2026-06-18
---
# Meshtastic Sidecar Node — Modular Deployment Runbook

Deploy a Raspberry Pi node with a real Meshtastic radio and an operator-selected combination of software modules. Each module is self-contained — install only what the site needs.

---

## Input Variables

Fill these in before running any module:

| Variable | Value | Description |
|----------|-------|-------------|
| `HOSTNAME` | | Node hostname (e.g., `mt-isr`) |
| `NODE_IP` | | Local IP address (e.g., `192.168.1.112`) |
| `SSH_USER` | | SSH username (e.g., `isr`) |
| `SSH_PASS` | | SSH password (from `.ref/credentials`) |
| `RADIO_DEVICE` | | Serial device for radio (e.g., `/dev/ttyACM0`) |
| `OS_VERSION` | | `debian-12` (bookworm) or `debian-13` (trixie) |

---

## Module Menu

Select which modules to deploy. Check off as completed:

```
Meshtastic Sidecar Node Deployment: $HOSTNAME ($NODE_IP)

Select modules to install:
[        ] Module 1: IP Settings — Static IP, hostname, DNS
[        ] Module 2: Tailscale — Headscale VPN registration
[        ] Module 3: meshtasticd — Meshtastic radio daemon
[        ] Module 4: Meshtastic Python — meshtastic CLI + Python API
[        ] Module 5: advBBS — Bulletin board system
[        ] Module 6: Meshing-Around — Mesh bot + WebGUI
[        ] Module 7: MeshMonitor — Feed mesh data to monitoring
```

### Dependencies

```
Module 1 ─── standalone
Module 2 ─── standalone
Module 3 ─── standalone (but needed by 4, 5, 6)
Module 4 ─── requires Module 3
Module 5 ─── requires Module 3
Module 6 ─── requires Module 3
Module 7 ─── requires Module 2
```

---

## Module 1: IP Settings

### Set hostname

```bash
sshpass -p '$SSH_PASS' ssh $SSH_USER@$NODE_IP

sudo hostnamectl set-hostname $HOSTNAME
echo "127.0.1.1  $HOSTNAME" | sudo tee -a /etc/hosts
```

### Configure static IP (if not using DHCP reservation)

For NetworkManager-managed systems (Raspberry Pi OS with desktop):

```bash
sudo nmcli con mod "preconfigured" \
  ipv4.method manual \
  ipv4.addresses "$NODE_IP/24" \
  ipv4.gateway "192.168.1.1" \
  ipv4.dns "1.1.1.1,8.8.8.8"
sudo nmcli con up "preconfigured"
```

For headless systems with `/etc/network/interfaces`:

```bash
sudo tee /etc/network/interfaces.d/eth0 << EOF
auto eth0
iface eth0 inet static
    address $NODE_IP/24
    gateway 192.168.1.1
    dns-nameservers 1.1.1.1 8.8.8.8
EOF
sudo systemctl restart networking
```

For WiFi-only Pis, use `wlan0` instead of `eth0` and configure via `wpa_supplicant` or NetworkManager.

### Configure DNS fallback

```bash
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf
```

### Verify

```bash
hostname                        # Should show $HOSTNAME
ip addr show                    # Should show $NODE_IP
ping -c 3 1.1.1.1               # Internet connectivity
ping -c 3 192.168.1.1            # Gateway reachable
```

---

## Module 2: Tailscale

### Choose tailnet

| Tailnet | Headscale URL | Prefix | Key generation |
|---------|---------------|--------|----------------|
| Echo6 | `https://vpn.echo6.co` | 100.64.0.0/10 | On Contabo |
| IdahoMesh | `https://vpn.idahomesh.com` | 100.100.0.0/16 | On CT 106 |

### Install Tailscale

```bash
curl -fsSL https://tailscale.com/install.sh | sh
```

### Generate preauthkey

**Echo6** (from cortex or any machine with Tailscale access to Contabo):

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

**IdahoMesh** (from utility Proxmox host):

```bash
ssh root@192.168.1.241 'pct exec 106 -- headscale preauthkeys create --user <USER_ID> --expiration 24h'
```

Users: malice (4), sidpatchy (2), nebra (3)

### Register with Headscale

```bash
sudo tailscale up \
  --login-server=$HEADSCALE_URL \
  --authkey=$PREAUTH_KEY \
  --hostname=$HOSTNAME
```

### Install DNS bootstrap drop-in (reboot-safe)

Prevents chicken-and-egg [[dns]] failure where tailscaled can't resolve the coordination server after reboot:

```bash
sudo mkdir -p /etc/systemd/system/tailscaled.service.d
sudo tee /etc/systemd/system/tailscaled.service.d/dns-bootstrap.conf << 'EOF'
[Service]
ExecStartPre=/bin/sh -c "grep -q nameserver /etc/resolv.conf || echo nameserver 1.1.1.1 > /etc/resolv.conf"
EOF
sudo systemctl daemon-reload
```

### Verify

```bash
tailscale status          # Should show connected
tailscale ip -4           # Should show 100.64.x.x or 100.100.x.x
ping -c 3 100.64.0.1     # Echo6: ping Contabo
ping -c 3 100.100.0.1    # IdahoMesh: ping Headscale
```

### Enable accept-routes (if needed)

Required to reach subnets advertised by the mesh-bridge (CT 107):

```bash
sudo tailscale up --login-server=$HEADSCALE_URL --accept-routes
```

---

## Module 3: meshtasticd

### Detect OS and add OBS repo

**Debian 12 (bookworm):**

```bash
curl -fsSL https://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_12/Release.key | \
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/network_Meshtastic_beta.gpg
echo 'deb http://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_12/ /' | \
  sudo tee /etc/apt/sources.list.d/meshtasticd.list
```

**Debian 13 (trixie) / Ubuntu 24.04:**

```bash
curl -fsSL https://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_13/Release.key | \
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/network_Meshtastic_beta.gpg
echo 'deb http://download.opensuse.org/repositories/network:/Meshtastic:/beta/Debian_13/ /' | \
  sudo tee /etc/apt/sources.list.d/meshtasticd.list
```

### Install

```bash
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y meshtasticd
```

### Configure — choose hardware type

#### Option A: USB radio (`/dev/ttyACM0` or `/dev/ttyUSB0`)

```bash
sudo tee /etc/meshtasticd/config.yaml << EOF
Lora:
  Module: sx1262
  DIO2_AS_RF_SWITCH: true
  CS: 8
  IRQ: 22
  Busy: 27
  Reset: 17

Serial:
  Enabled: true
  Device: $RADIO_DEVICE

Networking:
  EnableUDP: true
EOF
```

> **Note:** If using a USB-connected Meshtastic device (RAK, T-Beam, etc.), the `Lora` section should be removed entirely and the device is managed via the serial interface. The config becomes:

```bash
sudo tee /etc/meshtasticd/config.yaml << EOF
Serial:
  Enabled: true
  Device: $RADIO_DEVICE

Networking:
  EnableUDP: true
EOF
```

#### Option B: Nebra SX1262 Pi Hat (e.g., aida-nebra)

```bash
sudo tee /etc/meshtasticd/config.yaml << EOF
Lora:
  Module: sx1262
  DIO2_AS_RF_SWITCH: true
  CS: 24
  IRQ: 22
  Busy: 27
  Reset: 17

Networking:
  EnableUDP: true
EOF
```

#### Option C: SIM mode (no physical radio, virtual mesh node)

```bash
sudo tee /etc/meshtasticd/config.yaml << EOF
Lora:
  Module: sim

General:
  MACAddress: "DE:AD:00:XX:XX:XX"

Networking:
  EnableUDP: true
EOF
```

### Fix permissions

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

### Add user to dialout group (for serial access)

```bash
sudo usermod -a -G dialout meshtasticd
```

### Enable and start

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now meshtasticd
```

### Verify

```bash
sudo systemctl status meshtasticd            # Should be active (running)
sudo journalctl -u meshtasticd -n 30         # Check for radio detection
ss -tlnp | grep 4403                          # API port listening
```

Look for lines like `Connected to radio` or `Detected module: sx1262` in the journal output.

---

## Module 4: Meshtastic Python

**Requires:** Module 3 (meshtasticd must be running)

### Install

```bash
sudo apt-get install -y python3-pip python3-venv
pip3 install --break-system-packages meshtastic
```

Or in a venv if the system enforces PEP 668:

```bash
python3 -m venv /opt/meshtastic-cli
/opt/meshtastic-cli/bin/pip install meshtastic
sudo ln -sf /opt/meshtastic-cli/bin/meshtastic /usr/local/bin/meshtastic
```

### Set node identity

```bash
# Set long name (visible in client apps and mesh maps)
meshtastic --host localhost --set-owner "$HOSTNAME"

# Set short name (4 chars max, shown in compact views)
meshtastic --host localhost --set-owner-short "${HOSTNAME:0:4}"
```

### Set device role

| Role | Use case |
|------|----------|
| CLIENT | Default, standard mesh participant |
| CLIENT_MUTE | Receives but doesn't rebroadcast |
| ROUTER | Prioritizes forwarding, minimal local traffic |
| ROUTER_CLIENT | Router + normal client features |

```bash
meshtastic --host localhost --set device.role CLIENT
```

### Set position (optional, for mesh maps)

```bash
meshtastic --host localhost --setlat XX.XXXX --setlon -XXX.XXXX --setalt XXXX
```

### Verify

```bash
meshtastic --host localhost --info
```

Should show node name, ID, role, and radio parameters. If it says "Error connecting," verify meshtasticd is running and no other client is connected to port 4403.

> **Important:** Only ONE client can connect to the meshtasticd API at a time. If a service (advBBS, Meshing-Around) is already connected, disconnect it first before using the CLI.

---

## Module 5: advBBS

**Requires:** Module 3 (meshtasticd)

### Install prerequisites

```bash
sudo apt-get install -y python3-pip python3-venv git docker.io docker-compose
sudo systemctl enable --now docker
sudo usermod -a -G docker $SSH_USER
```

### Clone and configure

```bash
sudo mkdir -p /opt/advbbs
cd /opt/advbbs
sudo git clone https://forge.echo6.co/advbbs/advbbs.git .
sudo cp config.example.toml config.toml
```

Edit `config.toml`:

```toml
[bbs]
name = "$HOSTNAME BBS"
callsign = "${HOSTNAME^^}"
admin_password = "CHANGE_THIS"

[meshtastic]
connection_type = "tcp"
tcp_host = "localhost"
tcp_port = 4403

[database]
backup_path = "/data/backups"
backup_interval_hours = 24

[sync]
enabled = true
# Add federation peers as needed:
# [[sync.peers]]
# node_id = "!abc12345"
# name = "REMOTE-BBS"
# protocol = "advbbs"
# enabled = true
```

### Deploy with Docker

Standard (x86):

```bash
cd /opt/advbbs
sudo docker-compose up -d
```

Raspberry Pi (memory-optimized):

```bash
cd /opt/advbbs
sudo docker-compose -f docker-compose.rpi.yml build
sudo docker-compose -f docker-compose.rpi.yml up -d
```

### Deploy without Docker (native, for minimal Pi setups)

```bash
cd /opt/advbbs
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Create systemd service
sudo tee /etc/systemd/system/advbbs.service << EOF
[Unit]
Description=advBBS Meshtastic BBS
After=network.target meshtasticd.service
Requires=meshtasticd.service

[Service]
Type=simple
User=$SSH_USER
WorkingDirectory=/opt/advbbs
ExecStart=/opt/advbbs/venv/bin/python -m advbbs
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now advbbs
```

### Verify

```bash
sudo docker-compose logs -f          # Docker
sudo journalctl -u advbbs -f         # Native

# From another mesh node, DM the BBS node:
# !help
# Should get a response listing commands
```

---

## Module 6: Meshing-Around

**Requires:** Module 3 (meshtasticd)

### Option A: Docker (recommended for nodes with >1GB RAM)

```bash
sudo apt-get install -y docker.io docker-compose
sudo systemctl enable --now docker

sudo mkdir -p /opt/meshing-around
cd /opt/meshing-around

# Clone from upstream
sudo git clone https://github.com/SpudGunMan/meshing-around.git .

# Create config — point at local meshtasticd
# Edit mesh.ini or config file as needed:
# interface_type = tcp
# hostname = localhost
# port = 4403

sudo docker-compose up -d
```

WebGUI accessible at `http://$NODE_IP:8085`

### Option B: Native Python (for RPi Zero 2 W or low-memory nodes)

```bash
sudo mkdir -p /opt/meshing-around
cd /opt/meshing-around
sudo git clone https://github.com/SpudGunMan/meshing-around.git .
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Create systemd service
sudo tee /etc/systemd/system/meshing-around.service << EOF
[Unit]
Description=Meshing-Around Mesh Bot + WebGUI
After=network.target meshtasticd.service
Requires=meshtasticd.service

[Service]
Type=simple
User=$SSH_USER
WorkingDirectory=/opt/meshing-around
ExecStart=/opt/meshing-around/venv/bin/python mesh_bot.py
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now meshing-around
```

### Verify

```bash
sudo docker-compose logs -f                  # Docker
sudo journalctl -u meshing-around -f         # Native
curl -s http://localhost:8085 | head -5       # WebGUI responds
```

---

## Module 7: MeshMonitor

**Requires:** Module 2 (Tailscale, to reach MeshMonitor at 100.64.0.7)

MeshMonitor runs on CT 100 (192.168.1.100 / 100.64.0.7:8080). This module configures the sidecar node to report mesh data to the MeshMonitor instance.

### Configure MeshMonitor connection

MeshMonitor connects to sidecar nodes via the meshtasticd TCP API. The sidecar node needs to be reachable from MeshMonitor over Tailscale.

1. Ensure meshtasticd is running and listening on port 4403 (Module 3)
2. Ensure Tailscale is connected (Module 2) so MeshMonitor can reach this node
3. Register the node in MeshMonitor's web UI:

```bash
# MeshMonitor admin credentials (from .ref/credentials)
# URL: http://100.64.0.7:8080
# User: admin
# Pass: 7redditGold

# Open MeshMonitor web UI and add this node:
# - Node address: $TAILSCALE_IP:4403
# - Node name: $HOSTNAME
```

### Verify

```bash
# Confirm Tailscale can reach MeshMonitor
ping -c 3 100.64.0.7
curl -s http://100.64.0.7:8080 | head -5

# Confirm meshtasticd API is accessible from the network
ss -tlnp | grep 4403
```

---

## Post-Deploy Checklist

```
[ ] IP / hostname configured (Module 1)
[ ] Tailscale connected to correct tailnet (Module 2, if selected)
[ ] meshtasticd running, radio detected (Module 3, if selected)
[ ] Node visible on mesh (check from another node)
[ ] Selected service modules running (advBBS, Meshing-Around, etc.)
[ ] All services survive reboot: sudo reboot && verify after
[ ] Credentials logged in .ref/credentials
[ ] Node added to environment.md (IP, Tailscale IP, purpose)
[ ] Node added to services.md (if running services)
```

---

## Troubleshooting

### meshtasticd won't start

```bash
sudo journalctl -u meshtasticd -n 50 --no-pager

# Common issues:
# - Serial device not found: check ls -la /dev/ttyACM0 (or ttyUSB0)
# - Permission denied: sudo chown -R meshtasticd:meshtasticd /var/lib/meshtasticd /etc/meshtasticd
# - Wrong OBS repo for OS version: Debian 12 vs 13 repo URL mismatch
```

### Radio not detected

```bash
# List serial devices
ls -la /dev/ttyACM* /dev/ttyUSB*

# Check kernel messages for USB events
dmesg | tail -20

# If device was unplugged/replugged, restart meshtasticd
sudo systemctl restart meshtasticd
```

### Meshtastic CLI can't connect

```bash
# Only ONE client can connect at a time
# Stop any running services first:
sudo systemctl stop advbbs meshing-around 2>/dev/null

# Then try CLI
meshtastic --host localhost --info

# Restart services when done
sudo systemctl start advbbs meshing-around 2>/dev/null
```

### Tailscale won't connect after reboot

```bash
# Check if DNS bootstrap drop-in is installed
cat /etc/systemd/system/tailscaled.service.d/dns-bootstrap.conf

# If missing, install it (Module 2)
# If present, check resolv.conf
cat /etc/resolv.conf

# Force fallback DNS and restart
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
sudo systemctl restart tailscaled
tailscale status
```

### advBBS won't connect to meshtasticd

```bash
# Verify meshtasticd is running and API port is open
ss -tlnp | grep 4403

# Check advBBS config points to localhost:4403
grep -A 3 '\[meshtastic\]' /opt/advbbs/config.toml

# Verify no other client is holding the connection
# meshtasticd only allows ONE concurrent API client
```

### Node not visible on mesh

```bash
# Check meshtasticd logs for radio status
sudo journalctl -u meshtasticd | grep -i -E "radio|connect|error"

# Verify UDP is enabled (for SIM nodes or multi-daemon setups)
grep -i udp /etc/meshtasticd/config.yaml

# Verify node identity is set
meshtastic --host localhost --info 2>/dev/null || echo "Another client connected — stop services first"
```

### Low memory (RPi Zero 2 W)

The RPi Zero 2 W has ~416MB RAM. Monitor usage:

```bash
free -h
# If memory is tight:
# - Use native Python installs instead of Docker
# - Only run ONE service alongside meshtasticd
# - Use advBBS's docker-compose.rpi.yml if using Docker
# - Disable web-reader profile to save memory
```

---

## Quick Reference

```bash
# Service management
sudo systemctl status meshtasticd
sudo systemctl status advbbs
sudo systemctl status meshing-around
sudo journalctl -u meshtasticd -f

# Mesh CLI (stop services first!)
meshtastic --host localhost --info
meshtastic --host localhost --set-owner "NodeName"
meshtastic --host localhost --set device.role CLIENT

# Tailscale
tailscale status
tailscale ip -4
tailscale ping <other-node>
```

---

## Existing Nodes Reference

| Node | IP | User | Radio | OS | Modules |
|------|-----|------|-------|-----|---------|
| aida-nebra | 192.168.1.253 | zvx | Nebra SX1262 Hat | RPi OS | 2 (Echo6), 3, 4 |
| mt-burleybutte | 192.168.1.185 | bb | Nebra SX1262 Hat | RPi OS | 2 (IdahoMesh), 3, 4 |
| mt-isr | 192.168.1.112 | isr | USB `/dev/ttyACM0` | Debian 13 | 3 (installed, inactive) |

---

*Last updated: 2026-02-21*
