Release and packaging runbook
State: v0.4.0 is tagged and verified, but unpublished (Sep 4, 2026). An earlier v0.3.0 tag attempt triggered nothing (fork Actions quirk) and was deleted; no public GitHub releases exist. The repository version is 0.4.0 in Cargo.toml, and the release build/verification run is green. The runbook below starts with the explicit publish pass; do not create a second v0.4.0 tag.
0. Cut, verify, and publish the release
The successful verification run built and checked the complete release matrix but did not publish a GitHub release. The workflow also accepts a manual dispatch: a branch run is an isolated smoke build named 0.4.0-dev.<run-number>, while a dispatch from the release tag can publish only when the publish input is explicitly set to true.
Start the publish pass from the already verified tag:
gh workflow run Release --repo kreatzzz/woofer --ref v0.4.0 -f publish=trueThe assemble job validates every name, archive path, and checksum before the publish job uploads anything. Each successful build contains:
- Linux x86_64 and arm64 portable
.tar.gzarchives plus AppImages, built with pinned, SHA-256-verified AppImage tools; - Windows x86_64 and arm64 portable
.ziparchives plus per-user Inno Setup installers; - one universal macOS DMG; and
- a sorted
checksums.txtcovering every asset.
Windows Authenticode signing is optional, enabled only when both documented certificate secrets exist. macOS signing and notarization are optional too, but require the complete six-secret Apple contract; otherwise the DMG is ad-hoc/unsigned and needs the usual first-open approval. Tag-push smoke runs never receive signing secrets.
1. Homebrew tap (~10 min, fully scriptable)
gh repo create kreatzzz/homebrew-tap --public --clone=false
git clone https://github.com/kreatzzz/homebrew-tap /tmp/tap && cd /tmp/tap
mkdir -p CasksCasks/woofer.rb:
cask "woofer" do
version "0.4.0"
sha256 "SHA256_OF_THE_DMG" # from the release's checksums.txt
url "https://github.com/kreatzzz/woofer/releases/download/v#{version}/woofer-v#{version}-macos-universal.dmg"
name "Woofer"
desc "Fast, lightweight, native Spotify client"
homepage "https://github.com/kreatzzz/woofer"
livecheck do
url :url
strategy :github_latest
end
app "Woofer.app"
endOnce the release assets are public, the repository's handoff helper renders this cask with the real DMG hash (and renders the AUR and winget files at the same time):
bash packaging/release/prepare-packages.sh \
--dist /path/to/woofer-release-assets \
--version 0.4.0 \
--output-dir /tmp/woofer-packagesIt verifies every selected asset against checksums.txt before writing any manifest, so package files are not prepared with placeholder hashes.
Commit, push. Users: brew install kreatzzz/tap/woofer. Every future release: bump version + sha256 in one tap commit. If the first DMG is unsigned, put the first-open note in the README: right-click → Open, or xattr -cr /Applications/Woofer.app. Add the direct release URL only after the explicit publish pass has created a public asset.
2. AUR (~20 min, needs the user's aur.archlinux.org account + SSH key)
Two packages, each pushed to aur@aur.archlinux.org:<pkg>.git:
woofer (release tarball, x86_64):
pkgname=woofer
pkgver=0.4.0
pkgrel=1
pkgdesc="Fast, lightweight, native Spotify client built with Rust and egui"
arch=('x86_64')
url="https://github.com/kreatzzz/woofer"
license=('MIT')
depends=('alsa-lib' 'libpulse' 'wayland' 'libxkbcommon')
conflicts=('woofer-git')
source=("$pkgname-$pkgver.tar.gz::$url/releases/download/v$pkgver/woofer-v$pkgver-x86_64-unknown-linux-gnu.tar.gz")
sha256sums=('FILL_FROM_checksums.txt')
package() {
cd "$srcdir/$pkgname-v$pkgver-x86_64-unknown-linux-gnu"
install -Dm755 woofer "$pkgdir/usr/bin/woofer"
install -Dm644 packaging/applications/woofer.desktop \
"$pkgdir/usr/share/applications/woofer.desktop"
install -Dm644 packaging/icons/woofer.svg \
"$pkgdir/usr/share/icons/hicolor/scalable/apps/woofer.svg"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/woofer/LICENSE"
}Validate with makepkg -f (or namcap), then git init && git add . && git commit && git push aur:woofer.git. Test with yay -S woofer in a clean chroot if possible.
The helper writes the release package to /tmp/woofer-packages/aur/woofer/PKGBUILD and the rolling package to /tmp/woofer-packages/aur/woofer-git/PKGBUILD. The release package receives the verified x86_64 archive hash; the -git package deliberately uses sha256sums=('SKIP') because its source is a moving Git checkout.
woofer-git: same shape, makedepends=('cargo' 'git'), source=("$pkgname::git+$url.git"), pkgver() from git describe, build with cargo build --release --locked, same package() installs.
3. winget (Windows; the bureaucratic one)
- Fork
microsoft/winget-pkgsunderkreatzzz, shallow-clone it. - Add
manifests/k/kreatzzz/Woofer/0.4.0/with three YAML files:kreatzzz.Woofer.yaml— the version manifest withDefaultLocale: en-US.kreatzzz.Woofer.installer.yaml—InstallerType: inno, both architectures pointing at the GitHub…-setup.exeURLs, each with its sha256 fromchecksums.txt, andInstallerSwitches: { Silent: /VERYSILENT /SUPPRESSMSGBOXES /NORESTART, SilentWithProgress: /SILENT },AppsAndFeaturesEntrieswithProductCodeif the .iss declares one.kreatzzz.Woofer.locale.en-US.yaml— the default English locale with publisher, description, tags, and homepage. The helper writes these three files below/tmp/woofer-packages/winget/manifests/k/kreatzzz/Woofer/0.4.0/and fills both installer hashes from the publishedchecksums.txt.
- Validate locally with
winget validate(on Windows) or thewinget-pkgsCI, then open the PR with their template. The bot verifies the URLs and hashes; a human reviews (1-3 days). First-time submitters need a seasoned GitHub account — having the tap and AUR package public first helps the review.
Future versions: a new version folder per release.
After the first release
- Bump the tap (version + sha256) — one commit.
docs/_guide/download.mdrecords the paused release state and the source install path; add direct package links there when Homebrew/AUR/winget are live.- The update checker (
src/updates.rs) watcheskreatzzz/woofer/releases/latest— it starts working from the first real release.