Uh oh!
There was an error while loading. Please reload this page.
fix(installer): harden the bootstrap idiom to fail closed (cli#396) - #497
Conversation
The one-liner we print (and exec) for the installer was `bash <(curl -fsSL https://tracebloc.io/i.sh)`. Process substitution doesn't propagate curl's exit code: on a network/DNS/HTTP failure bash reads an empty script from the substituted fd and exits 0. So every copy-paste recovery hint derived from it — doctor's reinstall / "set one up" remedies, the cluster-discovery error, and prepareHostManualHint — could report success while nothing ran, or run a partial download. Move the URL and the idiom into one internal/installer package. The new shape is download-to-temp-file + `set -e`, but on a single line wrapped in a subshell: (set -e; tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT; curl -fsSL --tlsv1.2 URL -o "$tmp"; bash "$tmp") - fail-closed: `set -e` + `curl -o` aborts with curl's real status; the full download completes before anything runs, so no partial execution. - single line: a multi-line block pastes unreliably (PowerShell can run a multi-line paste bottom-up, executing the installer before the download). One line pastes the same everywhere. - paste-safe: the subshell scopes `set -e`/`trap` so pasting into an interactive shell can't arm errexit on it (which would close the terminal on the curl failure) or leave a stray EXIT trap. - interactive stdin preserved: runs a downloaded file, not a pipe or process substitution, so the installer's prompts still read the TTY. Every consumer now derives from installer.Cmd/Script, so the printed hint is byte-identical to the command we just tried, and a URL/idiom change lands everywhere at once. Adds an executable test that proves fail-closed (dead endpoint, offline) and no errexit/trap leak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shujaatTracebloc
commented
Aug 13, 2026
bugbot run |
There was a problem hiding this comment.
✅ 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 429e9e6. Configure here.
saadqbal
left a comment
There was a problem hiding this comment.
Nice — the win here is real: the printed hints were the fail-open bit (bash <(curl …) exits 0 on a bad download), and folding them into the same subshell set -e idiom that exec runs closes that. One helper, all six call sites derive from it, no private copy left. The executable installer_test (bogus endpoint → non-zero + no errexit/trap leak) is exactly the right proof and would go red against the old shape. go test ./internal/{cli,cluster,installer} and go vet both green locally. One tiny doc nit inline, non-blocking.
Uh oh!
There was an error while loading. Please reload this page.
Asad's review: the doc lumped `bash <(curl …)` in with the stdin-stealing argument, but process substitution hands bash a /dev/fd/N *filename*, so its stdin stays the TTY — that's why it was the original choice. The load-bearing reason to reject it is fail-open (it exits 0 on a curl failure), not stdin. Split the two rationales so the #397 justification is precise. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shujaatTracebloc
commented
Aug 13, 2026
Good catch — fixed in |
shujaatTracebloc
commented
Aug 13, 2026
bugbot run |
There was a problem hiding this comment.
✅ 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 047a4a5. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Fixes#396.
Problem
The one-liner we print (and exec) for the installer was
bash <(curl -fsSL https://tracebloc.io/i.sh). Process substitution doesn't propagate curl's exit code — on a network/DNS/HTTP failure, bash reads an empty script from the substituted fd and exits0. So every copy-paste recovery hint derived from it — doctor's reinstall / "set one up" remedies, the cluster-discovery error, andprepareHostManualHint— could report success while nothing ran, or run a partially-downloaded script.#394's automated path was already fail-closed (temp file +set -e+curl -o); only the copy-paste manual hints inherited the fail-open behavior.Fix
New
internal/installerpackage holds the URL and the one bootstrap idiom; every consumer derives frominstaller.Cmd/installer.Script(...). The printed hint is now byte-identical to the command we just tried, and a URL/idiom change lands everywhere at once.The new shape — download-to-temp-file +
set -e, but one line wrapped in a subshell:Why this over the issue's two options:
bash <(curl … || echo 'exit 1')The
( … )subshell is load-bearing: it scopesset -e/trapso pasting into an interactive shell can't arm errexit on it (which would close the user's terminal on the curl failure) or leave a stray EXIT trap. Running a downloaded file (not a pipe/substitution) keeps the installer's stdin on the TTY.Verification
go build ./...,go test ./...,make lint,make check-style,gofmt— all clean.127.0.0.1:1) and paste-safety (no errexit/trap leak into the caller's shell).%splaceholders;discover.gois in a non-harvested package).🤖 Generated with Claude Code
Note
Low Risk
User-facing installer strings change shape but behavior is intentionally hardened; scope is CLI messaging/exec wrappers with strong regression tests and no auth or data-path changes.
Overview
Fixes cli#396 by replacing the fail-open
bash <(curl …)copy-paste hints with a single shared bootstrap command that downloads to a temp file, usesset -e, and wraps everything in a one-line subshell so curl failures exit non-zero andset -e/trapdo not leak into the user’s shell when they paste it.Adds
internal/installer(URL,Cmd,Script) as the only source for the installer URL and shell idiom.doctor,upgrade,prepare-host, and cluster discovery errors now all useinstaller.Cmd/installer.Scriptinstead of local constants or inline URLs. On Unix, executed commands and manual retry hints are the same string (includingprepare-hostwith optionalTB_PREPARE_USER).New tests lock in single-line paste safety, TLS pinning, forbidden pipe/substitution shapes, and an executable check that failed downloads are non-zero without errexit/trap leaks.
Reviewed by Cursor Bugbot for commit 047a4a5. Bugbot is set up for automated code reviews on this repo. Configure here.