feat!: bootstrap takes the users file #54
5 changed files with 456 additions and 22 deletions
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -26,6 +26,44 @@ on the way to cutting its first release, and this file starts there.
|
||||||
`incus-admin` member is warned, not fatal — `box grant` refuses them today,
|
`incus-admin` member is warned, not fatal — `box grant` refuses them today,
|
||||||
which heavy-duty/box#99 fixes box-side with no rig change needed.
|
which heavy-duty/box#99 fixes box-side with no rig change needed.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **BREAKING: `rig bootstrap` takes the users file, and requires it** (#51) —
|
||||||
|
bootstrap already knew everything else about what a box *is* (class, host,
|
||||||
|
join, hostname) and wrote `/etc/rig/role` to say so; the users file was the
|
||||||
|
last piece of that answer it did not take, so bring-up was two commands and
|
||||||
|
the second was the forgettable one. `--users <path>` now runs the `users
|
||||||
|
apply` convergence as bootstrap's **final phase** — after the traits, after
|
||||||
|
the verified tailnet join, after the role marker (apply *reads* that
|
||||||
|
marker), and after the `host=yes` box install (so box-role users find the
|
||||||
|
`incus` group box's own `setup-host` built). One command, and the box has
|
||||||
|
its people on it. The file is still passed per invocation and **never
|
||||||
|
persisted**; `--users -` is refused, because bootstrap's stdin belongs to
|
||||||
|
the pre-auth key prompt.
|
||||||
|
|
||||||
|
**Migration: every existing `rig bootstrap` invocation must add `--users
|
||||||
|
<path>` or `--no-users`.** Omitting both is now a usage error (exit 2)
|
||||||
|
naming both flags, and passing both is a usage error too. Scripted
|
||||||
|
bring-up that already ran `rig users apply` as a separate step can either
|
||||||
|
fold it in (`--users ./users`, and drop the separate call) or keep the old
|
||||||
|
shape verbatim by adding `--no-users`. Required on `class=server` as well
|
||||||
|
as `class=human`: a server nobody logs into routinely is exactly where
|
||||||
|
shared-root access rots, and per-human accounts keep attribution intact
|
||||||
|
for the times someone does go in — so the complete path is the default
|
||||||
|
path, and skipping it is deliberate rather than an omission that looks
|
||||||
|
identical to forgetting. The box TENANT roles (`claude|codex|grok|
|
||||||
|
staging`) take neither flag: a guest is minted non-interactively by box,
|
||||||
|
never joins the tailnet, and has no SSH door of its own — entry is `box
|
||||||
|
shell`, gated by the host's `incus` grants.
|
||||||
|
|
||||||
|
A bad users file is caught **up front** now (the same parser apply uses,
|
||||||
|
before `apt`, the hostname change, and any spent pre-auth key), and on
|
||||||
|
`host=yes` with `RIG_SKIP_BOX_INSTALL=1` a box-role user with no `incus`
|
||||||
|
group refuses immediately instead of a hundred lines later — the one case
|
||||||
|
where the outcome is already certain. rig still never installs Incus and
|
||||||
|
never calls `box setup-host` on its own account; every other way that step
|
||||||
|
can fail lands in `users apply`'s existing refusal, unchanged.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **A `host=no` box with an `incus` group no longer hands out the bare
|
- **A `host=no` box with an `incus` group no longer hands out the bare
|
||||||
|
|
|
||||||
78
README.md
78
README.md
|
|
@ -98,21 +98,76 @@ second run changes nothing. (The box TENANT roles — `claude`, `codex`,
|
||||||
tenants* below.)
|
tenants* below.)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rig bootstrap control-plane --hostname my-coolify-box
|
rig bootstrap control-plane --hostname my-coolify-box --users ./users
|
||||||
rig bootstrap workload --hostname my-prod-box
|
rig bootstrap workload --hostname my-prod-box --users ./users
|
||||||
rig bootstrap runner --hostname my-ci-box
|
rig bootstrap runner --hostname my-ci-box --users ./users
|
||||||
rig bootstrap dev --hostname my-dev-box
|
rig bootstrap dev --hostname my-dev-box --users ./users
|
||||||
rig bootstrap workstation --hostname my-laptop
|
rig bootstrap workstation --hostname my-laptop --users ./users
|
||||||
rig bootstrap custom --hostname my-vm-host --class server --host yes --join authkey
|
rig bootstrap custom --hostname my-vm-host --class server --host yes --join authkey --users ./users
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `--users <path>` / `--no-users` — **required**, one or the other: the users
|
||||||
|
file this box's operators come from, converged as bootstrap's last phase
|
||||||
|
(see *One command, box ready* below)
|
||||||
- `--hostname <name>` — system + tailnet hostname (default: the role name;
|
- `--hostname <name>` — system + tailnet hostname (default: the role name;
|
||||||
`custom` has no default and requires it)
|
`custom` has no default and requires it)
|
||||||
- `--class <human|server>` — who lives here; decides root SSH's fate after
|
- `--class <human|server>` — who lives here; decides root SSH's fate after
|
||||||
`rig users apply` (see *The identity model* below)
|
the users phase (see *The identity model* below)
|
||||||
- `--host <yes|no>` — does this box host VMs (box/Incus)
|
- `--host <yes|no>` — does this box host VMs (box/Incus)
|
||||||
- `--join <authkey|login>` — how it enters the tailnet
|
- `--join <authkey|login>` — how it enters the tailnet
|
||||||
|
|
||||||
|
#### One command, box ready — `--users` is required
|
||||||
|
|
||||||
|
`rig bootstrap` already knows everything else about what a box *is* — class,
|
||||||
|
host, join, hostname — and writes `/etc/rig/role` to say so. The users file
|
||||||
|
was the last piece of that answer it did not take, so bring-up was two
|
||||||
|
commands and the second one was easy to forget. Now it takes it, and
|
||||||
|
**requires** it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rig bootstrap dev --hostname my-dev-box --users ./users # one command, people included
|
||||||
|
rig bootstrap dev --hostname my-dev-box --no-users # deliberately root-only
|
||||||
|
```
|
||||||
|
|
||||||
|
`--users <path>` runs exactly what `rig users apply --file <path>` runs, as
|
||||||
|
bootstrap's **final phase** — after the traits are set, after the tailnet
|
||||||
|
join is verified, and after `/etc/rig/role` is written, because apply *reads*
|
||||||
|
that marker (`class=` picks its root-SSH note, `host=` decides what a missing
|
||||||
|
`incus` group means). On a `host=yes` box it also lands after the `box`
|
||||||
|
install, so box-role users find the `incus` group box's `setup-host` built.
|
||||||
|
The file is passed per invocation and **never persisted** — bootstrap reads
|
||||||
|
it through apply and keeps nothing; `--users -` is refused, because
|
||||||
|
bootstrap's stdin belongs to the pre-auth key prompt.
|
||||||
|
|
||||||
|
Required on **every** role, `class=server` included. A bootstrapped box with
|
||||||
|
no users converges to a box only root can enter — on `class=human` a
|
||||||
|
half-built machine, and on `class=server` something worse than half-built: a
|
||||||
|
machine nobody logs into routinely is exactly where shared-root access rots,
|
||||||
|
and per-human accounts keep attribution intact for the times someone does go
|
||||||
|
in. So the complete path is the default path, and skipping it is a deliberate
|
||||||
|
`--no-users` rather than an omission that looks identical to forgetting.
|
||||||
|
Omitting both is a usage error naming both flags; passing both is a usage
|
||||||
|
error too — rig will not silently pick a winner.
|
||||||
|
|
||||||
|
A bad users file is caught **up front**, in the same breath as a bad
|
||||||
|
`--class`: bootstrap pre-flights it with the same parser apply uses, before
|
||||||
|
`apt`, before the hostname change, and before a single-use pre-auth key is
|
||||||
|
spent. And on `host=yes` with `RIG_SKIP_BOX_INSTALL=1`, a box-role user with
|
||||||
|
no `incus` group refuses immediately rather than a hundred lines later —
|
||||||
|
that is the one case where the outcome is already certain, since the run has
|
||||||
|
been told it will not install box. rig still **never** installs Incus or runs
|
||||||
|
`box setup-host` on its own account; the box CLI's own installer does that
|
||||||
|
(see the `host` trait), and every other way that step can fail lands in
|
||||||
|
apply's existing refusal at the end.
|
||||||
|
|
||||||
|
`--users` does **not** reach the box TENANT roles (`claude`, `codex`, `grok`,
|
||||||
|
`staging`). A tenant is a box-minted *guest*: box auto-runs its bootstrap at
|
||||||
|
mint, non-interactively, with no file to hand it; the guest never joins the
|
||||||
|
tailnet and has no SSH door of its own — you enter with `box shell`, gated by
|
||||||
|
the **host's** `incus` grants, which the host's own users file already
|
||||||
|
converged. A fleet-wide operator file has nothing to converge in there, and
|
||||||
|
requiring one would break the mint-time path outright.
|
||||||
|
|
||||||
**Roles are presets over three orthogonal traits**, nothing more — every
|
**Roles are presets over three orthogonal traits**, nothing more — every
|
||||||
per-role behavior keys off a trait, so any flag overrides its trait without
|
per-role behavior keys off a trait, so any flag overrides its trait without
|
||||||
needing a new role (`rig bootstrap workstation --host no` for a laptop that
|
needing a new role (`rig bootstrap workstation --host no` for a laptop that
|
||||||
|
|
@ -332,7 +387,8 @@ files, so a PATH export alone is invisible to it.
|
||||||
prompt, in the shipped script). The one creds-holding step a staging guest
|
prompt, in the shipped script). The one creds-holding step a staging guest
|
||||||
eventually needs — the tailnet workload join — stays **operator-run**, exactly
|
eventually needs — the tailnet workload join — stays **operator-run**, exactly
|
||||||
as box#69 designed it: `box shell` → `sudo rig bootstrap workload --hostname
|
as box#69 designed it: `box shell` → `sudo rig bootstrap workload --hostname
|
||||||
<name>` with a single-use tagged pre-auth key. After that join, re-running
|
<name> --users <path>` (or `--no-users` — a guest's door is `box shell`, gated
|
||||||
|
by the host's grants) with a single-use tagged pre-auth key. After that join, re-running
|
||||||
`rig bootstrap staging` still converges docker + hardening and leaves the
|
`rig bootstrap staging` still converges docker + hardening and leaves the
|
||||||
workload marker alone — the machine role is the truer statement of what the
|
workload marker alone — the machine role is the truer statement of what the
|
||||||
box became.
|
box became.
|
||||||
|
|
@ -581,7 +637,7 @@ Runner box only, run after `rig bootstrap runner` (the same two-step rhythm
|
||||||
as `bootstrap control-plane` → `coolify install`):
|
as `bootstrap control-plane` → `coolify install`):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
rig bootstrap runner --hostname my-ci-box
|
rig bootstrap runner --hostname my-ci-box --users ./users
|
||||||
rig runner install --repo acme/widgets
|
rig runner install --repo acme/widgets
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -712,6 +768,10 @@ Converges named operator accounts from a declarative users file — on **every**
|
||||||
class (see *The identity model*). Run as root. Convergent: a second identical
|
class (see *The identity model*). Run as root. Convergent: a second identical
|
||||||
run says "already converged; no changes".
|
run says "already converged; no changes".
|
||||||
|
|
||||||
|
This is also what `rig bootstrap --users <path>` runs as its last phase, so on
|
||||||
|
a fresh box you rarely call it by hand — it is the *re-converge* verb (a key
|
||||||
|
added, an operator revoked, a `--no-users` box growing people later).
|
||||||
|
|
||||||
```
|
```
|
||||||
# user roles ssh public key
|
# user roles ssh public key
|
||||||
dan admin,box ssh-ed25519 AAAA... dan@laptop
|
dan admin,box ssh-ed25519 AAAA... dan@laptop
|
||||||
|
|
|
||||||
8
bin/rig
8
bin/rig
|
|
@ -14,10 +14,16 @@ usage: rig <command> [args]
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
bootstrap <control-plane|workload|runner|dev|workstation|custom>
|
bootstrap <control-plane|workload|runner|dev|workstation|custom>
|
||||||
|
(--users <path> | --no-users)
|
||||||
[--hostname <name>] [--class <human|server>] [--host <yes|no>]
|
[--hostname <name>] [--class <human|server>] [--host <yes|no>]
|
||||||
[--join <authkey|login>]
|
[--join <authkey|login>]
|
||||||
OS plumbing on a pristine Debian box: hardening, unattended-upgrades,
|
OS plumbing on a pristine Debian box: hardening, unattended-upgrades,
|
||||||
tailscale join. Roles are presets over the three traits; any flag
|
tailscale join, then this box's operators. --users is REQUIRED on
|
||||||
|
every role — it converges the users file as bootstrap's last phase
|
||||||
|
(identical to 'rig users apply --file <path>', marker written first)
|
||||||
|
so one command leaves a box with its people on it; --no-users is the
|
||||||
|
deliberate opt-out and leaves root as the only door. The tenant roles
|
||||||
|
below take neither. Roles are presets over the three traits; any flag
|
||||||
overrides its trait, and custom states all of them. Prompts for a
|
overrides its trait, and custom states all of them. Prompts for a
|
||||||
single-use TAGGED tailnet pre-auth key (TS_AUTHKEY env overrides the
|
single-use TAGGED tailnet pre-auth key (TS_AUTHKEY env overrides the
|
||||||
prompt); the key's tags are the tailnet tag, verified after join —
|
prompt); the key's tags are the tailnet tag, verified after join —
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
||||||
. "$HERE/lib/runner-config.sh" # json_field / json_string_array read the netmap
|
. "$HERE/lib/runner-config.sh" # json_field / json_string_array read the netmap
|
||||||
# shellcheck source=SCRIPTDIR/lib/sshd.sh
|
# shellcheck source=SCRIPTDIR/lib/sshd.sh
|
||||||
. "$HERE/lib/sshd.sh" # harden_sshd — shared with the staging tenant
|
. "$HERE/lib/sshd.sh" # harden_sshd — shared with the staging tenant
|
||||||
|
# shellcheck source=SCRIPTDIR/lib/users-config.sh
|
||||||
|
. "$HERE/lib/users-config.sh" # parse_users_file — the --users PRE-FLIGHT only
|
||||||
|
# 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
|
||||||
|
# borrows the parser so a typo'd users file is caught in the same breath as a
|
||||||
|
# bad --class — before apt, before the tailnet join, before a pre-auth key is
|
||||||
|
# spent — instead of at the very end of a run the operator already paid for.
|
||||||
|
|
||||||
log() { printf 'rig-bootstrap: %s\n' "$*"; }
|
log() { printf 'rig-bootstrap: %s\n' "$*"; }
|
||||||
warn() { printf 'rig-bootstrap: WARNING: %s\n' "$*" >&2; }
|
warn() { printf 'rig-bootstrap: WARNING: %s\n' "$*" >&2; }
|
||||||
|
|
@ -16,19 +23,40 @@ die() { printf 'rig-bootstrap: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
usage: rig bootstrap <control-plane|workload|runner|dev|workstation|custom>
|
usage: rig bootstrap <control-plane|workload|runner|dev|workstation|custom>
|
||||||
|
(--users <path> | --no-users)
|
||||||
[--hostname <name>] [--class <human|server>]
|
[--hostname <name>] [--class <human|server>]
|
||||||
[--host <yes|no>] [--join <authkey|login>]
|
[--host <yes|no>] [--join <authkey|login>]
|
||||||
rig bootstrap <claude|codex|grok|staging> [--user <name>]
|
rig bootstrap <claude|codex|grok|staging> [--user <name>]
|
||||||
(the box TENANT roles — see their own --help)
|
(the box TENANT roles — see their own --help; they take
|
||||||
|
no --users, see below)
|
||||||
|
|
||||||
|
--users the users file this box's operators come from — REQUIRED. It is
|
||||||
|
applied as bootstrap's last phase, exactly as `rig users apply
|
||||||
|
--file <path>` would, so one command leaves a box with its
|
||||||
|
people on it. Passed per invocation and never persisted.
|
||||||
|
--no-users the deliberate opt-out — bootstrap converges the OS and the
|
||||||
|
tailnet and leaves the box with root as its only door.
|
||||||
--hostname system + tailnet hostname (default: the role name; custom has
|
--hostname system + tailnet hostname (default: the role name; custom has
|
||||||
no default and requires it)
|
no default and requires it)
|
||||||
--class who lives here — human|server. Decides root SSH's fate after
|
--class who lives here — human|server. Decides root SSH's fate after
|
||||||
`rig users apply`: human closes it, server keeps it as the
|
the users phase: human closes it (`rig users close-root`),
|
||||||
control plane's automation door.
|
server keeps it as the control plane's automation door.
|
||||||
--host does this box host VMs (box/Incus) — yes|no
|
--host does this box host VMs (box/Incus) — yes|no
|
||||||
--join how it enters the tailnet — authkey|login
|
--join how it enters the tailnet — authkey|login
|
||||||
|
|
||||||
|
One of --users/--no-users is required on every role, class=server included:
|
||||||
|
a box nobody logs into routinely is exactly where shared-root access rots,
|
||||||
|
and per-human accounts keep attribution intact for the times someone does go
|
||||||
|
in. So the complete path is the default path and skipping it is a deliberate
|
||||||
|
--no-users, not an omission.
|
||||||
|
|
||||||
|
--users does NOT reach the box TENANT roles (claude|codex|grok|staging). A
|
||||||
|
tenant is a box-minted GUEST: box auto-runs its bootstrap at mint,
|
||||||
|
non-interactively, with no file to hand it; the guest never joins the tailnet
|
||||||
|
and has no SSH door of its own — entry is `box shell`, gated by the HOST's
|
||||||
|
incus grants, which the host's own users file already converged. A fleet-wide
|
||||||
|
operator file has nothing to converge in there.
|
||||||
|
|
||||||
Roles are presets over the three traits; any flag overrides its trait.
|
Roles are presets over the three traits; any flag overrides its trait.
|
||||||
custom presets nothing and requires --hostname plus all three traits.
|
custom presets nothing and requires --hostname plus all three traits.
|
||||||
|
|
||||||
|
|
@ -92,8 +120,15 @@ 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.
|
||||||
TS_HOSTNAME="$ROLE"
|
TS_HOSTNAME="$ROLE"
|
||||||
[ "$ROLE" = "custom" ] && TS_HOSTNAME=""
|
[ "$ROLE" = "custom" ] && TS_HOSTNAME=""
|
||||||
|
USERS_FILE=""
|
||||||
|
NO_USERS=0
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--users)
|
||||||
|
[ $# -ge 2 ] || die "--users needs a value" 2
|
||||||
|
USERS_FILE="$2"; shift 2 ;;
|
||||||
|
--no-users)
|
||||||
|
NO_USERS=1; shift ;;
|
||||||
--hostname)
|
--hostname)
|
||||||
[ $# -ge 2 ] || die "--hostname needs a value" 2
|
[ $# -ge 2 ] || die "--hostname needs a value" 2
|
||||||
TS_HOSTNAME="$2"; shift 2 ;;
|
TS_HOSTNAME="$2"; shift 2 ;;
|
||||||
|
|
@ -150,6 +185,90 @@ if [ "$JOIN" = "login" ] && [ -n "${TS_AUTHKEY:-}" ]; then
|
||||||
die "join=login is interactive: unset TS_AUTHKEY or pass --join authkey" 2
|
die "join=login is interactive: unset TS_AUTHKEY or pass --join authkey" 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- who lives here (#51) -----------------------------------------------------
|
||||||
|
# The users file is the last piece of "what this box is" that bootstrap did not
|
||||||
|
# take, and it is REQUIRED rather than optional: a bootstrapped box with no
|
||||||
|
# users converges to a box only root can enter, and on class=human that is a
|
||||||
|
# half-built machine waiting for a second command the operator has to remember
|
||||||
|
# (`rig users close-root` is itself gated behind "once your admin key works" —
|
||||||
|
# which needs an admin to exist). class=server gets the same requirement on
|
||||||
|
# purpose: a server nobody logs into routinely is exactly where shared-root
|
||||||
|
# access rots, and per-human accounts keep attribution intact for the times
|
||||||
|
# someone does go in.
|
||||||
|
#
|
||||||
|
# The opt-out is a FLAG, not a default. Both states are then something the
|
||||||
|
# operator said out loud, which is the whole point — an omitted --users used to
|
||||||
|
# be indistinguishable from "I meant to and forgot", and the box that resulted
|
||||||
|
# looked identical either way. Contradicting yourself is a usage error too:
|
||||||
|
# --users and --no-users together is not a precedence puzzle rig should silently
|
||||||
|
# resolve, because whichever way it resolved would be the wrong one half the
|
||||||
|
# time.
|
||||||
|
if [ -n "$USERS_FILE" ] && [ "$NO_USERS" -eq 1 ]; then
|
||||||
|
die "--users and --no-users are contradictory: pass the file, or say --no-users, not both" 2
|
||||||
|
fi
|
||||||
|
if [ -z "$USERS_FILE" ] && [ "$NO_USERS" -eq 0 ]; then
|
||||||
|
die "one of --users <path> or --no-users is required: bootstrap converges this box's operators as its last phase, and a box with no named users is one only root can enter. Pass --users <path>, or --no-users to leave it root-only deliberately" 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The users file is PRE-FLIGHTED here and applied at the very end: everything
|
||||||
|
# below this point costs the operator something — apt, a hostname change, a
|
||||||
|
# single-use pre-auth key — and a users file with a typo in it must not be
|
||||||
|
# discovered after all of that was already spent. Same reason every other flag
|
||||||
|
# is validated before the root check: errors belong at the top of the run.
|
||||||
|
USERS_HAS_BOX_ROLE=0
|
||||||
|
if [ -n "$USERS_FILE" ]; then
|
||||||
|
# '-' (stdin) is apply's own convenience and cannot survive the trip through
|
||||||
|
# bootstrap: stdin here belongs to the pre-auth key prompt, and a users file
|
||||||
|
# piped in would either eat that prompt or be eaten by it. Refuse the token
|
||||||
|
# rather than let the two credentials-shaped reads fight over one pipe.
|
||||||
|
[ "$USERS_FILE" != "-" ] \
|
||||||
|
|| die "--users needs a real path: bootstrap's stdin is the pre-auth key prompt's, so it cannot also carry the users file. Write it to a file, or run 'rig users apply --file -' separately after --no-users" 2
|
||||||
|
[ -r "$USERS_FILE" ] || die "cannot read users file: $USERS_FILE" 2
|
||||||
|
if ! USERS_PARSED="$(parse_users_file "$USERS_FILE")"; then
|
||||||
|
die "invalid users file: $USERS_FILE — every error is listed above; nothing was changed" 2
|
||||||
|
fi
|
||||||
|
# Does anyone in the file carry role box? That single fact decides whether the
|
||||||
|
# incus precondition below applies at all — a users file naming only admins
|
||||||
|
# converges perfectly well on a host that has never seen Incus, and refusing
|
||||||
|
# it there would be rig inventing a prerequisite its own apply does not have.
|
||||||
|
if printf '%s\n' "$USERS_PARSED" | cut -d'|' -f2 | grep -qE '(^|,)box(,|$)'; then
|
||||||
|
USERS_HAS_BOX_ROLE=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# host=yes + a box-role user + no incus group = the refusal `users apply` already
|
||||||
|
# owns ("rig NEVER installs Incus: box's setup-host owns the daemon and its
|
||||||
|
# group"). rig does not build that stack here and does not call setup-host —
|
||||||
|
# whether rig should install box on a VM host is an open boundary question, and
|
||||||
|
# resolving it by accident inside a users change would be the worst way to
|
||||||
|
# answer it. What bootstrap CAN do is stop early instead of late.
|
||||||
|
#
|
||||||
|
# Early only where the outcome is already PROVEN, though. The ordinary host=yes
|
||||||
|
# path installs box further down, and box's own installer runs setup-host — so
|
||||||
|
# the group that is missing now will exist by the time the users phase runs, and
|
||||||
|
# an unconditional refusal here would reject the exact bring-up this issue is
|
||||||
|
# about. RIG_SKIP_BOX_INSTALL=1 is the one case with no such rescue: the
|
||||||
|
# operator has said this run will not touch box, so the group's absence is final
|
||||||
|
# and the run is doomed a hundred lines before it notices. The other failure
|
||||||
|
# shapes (no network, box's installer breaking) are not knowable this early and
|
||||||
|
# land in apply's own refusal at the end — the same message, one phase later.
|
||||||
|
#
|
||||||
|
# #49 (merged) added a SECOND host-level refusal to apply: the box CLI itself
|
||||||
|
# missing on host=yes, because the group is only the socket and the tier is
|
||||||
|
# `box grant`, which apply calls rather than reimplements. Under
|
||||||
|
# RIG_SKIP_BOX_INSTALL=1 that absence is just as final and just as knowable
|
||||||
|
# now as the group's, so the early check mirrors both rather than being a
|
||||||
|
# weaker proxy for one of them. Either one alone dooms the run.
|
||||||
|
if [ "$USERS_HAS_BOX_ROLE" -eq 1 ] && [ "$HOST" = "yes" ] \
|
||||||
|
&& [ "${RIG_SKIP_BOX_INSTALL:-}" = "1" ]; then
|
||||||
|
if ! getent group incus >/dev/null 2>&1; then
|
||||||
|
die "a user carries role box and this box hosts VMs (host=yes) but group incus is absent and RIG_SKIP_BOX_INSTALL=1 means this run will not install box — install the box CLI and run 'box setup-host' first; rig never installs Incus. (Or drop RIG_SKIP_BOX_INSTALL and let bootstrap install box as it normally does.)" 2
|
||||||
|
fi
|
||||||
|
if ! command -v box >/dev/null 2>&1; then
|
||||||
|
die "a user carries role box and this box hosts VMs (host=yes) but the box CLI is not on PATH and RIG_SKIP_BOX_INSTALL=1 means this run will not install it — the incus group is only the socket; the restricted tier is 'box grant', which rig calls rather than reimplements. Install the box CLI first. (Or drop RIG_SKIP_BOX_INSTALL and let bootstrap install box as it normally does.)" 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# --- guards ------------------------------------------------------------------
|
# --- guards ------------------------------------------------------------------
|
||||||
[ "$(id -u)" -eq 0 ] || die "must run as root"
|
[ "$(id -u)" -eq 0 ] || die "must run as root"
|
||||||
if [ -r /etc/os-release ]; then
|
if [ -r /etc/os-release ]; then
|
||||||
|
|
@ -520,6 +639,38 @@ if [ "$HOST" = "yes" ]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- users (the last phase, and it must be last) -------------------------------
|
||||||
|
# Ordering is a correctness property, not a preference. `users apply` READS
|
||||||
|
# /etc/rig/role: class= decides which root-SSH note it prints, and host= decides
|
||||||
|
# what an absent incus group means (refuse on yes, skip the box role with a
|
||||||
|
# warning on no). Run before the marker write, apply would see no marker at all
|
||||||
|
# and warn "re-run rig bootstrap so this box knows what it is" — in the middle
|
||||||
|
# of the very bootstrap that is teaching it. Run before the box install, a
|
||||||
|
# host=yes box would refuse its own box-role users for a group arriving twenty
|
||||||
|
# lines later. So: traits, then join, then marker, then box, then people.
|
||||||
|
#
|
||||||
|
# NOT persisted. rig takes the path, reads it once through apply, and keeps
|
||||||
|
# nothing — the users file lives in your private infra repo and is passed per
|
||||||
|
# invocation (README: "rig never persists it"). Taking it as a bootstrap flag
|
||||||
|
# must not quietly turn it into box state, so nothing here copies it anywhere,
|
||||||
|
# and /etc/rig keeps only the ledger of NAMES apply already wrote.
|
||||||
|
#
|
||||||
|
# Invoked as a child, not exec'd: bootstrap still owns the last word (the
|
||||||
|
# next-steps below), and a failing apply must fail the bootstrap — under
|
||||||
|
# `set -e` a non-zero apply ends the run right here, which is correct. The box
|
||||||
|
# is already hardened and joined at this point; what failed is one named phase,
|
||||||
|
# and it is re-runnable on its own with `rig users apply --file <path>`.
|
||||||
|
#
|
||||||
|
# A child also keeps apply's INVOKER gate intact, which is the point: SUDO_USER
|
||||||
|
# rides through, so `sudo rig bootstrap --users <file-naming-me-admin>` by a
|
||||||
|
# role-rig user refuses exactly as `sudo rig users apply` would. Bootstrap must
|
||||||
|
# not become a laundering path around the one gate that stops rig's scoped sudo
|
||||||
|
# from being root-equivalent. Bring-up runs as real root and is unaffected.
|
||||||
|
if [ -n "$USERS_FILE" ]; then
|
||||||
|
log "converging operators from ${USERS_FILE} (rig users apply)"
|
||||||
|
"$HERE/users-apply.sh" --file "$USERS_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
log "done — role ${ROLE}, hostname ${TS_HOSTNAME}"
|
log "done — role ${ROLE}, hostname ${TS_HOSTNAME}"
|
||||||
if [ "$ROLE" = "control-plane" ]; then
|
if [ "$ROLE" = "control-plane" ]; then
|
||||||
log "next: rig coolify install --version <pin>"
|
log "next: rig coolify install --version <pin>"
|
||||||
|
|
@ -528,9 +679,19 @@ elif [ "$ROLE" = "runner" ]; then
|
||||||
fi
|
fi
|
||||||
# Every class gets operators: humans always enter as themselves and elevate via
|
# Every class gets operators: humans always enter as themselves and elevate via
|
||||||
# sudo — a shared root login is unattributable. What differs by class is root
|
# sudo — a shared root login is unattributable. What differs by class is root
|
||||||
# SSH's fate once named users exist.
|
# SSH's fate once named users exist. With --users the accounts exist already, so
|
||||||
if [ "$CLASS" = "human" ]; then
|
# the note that used to point at the missing command now points at what is left
|
||||||
log "next: rig users apply --file <users-file>, then 'rig users close-root' once your admin key works"
|
# to do; --no-users still owes the box its people, and says so.
|
||||||
|
if [ -n "$USERS_FILE" ]; then
|
||||||
|
if [ "$CLASS" = "human" ]; then
|
||||||
|
log "next: 'rig users close-root' once your admin key works — verify you can SSH in as an admin FIRST"
|
||||||
|
else
|
||||||
|
log "operators are converged; root SSH stays — it is the control plane's automation door"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log "next: rig users apply --file <users-file> for named operator logins; root SSH stays — it is the control plane's automation door"
|
if [ "$CLASS" = "human" ]; then
|
||||||
|
log "--no-users: this box has no named operators — root is its only door. When you want them: rig users apply --file <users-file>, then 'rig users close-root' once your admin key works"
|
||||||
|
else
|
||||||
|
log "--no-users: this box has no named operators — root SSH is its only door, and it stays (the control plane's automation door). For named logins: rig users apply --file <users-file>"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
179
test/cli.sh
179
test/cli.sh
|
|
@ -168,6 +168,169 @@ check "bootstrap: the claim follows the doctor gate" \
|
||||||
0 "" test "${doctor_at:-999999}" -lt "${claim_at:-0}"
|
0 "" test "${doctor_at:-999999}" -lt "${claim_at:-0}"
|
||||||
check "bootstrap: a failed doctor warns without claiming the host" 0 "" \
|
check "bootstrap: a failed doctor warns without claiming the host" 0 "" \
|
||||||
grep -q "the CLI landed, the host stack is unproven" "$ROOT/commands/bootstrap.sh"
|
grep -q "the CLI landed, the host stack is unproven" "$ROOT/commands/bootstrap.sh"
|
||||||
|
# --- the users phase (#51): --users is required, --no-users is the opt-out ----
|
||||||
|
# Bootstrap takes the users file and applies it as its LAST phase, so one
|
||||||
|
# command leaves a box with its people on it. Everything about the FLAG is
|
||||||
|
# provable here — the whole surface sits before the root check, deliberately,
|
||||||
|
# because a users file with a typo must not be discovered after apt, a hostname
|
||||||
|
# change and a spent pre-auth key.
|
||||||
|
BOOT_USERS="$(mktemp -d)"
|
||||||
|
cat > "$BOOT_USERS/ok" <<'USERS'
|
||||||
|
dan admin ssh-ed25519 AAAAC3fixture dan@laptop
|
||||||
|
maria rig ssh-ed25519 AAAAC3fixture maria@mac
|
||||||
|
USERS
|
||||||
|
printf '%s\n' 'dan admin,box ssh-ed25519 AAAAC3fixture dan@laptop' > "$BOOT_USERS/box"
|
||||||
|
printf '%s\n' 'maria ops ssh-ed25519 AAAA maria@mac' > "$BOOT_USERS/bad"
|
||||||
|
# The REQUIREMENT, and the message that carries it: omitting both flags must
|
||||||
|
# name BOTH ways out, because an operator who forgot the file and one who meant
|
||||||
|
# to skip it type the identical command — the error is the only place rig can
|
||||||
|
# tell them apart.
|
||||||
|
check "bootstrap: omitting --users and --no-users exits 2" 2 "one of --users <path> or --no-users is required" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload
|
||||||
|
check "bootstrap: the requirement names --no-users as the way out" 2 "--no-users to leave it root-only" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname b
|
||||||
|
check "bootstrap: the requirement holds on class=server too" 2 "one of --users" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" control-plane --hostname cp
|
||||||
|
check "bootstrap: --users needs a value" 2 "needs a value" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users
|
||||||
|
# MUTUAL EXCLUSION, both orders: rig refuses to pick a winner rather than let a
|
||||||
|
# precedence rule decide who may enter the box. Both orders, because a
|
||||||
|
# "last flag wins" implementation would pass one of them silently.
|
||||||
|
check "bootstrap: --users with --no-users exits 2" 2 "contradictory" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/ok" --no-users
|
||||||
|
check "bootstrap: --no-users with --users exits 2 (either order)" 2 "contradictory" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --no-users --users "$BOOT_USERS/ok"
|
||||||
|
# Pre-flight: an unreadable or invalid file dies at the top of the run, exit 2,
|
||||||
|
# before the root check — the same contract every other flag here has.
|
||||||
|
check "bootstrap: an unreadable users file exits 2" 2 "cannot read users file" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/nope"
|
||||||
|
check "bootstrap: an invalid users file exits 2 with the parser's errors" 2 "invalid users file" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/bad"
|
||||||
|
check "bootstrap: the invalid-file refusal carries the parser's own line error" 2 "valid roles: admin rig box" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/bad"
|
||||||
|
# '-' is apply's stdin convenience and cannot survive the trip through
|
||||||
|
# bootstrap: stdin here is the pre-auth key prompt's. Refused, with the split
|
||||||
|
# ('--no-users' then apply by hand) named.
|
||||||
|
check "bootstrap: --users - is refused, naming the pre-auth key prompt" 2 "pre-auth key prompt" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users -
|
||||||
|
# The host=yes box-role precondition, surfaced EARLY — but only where the
|
||||||
|
# outcome is already proven: RIG_SKIP_BOX_INSTALL=1 means this run will not
|
||||||
|
# install box, so a missing incus group can no longer be rescued by the install
|
||||||
|
# further down. The group's presence is a property of whatever machine runs
|
||||||
|
# this harness, so it is driven with a shim `getent` instead — both directions,
|
||||||
|
# on any machine (repo precedent: the install.sh getent shim below).
|
||||||
|
#
|
||||||
|
# The box CLI's presence is the SECOND half of the precondition (#49 merged a
|
||||||
|
# matching die into apply), and it is a property of the runner in exactly the
|
||||||
|
# same way — this machine happens to have box on PATH, a CI runner may not. So
|
||||||
|
# it is shimmed in both directions too, and `box` is deliberately NOT inherited
|
||||||
|
# from the real PATH in these runs: a test that passes only where box happens
|
||||||
|
# to be installed proves nothing about the machine where it isn't.
|
||||||
|
INCUS_SHIM_NO="$BOOT_USERS/shim-no"; INCUS_SHIM_YES="$BOOT_USERS/shim-yes"
|
||||||
|
BOXLESS_SHIM="$BOOT_USERS/shim-nobox"
|
||||||
|
mkdir -p "$INCUS_SHIM_NO" "$INCUS_SHIM_YES" "$BOXLESS_SHIM"
|
||||||
|
# Answer only the `group incus` question; everything else falls through to the
|
||||||
|
# real getent, so the shim cannot quietly change some other lookup's answer.
|
||||||
|
cat > "$INCUS_SHIM_NO/getent" <<'SHIM'
|
||||||
|
#!/bin/sh
|
||||||
|
if [ "$1" = group ] && [ "$2" = incus ]; then exit 2; fi
|
||||||
|
exec /usr/bin/getent "$@"
|
||||||
|
SHIM
|
||||||
|
cat > "$INCUS_SHIM_YES/getent" <<'SHIM'
|
||||||
|
#!/bin/sh
|
||||||
|
if [ "$1" = group ] && [ "$2" = incus ]; then echo "incus:x:900:"; exit 0; fi
|
||||||
|
exec /usr/bin/getent "$@"
|
||||||
|
SHIM
|
||||||
|
# Group present, box absent — the shape #49's die now owns, and the one the
|
||||||
|
# old group-only precondition let through to fail a hundred lines later.
|
||||||
|
cat > "$BOXLESS_SHIM/getent" <<'SHIM'
|
||||||
|
#!/bin/sh
|
||||||
|
if [ "$1" = group ] && [ "$2" = incus ]; then echo "incus:x:900:"; exit 0; fi
|
||||||
|
exec /usr/bin/getent "$@"
|
||||||
|
SHIM
|
||||||
|
# A `box` that exists, for the satisfied case — so INCUS_SHIM_YES proves the
|
||||||
|
# precondition passes on its own terms rather than on the runner's luck.
|
||||||
|
printf '#!/bin/sh\nexit 0\n' > "$INCUS_SHIM_YES/box"
|
||||||
|
chmod +x "$INCUS_SHIM_NO/getent" "$INCUS_SHIM_YES/getent" \
|
||||||
|
"$BOXLESS_SHIM/getent" "$INCUS_SHIM_YES/box"
|
||||||
|
check "bootstrap: host=yes + box role + no incus + skipped box install exits 2" 2 "group incus is absent" \
|
||||||
|
env RIG_SKIP_BOX_INSTALL=1 PATH="$INCUS_SHIM_NO:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
check "bootstrap: that refusal points at box setup-host, not at rig" 2 "rig never installs Incus" \
|
||||||
|
env RIG_SKIP_BOX_INSTALL=1 PATH="$INCUS_SHIM_NO:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
# The group can be there while the CLI is not — #49's die owns that shape, and
|
||||||
|
# under the skip it is just as final and just as knowable now. PATH is built
|
||||||
|
# WITHOUT the real one so the absence is the test's, not the machine's.
|
||||||
|
check "bootstrap: host=yes + box role + incus group + no box CLI + skip exits 2" 2 "box CLI is not on PATH" \
|
||||||
|
env RIG_SKIP_BOX_INSTALL=1 PATH="$BOXLESS_SHIM:/usr/bin:/bin" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
check "bootstrap: that refusal names the tier, not just the socket" 2 "the restricted tier is 'box grant'" \
|
||||||
|
env RIG_SKIP_BOX_INSTALL=1 PATH="$BOXLESS_SHIM:/usr/bin:/bin" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
# It must NOT fire in the three shapes that are not doomed. A users file with
|
||||||
|
# no box-role user converges fine on a host that never saw Incus (refusing it
|
||||||
|
# would be rig inventing a prerequisite apply does not have); an incus group
|
||||||
|
# that exists satisfies it outright; and WITHOUT RIG_SKIP_BOX_INSTALL the
|
||||||
|
# missing group is the box install's to create further down — refusing there
|
||||||
|
# would reject the exact one-command bring-up this flag is for. Reaching the
|
||||||
|
# root check (exit 1) is the proof each passed the precondition.
|
||||||
|
check "bootstrap: no box-role user means no incus precondition" 1 "must run as root" \
|
||||||
|
env TS_AUTHKEY=x RIG_SKIP_BOX_INSTALL=1 PATH="$INCUS_SHIM_NO:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/ok"
|
||||||
|
check "bootstrap: an existing incus group satisfies the precondition" 1 "must run as root" \
|
||||||
|
env TS_AUTHKEY=x RIG_SKIP_BOX_INSTALL=1 PATH="$INCUS_SHIM_YES:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
check "bootstrap: without the skip, the box install is left to create the group" 1 "must run as root" \
|
||||||
|
env TS_AUTHKEY=x PATH="$INCUS_SHIM_NO:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" dev --hostname h --users "$BOOT_USERS/box"
|
||||||
|
# host=no is the other side of apply's host= rule — the box role is skipped
|
||||||
|
# with a warning there, never refused, so bootstrap must not refuse it either.
|
||||||
|
check "bootstrap: host=no never gets the incus precondition" 1 "must run as root" \
|
||||||
|
env TS_AUTHKEY=x RIG_SKIP_BOX_INSTALL=1 PATH="$INCUS_SHIM_NO:$PATH" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/box"
|
||||||
|
fi
|
||||||
|
# rig does NOT resolve the open "should rig install box" question here: the
|
||||||
|
# precondition refuses, it never calls setup-host itself. A grep that finds
|
||||||
|
# nothing (exit 1) is the pass — same shape as the never-apt-install-incus law.
|
||||||
|
check "bootstrap: the users phase never runs box setup-host itself" 1 "" \
|
||||||
|
grep -nE '^[[:space:]]*box setup-host' "$ROOT/commands/bootstrap.sh"
|
||||||
|
# ORDERING is a correctness property, not taste: apply READS /etc/rig/role
|
||||||
|
# (class= picks its root-SSH note, host= decides what a missing incus group
|
||||||
|
# means), and on host=yes it needs the group box's installer built. So the
|
||||||
|
# users phase must sit after BOTH the marker write and the box install. Line
|
||||||
|
# numbers, same idiom as the marker/box-install ordering asserts above;
|
||||||
|
# defaults fail closed. The apply call is grepped as a literal — single quotes
|
||||||
|
# intended, $HERE/$USERS_FILE are the script's own.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
users_apply_at="$(grep -n '"$HERE/users-apply.sh" --file "$USERS_FILE"' "$ROOT/commands/bootstrap.sh" | head -n1 | cut -d: -f1)"
|
||||||
|
check "bootstrap: the users phase invokes users apply" 0 "" \
|
||||||
|
test -n "$users_apply_at"
|
||||||
|
check "bootstrap: the users phase runs after the role marker write" \
|
||||||
|
0 "" test "${box_marker_at:-999999}" -lt "${users_apply_at:-0}"
|
||||||
|
check "bootstrap: the users phase runs after the box install" \
|
||||||
|
0 "" test "${box_install_at:-999999}" -lt "${users_apply_at:-0}"
|
||||||
|
# The users file is passed per invocation and NEVER persisted (README: "rig
|
||||||
|
# never persists it"). Taking it as a bootstrap flag must not quietly turn it
|
||||||
|
# into box state, so nothing may copy it anywhere. A grep that finds nothing
|
||||||
|
# (exit 1) is the pass — same shape as the never-apt-install-incus law.
|
||||||
|
check "bootstrap: the users file is never copied onto the box" 1 "" \
|
||||||
|
grep -nE '^[[:space:]]*(cp|install|mv|tee|cat)[[:space:]].*USERS_FILE' "$ROOT/commands/bootstrap.sh"
|
||||||
|
# Usage must carry both flags: an operator hitting the new requirement reads
|
||||||
|
# --help next, and finding only --users there would leave the opt-out a secret.
|
||||||
|
check "bootstrap: usage documents --users" 0 "--users" "$ROOT/commands/bootstrap.sh" --help
|
||||||
|
check "bootstrap: usage documents --no-users" 0 "--no-users" "$ROOT/commands/bootstrap.sh" --help
|
||||||
|
check "rig usage documents the bootstrap users flags" 0 "(--users <path> | --no-users)" \
|
||||||
|
"$ROOT/bin/rig" --help
|
||||||
|
# The TENANT family takes neither flag. Dispatch happens before this parser
|
||||||
|
# runs, so --users lands in the tenant script's own unknown-flag refusal — the
|
||||||
|
# decision (a box-minted guest has no SSH door of its own; entry is `box shell`,
|
||||||
|
# gated by the HOST's incus grants) is documented in usage and the README.
|
||||||
|
check "bootstrap: --users does not reach the tenant roles" 2 "unknown flag" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" claude --users "$BOOT_USERS/ok"
|
||||||
|
check "bootstrap: usage explains why tenants take no --users" 0 "box-minted GUEST" \
|
||||||
|
"$ROOT/commands/bootstrap.sh" --help
|
||||||
# --- README: the box rename (#12) --------------------------------------------
|
# --- README: the box rename (#12) --------------------------------------------
|
||||||
# The philosophy line must point at heavy-duty/box — the old claudebox slug
|
# The philosophy line must point at heavy-duty/box — the old claudebox slug
|
||||||
# only works through a GitHub redirect that one squatted rename away from
|
# only works through a GitHub redirect that one squatted rename away from
|
||||||
|
|
@ -192,8 +355,14 @@ check "README: documents the mismatch strip on host=no" 0 "" \
|
||||||
check "README: no stale 'group absent decides' semantics" 1 "" \
|
check "README: no stale 'group absent decides' semantics" 1 "" \
|
||||||
grep -n "when the \`incus\` group is absent, the \`host=\` trait decides" "$ROOT/README.md"
|
grep -n "when the \`incus\` group is absent, the \`host=\` trait decides" "$ROOT/README.md"
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
check "bootstrap: refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" workload
|
# Every machine-role invocation now states its users answer — the flag is
|
||||||
check "bootstrap: runner role parses, refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" runner
|
# required (#51), so reaching the root check at all proves it was accepted.
|
||||||
|
# --no-users here keeps these asserts about the ROOT CHECK; the --users path
|
||||||
|
# gets its own root-check assert below, against a valid fixture.
|
||||||
|
check "bootstrap: refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" workload --no-users
|
||||||
|
check "bootstrap: --users file reaches the root check" 1 "must run as root" \
|
||||||
|
env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" workload --users "$BOOT_USERS/ok"
|
||||||
|
check "bootstrap: runner role parses, refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" runner --no-users
|
||||||
# staging dispatches to the tenant mechanism now; reaching ITS root check
|
# staging dispatches to the tenant mechanism now; reaching ITS root check
|
||||||
# through bootstrap.sh proves the dispatch and the tenant arg pass in one go.
|
# through bootstrap.sh proves the dispatch and the tenant arg pass in one go.
|
||||||
# RIG_ROLE_MARKER points at an absent fixture: the tenant marker guard runs
|
# RIG_ROLE_MARKER points at an absent fixture: the tenant marker guard runs
|
||||||
|
|
@ -201,10 +370,10 @@ if [ "$(id -u)" -ne 0 ]; then
|
||||||
# a real /etc/rig/role of its own.
|
# a real /etc/rig/role of its own.
|
||||||
check "bootstrap: staging dispatches to the tenant mechanism, refuses non-root" 1 "must run as root" \
|
check "bootstrap: staging dispatches to the tenant mechanism, refuses non-root" 1 "must run as root" \
|
||||||
env RIG_ROLE_MARKER=/nonexistent/rig-role "$ROOT/commands/bootstrap.sh" staging
|
env RIG_ROLE_MARKER=/nonexistent/rig-role "$ROOT/commands/bootstrap.sh" staging
|
||||||
check "bootstrap: dev role parses, refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" dev
|
check "bootstrap: dev role parses, refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" dev --no-users
|
||||||
check "bootstrap: workstation parses, refuses non-root" 1 "must run as root" env -u TS_AUTHKEY "$ROOT/commands/bootstrap.sh" workstation
|
check "bootstrap: workstation parses, refuses non-root" 1 "must run as root" env -u TS_AUTHKEY "$ROOT/commands/bootstrap.sh" workstation --no-users
|
||||||
check "bootstrap: custom parses, refuses non-root" 1 "must run as root" \
|
check "bootstrap: custom parses, refuses non-root" 1 "must run as root" \
|
||||||
env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" custom --hostname b --class server --host no --join authkey
|
env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" custom --hostname b --class server --host no --join authkey --no-users
|
||||||
else
|
else
|
||||||
echo "skip: bootstrap non-root refusals (running as root)"
|
echo "skip: bootstrap non-root refusals (running as root)"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue