Repository navigation
Build & Release #310
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| schedule: | |
| - cron: '0 4 * * *' # Daily at 4 AM UTC | |
| workflow_dispatch: | |
| inputs: | |
| release_type: | |
| description: 'Release type' | |
| required: true | |
| default: 'nightly' | |
| type: choice | |
| options: | |
| - nightly | |
| - release | |
| permissions: | |
| contents: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # Cleanup nightly release if this is a scheduled/dispatch nightly build | |
| cleanup-nightly: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.release_type == 'nightly') | |
| steps: | |
| - name: Delete existing nightly release | |
| run: | | |
| gh release delete nightly --yes --repo ${{ github.repository }} || true | |
| gh api -X DELETE repos/${{ github.repository }}/git/refs/tags/nightly || true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| build: | |
| # 2026-07-03: moved off the shared bare-metal `splitux` fleet onto a | |
| # dedicated ephemeral, per-repo container runner (Phase 2 of the | |
| # org-wide containerization pilot - see ~/ssh-workspace/ci-runners/README.md | |
| # on the lab). gamescope-splitux and gbe_fork-splitux's Linux jobs keep | |
| # using the shared `splitux` label, completely unaffected. | |
| runs-on: [self-hosted, Linux, X64, splitux-ci] | |
| needs: [cleanup-nightly] | |
| if: always() && (needs.cleanup-nightly.result == 'success' || needs.cleanup-nightly.result == 'skipped') | |
| steps: | |
| - name: Verify dependencies | |
| run: | | |
| # Self-hosted runner has no sudo; deps are pre-installed on the host. | |
| # Fail fast with a clear message if anything is missing. | |
| pacman -Q \ | |
| base-devel pkgconf \ | |
| sdl2-compat \ | |
| 7zip unzip zip curl wget \ | |
| libx11 openssl \ | |
| bubblewrap slirp4netns \ | |
| fuse2 file patchelf | |
| - name: Install Rust | |
| run: | | |
| if ! command -v rustup &> /dev/null; then | |
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable | |
| fi | |
| # Idempotent: ensures a default exists even when rustup was pre-installed empty | |
| rustup default stable | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Cache Cargo | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: splitux-cargo-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: splitux-cargo- | |
| - name: Cache external deps | |
| id: deps-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: /tmp/deps-cache | |
| key: splitux-deps-v2 | |
| - name: Get version info | |
| id: version | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| VERSION="${{ github.ref_name }}" | |
| RELEASE_NAME="Splitux ${VERSION}" | |
| IS_NIGHTLY="false" | |
| PRERELEASE="false" | |
| else | |
| VERSION="nightly" | |
| RELEASE_NAME="Splitux Nightly ($(date +%Y-%m-%d))" | |
| IS_NIGHTLY="true" | |
| PRERELEASE="true" | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT | |
| echo "is_nightly=$IS_NIGHTLY" >> $GITHUB_OUTPUT | |
| echo "prerelease=$PRERELEASE" >> $GITHUB_OUTPUT | |
| - name: Fetch dependencies | |
| run: | | |
| mkdir -p res/goldberg res/gamescope-splitux res/bepinex res/facepunch | |
| CACHE_DIR="/tmp/deps-cache" | |
| mkdir -p "$CACHE_DIR" | |
| # Clean stale extraction dirs (self-hosted runner reuses /tmp; | |
| # BepInEx zips contain 000-permission files that block re-extraction) | |
| chmod -R u+rwX /tmp/bepinex-il2cpp 2>/dev/null || true | |
| rm -rf /tmp/bepinex-mono /tmp/bepinex-mono-linux /tmp/bepinex-il2cpp | |
| rm -rf /tmp/gbe-win /tmp/release | |
| # Gamescope-splitux (cached - stable version, canonical splitux-gg org) | |
| if [ -f "$CACHE_DIR/gamescope-splitux-v1.0.5.zip" ]; then | |
| echo "Using cached gamescope-splitux" | |
| unzip -o -q "$CACHE_DIR/gamescope-splitux-v1.0.5.zip" -d res/gamescope-splitux | |
| else | |
| curl -fsSL https://github.com/splitux-gg/gamescope-splitux/releases/download/v1.0.5/gamescope-splitux-v1.0.5.zip -o "$CACHE_DIR/gamescope-splitux-v1.0.5.zip" | |
| unzip -o -q "$CACHE_DIR/gamescope-splitux-v1.0.5.zip" -d res/gamescope-splitux | |
| fi | |
| # Goldberg emulator — collective release v1.1.0 (splitux-gg/gbe_fork-splitux dev HEAD): | |
| # GSE_FORCE_LOG gate + CI build-loop fix + EOSPlus/Steam friend-join (b7430e00) + | |
| # raw-UDP<->legacy-Steam-P2P bridge (95d65d05, GSE_IP_P2P_BRIDGE). Supersedes the old | |
| # gabrielgad/release-p2p-autoaccept (linux) + eosplus-clean (win) split pins. | |
| curl -fsSL https://github.com/splitux-gg/gbe_fork-splitux/releases/download/v1.1.0/emu-linux-release.tar.bz2 -o /tmp/gbe-linux.tar.bz2 | |
| tar -xjf /tmp/gbe-linux.tar.bz2 -C /tmp | |
| mkdir -p res/goldberg/{linux64,linux32,win,steamnetworkingsockets/x64,steamnetworkingsockets/x32} | |
| cp -f /tmp/release/regular/x64/*.so res/goldberg/linux64/ 2>/dev/null || true | |
| cp -f /tmp/release/regular/x32/*.so res/goldberg/linux32/ 2>/dev/null || true | |
| curl -fsSL https://github.com/splitux-gg/gbe_fork-splitux/releases/download/v1.1.0/emu-win-release.7z -o /tmp/gbe-win.7z | |
| 7z x -o/tmp/gbe-win /tmp/gbe-win.7z -y | |
| cp -f /tmp/gbe-win/release/experimental/x64/steam_api64.dll res/goldberg/win/ 2>/dev/null || true | |
| cp -f /tmp/gbe-win/release/experimental/x86/steam_api.dll res/goldberg/win/ 2>/dev/null || true | |
| cp -f /tmp/gbe-win/release/steamnetworkingsockets/x64/*.dll res/goldberg/steamnetworkingsockets/x64/ 2>/dev/null || true | |
| cp -f /tmp/gbe-win/release/steamnetworkingsockets/x86/*.dll res/goldberg/steamnetworkingsockets/x32/ 2>/dev/null || true | |
| # Fail loud if the critical Windows goldberg DLL didn't land — the artifact's | |
| # internal layout changed before (release/regular -> release/regular/x64) and the | |
| # `|| true` guards silently shipped an empty res/goldberg/win/ for months. | |
| test -s res/goldberg/win/steam_api64.dll || { echo "::error::goldberg steam_api64.dll missing from res/goldberg/win/ — check artifact layout"; exit 1; } | |
| # BepInEx 5 Mono Windows (cached) | |
| if [ -f "$CACHE_DIR/BepInEx_win_x64_5.4.23.2.zip" ]; then | |
| echo "Using cached BepInEx Mono Windows" | |
| cp "$CACHE_DIR/BepInEx_win_x64_5.4.23.2.zip" /tmp/bepinex-mono.zip | |
| else | |
| curl -fsSL https://github.com/BepInEx/BepInEx/releases/download/v5.4.23.2/BepInEx_win_x64_5.4.23.2.zip -o /tmp/bepinex-mono.zip | |
| cp /tmp/bepinex-mono.zip "$CACHE_DIR/BepInEx_win_x64_5.4.23.2.zip" | |
| fi | |
| mkdir -p res/bepinex/mono/core | |
| unzip -o -q /tmp/bepinex-mono.zip -d /tmp/bepinex-mono | |
| cp -f /tmp/bepinex-mono/BepInEx/core/* res/bepinex/mono/core/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-mono/winhttp.dll res/bepinex/mono/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-mono/doorstop_config.ini res/bepinex/mono/ 2>/dev/null || true | |
| # BepInEx 5 Mono Linux (cached) | |
| if [ -f "$CACHE_DIR/BepInEx_linux_x64_5.4.23.2.zip" ]; then | |
| echo "Using cached BepInEx Mono Linux" | |
| cp "$CACHE_DIR/BepInEx_linux_x64_5.4.23.2.zip" /tmp/bepinex-mono-linux.zip | |
| else | |
| curl -fsSL https://github.com/BepInEx/BepInEx/releases/download/v5.4.23.2/BepInEx_linux_x64_5.4.23.2.zip -o /tmp/bepinex-mono-linux.zip | |
| cp /tmp/bepinex-mono-linux.zip "$CACHE_DIR/BepInEx_linux_x64_5.4.23.2.zip" | |
| fi | |
| mkdir -p res/bepinex/mono-linux/core | |
| unzip -o -q /tmp/bepinex-mono-linux.zip -d /tmp/bepinex-mono-linux | |
| cp -f /tmp/bepinex-mono-linux/BepInEx/core/* res/bepinex/mono-linux/core/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-mono-linux/libdoorstop.so res/bepinex/mono-linux/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-mono-linux/run_bepinex.sh res/bepinex/mono-linux/ 2>/dev/null || true | |
| # BepInEx 6 IL2CPP (cached) | |
| if [ -f "$CACHE_DIR/BepInEx-Unity.IL2CPP-win-x64-6.0.0-pre.2.zip" ]; then | |
| echo "Using cached BepInEx IL2CPP" | |
| cp "$CACHE_DIR/BepInEx-Unity.IL2CPP-win-x64-6.0.0-pre.2.zip" /tmp/bepinex-il2cpp.zip | |
| else | |
| curl -fsSL https://github.com/BepInEx/BepInEx/releases/download/v6.0.0-pre.2/BepInEx-Unity.IL2CPP-win-x64-6.0.0-pre.2.zip -o /tmp/bepinex-il2cpp.zip | |
| cp /tmp/bepinex-il2cpp.zip "$CACHE_DIR/BepInEx-Unity.IL2CPP-win-x64-6.0.0-pre.2.zip" | |
| fi | |
| mkdir -p res/bepinex/il2cpp/core | |
| unzip -o -q /tmp/bepinex-il2cpp.zip -d /tmp/bepinex-il2cpp | |
| cp -f /tmp/bepinex-il2cpp/BepInEx/core/* res/bepinex/il2cpp/core/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-il2cpp/winhttp.dll res/bepinex/il2cpp/ 2>/dev/null || true | |
| cp -f /tmp/bepinex-il2cpp/doorstop_config.ini res/bepinex/il2cpp/ 2>/dev/null || true | |
| # SplituxFacepunch (cached) | |
| if [ -f "$CACHE_DIR/SplituxFacepunch-v2.1.0.dll" ]; then | |
| echo "Using cached SplituxFacepunch" | |
| cp "$CACHE_DIR/SplituxFacepunch-v2.1.0.dll" res/facepunch/SplituxFacepunch.dll | |
| else | |
| curl -fsSL https://github.com/splitux-gg/bepinex-facepunch-splitux/releases/download/v2.1.0/SplituxFacepunch.dll -o res/facepunch/SplituxFacepunch.dll | |
| cp res/facepunch/SplituxFacepunch.dll "$CACHE_DIR/SplituxFacepunch-v2.1.0.dll" | |
| fi | |
| - name: Build Splitux | |
| run: | | |
| cargo build --release | |
| - name: Package tarball | |
| run: | | |
| mkdir -p release/splitux/bin release/splitux/res | |
| # Copy binary | |
| cp target/release/splitux release/splitux/ | |
| chmod +x release/splitux/splitux | |
| # Copy gamescope-splitux | |
| cp res/gamescope-splitux/bin/* release/splitux/bin/ | |
| chmod +x release/splitux/bin/* | |
| # Copy resources (downloaded deps) | |
| cp -r res/* release/splitux/res/ | |
| # Copy app assets (fonts, icon) | |
| cp -r assets release/splitux/ | |
| cp LICENSE release/splitux/ 2>/dev/null || true | |
| # Copy scripts and desktop file | |
| cp launch.sh release/splitux/ | |
| cp install.sh release/splitux/ | |
| cp splitux.sh release/splitux/ | |
| cp splitux.desktop release/splitux/ | |
| chmod +x release/splitux/launch.sh release/splitux/install.sh release/splitux/splitux.sh | |
| # Create tarball | |
| cd release | |
| tar -czvf splitux-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz splitux/ | |
| # Create zip archive | |
| zip -r splitux-${{ steps.version.outputs.version }}-linux-x86_64.zip splitux/ | |
| - name: Build AppImage | |
| run: | | |
| # Download appimagetool | |
| curl -fsSL https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -o /tmp/appimagetool | |
| chmod +x /tmp/appimagetool | |
| # Create AppDir structure | |
| mkdir -p AppDir/usr/bin AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/256x256/apps AppDir/usr/share/splitux | |
| # Copy binary and resources | |
| cp target/release/splitux AppDir/usr/bin/ | |
| cp -r res/* AppDir/usr/share/splitux/ | |
| cp -r assets/* AppDir/usr/share/splitux/ | |
| cp res/gamescope-splitux/bin/* AppDir/usr/bin/ | |
| chmod +x AppDir/usr/bin/* | |
| # Create desktop file | |
| cat > AppDir/usr/share/applications/splitux.desktop << 'DESKTOP' | |
| [Desktop Entry] | |
| Type=Application | |
| Name=Splitux | |
| Comment=Local split-screen multiplayer for Steam games | |
| Exec=splitux | |
| Icon=splitux | |
| Categories=Game; | |
| Terminal=false | |
| DESKTOP | |
| # Copy/create icon (use a placeholder if none exists) | |
| if [ -f assets/icon.png ]; then | |
| cp assets/icon.png AppDir/usr/share/icons/hicolor/256x256/apps/splitux.png | |
| else | |
| echo "No icon found, AppImage will use default" | |
| fi | |
| # Create AppRun script | |
| cat > AppDir/AppRun << 'APPRUN' | |
| #!/bin/bash | |
| SELF=$(readlink -f "$0") | |
| HERE=${SELF%/*} | |
| export PATH="${HERE}/usr/bin:${PATH}" | |
| export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" | |
| export XDG_DATA_DIRS="${HERE}/usr/share:${XDG_DATA_DIRS}" | |
| export SPLITUX_RES_PATH="${HERE}/usr/share/splitux" | |
| exec "${HERE}/usr/bin/splitux" "$@" | |
| APPRUN | |
| chmod +x AppDir/AppRun | |
| # Symlinks for AppImage | |
| ln -sf usr/share/applications/splitux.desktop AppDir/splitux.desktop | |
| ln -sf usr/share/icons/hicolor/256x256/apps/splitux.png AppDir/splitux.png 2>/dev/null || true | |
| # Build AppImage (extract and run since FUSE may not work in container) | |
| /tmp/appimagetool --appimage-extract | |
| ARCH=x86_64 ./squashfs-root/AppRun AppDir release/Splitux-${{ steps.version.outputs.version }}-x86_64.AppImage | |
| - name: Upload to Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: ${{ steps.version.outputs.release_name }} | |
| tag_name: ${{ steps.version.outputs.is_nightly == 'true' && 'nightly' || github.ref_name }} | |
| prerelease: ${{ steps.version.outputs.prerelease }} | |
| body: | | |
| ${{ steps.version.outputs.is_nightly == 'true' && 'Automated nightly build. May be unstable.' || '' }} | |
| ## Downloads | |
| | Format | Description | | |
| |--------|-------------| | |
| | `.tar.gz` | Native Linux tarball (recommended for most users) | | |
| | `.zip` | Zip archive | | |
| | `.AppImage` | Portable AppImage (no installation required) | | |
| | `.flatpak` | Flatpak bundle (sandboxed) | | |
| ### Installation | |
| **Tarball/Zip:** | |
| ```bash | |
| tar -xzf splitux-*.tar.gz | |
| cd splitux | |
| ./install.sh | |
| ``` | |
| **AppImage:** | |
| ```bash | |
| chmod +x Splitux-*.AppImage | |
| ./Splitux-*.AppImage | |
| ``` | |
| **Flatpak:** | |
| ```bash | |
| flatpak install Splitux-*.flatpak | |
| flatpak run gg.splitux.Splitux | |
| ``` | |
| files: | | |
| release/splitux-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz | |
| release/splitux-${{ steps.version.outputs.version }}-linux-x86_64.zip | |
| release/Splitux-${{ steps.version.outputs.version }}-x86_64.AppImage | |
| fail_on_unmatched_files: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| build-flatpak: | |
| # Same dedicated ephemeral runner as `build` above. This job's | |
| # flatpak-builder step runs a nested bubblewrap sandbox internally - the | |
| # ephemeral container it runs in needs --cap-add SYS_ADMIN | |
| # --security-opt unmask=ALL at launch (set in the poller's systemd unit | |
| # on the lab) or bwrap's own proc mount is rejected. | |
| runs-on: [self-hosted, Linux, X64, splitux-ci] | |
| needs: [build] | |
| if: always() && needs.build.result == 'success' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get version info | |
| id: version | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| VERSION="${{ github.ref_name }}" | |
| IS_NIGHTLY="false" | |
| else | |
| VERSION="nightly" | |
| IS_NIGHTLY="true" | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "is_nightly=$IS_NIGHTLY" >> $GITHUB_OUTPUT | |
| - name: Cache flatpak-builder | |
| uses: actions/cache@v4 | |
| with: | |
| path: .flatpak-builder | |
| key: splitux-flatpak-builder-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: splitux-flatpak-builder- | |
| - name: Install Flatpak | |
| run: | | |
| # Self-hosted runner has no sudo; flatpak/flatpak-builder are pre-installed. | |
| # Runtime + remote installed per-user (no root needed). | |
| pacman -Q flatpak flatpak-builder | |
| flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo | |
| flatpak --user install -y flathub org.freedesktop.Platform//23.08 org.freedesktop.Sdk//23.08 || true | |
| - name: Build Flatpak | |
| run: | | |
| # Create flatpak manifest | |
| cat > gg.splitux.Splitux.yml << 'MANIFEST' | |
| app-id: gg.splitux.Splitux | |
| runtime: org.freedesktop.Platform | |
| runtime-version: '23.08' | |
| sdk: org.freedesktop.Sdk | |
| command: splitux | |
| finish-args: | |
| - --share=ipc | |
| - --share=network | |
| - --socket=x11 | |
| - --socket=wayland | |
| - --socket=pulseaudio | |
| - --device=all | |
| - --filesystem=home | |
| - --filesystem=/run/media | |
| - --filesystem=/media | |
| - --filesystem=/mnt | |
| - --filesystem=xdg-run/gamescope-0:create | |
| - --talk-name=org.freedesktop.Flatpak | |
| - --env=PATH=/app/bin:/usr/bin | |
| modules: | |
| - name: bubblewrap | |
| buildsystem: meson | |
| sources: | |
| - type: archive | |
| url: https://github.com/containers/bubblewrap/releases/download/v0.8.0/bubblewrap-0.8.0.tar.xz | |
| sha256: 957ad1149db9033db88e988b12bcebe349a445e1efc8a9b59ad2939a113d333a | |
| - name: fuse-overlayfs | |
| buildsystem: simple | |
| build-commands: | |
| - install -Dm755 fuse-overlayfs /app/bin/fuse-overlayfs | |
| sources: | |
| - type: file | |
| url: https://github.com/containers/fuse-overlayfs/releases/download/v1.14/fuse-overlayfs-x86_64 | |
| sha256: 4817a8896a9e6f0433080f88f5b71dec931e8829a89d64c71af94b0630ccb4a9 | |
| dest-filename: fuse-overlayfs | |
| - name: splitux | |
| buildsystem: simple | |
| build-options: | |
| build-args: | |
| - --share=network | |
| env: | |
| CARGO_HOME: /run/build/splitux/cargo | |
| RUSTUP_HOME: /run/build/splitux/rustup | |
| build-commands: | |
| - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal | |
| - . $CARGO_HOME/env && cargo build --release | |
| - install -Dm755 target/release/splitux /app/bin/splitux | |
| - mkdir -p /app/share/splitux | |
| - cp -r res/* /app/share/splitux/ || true | |
| sources: | |
| - type: dir | |
| path: . | |
| MANIFEST | |
| # Build flatpak | |
| flatpak-builder --force-clean --repo=repo build-dir gg.splitux.Splitux.yml | |
| # Create single-file bundle | |
| mkdir -p release | |
| flatpak build-bundle repo release/Splitux-${{ steps.version.outputs.version }}.flatpak gg.splitux.Splitux | |
| - name: Upload Flatpak to Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.version.outputs.is_nightly == 'true' && 'nightly' || github.ref_name }} | |
| files: release/Splitux-${{ steps.version.outputs.version }}.flatpak | |
| fail_on_unmatched_files: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |