ci: attach .deb and .rpm packages to releases and nightlies

Built by nfpm from the static musl binaries, with the man page and the
bash, zsh and fish completions. The names carry no version, so
releases/latest/download/soulseek-rs-amd64.deb stays a working link; the
nightly pair replaces itself on the rolling prerelease.
This commit is contained in:
Michel de Graaf
2026-09-21 12:58:09 +02:00
parent 645d24e29d
commit f09d550e4b
6 changed files with 229 additions and 3 deletions
+29
View File
@@ -105,6 +105,12 @@ jobs:
dry-run: true
dry-run-intended: true
- name: Build .deb and .rpm
if: endsWith(matrix.target, '-musl')
env:
TARGET: ${{ matrix.target }}
run: sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "$SOULSEEK_RS_VERSION" "$TARGET" . soulseek-rs-nightly
- name: Store packaged binary
uses: actions/upload-artifact@v4
with:
@@ -112,6 +118,8 @@ jobs:
path: |
${{ steps.package.outputs.tar || steps.package.outputs.zip }}
${{ steps.package.outputs.sha256 }}
soulseek-rs-nightly-*.deb
soulseek-rs-nightly-*.rpm
if-no-files-found: error
retention-days: 1
@@ -147,6 +155,10 @@ jobs:
soulseek-rs-nightly-aarch64-apple-darwin.sha256
soulseek-rs-nightly-x86_64-pc-windows-msvc.sha256
soulseek-rs-nightly-aarch64-pc-windows-msvc.sha256
soulseek-rs-nightly-amd64.deb
soulseek-rs-nightly-arm64.deb
soulseek-rs-nightly-x86_64.rpm
soulseek-rs-nightly-aarch64.rpm
)
printf '%s\n' "${expected[@]}" | sort > "$RUNNER_TEMP/expected-assets"
find dist -maxdepth 1 -type f -exec basename {} \; | sort > "$RUNNER_TEMP/actual-assets"
@@ -234,6 +246,7 @@ jobs:
timeout-minutes: 5
env:
VERSION: ${{ needs.source.outputs.version }}
DOWNLOAD: https://github.com/${{ github.repository }}/releases/download/nightly
steps:
- uses: actions/checkout@v4
with:
@@ -260,3 +273,19 @@ jobs:
PATH="$RUNNER_TEMP/wget-path" /bin/sh website/public/install.sh --nightly
"$SOULSEEK_RS_INSTALL_DIR/soulseek-rs" --version | grep -F "$VERSION"
- name: Install the .deb
run: |
curl -fsSLO "$DOWNLOAD/soulseek-rs-nightly-amd64.deb"
sudo apt-get install -y ./soulseek-rs-nightly-amd64.deb
soulseek-rs --version | grep -F "$VERSION"
dpkg -L soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz
dpkg -L soulseek-rs | grep -Fx /usr/share/zsh/vendor-completions/_soulseek-rs
- name: Install the .rpm
run: |
docker run --rm fedora:latest sh -ec "
dnf install -y '$DOWNLOAD/soulseek-rs-nightly-x86_64.rpm'
soulseek-rs --version | grep -F '$VERSION'
rpm -ql soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz
rpm -ql soulseek-rs | grep -Fx /usr/share/zsh/site-functions/_soulseek-rs
"
+67
View File
@@ -0,0 +1,67 @@
name: Packaging
on:
pull_request:
paths:
- 'Cargo.lock'
- 'Cargo.toml'
- 'packaging/**'
- 'scripts/package-linux.sh'
- 'soulseek-rs/src/cli.rs'
- 'soulseek-rs/src/run.rs'
- 'soulseek-rs/src/commands/**'
- '.github/workflows/packaging.yml'
permissions:
contents: read
concurrency:
group: packaging-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
VERSION: 0.0.0+git202601010000.0000000
jobs:
packages:
name: .deb and .rpm
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl
- uses: Swatinem/rust-cache@v2
- name: Install musl toolchain
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build the packages
env:
SOULSEEK_RS_VERSION: ${{ env.VERSION }}
TARGET: x86_64-unknown-linux-musl
run: |
cargo build --locked --release --target "$TARGET" --package soulseek-rs
sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "$VERSION" "$TARGET" . soulseek-rs-nightly
- name: Install the .deb
run: |
sudo apt-get install -y ./soulseek-rs-nightly-amd64.deb
soulseek-rs --version | grep -F "$VERSION"
dpkg -L soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz
dpkg -L soulseek-rs | grep -Fx /usr/share/zsh/vendor-completions/_soulseek-rs
- name: Install the .rpm
run: |
docker run --rm -v "$PWD:/packages:ro" fedora:latest sh -ec "
dnf install -y /packages/soulseek-rs-nightly-x86_64.rpm
soulseek-rs --version | grep -F '$VERSION'
rpm -ql soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz
rpm -ql soulseek-rs | grep -Fx /usr/share/zsh/site-functions/_soulseek-rs
"
+10
View File
@@ -200,6 +200,16 @@ jobs:
ref: refs/tags/${{ needs.release.outputs.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build and upload .deb and .rpm
if: endsWith(matrix.target, '-musl') && hashFiles('scripts/package-linux.sh') != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.release.outputs.tag }}
TARGET: ${{ matrix.target }}
run: |
sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "${TAG#v}" "$TARGET" dist
gh release upload "$TAG" dist/* --clobber
# The tap regenerates its formula from whatever `gh release view` calls latest,
# so it only needs a nudge once every archive and .sha256 is on the release —
# hence `needs: binaries` rather than firing alongside it. The tap also polls
+14 -3
View File
@@ -28,6 +28,11 @@ Releases are automated by [release-plz](https://release-plz.dev). You do not run
| `soulseek-rs-vX.Y.Z-x86_64-pc-windows-msvc.zip` | |
| `soulseek-rs-vX.Y.Z-aarch64-pc-windows-msvc.zip` | |
- packages the two Linux binaries as `soulseek-rs-amd64.deb`, `soulseek-rs-arm64.deb`,
`soulseek-rs-x86_64.rpm` and `soulseek-rs-aarch64.rpm`, with the man page and the
bash, zsh and fish completions. The names carry no version, so
`releases/latest/download/soulseek-rs-amd64.deb` is a link that keeps working,
- rewrites `Formula/soulseek-rs.rb` in the `michel/homebrew-tap` repository so
`brew install michel/tap/soulseek-rs` picks up the new macOS and Linux archives,
- merges `master` back into `develop`, so the branch the work happens on carries the
@@ -44,9 +49,15 @@ conflict.
`.github/workflows/nightly.yml` builds the tip of `develop` at 02:17 UTC every
day. It resolves the commit once, builds the same six targets as a numbered
release, verifies all twelve archives and checksum files as one set, then
replaces the mutable `nightly` prerelease and tag. The published release is
smoke-tested through both the curl and wget installer paths.
release, packages the two Linux ones as `soulseek-rs-nightly-*.deb` and `.rpm`,
verifies all sixteen files as one set, then replaces the mutable `nightly`
prerelease and tag. Every nightly binary reports `X.Y.Z+git<commit time, UTC>.<sha>`
instead of the crate version. The published release is smoke-tested through the
curl and wget installer paths, and by installing the `.deb` and the `.rpm`.
Never enable GitHub's Immutable Releases on this repository: nightly replaces
its assets and moves its tag, and a tag name burnt by that setting cannot be
reused.
Use **Actions → Nightly → Run workflow** to force a build. The workflow always
checks out `develop`, regardless of the ref selected in the dispatch form. The
+55
View File
@@ -0,0 +1,55 @@
name: soulseek-rs
arch: ${PKG_ARCH}
platform: linux
version: ${PKG_VERSION}
version_schema: none
release: "1"
section: net
priority: optional
maintainer: Michel de Graaf <michel@re-invention.nl>
description: |
Soulseek client for the terminal
A TUI, a scriptable command line and a daemon for the Soulseek
peer-to-peer network.
vendor: Michel de Graaf
homepage: https://re-invention.nl/soulseek-rs/
license: MIT
suggests:
- gnome-keyring
contents:
- src: target/nfpm/soulseek-rs
dst: /usr/bin/soulseek-rs
file_info:
mode: 0755
- src: target/nfpm/soulseek-rs.1.gz
dst: /usr/share/man/man1/soulseek-rs.1.gz
- src: target/nfpm/soulseek-rs.bash
dst: /usr/share/bash-completion/completions/soulseek-rs
- src: target/nfpm/soulseek-rs.fish
dst: /usr/share/fish/vendor_completions.d/soulseek-rs.fish
- src: target/nfpm/_soulseek-rs
dst: /usr/share/zsh/vendor-completions/_soulseek-rs
packager: deb
- src: target/nfpm/_soulseek-rs
dst: /usr/share/zsh/site-functions/_soulseek-rs
packager: rpm
- dst: /usr/share/doc/soulseek-rs
type: dir
packager: rpm
- dst: /usr/share/licenses/soulseek-rs
type: dir
packager: rpm
- src: README.md
dst: /usr/share/doc/soulseek-rs/README.md
packager: deb
- src: README.md
dst: /usr/share/doc/soulseek-rs/README.md
packager: rpm
type: doc
- src: LICENSE
dst: /usr/share/doc/soulseek-rs/copyright
packager: deb
- src: LICENSE
dst: /usr/share/licenses/soulseek-rs/LICENSE
packager: rpm
type: license
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
set -eu
umask 022
[ $# -ge 4 ] || {
echo "usage: $0 <binary> <version> <target-triple> <out-dir> [<package-basename>]" >&2
exit 2
}
NFPM_VERSION=2.47.0
binary=$1
PKG_VERSION=$2
target=$3
out=$4
package=${5:-soulseek-rs}
case "$target" in
x86_64-*) PKG_ARCH=amd64 rpm_arch=x86_64 ;;
aarch64-*) PKG_ARCH=arm64 rpm_arch=aarch64 ;;
*) echo "no package architecture for $target" >&2; exit 1 ;;
esac
case "$(uname -s)-$(uname -m)" in
Linux-x86_64) nfpm_asset=Linux_x86_64 nfpm_sha256=0660ca602b2d2d2ae4781a06c692b3eeb9d437ffea05b831d76e41f4a3188783 ;;
Linux-aarch64) nfpm_asset=Linux_arm64 nfpm_sha256=1c0f5f2999b9a974bfb04fdb0cc3306096de530ac5dbb25d739cc5f5219c919c ;;
*) echo "no pinned nfpm build for this machine" >&2; exit 1 ;;
esac
root=$(cd "$(dirname "$0")/.." && pwd)
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
stage=$root/target/nfpm
rm -rf "$stage"
mkdir -p "$stage" "$out"
out=$(cd "$out" && pwd)
cp "$binary" "$stage/soulseek-rs"
"$binary" completions print bash > "$stage/soulseek-rs.bash"
"$binary" completions print zsh > "$stage/_soulseek-rs"
"$binary" completions print fish > "$stage/soulseek-rs.fish"
"$binary" man > "$stage/soulseek-rs.1"
gzip -9n "$stage/soulseek-rs.1"
archive=nfpm_${NFPM_VERSION}_$nfpm_asset.tar.gz
curl -fsSL -o "$work/$archive" \
"https://github.com/goreleaser/nfpm/releases/download/v$NFPM_VERSION/$archive"
echo "$nfpm_sha256 $work/$archive" | sha256sum -c - >/dev/null
tar -xzf "$work/$archive" -C "$work" nfpm
export PKG_ARCH PKG_VERSION
cd "$root"
"$work/nfpm" package --config packaging/nfpm.yaml --packager deb --target "$out/$package-$PKG_ARCH.deb"
"$work/nfpm" package --config packaging/nfpm.yaml --packager rpm --target "$out/$package-$rpm_arch.rpm"