feat: release with GoReleaser and streamline installation - #2
Open
cardoe wants to merge 4 commits into
Open
Conversation
Audit of understackctl as a standalone CLI, covering migration blockers, cross-platform distribution and installation, platform correctness, CLI usability, code quality and repo hygiene. Intended to be triaged into issues and then removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tr9ob3LqrbsZL2rpJehEr3
Replaces the hand-rolled Makefile release path with GoReleaser and makes installing understackctl a one-liner on Linux, macOS and Windows. Distribution: - .goreleaser.yaml builds linux/darwin/windows on amd64/arm64, plus a darwin_all universal binary so macOS users cannot pick the wrong archive. Drops the 386 targets nobody runs. - Archives now hold the binary at their root alongside LICENSE, README and generated shell completions. Previously `tar xzf` produced a directory and shipped no license. - Adds -trimpath and a commit-pinned mod_timestamp for reproducible builds. - Homebrew cask and Scoop manifest are generated but not published (skip_upload: true) until rackerlabs/homebrew-tap and rackerlabs/scoop-bucket exist; the enablement checklist is inline. Installation: - install.sh detects OS/arch, verifies the SHA-256 checksum before installing, picks a writable install dir, and warns when it is not on PATH. BASE_URL points it at an internal mirror for restricted-egress environments. - README documents every install path, checksum and provenance verification, shell completion setup, and the external tools each command group needs. Version reporting: - New internal/version resolves the version from linker flags, falling back to debug.ReadBuildInfo so `go install ...@v0.1.0` no longer reports "dev". Normalizes the v prefix that GoReleaser's .Version strips so all install methods report the same string. - New `understackctl version [-o json]` for bug reports; --version keeps working. CI: - Release workflow uses goreleaser-action and attests build provenance, so archives can be checked with `gh attestation verify`. It accepts pre-release tags, which prerelease: auto keeps from being marked latest, and no longer apt-installs sed and grep. - New Release Build workflow validates the config, cross-compiles every published target and asserts the archive layout on each pull request, so release breakage surfaces before tagging. - Go Tests now runs on Linux, macOS and Windows rather than Linux alone. Also reduces the Makefile to a developer wrapper, stops .editorconfig from imposing spaces on Go sources, and gofmts the two files that drift had already broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tr9ob3LqrbsZL2rpJehEr3
As of v4, actions/attest-build-provenance is a thin wrapper over actions/attest and its README directs new implementations at the latter. Switches to actions/attest and feeds it dist/checksums.txt via subject-checksums, which attests every published archive in one step instead of globbing paths. Default behaviour is still SLSA build provenance, so `gh attestation verify` works unchanged. artifact-metadata: write is deliberately not granted; it is only needed for the storage record, which requires push-to-registry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tr9ob3LqrbsZL2rpJehEr3
golangci-lint's errcheck flagged the text branch of `understackctl version`, which discarded the fmt.Fprint error while the json branch already checked its encoder error. Reproduced with the pinned golangci-lint v2.1.2 and confirmed clean after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tr9ob3LqrbsZL2rpJehEr3
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.
Now that
understackctllives in its own repository, this replaces the hand-rolledMakefilerelease path with GoReleaser and makes installing the binary a one-liner on Linux, macOS and Windows.Why
The
Makefilereimplemented cross-compilation, per-OS archive formats and checksums in shell, and the result had some rough edges for anyone installing a release:understackctl_linux_amd64/understackctl), sotar xzfhanded you a folder rather than something runnable, and shipped noLICENSE.-trimpath, so absolute build paths were baked into every binary.go installbuilds reportedversion dev (unknown), which is useless in a bug report.brewpath and had to know their own CPU architecture.id-token: writewithout using it, andapt-get installedsedandgrep, which are already on every runner.What's in it
Distribution —
.goreleaser.yamlbuilds linux/darwin/windows on amd64/arm64, drops the386targets, and adds adarwin_alluniversal binary so macOS users cannot pick the wrong archive. Archives now hold the binary at their root alongsideLICENSE,README.mdand generated shell completions.-trimpathplus a commit-pinnedmod_timestampmake builds reproducible.Installation —
install.shdetects OS/arch, verifies the SHA-256 checksum before installing, picks a writable install directory, and warns when it is not onPATH.BASE_URLpoints it at an internal mirror for restricted-egress environments. The README now documents every install path, checksum and provenance verification, completion setup per shell, and which external tools each command group needs.Version reporting — new
internal/versionresolves the version from linker flags, falling back todebug.ReadBuildInfo()sogo install ...@v0.1.0reports a real version. It also normalises thevprefix that GoReleaser's.Versionstrips, so every install method reports the same string. Addsunderstackctl version [-o json];--versionkeeps working.CI — the release workflow uses
goreleaser-actionand attests build provenance viaactions/attest, so archives can be checked withgh attestation verify. It accepts pre-release tags (v0.1.0-rc.1), whichprerelease: autokeeps from being marked "latest". A newRelease Buildworkflow validates the config, cross-compiles every published target and asserts the archive layout on each pull request.Go Testsnow runs on Linux, macOS and Windows — we publish binaries for three platforms, and testing on one made that an aspirational claim.Also reduces the
Makefileto a developer wrapper (make build|test|lint|fmt-check|snapshot|check), stops.editorconfigfrom imposing spaces on Go sources, and gofmts the two files that drift had already broken.Verification
The full pipeline was run locally with GoReleaser v2.18.1 — 6 cross-compiled targets, universal binary, 7 archives, checksums, Homebrew cask and Scoop manifest, all inspected.
install.shwas exercised end-to-end against those artifacts over a local HTTP server, including the failure paths:BASE_URLwithoutVERSIONchecksums.txtEvery pinned action SHA was resolved with
git ls-remoterather than written from memory. That turned up two things worth noting:goreleaser-actionis on v7 (inputs unchanged, so the bump is safe), andattest-build-provenancev4 is now a thin wrapper whose README directs new implementations atactions/attest— so this usesactions/attestwithsubject-checksums: dist/checksums.txt, attesting every artifact in one step.artifact-metadata: writeis deliberately not granted; it is only needed for the storage record, which requirespush-to-registry.go vet,go test ./...,make fmt-checkandgoreleaser checkare all clean.Action needed before this helps macOS and Windows users
The Homebrew cask and Scoop manifest are generated but not published (
skip_upload: true), because publishing needs repositories that do not exist yet. A release cannot break on missing infrastructure, but thebrewandscoopinstall paths stay unavailable until someone:rackerlabs/homebrew-tapandrackerlabs/scoop-bucket.contents: writeon those repos asHOMEBREW_TAP_TOKEN/SCOOP_BUCKET_TOKEN— the defaultGITHUB_TOKENcannot push to another repository.env:lines in.github/workflows/release.yaml.skip_upload: false.The full checklist is inline in
.goreleaser.yaml. The README marks both paths as not-yet-available so nobody follows instructions that cannot work.Per the earlier discussion, the first standalone tag is intended to be
v0.1.0. Linux.deb/.rpmpackages were deliberately skipped, as were man pages (they would pull ingo-md2man).Also included:
AUDIT.mdThe first commit adds the repo-split audit that prompted this work. It is a working document meant to be triaged into issues and then deleted, not permanent documentation — happy to drop it from this PR if you would rather it lived somewhere else.
Its remaining findings are not addressed here. The most important one is that
device-type validate|addandflavor validate|addresolve their JSON schemas via relative paths that only exist inside the monorepo checkout, so those four commands cannot work from any binary this pipeline produces. That plus theKUBECONFIGhandling and the password-rotation bug inloadOrGenSecretare the natural next PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01Tr9ob3LqrbsZL2rpJehEr3
Generated by Claude Code