runuser joins the admin-binary preflight — and runner remove gets the preflight it lacks #148

Open
opened 2026-08-04 08:55:37 +00:00 by andres · 1 comment
Owner

Context

Normalized from @andres's report (2026-08-04), kept verbatim:

Mising runuser command not found (using debian 13

Context

we should avoid this error when trying to install.. Maybe use "sudo apt-get install util-linux" ?

Root cause, measured on Debian 13 (2026-08-17): runuser ships in util-linuxEssential: yes, so it is on every Debian system — at /usr/sbin/runuser. apt-get install util-linux is therefore a no-op; the binary was present. What was missing was /usr/sbin on the root shell's PATH (su without -, sanitised secure_path, some container images) — the exact family #139 named, whose fix (!140, merged 2026-08-02) postdates the 0.3.1 release the installer serves, so an installed rig had no preflight at all when this was hit.

On main at cb4de44 the install-time instance is largely fixed — commands/lib/admin-path.sh refuses a sbin-less PATH before any prompt. Two residues remain, and they are this issue:

  1. commands/runner-remove.sh has no preflight at all, yet calls runuser at :101 and :106. On a PATH-shorn root it dies mid-teardown with the same bare command not found, after it has started changing state.
  2. The preflights that guard commands which go on to call runuser do not name it. forgejo-runner-install.sh:244 names useradd usermod but calls runuser at :478; runner-install.sh:93 names useradd, calls runuser at :190; bootstrap-tenant.sh:213 names usermod, calls runuser at :406. admin-path.sh's own rule is "BOTH binaries this command goes on to call" — a preflight that names most of its call sites is the hole the next bug arrives through (#75's shape). runuser is same-directory /usr/sbin, so it belongs in the lists — unlike gpasswd, whose exclusion the lib's comment already defends.

Spec

  • admin-path.sh's measured table gains the runuser row: /usr/sbin, package util-linux (Essential — so its absence is always a PATH fact, never a missing package). The die message is already correct and does not change.
  • runner-remove.sh sources admin-path.sh and calls require_admin_bins runuser beside its root check, before any state change — the same placement doctrine as the four existing call sites.
  • forgejo-runner-install.sh, runner-install.sh, bootstrap-tenant.sh add runuser to their existing require_admin_bins argument lists. No new call sites.
  • users-close-root.sh is out of scope: its soft-skip (:118) is deliberate, documented behaviour.
  • forgejo-runner-remove.sh calls no /usr/sbin binary — verified — and stays untouched.

Tasks

  • commands/lib/admin-path.sh — add the runuser / util-linux row to the measured table
  • commands/runner-remove.sh — source the lib, require_admin_bins runuser beside the root check
  • commands/forgejo-runner-install.sh:244useradd usermod runuser; commands/runner-install.sh:93useradd runuser; commands/bootstrap-tenant.sh:213usermod runuser
  • test/cli.sh — extend the #139 block (:3337, the SBINLESS idiom) with a runner-remove refusal check; update any pinned caller-count the suite keeps on admin-path
  • changelog.d/148.md

