| title | Distribution & Releases | ||
|---|---|---|---|
| description | goreleaser matrix, Homebrew tap, Scoop bucket, install script, signing, SBOMs, and CI release pipeline for the kupe CLI | ||
| owner | platform-team | ||
| lastReviewed | 2026-04-20 | ||
| sidebar |
|
The CLI is shipped as pre-built statically linked binaries via goreleaser, a Homebrew tap, a Scoop bucket, and a curl | sh install script. All releases are signed and ship with SBOMs. This doc is the reference for the release pipeline — anyone cutting a release should be able to run through it end-to-end from here.
Built per release by goreleaser:
| OS | Arch | Format |
|---|---|---|
| darwin | amd64 | tar.gz |
| darwin | arm64 | tar.gz |
| linux | amd64 | tar.gz |
| linux | arm64 | tar.gz |
| windows | amd64 | zip |
| windows | arm64 | zip |
Optional (Phase 2): .deb / .rpm via nfpms, published to pkg.kupe.cloud as simple apt/yum repos.
main imports internal/build:
// internal/build/info.go
package build
var (
Version = "dev"
Commit = "none"
Date = "unknown"
GoVersion = runtime.Version()
)Goreleaser's ldflags overrides at link time:
builds:
- main: ./cmd/kupe
binary: kupe
env: [CGO_ENABLED=0]
ldflags:
- -s -w
- -X github.com/kupecloud/kupe-cli/internal/build.Version={{.Version}}
- -X github.com/kupecloud/kupe-cli/internal/build.Commit={{.ShortCommit}}
- -X github.com/kupecloud/kupe-cli/internal/build.Date={{.CommitDate}}
goos: [darwin, linux, windows]
goarch: [amd64, arm64]kupe version -o json reads these values — see commands.md.
CGO_ENABLED=0 across all targets. The keyring library (zalando/go-keyring) uses OS-native binaries (security on macOS, dbus over secretstore on Linux, wincred on Windows) via subprocess or pure-Go syscalls — no C dependencies. This keeps cross-compiles clean and avoids libc versioning trouble on old Linux distros.
project_name: kupe # binary + Homebrew formula name (`brew install kupecloud/tap/kupe`)
before:
hooks:
- go mod vendor
- go test ./...
builds:
- id: kupe
main: ./cmd/kupe
binary: kupe
env: [CGO_ENABLED=0]
ldflags: [...] # see above
goos: [darwin, linux, windows]
goarch: [amd64, arm64]
archives:
- id: kupe
name_template: "kupe_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
format: zip
files:
- README.md
- LICENSE
checksum:
name_template: "kupe_{{ .Version }}_checksums.txt"
algorithm: sha256
signs:
- cmd: cosign
args: ["sign-blob", "--yes", "--output-signature=${signature}", "${artifact}"]
artifacts: checksum
sboms:
- artifacts: archive
documents:
- "{{ .ArtifactName }}.sbom.spdx.json"
brews: # NOT homebrew_casks — formula installs on macOS *and* Linux
- repository:
owner: kupecloud
name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
homepage: "https://kupe.cloud"
description: "Official CLI for Kupe Cloud"
license: "Apache-2.0"
commit_author:
name: kupecloud-bot
email: bot@kupe.cloud
install: |
bin.install "kupe"
generate_completions_from_executable(bin/"kupe", "completion")
test: |
assert_match "kupe version", shell_output("#{bin}/kupe version")
scoops:
- repository:
owner: kupecloud
name: scoop-bucket
token: "{{ .Env.SCOOP_BUCKET_TOKEN }}"
homepage: "https://kupe.cloud"
description: "Official CLI for Kupe Cloud"
license: Apache-2.0
commit_author:
name: kupecloud-bot
email: bot@kupe.cloud
release:
github:
owner: kupecloud
name: kupe-cli
mode: append # semantic-release creates the release; goreleaser only attaches artifacts
draft: false
prerelease: auto
footer: |
## Install
```bash
brew tap kupecloud/tap
brew install kupe
# or, one-liner:
curl -fsSL https://get.kupe.cloud | sh
```
changelog:
use: github
sort: asc
filters:
exclude:
- "^docs:"
- "^chore:"
- "^test:"
- "^ci:"
- "^style:"Cosign keyless signing on the checksums.txt:
cosign sign-blob --yes --output-signature kupe_0.1.0_checksums.txt.sig kupe_0.1.0_checksums.txt- Keyless — no long-lived private key. Cosign uses the Sigstore OIDC identity from the GitHub Actions token (
id-token: writepermission). Verification goes through Fulcio + Rekor transparency log. - What's signed — the
checksums.txt, not individual archives. An archive is trusted transitively: verify the checksum file is signed, verify the archive matches the listed hash.
Consumers verify with:
cosign verify-blob \
--certificate kupe_0.1.0_checksums.txt.pem \
--certificate-identity-regexp "^https://github.com/kupecloud/kupe-cli/\.github/workflows/publish\.yaml@refs/heads/main$" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature kupe_0.1.0_checksums.txt.sig \
kupe_0.1.0_checksums.txtDocumented on the release page and in docs-public install guide.
anchore/syft, integrated natively into goreleaser. One SPDX JSON per archive:
kupe_0.1.0_darwin_arm64.tar.gz.sbom.spdx.json
Published to GitHub Release alongside the archive. Vulnerability scanning can consume SBOMs via grype sbom:kupe_0.1.0_darwin_arm64.tar.gz.sbom.spdx.json.
Tap repo: kupecloud/homebrew-tap (needs creating; see Open items).
Goreleaser auto-pushes an updated formula on each release. Users install with:
brew install kupecloud/tap/kupe
# or
brew tap kupecloud/tap && brew install kupeUpgrades via brew upgrade kupe. No self-update logic in the CLI.
Not for v1. Separate darwin_amd64 and darwin_arm64 binaries. Users on Intel Macs get amd64; users on Apple Silicon get arm64. Rosetta works as a fallback.
Bucket repo: kupecloud/scoop-bucket.
scoop bucket add kupe https://github.com/kupecloud/scoop-bucket
scoop install kupe
scoop update kupeAuto-managed by goreleaser.
Hosted at https://get.kupe.cloud. Simple POSIX shell script:
- Detect
$OSTYPE→ darwin / linux. - Detect
uname -m→ amd64 / arm64. - Fetch
https://github.com/kupecloud/kupe-cli/releases/latest/download/kupe_<version>_<os>_<arch>.tar.gz. - Fetch
kupe_<version>_checksums.txtand verify. - Extract and move
kupeto~/.local/bin/kupeby default, or to--install-dir/$KUPE_INSTALL_DIRwhen set. The script usessudoonly when the target directory is not writable. chmod +xand print success.
curl -fsSL https://get.kupe.cloud | sh
curl -fsSL https://get.kupe.cloud | sh -s -- --version=0.1.0
curl -fsSL https://get.kupe.cloud | sh -s -- --install-dir /usr/local/binScript lives at scripts/install.sh. Cloudflare fronts get.kupe.cloud and serves the script with a 5-minute cache.
The script does not curl | sh into nested shells or pull additional scripts. Everything it needs is in the one downloaded binary. Users who don't trust the script can read it at https://get.kupe.cloud before running.
- Semver 2.0.0.
- v0.x — interface can change. Deprecated flags get a
stderrdeprecation warning for at least one minor version before removal. - v1.0 gate:
- CLI has parity with terraform-provider-kupe v1.
- TUI has shipped.
kupe-apihas committed to a tagged stable version with an endpoint-stability SLA.
- Patch releases fix bugs only — no new flags or commands.
- Minor releases add commands, flags, and non-breaking output additions.
- Major releases break
-o jsonschemas or remove commands/flags.
Git tags prefixed v: v0.1.0, v0.2.0-rc.1, v1.0.0. Goreleaser strips the v for archive names.
Auto-generated from commit messages by goreleaser (changelog.use: github). Commit messages follow a light convention:
feat: add kupe cluster wait command
fix: restore --wait=false default for cluster delete
docs: document exec-plugin kubeconfig mode
chore: bump go to 1.26.3
feat: and fix: show up in release notes; docs:/chore:/test: are filtered out.
- Checkout, setup-go 1.26.3, vendor, lint, test, gosec, govulncheck, snapshot build.
- Cancels in-progress runs on subsequent pushes to the same PR.
- Triggers on
push: tags: ["v*"]. - Permissions:
contents: write,packages: write,id-token: write(for Cosign keyless). - Steps: checkout with full history (for changelog), setup-go 1.26.3, setup-cosign, setup-syft,
goreleaser release --clean. - Secrets required:
GITHUB_TOKEN(default).HOMEBREW_TAP_TOKEN— PAT withrepoonkupecloud/homebrew-tap.SCOOP_BUCKET_TOKEN— PAT withrepoonkupecloud/scoop-bucket.
- Duration target: under 10 minutes.
- Validates frontmatter shape (required keys present).
- Checks relative links resolve inside the repo.
- Warns on
lastReviewedolder than 6 months.
Goreleaser writes buildDate from the commit author date ({{ .CommitDate }}), not wall-clock time. Combined with fixed Go version, vendored deps, and -trimpath in ldflags, two builds from the same commit produce byte-identical binaries.
Verifiable by checking out a release tag and re-running goreleaser locally — the resulting dist/kupe_<ver>_<os>_<arch>.tar.gz should match the published archive's sha256 in kupe_<ver>_checksums.txt. (No dedicated make target yet; if reproducibility ever needs CI enforcement, add one then.)
Documented in README and docs-public. Steps:
brew uninstall kupeorscoop uninstall kupe(orrm ~/.local/bin/kupe/rm /usr/local/bin/kupefor install-script users).rm -rf ~/.config/kupe/.- On macOS:
security delete-generic-password -s cloud.kupe.cli -a <context>per context. - On Linux:
secret-tool clear service cloud.kupe.cli account <context>per context. - On Windows: remove the
cloud.kupe.cliCredential Manager entry for each context.
No automated uninstaller in v1 — Homebrew/Scoop handle the binary; users own the config and secrets.
kupecloudGitHub org tap repos. Confirmkupecloud/homebrew-tapandkupecloud/scoop-bucketexist (or create them) before the first release.get.kupe.cloudinfrastructure. Cloudflare page or a static bucket behind Cloudflare. Infrastructure ticket separate.- Deb/RPM repos.
pkg.kupe.cloudis not scoped for v1. Add vianfpms+ a simple apt/yum repo when demand justifies it. - Windows signing. Long-term we want an Authenticode-signed binary. Deferred — the Cosign signature on the checksum file is enough for initial users. Windows Defender may warn on first run; document it in the install guide.
- ARM Mac universal binaries. Not scoped. Re-evaluate after first release telemetry shows install distribution (if we ever turn on telemetry).