# 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 --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 ``` --- ## 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*