Release Runbook
View as markdownThis is the operational checklist for Shelbi maintainers publishing a release.
Run every command from a clean checkout of https://github.com/jlong/shelbi
unless a step says otherwise.
Current Release Scope
- Primary repository:
jlong/shelbi - Release binary:
shelbi - Release branch:
main - Release tags:
vMAJOR.MINOR.PATCH, for examplev0.1.0 - Homebrew tap: unresolved; plan recommends
shelbi/homebrew-shelbiif a Shelbi org exists, otherwisejlong/homebrew-shelbi - APT repository: unresolved; plan recommends a dedicated Pages or object storage repository
- APT repository domain: unresolved; plan recommends
https://apt.shelbi.dev - Current release owner: unresolved
- Signing-key owner: unresolved
Do not ship a release until every unresolved owner, repository, and domain is confirmed in the maintainer channel.
Required Access
The release operator needs these local tools:
cargo --version
git --version
gh --version
goreleaser --version
jq --version
dpkg-deb --version
ar --version
tar --version
gpg --version
apt-ftparchive --version
brew --version
docker --versionThe release automation needs these secrets:
GITHUB_TOKENor a GitHub App token with permission to create releases and upload release artifacts injlong/shelbi.TAP_GITHUB_TOKEN, a fine-scoped token or GitHub App credential that can write only to the confirmed Homebrew tap where possible.APT_REPO_TOKEN, a fine-scoped token or GitHub App credential that can write to the confirmed APT hosting repository.APT_GPG_PRIVATE_KEY, an ASCII-armored private key for APT metadata signing.APT_GPG_PASSPHRASE, the passphrase for the APT private key.APT_SIGNING_KEY_ID, the public fingerprint for the APT metadata signing key.- OIDC permissions for keyless Sigstore signing, or
COSIGN_*only if the release workflow chooses long-lived cosign secrets.
The signing-key owner rotates APT and artifact signing keys. The release owner rotates publishing tokens. Rotate immediately after suspected exposure, staff offboarding, or a failed release where a secret may have been printed in logs.
Pre-Tag Checks
Start from an up-to-date main:
git fetch origin main --tags
git checkout main
git pull --ff-only origin main
git status --shortgit status --short must print nothing. Confirm the version before tagging:
VERSION=0.1.0
test "$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "shelbi") | .version')" = "$VERSION"Run the workspace tests and build the exact release profile:
cargo test --workspace
cargo build --release --bin shelbi
./target/release/shelbi --versionThe version output must match the tag you intend to publish:
./target/release/shelbi --version | grep "shelbi $VERSION"Curate the changelog before tagging. site/content/docs/changelog.mdx is
hand-maintained and updates nowhere else, so add or finish this version's entry
now: a feature-level, newest-first note dated to when the work landed on main,
folding internal refactors into the capability they enabled. Commit it to main
before the tag so the published site matches the release.
GoReleaser Dry Run
Validate the GoReleaser configuration:
goreleaser checkBuild local snapshot artifacts without publishing:
goreleaser release --snapshot --cleanExpected snapshot output under dist/:
shelbi_Darwin_x86_64.tar.gzshelbi_Darwin_arm64.tar.gzshelbi_Linux_x86_64.tar.gzshelbi_0.1.0_amd64.debchecksums.txt- signed checksums or Sigstore bundle, if signing is configured
- Homebrew formula output, if the confirmed tap is configured
- APT repository metadata output, if the confirmed repository is configured
Linux arm64 and Debian arm64 artifacts are out of scope until they are
built and smoke-tested in CI or on a reliable runner.
Inspect the generated files:
find dist -maxdepth 2 -type f | sort
grep -E 'shelbi_(Darwin|Linux)|\\.deb$' dist/checksums.txtLocal Artifact Verification
Verify archive contents and checksums:
shasum -a 256 -c dist/checksums.txt
tar -tzf dist/shelbi_Linux_x86_64.tar.gz | grep '^shelbi$'
tar -xzf dist/shelbi_Linux_x86_64.tar.gz -C /tmp
/tmp/shelbi --version
rm /tmp/shelbiVerify the Debian package locally:
DEB=$(find dist -name 'shelbi_*_amd64.deb' | head -n 1)
dpkg-deb --info "$DEB"
dpkg-deb --contents "$DEB"
dpkg-deb --field "$DEB" Package Version Architecture Maintainer
dpkg-deb --field "$DEB" Package | grep '^shelbi$'
dpkg-deb --field "$DEB" Version | grep "^$VERSION"
dpkg-deb --field "$DEB" Architecture | grep '^amd64$'Install and execute the package in a clean Debian or Ubuntu container:
docker run --rm -v "$PWD/dist:/dist:ro" debian:bookworm bash -euxo pipefail -c '
apt-get update
apt-get install -y /dist/shelbi_*_amd64.deb
shelbi --version
command -v shelbi
'Homebrew Formula Verification
After the dry run generates or updates the formula, check the formula in the confirmed tap checkout:
TAP_DIR=/tmp/homebrew-shelbi-tap
git clone git@github.com:OWNER/HOMEBREW_TAP_REPO.git "$TAP_DIR"
cd "$TAP_DIR"
brew audit --strict --online Formula/shelbi.rb
brew style Formula/shelbi.rb
brew install --build-from-source Formula/shelbi.rb
shelbi --version
brew test shelbi
brew uninstall shelbiReplace OWNER/HOMEBREW_TAP_REPO with the confirmed Homebrew tap. If the tap
uses a different formula path, update the path before release and commit the
runbook correction in the same change.
APT Repository Verification
Verify repository metadata before users can consume it:
APT_ROOT=dist/apt
find "$APT_ROOT" -type f | sort
test -f "$APT_ROOT/dists/stable/Release"
test -f "$APT_ROOT/dists/stable/InRelease"
test -f "$APT_ROOT/dists/stable/Release.gpg"
gpg --verify "$APT_ROOT/dists/stable/Release.gpg" "$APT_ROOT/dists/stable/Release"
gpg --verify "$APT_ROOT/dists/stable/InRelease"
grep '^Suite: stable$' "$APT_ROOT/dists/stable/Release"
grep '^Codename: stable$' "$APT_ROOT/dists/stable/Release"
grep '^Architectures: amd64$' "$APT_ROOT/dists/stable/Release"
grep 'pool/.*/shelbi_.*_amd64.deb' "$APT_ROOT/dists/stable/main/binary-amd64/Packages"Then install from the staged repository in a container:
docker run --rm -v "$PWD/dist/apt:/repo:ro" debian:bookworm bash -euxo pipefail -c '
apt-get update
apt-get install -y ca-certificates gnupg
install -d -m 0755 /etc/apt/keyrings
cp /repo/shelbi-archive-keyring.gpg /etc/apt/keyrings/shelbi.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/shelbi.gpg] file:/repo stable main" > /etc/apt/sources.list.d/shelbi.list
apt-get update
apt-cache policy shelbi
apt-get install -y shelbi
shelbi --version
'For the live repository, replace the file:/repo source with the confirmed
HTTPS APT domain and repeat the same container test.
Tag And Publish
Create an annotated tag only after the dry run and local verification pass:
VERSION=0.1.0
git checkout main
git pull --ff-only origin main
git tag -a "v$VERSION" -m "Shelbi v$VERSION"
git push origin "v$VERSION"Publish with GoReleaser:
goreleaser release --cleanVerify the GitHub release:
gh release view "v$VERSION" --repo jlong/shelbi
gh release download "v$VERSION" --repo jlong/shelbi --dir "/tmp/shelbi-v$VERSION"
cd "/tmp/shelbi-v$VERSION"
shasum -a 256 -c checksums.txtExpected published artifacts:
- Darwin
amd64archive - Darwin
arm64archive - Linux
amd64archive - Debian
amd64package checksums.txt- Artifact signatures, if signing is enabled
- SBOM/provenance files, if configured
- Homebrew formula update in the confirmed tap
- APT pool package,
Packages,Release,InRelease, andRelease.gpg
Post-Release Verification
Verify install paths after publication:
brew update
brew install shelbi
shelbi --version
brew test shelbi
brew uninstall shelbiVerify APT from the live repository:
docker run --rm debian:bookworm bash -euxo pipefail -c '
apt-get update
apt-get install -y ca-certificates curl gnupg
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://APT_DOMAIN/shelbi-archive-keyring.gpg -o /etc/apt/keyrings/shelbi.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/shelbi.gpg] https://APT_DOMAIN stable main" > /etc/apt/sources.list.d/shelbi.list
apt-get update
apt-cache policy shelbi
apt-get install -y shelbi
shelbi --version
'Replace APT_DOMAIN with the confirmed release domain. The installed version
must match VERSION.
Rollback
Bad GitHub Artifacts
If an artifact is corrupt before the release is announced, stop package publication, delete the draft or unpublished release, and rerun the release from the same tag:
VERSION=0.1.0
gh release delete "v$VERSION" --repo jlong/shelbi --yes
goreleaser release --cleanAfter public announcement or package-manager publication, do not replace or delete GitHub release assets. Publish a patch version from a fix commit:
NEXT_VERSION=0.1.1
git checkout main
git pull --ff-only origin main
git tag -a "v$NEXT_VERSION" -m "Shelbi v$NEXT_VERSION"
git push origin "v$NEXT_VERSION"
goreleaser release --cleanIf the tag points at the wrong commit and the release is not public yet, delete the release and tag, then recreate the tag on the correct commit:
VERSION=0.1.0
gh release delete "v$VERSION" --repo jlong/shelbi --yes
git push origin ":refs/tags/v$VERSION"
git tag -d "v$VERSION"
git checkout CORRECT_COMMIT_SHA
git tag -a "v$VERSION" -m "Shelbi v$VERSION"
git push origin "v$VERSION"
goreleaser release --cleanAnnounce the tag move in the maintainer channel. Never move a tag silently after users may have fetched it. After public announcement, leave the bad tag in place and publish a patch version.
Bad Homebrew Formula
Revert the formula commit in the tap and push the revert:
cd "$TAP_DIR"
git pull --ff-only
git log --oneline -- Formula/shelbi.rb
git revert BAD_FORMULA_COMMIT_SHA
brew audit --strict --online Formula/shelbi.rb
brew test shelbi
git push origin HEADIf the formula points at a bad GitHub artifact, complete the GitHub artifact rollback first, then update the formula checksums and run the formula verification again.
Bad APT Package
Republish APT metadata without the bad package as the candidate version. Leave
the bad .deb in pool/ for auditability unless it is actively harmful. The
resulting repository must no longer advertise the bad version:
apt-cache policy shelbiRegenerate and sign repository metadata:
apt-ftparchive packages pool > dists/stable/main/binary-amd64/Packages
gzip -kf dists/stable/main/binary-amd64/Packages
apt-ftparchive release dists/stable > dists/stable/Release
gpg --batch --yes --default-key "$APT_SIGNING_KEY_ID" --clearsign -o dists/stable/InRelease dists/stable/Release
gpg --batch --yes --default-key "$APT_SIGNING_KEY_ID" -abs -o dists/stable/Release.gpg dists/stable/ReleaseRun the live APT container verification again. Publish a maintainer note telling
users to run sudo apt update before retrying. APT does not automatically
downgrade installed packages; once at least two versions exist, document the
manual downgrade:
apt-cache madison shelbi
sudo apt install shelbi=PREVIOUS_GOOD_VERSION
sudo apt-mark hold shelbiAPT Key Compromise
Treat a suspected signing-key exposure as a release incident:
- Freeze APT publication and remove repository write credentials from CI.
- Revoke the compromised key if a revocation certificate exists.
- Generate a new offline signing key and store its revocation certificate in the maintainer secret store.
- Export the new public key as
shelbi-archive-keyring.gpg. - Replace
APT_GPG_PRIVATE_KEY,APT_GPG_PASSPHRASE,APT_SIGNING_KEY_ID, and any deployment secret that could have accessed the old private key. - Re-sign the repository metadata with the new key.
- Publish the new keyring at the confirmed APT domain.
- Publish user-facing migration instructions that replace the old keyring file
before running
sudo apt update.
Use these local commands for the new key material:
gpg --batch --full-generate-key
gpg --list-secret-keys --keyid-format LONG
gpg --output shelbi-archive-keyring.gpg --export "$NEW_APT_SIGNING_KEY_ID"
gpg --output shelbi-archive-revocation.asc --gen-revoke "$NEW_APT_SIGNING_KEY_ID"Do not delete the old public key from public history. Users need a clear migration path from the old key to the new one.