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 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: | VERSION="${GITHUB_REF_NAME#v}" if [ -z "$VERSION" ] || [ "$VERSION" = "main" ]; then VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) fi echo "Packaging RPM version: ${VERSION}" # The requirements are derived from the binary that was actually # built, so the package always describes its own linkage. Building # through alien instead dropped libva2, because it has no Debian to # RPM name rule for it, which would have left hardware encoding # unavailable on an RPM install. REQUIRES="" for soname in $(objdump -p target/release/scythe | awk '/NEEDED/ {print $2}' | sort -u); do case "$soname" in libc.so.6|libm.so.6|libgcc_s.so.1|ld-linux*.so.2) continue ;; esac REQUIRES="${REQUIRES}Requires: ${soname}()(64bit)\n" done # libva is opened at runtime by the FFmpeg VAAPI device, so it is # not a link time dependency but is still needed for hardware # encoding to work. REQUIRES="${REQUIRES}Requires: libva.so.2()(64bit)\n" mkdir -p rpmbuild cat > rpmbuild/scythe.spec << 'SPEC' Name: scythe Version: RPM_VERSION Release: 1%{?dist} Summary: High-performance GPU hardware screen recorder and instant replay overlay License: MIT URL: https://github.com/oiupoyt/scythe BuildArch: x86_64 Provides: vrec RPM_REQUIRES %description High-performance GPU hardware screen recorder and instant replay overlay for Wayland and X11. %prep %build %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_datadir}/applications install -m 0755 %{_binary_path} %{buildroot}%{_bindir}/scythe ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-daemon ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-ui ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-daemon ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-ui ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec install -m 0644 %{_desktop_path} %{buildroot}%{_datadir}/applications/scythe.desktop ln -sf %{_datadir}/applications/scythe.desktop %{buildroot}%{_datadir}/applications/vrec.desktop %files %{_bindir}/scythe %{_bindir}/scythe-daemon %{_bindir}/scythe-ui %{_bindir}/vrec-daemon %{_bindir}/vrec-ui %{_bindir}/vrec %{_datadir}/applications/scythe.desktop %{_datadir}/applications/vrec.desktop %changelog * Tue Oct 21 2026 oiupoyt - RPM_VERSION-1 - Unified multicall binary with the compatibility symlinks - Dropped the retired GTK 3 requirements - Runtime dependencies taken from the built binary SPEC sed -i "s|RPM_VERSION|${VERSION}|g" rpmbuild/scythe.spec # The literal \n sequences are expanded by sed itself. Passing real # newlines instead would split the s command across lines and sed # would reject it as unterminated. sed -i "s|RPM_REQUIRES|${REQUIRES}|" rpmbuild/scythe.spec rpmbuild --define "_topdir ${PWD}/rpmbuild" \ --define "_binary_path ${PWD}/target/release/scythe" \ --define "_desktop_path ${PWD}/packaging/scythe.desktop" \ -bb rpmbuild/scythe.spec mv rpmbuild/RPMS/x86_64/*.rpm dist/ rpm -qp --requires dist/*.rpm | grep -q "libva.so.2" \ || { echo "::error::libva requirement missing from the RPM"; exit 1; } rpm -qp --requires dist/*.rpm | grep -q "libavcodec" \ || { echo "::error::FFmpeg 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 # libva is opened at runtime by the FFmpeg VAAPI device, so it is # not part of the link time closure and ldd never reports it. va_path=$(ldconfig -p | awk '$1=="libva.so.2" {print $NF; exit}') if [ -n "$va_path" ]; then cp -L "$va_path" AppDir/usr/lib/libva.so.2 else echo "::error::libva.so.2 not found, hardware encoding would not work in the image" exit 1 fi # 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 8 ]; 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