feat(release): GitHub Release workflow + install scripts + Homebrew formula (#153)#11
Merged
Merged
Conversation
…mula (#153)
Closes the v0.1 epic (#147). Phase 5 is the distribution
infrastructure that makes the CLI installable without `go install`.
## What lands in this PR
### .github/workflows/release.yml
Triggered by `v*.*.*` tags (and workflow_dispatch for manual
re-runs). Two-job pipeline:
- `release` (matrix): cross-compiles linux/{amd64,arm64},
darwin/{amd64,arm64}, windows/amd64. Each binary gets a
cosign keyless OIDC signature (.sig + .cert), per-platform
SHA256, and a stamped version string via -ldflags.
- `publish`: aggregates SHA256SUMS, stages the install scripts,
creates the GitHub Release with auto-generated notes +
every artifact attached. prerelease=true when the tag
contains a hyphen (e.g. v0.1.0-rc1).
A third `bump-homebrew-tap` job is wired but gated `if: false`
until the tracebloc/homebrew-tap repo + HOMEBREW_TAP_TOKEN secret
are set up. The path is: flip the gate, secret is added,
formula gets updated automatically on each tag. RELEASE_CHECKLIST
documents the one-time setup.
### scripts/install.sh
POSIX-compatible (tested against dash, busybox sh, bash) — the
customer's distro might not have bash. Detects OS+arch,
resolves the latest tag via the /releases/latest redirect-trail
(no rate-limited API call), downloads binary + SHA256SUMS,
verifies the checksum, optionally verifies cosign signature if
cosign is on PATH, installs to /usr/local/bin (falls back to
~/.local/bin with PATH advice if not writable).
One-liner: `curl -fsSL https://github.com/tracebloc/cli/releases/latest/download/install.sh | sh`
### scripts/install.ps1
PowerShell 5.1+ (ships with Windows 10 21H1+). amd64 only for
v0.1; arm64 errors with a clear "file an issue" pointer.
Strict-mode + halt-on-error so half-installs don't happen.
Same SHA256 + cosign verification surface as install.sh.
Installs to $LOCALAPPDATA\Programs\tracebloc and PATH-adds at
user scope.
One-liner: `irm https://github.com/tracebloc/cli/releases/latest/download/install.ps1 | iex`
### scripts/homebrew-formula.rb.tmpl
Formula template the bump-homebrew-tap job renders per release.
Substitutes __VERSION__, __TAG__, and four per-platform
__SHA_*__ placeholders via sed (no Ruby in the runner needed).
Ships pre-built binaries rather than building from source — the
k8s.io transitive dep tree is too heavy to build on customer
laptops.
### scripts/RELEASE_CHECKLIST.md
Per-release on-call doc. Distinguishes "what's automated"
(everything in the workflow) from "what needs one-time setup"
(homebrew-tap repo, eventually install.tracebloc.io DNS) from
"what's manual per release" (the actual tag push + sanity-test
of each install path).
## Hosting choice
GitHub Release raw URLs (per user decision). Zero infrastructure
needed; the bootstrap URL works the day v0.1.0 ships. install.
tracebloc.io is a v0.2 follow-up via CNAME / Cloudflare worker.
## Out of this PR
- Creating the tracebloc/homebrew-tap repo (separate; documented
in RELEASE_CHECKLIST.md)
- DNS for install.tracebloc.io (v0.2)
- Tagging v0.1.0 (post-merge, after the Phase 6 dogfood)
Local: vet, test -race -cover, gofmt -s, errcheck — all green.
After this merges, the v0.1 epic (#147) is mechanically complete
— `tag v0.1.0` produces the full release pipeline output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
👋 Heads-up — Code review queue is at 20 / 8 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
Bugbot PR #11 round 1: the previous flow printed "✓ checksum matches" unconditionally after the verify block, even when neither sha256sum nor shasum was on PATH (the no-tool branch set actual="" and the mismatch check guarded on -n "$actual", so it was silently skipped). Two failures in one: 1. Misleading log: claimed verification succeeded when it didn't run. 2. Security regression: an attacker could MITM the binary and the customer would never notice on a tool-missing host. Fix: refuse to install when no SHA256 tool is available. Error message lists the per-distro install commands so the customer knows the fix. Trade-off: a "warn-but-continue" mode would be friendlier on exotic hosts, but the whole point of this script is to verify a binary the customer pulls off the internet. "Friendly" defaults that skip the security check are exactly the install-script anti-pattern that's bitten the industry repeatedly. Hard error is the right call. `sh -n` syntax check clean.
Two Bugbot PR #11 r2 findings, both real: ## Medium — install.ps1: failed cosign verify treated as "skip" The previous flow's try-block contained BOTH the .sig/.cert download AND the cosign verify + Write-Error. With $ErrorActionPreference = 'Stop', Write-Error throws an exception caught by the SAME catch that handled missing-sig downloads — so a verified-failed binary went through the "couldn't download .sig/.cert — release may pre-date signing" branch and continued to install. Fix: separate the two concerns. Download in a try/catch (failures = "predates signing, skip"). Verify OUTSIDE the try via & cosign (external process, doesn't interact with $ErrorActionPreference); $LASTEXITCODE check + Write-Host + explicit exit 1 if non-zero. A failed signature now correctly refuses to install. ## Medium — install.sh: custom --prefix silently ignored if missing POSIX `test -w` returns false for nonexistent paths. The previous flow used `[ ! -w "$PREFIX" ]` to decide whether to fall back to ~/.local/bin — but that meant a legitimate `--prefix /opt/tracebloc` (a dir that doesn't exist yet) silently triggered the fallback, overriding the customer's explicit choice. Fix: try `mkdir -p "$PREFIX"` first; if THAT succeeds AND the resulting dir is -w, use it. Only fall back if mkdir fails (no write perms on parent) OR the existing dir is unwriteable. The dominant default-prefix case (`/usr/local/bin` without sudo) still routes to ~/.local/bin; custom prefixes get respected. Local: `sh -n scripts/install.sh` clean; full Go test suite + gofmt + errcheck green.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e5639d6. Configure here.
…bot r3) Two more PowerShell-specific Bugbot findings on PR #11: ## Medium — Write-Error + exit 1 = dead exit, ugly UX With $ErrorActionPreference = 'Stop', every Write-Error call throws a terminating error BEFORE any subsequent statement runs, so the `exit 1` lines after them were dead code. The result: a customer hitting an error saw PowerShell's full error record (stack trace + script line numbers + ErrorCategoryInfo) instead of a clean one-line message. The cosign branch was fixed in r2 with Write-Host + explicit exit. The earlier error paths (Get-Arch, Resolve-Tag, SHA256 mismatch, missing SHA256SUMS entry) still used the broken Write-Error pattern. Fix: add a Fail helper that does `Write-Host "Error: $msg" -ForegroundColor Red; exit 1` and replace every Write-Error call site with `Fail "..."`. DRY + consistent UX. ## Medium-Security — Null user PATH = leading semicolon = PATH-injection GetEnvironmentVariable('Path', 'User') returns $null on fresh Windows installs (or accounts that never set a user-scope PATH). The naive `"$userPath;$InstallPrefix"` interpolation then produced `";C:\Users\...\Programs\tracebloc"` — a leading semicolon = empty PATH entry, which Windows resolves as the CURRENT WORKING DIRECTORY. That's a well-known PATH-injection vector: any binary planted in the user's cwd runs ahead of real PATH entries. Fix: null-guard $userPath before concatenation. If $userPath is falsy (null or empty), use just $InstallPrefix as the new value. The `$existingEntries -notcontains` check now also handles the null case correctly via the `if ($userPath) { ... } else { @() }` fallback. Local: go test green, gofmt + errcheck clean. This is r3 on PR #11 — the install scripts have surfaced the most findings because they're shell+PowerShell where bugbot's language-specific coverage is sharpest. Each finding has been real.
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
Closes the v0.1 epic (tracebloc/client#147). Phase 5 is the distribution infrastructure that makes the CLI installable without
go install. After this merges, taggingv0.1.0triggers the full release pipeline.What lands
.github/workflows/release.ymlbump-homebrew-tap) wired but gatedif: falseuntil the tap repo +HOMEBREW_TAP_TOKENare set up.scripts/install.sh/usr/local/bininstall with$HOME/.local/binfallback.scripts/install.ps1scripts/homebrew-formula.rb.tmplbottle-style formula; release workflowsed-renders it per tag. Ships pre-built binaries (k8s.io dep tree is too heavy for source builds on customer laptops).scripts/RELEASE_CHECKLIST.mdInstall one-liners (post v0.1.0 tag)
Hosting choice
GitHub Release raw URLs (per pre-build user decision). Zero infrastructure setup; the bootstrap URL works the day v0.1.0 ships.
install.tracebloc.iovanity URL is documented as a v0.2 follow-up.Verification surface
Each binary is signed with cosign keyless OIDC against this workflow's identity. install.sh / install.ps1 download
.sig+.certand verify if cosign is on PATH. SHA256SUMS is the unconditional baseline check.cosign verify-blob \ --certificate-identity-regexp \ 'https://github.com/tracebloc/cli/.github/workflows/release.yml@.*' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ --certificate tracebloc-v0.1.0-linux-amd64.cert \ --signature tracebloc-v0.1.0-linux-amd64.sig \ tracebloc-v0.1.0-linux-amd64Out of this PR (separate setup, documented in RELEASE_CHECKLIST.md)
tracebloc/homebrew-taprepoHOMEBREW_TAP_TOKENsecret + flippingbump-homebrew-tap'sif:gateinstall.tracebloc.ioDNS (v0.2 follow-up)v0.1.0itself (post-merge, after Phase 6 dogfood)Test plan
go test -race ./...— greengofmt -s -l .— no drifterrcheck ./...— greeninstall.shsyntax check:sh -n scripts/install.shcleanv0.1.0-rc1tag, verify all 5 binaries + sigs + checksums attach to the Releaseinstall.shworks against the rc release on a clean Linux + macOS hostinstall.ps1works against the rc release on a clean Windows hostAfter this merges, the v0.1 epic (#147) is mechanically complete — a
git tag v0.1.0 && git pushproduces the full distribution.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Note
Medium Risk
New supply-chain surface (release workflow, install scripts, signing); mistakes could ship bad binaries or weaken verification, but changes are CI/scripts only and don't alter runtime CLI logic.
Overview
Adds tag-driven distribution so customers can install the CLI without
go install. Pushing av*.*.*tag (or manualworkflow_dispatch) runs a new Release workflow: five-way cross-compile, cosign keyless.sig/.certper binary, aggregated SHA256SUMS, and a GitHub Release that bundles binaries, checksums, andinstall.sh/install.ps1atreleases/latest/download/. Prerelease is set when the tag contains-.New POSIX
install.shand PowerShellinstall.ps1resolve latest (or pinned) release, verify SHA256 (refuse install without a hasher), optionally verify cosign against the workflow identity, then install with sane prefix/PATH behavior. A Homebrew formula template plus abump-homebrew-tapjob (currentlyif: false) render per-platform SHAs and push totracebloc/homebrew-taponce secrets exist.RELEASE_CHECKLIST.mddocuments automated vs one-time vs per-release steps.Reviewed by Cursor Bugbot for commit 0d1aaf8. Bugbot is set up for automated code reviews on this repo. Configure here.