ENCLAVR DOCS

Prepare and enroll a host

Enroll a new machine using its own OpenBao consumer identity. Prefer delivery from a trusted existing member over SSH. Membership alone does not authorize issuing AppRoles: an OpenBao administrator issues the credential first. No migration receipt or 1Password account is required.

For the TUI, launch ./enclavr and use Operations → Issue host identity, then Enroll remote host or Enroll this host. All operation inputs are individual form fields; no request JSON or external editor is needed. Open a field with Enter, apply its value with Ctrl+S, press r to review, and confirm the completed plan with y. Enter alone never confirms. Errors retain the draft and identify the field to correct. Escape backs out; Ctrl+C cancels work and asks whether to exit the session.

1. Declare the exact host

Run the CLI command ./enclavr namespace status on an accepted member to locate the namespace writer. On that writer, launch the TUI with ./enclavr and select Config → Add host config, or use ./enclavr host add --namespace <namespace>.

Declare the hostname exactly as the target's hostname command reports it, with its platform, architecture, site, features and managed users. Ubuntu hosts must use 26.04. Enrollment rejects undeclared or inventory-only machines; ordinary host deploy retains its successful no-op for an unknown hostname.

Publish affected DNS/Caddy inputs through their declared service owners first. Enrollment validates the current full deployment inputs and configured tailnet policy; stale publications and policy drift fail closed.

2. Issue a dedicated consumer connection

On the namespace writer, launch ./enclavr and select Operations → Obtain administrator access. Enter the original recovery share in the concealed field. Enclavr discovers the deployed OpenBao directory and displays the accepted namespace and cluster read-only; no checkpoint file or paths are entered. Review and confirm with y. Enclavr verifies writer ownership and cluster trust, temporarily enables the loopback recovery listener, obtains a bounded administrator token in memory and closes the listener. Never initialize another cluster or recreate seal/TLS material to enroll a host.

The Issue host identity form collects the exact host, unique consumer name, issuance directory and additional read-only item names. It includes the three core items automatically. If administrator access is needed, this same standalone workflow opens while retaining the identity draft. Review the draft again after authentication; obtaining authority does not confirm issuance. It rejects conflicting host/cluster/scope checkpoints, orphaned connections and existing server role names lacking their checkpoint. Only the issued consumer connection goes to the target.

The initial cluster root being revoked means the original bootstrap token was retired after setup. Issuing a new host consumer identity is part of enrollment. It does not revoke or unenroll that host.

Administrator access is reused only within the current TUI process. Confirmed exit revokes it; Ctrl+C cancels work immediately and asks whether to exit. The TUI stays open when the token expires. OpenBao owns its configured lifetime: the attended token role is initialized with a four-hour maximum and renewal disabled. There is no TUI deadline or duration setting. New AppRoles also use four-hour token lifetimes; the attended review includes updating existing namespace AppRole lifetimes without replacing their credentials. Recovery input and administrator tokens are never saved to files.

For the CLI-only provisioning alternative, add one identity to the protected provisioning request, for example:

{
  "name": "new-host-consumer",
  "hostname": "new-host.example.enclavr.io",
  "items": [
    "assets.example.enclavr.io",
    "secrets.example.enclavr.io",
    "users.example.enclavr.io"
  ]
}

Include the exact additional personal/service items referenced by this host's plan. The three core items alone may be insufficient. Newly issued connections carry the host binding. Existing legacy connections remain unchanged on repeat provisioning; fresh enrollment rejects an unbound connection rather than guessing its owner or rotating it.

The administrator retains issuance checkpoints and original operator custody. Deliver only <name>.connection.json to its intended host. It contains the namespace, initialized cluster ID, explicit HTTPS endpoints, CA certificate, host binding, AppRole RoleID/SecretID and item scope. Keep it in a real 0700 directory as a regular 0600 file. Never paste its contents into argv, chat, logs or Git, and never copy an existing member's own connection.

3. Check prerequisites

Starting point Procedure
SSH-accessible host with Git Prefer remote enroll from a trusted accepted member. The target needs no Go installation or repository credential.
Existing authorized checkout Use host enroll on the exact target.
Native Ubuntu 26.04 or Kali without Git/toolchain Use the corresponding standalone bootstrap with --openbao-config /absolute/private/connection.json; it now uses OpenBao exclusively. Then validate and deploy through Go.
macOS, AL2023 or WSL2 Use remote enrollment or an authorized checkout; the native Ubuntu/Kali bootstrap scripts do not apply.

The standalone Ubuntu and Kali bootstraps read configr_src_ref and configr_op_repo_read_key from secrets.<namespace> through the accepted OpenBao connection. The source must be an HTTPS Git URL on forgejo.<namespace> naming one owner and repository. The checkout goes under <destination>/forgejo.<namespace>/<owner>/<repository>. Use --branch NAME to request an exact branch; otherwise a new clone uses the remote default. An existing matching checkout retains its revision and local changes, while a different origin or explicitly requested branch is rejected. Failed clones remove only their temporary checkout and authentication files, so a retry can complete. GitHub CLI, GitHub authentication and repository fan-out are no longer bootstrap prerequisites; --github-entity is retained as legacy metadata only. Once the checkout is available, the bootstrap delegates validation and deployment to the Go control plane.

The target must reach its pinned OpenBao endpoints before acceptance. If those addresses are on Tailscale, establish approved tailnet connectivity first. SSH reachability alone does not provide an OpenBao route. Enrollment does not open public access, change ACLs, forward the administrator's SSH agent, or replace endpoint trust.

