Cutting a release

This is the maintainer-facing walkthrough for publishing a new GopherTrunk version. The release workflows (.github/workflows/release.yml, .github/workflows/installer.yml) do almost all of the heavy lifting — this doc covers what humans need to do before, during, and after.

Pre-release checklist

Run these on a clean checkout of main at the commit you intend to tag. Anything failing means the tag isn’t ready.

  • git status is clean and git pull --ff-only origin main is a no-op (you’re at the tip of main).
  • gofmt -l . prints nothing. CI gates this since v0.1.5; if it fails, run gofmt -w . and commit before tagging.
  • make lint (= go vet ./...) clean.
  • go test -race -count=1 ./... green. Slow but matches CI.
  • make integration green (the dispatch table of per-protocol control-channel integration tests; see Makefile).
  • make release-dry-run VERSION=vX.Y.Z produces a Linux binary under dist/dry-run/gophertrunk and gophertrunk version prints the expected version / commit / build time.
  • make version-refs-check passes (the same check CI runs as the version-refs job): CHANGELOG.md / README / docs fallback all still name the newest stable tag, so you are starting from a consistent state.
  • CHANGELOG.md [Unreleased] section covers every merged PR since the previous tag. Cross-check with: sh git log <prev-tag>..main --oneline Any PR mentioned in that log but not in the CHANGELOG needs a bullet before tagging.
  • No open issues blocking release on reference hardware (NESDR Smart v5, RTL-SDR Blog v3 / v4) — see hardware.md. If something’s broken on the reference hardware, document it in the release notes or hold the release.

Tagging

  1. Promote [Unreleased] and bump every hand-maintained version reference in one go:
    make release-prep VERSION=vX.Y.Z
    

    This runs scripts/release-refs.py bump, which renames ## [Unreleased] to ## [vX.Y.Z] — YYYY-MM-DD (inserting a fresh empty ## [Unreleased] above it, or a placeholder heading if nothing was recorded), and points README.md’s Quick Start VERSION= line and the docs site’s fallback tag (docs/_includes/latest-version.html) at the new version. It is idempotent and verifies itself. Commit and push:

    git add CHANGELOG.md README.md docs/_includes/latest-version.html
    git commit -m "release: vX.Y.Z"
    git push origin main
    

    If you skip this step, the release workflow’s version-refs job does the same bump after publishing and opens a release/refs-vX.Y.Z PR for it — merge that promptly, because the version-refs CI job is red on main until the references catch up with the tag. If the repository does not allow GitHub Actions to create pull requests, the job still pushes the branch and leaves a warning with a compare link in the run summary; open the PR from that link (the release run stays green).

  2. Create and push the annotated tag. Annotated (not lightweight) so git describe produces a useful version string inside the binary’s ldflags:
    git tag -a vX.Y.Z -m "vX.Y.Z"
    git push origin vX.Y.Z
    
  3. Watch the release workflow. The push triggers .github/workflows/release.yml, which:
    • builds gophertrunk.exe (amd64 + arm64) and the Windows installer (Zadig-bundled, Inno Setup),
    • builds Linux + macOS tarballs for amd64 and arm64,
    • builds all three web consoles (standard + Signal Lab + Config Builder) and stages them under gophertrunk-web/ alongside every artifact,
    • computes SHA256SUMS over every file,
    • attaches everything to a new GitHub Release whose body is the fixed per-platform install blurb from release.yml plus GitHub’s auto-generated notes (generate_release_notes: true, i.e. the merged-PR list since the previous tag). The release body does not read CHANGELOG.md, which is why step 1 above has to happen by hand.

    If any job fails, the release won’t publish. Fix and re-tag with a fresh patch number — do not delete-and-recreate a tag that ever went public, since GitHub’s tag deletion is not retroactive for clones.

Post-release

  • Merge the release/refs-vX.Y.Z PR if the workflow opened one (i.e. you tagged without running make release-prep). The README Quick Start VERSION= snippet and the docs-site fallback in docs/_includes/latest-version.html are covered by that step; downloads.md is fully ``-templated and needs nothing.
  • Sanity-check the published gophertrunk-<ver>-windows-amd64-setup.exe on a real Windows 11 machine — install, run Zadig from the Start Menu, run gophertrunk sdr list, uninstall and confirm the cleanup prompt. The Windows installer has the most moving parts (Inno Setup script + Zadig bundle + uninstaller cleanup), so smoke-testing it post-release catches regressions the headless CI can’t.
  • Announce in whatever channels the project uses.

Patch releases

If a fix needs to ship without all of main’s unreleased feature work:

  1. Branch off the previous tag: git checkout -b release/vX.Y vX.Y.0.
  2. Cherry-pick the fix commits onto the release branch.
  3. Bump the patch component in CHANGELOG.md and tag from the release branch (git tag -a vX.Y.Z release/vX.Y).
  4. Push the branch + tag.

Don’t tag patch releases off main if main contains unreleased feature work — the patch would silently ship every unreleased feature too.

Rolling back

If a published release has to be pulled:

  • Don’t delete the tag or the GitHub Release. Edit the release description to mark it superseded and point to the replacement version.
  • Tag a fresh patch release with the fix.
  • Update README.md and docs/downloads.md to the new patch.