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.