The intended deployment account must exist and own the checkout location. Its required sudo access is used later by the ordered deployment. Finalize hostname, architecture and disk layout first.

4. Remote enrollment

Use a clean, committed controller branch containing the enrollment commands. Verify the target SSH host key independently and add it to the controller's known-hosts file. Public-key authentication must work without a prompt. Set the target hostname and install Git before proceeding.

In the TUI, launch ./enclavr, select Operations → Enroll remote host and select the exact inventory host and a target account from users.<namespace>, then enter its verified SSH address and port. The namespace is derived from the accepted connection. The default checkout is ${XDG_STATE_HOME:-$HOME}/forgejo.${enclavr_name}/OrganisationServiceManagement/core.enclavr.io; the installed connection defaults to ${XDG_STATE_HOME:-$HOME/.local/state}/enclavr-openbao-consumer/connection.json. HOME and XDG_STATE_HOME belong to the target account, not the controller. Custom overrides must be literal absolute paths; review shows resolved paths.

Enclavr selects an existing declared consumer at its authoritative custody location, or one previously issued by this workflow. New managed issuance is kept under the writer account's private state directory, enclavr-openbao-writer/enrollment/<namespace>/, with a deterministic host-bound identity name and retained checkpoint. Missing declared credentials, conflicting scope and ambiguous identity records fail closed. No filesystem search or replacement identity is used to bypass existing custody.

If issuance is needed, a prefilled identity form opens with the host, namespace, name and output location already supplied. Review the proposed read-only scope; attended administrator authentication opens if needed. Confirm issuance, then return and review enrollment again. Preparing credentials never confirms enrollment. Review validates current inputs and probes SSH identity/Git without delivering credentials. The receiver rechecks target file/checkout collisions during confirmed acceptance. Conflicting state is never overwritten.

For CLI request-file use only, create a regular 0600 enrollment.json in a private directory. Paths are literal absolute paths; JSON does not expand shell variables or ~. connection_file is on the controller; destination and repository are on the target:

{
  "namespace": "example.enclavr.io",
  "cluster_id": "<existing-initialized-cluster-id>",
  "hostname": "new-host.example.enclavr.io",
  "connection_file": "/home/operator/.local/state/enclavr/openbao-admin/identities/new-host-consumer.connection.json",
  "destination": "/home/deploy/.local/state/enclavr-openbao-consumer/connection.json",
  "repository": "/home/deploy/core.enclavr.io",
  "ssh_address": "192.0.2.10",
  "ssh_user": "deploy",
  "ssh_port": 22
}

Review and confirm with these CLI commands:

./enclavr remote enroll --request-file /absolute/private/enrollment.json
./enclavr remote enroll --request-file /absolute/private/enrollment.json --yes

The TUI shows the same application plan, then requires y to enroll. It passes the form values in memory; it does not create an intermediate request file.

The controller checks the candidate against its accepted cluster trust, verifies the target's exact hostname and architecture over strict SSH, builds the exact committed source for that platform, and delivers a Git bundle and compiled Go receiver. Credentials travel only over protected SSH stdin. Builds are cached by source and platform; unchanged retries reuse completed artifacts.

The receiver independently checks hostname, observed platform, live OpenBao access, deployment inputs and tailnet policy. It creates the pinned branch checkout, stores the host connection outside it, adopts the protected .env, records acceptance and installs the compiled entrypoint. An existing checkout must have the same branch, revision and origin with no local changes. Enrollment refuses to reset or overwrite a different checkout.

5. Local enrollment

For an existing authorized checkout, launch ./enclavr and select Operations → Enroll this host. The form starts with the current hostname and checkout. Supply the privately delivered consumer connection and its permanent protected path outside the checkout; review and confirm. The CLI alternative uses the same JSON without the three SSH properties. connection_file is the candidate already delivered privately to this host. Run from the exact repository directory:

./enclavr host enroll --request-file /absolute/private/enrollment.json
./enclavr host enroll --request-file /absolute/private/enrollment.json --yes

A fresh local checkout needs the Go prerequisite or an already compiled matching entrypoint.

Acceptance writes a repository-root .env selecting OpenBao and the absolute connection path. It records current inventory acceptance beside the connection as connection.json.enrollment.json. It does not add the host to an old migration receipt or reuse migration acceptance.

6. Deploy and verify

On the enrolled host, from the checkout:

./enclavr config validate
./enclavr host plan
./enclavr host deploy --yes
./enclavr host status

Enrollment prepares access; host deploy applies the ordered packages, users and services. Check the initial deployment and an unchanged repeat run before declaring the machine fully deployed.

Retry and conflict handling

Repeat the same request after an interruption. A checkout already published, connection already installed, or .env already adopted is retained when it matches. Every attempt revalidates current authoritative inputs. Unchanged connections, environment files, acceptance records and installed binaries are preserved; changed valid assets update acceptance without rotating credentials.

A conflicting environment, credential, installed binary, symlink, broad permissions, local Git work, stale source revision, revoked identity or failed policy check stops the operation. Enrollment never replaces or downgrades a different installed Enclavr binary. Inspect and reconcile through its owner. Do not remove issuance checkpoints, replace cluster material, or use another host's identity to make a retry succeed. A failed full deployment can be retried separately without enrolling or issuing a credential again.

See Plan, deploy, and verify for host deployment details.