# Host enrollment

`dreamlake hosts enroll` bootstraps a user-owned nymph over your existing SSH
connection, then waits for the backend to confirm that exact enrollment online.
Provider registration is not required. It requires the new authenticated Hosts
API and a control plane supporting identity-bound grants and signed reconnect;
these changes must be deployed together. `--dry-run` only validates inputs and
does not contact the server or target.

The target needs Python 3, OpenSSL, an accessible systemd user manager, and user
linger enabled for service persistence after logout. Enrollment never uses sudo.
It reuses an installed `nymph`, or downloads the official installer into the
user-local binary directory. `--nymph-version` selects an installer version when
the binary is absent. The target generates and retains its nymph private key;
only its public key and Unix username go to the enrollment API. Short-lived
grants travel to the target over SSH stdin, not process arguments or logs.

Check the target account before enrollment:

```shell
ssh bos14-ctrl 'systemctl --user show-environment >/dev/null && loginctl show-user "$(id -un)" -p Linger --value'
```

If this prints `no`, enable lingering for that account:

```shell
ssh bos14-ctrl 'loginctl enable-linger "$(id -un)"'
```

If local policy denies this operation, ask the host administrator to enable it.
Enrollment checks these prerequisites before creating an identity or requesting
a grant.

Use `--lakeshore-id` when your namespace has multiple control-plane connections.
The command prints a request ID before enrollment. After an interrupted request,
repeat the same inputs with `--request-id <id>`. If the API returns `retryAt`, wait
until that time before starting a new request. JSON errors retain the request ID
and available operation/retry fields.
`--wait-seconds` bounds readiness polling (default 60). A timeout reports
`pending` and exits nonzero; it does not claim enrollment succeeded. Repeat
execution preserves the target identity and reuses the user service. Runner/job
readiness is a separate check from a connected nymph.

Direct enrollment on the target host is planned but has no implemented command
syntax yet. For the broader workflow, see the upcoming
[host enrollment guide](https://docs.dreamlake.ai/hosts/enroll) in the main
DreamLake docs (not yet deployed). This page is the CLI input reference.

```shell
dreamlake hosts enroll -p fortyfive/bos14 -n bos14-ctrl --ssh bos14-ctrl
dreamlake hosts status fortyfive/bos14/bos14-ctrl --json
dreamlake hosts status 'fortyfive/bos14/*' --json
```

For configuration-only validation:

```shell
dreamlake hosts enroll -p fortyfive/bos14 -n bos14-ctrl \
  --ssh bos14-ctrl --dry-run

# Equivalent full name, with structured output:
dreamlake hosts enroll -n fortyfive/bos14/bos14-ctrl \
  --ssh '-i "/Users/ge/My Keys/key" \
    -J ge@bastion.example.com \
    -p 2222 geyang@bos14-ctrl.internal' --dry-run --json
```

Names have exactly three components: namespace/group/host. A matching explicit
prefix and embedded prefix are accepted; conflicting prefixes and wildcards are
rejected. Each component is 1–64 letters, digits, underscores or hyphens and starts
with a letter or digit. A namespace in a name does not grant access to it.
The control-plane namespace comes from the selected connection and can differ
from the DreamLake namespace.

## JSON input

```shell
dreamlake hosts enroll --config ./bos14-host.json --dry-run --json
```

```json
{
  "name": "fortyfive/bos14/bos14-ctrl",
  "ssh": {
    "host": "bos14-ctrl.internal",
    "user": "geyang",
    "port": 2222,
    "identityFile": "/Users/ge/.ssh/bos14_ed25519",
    "jumpHost": "ge@bastion.example.com",
    "options": { "ServerAliveInterval": "30" }
  }
}
```

The file may also contain `prefix`. Explicit CLI name/prefix fields override
those file fields, then prefix conflicts are checked. `--ssh` replaces the
entire file SSH object. Unknown fields are rejected. Identity files are local
paths, never embedded key contents. Use either `ssh.user` or `user@host`, not both.
`--json` controls output only.

SSH argument text is tokenized without shell expansion or evaluation; multiline
whitespace, line continuations, and quoted paths are supported. Do not include
`ssh` itself or a remote command. The initial supported flags are `-i`, `-J`,
`-p`, `-o`, `-l`, and `-F`, each followed by a separate value. Supported `-o`
settings are BatchMode, ServerAliveInterval, ServerAliveCountMax, ConnectTimeout,
IdentitiesOnly, StrictHostKeyChecking, and UserKnownHostsFile. Use an existing
SSH config alias for more advanced transport settings. Preview validates syntax,
not whether an identity file exists, an SSH alias resolves, or SSH authentication works.
Passwords are not accepted in arguments or JSON. Future interactive enrollment
should let OpenSSH prompt for target/jump-host passwords locally; the preview
does not attempt authentication and does not require agent forwarding.

Group status accepts `--page` (1–100000) and `--page-size` (1–100). Exact host
lookup starts at the first page and follows matching pages automatically.

Host status is read from the backend; enrollment succeeds only when the returned
enrollment ID reports online. Infrastructure provisioning and workload execution
remain separate from this operation.

## Optional cross-device SSH credential storage (preview only)

Enrollment initiated from your laptop uses your local SSH access. Normal
operation through nymph does not require saved SSH credentials. To request
future per-host vault access from your other devices, including mobile, opt in:

```shell
dreamlake hosts enroll -n fortyfive/bos14/bos14-ctrl \
  --ssh '-i /Users/ge/.ssh/bos14_ed25519 bos14-ctrl' \
  --save-credentials --dry-run --json
```

Saving credentials does not authorize DreamLake backend services to initiate
SSH connections. Any backend SSH delegation is a separate permission to design;
this flag does not grant it. The vault access, encryption, recovery, and
revocation mechanisms remain to be implemented.

This flag currently records **intent only**: output includes
`credentialSaveRequested: true`, `credentialsSaved: false`, and a `warnings`
array. Without the flag, credential saving is not requested. Human-readable
warnings go to stderr. The preview does not read identity files, save, or upload
credentials. Real enrollment with `--save-credentials` remains unsupported and
exits nonzero before contacting the target; omit the flag for normal enrollment. Do not put private keys or passwords into configuration JSON. The flag
is a CLI-only opt-in; it is not accepted as a configuration JSON field.

## Cross-component integration check

The repository includes a repeatable check against an isolated local Hosts API,
control plane, persistent databases, and a compiled nymph binary:

```shell
pnpm build
node scripts/test-hosts-integration.mjs \
  /path/to/protected-test-environment.json /path/to/nymph
```

The protected JSON supplies `remote`, `namespace`, `token`, `outsiderToken`, and
`lakeshoreId`; use test-only credentials and a loopback server. The test launches
the actual CLI and nymph subprocesses, verifies online status, stable explicit
request replay, consumed-token and token-absent reconnect, and forbidden access
before target mutation. It uses local SSH/service adapters, so this is not a
live-network SSH or actual-systemd test. Real target access and runner execution
need their own acceptance checks. Test processes are stopped and temporary
identity files removed afterward.
