Machine classes: server-class vs host-class as bootstrap's organizing axis #25

Closed
opened 2026-07-17 16:33:11 +00:00 by dan-claude-bot · 1 comment
dan-claude-bot commented 2026-07-17 16:33:11 +00:00 (Migrated from github.com)

Context

Working through the sudo/root "dance" surfaced that the fleet has two coherent identity models, not one inconsistent one — and that the distinction explains every privilege question we've hit. This issue makes that distinction a first-class rig concept: the machine class, the axis the bootstrap roles were implicitly organized around all along.

The model

server-class host-class
Roles control-plane, workload, runner staging, dev (dev added by this issue)
Humans none operators (defined via rig users, companion issue below)
Identity root is the management plane (control plane SSHes as root; operators enter as root) named users; root direct only for bring-up
sudo absent — nothing to elevate from present, installed by rig users, scoped by role
Typical tailnet tag tag:server (workload/control-plane), tag:ci (runner) tag:local — never tag:server: hosts are not control-plane-managed, their guests are
Runs the fleet's services / CI the VM substrate (Incus + box) whose guests are server-class

Setting up multiple users in the control plane or on a prod box makes no sense — only admins belong there and root already is that; adding users/sudo/profiles would be surface without payoff. Hosts are the opposite: humans live there, mint boxes, and need scoped, non-colliding access.

