Repository navigation
feat: install scripts, Linux packages, a container image and automated releases - #2
Merged
Merged
Conversation
Alex (Alexkuva)
self-requested a review
October 1, 2026 11:12
Alex (Alexkuva)
previously approved these changes
Oct 1, 2026
backtrack5r3 (tomflenner)
dismissed
Alex (Alexkuva)’s stale review
via
October 3, 2026 09:22
242c448
…r image install/install.sh (Linux, macOS) and install/install.ps1 (Windows) fetch the release archive for the host's platform, verify it against the release's checksums.txt and install the binary; both take a version and an install directory, and the shell script uses sudo only when the target needs it. GoReleaser now also produces .deb, .rpm and .apk packages with shell completions, a Homebrew cask and a Scoop manifest for kaitencloud/homebrew-tap and kaitencloud/scoop-bucket, and a multi-platform ghcr.io/kaitencloud/cli image on a distroless base. Package and archive names carry no version, so releases/latest/download/<asset> is a stable URL for every one of them. The cask and the manifest are pushed only when HOMEBREW_TAP_GITHUB_TOKEN is set and the tag is not a pre-release; otherwise they are left in dist/. CI validates the GoReleaser configuration and both install scripts on every pull request, and `task release:snapshot` builds the whole release locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
The tap does not exist yet, so the cask could not be published anyway. The token the release passes for package-manager repositories is now named for its one remaining user, SCOOP_BUCKET_GITHUB_TOKEN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
Same reason as the Homebrew cask: the bucket does not exist yet. With it goes the only token the release workflow needed beyond GITHUB_TOKEN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
Every merge to main now runs release-please, which keeps one "chore: release X.Y.Z" pull request open with the next version, computed from the Conventional Commit messages since the last release, and the CHANGELOG.md entry. Merging it tags vX.Y.Z and creates the GitHub release; the goreleaser job in the same workflow then builds and attaches the archives, packages and image. Nobody pushes a tag by hand any more, so the tag trigger goes. release-please acts as a GitHub App (RELEASE_APP_ID, RELEASE_APP_PRIVATE_KEY): a pull request opened with GITHUB_TOKEN starts no workflow, and the release pull request has to pass the required checks like any other. GoReleaser keeps using GITHUB_TOKEN. GoReleaser appends its install footer to the notes release-please wrote instead of generating a second changelog. Every archive, package and the image get a build-provenance attestation. workflow_dispatch rebuilds an existing tag if the goreleaser job fails after tagging. Pre-1.0, feat and breaking changes bump the minor, fix the patch; the first release is 0.1.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
release-please reads the commits that land on main, and a squash merge makes the pull request title the commit message. The PR title check runs on pull_request_target, like the DCO check, and checks no code out. CONTRIBUTING.md and the pull request template say what each type releases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
README's Releasing section covers the release pull request, how commit types map to versions, re-running a failed publish, and the maintainer setup: the GitHub App, making the GHCR package public, keeping immutable releases off. task release:check now parses install.ps1 as CI does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: tomflenner <flennertom@gmail.com>
backtrack5r3 (tomflenner)
force-pushed
the
feat/install-and-packaging
branch
from
October 13, 2026 20:55
f51b27a to
dba11e7
Compare
7 tasks done
fuzcap
approved these changes
Nov 9, 2026
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.
What does this PR change?
Everything needed to install
kaitenwith one command on each platform, and to package every release:install/install.sh(Linux, macOS) andinstall/install.ps1(Windows): detect OS and architecture, download the release archive, verify its SHA-256 against the release'schecksums.txt, install the binary. Both accept a version and an install directory; the shell script usessudoonly when the target directory actually needs it, and the PowerShell one installs under%LOCALAPPDATA%\Programs\kaitenand adds it to the userPATH, no administrator rights..goreleaser.yaml) now also produces:.deb,.rpmand.apkpackages (binary, completions,LICENSEandNOTICEin each format's convention); a multi-platformghcr.io/kaitencloud/cliimage on a distroless non-root base. The archives now carry shell completions too.kaiten_<os>_<arch>.*without the version, soreleases/latest/download/<asset>is a stable URL for all twelve of them. The install scripts rely on that.release.ymlnow runs on every push tomain: release-please keeps onechore: release X.Y.ZPR open with the next semver version (computed from Conventional Commits) and theCHANGELOG.mdentry. Merging that PR tagsvX.Y.Zand creates the GitHub release, and thegoreleaserjob in the same run attaches the archives, packages and the GHCR image, plus SLSA build-provenance attestations. The hand-pushed tag trigger is gone;workflow_dispatchre-publishes an existing tag if GoReleaser fails after tagging. Pre-1.0:feat/breaking → minor,fix→ patch, first release0.1.0.pr-title.yml: a PR title check (Conventional Commits) onpull_request_target, like DCO, since the squash-merge title is what release-please reads. CONTRIBUTING and the PR template say what each type releases.ci.yml(lintjob):goreleaser check,shellcheckon the shell installer and a PowerShell parse of the Windows one, so a broken installer or release config fails the PR rather than the tag.Taskfile.yml:task release:snapshotbuilds the whole release intodist/without a tag;task release:checkruns the same checks as CI (GoReleaser, shellcheck, PowerShell parse).Before merging: one-time setup
kaitencloud/cliwith Contents, Pull requests and Issues read/write. Put its ID in the Actions variableRELEASE_APP_IDand its private key in the Actions secretRELEASE_APP_PRIVATE_KEY. Without it the release PR would be opened byGITHUB_TOKEN, which starts no workflow, so the required checks would never run on it.cliGHCR package public. Keep immutable releases off (GoReleaser attaches assets to the release release-please already published).Recommended, not required: add
PR titleto the ruleset's required checks, restrict merges to squash, and add a tag ruleset onv*that only the release App can create.Modeled on dapr/cli (per-OS install scripts, "latest release" resolution) and open-feature/cli (container image, checksum verification in the script).
Why?
A public CLI is only as usable as its install path. Until now the options were
go installor unpacking an archive by hand.Testing
goreleaser check(v2.12.7, the pinned version): clean, no deprecation warnings (usesdockers_v2, not the phased-outdockers).goreleaser release --snapshotlocally: all six binaries, archives,.deb/.rpm/.apkand both container images build. Verified withdpkg -c,rpm -qlp/rpm -qLpandtar -tthat every package carries the binary, the completions,LICENSEandNOTICE; ran the arm64 image.install/install.shagainst the snapshot served over local HTTP: installs and prints the version; creates a missing nested install directory as the user, not root; refuses a tampered checksum and a missing checksum entry without writing anything; builds the right GitHub URL for a pinned version.install/install.ps1underpwshon macOS against the same server: downloads, verifies, extracts and placeskaiten.exe; refuses a tampered checksum before extracting; rejects an unsupported architecture. (Running the.exeis the one step that cannot be exercised off Windows.)shellcheck --shell=sh, PowerShell parser,go build,go vet,go test, golangci-lint: all clean.mainand the release-please changes:task release:checkpasses (and fails on a deliberately brokeninstall.ps1);goreleaser release --snapshot --skip=publish,dockerbuilds all 12 assets withchangelog.disableandrelease.mode: append; actionlint is clean onrelease.ymlandpr-title.yml. release-please itself can only run once this is onmain: the first push opens thechore: release 0.1.0PR.Not in this PR
kaitencloud/homebrew-tapnorkaitencloud/scoop-bucketexists yet. The removed blocks are in commite417a77if they come back. Publishing still needs nothing beyondGITHUB_TOKEN; only release-please uses the App token.releases/latest/download/…returns 404 until the first release PR is merged.Checklist
CONTRIBUTING.md.Signed-off-byline.🤖 Generated with Claude Code