diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61e838dc..587e557c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -333,6 +333,56 @@ jobs: if-no-files-found: error retention-days: 7 + linux-flatpak-changes: + name: detect Linux Flatpak changes + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.paths.outputs.changed }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - id: paths + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.sha }} + run: | + if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_flatpak\.sh$|flatpak/|scripts/pack_love\.sh$|docs/linux-flatpak\.md$|\.github/workflows/(ci|release)\.yml$)'; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + linux-flatpak-build: + name: Linux Flatpak build + needs: linux-flatpak-changes + if: needs.linux-flatpak-changes.outputs.changed == 'true' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - name: Install flatpak-builder deps + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y flatpak flatpak-builder elfutils imagemagick + - name: Build Flatpak bundle + run: | + set -euo pipefail + scripts/build_flatpak.sh --version 0.0.0 + - name: Upload the Flatpak + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-linux-flatpak + path: | + dist/flatpak/gen1recomp-0.0.0-linux.flatpak + dist/flatpak/gen1recomp-0.0.0-linux.flatpak.sha256 + if-no-files-found: error + retention-days: 7 + headless: name: headless suites (no ROM) runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3df3ec4..ef26d7bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,6 +173,38 @@ jobs: if-no-files-found: error retention-days: 1 + linux-flatpak: + name: build Linux Flatpak + needs: [version, love-payload] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - name: Install flatpak-builder deps + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y flatpak flatpak-builder elfutils imagemagick + - name: Download shared payload + uses: actions/download-artifact@v8 + with: + name: gen1recomp-release-love + path: .bazinga/work + - name: Build Flatpak bundle + run: | + set -euo pipefail + scripts/build_flatpak.sh \ + --version "${{ needs.version.outputs.version }}" \ + --game-love .bazinga/work/game.love + - name: Upload Flatpak release + uses: actions/upload-artifact@v7 + with: + name: gen1recomp-linux-flatpak-release + path: | + dist/flatpak/gen1recomp-${{ needs.version.outputs.version }}-linux.flatpak + dist/flatpak/gen1recomp-${{ needs.version.outputs.version }}-linux.flatpak.sha256 + if-no-files-found: error + retention-days: 1 + xbox-uwp: name: build Xbox UWP release needs: [version, love-payload] @@ -287,7 +319,7 @@ jobs: retention-days: 1 release: - needs: [version, love-payload, xbox-uwp, linux-arm64, native-tls-win] + needs: [version, love-payload, xbox-uwp, linux-arm64, linux-flatpak, native-tls-win] runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} steps: @@ -498,6 +530,13 @@ jobs: name: gen1recomp-linux-arm64-release path: dist/linux-arm64 + - name: Download Linux Flatpak release + if: github.repository == 'bryanthaboi/gen1recomp' + uses: actions/download-artifact@v8 + with: + name: gen1recomp-linux-flatpak-release + path: dist/flatpak + - name: Stage release assets if: github.repository == 'bryanthaboi/gen1recomp' id: assets @@ -509,16 +548,24 @@ jobs: mkdir -p "$outdir" cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip" cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip" - cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip" + # x86_64 desktop Linux: raw AppImage (no zip wrapper). + x64_appimage="dist/linux/gen1recomp-linux-x86_64.AppImage" + [ -f "$x64_appimage" ] || { echo "::error::$x64_appimage not found (expected from scripts/build.sh linux)"; exit 1; } + cp "$x64_appimage" "$outdir/gen1recomp-${v}-linux-x86_64.AppImage" + chmod +x "$outdir/gen1recomp-${v}-linux-x86_64.AppImage" # arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on # its own runner because LÖVE publishes no aarch64 binary and the # AppImage has to be compiled natively; ships as a runnable - # AppImage rather than a zip so `chmod +x && ./it` just works. + # AppImage so `chmod +x && ./it` just works. arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage" [ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; } cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage" chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage" + + flatpak_bundle="dist/flatpak/gen1recomp-${v}-linux.flatpak" + [ -f "$flatpak_bundle" ] || { echo "::error::$flatpak_bundle not found (expected from the linux-flatpak job)"; exit 1; } + cp "$flatpak_bundle" "$outdir/gen1recomp-${v}-linux.flatpak" apk="$(find dist/android/release -name '*.apk' | head -1)" [ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/release"; exit 1; } cp "$apk" "$outdir/gen1recomp-${v}-android.apk" @@ -656,8 +703,9 @@ jobs: release_files=( "dist/release/gen1recomp-${v}-macos.zip" "dist/release/gen1recomp-${v}-windows.zip" - "dist/release/gen1recomp-${v}-linux.zip" + "dist/release/gen1recomp-${v}-linux-x86_64.AppImage" "dist/release/gen1recomp-${v}-linux-arm64.AppImage" + "dist/release/gen1recomp-${v}-linux.flatpak" "dist/release/gen1recomp-${v}-android.apk" "dist/release/gen1recomp++-${v}-ios.ipa" "dist/release/gen1recomp-${v}-switch.zip" diff --git a/README.md b/README.md index 8591c88b..e4b52816 100644 --- a/README.md +++ b/README.md @@ -228,18 +228,27 @@ entry: a desktop shortcut per game, a Steam entry, or a handheld frontend. | `--launcher` | open the launcher anyway, so you can edit a shortcut you already made | -## Linux on arm64 (Raspberry Pi) +## Linux desktop (AppImage / Flatpak) -Alongside the x86_64 `gen1recomp-*-linux.zip`, every release ships -`gen1recomp-*-linux-arm64.AppImage` for 64-bit ARM desktop Linux — Raspberry -Pi 4/5, Armbian and other SBC distros, and arm64 VMs on Apple Silicon: +Releases ship raw AppImages (no zip wrapper) plus an optional Flatpak bundle: + +- `gen1recomp-*-linux-x86_64.AppImage` +- `gen1recomp-*-linux-arm64.AppImage` (Raspberry Pi 4/5, Armbian, arm64 VMs) +- `gen1recomp-*-linux.flatpak` (see [docs/linux-flatpak.md](docs/linux-flatpak.md)) + +```sh +chmod +x gen1recomp-*-linux-x86_64.AppImage +./gen1recomp-*-linux-x86_64.AppImage +``` ```sh chmod +x gen1recomp-*-linux-arm64.AppImage ./gen1recomp-*-linux-arm64.AppImage ``` -LÖVE publishes no aarch64 binary of any kind, so this artifact compiles the +Shared troubleshooting (FUSE, curl, portable mode): [docs/linux-appimage.md](docs/linux-appimage.md). + +LÖVE publishes no aarch64 binary of any kind, so the arm64 artifact compiles the engine — and SDL2, OpenAL and the codecs — from source inside a Debian bullseye arm64 container. It needs only glibc 2.29+, libstdc++, freetype and zlib on the host; OpenGL, X11, Wayland, KMSDRM, ALSA and PulseAudio are all diff --git a/docs/linux-appimage.md b/docs/linux-appimage.md new file mode 100644 index 00000000..2a6797dc --- /dev/null +++ b/docs/linux-appimage.md @@ -0,0 +1,58 @@ +# Linux AppImage packaging + +Releases ship raw AppImages (no zip wrapper): + +- `gen1recomp--linux-x86_64.AppImage` +- `gen1recomp--linux-arm64.AppImage` + +```sh +chmod +x gen1recomp-*-linux-x86_64.AppImage +./gen1recomp-*-linux-x86_64.AppImage +``` + +Flatpak users should prefer the `.flatpak` bundle (see +[linux-flatpak.md](linux-flatpak.md)); it avoids host glibc / FUSE / curl +mismatches on immutable desktops. + +## Common failure modes + +| Symptom | Cause | Fix | +|---|---|---| +| Won't start / `libfuse.so.2` | No FUSE | `sudo apt install libfuse2` (or `libfuse2t64`) **or** `./app.AppImage --appimage-extract-and-run` | +| Update check / mods / save sync fail | Host `curl` missing or broken by AppImage `LD_LIBRARY_PATH` | Install curl; current builds scrub `LD_LIBRARY_PATH` for **host** curl and keep it for a bundled AppDir curl | +| Settings / sync reset after quit | Portable mode next to a read-only AppImage parent (`/opt`, system dir) | Remove `portable.txt` or move the AppImage to a writable folder; the game falls back to the XDG save dir when the probe write fails | +| "Download AppImage update" | Shell/`minShell` gate needs a full native package | Download the new `.AppImage`, `chmod +x`, replace the old file | +| Steam / Game Mode weirdness after update | Overlay `LD_PRELOAD` / PID change | HostShell unsets `LD_PRELOAD` for children and `execv`s `$APPIMAGE` on restart | + +## Network transport (HostShell) + +Desktop Linux fetches go through host or bundled `curl`: + +1. Flatpak `/app/bin/curl` (bundled, keep sandbox libs) +2. `$APPDIR/usr/bin/curl` or `$APPDIR/bin/curl` (keep `$APPDIR` on `LD_LIBRARY_PATH`) +3. Host `curl` (`env -u LD_LIBRARY_PATH`, and always `-u LD_PRELOAD`) + +## Portable mode + +Drop `portable.txt` beside the `.AppImage` to keep saves next to the binary. +The launcher probes writability with a unique `.write_probe_