How to cut a release. The pipeline is mostly automated via cargo-dist; a few channels still need manual maintainer steps until CI has the right credentials to do them itself.
If the one-time setup below is done (tap, bucket, and AUR clones live under ~/qn/), a full release is two commands:
just release-prepare X.Y.Z
# release-prepare drives bump → PR → squash-merge → tag → CI through to a green run.
# Watch the workflow; come back when it's done.
just release-finalize
# Auto-detects the just-released version from the latest git tag, then bumps
# the Homebrew tap, Scoop bucket, and AUR qn-bin clones to match. Opens a PR
# against the tap and bucket repos (their main branches are protected) and
# prints the `git push` command for AUR. Merge the two PRs and run the push
# to publish.Override the clone-root directory if your clones live elsewhere: just release-finalize ~/work/quicknode. Override the version too if you're backfilling an older release: just release-finalize ~/qn 0.1.4.
The rest of this document covers what each step does in detail, what to do if part of the pipeline fails, and the one-time setup for each channel.
Three named recipes in the Justfile:
-
just release-prepare X.Y.Z— orchestrates the bump → branch → PR → squash-merge → tag-push → wait-for-CI sequence. Tag push is what firesrelease.yml; do not also runrelease-create-tag(it races cargo-dist's host job).Internally calls
release-bump,release-open-pr,release-merge-pr,release-tag-main,release-wait-ci. Each is also runnable standalone if a step fails and you need to retry. -
release.ymlruns in CI — cross-compiles 7 targets, creates the GitHub Release, attaches archives + sha256 sidecars + SLSA attestations, then fans out to per-channel publish jobs.Publish channels currently in CI:
custom-publish-crates→ publishesquicknode-clito crates.iocustom-publish-docker→ builds multi-arch image, pushes toghcr.io/quicknode/qncustom-publish-deb→ packages.debper arch, uploads to the GitHub Release as assetscustom-publish-copr→ builds an SRPM frompackaging/qn-bin.spec(whose%prepdownloads the SLSA-attested prebuilt binary), dispatches viacopr-cli buildto thequicknode/qnCOPR project
-
Maintainer manual steps after CI succeeds. Some channels still need a person to drive the publish because CI doesn't yet have the credentials it needs.
Run these from the repo root (~/qn/cli) after release.yml is green for the new tag.
Sync the formula cargo-dist generated as a release artifact into the tap repo:
just release-update-homebrew-tap X.Y.Z ~/qn/homebrew-tapThe recipe downloads qn.rb from the GitHub Release, copies it to Formula/qn.rb on a release/vX.Y.Z branch cut from the tap's latest origin/main, commits, pushes the branch, and opens a PR (the tap's main is protected, so changes must land via PR). Review and merge the PR to publish. Re-running the recipe recreates the branch and updates the open PR.
Bump the canonical version in bucket/qn.json:
just release-update-scoop-bucket X.Y.Z ~/qn/scoop-bucketThe recipe pulls the Windows zip's sha256 from the release, renders a manifest with version, hash, and an autoupdate block at bucket/qn.json, commits it on a release/vX.Y.Z branch cut from the bucket's latest origin/main, pushes the branch, and opens a PR (the bucket's main is protected, so changes must land via PR). Review and merge the PR to publish. Once a user has tapped the bucket, scoop update finds new versions on its own — this manual step just keeps scoop search qn honest about what's current.
Bump pkgver in the qn-bin AUR package:
just release-update-aur-bin X.Y.Z ~/qn/qn-bin
git -C ~/qn/qn-bin pushThe recipe pulls both Linux gnu sha256 sidecars (x86_64 + aarch64) from the release, renders a PKGBUILD + .SRCINFO, and stages them. Push goes to ssh://aur@aur.archlinux.org/qn-bin.git — the AUR's git remote.
release-finalize finishes by calling release-update-install-notes, which prepends a curated "How to install" section to the GitHub release body. Source for the block is packaging/release-notes-install.md.tmpl; edit it there if the install copy needs to change. The recipe is idempotent — re-running against the same release replaces the existing block rather than stacking duplicates — so it's safe to invoke standalone:
just release-update-install-notes X.Y.Z # edits the release in place
just release-update-install-notes X.Y.Z --dry-run # prints the assembled body without editingcargo-dist's auto-generated content is preserved below a --- separator.
A few channels needed manual setup the first time. Captured here so the next maintainer doesn't have to rediscover them.
Public repo on GitHub. Must be public — brew tap does an anonymous git clone. Has a single Formula/qn.rb per formula. cargo-dist generates the formula as a release artifact (whether or not we auto-publish), so the maintainer's job is just to commit it into the tap.
Public repo on GitHub. Must be public — Scoop does an anonymous git clone. Has bucket/qn.json per package. We hand-render the manifest in the recipe (cargo-dist doesn't generate one).
Maintainer needs an AUR account at https://aur.archlinux.org with an SSH key registered. Once that's set up:
# Clone the (currently empty) AUR git remote
mkdir -p ~/qn
cd ~/qn
git clone ssh://aur@aur.archlinux.org/qn-bin.git
# AUR returns: "warning: You appear to have cloned an empty repository." — expected.
# Render PKGBUILD + .SRCINFO from the latest release
cd ~/qn/cli
just release-update-aur-bin X.Y.Z ~/qn/qn-bin
# AUR expects the default branch to be `master`. Modern git defaults to `main`,
# so rename the freshly-created branch before the first push.
git -C ~/qn/qn-bin branch -m main master
git -C ~/qn/qn-bin push -u origin masterThe first push registers the package on the AUR. Subsequent pushes just update it — no rename needed (the branch stays master).
After publishing: confirm via https://aur.archlinux.org/packages/qn-bin (the RPC at /rpc/v5/info can lag the package page by a few minutes — trust the web page, not the RPC, for fresh registrations).
Maintainer needs a Fedora account at https://accounts.fedoraproject.org and a COPR project quicknode/qn created at https://copr.fedorainfracloud.org. Project settings to set on creation:
- Chroots: current Fedora releases + EPEL 9 (covers RHEL 9, Rocky 9, Alma 9). Skip EPEL 8 unless requested — its glibc is too old for our gnu binaries.
- Build settings: enable internet access for builds (the
%prepstep inpackaging/qn-bin.speccurls the prebuilt tarball from the GitHub Release; without net the build can't download it).
CI auths to COPR via four repo secrets — copr-cli itself only reads credentials from a config file at ~/.config/copr (no env-var fallback), so we provision the four fields separately and let the workflow assemble the file at build time. Generate the values at https://copr.fedorainfracloud.org/api/; the page shows a [copr-cli] config block with these four lines. Copy each field's value into its own secret:
| Secret | Field from the COPR API page |
|---|---|
COPR_LOGIN |
login = … |
COPR_USERNAME |
username = … |
COPR_TOKEN |
token = … |
COPR_URL |
copr_url = … (almost always https://copr.fedorainfracloud.org) |
Set each with gh secret set COPR_LOGIN --repo quicknode/cli etc., pasting just the value (no login = prefix, no quotes). Splitting them this way also means the token can be rotated without re-pasting the other three.
packaging/qn-bin.spec lives in this repo; it's a thin spec whose %prep downloads the SLSA-attested prebuilt linux-gnu tarball and verifies it against the .sha256 sidecar, so COPR isn't rebuilding qn from Rust source — it's just packaging the upstream binary into an RPM per chroot. Same trust chain as everywhere else qn ships.
If a single publish-* job in release.yml fails (e.g. crates.io rejected the publish because the token expired), the rest of the release is still good — the GitHub Release, attestations, and other channels remain published.
To retry just the failed job:
gh run rerun <run-id> --failed --repo quicknode/cliFor crates.io specifically, the manual fallback if CI's auth is broken is:
just release-cargo-publish
# Requires `cargo login` first.just lintclean (cargo clippy --all-targets -- -D warnings)just testcleanjust release-cargo-publish-checkclean (validates the crate tarball without uploading)dist planexits 0 (verifies the generated workflow matchesdist-workspace.toml)
If dist plan complains the workflow is out of date, run just dist-regen to regenerate and commit the result.