DocsWorking on Yarrix

Releasing (maintainers)

View source on GitHubdocs/RELEASING.md

On this page

Releases are built, signed and published from a maintainer's own machine. No GitHub runner builds or signs anything. One command does it:

MINISIGN_SECRET_KEY=/path/outside/the/repo/yarrix.key scripts/release/cut.sh 0.1.0

<version> is semver without the v (the tag is v0.1.0); a version with a - (0.1.0-rc.1) is a prerelease.

One-time setup#

  • The signing key. The public half is minisign.pub in the repo. Keep the secret key file outside the repository (cut.sh refuses a key inside it) and point MINISIGN_SECRET_KEY at it. If it has a password, set MINISIGN_PASSWORD or let minisign prompt. Never commit a key.
  • gh. gh auth login as someone who can create releases on the repository. A dry run needs no gh.
  • The repository. cut.sh uses origin's owner/name. Override with --repo owner/name or YARRIX_GH_REPO. Images go to ghcr.io/<owner lowercased>/yarrix and .../yarrix-agent (YARRIX_IMAGE_NAMESPACE overrides); for them run docker login ghcr.io first.
  • Tools on the build machine: bun 1.4.4, Rust through rustup with the targets aarch64-apple-darwin, x86_64-apple-darwin, x86_64-pc-windows-gnu (and mingw-w64), Docker with buildx, zip, makensis, minisign, jq, python3, cargo-deny, gitleaks. See the header of scripts/release/dry-run.sh.
  • makensis builds the Windows setup.exe (brew install makensis; 3.13 on macOS 27 is tested). It must run under a UTF-8 locale: under the C locale the Homebrew build aborts with std::bad_alloc (exit 134) on a Unicode installer. package.sh picks a UTF-8 locale the machine has (C.UTF-8 or en_US.UTF-8) and fails unless makensis reports writing a Unicode installer. YARRIX_NSIS_ANSI=1 builds the ANSI installer for a local check only; cut.sh refuses to publish it, so unset it before a release.

What cut.sh does, in order#

  1. Refuses unless the working tree is clean and you are on main at the same commit as origin/main, and the tag does not exist yet.
  2. Runs scripts/ci/local.sh --strict, which includes scripts/gen-third-party-notices.ts --check, so a stale THIRD_PARTY_NOTICES.md stops the release. A skipped stage fails it. --allow-skips runs it without --strict and prints every SKIP it accepted, so you can see what was not checked.
  3. Builds every artifact with scripts/release/dry-run.sh v<version> (hub binaries, agents, Windows zip and setup.exe, checksums, SBOM), stamped with the version and trusting the public key.
  4. Checks that LICENSE, NOTICE and THIRD_PARTY_NOTICES.md are in the release files and inside the Windows zip. (Both Docker images carry them in /licenses.)
  5. Writes release notes from changes/unreleased/*.json (scripts/release/changelog-release.py, run on a scratch copy, so the working tree stays clean).
  6. Signs MANIFEST.json and SHA256SUMS with scripts/release/sign-release.sh --local, which first re-checks every file against both, then verifies the signatures against the public key.
  7. Tags v<version>, pushes the tag, and runs gh release create --draft with every file. The release stays a draft.
  8. With --push-images, builds and pushes the two multi-arch images. Without it nothing is pushed to a registry (binaries only).

--dry-run stops before step 7: no tag, no gh, no push (with --push-images the images are built and not pushed). A git state a real run would refuse is printed as WOULD REFUSE instead of stopping. --skip-checks (dry run only) skips step 2. Artifacts land in dist/release/v<version>/, the files to upload in dist/release/v<version>.upload/, and the notes in dist/release/v<version>.notes.md.

After cut.sh#

  1. Open the draft on GitHub, read the notes, and check the files. Verify a download as in verify-release.md.
  2. Publish the draft by hand.
  3. Consume the change entries so the next release does not repeat them: python3 scripts/release/changelog-release.py <version>, then commit changelog.json and the removed changes/unreleased/*.json to main.

If something fails#

Nothing before step 7 changes the repository or the hosting side, so rerun cut.sh after fixing the cause. If step 7 fails after the tag was pushed, delete the tag (git push origin :refs/tags/v<version>, git tag -d v<version>) and any partial draft before retrying.