Releasing rickub
This repository ships three CI workflows, all running on rickub's own
GitHub-Actions-compatible CI:
| Workflow | Trigger | What it does |
|---|---|---|
.github/workflows/ci.yml |
every pull request, and pushes to main |
gofmt check, go vet, go test, go build |
.github/workflows/rc.yml |
every push to main |
tests, cross-compiles all four targets, publishes a prerelease v<BASE>-rc.<run_number> |
.github/workflows/release.yml |
manual only (workflow_dispatch) |
tests, cross-compiles, publishes a real release v<version> |
Both release-producing workflows call the same script,
scripts/build-dist.sh, so an RC and a real release
are byte-for-byte the same pipeline with a different version stamp.
How the pipeline reaches the release API
The workflows are written as ordinary GitHub Actions jobs — actions/checkout@v4,
actions/setup-go@v5, actions/upload-artifact@v4,
softprops/action-gh-release@v2 — and rickub's CI is expected to run them as
such. Two platform facts matter when a run fails before or at publishing:
Fetching uses: actions. rickub's runner fetches every action from
github.com. A @vN tag is resolved with a git ls-remote against github.com;
when that call is anonymous, GitHub rate-limits it per datacenter IP and the run
dies in "Set up Go" with could not read Username for 'https://github.com'.
That is a fleet configuration matter, not a workflow one: the runner
authenticates those fetches when RICKUB_ACTIONS_GITHUB_TOKEN is set
(web/actions/runtime/stage.go in the rickub server). Do not work around it by
pinning SHAs here — the point of this repository's workflows is to be a normal
job.
Publishing goes through the node's CI proxy. The CI guest reaches the
control plane only through the results proxy (web/ciproxy in the rickub
server), and $GITHUB_API_URL — what softprops/action-gh-release talks to —
points at that proxy with the /gh REST shim behind it. The proxy must forward
the /gh/ prefix: shipped in rickub PR
ogirardot/rickub#3, active on a
node once its rickub-fleetd has been rebuilt from that change. On a node
running an older fleetd, both workflows build, test and upload their
actions/upload-artifact bundle, then fail on the "Publish …" step with a
403; the archives are still downloadable from the run's artifacts, so a
release can be published by hand in the meantime.
You can rehearse the whole pipeline without publishing by dispatching
release.yml with dry_run: true — it builds, tests, verifies the tag is
free and uploads the artifacts, but never calls the release API.
The VERSION file
VERSION at the repository root holds the next version to be
released, as a bare MAJOR.MINOR.PATCH string with no leading v:
0.1.0
rc.ymlreads it to name release candidates: withVERSION=0.1.0, the
17th push tomainpublishes the prereleasev0.1.0-rc.17.release.ymlcompares it against the dispatched version and emits a
warning (not a failure) on a mismatch, so an out-of-band hotfix is still
possible.
Bumping VERSION is a normal pull request. After releasing v0.1.0, open a
PR setting VERSION to 0.2.0 (or 0.1.1); once it merges, RCs on main
start counting toward the next release. Nothing in CI ever writes to this file.
Release candidates (automatic)
Every push to main runs rc.yml:
go test ./...scripts/build-dist.sh <BASE>-rc.<run_number>- publishes a prerelease at tag
v<BASE>-rc.<run_number>, with the four
.tar.gzarchives andSHA256SUMSattached.
The tag does not exist beforehand — the release shim creates it at
target_commitish, which the workflow sets to ${{ github.sha }}, so CI cuts
its own RC tags.
concurrency: { group: rc, cancel-in-progress: true } means only the newest
push to main is building at any moment; superseded RC runs are cancelled, so
RC numbers are not contiguous. That is expected — run_number is the source of
uniqueness, not a count of published RCs.
Releases created by CI do not re-trigger workflows (the server has a
recursion guard), so an RC never kicks off another build.
Cutting a real release
- Make sure
mainis green and the latest RC is the build you want to ship. - If needed, land a PR bumping
VERSIONto the version you are
about to release. - In the rickub web UI, open Actions → Release → Run workflow.
- Pick the branch/ref (normally
main), enter the version as bare
MAJOR.MINOR.PATCH— e.g.1.2.3, no leadingv— leavedry_run
unchecked, and run it.
The workflow then:
- validates the format and refuses a leading
vor anything that is not
X.Y.Z; - refuses to continue if the tag
v<version>already exists locally or on the
remote, and additionally probes the releases API for that tag (a non-200,
non-404 answer is only a warning — the git tag check is the real gate); - runs
go vetandgo test; - builds the four archives +
SHA256SUMSviascripts/build-dist.sh; - publishes a non-prerelease release at
v<version>, targeting
${{ github.sha }}— the exact commit of the ref you dispatched — so the tag
is created at that commit.
There is no approval gate: rickub CI silently drops environment:, so the
permission to run this workflow is the permission to release.
⚠️ Use the web UI, not the dispatch API
release.yml is the only workflow in this repository that declares
workflow_dispatch, and it must stay that way.
The rickub dispatch API — POST /api/v1/repos/{owner}/{repo}/actions/dispatch
and the rickub run dispatch CLI — does not take a workflow name: it fires
every workflow_dispatch workflow on the ref. If a second dispatchable
workflow were added here, one rickub run dispatch would start both. Keeping
the trigger unique makes that failure mode impossible.
The web UI's Run workflow button can target a single workflow, so it is
the supported way to cut a release. If you must use the API, remember it will
run release.yml (and only release.yml, as long as this rule holds), and it
still needs the version input.
If you ever need a second manually-triggered pipeline, give it a
workflow_call trigger and invoke it as a reusable workflow from release.yml
rather than adding another workflow_dispatch.
Artifacts
scripts/build-dist.sh produces, in dist/:
rickub_<version>_linux_amd64.tar.gz
rickub_<version>_linux_arm64.tar.gz
rickub_<version>_darwin_amd64.tar.gz
rickub_<version>_darwin_arm64.tar.gz
SHA256SUMS
<version> is the bare version (no v): 1.2.3 for a release,
0.1.0-rc.17 for a candidate. Each archive contains the rickub binary at the
top level plus README.md (and LICENSE, automatically, once this repo has
one).
All four are cross-compiled on a single ubuntu-latest runner with
CGO_ENABLED=0 and -trimpath. The rickub runner fleet is Linux/amd64 only —
there are no macOS or arm64 hosts — so the darwin and arm64 binaries are never
executed by CI. Keeping the CLI pure Go is what makes this work; introducing
cgo would break three of the four targets.
The version is stamped with
-ldflags "-X rickub.com/rickub/cli/cmd.Version=<version>" (plus -s -w), so
rickub version reports the release version instead of the dev default.
To verify a download:
sha256sum -c SHA256SUMS --ignore-missing
Both workflows also upload dist/ via actions/upload-artifact@v4, so the
binaries are retrievable from the run page even if the release API call fails.
Building locally
# same script CI runs
scripts/build-dist.sh 1.2.3
# single local binary
go build -ldflags "-X rickub.com/rickub/cli/cmd.Version=$(cat VERSION)-dev" -o rickub .
The script needs Go and tar; it uses sha256sum where available and falls
back to shasum -a 256 on macOS. It stages into a temporary directory and
leaves nothing untracked behind — dist/ is already in .gitignore.
Troubleshooting
403 publishing the release. The node's CI proxy does not forward /gh/
yet — see "Publishing goes through the node's CI proxy" above.
could not read Username for 'https://github.com' in "Set up Go". The
fleet is fetching actions from GitHub anonymously and got rate-limited — see
"Fetching uses: actions" above; the fix is on the runner, not here.
Resource not accessible by integration / 404 on release creation. The
GITHUB_TOKEN is read-only by default; the workflow must declare
permissions: { contents: write }. Both rc.yml and release.yml do.
422 creating the release or uploading an asset. A release for that tag,
or an asset with that name, already exists — the shim rejects duplicates. This
normally means a partially completed run is being retried; delete the release
(or the asset) and re-run.
Editing a release via PATCH. The shim reads draft and prerelease
unconditionally, so always send both fields in a PATCH body or you will
silently flip a release to draft.
Nothing to release / make_latest ignored. The shim does not implement
make_latest or generate_release_notes, and there is no
GET /releases/latest. The workflows therefore write their own release notes
and never ask the API to pick a "latest" release.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
|