From 88e59b6ec8b378dbb379bedc7dc3f4a4df17a3a3 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl <244098813+codex-bot-andresmgsl@users.noreply.github.com> Date: Sat, 25 Jul 2026 11:00:21 +0000 Subject: [PATCH] test: cover machine template registry --- README.md | 28 +++++++++++++++++ changelog.d/152.md | 1 + commands/bootstrap.sh | 3 +- commands/template-lint.sh | 17 +++++----- test/cli.sh | 65 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 changelog.d/152.md diff --git a/README.md b/README.md index b13d346..17e2732 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,34 @@ unattended VM-host appliance) — and `workstation` is the machine at the keyboa end of all the SSH connections: `root-door=closed`, `join=login`, entering the tailnet as *your* device rather than the fleet's. +### Machine-role templates + +Machine presets can also live in the +[heavy-duty/rig-templates](https://github.com/heavy-duty/rig-templates) +registry. A `*-server` directory is a fleet-machine definition; the exact +name `workstation` is the deliberate suffix-less exception. Its +`template.env` contains exactly the three traits bootstrap's built-in table +uses: + +```dotenv +ROOT_DOOR="open" # open|closed +HOST="no" # yes|no +JOIN="authkey" # authkey|login +``` + +An `install.sh` is optional. When present, bootstrap runs it as root, +non-interactively, from the definition directory with `RIG_ROLE` set, after +the tailnet join, host setup, role marker prerequisites, and operator +convergence. A nonzero exit fails bootstrap and names the role and registry +source. The definition owns idempotence, just as bootstrap does. + +Built-in roles and `custom` take precedence over registry names. Any other +non-tenant role is looked up in the resolved registry; the same three source +knobs below apply, including `RIG_TEMPLATES_DIR` for an offline local +definition. Pin reviewed registry content into rig's tree before using it on +fleet machines: an optional machine `install.sh` executes as root on metal, +and an override is the operator explicitly choosing a different trust root. + ### `rig bootstrap -box` — the box tenants Run as root, **inside** a [box](https://github.com/heavy-duty/box)-minted diff --git a/changelog.d/152.md b/changelog.d/152.md new file mode 100644 index 0000000..7238dcc --- /dev/null +++ b/changelog.d/152.md @@ -0,0 +1 @@ +Machine-role templates can declare bootstrap traits and an optional final root install hook (#152) diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index 2b040cb..bf19ba6 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -144,7 +144,8 @@ case "$ROLE" in templates_resolve || exit 2 trap '[ -n "$TEMPLATES_TMP" ] && rm -rf "$TEMPLATES_TMP"' EXIT MACHINE_TEMPLATE_DIR="$REGISTRY_DIR/$ROLE" - if [ "$(template_family "$ROLE" 2>/dev/null || true)" != "machine" ] \ + if { [ "$ROLE" != "workstation" ] && [[ ! "$ROLE" =~ ^[a-z][a-z0-9-]*-server$ ]]; } \ + || [ "$(template_family "$ROLE" 2>/dev/null || true)" != "machine" ] \ || [ ! -f "$MACHINE_TEMPLATE_DIR/template.env" ]; then MACHINE_ROLES="$(templates_machine_roles "$REGISTRY_DIR" | paste -sd'|' -)" [ -n "$MACHINE_ROLES" ] || MACHINE_ROLES="none" diff --git a/commands/template-lint.sh b/commands/template-lint.sh index 3350130..5f570df 100755 --- a/commands/template-lint.sh +++ b/commands/template-lint.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# rig template-lint ... — is this a valid tenant-role definition? +# rig template-lint ... — is this a valid role definition? # # rig defines what a valid template is (the schema lives in # lib/templates.sh, beside the mint-time parser that enforces it); the @@ -23,12 +23,15 @@ usage() { cat <<'EOF' usage: rig template-lint ... -Validate tenant-role definitions (the heavy-duty/rig-templates shape): -each must carry a family-suffixed name (rig#76), a template.env -that parses against the allowlist (KEY="value" only — the file is data, -never sourced), an install.sh with a shebang, and a non-blank creds.md. -Every refusal names the failing key or file. Exits non-zero if any -definition fails; nothing is written. +Validate role definitions (the heavy-duty/rig-templates shape). + +Tenant roles use a *-box directory, tenant template.env schema, a shebang +install.sh, and non-blank creds.md. Machine roles use a *-server directory +(or exact name workstation), the ROOT_DOOR/HOST/JOIN schema, no creds.md, +and an optional install.sh which must be non-empty and carry a shebang. +template.env is parsed as KEY="value" data and never sourced. Every refusal +names the failing key or file. Exits non-zero if any definition fails; +nothing is written. EOF } diff --git a/test/cli.sh b/test/cli.sh index 450f064..e4955ae 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -613,6 +613,25 @@ mkdir -p "$TPL_FIX/badnode-box" printf 'USER="x"\nCONTEXT_PATH=".x/A.md"\nCLI_NAME="x"\nPATH_LINE="p"\nNEEDS_NODE="maybe"\n' > "$TPL_FIX/badnode-box/template.env" mkdir -p "$TPL_FIX/badapt-box" printf 'USER="x"\nCONTEXT_PATH=".x/A.md"\nCLI_NAME="x"\nPATH_LINE="p"\nAPT_EXTRAS="zsh -o"\n' > "$TPL_FIX/badapt-box/template.env" +mkdir -p "$TPL_FIX/scratch-server" +printf 'ROOT_DOOR="closed"\nHOST="no"\nJOIN="login"\n' > "$TPL_FIX/scratch-server/template.env" +mkdir -p "$TPL_FIX/workstation" +printf 'ROOT_DOOR="closed"\nHOST="yes"\nJOIN="login"\n' > "$TPL_FIX/workstation/template.env" +mkdir -p "$TPL_FIX/hooked-server" +printf 'ROOT_DOOR="open"\nHOST="no"\nJOIN="authkey"\n' > "$TPL_FIX/hooked-server/template.env" +printf '#!/usr/bin/env bash\nexit 1\n' > "$TPL_FIX/hooked-server/install.sh" +mkdir -p "$TPL_FIX/baddoor-server" +printf 'ROOT_DOOR="ajar"\nHOST="no"\nJOIN="authkey"\n' > "$TPL_FIX/baddoor-server/template.env" +mkdir -p "$TPL_FIX/tenantkeys-server" +cp "$TPL_FIX/scratch-box/template.env" "$TPL_FIX/tenantkeys-server/template.env" +mkdir -p "$TPL_FIX/machinekeys-box" +cp "$TPL_FIX/scratch-server/template.env" "$TPL_FIX/machinekeys-box/template.env" +mkdir -p "$TPL_FIX/creds-server" +cp "$TPL_FIX/scratch-server/template.env" "$TPL_FIX/creds-server/template.env" +printf 'not used\n' > "$TPL_FIX/creds-server/creds.md" +mkdir -p "$TPL_FIX/noshebang-server" +cp "$TPL_FIX/scratch-server/template.env" "$TPL_FIX/noshebang-server/template.env" +printf 'exit 0\n' > "$TPL_FIX/noshebang-server/install.sh" # THE HARD CUT, tenant half (#76). The pre-rename names are gone and must fail # as UNKNOWN — asserted per name, because an alias left in for one tenant is the @@ -653,6 +672,28 @@ check "bootstrap: tenant roles dispatch through bootstrap.sh" 0 "Box TENANT role check "bootstrap: an unheard-of '-box' role still dispatches (zero code changes)" 0 "Box TENANT roles" \ "$ROOT/commands/bootstrap.sh" scratch-box --help +# Machine roles use the same resolved registry but remain table-compatible: +# loading happens before flag parsing, so an explicit flag overrides the +# definition exactly as it overrides a built-in row. +check "machine template: traits load from the local registry" 2 "join=login" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" TS_AUTHKEY=x \ + "$ROOT/commands/bootstrap.sh" scratch-server --no-users +if [ "$(id -u)" -ne 0 ]; then + check "machine template: a flag overrides the loaded trait" 1 "must run as root" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" TS_AUTHKEY=x \ + "$ROOT/commands/bootstrap.sh" scratch-server --no-users --join authkey +fi +check "machine template: invalid ROOT_DOOR is refused by key" 2 "ROOT_DOOR" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" \ + "$ROOT/commands/bootstrap.sh" baddoor-server --no-users +check "machine template: unknown role lists machine definitions" 2 "scratch-server" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" "$ROOT/commands/bootstrap.sh" absent-server +check "machine template: unknown role names the resolved source" 2 "RIG_TEMPLATES_DIR" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" "$ROOT/commands/bootstrap.sh" absent-server +check "machine template: a registry role cannot shadow a built-in" 2 "unset TS_AUTHKEY" \ + env RIG_TEMPLATES_DIR="$TPL_FIX" TS_AUTHKEY=x \ + "$ROOT/commands/bootstrap.sh" workstation --no-users + # The tenant marker guard (#83), against marker FIXTURES (never the harness # machine's real /etc/rig/role): converging a tenant onto a machine-role box or a # VM host (host=yes) refuses for every tenant — and names the staging PAIR, @@ -844,6 +885,30 @@ cp "$TPL_FIX/scratch-box/template.env" "$TPL_FIX/scratch-box/creds.md" "$TPL_FIX printf 'exit 0\n' > "$TPL_FIX/noshebang-box/install.sh" check "template-lint: an install.sh without a shebang is refused" 1 "no shebang" \ "$ROOT/commands/template-lint.sh" "$TPL_FIX/noshebang-box" +check "template-lint: a traits-only machine definition passes" 0 "OK: " \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/scratch-server" +check "template-lint: workstation is the machine-family carve-out" 0 "OK: " \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/workstation" +check "template-lint: machine roles refuse tenant keys" 1 "unknown key: USER" \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/tenantkeys-server" +check "template-lint: tenant roles refuse machine keys" 1 "unknown key: ROOT_DOOR" \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/machinekeys-box" +check "template-lint: machine roles refuse creds.md" 1 "creds.md is not allowed" \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/creds-server" +check "template-lint: machine install.sh requires a shebang" 1 "no shebang" \ + "$ROOT/commands/template-lint.sh" "$TPL_FIX/noshebang-server" +# The install is deliberately after the users phase and its wrapper names both +# role and source. Dynamic execution belongs to the root integration path; the +# non-root offline harness pins the safety ordering and failure contract. +machine_hook_at="$(grep -n 'running install hook for' "$ROOT/commands/bootstrap.sh" | head -n1 | cut -d: -f1)" +check "machine template: install hook is bootstrap's last convergence phase" 0 "" \ + test "${users_apply_at:-999999}" -lt "${machine_hook_at:-0}" +# shellcheck disable=SC2016 +check "machine template: install failure names role and source" 0 "" \ + grep -qF 'install hook failed for role $ROLE from $(templates_source_desc)' "$ROOT/commands/bootstrap.sh" +# shellcheck disable=SC2016 +check "machine template: install runs from its definition with RIG_ROLE" 0 "" \ + grep -qF 'cd "$MACHINE_TEMPLATE_DIR" && RIG_ROLE="$ROLE" ./install.sh' "$ROOT/commands/bootstrap.sh" rm -rf "$TPL_FIX" "$TPL_WORK" # Creds-free BY CONSTRUCTION, provable by absence (box#69's grep-refusal