Runner Connection Bootstrap
homeboy runner connect <runner-id> uses the first-class runner registry added
for issue #2526.
Registry Contract
Runner configs are JSON files at ~/.config/homeboy/runners/<id>.json with the
initial #2526 shape:
{
"kind": "ssh",
"server_id": "lab-box",
"workspace_root": "/srv/homeboy-lab",
"homeboy_path": "homeboy",
"daemon": true
}Only kind: "ssh", server_id, and optional homeboy_path are used by the
Wave 1 connection commands. Registry CRUD owns creation, validation, and future
execution metadata such as workspace_root, concurrency_limit, env, and
resources.
Connection Shape
The remote daemon is started with homeboy daemon start --addr 127.0.0.1:0 and
the reported address is rejected unless it is loopback. The local client reaches
the daemon through an SSH -L 127.0.0.1:<local>:127.0.0.1:<remote> tunnel.
Session metadata is stored at ~/.config/homeboy/runner-sessions/<id>.json so
status and disconnect can inspect or close the local tunnel later.
Before a direct-SSH connect is reported as successful, Homeboy captures the
local tunnel process start identity and observes it through a bounded
post-establishment window while the exact remote lease/PID continues answering
/health. A failed observation cleans only that exact owned child. Connect
failure evidence may add durability_stage; existing health attempt fields are
preserved. health_attempt_count and health_attempts contain failed initial
health probes and failed durability observations, not successful observations.
Rolling Generations
The runner-layer rolling-generation primitive models daemon replacement as generations. A validated candidate starts on its own endpoint before it becomes the admission owner. Existing jobs remain owned by the draining generation, including their logs, cancellation, artifacts, and reconciliation records. A draining generation retires only after its authoritative active-job count reaches zero.
Its serializable status names the admission owner and each generation’s endpoint,
active-job count, and admitting or draining state. A failed candidate startup
is removed without changing the prior owner; repeating a refresh for the active
generation is idempotent. Older single-generation records remain valid as one
admitting generation.
Runner-Owned Service
A runner can own its daemon as a systemd user unit (#13881 step 3). The controller then never starts, replaces, rotates, or retires the remote daemon.
homeboy runner service install <runner-id> requires an idle runner. It:
- Writes
~/.config/systemd/user/homeboy-runner-<id>-<controller>.service. The unit runshomeboy daemon serve --addr 127.0.0.1:0from the controller’s stable link~/.local/share/homeboy/runner-service/<id>/controllers/<controller>/homeboy, indaemon-generations/<id>/controllers/<controller>/primary, withRestart=always. The controller scope is a readable sanitized prefix plus the full SHA-256 of the raw controller ID, shared by service names, binary slots, and daemon-generation paths so sanitized IDs cannot alias. - Stops the idle controller-started daemon so the unit takes the daemon owner lock, and stops idle generation daemons left by earlier rotation.
- Waits for the unit’s lease, then marks the runner
service_managed.
For a service-managed runner:
- Connect waits for the service’s fresh lease, opens the tunnel, and writes the session. It takes no runtime promotion lease, so attaching never waits behind a running Cook’s generation pin.
- Disconnect closes the tunnel and removes the session. The daemon and its jobs keep running.
- Refresh never rotates generations. It builds the new binary, drains through the active-job guard, repoints the unit’s link, and restarts the unit. The daemon’s own startup recovers anything a previous owner left behind.
The service needs systemd user lingering on the runner host
(loginctl enable-linger) so the unit keeps running without a login session.
Older releases used the unsuffixed homeboy-runner-<id>.service, shared binary
link, and daemon-generations/<id>/primary; the intermediate controller-scoped
scheme also used sanitized IDs without a hash. New installs never overwrite,
stop, or adopt either legacy unit name/path. Such a unit may continue owning jobs
while controllers move to hash-scoped units. Retire a legacy unit only after an
operator verifies its exact unit and lease and confirms that it has no active
jobs. Then stop and disable that specific unit; remove its unit file, binary
link, or state directory only when no remaining controller depends on them.
Do not use the new controller-scoped service commands to perform legacy cleanup.
Unreachable recorded host
Connect dials the configured server host. It does not discover a replacement
address. SSH exit 255 with Host is down, or the same resolution/reachability
failure, is transport_unreachable with that recorded host and the next check.
That is not a missing Homeboy install.
A numeric DHCP address is not stable. Set a stable hostname, or a DHCP reservation, on the existing server, then reconnect:
homeboy server set <server-id> --json '{"host":"<hostname>"}'SSH host key verification stays on. Do not accept a new key.