4.0 KiB
Release Packaging
Releases are published by GitHub Actions. Pushing a v* tag is the whole
release procedure; the rest of this document describes what that automation
runs, and how to reproduce it by hand when needed.
Workflows
| Workflow | Trigger | Result |
|---|---|---|
ci.yml |
push to main, every pull request |
gofmt, go vet, go test on Linux and macOS, plus a cross-build of all five release targets |
release.yml |
push of a v* tag |
runs make release-check, then release.sh, then publishes the GitHub release |
nightly.yml |
push to main |
rebuilds the tip of main and replaces the nightly prerelease |
release.yml builds through release.sh rather than reimplementing packaging,
so a local run produces byte-identical archives.
Release notes
release.yml looks for docs/releases/<tag>.md. If that file exists it becomes
the release body; otherwise GitHub generates notes from commit history. Write
the file before pushing the tag when a release deserves a real description.
The nightly prerelease
nightly.yml force-moves a rolling nightly tag to the tip of main and
republishes a prerelease from it. It is marked prerelease deliberately, so
GitHub's Latest badge stays on the newest v* release.
Because that tag moves, version discovery in build.sh and release.sh is
pinned with --match 'v*'. Without the filter git describe would select
nightly and stamp binaries with it instead of v<last release>-<n>-g<sha>.
Keep the filter if you touch those scripts.
Create a Tag
Use a semantic version tag. Pushing it is what triggers release.yml:
git status --short
git tag -a v0.2.0 -m "sshkeeper v0.2.0"
git push origin v0.2.0
The remaining sections describe the manual equivalent, which is still the way to test packaging locally or to recover if Actions is unavailable.
The release script uses git describe --tags --match 'v*' --always --dirty by
default. The --match 'v*' filter matters: nightly builds move a nightly tag
across main, and without the filter git describe would pick that tag and
stamp binaries nightly instead of v<last release>-<n>-g<sha>. You can also
pass the version explicitly:
./release.sh v0.2.0
or:
VERSION=v0.2.0 ./release.sh
For reproducible archives, the script uses SOURCE_DATE_EPOCH. By default it
uses the timestamp of the latest git commit. To force a specific timestamp:
SOURCE_DATE_EPOCH=1760000000 ./release.sh v0.2.0
Run Release Checks
Before packaging, run:
make release-check
This runs:
go test ./...go vet ./...- native
go build linux/amd64cross-build withCGO_ENABLED=0linux/arm64cross-build withCGO_ENABLED=0darwin/amd64cross-build withCGO_ENABLED=0darwin/arm64cross-build withCGO_ENABLED=0windows/amd64cross-build withCGO_ENABLED=0
Build Artifacts
Run:
./release.sh v0.2.0
Expected files in dist/:
sshkeeper_v0.2.0_linux_amd64.tar.gz
sshkeeper_v0.2.0_linux_arm64.tar.gz
sshkeeper_v0.2.0_darwin_amd64.tar.gz
sshkeeper_v0.2.0_darwin_arm64.tar.gz
sshkeeper_v0.2.0_windows_amd64.zip
checksums.txt
Each archive contains:
sshkeeperorsshkeeper.exeREADME.mdLICENSEdocs/guide.md
Verify Checksums
From the dist/ directory:
sha256sum -c checksums.txt
Expected result: every archive reports OK.
Publish in GitHub Release
release.yml does this automatically on tag push. To publish by hand, upload:
- all five platform archives
checksums.txt
Release notes should mention platform status:
- Linux and macOS are primary release targets.
- macOS builds are available as tar.gz for amd64 and arm64 and require the
system
sshclient. - Windows build is experimental and requires OpenSSH Client available as
ssh.exeinPATH.
Packaging TODO
Prepare these package channels after the first archive-based release:
- deb package
- Arch PKGBUILD / AUR
- rpm later
- Homebrew tap
- Scoop manifest
- Winget later