The recursive part that makes it clean: a staging box (guest VM on a staging host) is itself server-class — root-only, no human users, managed by the control plane over the tailnet. The staging host may in practice run a single VM, but the principle is unchanged: host-class shell, server-class guests. (Consequence tracked in heavy-duty/box#69's follow-up: the staging template should be root-only — drop the ops user — with the small bin/box special-case for a root template user.)

What changes

  1. Encode class in bootstrap (commands/bootstrap.sh): a role→class map next to the role case. Class drives the per-class behavior that today is ad-hoc per role: the tag:server post-join refusal (host-class-wide, not staging-specific), the /dev/kvm advisory (host-class), and the closing next-steps log (host-class points at box install + rig users; server-class points at its existing runbooks).
  2. Add the dev role — the host-class twin of staging, per the precedent that near-identical roles stay distinct for name honesty (control-plane vs workload). Same plumbing; hostname default dev; same host-class guards.
  3. Persist the role/class marker: bootstrap writes /etc/rig/role (e.g. role=staging class=host), convergent like the hostname. This is rig's first deliberate state on a box — justified because the no-state posture is about credentials, and a role marker is not a secret; precedent exists (.rig-labels on runners). Consumers: rig users' class gate (refuse on server-class), rig <cmd> sanity warnings later (e.g. coolify install on a non-control-plane box). If we reject the marker, the fallback is advisory-only gating — decide in the plan.
  4. README: the identity model — the class table above plus the per-machine identity table (who installs what, who runs as what, elevation path), replacing the current flat role list. This is the document that ends the sudo/root confusion for the next person.

Non-goals

  • No behavior change for server-class roles — they stay humanless and root-only; that is the point.
  • No rig users implementation (companion issue).
  • No box changes (root-only staging template, global install, project awareness — tracked in heavy-duty/box).
  • No new tailnet tags; tag conventions are documented, not invented.

Open questions (settle in the plan doc)

  • Marker file: /etc/rig/role as proposed, or advisory-only gating with no state?
  • Should host-class bootstrap install box globally (needs box's global-install issue to land first), or keep the pointer? Leaning pointer until box#global-install ships, then revisit.

Acceptance

  • rig bootstrap dev exists and behaves as host-class (tag:server refusal post-join, kvm advisory, next-steps log); test/cli.sh covers it exactly as staging is covered.
  • The role→class map is the single place a role's class is declared; no per-role copies of class behavior.
  • Bootstrap on any role writes the marker (if adopted) and a second run converges it silently.
  • README presents the class model and identity table; shellcheck + bash test/cli.sh green.

Companion users issue: https://github.com/heavy-duty/rig/issues/24
Prior related: heavy-duty/rig#22 (staging role, PR #23), heavy-duty/box#68 (staging template, PR #69)

## Context Working through the sudo/root "dance" surfaced that the fleet has **two coherent identity models, not one inconsistent one** — and that the distinction explains every privilege question we've hit. This issue makes that distinction a first-class rig concept: the **machine class**, the axis the bootstrap roles were implicitly organized around all along. ## The model | | **server-class** | **host-class** | |---|---|---| | Roles | `control-plane`, `workload`, `runner` | `staging`, `dev` (dev added by this issue) | | Humans | none | operators (defined via `rig users`, companion issue below) | | Identity | root **is** the management plane (control plane SSHes as root; operators enter as root) | named users; root direct only for bring-up | | sudo | absent — nothing to elevate from | present, installed by `rig users`, scoped by role | | Typical tailnet tag | `tag:server` (workload/control-plane), `tag:ci` (runner) | `tag:local` — never `tag:server`: hosts are not control-plane-managed, their guests are | | Runs | the fleet's services / CI | the VM substrate (Incus + box) whose **guests** are server-class | Setting up multiple users in the control plane or on a prod box makes no sense — only admins belong there and root already is that; adding users/sudo/profiles would be surface without payoff. Hosts are the opposite: humans live there, mint boxes, and need scoped, non-colliding access. **The recursive part that makes it clean:** a staging box (guest VM on a staging host) is itself **server-class** — root-only, no human users, managed by the control plane over the tailnet. The staging host may in practice run a single VM, but the principle is unchanged: host-class shell, server-class guests. (Consequence tracked in heavy-duty/box#69's follow-up: the staging template should be root-only — drop the `ops` user — with the small `bin/box` special-case for a root template user.) ## What changes 1. **Encode class in bootstrap** (`commands/bootstrap.sh`): a role→class map next to the role case. Class drives the per-class behavior that today is ad-hoc per role: the `tag:server` post-join refusal (host-class-wide, not staging-specific), the `/dev/kvm` advisory (host-class), and the closing next-steps log (host-class points at box install + `rig users`; server-class points at its existing runbooks). 2. **Add the `dev` role** — the host-class twin of `staging`, per the precedent that near-identical roles stay distinct for name honesty (`control-plane` vs `workload`). Same plumbing; hostname default `dev`; same host-class guards. 3. **Persist the role/class marker**: bootstrap writes `/etc/rig/role` (e.g. `role=staging class=host`), convergent like the hostname. This is rig's first deliberate state on a box — justified because the no-state posture is about *credentials*, and a role marker is not a secret; precedent exists (`.rig-labels` on runners). Consumers: `rig users`' class gate (refuse on server-class), `rig <cmd>` sanity warnings later (e.g. `coolify install` on a non-control-plane box). If we reject the marker, the fallback is advisory-only gating — decide in the plan. 4. **README: the identity model** — the class table above plus the per-machine identity table (who installs what, who runs as what, elevation path), replacing the current flat role list. This is the document that ends the sudo/root confusion for the next person. ## Non-goals - No behavior change for server-class roles — they stay humanless and root-only; that is the point. - No `rig users` implementation (companion issue). - No box changes (root-only staging template, global install, project awareness — tracked in heavy-duty/box). - No new tailnet tags; tag conventions are documented, not invented. ## Open questions (settle in the plan doc) - Marker file: `/etc/rig/role` as proposed, or advisory-only gating with no state? - Should host-class bootstrap *install* box globally (needs box's global-install issue to land first), or keep the pointer? Leaning pointer until box#global-install ships, then revisit. ## Acceptance - `rig bootstrap dev` exists and behaves as host-class (tag:server refusal post-join, kvm advisory, next-steps log); `test/cli.sh` covers it exactly as `staging` is covered. - The role→class map is the single place a role's class is declared; no per-role copies of class behavior. - Bootstrap on any role writes the marker (if adopted) and a second run converges it silently. - README presents the class model and identity table; shellcheck + `bash test/cli.sh` green. --- Companion users issue: https://github.com/heavy-duty/rig/issues/24 Prior related: heavy-duty/rig#22 (staging role, PR #23), heavy-duty/box#68 (staging template, PR #69)
dan-claude-bot commented 2026-07-17 18:04:38 +00:00 (Migrated from github.com)

Superseded by #26. Working staging and workstations through the class binary showed it bundled three independent questions (who lives there / does it host VMs / how it joins the tailnet) that only correlated across the first four roles — the staging host is unattended (class=server) despite hosting VMs, and a workstation joins user-owned via interactive login, which the authkey model refuses by design. #26 replaces the binary with orthogonal traits and keeps roles as presets over them.

Superseded by #26. Working staging and workstations through the class binary showed it bundled three independent questions (who lives there / does it host VMs / how it joins the tailnet) that only correlated across the first four roles — the staging host is unattended (class=server) despite hosting VMs, and a workstation joins user-owned via interactive login, which the authkey model refuses by design. #26 replaces the binary with orthogonal traits and keeps roles as presets over them.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/rig#25
No description provided.