From c10f797cde850a45064a579136e93847a119f6f8 Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 09:55:16 -0700 Subject: [PATCH 1/9] Add RPM (Fedora) and MSI (Windows) packaging - LICENSE (MIT) + Cargo.toml metadata (license, description, repository) - packaging/linux/systemd/presence-switch.service: per-user unit installed to /usr/lib/systemd/user/, started via `systemctl --user enable --now` - packaging/linux/rpm/presence-switch.spec: offline RPM build with vendored crate dependencies; Release: %{?_release} pattern keeps dev builds sorting below a future tagged release - wix/main.wxs: WiX 3 / wixl-compatible MSI, per-user install to %LOCALAPPDATA%, HKCU Run-key autostart (schtasks /SC ONLOGON requires admin so isn't usable from a non-elevated per-user MSI) - scripts/package.sh: local build orchestrator with rpm / msi / all subcommands; self-contains its PATH for rustup + dotnet tools - .github/workflows/package.yml: builds .rpm in fedora container and .msi via mingw-w64 cross-compile + wixl on ubuntu-latest, both as workflow artifacts on push to main, PR, or workflow_dispatch Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 130 ++++++++++++++++++ .gitignore | 1 + Cargo.toml | 3 + LICENSE | 21 +++ README.md | 32 +++++ packaging/linux/rpm/presence-switch.spec | 57 ++++++++ .../linux/systemd/presence-switch.service | 16 +++ scripts/package.sh | 118 ++++++++++++++++ wix/main.wxs | 116 ++++++++++++++++ 9 files changed, 494 insertions(+) create mode 100644 .github/workflows/package.yml create mode 100644 LICENSE create mode 100644 packaging/linux/rpm/presence-switch.spec create mode 100644 packaging/linux/systemd/presence-switch.service create mode 100755 scripts/package.sh create mode 100644 wix/main.wxs diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..a0ce637 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,130 @@ +name: package + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + rpm: + name: Build RPM (Fedora) + runs-on: ubuntu-latest + container: fedora:latest + + steps: + - name: Install build dependencies + run: | + dnf install -y \ + rust cargo \ + rpm-build rpmdevtools systemd-rpm-macros \ + git tar xz rsync + + - uses: actions/checkout@v4 + + - name: Compute version metadata + id: ver + run: | + version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) + short_sha="${GITHUB_SHA::7}" + rpm_release="0.dev.${GITHUB_RUN_NUMBER}.git${short_sha}" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + echo "rpm_release=$rpm_release" >> "$GITHUB_OUTPUT" + + - name: Vendor crate dependencies + run: cargo vendor vendor + + - name: Assemble source and vendor tarballs + run: | + set -euo pipefail + rpmdev-setuptree + name="presence-switch" + version="${{ steps.ver.outputs.version }}" + srcdir="${name}-${version}" + stage="$(mktemp -d)" + # Source tarball mirrors the checkout but excludes build output, the + # vendor directory (shipped separately), and VCS metadata. + rsync -a \ + --exclude='/target' \ + --exclude='/vendor' \ + --exclude='/.git' \ + ./ "${stage}/${srcdir}/" + tar -C "${stage}" -czf "${HOME}/rpmbuild/SOURCES/${srcdir}.tar.gz" "${srcdir}" + tar -cJf "${HOME}/rpmbuild/SOURCES/${srcdir}-vendor.tar.xz" vendor + + - name: Build RPM + run: | + rpmbuild -bb \ + --define "_release ${{ steps.ver.outputs.rpm_release }}" \ + packaging/linux/rpm/presence-switch.spec + + - name: Stage built RPMs + run: | + mkdir -p out + cp "${HOME}"/rpmbuild/RPMS/*/*.rpm out/ + + - name: Upload RPM artifact + uses: actions/upload-artifact@v4 + with: + name: presence-switch-rpm-${{ steps.ver.outputs.short_sha }} + path: out/*.rpm + if-no-files-found: error + + msi: + name: Build MSI (Linux cross-compile) + runs-on: ubuntu-latest + + env: + WIN_TARGET: x86_64-pc-windows-gnu + + steps: + - uses: actions/checkout@v4 + + - name: Install mingw-w64 and msitools + run: | + sudo apt-get update + sudo apt-get install -y gcc-mingw-w64-x86-64 msitools + + - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-gnu + + - uses: Swatinem/rust-cache@v2 + with: + key: windows-gnu + + - name: Compute version metadata + id: ver + run: | + version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) + short_sha="${GITHUB_SHA::7}" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + + - name: Cross-compile Windows binary + env: + # Rust auto-detects the mingw linker by triple, but be explicit so + # this doesn't silently regress if the apt package layout changes. + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc + run: cargo build --release --locked --target "${WIN_TARGET}" + + - name: Build MSI with wixl + run: | + mkdir -p target/wix + wixl \ + --arch x64 \ + -D "ExePath=target/${WIN_TARGET}/release/presence-switch.exe" \ + --output "target/wix/presence-switch-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.short_sha }}.msi" \ + wix/main.wxs + + - name: Upload MSI artifact + uses: actions/upload-artifact@v4 + with: + name: presence-switch-msi-${{ steps.ver.outputs.short_sha }} + path: target/wix/*.msi + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 4fd92fe..e335923 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +/vendor .claude/settings.local.json diff --git a/Cargo.toml b/Cargo.toml index 8fe246d..ba878da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ name = "presence-switch" version = "0.1.0" edition = "2024" +license = "MIT" +description = "Discord Rich Presence IPC proxy that multiplexes RPC messages across multiple Discord instances" +repository = "https://github.com/kramerc/presence-switch" [dependencies] lazy_static = "1.5.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..19147b8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Kramer Campbell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 500f196..e42e655 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,34 @@ The IPC binary protocol uses a simple format: 4-byte LE opcode + 4-byte LE lengt cargo build --release ``` +## Installing + +Pre-built dev artifacts are produced on every push to `main` by the [`package`](.github/workflows/package.yml) workflow. Grab the latest `.rpm` (Fedora/RHEL) or `.msi` (Windows) from the workflow run's Artifacts section. + +To build packages locally: + +```sh +scripts/package.sh rpm # → ~/rpmbuild/RPMS/x86_64/presence-switch-*.rpm +scripts/package.sh msi # → target/wix/presence-switch-*.msi (cross-compiled from Linux) +scripts/package.sh all +``` + +See `scripts/package.sh --help` for the toolchain requirements. + +### Linux (Fedora/RHEL) + +```sh +sudo dnf install ./presence-switch-*.rpm +systemctl --user daemon-reload +systemctl --user enable --now presence-switch +``` + +The package installs a per-user systemd unit at `/usr/lib/systemd/user/presence-switch.service`. View logs with `journalctl --user -u presence-switch`. + +### Windows + +Double-click the `.msi` to install per-user (no admin prompt). The installer registers a Scheduled Task that launches presence-switch at logon for the installing user. Uninstall via *Settings → Apps & features*. + ## Usage 1. Close Discord or ensure `discord-ipc-0` is not taken @@ -76,3 +104,7 @@ src/ ├── unix.rs # Unix domain socket connection └── windows.rs # Named pipe connection ``` + +## License + +[MIT](LICENSE) © Kramer Campbell diff --git a/packaging/linux/rpm/presence-switch.spec b/packaging/linux/rpm/presence-switch.spec new file mode 100644 index 0000000..ee50b27 --- /dev/null +++ b/packaging/linux/rpm/presence-switch.spec @@ -0,0 +1,57 @@ +Name: presence-switch +Version: 0.1.0 +# _release is overridden via `rpmbuild --define "_release ..."` for dev builds. +# When unset (e.g. a future tagged release), it falls back to `1`. +Release: %{?_release}%{!?_release:1}%{?dist} +Summary: Discord Rich Presence IPC proxy +License: MIT +URL: https://github.com/kramerc/presence-switch +Source0: %{name}-%{version}.tar.gz +# Vendored crate dependencies — required because Fedora build environments +# (mock, koji, COPR) run offline. +Source1: %{name}-%{version}-vendor.tar.xz + +BuildRequires: rust >= 1.85 +BuildRequires: cargo +BuildRequires: systemd-rpm-macros +ExclusiveArch: x86_64 aarch64 + +%description +A Discord Rich Presence IPC proxy that multiplexes RPC messages across +multiple running Discord instances. It binds the first available +discord-ipc-{0..9} socket name and relays incoming RPC frames to every +other Discord instance, so a single RPC client can broadcast its +presence to multiple Discord clients simultaneously. + +Designed to run as a per-user systemd service; enable with: + systemctl --user enable --now presence-switch + +%prep +%autosetup -n %{name}-%{version} +tar -xf %{SOURCE1} +mkdir -p .cargo +cat > .cargo/config.toml <<'EOF' +[source.crates-io] +replace-with = "vendored-sources" + +[source.vendored-sources] +directory = "vendor" +EOF + +%build +cargo build --release --locked --offline + +%install +install -Dm0755 target/release/presence-switch %{buildroot}%{_bindir}/presence-switch +install -Dm0644 packaging/linux/systemd/presence-switch.service \ + %{buildroot}%{_userunitdir}/presence-switch.service + +%files +%license LICENSE +%doc README.md +%{_bindir}/presence-switch +%{_userunitdir}/presence-switch.service + +%changelog +* Thu May 14 2026 Kramer Campbell - 0.1.0-1 +- Initial RPM package diff --git a/packaging/linux/systemd/presence-switch.service b/packaging/linux/systemd/presence-switch.service new file mode 100644 index 0000000..1664b2b --- /dev/null +++ b/packaging/linux/systemd/presence-switch.service @@ -0,0 +1,16 @@ +[Unit] +Description=Discord Rich Presence IPC proxy +Documentation=https://github.com/kramerc/presence-switch +After=graphical-session.target +PartOf=graphical-session.target + +[Service] +Type=simple +ExecStart=/usr/bin/presence-switch +Restart=on-failure +RestartSec=3 +NoNewPrivileges=true +ProtectSystem=strict + +[Install] +WantedBy=default.target diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..33335eb --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Local packaging script for presence-switch. +# +# Mirrors the steps run by .github/workflows/package.yml so you can reproduce +# CI builds (or just inspect failures) without pushing. +# +# Usage: +# scripts/package.sh rpm Build the .rpm into ~/rpmbuild/RPMS/ +# scripts/package.sh msi Cross-compile the Windows binary and build the .msi +# scripts/package.sh all Both +# +# Required for RPM: rpm-build rpmdevtools systemd-rpm-macros rsync cargo +# Required for MSI: mingw64-gcc msitools (provides wixl) +# plus rustup target x86_64-pc-windows-gnu + +set -euo pipefail + +# Ensure tooling installed by rustup and `dotnet tool install --global` is +# findable, regardless of whether the user has these dirs in their shell rc. +export PATH="$HOME/.dotnet/tools:$HOME/.cargo/bin:$PATH" + +cd "$(dirname "$0")/.." +ROOT=$(pwd) +NAME=presence-switch +VERSION=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) + +if SHORT_SHA=$(git rev-parse --short=7 HEAD 2>/dev/null); then + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + SHORT_SHA="${SHORT_SHA}.dirty" + fi +else + SHORT_SHA="nogit" +fi + +LOCAL_RELEASE="0.local.${SHORT_SHA}" + +log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } +need() { command -v "$1" >/dev/null 2>&1 || fail "$1 not found. $2"; } + +build_rpm() { + log "Building RPM name=${NAME} version=${VERSION} release=${LOCAL_RELEASE}" + + need rpmbuild "Install: sudo dnf install rpm-build" + need rpmdev-setuptree "Install: sudo dnf install rpmdevtools" + need rsync "Install: sudo dnf install rsync" + need cargo "Install rustup: https://rustup.rs" + + rpmdev-setuptree + + log "Vendoring crate dependencies" + cargo vendor vendor >/dev/null + + log "Assembling source + vendor tarballs" + local srcdir="${NAME}-${VERSION}" + local stage; stage=$(mktemp -d) + rsync -a \ + --exclude='/target' \ + --exclude='/vendor' \ + --exclude='/.git' \ + ./ "${stage}/${srcdir}/" + tar -C "${stage}" -czf "${HOME}/rpmbuild/SOURCES/${srcdir}.tar.gz" "${srcdir}" + tar -cJf "${HOME}/rpmbuild/SOURCES/${srcdir}-vendor.tar.xz" vendor + rm -rf "${stage}" + + log "Running rpmbuild" + # --nodeps: locally we trust whatever cargo/rust the user has (often + # rustup), instead of requiring the system `cargo` RPM that the spec's + # BuildRequires demands for CI builds inside a Fedora container. + rpmbuild -bb \ + --nodeps \ + --define "_release ${LOCAL_RELEASE}" \ + packaging/linux/rpm/presence-switch.spec + + local built + built=$(find "${HOME}/rpmbuild/RPMS" -name "${NAME}-${VERSION}-${LOCAL_RELEASE}*.rpm" | head -n1) + log "Built RPM: ${built}" +} + +build_msi() { + log "Building MSI name=${NAME} version=${VERSION} sha=${SHORT_SHA}" + + need cargo "Install rustup: https://rustup.rs" + need x86_64-w64-mingw32-gcc "Install: sudo dnf install mingw64-gcc" + need wixl "Install: sudo dnf install msitools" + + if ! rustup target list --installed 2>/dev/null | grep -q '^x86_64-pc-windows-gnu$'; then + log "Adding rustup target x86_64-pc-windows-gnu" + rustup target add x86_64-pc-windows-gnu + fi + + log "Cross-compiling presence-switch.exe" + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc \ + cargo build --release --locked --target x86_64-pc-windows-gnu + + log "Linking MSI" + mkdir -p target/wix + local out="target/wix/${NAME}-${VERSION}-${SHORT_SHA}.msi" + wixl \ + --arch x64 \ + -D "ExePath=target/x86_64-pc-windows-gnu/release/${NAME}.exe" \ + --output "${out}" \ + wix/main.wxs + log "Built MSI: ${ROOT}/${out}" +} + +usage() { + sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//' +} + +case "${1:-}" in + rpm) build_rpm ;; + msi) build_msi ;; + all) build_rpm; build_msi ;; + -h|--help) usage; exit 0 ;; + "") usage; exit 1 ;; + *) fail "Unknown command: $1 (try 'rpm', 'msi', 'all', or --help)" ;; +esac diff --git a/wix/main.wxs b/wix/main.wxs new file mode 100644 index 0000000..f56ede8 --- /dev/null +++ b/wix/main.wxs @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4ec72d99d2f6052c9fbc7c5afea653f25fe8aabd Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 10:01:47 -0700 Subject: [PATCH 2/9] Install wixl separately on Ubuntu (Debian splits it from msitools) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index a0ce637..9495e35 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -85,10 +85,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install mingw-w64 and msitools + - name: Install mingw-w64 and wixl + # Debian/Ubuntu split wixl out of msitools — install both. run: | sudo apt-get update - sudo apt-get install -y gcc-mingw-w64-x86-64 msitools + sudo apt-get install -y gcc-mingw-w64-x86-64 msitools wixl - uses: dtolnay/rust-toolchain@stable with: From becbe55c7ba7ca3eba089e9916c81ef11d60aea0 Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 10:11:17 -0700 Subject: [PATCH 3/9] Capitalize package workflow name --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 9495e35..fb24d7a 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,4 +1,4 @@ -name: package +name: Package on: push: From c2f52d261411ab98c5745a85ec147e0d23e53d37 Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 10:14:05 -0700 Subject: [PATCH 4/9] Address Copilot review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wix/main.wxs: thread Version through $(var.Version) instead of hardcoding 0.1.0, so MajorUpgrade detection stays correct as Cargo.toml version bumps - README: describe the HKCU Run-key autostart instead of the (rejected) Scheduled Task approach - systemd unit: drop ProtectSystem=strict (too aggressive for user units — it makes $HOME read-only, which would break any future state-write) - scripts/package.sh: drop dead $HOME/.dotnet/tools PATH entry (wixl is apt/dnf-installed, not a dotnet global tool); replace brittle sed line numbers in usage() with a heredoc; pass -D Version=$VERSION to wixl; trap to clean up ./vendor after the rpm build - .github/workflows/package.yml: pass -D Version to wixl in CI as well Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 1 + README.md | 2 +- .../linux/systemd/presence-switch.service | 1 - scripts/package.sh | 28 ++++++++++++++++--- wix/main.wxs | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index fb24d7a..124bb99 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -119,6 +119,7 @@ jobs: mkdir -p target/wix wixl \ --arch x64 \ + -D "Version=${{ steps.ver.outputs.version }}" \ -D "ExePath=target/${WIN_TARGET}/release/presence-switch.exe" \ --output "target/wix/presence-switch-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.short_sha }}.msi" \ wix/main.wxs diff --git a/README.md b/README.md index e42e655..c2a5fc7 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ The package installs a per-user systemd unit at `/usr/lib/systemd/user/presence- ### Windows -Double-click the `.msi` to install per-user (no admin prompt). The installer registers a Scheduled Task that launches presence-switch at logon for the installing user. Uninstall via *Settings → Apps & features*. +Double-click the `.msi` to install per-user (no admin prompt). The installer adds an entry under `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` so presence-switch launches at every logon — inspect or disable it via *Task Manager → Startup apps*. Uninstall via *Settings → Apps & features*. ## Usage diff --git a/packaging/linux/systemd/presence-switch.service b/packaging/linux/systemd/presence-switch.service index 1664b2b..1d5d8c1 100644 --- a/packaging/linux/systemd/presence-switch.service +++ b/packaging/linux/systemd/presence-switch.service @@ -10,7 +10,6 @@ ExecStart=/usr/bin/presence-switch Restart=on-failure RestartSec=3 NoNewPrivileges=true -ProtectSystem=strict [Install] WantedBy=default.target diff --git a/scripts/package.sh b/scripts/package.sh index 33335eb..3605d65 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -15,9 +15,9 @@ set -euo pipefail -# Ensure tooling installed by rustup and `dotnet tool install --global` is -# findable, regardless of whether the user has these dirs in their shell rc. -export PATH="$HOME/.dotnet/tools:$HOME/.cargo/bin:$PATH" +# Ensure tooling installed by rustup is findable, regardless of whether the +# user has ~/.cargo/bin in their shell rc. +export PATH="$HOME/.cargo/bin:$PATH" cd "$(dirname "$0")/.." ROOT=$(pwd) @@ -50,6 +50,9 @@ build_rpm() { log "Vendoring crate dependencies" cargo vendor vendor >/dev/null + # Clean up the vendor tree at end of run so subsequent local `cargo build` + # invocations don't unknowingly operate against offline-vendored deps. + trap 'rm -rf vendor' RETURN log "Assembling source + vendor tarballs" local srcdir="${NAME}-${VERSION}" @@ -96,8 +99,11 @@ build_msi() { log "Linking MSI" mkdir -p target/wix local out="target/wix/${NAME}-${VERSION}-${SHORT_SHA}.msi" + # -D Version threads the Cargo.toml version into the MSI's ProductVersion + # so MajorUpgrade detection stays correct as the project version bumps. wixl \ --arch x64 \ + -D "Version=${VERSION}" \ -D "ExePath=target/x86_64-pc-windows-gnu/release/${NAME}.exe" \ --output "${out}" \ wix/main.wxs @@ -105,7 +111,21 @@ build_msi() { } usage() { - sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//' + cat < From 14cd0b9b46e6495b3c340700bebbfe7c3326da3f Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 10:28:31 -0700 Subject: [PATCH 5/9] Wire up release-tag handling in the package workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Trigger on push to `v*` tags in addition to main / PR / dispatch. - Tag pushes validate `tag_version == Cargo.toml version` and fail loudly if mismatched; otherwise they emit clean release artifacts (RPM `Release: 1%{?dist}`, MSI filename without short-sha suffix). - New `release` job: runs only on tag push, depends on both build jobs, downloads their artifacts, and publishes a GitHub Release for the tag with `gh release create --generate-notes --verify-tag`. - README: add Releasing section documenting the bump → merge → tag flow, and point the Installing section at Releases for tagged builds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 95 +++++++++++++++++++++++++++++++---- README.md | 17 ++++++- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 124bb99..dc8ed8a 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -3,6 +3,7 @@ name: Package on: push: branches: [main] + tags: ['v*'] pull_request: branches: [main] workflow_dispatch: @@ -28,13 +29,34 @@ jobs: - name: Compute version metadata id: ver + # On tag pushes (refs/tags/v*) emit a clean release (`Release: 1`) and + # validate that the tag matches Cargo.toml; on all other triggers emit + # a dev-build release tag with run number + short SHA. run: | + set -euo pipefail version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) short_sha="${GITHUB_SHA::7}" - rpm_release="0.dev.${GITHUB_RUN_NUMBER}.git${short_sha}" - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" - echo "rpm_release=$rpm_release" >> "$GITHUB_OUTPUT" + + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + tag_version="${GITHUB_REF_NAME#v}" + if [ "$tag_version" != "$version" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${version}" >&2 + exit 1 + fi + rpm_release="1" + artifact_name="presence-switch-rpm-${GITHUB_REF_NAME}" + is_release="true" + else + rpm_release="0.dev.${GITHUB_RUN_NUMBER}.git${short_sha}" + artifact_name="presence-switch-rpm-${short_sha}" + is_release="false" + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + echo "rpm_release=$rpm_release" >> "$GITHUB_OUTPUT" + echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT" + echo "is_release=$is_release" >> "$GITHUB_OUTPUT" - name: Vendor crate dependencies run: cargo vendor vendor @@ -71,7 +93,7 @@ jobs: - name: Upload RPM artifact uses: actions/upload-artifact@v4 with: - name: presence-switch-rpm-${{ steps.ver.outputs.short_sha }} + name: ${{ steps.ver.outputs.artifact_name }} path: out/*.rpm if-no-files-found: error @@ -102,10 +124,27 @@ jobs: - name: Compute version metadata id: ver run: | + set -euo pipefail version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) short_sha="${GITHUB_SHA::7}" - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + tag_version="${GITHUB_REF_NAME#v}" + if [ "$tag_version" != "$version" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${version}" >&2 + exit 1 + fi + msi_filename="presence-switch-${version}.msi" + artifact_name="presence-switch-msi-${GITHUB_REF_NAME}" + else + msi_filename="presence-switch-${version}-${short_sha}.msi" + artifact_name="presence-switch-msi-${short_sha}" + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + echo "msi_filename=$msi_filename" >> "$GITHUB_OUTPUT" + echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT" - name: Cross-compile Windows binary env: @@ -121,12 +160,50 @@ jobs: --arch x64 \ -D "Version=${{ steps.ver.outputs.version }}" \ -D "ExePath=target/${WIN_TARGET}/release/presence-switch.exe" \ - --output "target/wix/presence-switch-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.short_sha }}.msi" \ + --output "target/wix/${{ steps.ver.outputs.msi_filename }}" \ wix/main.wxs - name: Upload MSI artifact uses: actions/upload-artifact@v4 with: - name: presence-switch-msi-${{ steps.ver.outputs.short_sha }} + name: ${{ steps.ver.outputs.artifact_name }} path: target/wix/*.msi if-no-files-found: error + + release: + name: Publish GitHub Release + needs: [rpm, msi] + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download RPM artifact + uses: actions/download-artifact@v4 + with: + name: presence-switch-rpm-${{ github.ref_name }} + path: artifacts/ + + - name: Download MSI artifact + uses: actions/download-artifact@v4 + with: + name: presence-switch-msi-${{ github.ref_name }} + path: artifacts/ + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + ls -la artifacts/ + # --generate-notes auto-generates the body from commits since last + # release; --verify-tag fails fast if the tag isn't actually pushed. + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + --verify-tag \ + artifacts/*.rpm artifacts/*.msi diff --git a/README.md b/README.md index c2a5fc7..7e5ca4e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ cargo build --release ## Installing -Pre-built dev artifacts are produced on every push to `main` by the [`package`](.github/workflows/package.yml) workflow. Grab the latest `.rpm` (Fedora/RHEL) or `.msi` (Windows) from the workflow run's Artifacts section. +Tagged releases publish `.rpm` and `.msi` builds to the [Releases](https://github.com/kramerc/presence-switch/releases) page. For unreleased changes, the [`Package`](.github/workflows/package.yml) workflow also produces dev artifacts on every push to `main` and every PR — download them from the workflow run's Artifacts section. To build packages locally: @@ -105,6 +105,21 @@ src/ └── windows.rs # Named pipe connection ``` +## Releasing + +To cut a new release: + +1. Bump `version` in `Cargo.toml` (and run `cargo update -w` so `Cargo.lock` matches). +2. Commit the bump and merge to `main`. +3. Tag the commit on `main` matching the new version, e.g.: + ```sh + git tag v0.2.0 + git push origin v0.2.0 + ``` +4. The [`Package`](.github/workflows/package.yml) workflow runs on the tag, validates that the tag matches `Cargo.toml`, builds the `.rpm` and `.msi`, and publishes a GitHub Release with both attached and auto-generated notes from the commits since the previous release. + +If the tag version doesn't match `Cargo.toml`'s `version` field, both build jobs fail loudly before doing any work. + ## License [MIT](LICENSE) © Kramer Campbell From 4e9030514015e34b28d84cd2bdc935727cb8da80 Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 10:34:26 -0700 Subject: [PATCH 6/9] Thread version into the RPM spec via --define _version Previously the spec hardcoded `Version: 0.1.0`, so future version bumps would either silently ship the wrong NEVRA or require touching the spec every release. Mirror the wxs `$(var.Version)` pattern: the spec now declares `Version: %{_version}`, and both `scripts/package.sh` and `.github/workflows/package.yml` pass `--define "_version $VERSION"` sourced from Cargo.toml. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 1 + packaging/linux/rpm/presence-switch.spec | 5 ++++- scripts/package.sh | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index dc8ed8a..7a4d98b 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -82,6 +82,7 @@ jobs: - name: Build RPM run: | rpmbuild -bb \ + --define "_version ${{ steps.ver.outputs.version }}" \ --define "_release ${{ steps.ver.outputs.rpm_release }}" \ packaging/linux/rpm/presence-switch.spec diff --git a/packaging/linux/rpm/presence-switch.spec b/packaging/linux/rpm/presence-switch.spec index ee50b27..3fdf073 100644 --- a/packaging/linux/rpm/presence-switch.spec +++ b/packaging/linux/rpm/presence-switch.spec @@ -1,5 +1,8 @@ Name: presence-switch -Version: 0.1.0 +# _version is passed via `rpmbuild --define "_version ..."` so the CI workflow +# and local build script can thread Cargo.toml's version through without +# requiring a manual edit here for each release. +Version: %{_version} # _release is overridden via `rpmbuild --define "_release ..."` for dev builds. # When unset (e.g. a future tagged release), it falls back to `1`. Release: %{?_release}%{!?_release:1}%{?dist} diff --git a/scripts/package.sh b/scripts/package.sh index 3605d65..18b9eec 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -72,6 +72,7 @@ build_rpm() { # BuildRequires demands for CI builds inside a Fedora container. rpmbuild -bb \ --nodeps \ + --define "_version ${VERSION}" \ --define "_release ${LOCAL_RELEASE}" \ packaging/linux/rpm/presence-switch.spec From 56d53c8a9b052f89d84b431acb518b363389fa0d Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 11:10:08 -0700 Subject: [PATCH 7/9] Switch RPM build to cargo-generate-rpm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the spec file + rpmbuild pipeline in favor of a Cargo.toml-native package definition. Net result: ~40 fewer lines, ~50% fewer moving parts. - Cargo.toml gains a `[package.metadata.generate-rpm]` block listing the install assets (binary, systemd user unit, LICENSE, README). name, version, license, summary, and url are inherited from `[package]`. - The rpm job no longer runs in a `fedora:latest` container; runs on plain `ubuntu-latest` like the msi job. Both jobs are now symmetric: cargo build → cargo-{generate-rpm|wix-equivalent}. - Drops: vendor offline-build setup, source-and-vendor tarball assembly, rpmdev-setuptree, rpmbuild, --define _version/_release plumbing, the spec file itself, the now-empty packaging/linux/rpm/ directory. - Release tag for dev builds is supplied at invocation time via `cargo generate-rpm -s 'release = "0.dev..git"'`; for tagged releases the default `release = "1"` from Cargo.toml metadata is used. - Local script and README updated to reference target/generate-rpm/. What we give up: debuginfo subpackages (rpmbuild auto-generated, not useful for end-user distribution of a small daemon) and the `%{?dist}` suffix in the Release field (only relevant for per-Fedora-version artifacts, which we're not shipping). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package.yml | 65 +++++++----------------- Cargo.toml | 13 +++++ README.md | 2 +- packaging/linux/rpm/presence-switch.spec | 60 ---------------------- scripts/package.sh | 58 +++++++-------------- 5 files changed, 50 insertions(+), 148 deletions(-) delete mode 100644 packaging/linux/rpm/presence-switch.spec diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 7a4d98b..ec41fb8 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -13,25 +13,24 @@ env: jobs: rpm: - name: Build RPM (Fedora) + name: Build RPM runs-on: ubuntu-latest - container: fedora:latest steps: - - name: Install build dependencies - run: | - dnf install -y \ - rust cargo \ - rpm-build rpmdevtools systemd-rpm-macros \ - git tar xz rsync - - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Install cargo-generate-rpm + run: cargo install cargo-generate-rpm --locked + - name: Compute version metadata id: ver - # On tag pushes (refs/tags/v*) emit a clean release (`Release: 1`) and - # validate that the tag matches Cargo.toml; on all other triggers emit - # a dev-build release tag with run number + short SHA. + # On tag pushes (refs/tags/v*) emit a clean release (`release = "1"`) + # and validate that the tag matches Cargo.toml; on all other triggers + # emit a dev-build release tag with run number + short SHA. run: | set -euo pipefail version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2) @@ -45,57 +44,31 @@ jobs: fi rpm_release="1" artifact_name="presence-switch-rpm-${GITHUB_REF_NAME}" - is_release="true" else rpm_release="0.dev.${GITHUB_RUN_NUMBER}.git${short_sha}" artifact_name="presence-switch-rpm-${short_sha}" - is_release="false" fi echo "version=$version" >> "$GITHUB_OUTPUT" echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" echo "rpm_release=$rpm_release" >> "$GITHUB_OUTPUT" echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT" - echo "is_release=$is_release" >> "$GITHUB_OUTPUT" - - - name: Vendor crate dependencies - run: cargo vendor vendor - - name: Assemble source and vendor tarballs - run: | - set -euo pipefail - rpmdev-setuptree - name="presence-switch" - version="${{ steps.ver.outputs.version }}" - srcdir="${name}-${version}" - stage="$(mktemp -d)" - # Source tarball mirrors the checkout but excludes build output, the - # vendor directory (shipped separately), and VCS metadata. - rsync -a \ - --exclude='/target' \ - --exclude='/vendor' \ - --exclude='/.git' \ - ./ "${stage}/${srcdir}/" - tar -C "${stage}" -czf "${HOME}/rpmbuild/SOURCES/${srcdir}.tar.gz" "${srcdir}" - tar -cJf "${HOME}/rpmbuild/SOURCES/${srcdir}-vendor.tar.xz" vendor - - - name: Build RPM - run: | - rpmbuild -bb \ - --define "_version ${{ steps.ver.outputs.version }}" \ - --define "_release ${{ steps.ver.outputs.rpm_release }}" \ - packaging/linux/rpm/presence-switch.spec + - name: Compile release binary + run: cargo build --release --locked - - name: Stage built RPMs + - name: Generate RPM run: | - mkdir -p out - cp "${HOME}"/rpmbuild/RPMS/*/*.rpm out/ + mkdir -p target/generate-rpm + cargo generate-rpm \ + --output "target/generate-rpm/presence-switch-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.rpm_release }}.x86_64.rpm" \ + -s 'release = "${{ steps.ver.outputs.rpm_release }}"' - name: Upload RPM artifact uses: actions/upload-artifact@v4 with: name: ${{ steps.ver.outputs.artifact_name }} - path: out/*.rpm + path: target/generate-rpm/*.rpm if-no-files-found: error msi: diff --git a/Cargo.toml b/Cargo.toml index ba878da..4659c51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,16 @@ tokio-util = "0.7.18" tracing = "0.1.44" tracing-subscriber = "0.3.23" windows-sys = "0.61.2" + +# RPM packaging via cargo-generate-rpm. +# `name`, `version`, `license`, and `summary` are inherited from [package]. +# Override `release` at build time for dev builds: +# cargo generate-rpm -s 'release = "0.dev..git"' +[package.metadata.generate-rpm] +release = "1" +assets = [ + { source = "target/release/presence-switch", dest = "/usr/bin/presence-switch", mode = "755" }, + { source = "packaging/linux/systemd/presence-switch.service", dest = "/usr/lib/systemd/user/presence-switch.service", mode = "644" }, + { source = "LICENSE", dest = "/usr/share/licenses/presence-switch/LICENSE", mode = "644", doc = true }, + { source = "README.md", dest = "/usr/share/doc/presence-switch/README.md", mode = "644", doc = true }, +] diff --git a/README.md b/README.md index 7e5ca4e..c3af335 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Tagged releases publish `.rpm` and `.msi` builds to the [Releases](https://githu To build packages locally: ```sh -scripts/package.sh rpm # → ~/rpmbuild/RPMS/x86_64/presence-switch-*.rpm +scripts/package.sh rpm # → target/generate-rpm/presence-switch-*.rpm scripts/package.sh msi # → target/wix/presence-switch-*.msi (cross-compiled from Linux) scripts/package.sh all ``` diff --git a/packaging/linux/rpm/presence-switch.spec b/packaging/linux/rpm/presence-switch.spec deleted file mode 100644 index 3fdf073..0000000 --- a/packaging/linux/rpm/presence-switch.spec +++ /dev/null @@ -1,60 +0,0 @@ -Name: presence-switch -# _version is passed via `rpmbuild --define "_version ..."` so the CI workflow -# and local build script can thread Cargo.toml's version through without -# requiring a manual edit here for each release. -Version: %{_version} -# _release is overridden via `rpmbuild --define "_release ..."` for dev builds. -# When unset (e.g. a future tagged release), it falls back to `1`. -Release: %{?_release}%{!?_release:1}%{?dist} -Summary: Discord Rich Presence IPC proxy -License: MIT -URL: https://github.com/kramerc/presence-switch -Source0: %{name}-%{version}.tar.gz -# Vendored crate dependencies — required because Fedora build environments -# (mock, koji, COPR) run offline. -Source1: %{name}-%{version}-vendor.tar.xz - -BuildRequires: rust >= 1.85 -BuildRequires: cargo -BuildRequires: systemd-rpm-macros -ExclusiveArch: x86_64 aarch64 - -%description -A Discord Rich Presence IPC proxy that multiplexes RPC messages across -multiple running Discord instances. It binds the first available -discord-ipc-{0..9} socket name and relays incoming RPC frames to every -other Discord instance, so a single RPC client can broadcast its -presence to multiple Discord clients simultaneously. - -Designed to run as a per-user systemd service; enable with: - systemctl --user enable --now presence-switch - -%prep -%autosetup -n %{name}-%{version} -tar -xf %{SOURCE1} -mkdir -p .cargo -cat > .cargo/config.toml <<'EOF' -[source.crates-io] -replace-with = "vendored-sources" - -[source.vendored-sources] -directory = "vendor" -EOF - -%build -cargo build --release --locked --offline - -%install -install -Dm0755 target/release/presence-switch %{buildroot}%{_bindir}/presence-switch -install -Dm0644 packaging/linux/systemd/presence-switch.service \ - %{buildroot}%{_userunitdir}/presence-switch.service - -%files -%license LICENSE -%doc README.md -%{_bindir}/presence-switch -%{_userunitdir}/presence-switch.service - -%changelog -* Thu May 14 2026 Kramer Campbell - 0.1.0-1 -- Initial RPM package diff --git a/scripts/package.sh b/scripts/package.sh index 18b9eec..cd3eae4 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -5,11 +5,11 @@ # CI builds (or just inspect failures) without pushing. # # Usage: -# scripts/package.sh rpm Build the .rpm into ~/rpmbuild/RPMS/ +# scripts/package.sh rpm Build the .rpm into target/generate-rpm/ # scripts/package.sh msi Cross-compile the Windows binary and build the .msi # scripts/package.sh all Both # -# Required for RPM: rpm-build rpmdevtools systemd-rpm-macros rsync cargo +# Required for RPM: cargo, cargo-generate-rpm (cargo install cargo-generate-rpm) # Required for MSI: mingw64-gcc msitools (provides wixl) # plus rustup target x86_64-pc-windows-gnu @@ -41,43 +41,19 @@ need() { command -v "$1" >/dev/null 2>&1 || fail "$1 not found. $2"; } build_rpm() { log "Building RPM name=${NAME} version=${VERSION} release=${LOCAL_RELEASE}" - need rpmbuild "Install: sudo dnf install rpm-build" - need rpmdev-setuptree "Install: sudo dnf install rpmdevtools" - need rsync "Install: sudo dnf install rsync" - need cargo "Install rustup: https://rustup.rs" - - rpmdev-setuptree - - log "Vendoring crate dependencies" - cargo vendor vendor >/dev/null - # Clean up the vendor tree at end of run so subsequent local `cargo build` - # invocations don't unknowingly operate against offline-vendored deps. - trap 'rm -rf vendor' RETURN - - log "Assembling source + vendor tarballs" - local srcdir="${NAME}-${VERSION}" - local stage; stage=$(mktemp -d) - rsync -a \ - --exclude='/target' \ - --exclude='/vendor' \ - --exclude='/.git' \ - ./ "${stage}/${srcdir}/" - tar -C "${stage}" -czf "${HOME}/rpmbuild/SOURCES/${srcdir}.tar.gz" "${srcdir}" - tar -cJf "${HOME}/rpmbuild/SOURCES/${srcdir}-vendor.tar.xz" vendor - rm -rf "${stage}" - - log "Running rpmbuild" - # --nodeps: locally we trust whatever cargo/rust the user has (often - # rustup), instead of requiring the system `cargo` RPM that the spec's - # BuildRequires demands for CI builds inside a Fedora container. - rpmbuild -bb \ - --nodeps \ - --define "_version ${VERSION}" \ - --define "_release ${LOCAL_RELEASE}" \ - packaging/linux/rpm/presence-switch.spec - - local built - built=$(find "${HOME}/rpmbuild/RPMS" -name "${NAME}-${VERSION}-${LOCAL_RELEASE}*.rpm" | head -n1) + need cargo "Install rustup: https://rustup.rs" + need cargo-generate-rpm "Install: cargo install cargo-generate-rpm --locked" + + log "Compiling release binary" + cargo build --release --locked + + log "Generating RPM" + mkdir -p target/generate-rpm + cargo generate-rpm \ + --output "target/generate-rpm/${NAME}-${VERSION}-${LOCAL_RELEASE}.x86_64.rpm" \ + -s "release = \"${LOCAL_RELEASE}\"" + + local built="${ROOT}/target/generate-rpm/${NAME}-${VERSION}-${LOCAL_RELEASE}.x86_64.rpm" log "Built RPM: ${built}" } @@ -119,11 +95,11 @@ Mirrors the steps run by .github/workflows/package.yml so you can reproduce CI builds (or just inspect failures) without pushing. Usage: - scripts/package.sh rpm Build the .rpm into ~/rpmbuild/RPMS/ + scripts/package.sh rpm Build the .rpm into target/generate-rpm/ scripts/package.sh msi Cross-compile the Windows binary and build the .msi scripts/package.sh all Both -Required for RPM: rpm-build rpmdevtools systemd-rpm-macros rsync cargo +Required for RPM: cargo, cargo-generate-rpm (cargo install cargo-generate-rpm) Required for MSI: mingw64-gcc msitools (provides wixl) plus rustup target x86_64-pc-windows-gnu EOF From 110cd73076dcc28ad92be0d34485f6c408ff2912 Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 11:12:31 -0700 Subject: [PATCH 8/9] Broaden Linux install docs beyond Fedora The cargo-generate-rpm switch removed the Fedora-specific bits (no %{?dist} stamping, no systemd-rpm-macros, no fedora:latest container), so the produced RPM works on any RPM distro with systemd and a compatible glibc. Update the README install section to call that out explicitly: list dnf and zypper examples, note the glibc 2.39 baseline from the Ubuntu-24.04-based CI runner, and broaden the section title. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c3af335..d4ee20d 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,16 @@ scripts/package.sh all See `scripts/package.sh --help` for the toolchain requirements. -### Linux (Fedora/RHEL) +### Linux (any RPM-based distro with systemd) ```sh -sudo dnf install ./presence-switch-*.rpm +sudo dnf install ./presence-switch-*.rpm # Fedora, RHEL, CentOS, Rocky, Alma +sudo zypper install ./presence-switch-*.rpm # openSUSE systemctl --user daemon-reload systemctl --user enable --now presence-switch ``` -The package installs a per-user systemd unit at `/usr/lib/systemd/user/presence-switch.service`. View logs with `journalctl --user -u presence-switch`. +The package installs a per-user systemd unit at `/usr/lib/systemd/user/presence-switch.service`. View logs with `journalctl --user -u presence-switch`. CI builds the RPM against Ubuntu 24.04's glibc (2.39+), so the target distro needs glibc ≥ 2.39 — that covers Fedora 41+, RHEL 10+, recent openSUSE Tumbleweed, and similar. ### Windows From 92e178be89f4bdf49b637fffcf7a92452a8a123a Mon Sep 17 00:00:00 2001 From: Kramer Campbell Date: Thu, 14 May 2026 11:39:26 -0700 Subject: [PATCH 9/9] Bump GitHub-official actions to Node-24 versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub deprecated Node 20 for JavaScript actions; v4 of checkout/upload/ download-artifact run on Node 20 and warn loudly. They'll be force-flipped to Node 24 on 2026-06-02 anyway, but the cleaner fix is to pin the newer majors that ship with Node 24 baked in. - actions/checkout@v4 → v6 (Nov 2025) - actions/upload-artifact@v4 → v7 (Apr 2026) - actions/download-artifact@v4 → v8 (Mar 2026) Swatinem/rust-cache@v2 is already on Node 24 (v2.9.x runs node24), and dtolnay/rust-toolchain is a composite action with no JS runtime concern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/package.yml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01cfcfc..e733f46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy @@ -23,7 +23,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index ec41fb8..d0dfcba 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable @@ -65,7 +65,7 @@ jobs: -s 'release = "${{ steps.ver.outputs.rpm_release }}"' - name: Upload RPM artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ steps.ver.outputs.artifact_name }} path: target/generate-rpm/*.rpm @@ -79,7 +79,7 @@ jobs: WIN_TARGET: x86_64-pc-windows-gnu steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install mingw-w64 and wixl # Debian/Ubuntu split wixl out of msitools — install both. @@ -138,7 +138,7 @@ jobs: wix/main.wxs - name: Upload MSI artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ steps.ver.outputs.artifact_name }} path: target/wix/*.msi @@ -154,16 +154,16 @@ jobs: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Download RPM artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: presence-switch-rpm-${{ github.ref_name }} path: artifacts/ - name: Download MSI artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: presence-switch-msi-${{ github.ref_name }} path: artifacts/