Documentation and repo-hygiene pass following the client/server split:
Plan drift (audited all other active plans against current code):
- oikos-gaps-and-improvements.md: mark Section C and D.5 resolved (both
described cmd/hermes, renamed to cmd/nomos with a real LLM loop since);
refresh ~10 stale file:line citations; fix tool-count (33, not 28).
- liveness-drift-and-ux-cohesion.md: fix stale default-model claim (now
deepseek-v4-pro since 2026-07-10) and "not yet deployed" status.
- nomos-agent-code-review.md: fix C1's citation (one unauthenticated route
to nomos now, not two, after the client/server split).
- wails-desktop-app.md: record the production deploy outcome.
Repo structure: added missing directories to README/CONTRIBUTING layout
tables (checks/, tools/, cmd/webhook/, docs/operations/), fixed a broken
link, added ADR 0015 documenting the auth/CORS/client-split model (there
wasn't one despite CONTRIBUTING's own process requiring it), normalized
ADR 0013/0014's format drift, added an Authentication section to
AGENTS.md/CLIENTS.md (every example call was missing the now-required
bearer header).
Retired the Goose+Nomos workstation flow (bootstrap.sh --with-nomos,
tools/setup-nomos-soul.sh, .agents/operations/nomos-agent.md) and the
Caveman auto-install tooling (tools/setup-caveman.sh, tools/caveman/) —
both superseded by the production containerized Nomos agent, which has
never used either. Kept .agents/shared/caveman.md itself (the terse
writing-style convention agents still follow by reading it).
Deleted the orphaned legacy Python oikos/ directory — nothing imports it,
and bin/homelab (the CLI it was kept for) no longer exists in the repo.
Rewrote .agents/operations/agent-enrollment.md (365 -> ~110 lines) and
commands.md to match the current architecture instead of the retired
`homelab` CLI; migrated the still-true networking prerequisites (Netbird,
split-horizon DNS, SSH key distribution) into the knowledge base as a
runbook via upsert_knowledge rather than duplicating them in markdown.
Updated all 10 .agents/skills/ runbooks referencing the dead CLI with
their real MCP tool / REST API equivalents, or flagged them as needing
verification where no equivalent is confirmed yet.
Two real bugs found and fixed, not just docs:
- The tools/setup-*.sh auto-setup glob was tools/*.setup.sh in THREE
places (tools/post-pull.sh, bootstrap.sh, and internal/httpapi/impl.go's
GetClientContext handler) since the mechanism's introduction on
2026-06-02 — never matched any real filename, so no client has ever
picked up an auto-setup script via git-pull or the context-poller sync.
Fixed all three; the Go server-side fix is the one that actually matters
since it's what the current context-poller mechanism depends on.
- bootstrap.sh removed dead vestigial --gitea-token/--gitea-user flags
(parsed, never consumed) left over from an earlier clone-based model.
Also flagged, not fixed (documented as an open gap in
client-enrollment/SKILL.md): bootstrap.sh tells a freshly-enrolled client
to call POST /api/v1/clients/{slug}/activate to finish enrollment, but
that route doesn't exist in api/openapi.yaml — EnrollClient sets entities
to provisioning and nothing currently transitions them to active.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.3 KiB
Agent enrollment — operational notes
For the actual enrollment flow, see CLIENTS.md
— it's the current, authoritative version. This page used to duplicate
that flow in more detail, describing a homelab CLI-based two-step
ceremony (homelab client add reserves an inventory slot → client
bootstraps → operator finalizes the pubkey). That CLI and that flow don't
exist anymore — enrollment today is one shot: bootstrap.sh calls
POST /api/v1/clients/enroll directly and gets back an age keypair +
Infisical identity in the same response. What's left here is the handful
of things that are still true and weren't already covered elsewhere.
Prerequisites
| Requirement | Why | How to check |
|---|---|---|
Hostname matches an entry in inventory.yaml |
EnrollClient looks up the entity by slug derived from hostname; it must exist in planned/provisioning state. |
hostname (Linux) / scutil --get LocalHostName (macOS) |
| OS is Linux or macOS | bootstrap detects via uname -s |
uname -s |
| On the mesh (Netbird) or on the LAN | enrollment validates mesh IP against expected subnets | netbird status |
curl, jq, age, python3 |
bootstrap preflight (bootstrap.sh:100) — auto-installed on Fedora/RHEL/Debian/Ubuntu/macOS if missing |
command -v curl jq age python3 |
Can resolve *.hubris.network |
bootstrap calls the Oikos API and writes https://mcp.hubris.network/mcp |
dig +short mcp.hubris.network |
Hostname mismatch is the most common bootstrap failure
If the entity for your hostname doesn't exist yet (in planned or
provisioning state), enrollment 4xxs. Two fixes:
- Rename the host to match an existing planned entity:
sudo hostnamectl set-hostname <inventory-name>(Linux) or System Preferences → Sharing (macOS), then re-run. - Add/rename the inventory entry: edit
seeds/inventory.yaml, ingest viaoikos seed(or the equivalent MCP/API entity-creation path), then re-run bootstrap.
Networking prerequisites (Netbird, DNS, SSH key distribution)
Migrated to a runbook in the knowledge base — query
search_knowledge("netbird mesh dns") or get_entity_knowledge, or ask
Nomos. Covers: getting onto the Netbird mesh (interactive OIDC vs.
setup-key), why OIDC login can fail from off-mesh, split-horizon DNS
options, and distributing a new workstation's SSH pubkey across the fleet
via ssh/deploy-keys.sh.
Claude Code permissions for fleet ops
By default Claude Code's auto-mode classifier asks for confirmation on every
ssh into the mesh. Pre-authorize the common fleet ssh pattern by adding to
~/.claude/settings.json:
{
"permissions": {
"defaultMode": "auto",
"allow": [
"Bash(ssh -p 22022 *)"
]
}
}
This covers any ssh to a mesh peer on the homelab netbird port, scoped tight enough that the classifier doesn't gate it but loose enough to handle the variety of arguments.
Open questions (not verified against current architecture — don't
guess these from the old flow)
The old two-step ceremony had answers for these; the current one-shot
/api/v1/clients/enroll flow may handle them differently and this hasn't
been re-verified:
- Removing a client. No current equivalent confirmed for the old
homelab client remove(inventory removal + secret re-keying + key revocation). Likely maps to an entity lifecycle transition (.agents/skills/lifecycle-deprecate-node/orlifecycle-destroy-node/) but those skills reference the same dead CLI and need their own check. - Granting a secret to an already-enrolled client. The old flow
hand-edited
.sops.yamlcreation_rules+sops updatekeys. Given Infisical is now the primary secrets backend (SOPS is the DR fallback), the current mechanism is probably Infisical-side, not a.sops.yamledit — not confirmed.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Enrollment 404s / entity not found | Hostname doesn't match a planned/provisioning inventory entry |
See "Hostname mismatch" above |
gnutls_handshake() failed / TLS errors reaching *.hubris.network |
Client DNS resolves *.hubris.network to the public VPS instead of the LAN/mesh path |
See the networking runbook (split-horizon DNS section) |
Chat-mode ! shell can't sudo (a terminal is required to read the password) |
Claude Code's ! invocation doesn't allocate a tty, and standard sudo won't read its password from stdin or a non-tty pipe. |
Run the sudo'd command in a real terminal outside chat. For commands the agent issues repeatedly, configure passwordless sudo for the narrow set (e.g. /etc/sudoers.d/homelab-self with <user> ALL=(ALL) NOPASSWD: /usr/bin/dnf upgrade -y, /usr/bin/apt-get *). |
Changelog
2026-07-12 — trimmed to current architecture
Removed everything describing the retired homelab CLI-based two-step
enrollment ceremony (now: CLIENTS.md's one-shot flow), the Nous-Hermes/
Goose cross-link (that whole flow was removed the same day), and CLI-syntax
troubleshooting rows with no current equivalent. Migrated the still-true
Netbird/DNS/SSH-distribution content to a knowledge-base runbook rather
than duplicating it here. What's left is genuinely current or explicitly
flagged as unverified. Original ~365-line version is in git history
(git log -- .agents/operations/agent-enrollment.md) if any of the removed
detail turns out to still be needed.
2026-06-02 — SSH reachability post-bootstrap steps
Added a section covering SSH key generation, pubkey publication, deployment to hosts, SSH config generation, and LAN IP registration. New workstations enrolled via this doc automatically join the SSH mesh. (Superseded 2026-07-12 — migrated to the networking runbook.)
2026-05-31 — cross-link to nomos-agent.md
Added a sibling page covering Nous-Hermes-on-Goose enrollment. (Removed 2026-07-12 along with the rest of that flow.)
2026-05-21 — netbird-ssh JWT issuer + username + LAN-fallback troubleshooting rows
Added three rows to the troubleshooting table covering issues surfaced during the netbird vanilla migration. (Migrated 2026-07-12 to the networking runbook.)
2026-05-20 — initial page
Captures the enrollment flow validated during Phase 2 of the homelab context distribution rollout. hubris + LXC 105 (apps) enrolled; first workstation (republic-laptop) blocked on hostname mismatch, documented the resolution.