Files
dotmesh-monitor/CLAUDE.md
T
2026-10-29 22:56:02 +00:00

5.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Ansible playbooks that deploy and configure MeshCore packet-capture monitoring nodes running on Raspberry Pi Zero W / Zero 2 W hardware (hostnames dm-baldock, dm-ashwell, dm-edworth). All work happens under ansible/.

Commands

cd ansible

# Deploy a single host (recommended for first run / testing)
ansible-playbook -i inventory.yml site.yml --limit dm-edworth

# Deploy all nodes
ansible-playbook -i inventory.yml site.yml

# Dry run
ansible-playbook -i inventory.yml site.yml --limit dm-edworth --check

# If sudo needs a password
ansible-playbook -i inventory.yml site.yml --limit dm-edworth --ask-become-pass

ansible.cfg sets inventory.yml and ../.vault_pass as defaults, so -i/--vault-password-file are usually implicit. The play prompts for a Tailscale auth key each run — leave blank if the node is already authenticated.

Bootstrapping a brand-new node needs manual, out-of-band steps first (flashing the SD card, ssh-copy-id, running sudo tailscale up, running the meshcore-packet-capture installer interactively over SSH). See the full checklist in README.md before assuming site.yml alone can bring up a new host — it can't, on the first pass.

Architecture

Inventory: inventory.yml defines group meshcore (all nodes), with subgroups zero_w / zero2_w by hardware. site.yml applies the meshcore group with become: true at the play level.

Vars layering: group_vars/all/vars.yml (shared defaults, e.g. SSH pubkeys, wifi networks every node joins) → group_vars/meshcore.yml (MQTT/packet-capture config, still plaintext) → host_vars/<host>/vars.yml (per-host serial_port for the MeshCore USB device, wifi_ssid_location). Secrets are split into parallel vault.yml files (Ansible Vault, password in gitignored ../.vault_pass) at both the group_vars/all/ and per-host levels — */vault.yml.example documents the expected keys.

Roles run in this order (site.yml): wifi → base → meshcore_cli → meshcore_capture → scripts.

  • wifi — NetworkManager (community.general.nmcli) connections for dotnetwork (home), dotmobile (phone hotspot fallback), and the host-specific deployed-location network.
  • base — authorizes SSH keys, installs base apt packages, sets MOTD, installs Tailscale (always) and authenticates it only if an auth key was supplied.
  • meshcore_cli — installs meshcore-cli via pipx.
  • meshcore_capture — wraps the upstream agessaman/meshcore-packet-capture installer. This is the trickiest role in the repo; see below.
  • scripts — deploys small helper scripts (voltage.sh, bandwidth.sh).

meshcore_capture role — two installer generations

The upstream installer changed its layout between versions, and this repo must support both because two nodes were bootstrapped under the old one:

  • meshcore_capture_layout: system (default, set in group_vars/all/vars.yml) — current upstream (v2.0.0+): dedicated meshcore-capture system user, /opt + /etc/meshcore-packet-capture, config.d/*.toml, service meshcore-packet-capture.service.
  • meshcore_capture_layout: legacy — how dm-baldock/dm-ashwell were originally set up: flat ~/.meshcore-packet-capture, .env.local, service meshcore-capture.service. Only kept so the role stays a safe no-op on those two hosts; do not use it for new nodes.

Which config template, service name, and update-script flags get used all branch on this variable — check host_vars/<host>/vars.yml for an override before assuming the system defaults apply.

The installer itself cannot run non-interactively on a fresh install — it opens /dev/tty directly for prompts and hangs forever over plain SSH/ansible with no human to answer. Ansible's creates: guard on that task means it only ever runs once; a human must SSH in and run it manually the first time (see README.md step 9), after which reruns of site.yml just rewrite config and restart the service. Do not attempt to script answers into it via script/pty tricks — this was already tried and abandoned as too fragile.

Other non-obvious constraints

  • ansible_become as a connection variable takes precedence over a task's own become: false, silently running supposedly-unprivileged tasks as root. Because of this, become is set once at the play level in site.yml, never in group_vars/host_vars — see the comment at the top of group_vars/all/vars.yml.
  • armv6l hosts (Pi Zero W, i.e. dm-baldock) have no prebuilt cffi wheel and need libffi-dev/pkg-config to compile it; that apt task in the base role is conditioned on ansible_architecture.
  • Flashing SD cards with Raspberry Pi Imager 1.9.6 (the current Flathub build) silently fails to write OS customisation on Trixie images — grab rpi-imager_2.0.10_amd64.deb+ from GitHub releases instead. Full detail in README.md ("Imager gotcha").