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 fordotnetwork(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— installsmeshcore-clivia pipx.meshcore_capture— wraps the upstreamagessaman/meshcore-packet-captureinstaller. 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 ingroup_vars/all/vars.yml) — current upstream (v2.0.0+): dedicatedmeshcore-capturesystem user,/opt+/etc/meshcore-packet-capture,config.d/*.toml, servicemeshcore-packet-capture.service.meshcore_capture_layout: legacy— howdm-baldock/dm-ashwellwere originally set up: flat~/.meshcore-packet-capture,.env.local, servicemeshcore-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_becomeas a connection variable takes precedence over a task's ownbecome: false, silently running supposedly-unprivileged tasks as root. Because of this,becomeis set once at the play level insite.yml, never ingroup_vars/host_vars— see the comment at the top ofgroup_vars/all/vars.yml.armv6lhosts (Pi Zero W, i.e.dm-baldock) have no prebuiltcffiwheel and needlibffi-dev/pkg-configto compile it; that apt task in thebaserole is conditioned onansible_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").