homelab CLI v0.7: add ha token + ha ssh for Home Assistant
Mined another devvm user's Claude sessions for repeated, hand-rolled command patterns worth absorbing into the shared CLI. The dominant signal was Home Assistant "Sofia" work: a `kubectl | base64 | jq` token-extraction pipeline re-derived ~420x, and a bespoke non-interactive `ssh -o …` invocation reinvented ~30x — every session. The existing `home-assistant-sofia.py` already covers the API but goes unused from an arbitrary cwd (needs an env var set + a cwd-relative path), so agents bypassed it and hand-rolled everything. Add two verbs covering exactly the gaps the `ha` MCP can't (entity state/control stays with the MCP): - `ha token [--instance sofia|london]` (read): resolves the long-lived API token live from k8s secret openclaw/openclaw-secrets via the ambient kubeconfig — no pre-set env var. Composes as `curl -H "Authorization: Bearer $(homelab ha token)"`. - `ha ssh [--instance sofia|london] -- <cmd>` (write): deterministic non-interactive ssh to the HA host using the invoking user's key. Also fix the root cause: `home-assistant-sofia.py` now falls back to `homelab ha token` when its env var is unset (works from any directory), and the home-assistant skill points agents at these verbs + `homelab metrics query` instead of hand-rolled curls. README + ADR-0012 + AGENTS.md updated per the per-verb-group convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
600f1f933c
commit
48225f2dea
9 changed files with 378 additions and 9 deletions
46
docs/adr/0012-homelab-ha-verbs.md
Normal file
46
docs/adr/0012-homelab-ha-verbs.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# homelab Home Assistant verbs: token resolution + host SSH, not entity control
|
||||
|
||||
v0.7 adds `ha token` and `ha ssh`. They were chosen by mining a heavy HA
|
||||
operator's sessions: across ~1,900 shell commands the single most-repeated line
|
||||
(420×) was a hand-rolled `kubectl … | base64 -d | python -c '…token'` pipeline,
|
||||
and a bespoke `ssh -o StrictHostKeyChecking=no -o …` invocation was redefined as
|
||||
a shell function ~30× — both re-derived from scratch every session. The existing
|
||||
`home-assistant-sofia.py` already covers the *API*, but it goes unused from an
|
||||
arbitrary cwd (it needs `HOME_ASSISTANT_SOFIA_TOKEN` set and is referenced by a
|
||||
cwd-relative path), so agents bypassed it. A global verb on `$PATH` closes that
|
||||
gap for every user in every directory.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Only the two gaps the `ha` MCP can't fill.** The `ha` MCP server already
|
||||
does entity state and control (`get_state`, `call_service`, history, logs).
|
||||
Per the CLI's founding rule — *MCP-encoded actions are out of scope* (ADR-0004)
|
||||
— we do **not** reimplement `on`/`off`/`list`/`state`. We add only token
|
||||
*resolution* and host *SSH*, neither of which an API-only MCP can provide. The
|
||||
value is endpoint/secret/host resolution, exactly like `net`/`dns` (ADR-0010).
|
||||
- **`ha token` resolves live from the cluster, not from an env var.** It reads
|
||||
k8s Secret `openclaw/openclaw-secrets`, field `skill_secrets` (a base64 JSON
|
||||
blob of several tokens), and prints the per-instance key
|
||||
(`home_assistant_sofia_token` / `home_assistant_token`) via the ambient
|
||||
kubeconfig. This is robust to env drift — the precise failure that made agents
|
||||
re-derive the pipeline. Read-tier, prints the bare token to stdout so it
|
||||
composes in `$(…)`, mirroring `memory secret`.
|
||||
- **`ha ssh` is deterministic and per-user.** Flags are fixed for unattended
|
||||
use: `-F /dev/null` (ignore user ssh-config), `StrictHostKeyChecking=no` +
|
||||
`UserKnownHostsFile=/dev/null` (no host-key prompt/record — agents have no
|
||||
TTY), `BatchMode=yes` + `ConnectTimeout=10` (fail fast, never hang). The key
|
||||
is the **invoking user's** `~/.ssh/id_ed25519`, so the verb isn't tied to
|
||||
whoever first wrote the workflow; that user's key must be enrolled on the HA
|
||||
host. Write-tier (runs an arbitrary remote command).
|
||||
- **sofia is the default; london is structural.** The devvm sits on the Sofia
|
||||
LAN, so `vbarzin@192.168.1.8` is reachable and is the default instance. london
|
||||
(`hassio@192.168.8.103`) is in the instance map so `ha token --instance london`
|
||||
works (a pure secret read), but `ha ssh --instance london` generally won't
|
||||
connect from here — london is remote. We model it correctly rather than
|
||||
pretend it's reachable.
|
||||
- **Scope held at two verbs.** `ha api` (an authenticated curl passthrough for
|
||||
the endpoints the MCP/script don't cover — `/api/template`, `/reload`,
|
||||
`check_config`, `/error_log`) was deferred: once `ha token` exists, raw curl is
|
||||
already unblocked, and a generic passthrough overlaps the MCP. Re-measure via
|
||||
`usage top` (ADR-0011); add targeted sugar verbs only if those endpoints are
|
||||
still hand-rolled often.
|
||||
Loading…
Add table
Add a link
Reference in a new issue