Chapter 7
Releasing
How a version of Knode is cut, built, signed, notarised and published, so every release is done the same way. The installs it produces are described in decision 0007.
One version
The knode binary (the CLI, the MCP server and Knode Service) and the apps share one version.
It lives in one place, version under [workspace.package] in knode/core/Cargo.toml, and the apps
read the same number.
Versions follow semantic versioning: MAJOR.MINOR.PATCH.
- Before 1.0, the minor number is the milestone:
0.1.0is v0.1,0.2.0is v0.2. Anything may still change between minor versions, including the.knodeformat and the MCP tools, and the release notes say how to move across. - Patch releases (
0.1.1) fix bugs and change nothing people rely on. - Pre-releases add a suffix:
0.2.0-beta.1,0.2.0-rc.1. They are published as pre-releases and Homebrew does not offer them. - From 1.0, a breaking change to the format, the MCP tools or the CLI means a new major version.
Changelog
CHANGELOG.md in the knode repository follows Keep a Changelog.
- Every pull request people would notice adds a line under
## [Unreleased], in the same pull request: what changed for them, not how it was built. - Lines go under
Added,Changed,Fixed,RemovedorSecurity. - When a version is cut,
## [Unreleased]becomes## [X.Y.Z] - YYYY-MM-DDand a new empty## [Unreleased]goes on top. - That section becomes the release notes. Notes are written for people: start with what is new and why it matters, then the details.
Tagging
A release is a tag on the default branch of knode: vX.Y.Z (or vX.Y.Z-beta.N).
-
Open a pull request
chore(release): X.Y.Zthat sets the version incore/Cargo.toml, updatescore/Cargo.lockand moves the changelog entries under the new version. -
When it is merged, tag that commit with an annotated tag and push the tag:
git tag -a vX.Y.Z -m "Knode X.Y.Z" git push origin vX.Y.Z
Tags are never moved or deleted once pushed. A broken release is fixed by a new patch version. There
are no release branches before 1.0; if an older version ever needs a fix, a release/X.Y branch is
cut from its tag.
The release workflow
Pushing a tag runs .github/workflows/release.yml in the knode repository:
-
Check the version. The tag must match the version in
core/Cargo.toml, or the release stops. -
Build the
knodebinary withcargo build --release --locked -p knode-cli:Target Runner Archive aarch64-apple-darwinmacOS, Apple silicon knode-vX.Y.Z-aarch64-apple-darwin.tar.gzx86_64-apple-darwinmacOS, Apple silicon (cross) knode-vX.Y.Z-x86_64-apple-darwin.tar.gzuniversal-apple-darwinboth Mac builds joined with lipoknode-vX.Y.Z-universal-apple-darwin.tar.gzx86_64-unknown-linux-gnuUbuntu 22.04, x86_64 knode-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gzaarch64-unknown-linux-gnuUbuntu 22.04, arm64 knode-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gzLinux builds run on the oldest supported Ubuntu so the binary works with older versions of glibc.
-
Sign and notarise the Mac binaries (see below).
-
Package each binary with
LICENSE,NOTICEandREADME.mdas a.tar.gz, with a.sha256file next to it. -
Draft the GitHub Release for the tag, with every archive, its
.sha256and oneSHA256SUMSfile for all of them. The notes come from the changelog section for that version.
The release stays a draft until a maintainer has checked it and publishes it by hand. Nothing is published automatically.
The Mac app joins the workflow when it ships: the app is archived with Xcode, signed, notarised,
stapled and attached as Knode-X.Y.Z.dmg, with its checksum in SHA256SUMS.
Signing and notarisation
Everything that runs on a Mac is signed with the project's Developer ID and notarised by Apple, so Gatekeeper opens it without warnings.
- The binary is signed with
codesign --options runtime --timestamp(the hardened runtime) and sent tonotarytool. A bare binary cannot be stapled; Gatekeeper checks its notarisation online. - The app is signed with a Developer ID Application certificate, including the
knodebinary it carries, notarised, and the ticket is stapled to the app and the.dmg, so it opens offline too. - Notarisation uses an App Store Connect API key, not a personal Apple ID password.
Signing material lives only in GitHub Actions secrets on the knode repository, never in a
repository, an issue or a log (chapter 4):
| Secret | What it holds |
|---|---|
MACOS_CERTIFICATE_P12 | The Developer ID Application certificate and its key, as a base64 .p12 |
MACOS_CERTIFICATE_PASSWORD | The password of that .p12 |
MACOS_SIGNING_IDENTITY | The identity to sign with, for example Developer ID Application: Name (TEAMID) |
APPLE_API_KEY_P8 | The App Store Connect API key, as a base64 .p8 |
APPLE_API_KEY_ID | The key's id |
APPLE_API_ISSUER_ID | The issuer id of the App Store Connect team |
When the certificate secrets are missing, the workflow still builds and packages, skips signing and notarisation, and warns about it. Forks and test tags work this way; a real release never ships unsigned Mac builds.
The workflow imports the certificate into a temporary keychain that is deleted at the end of the job. If a certificate or key leaks, revoke it at Apple first, then replace the secret.
Homebrew
knodemd/homebrew-tap holds two files, both pointing at the release assets:
Formula/knode.rb: theknodebinary, from the per-platform archives (Apple silicon, Intel and Linux).Casks/knode.rb: the Mac app, fromKnode-X.Y.Z.dmg. It has nobinarystanza, so it never links a secondknodewhen the formula is installed.
After the release is published, a pull request to the tap sets version and every sha256 from
SHA256SUMS. Before it is merged, it is tested on a Mac:
brew tap knodemd/tap
cd "$(brew --repository knodemd/tap)" && gh pr checkout <number>
brew reinstall knode && brew test knode
brew audit --strict knode
brew reinstall --cask knode
The download on knode.md
knode.md links to the latest release's .dmg, and knode.md/install lists every install command.
https://knode.md/install.sh (from v0.2) downloads the right archive for the platform from the latest
release and checks it against SHA256SUMS before installing. A new install method changes the page and
decision 0007 together.
Checklist
Before the tag:
- The milestone's issues are closed or moved to the next one.
-
mainis green: the core checks and the app build pass. -
CHANGELOG.mdhas a section for the version, written for people. - The version is set in
core/Cargo.tomlandcore/Cargo.lock, merged through a pull request.
The release:
- Annotated tag
vX.Y.Zon the merged commit, pushed. - The release workflow passed and signed and notarised the Mac builds (no "not signed" warning).
- The draft release has every archive, every
.sha256,SHA256SUMSand the app's.dmg. - Download one archive, check it with
shasum -a 256 -c, and runknode --version. - On a Mac,
spctl --assessaccepts the app, and the app opens without a warning. - Notes reviewed, then the release is published.
After:
- Tap pull request with the new version and checksums, tested, merged.
-
brew upgrade knodeandbrew upgrade --cask knodeinstall the new version. - knode.md download and install page point at the new version.
- The milestone is closed and the release is announced.