Files
oikos/.agents/operations/agent-enrollment.md
dtoro d80a394b7f
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
docs: fix plan/repo drift, retire dead Goose+Nomos and Caveman tooling
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>
2026-07-12 18:19:41 +02:00

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 via oikos 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/ or lifecycle-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.yaml creation_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.yaml edit — 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.)

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.