Skip to content

feat(install): prepare-host admin step — re-land onto develop (#1178, client) - #381

Merged
LukasWodka merged 9 commits into
developfrom
feat/lpi-prepare-host-reland
Jul 27, 2026
Merged

feat(install): prepare-host admin step — re-land onto develop (#1178, client)#381
LukasWodka merged 9 commits into
developfrom
feat/lpi-prepare-host-reland

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Re-land: prepare-host client half onto develop

tracebloc/backend#1178 (client half). This re-targets the prepare-host work that was previously reviewed + approved in #377.

Why this PR exists:#377 was merged, but into its stacked base feat/lpi-tier0 rather than develop. That branch had already reached develop via #375's squash-merge, so it's a dead end — run_prepare_host never actually landed on develop. This PR puts the exact same (approved) commit on top of current develop so it ships.

Contents (identical to the approved #377 change — a clean cherry-pick, only the generated manifest.sha256 regenerated for develop):

  • scripts/lib/setup-linux.shrun_prepare_host (installs runtime + prereqs; grants docker-group access only to an explicit TB_PREPARE_USER, never $SUDO_USER; message gated on an actual grant).
  • scripts/install-k8s.shprepare-host mode routing.
  • scripts/tests/setup-linux.bats — the run_prepare_host tests.
  • scripts/manifest.sha256 — regenerated.

Pairs with the already-merged CLI half tracebloc/cli#394 (tracebloc prepare-host <researcher-username>).

Test plan

  • shellcheck --severity=error clean on the touched scripts.
  • gen-manifest.sh --check clean.
  • bats scripts/tests/*.bats — only the 3 pre-existing macOS baseline failures (validate_config valid / un-stamped DEFAULT_REF / _extract_yaml_value single-quoted).

🤖 Generated with Claude Code


Note

Medium Risk
Changes privileged installer/bootstrap behavior (sudo, docker group, early exit paths) on shared hosts; scope is limited to the new prepare-host flow with extensive bats coverage and no cluster/credential provisioning in that mode.

Overview
Adds prepare-host as a one-time, admin-only installer path so a shared Linux host can get Docker and system deps without provisioning tracebloc as the admin. Researchers named via TB_PREPARE_USER (never $SUDO_USER) receive docker-group access; messaging only promises a no-admin install when that grant succeeds.

Routing:install-k8s.sh treats prepare-host / --prepare-host as terminal (like --diagnose), including when mixed with flags such as --force, and runs run_prepare_host with a lightweight EXIT trap so sudo keepalive is torn down. install.sh still bypasses the healthy tracebloc doctor bailout for prepare-host, but no longer exports TB_FORCE_REINSTALL for that case (only true force/reinstall/pinned-ref paths do).

Docker under prep:install_docker_engine honors TB_PREPARE_HOST_MODE: verify/start the daemon with sudo, skip adding the admin to the docker group, block sg docker re-exec (which would drop args and run a full install), and use prepare-host-specific error text instead of “log out and back in” / “re-run this installer”. Diagnostic systemctl pipelines gain || true so set -e cannot exit before guidance prints.

Bats cover bootstrap bailout vs force propagation and prepare-host Docker/grant behavior; manifest.sha256 is updated for the touched scripts.

Reviewed by Cursor Bugbot for commit ff2a3d2. Bugbot is set up for automated code reviews on this repo. Configure here.

…all unprivileged (#1178)
Adds the admin-only prepare-host path: installs the container runtime +
prerequisites and grants a named non-admin user docker-group access, so they
later install tracebloc at Tier 0 with no root. Rebased onto the current
feat/lpi-tier0 (which now targets develop after routing #374 merged); collapsed
from the prior 4-commit history to avoid replaying an intermediate that carried
resolved conflict markers.
Incorporates the three Bugbot fixes from that history:
- reap the sudo keepalive on EXIT (no lingering background sudo -v loop);
- grant docker-group ONLY to the explicit TB_PREPARE_USER, never $SUDO_USER
(the admin who ran prepare-host);
- only print the "researcher can now install with no admin" message when a
docker-group grant actually succeeded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

Comment threadscripts/lib/setup-linux.sh
LukasWodkaand others added 2 commits July 24, 2026 17:09
# Conflicts:
#	scripts/manifest.sha256
…e admin (Bugbot)
install_docker_engine's tail assumed the invoking user is the end-user:
on the documented non-root prepare-host path it added the ADMIN to the
docker group and the sg re-exec re-ran the script WITHOUT the
prepare-host argument -- a silent FULL provision as the admin -- and a
socket-less admin aborted before the TB_PREPARE_USER grant. In
TB_PREPARE_HOST_MODE the daemon check runs via sudo, the group-add and
re-exec are skipped, and only the researcher named by TB_PREPARE_USER is
ever granted the socket (same least-privilege rule as #377). Two bats
regressions pin both escapes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

Comment threadscripts/install-k8s.sh Outdated
Comment threadscripts/lib/setup-linux.sh
…l active-daemon corner (Bugbot r2)
Two follow-ups on the prepare-host re-land:
- install.sh: prepare-host/--prepare-host now skip the healthy-install
bailout — the argument is useful precisely on a machine that is already
set up (grant ANOTHER researcher docker-group access), and the bailout
would open the home screen and silently skip the TB_PREPARE_USER grant.
It skips ONLY the bailout: the force flag is split out (_tb_force), so
host-prep is never mislabeled as a forced reinstall — a stale sub-script
without the prepare-host dispatch would otherwise treat the run as a
full forced provision.
- setup-linux.sh: in prepare-host mode, a daemon that systemd reports
ACTIVE but that doesn't answer 'sudo docker info' is now terminal with
prepare-host-appropriate guidance, instead of falling through to the
docker-group 'log out and back in' abort meant for regular users.
Regression tests: bootstrap bats stamps DEFAULT_REF like the release
pipeline (assignment line only) to prove prepare-host passes the bailout
with TB_FORCE_REINSTALL unset; setup-linux bats covers the active-but-
unresponsive daemon corner. 16/16 + 58/58.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

e578a31 addresses both Bugbot Mediums:

  1. Bootstrap bailout skips prepare-hostinstall.sh now exempts prepare-host/--prepare-host from the healthy-install bailout (it's useful precisely on healthy machines: granting another researcher docker-group access). The exemption skips only the bailout — the force flag is split into its own _tb_force, so TB_FORCE_REINSTALL stays unset and a host-prep can never be mislabeled as a forced reinstall by a stale sub-script lacking the dispatch.
  2. Prepare-host still hits logout abort — in prepare-host mode, a daemon that systemd reports active but that won't answer sudo docker info is now terminal with prepare-host-appropriate guidance ("check 'sudo docker info', then re-run prepare-host"), instead of falling through to the docker-group "log out and back in" abort meant for regular users. A daemon that's genuinely down still falls through to the shared diagnostics.

Regression tests for both: the bootstrap suite stamps DEFAULT_REF the way the release pipeline does (assignment line only) to prove prepare-host passes the bailout with TB_FORCE_REINSTALL=unset; the setup-linux suite covers the active-but-unresponsive corner. 16/16 + 58/58 bats, shellcheck/style/manifest clean.

Comment threadscripts/lib/setup-linux.sh
Comment threadscripts/lib/setup-linux.sh
…Bugbot r3)
- Daemon DOWN in prepare-host mode: start it via sudo (starting Docker IS
host preparation), re-verify, and stay terminal in prepare-host wording
on every failure — the shared diagnostics end with 're-run this
installer', which for the admin means a full provision as themselves,
the exact outcome prepare-host exists to prevent. Reboot-required case
gets prepare-host wording too.
- TB_PREPARE_USER is trimmed before the non-empty gate and the grant, so
a pasted value with stray spaces doesn't fail usermod while skipping
the honest no-grant messaging.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadscripts/install-k8s.sh Outdated
install.sh's bailout exemption scans all of "$@" for prepare-host, but
the sub-script only dispatched on $1 — so '--force prepare-host' skipped
the bailout, exported the force flag, and then fell through into a FULL
FORCED provision as the admin. The dispatch now scans every argument,
matching the bootstrap's contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadscripts/lib/setup-linux.sh
…(Bugbot r5)
Running NOW isn't host-prep: after a reboot the Tier-0 researcher can't
start the daemon themselves. The success path now ensures docker.service
is enabled on boot (best-effort), matching the fresh-install and
daemon-down recovery paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

@cursor review

Comment threadscripts/lib/setup-linux.sh
…uidance (Bugbot r6)
systemctl status exits 3 for an inactive unit (and the shared block's
grep exits 1 on no match) — under set -e -o pipefail the failing
diagnostics pipeline aborted BEFORE the terminal error message, a silent
death with no re-run guidance. Both the prepare-host block and the shared
daemon-down block now guard the pipeline with || true; the bats stub
returns the real rc 3 to exercise the path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ad6ae2a. Configure here.

@LukasWodkaLukasWodka self-assigned this Jul 26, 2026
divyasinghds
divyasinghds previously approved these changes Jul 27, 2026

@divyasinghdsdivyasinghds left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. A clean cherry-pick of the already-reviewed-and-approved #377 onto develop (only manifest.sha256 regenerated), re-landed because #377 merged into a dead-end branch.

  • The install.sh refactor decoupling _tb_force from _tb_bail_ok is correct: prepare-host skips the healthy bailout without exporting TB_FORCE_REINSTALL, while --force/--reinstall/REF/BRANCH still set both — no regression (the new test asserts TB_FORCE_REINSTALL=unset).
  • Least-privilege holds: prepare-host mode never grants the socket to the invoking admin or sg-re-execs (which would silently become a full provision), grants only an explicit TB_PREPARE_USER (never $SUDO_USER), trims the username before the grant, and stays terminal in prepare-host wording on every Docker failure path. The || true guards on the diagnostic pipelines correctly prevent a set -e death before the guidance prints.

Reviewed with Claude Code.

@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

aptracebloc
aptracebloc previously approved these changes Jul 27, 2026

@aptraceblocaptracebloc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — re-land of the already-approved #377 prepare-host work, and I re-verified the security-critical part.

run_prepare_host grants docker-group access (root-equivalent) only to an explicitly-named TB_PREPARE_USER — it deliberately does NOT fall back to $SUDO_USER (the admin who ran prepare-host), whitespace-trims the value, refuses empty/root, sets granted=1 only on a successful usermod, and only makes the "no-admin install" promise when a grant actually happened. That's the right shape for a privileged grant — no auto-granting root-equivalent access to the wrong user. 8 Bugbot findings resolved, CI green.

One caveat that gates the merge (not the approval): the branch was CONFLICTING with develop — develop advanced and touched setup-linux.sh, setup-linux.bats, and manifest.sha256, which this branch also changes. The manifest conflict is mechanical, but setup-linux.sh is the security-critical file, so the conflict resolution there is worth a re-check before/at merge (I verified the pre-rebase state; a merge could shift run_prepare_host). If it's already been resolved and merged, all the better — just flagging in case the resolution touched that function.

— drafted with Claude (Opus 4.8), sent by @aptracebloc

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ad6ae2a. Configure here.

Adjacency conflict in setup-linux.bats: keep #381's prepare-host tests AND
develop's _fetch_kubectl test (both new, same insertion point); the
_ensure_helm_prereqs tests were removed on develop (#396). Regenerate manifest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka
LukasWodka dismissed stale reviews from aptracebloc and divyasinghds via ff2a3d2July 27, 2026 08:00
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ff2a3d2. Configure here.

@LukasWodka
LukasWodka merged commit 8d9047e into developJul 27, 2026
32 checks passed
@divyasinghds
divyasinghds deleted the feat/lpi-prepare-host-reland branch July 27, 2026 08:06
shujaatTracebloc added a commit that referenced this pull request Jul 31, 2026
…installs (#511)
* fix(#427): grant docker group on any daemon run; refuse sudo-wrapped installs
Two compounding identity bugs on Linux:
1. The docker-group grant ran ONLY inside the fresh-Docker-install branch. On a box
where Docker was already present and the user wasn't in the group, the else-branch
printed "Docker" without granting, and the recovery path dead-ended at "Could not
connect to Docker. Try logging out and back in…" — which couldn't help, because
membership was never granted. Re-runs looped on the same message.
Fix: after the install/else, ensure the invoking user is in the docker group
whenever the daemon path is chosen, regardless of a fresh install. Skip if already
a member (no redundant usermod); prepare-host stays exempt (only TB_PREPARE_USER
is granted, later — least-privilege, #381). The existing sg-docker re-exec then
activates the new membership in-session, so no dead-end loop.
2. Nothing was SUDO_USER-aware: `sudo bash install.sh` ran the WHOLE provision as
root — usermod granted root (not the user), and ~/.tracebloc, ~/.kube/config, and
the chmod-600 credential landed root-owned under /root, with no chown anywhere to
undo it. The installer's model is to run as the daily user and elevate per-step
(RFC-0002), so rather than a fragile ownership remap, refuse the sudo-wrapped full
run early (before any file is created): refuse_sudo_wrapped_install errors when
EUID 0 AND $SUDO_USER is a real (non-root) user. Exemptions: a genuine root login
(no SUDO_USER); prepare-host (the admin path, already dispatched+exited in main()).
Because we never run as root-with-SUDO_USER, every $HOME/$USER path in the tree
stays correct with no remap.
Also: honor TB_PREPARE_USER as the grant target in the main install (new
_real_install_user helper; the #418 Windows peer).
Tests: install_docker_engine grants on the pre-installed path + when only
TB_PREPARE_USER differs, skips when already a member, never grants the admin in
prepare-host, still grants on a fresh install; refuse_sudo_wrapped_install refuses
sudo+SUDO_USER but allows root-login / sudo -i / non-root; _real_install_user.
Closes#427
* fix(#427): grant the invoking user (not TB_PREPARE_USER); name TB_PREPARE_USER in the refuse hint
Bugbot:
- The main-install docker-group grant targeted _real_install_user (TB_PREPARE_USER
when set), but socket access and the sg-docker re-exec key off $USER. When they
differ (e.g. a leftover `export TB_PREPARE_USER=` from prepare-host), the invoking
user never got membership and hit the same dead-end this PR fixes. Grant $USER
directly — the sudo-wrapped run is already refused, so $USER is the real daily
user — and keep TB_PREPARE_USER on the prepare-host path only (where it's granted).
Removed the now-unused _real_install_user helper + its tests.
- The sudo-refuse hint pointed admins at a BARE `prepare-host`, but run_prepare_host
only grants when TB_PREPARE_USER is set — so that remedy prepares the daemon and
grants nobody. Name it: `export TB_PREPARE_USER=<user> && … prepare-host`.
Tests: the grant targets $USER even with a leftover TB_PREPARE_USER; the refuse hint
includes TB_PREPARE_USER=<user>.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#427): refuse hint names a RESEARCHER placeholder, not the admin (Bugbot r2)
The sudo-refuse prepare-host remedy filled TB_PREPARE_USER=${SUDO_USER}, but SUDO_USER
is the ADMIN who ran sudo — the "setting up for someone else" case targets a DIFFERENT
researcher. Following it would grant the admin docker-group access and leave the
intended user locked out (the #377 least-privilege footgun). Use a
<researcher-username> placeholder and say "not yourself". Test updated to require the
placeholder and reject $SUDO_USER.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#427): key the sg-docker re-exec off _grant_user, consistent with the grant (reviewer)
The grant resolved its target with a `${USER:-$(id -un)}` fallback, but the in-session
sg-docker re-exec guard still keyed off bare `$USER`. In the exact USER-unset case the
fallback exists for, the grant landed on `$(id -un)` while that guard saw an empty
`$USER`, skipped the re-exec, and re-introduced the "log out and back in" dead-end.
Hoist _grant_user to the top of install_docker_engine and use it in both places so
they can't disagree. Guard test added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@LukasWodka@divyasinghds@aptracebloc@saadqbal@waqaskhanroghani@shujaatTracebloc