Skip to content

fix(install.sh): the installer could never upgrade an existing install#151

Merged
fullstackjam merged 1 commit into
mainfrom
fix/installer-cannot-upgrade
Jul 17, 2026
Merged

fix(install.sh): the installer could never upgrade an existing install#151
fullstackjam merged 1 commit into
mainfrom
fix/installer-cannot-upgrade

Conversation

@fullstackjam

Copy link
Copy Markdown
Member

What does this PR do?

Stops scripts/install.sh prompting on stdin, and makes the already-installed path actually update.

Why?

Found while trying to verify v0.66.0 in a VM: openboot version kept reporting 0.65.0 after a successful-looking curl -fsSL openboot.dev/install.sh | bash.

curl … | bash means stdin is the pipe carrying the script, not the user's keyboard. Both prompts read from it anyway. Demonstrated:

$ cat demo.sh | bash
已安装,是否重装?
bash: line 3: cho: command not found        ← read ate a byte of the script
  → REPLY = []  → 保留旧版本,不升级

So the already-installed branch asked Reinstall? (y/N), never received the answer, took the No default, printed "Using existing installation", and exec'd the old binary. Every existing user was pinned to whatever version they first installed, no matter how many times they ran the installer. Releases were going out that nobody could receive — and the symptom pointed nowhere near the cause.

There's a second, independent staleness path: Homebrew only refreshes a tap every HOMEBREW_AUTO_UPDATE_SECS (24h by default), so a release published inside that window is invisible and brew upgrade reports success while leaving the old binary in place. The report that prompted this landed 23.5h after the previous release.

Changes

  • prompts go through a new ask_tty helper — reads /dev/tty, falls back to an explicit default when there's no terminal (CI, piped shells)
  • the already-installed branch no longer asks. Someone running the installer wants the current release; defaulting to No was hostile even when the read worked
  • brew update before upgrading, so a fresh release isn't hidden by the tap's auto-update window
  • the resolved version is always printed — running the installer and silently getting yesterday's binary is precisely what failed here

Testing

  • go vet ./... passes
  • Relevant tests added — internal/archtest/installsh_test.go pins the no-bare-read rule; verified it fails (with file:line) when the old read -p is restored, and passes on the fix
  • Tested locally — full L1 green (25 packages); bash -n clean; verified under a real pipe that ask_tty returns its default without consuming the script

Notes for reviewer

The rule is enforced statically because the behavioural gap is wider than this one script: curl-bash-smoke is gated if: github.event_name != 'pull_request' — it never ran on any of the recent PRs (they all show it as skipping) — and it only drives the mock-server path, so scripts/install.sh's brew branch has no behavioural test at all. Upgrade-over-existing-install is the case to add, and un-gating the job on PRs is worth a decision. Recorded in HARNESS.md rather than fixed here to keep this PR to the bug.

`curl -fsSL openboot.dev/install.sh | bash` is the documented install
command, and there stdin is the pipe carrying the script — not the user's
keyboard. Both prompts read from it anyway.

The consequence was not cosmetic. The already-installed branch asked
"Reinstall? (y/N)" and defaulted to No; via curl|bash the `read` consumed
the script's own next bytes as the answer, which never matched ^[Yy]$. So
it printed "Using existing installation", exec'd the old binary, and every
existing user stayed on their old version no matter how many times they
ran the installer. Releases went out that nobody could receive. The
symptom — `openboot version` reporting yesterday's build right after a
successful-looking install — pointed nowhere near the cause. (It also ate
a byte of the script, so the following line ran mangled.)

Changes:

- prompts go through a new ask_tty helper: reads /dev/tty, and falls back
  to an explicit default when there is no terminal (CI, piped shells).
- the already-installed branch no longer asks. Someone running the
  installer wants the current release; "reinstall?" defaulting to No was
  hostile even when the read worked.
- `brew update` before upgrading. Homebrew only refreshes a tap every
  HOMEBREW_AUTO_UPDATE_SECS (24h default), so a release published inside
  that window is invisible and `upgrade` reports success while leaving the
  old binary in place — the second, independent way to get a stale
  install. This is not theoretical either: the report that prompted this
  landed 23.5h after the previous release.
- the resolved version is always printed. Running the installer and
  silently getting yesterday's binary is exactly what this path failed at;
  printing it makes that impossible to miss.

An archtest pins the rule so a bare `read` can't come back. It is a static
check because the behavioural gap is wider than one script:
curl-bash-smoke is gated `if: github.event_name != 'pull_request'` and
only drives the mock-server path, so scripts/install.sh's brew branch has
no test at all. Noted in HARNESS.md as the next control to add.
@github-actions github-actions Bot added tests Tests only docs labels Jul 17, 2026
@fullstackjam
fullstackjam merged commit f90fcf9 into main Jul 17, 2026
14 checks passed
@fullstackjam
fullstackjam deleted the fix/installer-cannot-upgrade branch July 17, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs tests Tests only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant