scythe/.github/workflows/release.yml
spearzificial 2bd1e7c598 fix(packaging): make the AppImage self-contained and restore the RPM libva requirement
Two defects found by inspecting the published v0.2.19 artifacts.

The AppImage contained the binary and the desktop file and nothing else.
The binary links FFmpeg, PipeWire, ALSA and XCB directly, so the image
only ran on a host that happened to carry the exact FFmpeg build the
binary was linked against, which is the opposite of what an AppImage is
for. The packaging step now bundles the shared library closure that ldd
resolves, points the binary at it with an RPATH of $ORIGIN/../lib, and
prepends it to LD_LIBRARY_PATH in AppRun. The C runtime is deliberately
left on the host, because bundling a different glibc than the rest of
the system expects produces failures that are very hard to trace. The
step fails if fewer than five libraries were collected or if the image
was not produced, so a silently empty image can never ship.

alien drops libva2 when it converts the Debian package to an RPM,
because it has no Debian to RPM name rule for it. libva is loaded at
runtime by the FFmpeg VAAPI device, so the requirement is now added back
explicitly with rpm --addrequires and the build fails if it is missing
from the finished package. Without it an RPM install would leave
hardware encoding unavailable until the user noticed and installed libva
by hand.

Version bumped to 0.2.20 across the manifest, lock file, RPM spec, AUR
PKGBUILD and the guide.
2026-10-21 17:02:00 +05:30

252 lines
10 KiB
YAML

name: Release & Packaging
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux:
name: Build Linux Packages (.tar.gz, .deb, .rpm, .AppImage)
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config clang libva-dev \
libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev \
libavfilter-dev libavdevice-dev \
libpipewire-0.3-dev libgtk-3-dev libgtk-layer-shell-dev libxcb1-dev libasound2-dev \
rpm alien wget patchelf
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build Release Binaries
run: cargo build --release
- name: Package Linux Tarball (Unified Binary)
run: |
mkdir -p dist/scythe-linux-x86_64
cp target/release/scythe dist/scythe-linux-x86_64/
ln -sf scythe dist/scythe-linux-x86_64/scythe-daemon
ln -sf scythe dist/scythe-linux-x86_64/scythe-ui
ln -sf scythe dist/scythe-linux-x86_64/vrec-daemon
ln -sf scythe dist/scythe-linux-x86_64/vrec-ui
ln -sf scythe dist/scythe-linux-x86_64/vrec
cp packaging/scythe.desktop dist/scythe-linux-x86_64/
tar -czvf dist/scythe-linux-x86_64.tar.gz -C dist scythe-linux-x86_64
- name: Build Debian Package (.deb)
run: |
VERSION="${GITHUB_REF_NAME#v}"
if [ -z "$VERSION" ] || [ "$VERSION" = "main" ]; then
VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2)
fi
echo "Packaging Debian version: ${VERSION}"
mkdir -p "deb/scythe_${VERSION}/DEBIAN" "deb/scythe_${VERSION}/usr/bin" "deb/scythe_${VERSION}/usr/share/applications"
cat > "deb/scythe_${VERSION}/DEBIAN/control" << EOF
Package: scythe
Provides: vrec
Version: ${VERSION}
Section: video
Priority: optional
Architecture: amd64
Maintainer: oiupoyt
Description: High-performance GPU hardware screen recorder and instant replay overlay
Depends: libavcodec60, libavformat60, libavutil58, libswresample4, libswscale7, libpipewire-0.3-0, libxcb1, libasound2, libva2
EOF
cp target/release/scythe "deb/scythe_${VERSION}/usr/bin/scythe"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-daemon"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-ui"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-daemon"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-ui"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec"
cp packaging/scythe.desktop "deb/scythe_${VERSION}/usr/share/applications/"
dpkg-deb --build "deb/scythe_${VERSION}" "dist/scythe_${VERSION}_amd64.deb"
- name: Build RPM Package (.rpm)
run: |
DEB_FILE=$(ls dist/*.deb | head -n 1)
alien --to-rpm --scripts "$DEB_FILE"
mv *.rpm dist/
# alien resolves the Debian package names to sonames, but it drops
# libva2 because there is no matching Debian to RPM name rule.
# libva is loaded at runtime by the FFmpeg VAAPI device, so the
# requirement has to be added back explicitly.
rpm --addrequires "libva.so.2()(64bit)" dist/*.rpm
rpm -qp --requires dist/*.rpm | grep -q "libva.so.2" \
|| { echo "::error::libva requirement missing from the RPM"; exit 1; }
- name: Package Standalone AppImage
run: |
mkdir -p AppDir/usr/bin AppDir/usr/lib AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/scalable/apps
cp target/release/scythe AppDir/usr/bin/
ln -sf scythe AppDir/usr/bin/scythe-ui
ln -sf scythe AppDir/usr/bin/scythe-daemon
ln -sf scythe AppDir/usr/bin/vrec-ui
ln -sf scythe AppDir/usr/bin/vrec-daemon
ln -sf scythe AppDir/usr/bin/vrec
cp packaging/scythe.desktop AppDir/usr/share/applications/
# Bundle the shared library closure. Without this the image only
# runs on a host that happens to carry the exact FFmpeg build the
# binary was linked against, which defeats the point of an
# AppImage. ldd already resolves the full transitive closure.
ldd target/release/scythe | awk '/=>/ {print $3}' | sort -u | while read -r lib; do
[ -e "$lib" ] || continue
base=$(basename "$lib")
# The C runtime stays on the host: bundling a different glibc
# than the kernel and the other system libraries expect breaks
# the image in ways that are hard to diagnose.
case "$base" in
libc.so.6|libm.so.6|libgcc_s.so.1|ld-linux*.so.2|libpthread.so.0|libdl.so.2|librt.so.1) continue ;;
esac
cp -L "$lib" "AppDir/usr/lib/$base"
done
lib_count=$(ls -1 AppDir/usr/lib | wc -l)
echo "bundled libraries: ${lib_count}"
ls -1 AppDir/usr/lib
# Prefer the bundled copies over anything the host provides.
patchelf --set-rpath '$ORIGIN/../lib' AppDir/usr/bin/scythe
patchelf --print-rpath AppDir/usr/bin/scythe
cat > AppDir/AppRun << 'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
export LD_LIBRARY_PATH="${HERE}/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
exec "${HERE}/usr/bin/scythe" "$@"
EOF
chmod +x AppDir/AppRun
touch AppDir/usr/share/icons/hicolor/scalable/apps/media-record.svg
touch AppDir/media-record.svg
ln -s usr/share/applications/scythe.desktop AppDir/scythe.desktop
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O appimagetool
chmod +x appimagetool
./appimagetool --appimage-extract-and-run AppDir dist/scythe-x86_64.AppImage || true
# The image is only useful if the libraries actually made it in.
lib_count=$(ls -1 AppDir/usr/lib | wc -l)
echo "bundled libraries: ${lib_count}"
if [ "${lib_count}" -lt 5 ]; then
echo "::error::AppImage bundled only ${lib_count} libraries, expected the FFmpeg closure"
exit 1
fi
if [ ! -f dist/scythe-x86_64.AppImage ]; then
echo "::error::AppImage was not produced"
exit 1
fi
- name: Upload Linux Artifacts
uses: actions/upload-artifact@v4
with:
name: linux-packages
path: dist/*
build-windows:
name: Build Windows Binaries (.exe)
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
path-type: minimal
install: >-
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-rust
mingw-w64-x86_64-ffmpeg
mingw-w64-x86_64-pkg-config
mingw-w64-x86_64-clang
mingw-w64-x86_64-ntldd
python3
- name: Build Release Binaries (Windows)
shell: msys2 {0}
env:
LIBCLANG_PATH: /mingw64/bin
BINDGEN_EXTRA_CLANG_ARGS: "--target=x86_64-w64-windows-gnu"
run: |
cargo build --release
mkdir -p dist/bundle
cp target/release/scythe.exe dist/bundle/
python3 packaging/windows/bundle_deps.py dist/bundle
strip dist/bundle/*.exe || true
strip --strip-unneeded dist/bundle/*.dll || true
- name: Package Windows Standalone (.exe), Installer, and Zip
shell: pwsh
run: |
if (!(Get-Command makensis -ErrorAction SilentlyContinue)) {
if (Test-Path "C:\Program Files (x86)\NSIS\makensis.exe") {
$env:Path += ";C:\Program Files (x86)\NSIS"
} else {
choco install nsis -y --no-progress
$env:Path += ";C:\Program Files (x86)\NSIS"
}
}
makensis packaging/windows/scythe-installer.nsi
makensis packaging/windows/scythe-standalone.nsi
Compress-Archive -Path dist/bundle/* -DestinationPath dist/scythe-windows-x86_64.zip -Force
- name: Upload Windows Artifacts
uses: actions/upload-artifact@v4
with:
name: windows-packages
path: |
dist/scythe-setup.exe
dist/scythe.exe
dist/scythe-windows-x86_64.zip
publish-release:
name: Publish GitHub Release
needs: [build-linux, build-windows]
if: always() && !cancelled() && (needs.build-linux.result == 'success' || needs.build-windows.result == 'success')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all built artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
path: artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "Scythe ${{ github.ref_name }}"
body: |
High-performance GPU hardware screen recorder and instant replay overlay for Linux and Windows.
### Packages
- **Windows**: `scythe-setup.exe` (Official installer with Start Menu & Desktop shortcuts)
- **Windows Portable**: `scythe.exe` (Standalone portable launcher)
- **Windows Archive**: `scythe-windows-x86_64.zip` (Portable zip archive)
- **Universal Linux**: `scythe-x86_64.AppImage`
- **Debian / Ubuntu / Mint**: `scythe_*_amd64.deb`
- **Fedora / RHEL / openSUSE**: `*.rpm`
- **Generic Linux**: `scythe-linux-x86_64.tar.gz`
files: |
artifacts/linux-packages/*
artifacts/windows-packages/*
draft: false
prerelease: false
generate_release_notes: false