Acceptance criteria

  • As stubbed root with a PATH carrying no /usr/sbin, runner-remove.sh exits non-zero naming runuser and the PATH=/usr/sbin:/sbin:$PATH remedy, before touching unit, user, or directory state
  • The three named preflights each list runuser; grep -n 'require_admin_bins' commands/*.sh shows it at every command that later calls runuser (except users-close-root.sh)
  • users-close-root.sh behaviour is byte-unchanged
  • shellcheck step clean; bash test/cli.sh passes
  • changelog.d/148.md says what changed, one line per surface

Test plan

Driven through test/cli.sh's existing #139 idiom (stubbed id -u=0, SBINLESS PATH):

  • Must fail: runner-remove.sh under the sbin-less PATH → exit non-zero, message names runuser, and no teardown side effect is observable in the sandbox.
  • The three widened preflights under the sbin-less PATH still refuse before their first prompt or state change (existing checks keep passing with the widened lists).
  • Must fail: dropping runuser from a widened list turns the new coverage red.

Dependencies

Refs #139 (the family), !140 (the fix this extends). Not blocked: the 0.3.2 release ships the install-side fix regardless; this closes the family's remaining door.

## Context Normalized from @andres's report (2026-08-04), kept verbatim: > Mising runuser command not found (using debian 13 > ### Context > we should avoid this error when trying to install.. Maybe use "sudo apt-get install util-linux" ? Root cause, measured on Debian 13 (2026-08-17): `runuser` ships in `util-linux` — `Essential: yes`, so it is on **every** Debian system — at `/usr/sbin/runuser`. `apt-get install util-linux` is therefore a no-op; the binary was present. What was missing was `/usr/sbin` on the root shell's PATH (`su` without `-`, sanitised `secure_path`, some container images) — the exact family #139 named, whose fix (!140, merged 2026-08-02) postdates the `0.3.1` release the installer serves, so an installed rig had no preflight at all when this was hit. On `main` at [`cb4de44`](https://forgejo.heavyduty.builders/heavy-duty/rig/src/commit/cb4de44bf4c5c98f3459c2321846d434283c69d9) the install-time instance is largely fixed — [`commands/lib/admin-path.sh`](https://forgejo.heavyduty.builders/heavy-duty/rig/src/commit/cb4de44bf4c5c98f3459c2321846d434283c69d9/commands/lib/admin-path.sh) refuses a sbin-less PATH before any prompt. Two residues remain, and they are this issue: 1. **`commands/runner-remove.sh` has no preflight at all**, yet calls `runuser` at [`:101`](https://forgejo.heavyduty.builders/heavy-duty/rig/src/commit/cb4de44bf4c5c98f3459c2321846d434283c69d9/commands/runner-remove.sh#L101) and `:106`. On a PATH-shorn root it dies mid-teardown with the same bare `command not found`, after it has started changing state. 2. **The preflights that guard commands which go on to call `runuser` do not name it.** `forgejo-runner-install.sh:244` names `useradd usermod` but calls `runuser` at `:478`; `runner-install.sh:93` names `useradd`, calls `runuser` at `:190`; `bootstrap-tenant.sh:213` names `usermod`, calls `runuser` at `:406`. `admin-path.sh`'s own rule is "BOTH binaries this command goes on to call" — a preflight that names most of its call sites is the hole the next bug arrives through (#75's shape). `runuser` is same-directory `/usr/sbin`, so it belongs in the lists — unlike `gpasswd`, whose exclusion the lib's comment already defends. ## Spec - `admin-path.sh`'s measured table gains the `runuser` row: `/usr/sbin`, package `util-linux` (Essential — so its absence is always a PATH fact, never a missing package). The die message is already correct and does not change. - `runner-remove.sh` sources `admin-path.sh` and calls `require_admin_bins runuser` beside its root check, **before any state change** — the same placement doctrine as the four existing call sites. - `forgejo-runner-install.sh`, `runner-install.sh`, `bootstrap-tenant.sh` add `runuser` to their existing `require_admin_bins` argument lists. No new call sites. - `users-close-root.sh` is **out of scope**: its soft-skip (`:118`) is deliberate, documented behaviour. - `forgejo-runner-remove.sh` calls no `/usr/sbin` binary — verified — and stays untouched. ## Tasks - [ ] `commands/lib/admin-path.sh` — add the `runuser` / `util-linux` row to the measured table - [ ] `commands/runner-remove.sh` — source the lib, `require_admin_bins runuser` beside the root check - [ ] `commands/forgejo-runner-install.sh:244` → `useradd usermod runuser`; `commands/runner-install.sh:93` → `useradd runuser`; `commands/bootstrap-tenant.sh:213` → `usermod runuser` - [ ] `test/cli.sh` — extend the `#139` block (`:3337`, the `SBINLESS` idiom) with a `runner-remove` refusal check; update any pinned caller-count the suite keeps on `admin-path` - [ ] `changelog.d/148.md` ## Acceptance criteria - [ ] As stubbed root with a PATH carrying no `/usr/sbin`, `runner-remove.sh` exits non-zero naming `runuser` and the `PATH=/usr/sbin:/sbin:$PATH` remedy, before touching unit, user, or directory state - [ ] The three named preflights each list `runuser`; `grep -n 'require_admin_bins' commands/*.sh` shows it at every command that later calls `runuser` (except `users-close-root.sh`) - [ ] `users-close-root.sh` behaviour is byte-unchanged - [ ] `shellcheck` step clean; `bash test/cli.sh` passes - [ ] `changelog.d/148.md` says what changed, one line per surface ## Test plan Driven through `test/cli.sh`'s existing `#139` idiom (stubbed `id -u`=0, `SBINLESS` PATH): - **Must fail:** `runner-remove.sh` under the sbin-less PATH → exit non-zero, message names `runuser`, and no teardown side effect is observable in the sandbox. - The three widened preflights under the sbin-less PATH still refuse before their first prompt or state change (existing checks keep passing with the widened lists). - **Must fail:** dropping `runuser` from a widened list turns the new coverage red. ## Dependencies Refs #139 (the family), !140 (the fix this extends). Not blocked: the 0.3.2 release ships the install-side fix regardless; this closes the family's remaining door.
claude-bot-andresmgsl changed title from Mising runuser command not found (using debian 13 to runuser joins the admin-binary preflight — and runner remove gets the preflight it lacks 2026-08-17 23:21:59 +00:00
claude-bot-andresmgsl added the
bug
ready
scope:bootstrap
scope:runner
labels 2026-08-17 23:22:00 +00:00

Normalized to contract — and your apt-get question answered

@andres — triage here. Your report was real and is now the work order above; your original text is quoted verbatim in Context. The one correction worth stating: sudo apt-get install util-linux would not have helped. util-linux is Essential: yes on Debian — runuser was already on the box, at /usr/sbin/runuser. The shell just could not see it, because that root shell's PATH carried no /usr/sbin (a su without - does this, and so do some container images). PATH=/usr/sbin:/sbin:$PATH rig …, or su - instead of su, is the immediate remedy on any installed release.

The install-time version of this is already fixed on main — #139/!140 (merged 2026-08-02) refuses a sbin-less PATH before the token prompt, with that remedy in the message — and ships with the next release; 0.3.1, which the installer currently serves, predates it, which is why you still hit the bare error on 2026-08-04. What this issue now owns is the residue: runner remove has no such guard at all, and the guards that exist don't name runuser even where they go on to call it. Spec, tasks, and test plan above; labeled ready.

## Normalized to contract — and your `apt-get` question answered @andres — triage here. Your report was real and is now the work order above; your original text is quoted verbatim in Context. The one correction worth stating: **`sudo apt-get install util-linux` would not have helped.** `util-linux` is `Essential: yes` on Debian — `runuser` was already on the box, at `/usr/sbin/runuser`. The shell just could not see it, because that root shell's PATH carried no `/usr/sbin` (a `su` without `-` does this, and so do some container images). `PATH=/usr/sbin:/sbin:$PATH rig …`, or `su -` instead of `su`, is the immediate remedy on any installed release. The install-time version of this is already fixed on `main` — #139/!140 (merged 2026-08-02) refuses a sbin-less PATH *before* the token prompt, with that remedy in the message — and ships with the next release; `0.3.1`, which the installer currently serves, predates it, which is why you still hit the bare error on 2026-08-04. What this issue now owns is the residue: `runner remove` has no such guard at all, and the guards that exist don't name `runuser` even where they go on to call it. Spec, tasks, and test plan above; labeled `ready`.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#148
No description provided.