Compare commits
2
Commits
6173bf3ac2
...
673cedb6b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673cedb6b3 | ||
|
|
5d2569106e |
No files matched your search
@@ -0,0 +1,107 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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").
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Wrapper for upgrade.yml.
|
||||||
|
# Usage: ./upgrade.sh [--reboot] [ansible-playbook args, e.g. --limit dm-edworth --ask-become-pass]
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
EXTRA=()
|
||||||
|
if [[ "${1:-}" == "--reboot" ]]; then
|
||||||
|
EXTRA=(-e reboot=true)
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec ansible-playbook upgrade.yml "${EXTRA[@]}" "$@"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
# apt update + dist-upgrade on meshcore nodes, one host at a time.
|
||||||
|
# ansible-playbook upgrade.yml [--limit dm-edworth]
|
||||||
|
# ansible-playbook upgrade.yml -e reboot=true # reboot only nodes that need it
|
||||||
|
- name: Upgrade packages
|
||||||
|
hosts: meshcore
|
||||||
|
become: true
|
||||||
|
serial: 1
|
||||||
|
vars:
|
||||||
|
reboot: false
|
||||||
|
tasks:
|
||||||
|
- name: apt update and dist-upgrade
|
||||||
|
ansible.builtin.apt:
|
||||||
|
update_cache: true
|
||||||
|
upgrade: dist
|
||||||
|
autoremove: true
|
||||||
|
|
||||||
|
- name: Check whether a reboot is required
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /var/run/reboot-required
|
||||||
|
register: reboot_required
|
||||||
|
|
||||||
|
- name: Report reboot status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ inventory_hostname }}: reboot {{ 'required' if reboot_required.stat.exists else 'not required' }}"
|
||||||
|
|
||||||
|
- name: Reboot if required
|
||||||
|
ansible.builtin.reboot:
|
||||||
|
when: reboot | bool and reboot_required.stat.exists
|
||||||
Reference in new issue
Block a user