feat(release): curl|sh installer + SHA256SUMS + build provenance#506
Merged
Conversation
8 tasks
Member
Author
|
Adversarial review (Codex) caught a real fan-out bug in the original tag-trigger design — reverted in f095516. See updated PR body for full rationale. |
Triggers build-runtime on push of `v*` tags so a single git tag produces the GitHub Release plus all artifacts, no manual `gh release create` step needed. Adds a SHA256SUMS file covering every published tarball so a future `curl | sh` installer can verify integrity. Updates the stale workflow header comment (it mentioned only runtime tarballs and omitted the CLI output) and documents the prebuilt-binary install path in README.md alongside the existing `cargo install` instructions.
…tation Aligns boxlite's release artifacts with the 2026 cargo-dist / uv / ripgrep convention. Per-file `.sha256` sidecars are what `cargo-binstall` and most shell-based installers look for by default, and GitHub Artifact Attestations (`actions/attest-build-provenance@v2`, OIDC-signed, no secrets) give users a provenance check via `gh attestation verify` without us having to manage GPG keys. The combined `SHA256SUMS` stays for batch verification. README adds a short verification snippet pointing at the new sidecar and attestation.
Adversarial review (Codex) flagged: when softprops/action-gh-release@v2 creates a release using GITHUB_TOKEN, the resulting release.published event does NOT trigger downstream workflows. This is GitHub's anti-recursion guard, confirmed in both softprops' README and GitHub Actions docs. The push.tags trigger added in d9a0625 therefore silently broke fan-out to build-wheels, build-node, build-c, and build-go — a tag would publish a release with the CLI/runtime but no Python wheels, npm packages, or C archives. Revert to release.published-only gating. The two-command release flow still works fine: git push origin v0.9.4 gh release create v0.9.4 --generate-notes --verify-tag (The user's own token, not GITHUB_TOKEN, creates that release event, so all SDK workflows fan out correctly.)
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.
61a8d07 to
273ddf0
Compare
… coverage Three adversarial-review iterations tightened the installer: - Atomic replace via stage-in-target + mv -f (GNU install truncates in place; macOS BSD install is already atomic, so this normalizes Linux behavior). - install.sh itself now ships in SHA256SUMS, has its own .sha256 sidecar, and is included in the actions/attest-build-provenance@v2 subject set. Render step moved before sidecar generation so a single loop covers every release asset; sh -n install.sh runs as a gate before upload. - BOXLITE_EXPECTED_SHA256 lets callers pin an out-of-band digest that takes precedence over the embedded fast-path and the remote sidecar; pinned installs without it emit a stderr warning explicitly naming the weaker trust boundary. - scripts/release/test_install.sh runs as a CI gate from lint.yml (path- filtered on scripts/release/** + build-runtime.yml) and as a pre-publish step inside build-runtime.yml against the freshly-rendered installer, so regressions on any of the above are caught before a release ships.
Comment on lines
+287
to
+296
| install_script: | ||
| name: Installer script smoke test | ||
| needs: changes | ||
| if: ${{ needs.changes.outputs.install_script == 'true' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Run installer script smoke test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end story for installing the
boxliteCLI withcurl | sh, plus the supply-chain integrity tooling around it.Release artifacts (
build-runtime.yml):.sha256sidecars next to every tarball (matches cargo-dist / uv / ripgrep convention; unlockscargo-binstallout of the box).SHA256SUMSfor batch verification.actions/attest-build-provenance@v2— sigstore-backed, OIDC, no secrets. Verify withgh attestation verify <file> --repo boxlite-ai/boxlite.install.shpublished as a release asset.scripts/release/install.sh.template— 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 sidecars at release time, then uploads the renderedinstall.sh.User-facing one-liner:
How install.sh behaves
BOXLITE_INSTALL_DIR=$HOME/.local/bin(no sudo needed).uname -s/uname -m; rejects macOS Intel and any other arch.<artifact>.sha256from the release forBOXLITE_VERSIONpinning.mktemp -d, verify, extract withtar --no-same-owner, theninstall -m 0755into place. Trap cleans the tmpdir.Adversarial review history
Codex adversarial review surfaced three findings on an earlier revision; all were validated and fixed in this branch:
push.tags: ['v*']trigger silently broke SDK fan-out becausesoftprops/action-gh-release@v2creates releases viaGITHUB_TOKEN, and GitHub's anti-recursion guard blocks the resultingrelease.publishedevent from triggering downstream workflows. Reverted (f095516). Release flow is nowgit push tag && gh release create ....tar -xzf … && mv && chmodcould plant a/usr/local/bin/boxliteowned by the CI runner UID when run under sudo (privilege escalation on hosts where the user's UID matches the runner's). starship has the same bug; uv/mise dodge by never recommending the root path. Switched totar --no-same-owner+install -m 0755(61a8d07).curl, so they decorated curl, not sh. POSIX rule:VAR=val cmd1 | cmd2only sets VAR for cmd1. Fixed in 61a8d07.Test plan
git push origin v0.0.0-rc1 && gh release create v0.0.0-rc1 --generate-notes --verify-tag) on a fork..sha256sidecars +SHA256SUMS+install.sh+ provenance attestations.sha256sum -c <tarball>.sha256— expectOK.gh attestation verify <tarball> --repo <fork>— expect a successful match.build-wheels,build-node,build-c) fired on the samerelease: publishedevent.curl -fsSL <release>/install.sh | sh. Expect~/.local/bin/boxlite --versionmatches the tag.docker run -it ubuntu:22.04 …): same.curl <release>/install.sh | BOXLITE_VERSION=v9.9.9 sh— must fail clearly on missing tarball.sudo BOXLITE_INSTALL_DIR=/usr/local/bin sh install.shthenls -l /usr/local/bin/boxlite— ownerroot, mode0755.Consolidates the previously-stacked PR #507 into this branch.