Skip to content

Latest commit

 

History

History
171 lines (110 loc) · 9.2 KB

File metadata and controls

171 lines (110 loc) · 9.2 KB

Releasing qn

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.

Quick release

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.

Per-release flow

Three named recipes in the Justfile:

  1. just release-prepare X.Y.Z — orchestrates the bump → branch → PR → squash-merge → tag-push → wait-for-CI sequence. Tag push is what fires release.yml; do not also run release-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.

  2. release.yml runs 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 → publishes quicknode-cli to crates.io
    • custom-publish-docker → builds multi-arch image, pushes to ghcr.io/quicknode/qn
    • custom-publish-deb → packages .deb per arch, uploads to the GitHub Release as assets
    • custom-publish-copr → builds an SRPM from packaging/qn-bin.spec (whose %prep downloads the SLSA-attested prebuilt binary), dispatches via copr-cli build to the quicknode/qn COPR project
  3. 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.

Manual steps after each release

Run these from the repo root (~/qn/cli) after release.yml is green for the new tag.

Homebrew

Sync the formula cargo-dist generated as a release artifact into the tap repo:

just release-update-homebrew-tap X.Y.Z ~/qn/homebrew-tap

The 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.

Scoop

Bump the canonical version in bucket/qn.json:

just release-update-scoop-bucket X.Y.Z ~/qn/scoop-bucket

The 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.

AUR

Bump pkgver in the qn-bin AUR package:

just release-update-aur-bin X.Y.Z ~/qn/qn-bin
git -C ~/qn/qn-bin push

The 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.

Curated install block on the release notes

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 editing

cargo-dist's auto-generated content is preserved below a --- separator.

One-time setup notes

A few channels needed manual setup the first time. Captured here so the next maintainer doesn't have to rediscover them.

Homebrew tap (quicknode/homebrew-tap)

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.

Scoop bucket (quicknode/scoop-bucket)

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).

AUR (qn-bin)

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 master

The 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).

COPR (quicknode/qn)

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 %prep step in packaging/qn-bin.spec curls 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.

Recovery: a publish channel failed

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/cli

For crates.io specifically, the manual fallback if CI's auth is broken is:

just release-cargo-publish
# Requires `cargo login` first.

Sanity-checks before tagging

  • just lint clean (cargo clippy --all-targets -- -D warnings)
  • just test clean
  • just release-cargo-publish-check clean (validates the crate tarball without uploading)
  • dist plan exits 0 (verifies the generated workflow matches dist-workspace.toml)

If dist plan complains the workflow is out of date, run just dist-regen to regenerate and commit the result.