forked from heavy-duty/rig
Merge pull request #156 from codex-bot-andresmgsl/build/152-machine-role-template
feat: add registry-backed machine roles
This commit is contained in:
commit
2af3b24fd8
6 changed files with 234 additions and 20 deletions
28
README.md
28
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
|
end of all the SSH connections: `root-door=closed`, `join=login`, entering the
|
||||||
tailnet as *your* device rather than the fleet's.
|
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 <role>-box` — the box tenants
|
### `rig bootstrap <role>-box` — the box tenants
|
||||||
|
|
||||||
Run as root, **inside** a [box](https://github.com/heavy-duty/box)-minted
|
Run as root, **inside** a [box](https://github.com/heavy-duty/box)-minted
|
||||||
|
|
|
||||||
3
changelog.d/152.md
Normal file
3
changelog.d/152.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Machine-role templates can declare bootstrap traits and an optional final root install hook (#152)
|
||||||
|
|
@ -12,6 +12,8 @@ HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
||||||
. "$HERE/lib/users-config.sh" # parse_users_file — the --users PRE-FLIGHT only
|
. "$HERE/lib/users-config.sh" # parse_users_file — the --users PRE-FLIGHT only
|
||||||
# shellcheck source=SCRIPTDIR/lib/manifest.sh
|
# shellcheck source=SCRIPTDIR/lib/manifest.sh
|
||||||
. "$HERE/lib/manifest.sh" # manifest_stamp — provenance, written beside the marker
|
. "$HERE/lib/manifest.sh" # manifest_stamp — provenance, written beside the marker
|
||||||
|
# shellcheck source=SCRIPTDIR/lib/templates.sh
|
||||||
|
. "$HERE/lib/templates.sh" # registry-backed machine-role definitions
|
||||||
# The users lib is sourced for validation, never for convergence: `users apply`
|
# The users lib is sourced for validation, never for convergence: `users apply`
|
||||||
# stays the single owner of what a users file DOES to a box (#51). Bootstrap
|
# stays the single owner of what a users file DOES to a box (#51). Bootstrap
|
||||||
# borrows the parser so a typo'd users file is caught in the same breath as a
|
# borrows the parser so a typo'd users file is caught in the same breath as a
|
||||||
|
|
@ -119,6 +121,7 @@ EOF
|
||||||
|
|
||||||
# --- args (validated before the root check, so errors are testable) ---------
|
# --- args (validated before the root check, so errors are testable) ---------
|
||||||
ROLE="${1:-}"
|
ROLE="${1:-}"
|
||||||
|
MACHINE_TEMPLATE_DIR=""
|
||||||
case "$ROLE" in
|
case "$ROLE" in
|
||||||
--undo)
|
--undo)
|
||||||
shift
|
shift
|
||||||
|
|
@ -136,7 +139,20 @@ case "$ROLE" in
|
||||||
exec "$HERE/bootstrap-tenant.sh" "$@" ;;
|
exec "$HERE/bootstrap-tenant.sh" "$@" ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
"") usage >&2; die "role required (control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a '-box' tenant role from the template registry, e.g. claude-box)" 2 ;;
|
"") usage >&2; die "role required (control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a '-box' tenant role from the template registry, e.g. claude-box)" 2 ;;
|
||||||
*) die "unknown role: $ROLE (want control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a '-box' tenant role from the template registry, e.g. claude-box)" 2 ;;
|
*)
|
||||||
|
shift
|
||||||
|
templates_resolve || exit 2
|
||||||
|
trap '[ -n "$TEMPLATES_TMP" ] && rm -rf "$TEMPLATES_TMP"' EXIT
|
||||||
|
MACHINE_TEMPLATE_DIR="$REGISTRY_DIR/$ROLE"
|
||||||
|
if [[ ! "$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"
|
||||||
|
die "unknown role: $ROLE (want control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom; machine roles from $(templates_source_desc): $MACHINE_ROLES; or a '-box' tenant role)" 2
|
||||||
|
fi
|
||||||
|
machine_template_parse_env "$MACHINE_TEMPLATE_DIR/template.env" \
|
||||||
|
|| die "invalid machine role $ROLE from $(templates_source_desc)" 2 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Role→traits map — the single place a role's shape is declared (issue #26).
|
# Role→traits map — the single place a role's shape is declared (issue #26).
|
||||||
|
|
@ -156,6 +172,7 @@ case "$ROLE" in
|
||||||
dev-server) ROOT_DOOR=closed HOST=yes JOIN=authkey ;;
|
dev-server) ROOT_DOOR=closed HOST=yes JOIN=authkey ;;
|
||||||
workstation) ROOT_DOOR=closed HOST=yes JOIN=login ;;
|
workstation) ROOT_DOOR=closed HOST=yes JOIN=login ;;
|
||||||
custom) ;;
|
custom) ;;
|
||||||
|
*) ROOT_DOOR="$TPL_ROOT_DOOR" HOST="$TPL_HOST" JOIN="$TPL_JOIN" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# custom has no hostname default: a made-up name on a made-up shape helps nobody.
|
# custom has no hostname default: a made-up name on a made-up shape helps nobody.
|
||||||
|
|
@ -784,6 +801,17 @@ if [ -n "$USERS_FILE" ]; then
|
||||||
"$HERE/users-apply.sh" --file "$USERS_FILE"
|
"$HERE/users-apply.sh" --file "$USERS_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# A registry machine's optional install is the final convergence phase: after
|
||||||
|
# join, host setup, the marker prerequisites, and operators. It inherits the
|
||||||
|
# caller environment, adds only the selected role, and runs from its definition
|
||||||
|
# directory. Definitions own idempotence, like bootstrap itself.
|
||||||
|
if [ -n "$MACHINE_TEMPLATE_DIR" ] && [ -e "$MACHINE_TEMPLATE_DIR/install.sh" ]; then
|
||||||
|
log "running install hook for ${ROLE} from $(templates_source_desc)"
|
||||||
|
if ! (cd "$MACHINE_TEMPLATE_DIR" && RIG_ROLE="$ROLE" bash ./install.sh); then
|
||||||
|
die "install hook failed for role $ROLE from $(templates_source_desc)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
log "done — role ${ROLE}, hostname ${TS_HOSTNAME}"
|
log "done — role ${ROLE}, hostname ${TS_HOSTNAME}"
|
||||||
if [ "$ROLE" = "control-plane-server" ]; then
|
if [ "$ROLE" = "control-plane-server" ]; then
|
||||||
log "next: rig coolify install --version <pin>"
|
log "next: rig coolify install --version <pin>"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ RIG_TEMPLATES_PIN=be749f7fd1ff8dd7c2359bbce7fd6abd3f403eb0
|
||||||
# KEY="value" — nothing else. Parsed by regex, never sourced.
|
# KEY="value" — nothing else. Parsed by regex, never sourced.
|
||||||
TEMPLATE_KEYS_REQUIRED=(USER CONTEXT_PATH CLI_NAME PATH_LINE)
|
TEMPLATE_KEYS_REQUIRED=(USER CONTEXT_PATH CLI_NAME PATH_LINE)
|
||||||
TEMPLATE_KEYS_OPTIONAL=(CLI_SRC NEEDS_NODE APT_EXTRAS)
|
TEMPLATE_KEYS_OPTIONAL=(CLI_SRC NEEDS_NODE APT_EXTRAS)
|
||||||
|
MACHINE_KEYS_REQUIRED=(ROOT_DOOR HOST JOIN)
|
||||||
|
|
||||||
# templates_source_desc — where the resolved registry came from, for error
|
# templates_source_desc — where the resolved registry came from, for error
|
||||||
# messages and logs: a misconfigured RIG_TEMPLATES_REPO must be visible in
|
# messages and logs: a misconfigured RIG_TEMPLATES_REPO must be visible in
|
||||||
|
|
@ -128,6 +129,26 @@ templates_roles() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# template_family <role> — directory names are the registry's family tag.
|
||||||
|
# workstation is the one intentional suffix-less machine role (#152 / epic D5).
|
||||||
|
template_family() {
|
||||||
|
case "$1" in
|
||||||
|
*-box) printf 'tenant\n' ;;
|
||||||
|
*-server|workstation) printf 'machine\n' ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# templates_machine_roles <registry-dir> — only machine definitions, for the
|
||||||
|
# machine bootstrap's unknown-role refusal.
|
||||||
|
templates_machine_roles() {
|
||||||
|
local role
|
||||||
|
while IFS= read -r role; do
|
||||||
|
[ "$(template_family "$role" 2>/dev/null || true)" = "machine" ] || continue
|
||||||
|
printf '%s\n' "$role"
|
||||||
|
done < <(templates_roles "$1")
|
||||||
|
}
|
||||||
|
|
||||||
# template_parse_env <template.env> — parse against the allowlist. Sets
|
# template_parse_env <template.env> — parse against the allowlist. Sets
|
||||||
# TPL_USER, TPL_CONTEXT_PATH, TPL_CLI_NAME, TPL_CLI_SRC, TPL_PATH_LINE,
|
# TPL_USER, TPL_CONTEXT_PATH, TPL_CLI_NAME, TPL_CLI_SRC, TPL_PATH_LINE,
|
||||||
# TPL_NEEDS_NODE (default no), TPL_APT_EXTRAS. Every refusal names the
|
# TPL_NEEDS_NODE (default no), TPL_APT_EXTRAS. Every refusal names the
|
||||||
|
|
@ -206,6 +227,62 @@ template_parse_env() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# machine_template_parse_env <template.env> — the fleet-machine traits schema.
|
||||||
|
# The globals match bootstrap's table columns so a definition becomes a table
|
||||||
|
# row without changing any downstream trait behavior.
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
machine_template_parse_env() {
|
||||||
|
local file="$1" line key val n=0 seen=" " k ok
|
||||||
|
TPL_ROOT_DOOR="" TPL_HOST="" TPL_JOIN=""
|
||||||
|
[ -f "$file" ] || { printf 'template.env missing: %s\n' "$file" >&2; return 1; }
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
n=$((n+1))
|
||||||
|
case "$line" in ''|'#'*) continue ;; esac
|
||||||
|
if [[ ! "$line" =~ ^([A-Z_]+)=\"(.*)\"$ ]]; then
|
||||||
|
printf 'template.env:%d: not KEY="value": %s\n' "$n" "$line" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
key="${BASH_REMATCH[1]}" val="${BASH_REMATCH[2]}"
|
||||||
|
ok=""
|
||||||
|
for k in "${MACHINE_KEYS_REQUIRED[@]}"; do
|
||||||
|
[ "$key" = "$k" ] && ok=1
|
||||||
|
done
|
||||||
|
[ -n "$ok" ] || {
|
||||||
|
printf 'template.env:%d: unknown key: %s (allowed: %s)\n' \
|
||||||
|
"$n" "$key" "${MACHINE_KEYS_REQUIRED[*]}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
case "$seen" in *" $key "*)
|
||||||
|
printf 'template.env:%d: duplicate key: %s\n' "$n" "$key" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
seen="$seen$key "
|
||||||
|
case "$key" in
|
||||||
|
ROOT_DOOR) TPL_ROOT_DOOR="$val" ;;
|
||||||
|
HOST) TPL_HOST="$val" ;;
|
||||||
|
JOIN) TPL_JOIN="$val" ;;
|
||||||
|
esac
|
||||||
|
done < "$file"
|
||||||
|
for k in "${MACHINE_KEYS_REQUIRED[@]}"; do
|
||||||
|
case "$seen" in *" $k "*) ;; *)
|
||||||
|
printf 'template.env: missing required key: %s\n' "$k" >&2
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "$TPL_ROOT_DOOR" in
|
||||||
|
open|closed) ;;
|
||||||
|
*) printf 'template.env: ROOT_DOOR: want open or closed, got: %s\n' "$TPL_ROOT_DOOR" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
case "$TPL_HOST" in
|
||||||
|
yes|no) ;;
|
||||||
|
*) printf 'template.env: HOST: want yes or no, got: %s\n' "$TPL_HOST" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
case "$TPL_JOIN" in
|
||||||
|
authkey|login) ;;
|
||||||
|
*) printf 'template.env: JOIN: want authkey or login, got: %s\n' "$TPL_JOIN" >&2; return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# render_tenant_context <role> <creds.md> — the agent-context file's
|
# render_tenant_context <role> <creds.md> — the agent-context file's
|
||||||
# content, on stdout: the one file every agent reads before touching
|
# content, on stdout: the one file every agent reads before touching
|
||||||
# anything. The skeleton is MECHANISM and lives here once — the box#80 guard
|
# anything. The skeleton is MECHANISM and lives here once — the box#80 guard
|
||||||
|
|
@ -248,19 +325,32 @@ EOF
|
||||||
# protects the registry, the mint-time parse protects a mint served through
|
# protects the registry, the mint-time parse protects a mint served through
|
||||||
# RIG_TEMPLATES_REPO/_DIR that CI never saw.
|
# RIG_TEMPLATES_REPO/_DIR that CI never saw.
|
||||||
template_lint() {
|
template_lint() {
|
||||||
local dir="${1%/}" role
|
local dir="${1%/}" role family
|
||||||
role="$(basename "$dir")"
|
role="$(basename "$dir")"
|
||||||
[ -d "$dir" ] || { printf '%s: not a directory\n' "$dir" >&2; return 1; }
|
[ -d "$dir" ] || { printf '%s: not a directory\n' "$dir" >&2; return 1; }
|
||||||
case "$role" in
|
family="$(template_family "$role" 2>/dev/null || true)"
|
||||||
*-box|*-server) ;;
|
[ -n "$family" ] || {
|
||||||
*) printf '%s: role directories carry a family suffix (-box for box tenants, -server for fleet machines — rig#76)\n' "$role" >&2; return 1 ;;
|
printf '%s: role directories carry a family suffix (-box for box tenants, -server for fleet machines — rig#76; workstation is #152 machine carve-out)\n' "$role" >&2
|
||||||
esac
|
return 1
|
||||||
template_parse_env "$dir/template.env" || return 1
|
}
|
||||||
[ -s "$dir/install.sh" ] \
|
if [ "$family" = "tenant" ]; then
|
||||||
|| { printf '%s: install.sh missing or empty\n' "$role" >&2; return 1; }
|
template_parse_env "$dir/template.env" || return 1
|
||||||
head -n1 "$dir/install.sh" | grep -q '^#!' \
|
[ -s "$dir/install.sh" ] \
|
||||||
|| { printf '%s: install.sh has no shebang\n' "$role" >&2; return 1; }
|
|| { printf '%s: install.sh missing or empty\n' "$role" >&2; return 1; }
|
||||||
grep -q '[^[:space:]]' "$dir/creds.md" 2>/dev/null \
|
head -n1 "$dir/install.sh" | grep -q '^#!' \
|
||||||
|| { printf '%s: creds.md missing or blank (the context renderer splices it in — a blank paragraph would ship a context file with a hole)\n' "$role" >&2; return 1; }
|
|| { printf '%s: install.sh has no shebang\n' "$role" >&2; return 1; }
|
||||||
|
grep -q '[^[:space:]]' "$dir/creds.md" 2>/dev/null \
|
||||||
|
|| { printf '%s: creds.md missing or blank (the context renderer splices it in — a blank paragraph would ship a context file with a hole)\n' "$role" >&2; return 1; }
|
||||||
|
else
|
||||||
|
machine_template_parse_env "$dir/template.env" || return 1
|
||||||
|
[ ! -e "$dir/creds.md" ] \
|
||||||
|
|| { printf '%s: creds.md is not allowed for machine roles (machines render no tenant context)\n' "$role" >&2; return 1; }
|
||||||
|
if [ -e "$dir/install.sh" ]; then
|
||||||
|
[ -s "$dir/install.sh" ] \
|
||||||
|
|| { printf '%s: install.sh is empty\n' "$role" >&2; return 1; }
|
||||||
|
head -n1 "$dir/install.sh" | grep -q '^#!' \
|
||||||
|
|| { printf '%s: install.sh has no shebang\n' "$role" >&2; return 1; }
|
||||||
|
fi
|
||||||
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# rig template-lint <role-dir>... — is this a valid tenant-role definition?
|
# rig template-lint <role-dir>... — is this a valid role definition?
|
||||||
#
|
#
|
||||||
# rig defines what a valid template is (the schema lives in
|
# rig defines what a valid template is (the schema lives in
|
||||||
# lib/templates.sh, beside the mint-time parser that enforces it); the
|
# lib/templates.sh, beside the mint-time parser that enforces it); the
|
||||||
|
|
@ -23,12 +23,15 @@ usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
usage: rig template-lint <role-dir>...
|
usage: rig template-lint <role-dir>...
|
||||||
|
|
||||||
Validate tenant-role definitions (the heavy-duty/rig-templates shape):
|
Validate role definitions (the heavy-duty/rig-templates shape).
|
||||||
each <role-dir> must carry a family-suffixed name (rig#76), a template.env
|
|
||||||
that parses against the allowlist (KEY="value" only — the file is data,
|
Tenant roles use a *-box directory, tenant template.env schema, a shebang
|
||||||
never sourced), an install.sh with a shebang, and a non-blank creds.md.
|
install.sh, and non-blank creds.md. Machine roles use a *-server directory
|
||||||
Every refusal names the failing key or file. Exits non-zero if any
|
(or exact name workstation), the ROOT_DOOR/HOST/JOIN schema, no creds.md,
|
||||||
definition fails; nothing is written.
|
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
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
62
test/cli.sh
62
test/cli.sh
|
|
@ -613,6 +613,22 @@ 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"
|
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"
|
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"
|
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="authkey"\n' > "$TPL_FIX/workstation/template.env"
|
||||||
|
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
|
# 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
|
# as UNKNOWN — asserted per name, because an alias left in for one tenant is the
|
||||||
|
|
@ -653,6 +669,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" \
|
check "bootstrap: an unheard-of '-box' role still dispatches (zero code changes)" 0 "Box TENANT roles" \
|
||||||
"$ROOT/commands/bootstrap.sh" scratch-box --help
|
"$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
|
# 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
|
# 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,
|
# VM host (host=yes) refuses for every tenant — and names the staging PAIR,
|
||||||
|
|
@ -844,6 +882,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"
|
printf 'exit 0\n' > "$TPL_FIX/noshebang-box/install.sh"
|
||||||
check "template-lint: an install.sh without a shebang is refused" 1 "no shebang" \
|
check "template-lint: an install.sh without a shebang is refused" 1 "no shebang" \
|
||||||
"$ROOT/commands/template-lint.sh" "$TPL_FIX/noshebang-box"
|
"$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" bash ./install.sh' "$ROOT/commands/bootstrap.sh"
|
||||||
rm -rf "$TPL_FIX" "$TPL_WORK"
|
rm -rf "$TPL_FIX" "$TPL_WORK"
|
||||||
|
|
||||||
# Creds-free BY CONSTRUCTION, provable by absence (box#69's grep-refusal
|
# Creds-free BY CONSTRUCTION, provable by absence (box#69's grep-refusal
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue