---
name: client-enrollment
risk_class: config_mutation
inputs: [hostname, kind, role]
verification: "homelab doctor (on the new client)"
docs_update_checklist: [hosts_narrative_page_if_lxc_or_vm]
---

# Client enrollment

Goal: bring a new host (workstation, LXC, VM) into inventory and the
secrets model, with mesh membership only where it's actually needed.
This wraps the existing `homelab client add` flow — see
[operations/agent-enrollment.md](../../operations/agent-enrollment.md) for
the full walkthrough; this runbook is the risk/lifecycle framing.

1. On any enrolled client: `homelab client add <hostname>` — appends a
   `hosts.<name>:` block to `inventory.yaml` (lifecycle `state: planned`
   → `provisioning`, per [seeds/ontology.yaml](../../../seeds/ontology.yaml)),
   commits + pushes.
2. Netbird join is **optional, not a required step** — only needed for
   hosts that must be reachable off-LAN (workstations that roam, e.g.
   `republic-laptop`, `mac-mini`). A node reachable on the household LAN
   (192.168.8.0/24 — most LXCs/VMs) doesn't need it: it's already
   reachable directly, and off-LAN clients reach it too via hubris's
   routed `192.168.8.0/24` Netbird network resource. Skip this step for
   LAN-only nodes; do it (out-of-band, console or setup key) only for
   hosts that need independent off-LAN reachability.
3. On the new host: run `bootstrap.sh` (add `--with-nomos` to also
   enroll the Hermes agent). This provisions `/etc/age/key.txt`, the
   sync timer, and prints an age pubkey.
4. Back on an enrolled client: `homelab client add <hostname>
   --finalize-pubkey <age1...>` — sets `age_pubkey`, grants shared
   secrets, re-keys SOPS, commits + pushes. This is the
   `provisioning → active` transition.
5. Verify: `homelab doctor` on the new client should show all checks
   green (clone, sync timer, age key, CLI symlink, MCP reachable).

Docs-update checklist: if the new host is an LXC/VM, add its narrative
page under `containers/` or `vms/` and set `doc_page` in its inventory
entry (host-level cards don't have a `doc_page` field yet — services do;
narrative pages are still found via the generated `see_also` in
`inventory.yaml`).
