Skip to content

feat(install): ship curl|sh installer with embedded checksums#507

Merged
DorianZheng merged 3 commits into
ci/cli-release-tag-checksumsfrom
ci/install-script
May 12, 2026
Merged

feat(install): ship curl|sh installer with embedded checksums#507
DorianZheng merged 3 commits into
ci/cli-release-tag-checksumsfrom
ci/install-script

Conversation

@DorianZheng

Copy link
Copy Markdown
Member

Stacked on top of #506 (ci/cli-release-tag-checksums). Merge that first.

Summary

  • Add scripts/install.sh.template, a POSIX-sh installer modeled on mise (mise.run) and uv (astral.sh/uv/install.sh).
  • CI substitutes __VERSION__ and the three __SHA_*__ placeholders from the per-file .sha256 sidecars generated in PR feat(release): curl|sh installer + SHA256SUMS + build provenance #506, then uploads the rendered install.sh to the release. The substitution step has a sanity check that fails the workflow if any placeholder slipped through.
  • README's REST API quick-start install becomes a single curl … | sh line. The manual sha256sum + gh attestation verify recipe stays below for users who download tarballs directly.

How it works

  1. CI on tag push (or release: published) builds the CLI on three targets.
  2. upload_to_release job downloads the artifacts, writes per-file .sha256 sidecars, then renders dist/install.sh from scripts/install.sh.template.
  3. Rendered script has the current version's checksum embedded for the fast path. Non-current versions (BOXLITE_VERSION=v...) fall through to fetching <artifact>.sha256 from the release.

Behavior

  • Default install dir: \$HOME/.local/bin (overridable with BOXLITE_INSTALL_DIR).
  • Target detection: Darwin-arm64aarch64-apple-darwin; Linux-x86_64x86_64-unknown-linux-gnu; Linux-aarch64|arm64aarch64-unknown-linux-gnu.
  • Hard error on macOS Intel and any other arch.
  • Uses curl or wget; uses sha256sum or shasum -a 256. POSIX sh only (no bashisms).
  • Atomic install: download to mktemp -d, verify, then mv the binary into place.
  • Trap cleans up tmpdir on success or interrupt.

Test plan

  • shellcheck scripts/install.sh.template — zero warnings.
  • sh -n on the template and on a locally-rendered copy — clean.
  • Manual sed dry-run with fake checksums — placeholders correctly replaced.
  • Push a throwaway v0.0.0-rc1 tag to a fork; confirm install.sh appears in the release with real checksums substituted in.
  • curl … install.sh | sh on macOS arm64: ~/.local/bin/boxlite --version matches.
  • curl … install.sh | sh inside docker run -it ubuntu:22.04 (with curl ca-certificates): same.
  • Sad path: BOXLITE_VERSION=v9.9.9 curl … | sh — must fail clearly on 404.
  • Sad path: simulate macOS Intel — must reject with the documented error message.

Adds `scripts/install.sh.template`, a POSIX-sh installer modeled after mise
and uv. CI substitutes `__VERSION__` and per-target `__SHA_*__` placeholders
from the just-generated `.sha256` sidecars and uploads the rendered
`install.sh` to the release. Users get:

    curl -fsSL https://github.com/boxlite-ai/boxlite/releases/latest/download/install.sh | sh

The script auto-detects target via uname, defaults to `$HOME/.local/bin`
(overridable with `BOXLITE_INSTALL_DIR`), verifies the tarball against the
embedded checksum on the fast path or fetches the `.sha256` sidecar for
non-current versions (`BOXLITE_VERSION=v...`), and atomically moves the
binary into place. Rejects macOS Intel and unsupported archs with a clear
error.

README's install block becomes a one-liner; the manual verify recipe
(sha256sum + gh attestation verify) stays underneath.
`scripts/release/install.sh.template` reflects what the file actually is:
release-time infrastructure consumed only by CI, not a script users run from
the repo. Matches the existing scripts/{build,ci,deploy,setup,images}/
subdir-by-purpose layout and leaves room for future release-side tooling
(release-note generator, version bumpers) alongside it.
…r pipe

Two Codex adversarial review findings:

1. The installer extracted with default tar settings and then `mv`'d the
   binary into place. When run with sudo for /usr/local/bin, tar's
   ownership-restore can plant a /usr/local/bin/boxlite owned by the CI
   runner's UID (501 on macOS runners, 1001 on Ubuntu) — which on many
   Linux desktops happens to be the user's own UID, making a privileged
   PATH binary writable by an unprivileged process. starship's installer
   has the same bug; uv and mise dodge it by never recommending the root
   path. We can do better with one line. Switched to
   `tar --no-same-owner` + `install -m 0755`, both portable across macOS
   BSD tools and GNU coreutils.

2. README's "pin a version" snippet placed BOXLITE_VERSION/INSTALL_DIR
   before `curl`. POSIX rule: `VAR=val cmd1 | cmd2` decorates only cmd1,
   so the variables never reached the `sh` process actually running the
   installer. Moved the env-var prefix to the sh side of the pipe.
@DorianZheng

Copy link
Copy Markdown
Member Author

Addressed all three Codex adversarial-review findings:

@DorianZheng DorianZheng merged commit 61a8d07 into ci/cli-release-tag-checksums May 12, 2026
@DorianZheng DorianZheng deleted the ci/install-script branch May 12, 2026 10:36
Sign up for free to 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.

1 participant