install: derive $HOME from getent when the environment has none (#39) #41

Merged
dan-claude-bot merged 1 commit from fix/home-unset into main 2026-07-18 23:53:21 +00:00
dan-claude-bot commented 2026-07-18 22:36:09 +00:00 (Migrated from github.com)

Closes #39.

cloud-init's runcmd carries no $HOME, and install.sh (set -euo pipefail) died on its first $HOME expansion with a bash unbound-variable stack — found live while e2e-testing box#88's seed, which pins HOME=/root on its install line as its own scar (that pin stays; this makes the installer robust for every other caller).

The fix (install.sh, before any path derivation): when $HOME is unset, derive it from getent passwd for the effective uid (works for root and non-root); when getent has no answer either, refuse by name (set HOME and re-run) instead of a stack trace. || true on the pipeline so pipefail can't turn the no-answer case into an unnamed death — the refusal owns it.

Verification

  • test/cli.sh: 321 passed, 0 failed (main baseline 318; +3: a real driven install with env -u HOME and a shim getent landing the tree under the derived home; the no-answer refusal pinned by message and exit code).
  • shellcheck -x (CI's invocation): clean.

🤖 Generated with Claude Code

Closes #39. cloud-init's `runcmd` carries no `$HOME`, and `install.sh` (`set -euo pipefail`) died on its first `$HOME` expansion with a bash unbound-variable stack — found live while e2e-testing box#88's seed, which pins `HOME=/root` on its install line as its own scar (that pin stays; this makes the installer robust for every other caller). **The fix** (`install.sh`, before any path derivation): when `$HOME` is unset, derive it from `getent passwd` for the effective uid (works for root and non-root); when getent has no answer either, refuse **by name** (`set HOME and re-run`) instead of a stack trace. `|| true` on the pipeline so pipefail can't turn the no-answer case into an unnamed death — the refusal owns it. **Verification** - `test/cli.sh`: **321 passed, 0 failed** (main baseline 318; +3: a real driven install with `env -u HOME` and a shim getent landing the tree under the derived home; the no-answer refusal pinned by message and exit code). - `shellcheck -x` (CI's invocation): clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
danmt (Migrated from github.com) reviewed 2026-07-18 22:36:09 +00:00
claude-bot-andresmgsl (Migrated from github.com) reviewed 2026-07-18 22:39:47 +00:00
claude-bot-andresmgsl (Migrated from github.com) left a comment

Reviewed — I agree with all of this, no concerns.

First review — a focused fix for #39: cloud-init's runcmd carries no $HOME, so under set -euo pipefail the first $HOME expansion died with an unbound-variable stack instead of installing. Things I checked that could have been bugs but aren't:

  • The set -e / set -u trap is handled correctly. The guard runs before any path is derived from $HOME, and ${HOME:-} in the [ -z ] test avoids tripping -u while probing. HOME="$(getent passwd "$(id -u)" | cut -d: -f6 || true)" — the || true is load-bearing: var=$(cmd) propagates the command's exit status to set -e, so without it a getent that exits nonzero (no entry, or getent absent) would kill the script with its code before reaching the named refusal. With it, the substitution yields empty and the explicit [ -z "$HOME" ] refusal owns every no-answer case — never a bare stack.
  • getent passwd $(id -u) is the right lookup, keyed on the effective uid, so it resolves for root and non-root alike; field 6 (cut -d: -f6) is the home dir. A colon can't appear in a passwd home field, so the cut is safe.
  • Refusal is actionable — names the fix (set HOME and re-run) and the uid, exits 1, no side effects.
  • The tests pin both branches: test/cli.sh:973 drives a real env -u HOME install through a shim getent and asserts the tree lands under the derived home; :987 swaps in an exit 2 getent and pins the by-name refusal + exit 1. 321 passed (main baseline 318, +3), shellcheck -x clean.

The box#88 seed's HOME=/root pin correctly stays as its own scar; this makes the installer robust for every other caller. Ready to land.

automated review by claude-bot-andresmgsl · heavy-duty-review-bot

✅ **Reviewed — I agree with all of this, no concerns.** First review — a focused fix for #39: cloud-init's `runcmd` carries no `$HOME`, so under `set -euo pipefail` the first `$HOME` expansion died with an unbound-variable stack instead of installing. Things I checked that could have been bugs but aren't: - **The `set -e` / `set -u` trap is handled correctly.** The guard runs before any path is derived from `$HOME`, and `${HOME:-}` in the `[ -z ]` test avoids tripping `-u` while probing. `HOME="$(getent passwd "$(id -u)" | cut -d: -f6 || true)"` — the `|| true` is load-bearing: `var=$(cmd)` propagates the command's exit status to `set -e`, so without it a getent that exits nonzero (no entry, or getent absent) would kill the script with *its* code before reaching the named refusal. With it, the substitution yields empty and the explicit `[ -z "$HOME" ]` refusal owns every no-answer case — never a bare stack. - **`getent passwd $(id -u)` is the right lookup**, keyed on the *effective* uid, so it resolves for root and non-root alike; field 6 (`cut -d: -f6`) is the home dir. A colon can't appear in a passwd home field, so the cut is safe. - **Refusal is actionable** — names the fix (`set HOME and re-run`) and the uid, exits 1, no side effects. - **The tests pin both branches**: `test/cli.sh:973` drives a real `env -u HOME` install through a shim getent and asserts the tree lands under the derived home; `:987` swaps in an `exit 2` getent and pins the by-name refusal + exit 1. 321 passed (main baseline 318, +3), `shellcheck -x` clean. The box#88 seed's `HOME=/root` pin correctly stays as its own scar; this makes the installer robust for every other caller. Ready to land. — _automated review by `claude-bot-andresmgsl` · heavy-duty-review-bot_
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-18 22:40:55 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: Approve — I agree with this as-is.

Focused fix for #39: cloud-init runcmd has no $HOME, and under set -euo pipefail the first $HOME expansion was a stack instead of an install.

Checked

  • Guard order — runs before any path is derived from $HOME; ${HOME:-} in the probe avoids tripping -u.
  • || true on the getent pipeline — load-bearing under pipefail: a no-answer getent would otherwise kill the script with its exit code before the named refusal. With it, empty HOME falls through to the explicit message + exit 1.
  • Lookup is by effective uid (getent passwd $(id -u) field 6) — correct for root and non-root; passwd home fields cannot contain :, so cut -d: -f6 is safe.
  • Tests pin both branches — real env -u HOME install via shim getent lands under the derived home; exit 2 getent pins by-name refusal + exit 1.
  • CI green (check / db-integration / reconcile / scope).

box#88's HOME=/root pin correctly stays as its own scar. Ready to land.

**Verdict: Approve** — I agree with this as-is. Focused fix for #39: cloud-init `runcmd` has no `$HOME`, and under `set -euo pipefail` the first `$HOME` expansion was a stack instead of an install. ### Checked - **Guard order** — runs before any path is derived from `$HOME`; `${HOME:-}` in the probe avoids tripping `-u`. - **`|| true` on the getent pipeline** — load-bearing under pipefail: a no-answer getent would otherwise kill the script with *its* exit code before the named refusal. With it, empty HOME falls through to the explicit message + exit 1. - **Lookup is by effective uid** (`getent passwd $(id -u)` field 6) — correct for root and non-root; passwd home fields cannot contain `:`, so `cut -d: -f6` is safe. - **Tests pin both branches** — real `env -u HOME` install via shim getent lands under the derived home; `exit 2` getent pins by-name refusal + exit 1. - CI green (check / db-integration / reconcile / scope). box#88's `HOME=/root` pin correctly stays as its own scar. Ready to land.
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-18 22:48:09 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: I agree with everything and have no additional feedback.

The fallback runs before any HOME-derived path, safely probes an unset variable under set -u, resolves the effective user through getent, and turns a missing lookup into an actionable failure. The tests cover both successful derivation and refusal, the linked cloud-init scenario is addressed, and all current checks pass.

Verdict: I agree with everything and have no additional feedback. The fallback runs before any HOME-derived path, safely probes an unset variable under `set -u`, resolves the effective user through `getent`, and turns a missing lookup into an actionable failure. The tests cover both successful derivation and refusal, the linked cloud-init scenario is addressed, and all current checks pass.
dan-claude-bot commented 2026-07-18 22:52:17 +00:00 (Migrated from github.com)

Round 1: unanimous (codex / grok / claude -agree, nothing outstanding). CI green. Requesting @danmt for final review.

Round 1: unanimous (codex ✅ / grok ✅ / claude ✅-agree, nothing outstanding). CI green. Requesting @danmt for final review.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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