From 42897205bf9e084492da414c94c66f60edb439fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20R=C3=A9quillart?= Date: Tue, 10 Mar 2026 16:44:18 +0100 Subject: [PATCH 1/4] ci: add armv6 (Raspberry Pi B/Zero) build support Cross-compilation via `cross` doesn't work for armv6 due to C dependencies (alsa, dbus, pulseaudio) and bindgen (aws-lc-rs). Instead, build natively under QEMU using a RaspiOS container, forcing the correct rustup host triple (arm-unknown-linux-gnueabihf) since QEMU reports armv7 by default. - Add .github/Dockerfile.armv6 for QEMU-native builds - Add armv6 to the CD matrix (linux only, slim/default/full) - Use docker buildx with GHA layer cache in the CD workflow --- .github/Dockerfile.armv6 | 74 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/cd.yml | 31 +++++++++++++++-- .gitignore | 3 ++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 .github/Dockerfile.armv6 diff --git a/.github/Dockerfile.armv6 b/.github/Dockerfile.armv6 new file mode 100644 index 00000000..3f3e312c --- /dev/null +++ b/.github/Dockerfile.armv6 @@ -0,0 +1,74 @@ +# syntax=docker/dockerfile:1 + +# ── Builder base images ──────────────────────────────────────────────────────── +# amd64: native build on Debian Trixie +# arm64/arm: RaspiOS (Trixie), runs natively via QEMU binfmt_misc — +# no cross-compiler needed, even for bindgen +FROM debian:trixie-slim AS base-amd64 +FROM vascoguita/raspios:arm64 AS base-arm64 +FROM vascoguita/raspios:armhf AS base-arm + +# ── Builder ─────────────────────────────────────────────────────────────────── +ARG TARGETARCH +FROM base-${TARGETARCH} AS builder + +ARG TARGETARCH +ARG TARGETVARIANT +# Default: slim build with alsa only +ARG FEATURES="--no-default-features --features alsa_backend" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + gcc \ + libc6-dev \ + # required by aws-lc-rs (bindgen) on non-x86_64/aarch64 targets + libclang-dev \ + cmake \ + pkg-config \ + # alsa_backend + base + libasound2-dev \ + libssl-dev \ + # dbus_mpris + libdbus-1-dev \ + # pulseaudio_backend + libpulse-dev \ + # rodiojack_backend + libjack-dev \ + && rm -rf /var/lib/apt/lists/* + +# Force the correct rustup host triple. +# QEMU reports armv7 even inside an armv6 container — override manually. +RUN set -eux; \ + case "${TARGETARCH}${TARGETVARIANT}" in \ + armv6) RUST_HOST="arm-unknown-linux-gnueabihf" ;; \ + armv7) RUST_HOST="armv7-unknown-linux-gnueabihf" ;; \ + arm64) RUST_HOST="aarch64-unknown-linux-gnu" ;; \ + amd64) RUST_HOST="x86_64-unknown-linux-gnu" ;; \ + *) RUST_HOST="" ;; \ + esac; \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable --profile minimal \ + ${RUST_HOST:+--default-host "$RUST_HOST"} +ENV PATH="/root/.cargo/bin:$PATH" + +# Use native bindgen (no cross-compilation) +ENV AWS_LC_SYS_BINDGEN=1 + +WORKDIR /build + +COPY Cargo.lock Cargo.toml ./ +COPY src/ src/ + +# Cargo cache is preserved between builds so changing FEATURES +# does not recompile all dependencies from scratch. +# shellcheck disable=SC2086 # $FEATURES must be word-split intentionally +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/root/.cargo/git \ + --mount=type=cache,target=/build/target \ + cargo build --locked --release $FEATURES && \ + cp target/release/spotifyd /spotifyd + +# ── Binary export: extract via --output type=local ─────────────────────────── +FROM scratch AS export +COPY --from=builder /spotifyd /spotifyd diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 5508ddda..bee99b09 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [macos, linux] - arch: [x86_64, aarch64, armv7] + arch: [x86_64, aarch64, armv7, armv6] rust: [stable] artifact_type: ['slim', 'default', 'full'] # The build strategy will build all types for each OS specified include: @@ -30,6 +30,9 @@ jobs: - os: linux arch: aarch64 runs_on: ubuntu-22.04-arm + - os: linux + arch: armv6 + runs_on: ubuntu-latest # DBus configuration - artifact_type: slim dbus: '' @@ -59,6 +62,8 @@ jobs: artifact_type: 'full' - os: macos arch: armv7 + - os: macos + arch: armv6 steps: - name: Checking out sources uses: actions/checkout@v4 @@ -66,7 +71,7 @@ jobs: if: matrix.os == 'macos' run: brew install dbus portaudio - name: Installing needed Ubuntu dependencies - if: startsWith(matrix.runs_on, 'ubuntu') && matrix.target == '' + if: startsWith(matrix.runs_on, 'ubuntu') && matrix.target == '' && matrix.arch != 'armv6' run: | sudo apt-get update # WORKAROUND: fix aws-lc-sys compiler bug failure @@ -87,7 +92,7 @@ jobs: features="--no-default-features --features ${{ matrix.dbus }},${{ matrix.audio_backends }}" echo CARGO_ARGS="--locked --release $features" | tee -a "$GITHUB_ENV" - name: Build (using cargo) - if: matrix.target == '' + if: matrix.target == '' && matrix.arch != 'armv6' run: | cargo +${{ matrix.rust }} build $CARGO_ARGS - name: Build (using cross) @@ -99,6 +104,26 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} args: $CARGO_ARGS + - name: Set up QEMU + if: matrix.arch == 'armv6' + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + if: matrix.arch == 'armv6' + uses: docker/setup-buildx-action@v3 + - name: Build (using docker buildx for armv6) + if: matrix.arch == 'armv6' + run: | + docker buildx build \ + --platform linux/arm/v6 \ + --file .github/Dockerfile.armv6 \ + --target export \ + --output type=local,dest=./armv6-out \ + --cache-from type=gha,scope=armv6-${{ matrix.artifact_type }} \ + --cache-to type=gha,scope=armv6-${{ matrix.artifact_type }},mode=max \ + --build-arg FEATURES="--no-default-features --features ${{ matrix.dbus }},${{ matrix.audio_backends }}" \ + . + mkdir -p target/release + cp ./armv6-out/spotifyd target/release/spotifyd - name: Uploading artifacts uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 22480c5c..50b67f78 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ tags .vscode # Windows logs *.log + +# docker cross build +dist/ From e39bc0bf4a60c32232973c7775a071e26c8358a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20R=C3=A9quillart?= Date: Sat, 21 Mar 2026 20:31:18 +0100 Subject: [PATCH 2/4] ci: replace aarch64 native runner and armv7 cross with QEMU docker buildx aarch64 no longer uses ubuntu-22.04-arm; armv7 no longer uses `cross`. Both now build via docker buildx + QEMU like armv6, using docker_platform matrix entries. Artifact upload path also cleaned up (no more matrix.target). Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/cd.yml | 46 +++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index bee99b09..02887495 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -27,9 +27,6 @@ jobs: runs_on: macos-15 - os: linux runs_on: ubuntu-22.04 - - os: linux - arch: aarch64 - runs_on: ubuntu-22.04-arm - os: linux arch: armv6 runs_on: ubuntu-latest @@ -40,10 +37,16 @@ jobs: dbus: dbus_mpris - artifact_type: full dbus: dbus_mpris - # Cross Compilation Targets + # Docker/QEMU build platforms + - os: linux + arch: armv6 + docker_platform: linux/arm/v6 - os: linux arch: armv7 - target: armv7-unknown-linux-gnueabihf + docker_platform: linux/arm/v7 + - os: linux + arch: aarch64 + docker_platform: linux/arm64 # Audio backend configuration: Linux - os: linux artifact_type: slim @@ -71,7 +74,7 @@ jobs: if: matrix.os == 'macos' run: brew install dbus portaudio - name: Installing needed Ubuntu dependencies - if: startsWith(matrix.runs_on, 'ubuntu') && matrix.target == '' && matrix.arch != 'armv6' + if: matrix.os == 'linux' && matrix.docker_platform == '' run: | sudo apt-get update # WORKAROUND: fix aws-lc-sys compiler bug failure @@ -92,43 +95,34 @@ jobs: features="--no-default-features --features ${{ matrix.dbus }},${{ matrix.audio_backends }}" echo CARGO_ARGS="--locked --release $features" | tee -a "$GITHUB_ENV" - name: Build (using cargo) - if: matrix.target == '' && matrix.arch != 'armv6' + if: matrix.docker_platform == '' run: | cargo +${{ matrix.rust }} build $CARGO_ARGS - - name: Build (using cross) - if: matrix.target != '' - uses: houseabsolute/actions-rust-cross@v1 - with: - cross-version: baf457e # currently needed (check again if cross version > 0.2.5) - command: build - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - args: $CARGO_ARGS - name: Set up QEMU - if: matrix.arch == 'armv6' + if: matrix.docker_platform != '' uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - if: matrix.arch == 'armv6' + if: matrix.docker_platform != '' uses: docker/setup-buildx-action@v3 - - name: Build (using docker buildx for armv6) - if: matrix.arch == 'armv6' + - name: Build (using docker buildx) + if: matrix.docker_platform != '' run: | docker buildx build \ - --platform linux/arm/v6 \ + --platform ${{ matrix.docker_platform }} \ --file .github/Dockerfile.armv6 \ --target export \ - --output type=local,dest=./armv6-out \ - --cache-from type=gha,scope=armv6-${{ matrix.artifact_type }} \ - --cache-to type=gha,scope=armv6-${{ matrix.artifact_type }},mode=max \ + --output type=local,dest=./${{ matrix.arch }}-out \ + --cache-from type=gha,scope=${{ matrix.arch }}-${{ matrix.artifact_type }} \ + --cache-to type=gha,scope=${{ matrix.arch }}-${{ matrix.artifact_type }},mode=max \ --build-arg FEATURES="--no-default-features --features ${{ matrix.dbus }},${{ matrix.audio_backends }}" \ . mkdir -p target/release - cp ./armv6-out/spotifyd target/release/spotifyd + cp ./${{ matrix.arch }}-out/spotifyd target/release/spotifyd - name: Uploading artifacts uses: actions/upload-artifact@v4 with: name: spotifyd-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.artifact_type }} - path: target/${{ matrix.target }}/release/spotifyd + path: target/release/spotifyd release: # only runs when a version tag is pushed if: startsWith(github.ref, 'refs/tags/v') needs: build From 425b2f078f8a19d1d8cbbdabf92d04c8e9f3882a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20R=C3=A9quillart?= Date: Sat, 21 Mar 2026 22:15:15 +0100 Subject: [PATCH 3/4] ci: add .deb packaging for Linux builds Generates a .deb per arch (amd64, arm64, armhf, armel) after each Linux build, with the user systemd service installed in /usr/lib/systemd/user/. Version is derived from the git tag; falls back to 0.0.0+ on master. .deb artifacts are published directly in GitHub releases alongside tar.gz. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/cd.yml | 49 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 02887495..6cbbee7e 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -118,11 +118,49 @@ jobs: . mkdir -p target/release cp ./${{ matrix.arch }}-out/spotifyd target/release/spotifyd + - name: Package .deb + if: matrix.os == 'linux' + run: | + case "${{ matrix.arch }}" in + x86_64) DEB_ARCH=amd64 ;; + aarch64) DEB_ARCH=arm64 ;; + armv7) DEB_ARCH=armhf ;; + armv6) DEB_ARCH=armel ;; + esac + VERSION="${GITHUB_REF_NAME#v}" + if [[ "$VERSION" == "$GITHUB_REF_NAME" ]]; then + VERSION="0.0.0+$(git rev-parse --short HEAD)" + fi + PKG="spotifyd_${VERSION}_${DEB_ARCH}" + mkdir -p "${PKG}/DEBIAN" \ + "${PKG}/usr/bin" \ + "${PKG}/usr/lib/systemd/user" + cp target/release/spotifyd "${PKG}/usr/bin/spotifyd" + cp contrib/spotifyd.service "${PKG}/usr/lib/systemd/user/spotifyd.service" + cat > "${PKG}/DEBIAN/control" < + Section: sound + Priority: optional + Description: A spotify playing daemon + Spotifyd streams music just like the official client, but is more + lightweight and can be run on devices that the Spotify app does not support. + EOF + dpkg-deb --build --root-owner-group "${PKG}" + echo "DEB_PATH=${PKG}.deb" >> "$GITHUB_ENV" - name: Uploading artifacts uses: actions/upload-artifact@v4 with: name: spotifyd-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.artifact_type }} path: target/release/spotifyd + - name: Uploading .deb artifact + if: matrix.os == 'linux' + uses: actions/upload-artifact@v4 + with: + name: spotifyd-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.artifact_type }}-deb + path: ${{ env.DEB_PATH }} release: # only runs when a version tag is pushed if: startsWith(github.ref, 'refs/tags/v') needs: build @@ -137,8 +175,14 @@ jobs: do pushd $artifact_dir artifact_name=$(basename $artifact_dir) - tar czvf $artifact_name.tar.gz spotifyd - shasum -a 512 $artifact_name.tar.gz > $artifact_name.sha512 + if ls *.deb 2>/dev/null; then + for deb in *.deb; do + shasum -a 512 "$deb" > "$deb.sha512" + done + else + tar czvf $artifact_name.tar.gz spotifyd + shasum -a 512 $artifact_name.tar.gz > $artifact_name.sha512 + fi popd done - name: Releasing assets @@ -146,6 +190,7 @@ jobs: with: files: | **/*.tar.gz + **/*.deb **/*.sha512 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 998ca0e8e8e5d6c6d26d8087530f46aff771e3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20R=C3=A9quillart?= Date: Sat, 21 Mar 2026 22:17:35 +0100 Subject: [PATCH 4/4] ci: trigger APT repo rebuild after release Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/cd.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6cbbee7e..c618acda 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -194,3 +194,18 @@ jobs: **/*.sha512 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + notify-apt-repo: + name: Trigger APT repo update + needs: release + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: {} + steps: + - name: Trigger apt-repo rebuild + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.APT_REPO_TOKEN }} + repository: b0bbywan/odio-apt-repo + event-type: release-published + client-payload: '{"repo": "${{ github.repository }}", "tag": "${{ github.ref_name }}"}'