status across configured hosts via read-only commands (claude-docker wrapper on the Pi5, local docker/systemctl here). Stable table + findings list, exit 0/2/1, CI job containers skipped, unreachable hosts become findings. Parsers unit-tested; smoked against the real fleet (19 genuine findings). Spec: doc/tool-parity.md 4.2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011KikHkfCiC3yELbsMN8fT9
fleet — one-shot homelab health snapshot
Replaces the hand-rolled ssh pi5-claude sudo claude-docker ps …
loops from every runbook with a single command that checks all
hosts: container states, disk fill and failed systemd units.
Read-only by construction — it only runs ps/df/systemctl through
the read-only claude-docker wrapper, so it needs no new permissions.
From doc/tool-parity.md §4.2.
Usage
fleet status [--host NAME] # all hosts, or one
== pi5 ==
containers: 54/58 healthy
disk / 81% used, 82G free
disk /srv/storage1 58% used, 1.2T free
failed units: 0 system, 0 user
...
19 finding(s):
pi5: container dozzle: exited (Exited (0) 5 months ago)
brasse-linux01: failed unit: cpupower-gui.service
| Exit | Meaning |
|---|---|
| 0 | everything healthy |
| 2 | findings printed (unhealthy/exited containers, disk ≥ warn %, failed units) |
| 1 | error (unknown host, config problem) |
Output is a stable text table — an agent can diff it between runs. Unreachable hosts become findings, not crashes. Gitea Actions' transient job containers are skipped.
Config
~/.config/fleet/config.json, created on first run (FLEET_CONFIG
overrides):
{
"disk_warn_pct": 85,
"hosts": [
{"name": "pi5", "ssh": "pi5-claude", "docker_cmd": "sudo claude-docker", "df_targets": "/ /srv/storage1", "user_units": false},
{"name": "brasse-linux01", "ssh": "", "docker_cmd": "docker", "df_targets": "/ /home", "user_units": true}
]
}
ssh: "" means run locally. Add hosts by appending entries.
Build & test
go test ./...
go build -o build/fleet .