From 5cee3fbdb6cd3090d0fa744e52394c146d513ede Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 13:50:40 +0800 Subject: [PATCH 1/6] ci: install rpm and launch AppImage on Wayland --- .github/workflows/linux-installers.yml | 256 +++++++++++++++++++++++++ CHANGELOG.md | 4 + docs/linux-display.md | 24 +++ 3 files changed, 284 insertions(+) create mode 100644 .github/workflows/linux-installers.yml diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml new file mode 100644 index 00000000..0df3c0ca --- /dev/null +++ b/.github/workflows/linux-installers.yml @@ -0,0 +1,256 @@ +name: Linux Installer Lifecycle + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: linux-installers-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build x64 rpm and AppImage + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - name: Set up embeddable Racket CS + uses: ./.github/actions/setup-embed-racket + - name: Install Rivet and native packaging tools + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install --yes desktop-file-utils libgtk-4-dev libsecret-1-dev rpm + raco pkg install --auto --no-docs --name rivet --link "$PWD" + - name: Build release artifacts + shell: bash + env: + APPIMAGE_EXTRACT_AND_RUN: "1" + run: | + set -euo pipefail + root="$RUNNER_TEMP/rivet-linux-installers" + mkdir -p "$root" + cd "$root" + raco rivet new Smoke + cd Smoke + mkdir -p assets/nested + printf %s packaged-resource > assets/nested/product.txt + python3 - <<'PY' + import struct, zlib + from pathlib import Path + + def chunk(tag, data): + crc = zlib.crc32(tag + data) & 0xFFFFFFFF + return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", crc) + + ihdr = struct.pack(">IIBBBBB", 1, 1, 8, 6, 0, 0, 0) + idat = zlib.compress(b"\x00\x00\x00\x00\xff") + Path("assets/icon.png").write_bytes( + b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + + chunk(b"IDAT", idat) + chunk(b"IEND", b"")) + + path = Path("rivet.rktd") + original = path.read_text(encoding="utf-8") + updated = original.replace( + '(resources . ())', + '(resources . ("assets")) ' + '(linux-icon . "assets/icon.png") ' + '(linux-formats . ("rpm" "appimage"))') + if updated == original: + raise SystemExit("failed to configure Linux installer smoke") + path.write_text(updated, encoding="utf-8") + PY + raco rivet release --development --without-updates + raco rivet verify + artifacts="$RUNNER_TEMP/rivet-linux-native-artifacts" + diagnostics="$RUNNER_TEMP/rivet-linux-build-diagnostics" + mkdir -p "$artifacts" "$diagnostics" + cp dist/*.rpm dist/*.AppImage "$artifacts/" + sha256sum "$artifacts"/* > "$diagnostics/SHA256SUMS" + rpm -qpi "$artifacts"/*.rpm > "$diagnostics/rpm-info.txt" + raco rivet doctor --json > "$diagnostics/doctor.json" + - name: Upload installers + uses: actions/upload-artifact@v6 + with: + name: linux-native-installers + path: ${{ runner.temp }}/rivet-linux-native-artifacts + if-no-files-found: error + - name: Upload build diagnostics + if: always() + uses: actions/upload-artifact@v6 + with: + name: linux-native-build-diagnostics + path: ${{ runner.temp }}/rivet-linux-build-diagnostics + if-no-files-found: warn + + appimage-wayland: + name: AppImage launch on Ubuntu 24.04 Wayland + needs: build + runs-on: ubuntu-24.04 + steps: + - uses: actions/download-artifact@v7 + with: + name: linux-native-installers + path: ${{ runner.temp }}/installers + - name: Install headless Wayland compositor + run: | + sudo apt-get update + sudo apt-get install --yes weston + - name: Launch AppImage outside the build tree + shell: bash + env: + APPIMAGE_EXTRACT_AND_RUN: "1" + run: | + set -euo pipefail + diagnostics="$RUNNER_TEMP/rivet-appimage-diagnostics" + mkdir -p "$diagnostics" + export XDG_RUNTIME_DIR="$RUNNER_TEMP/rivet-appimage-runtime" + export WAYLAND_DISPLAY=wayland-rivet-appimage + export GDK_BACKEND=wayland + unset DISPLAY + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + weston --backend=headless-backend.so \ + --socket="$WAYLAND_DISPLAY" --idle-time=0 \ + --log="$diagnostics/weston.log" & + weston_pid=$! + app_pid="" + cleanup() { + local status=$? + trap - EXIT + if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then + kill -TERM "$app_pid" 2>/dev/null || true + wait "$app_pid" 2>/dev/null || true + fi + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + exit "$status" + } + trap cleanup EXIT + for _ in $(seq 1 100); do + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" && break + kill -0 "$weston_pid" 2>/dev/null || { + cat "$diagnostics/weston.log" + exit 1 + } + sleep 0.05 + done + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + appimage="$(find "$RUNNER_TEMP/installers" -type f -name '*.AppImage' -print -quit)" + test -n "$appimage" + chmod +x "$appimage" + "$appimage" >"$diagnostics/application.log" 2>&1 & + app_pid=$! + for _ in $(seq 1 50); do + kill -0 "$app_pid" 2>/dev/null || { + wait "$app_pid" || true + cat "$diagnostics/application.log" + exit 1 + } + sleep 0.1 + done + kill -TERM "$app_pid" + wait "$app_pid" || test "$?" -eq 143 + app_pid="" + trap - EXIT + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + - name: Upload AppImage diagnostics + if: always() + uses: actions/upload-artifact@v6 + with: + name: ubuntu-appimage-wayland-diagnostics + path: ${{ runner.temp }}/rivet-appimage-diagnostics + if-no-files-found: warn + + fedora-rpm-wayland: + name: rpm install and launch on Fedora 44 Wayland + needs: build + runs-on: ubuntu-24.04 + container: fedora:44 + steps: + - uses: actions/download-artifact@v7 + with: + name: linux-native-installers + path: /tmp/rivet-installers + - name: Install desktop runtime + run: dnf install --assumeyes dbus-daemon desktop-file-utils gtk4 procps-ng weston + - name: Install, launch, and uninstall rpm + shell: bash + run: | + set -euo pipefail + diagnostics=/tmp/rivet-fedora-diagnostics + mkdir -p "$diagnostics" + rpm_artifact="$(find /tmp/rivet-installers -type f -name '*.rpm' -print -quit)" + test -n "$rpm_artifact" + dnf install --assumeyes "$rpm_artifact" >"$diagnostics/dnf-install.log" 2>&1 + rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' smoke \ + >"$diagnostics/installed-version.txt" + test -x /opt/Smoke/RivetHost + test -f /usr/share/applications/Smoke.desktop + desktop-file-validate /usr/share/applications/Smoke.desktop + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource + + export XDG_RUNTIME_DIR=/tmp/rivet-fedora-wayland-runtime + export WAYLAND_DISPLAY=wayland-rivet-fedora + export GDK_BACKEND=wayland + unset DISPLAY + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + weston --backend=headless-backend.so \ + --socket="$WAYLAND_DISPLAY" --idle-time=0 \ + --log="$diagnostics/weston.log" & + weston_pid=$! + app_pid="" + cleanup() { + local status=$? + trap - EXIT + if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then + kill -TERM "$app_pid" 2>/dev/null || true + wait "$app_pid" 2>/dev/null || true + fi + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + dnf remove --assumeyes smoke >>"$diagnostics/dnf-remove.log" 2>&1 || true + exit "$status" + } + trap cleanup EXIT + for _ in $(seq 1 100); do + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" && break + kill -0 "$weston_pid" 2>/dev/null || { + cat "$diagnostics/weston.log" + exit 1 + } + sleep 0.05 + done + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + /opt/Smoke/RivetHost >"$diagnostics/application.log" 2>&1 & + app_pid=$! + for _ in $(seq 1 50); do + kill -0 "$app_pid" 2>/dev/null || { + wait "$app_pid" || true + cat "$diagnostics/application.log" + exit 1 + } + sleep 0.1 + done + kill -TERM "$app_pid" + wait "$app_pid" || test "$?" -eq 143 + app_pid="" + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + dnf remove --assumeyes smoke >"$diagnostics/dnf-remove.log" 2>&1 + test ! -e /opt/Smoke/RivetHost + test ! -e /usr/share/applications/Smoke.desktop + trap - EXIT + - name: Upload Fedora diagnostics + if: always() + uses: actions/upload-artifact@v6 + with: + name: fedora-rpm-wayland-diagnostics + path: /tmp/rivet-fedora-diagnostics + if-no-files-found: warn diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ab76a2..8dc5d644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Add real Linux installer lifecycle gates: build x64 rpm/AppImage artifacts, + install and launch the rpm on Fedora 44 under native Wayland, launch the + AppImage outside the build tree on Ubuntu 24.04, uninstall the rpm, and + retain package/compositor/application diagnostics as CI artifacts. - Adopt a Wayland-first Linux display policy, document X11/XWayland as best-effort GTK compatibility, and launch the packaged GTK4 host through a headless native Wayland compositor in CI. diff --git a/docs/linux-display.md b/docs/linux-display.md index 223c378f..1ccf1bd6 100644 --- a/docs/linux-display.md +++ b/docs/linux-display.md @@ -49,6 +49,30 @@ desktop coverage. Linux production graduation still requires install, upgrade, GPU, input, scaling, portal, GNOME, and KDE evidence across the declared distribution matrix. +## Distribution lifecycle matrix + +Rivet's support promise follows vendor maintenance instead of accumulating an +unbounded list of historical images: + +| Family | Qualification policy | Automated release evidence | +| --- | --- | --- | +| Ubuntu | Supported LTS releases while they receive standard security maintenance | Ubuntu 24.04: deb lifecycle and AppImage launch under headless native Wayland | +| Debian | Current stable release during Debian's regular support period | Debian 13 is the qualification target; it is not promoted until its install gate exists | +| Fedora | Current and previous Fedora releases while upstream still maintains them | Fedora 44: rpm install, launch, and uninstall under headless native Wayland | + +The matrix is reviewed for every Rivet minor release. A distribution enters the +support table only after its native package is installed by the real package +manager, launched outside the build tree through `GDK_BACKEND=wayland` with no +`DISPLAY`, and cleanly removed. AppImage is tested independently because its +dependency closure and replacement policy differ from deb/rpm. CI uploads the +package-manager transcript, exact installed version, compositor log, and +application log so a failed gate can be reproduced rather than guessed at. + +Fedora advances on its roughly thirteen-month upstream lifecycle; CI pins a +specific maintained release instead of using `fedora:latest`. Ubuntu LTS and +Debian stable advance only in a reviewable matrix change. X11/XWayland remains +outside this release matrix. + ## Removal threshold X11 compatibility can be removed when upstream GTK or the supported From 1a19272b551e6f65c6846f4d730b52ff764d0c31 Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 14:05:45 +0800 Subject: [PATCH 2/6] linux: make Ubuntu-built rpm installable on Fedora --- .github/workflows/linux-installers.yml | 1 + rivet-cli/rpm.rkt | 10 ++++++++++ tests/linux-native-installer.rkt | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml index 0df3c0ca..5bd8807d 100644 --- a/.github/workflows/linux-installers.yml +++ b/.github/workflows/linux-installers.yml @@ -72,6 +72,7 @@ jobs: cp dist/*.rpm dist/*.AppImage "$artifacts/" sha256sum "$artifacts"/* > "$diagnostics/SHA256SUMS" rpm -qpi "$artifacts"/*.rpm > "$diagnostics/rpm-info.txt" + rpm -qp --requires "$artifacts"/*.rpm > "$diagnostics/rpm-requires.txt" raco rivet doctor --json > "$diagnostics/doctor.json" - name: Upload installers uses: actions/upload-artifact@v6 diff --git a/rivet-cli/rpm.rkt b/rivet-cli/rpm.rkt index b40816c1..91f71f9e 100644 --- a/rivet-cli/rpm.rkt +++ b/rivet-cli/rpm.rkt @@ -52,6 +52,15 @@ (call-with-output-file destination #:exists 'truncate/replace (lambda (out) + ;; The pinned embeddable Racket archive is built on Ubuntu, whose + ;; libtinfo exports versioned ncurses symbols. Fedora intentionally + ;; publishes the same libtinfo.so.6 ABI without those versioned RPM + ;; Provides. Keep rpmbuild's dependency discovery for every other ELF + ;; requirement, but replace only that unsatisfiable metadata edge with + ;; the Fedora package that owns libtinfo.so.6. The native Wayland RPM + ;; install/launch gate proves the resulting runtime link, rather than + ;; papering over all automatically discovered dependencies. + (display "%global __requires_exclude ^libtinfo\\.so\\.6\\(NCURSES6_TINFO_.*$\n" out) (define (tag key value) (fprintf out "~a: ~a\n" key value)) (tag "Name" (rpm-package-name project)) @@ -65,6 +74,7 @@ (tag "URL" "https://github.com/turinglambdaai/rivet") (tag "BuildArch" (rpm-architecture)) (tag "Requires" "gtk4") + (tag "Requires" "ncurses-libs") (display "\n%description\n" out) (fprintf out "~a native desktop app. The application payload and the\n" (project-display-name project)) diff --git a/tests/linux-native-installer.rkt b/tests/linux-native-installer.rkt index 436d51c4..04925fe0 100644 --- a/tests/linux-native-installer.rkt +++ b/tests/linux-native-installer.rkt @@ -86,6 +86,11 @@ (check-true (regexp-match? #rx"(?m:^Version: 1\\.2\\.0$)" spec)) (check-true (regexp-match? #rx"(?m:^Release: 7$)" spec)) (check-true (regexp-match? #rx"(?m:^Requires: gtk4$)" spec)) + (check-true (regexp-match? #rx"(?m:^Requires: ncurses-libs$)" spec)) + (check-true + (regexp-match? + #rx"(?m:^%global __requires_exclude \\^libtinfo\\\\[.]so)" + spec)) (check-true (regexp-match? #rx"\"/opt/Demo_App/\\*\"" spec)) ;; System-owned directories must not be re-owned by the package. (check-false (regexp-match? #rx"%dir \"/usr/share/applications\"" spec)) From 92033e32457b7a5ed763e3c8e0229e4701e170cd Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 14:13:35 +0800 Subject: [PATCH 3/6] linux: preserve rpm dependency filter escaping --- rivet-cli/rpm.rkt | 5 ++++- tests/linux-native-installer.rkt | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/rivet-cli/rpm.rkt b/rivet-cli/rpm.rkt index 91f71f9e..18b3a558 100644 --- a/rivet-cli/rpm.rkt +++ b/rivet-cli/rpm.rkt @@ -60,7 +60,10 @@ ;; the Fedora package that owns libtinfo.so.6. The native Wayland RPM ;; install/launch gate proves the resulting runtime link, rather than ;; papering over all automatically discovered dependencies. - (display "%global __requires_exclude ^libtinfo\\.so\\.6\\(NCURSES6_TINFO_.*$\n" out) + ;; RPM expands macro bodies before compiling this regular expression, + ;; so the generated spec needs two backslashes to deliver one escaped + ;; punctuation character to the dependency filter. + (display "%global __requires_exclude ^libtinfo\\\\.so\\\\.6\\\\(NCURSES6_TINFO_.*$\n" out) (define (tag key value) (fprintf out "~a: ~a\n" key value)) (tag "Name" (rpm-package-name project)) diff --git a/tests/linux-native-installer.rkt b/tests/linux-native-installer.rkt index 04925fe0..7e367f29 100644 --- a/tests/linux-native-installer.rkt +++ b/tests/linux-native-installer.rkt @@ -88,9 +88,9 @@ (check-true (regexp-match? #rx"(?m:^Requires: gtk4$)" spec)) (check-true (regexp-match? #rx"(?m:^Requires: ncurses-libs$)" spec)) (check-true - (regexp-match? - #rx"(?m:^%global __requires_exclude \\^libtinfo\\\\[.]so)" - spec)) + (string-contains? + spec + "%global __requires_exclude ^libtinfo\\\\.so\\\\.6\\\\(NCURSES6_TINFO_.*$")) (check-true (regexp-match? #rx"\"/opt/Demo_App/\\*\"" spec)) ;; System-owned directories must not be re-owned by the package. (check-false (regexp-match? #rx"%dir \"/usr/share/applications\"" spec)) From a33a3cda08f79700e2051b1673a11dc1e7dcbd92 Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 14:25:09 +0800 Subject: [PATCH 4/6] ci: bound Linux installer shutdown checks --- .github/workflows/linux-installers.yml | 35 +++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml index 5bd8807d..e1f3ed67 100644 --- a/.github/workflows/linux-installers.yml +++ b/.github/workflows/linux-installers.yml @@ -15,6 +15,7 @@ jobs: build: name: Build x64 rpm and AppImage runs-on: ubuntu-24.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v7 - name: Set up embeddable Racket CS @@ -92,6 +93,7 @@ jobs: name: AppImage launch on Ubuntu 24.04 Wayland needs: build runs-on: ubuntu-24.04 + timeout-minutes: 20 steps: - uses: actions/download-artifact@v7 with: @@ -120,11 +122,23 @@ jobs: --log="$diagnostics/weston.log" & weston_pid=$! app_pid="" + stop_bounded() { + local pid="$1" + local label="$2" + kill -TERM "$pid" 2>/dev/null || true + for _ in $(seq 1 200); do + kill -0 "$pid" 2>/dev/null || return 0 + sleep 0.05 + done + kill -KILL "$pid" 2>/dev/null || true + echo "$label did not stop within ten seconds of SIGTERM" >&2 + return 1 + } cleanup() { local status=$? trap - EXIT if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then - kill -TERM "$app_pid" 2>/dev/null || true + stop_bounded "$app_pid" application || status=1 wait "$app_pid" 2>/dev/null || true fi kill "$weston_pid" 2>/dev/null || true @@ -154,7 +168,7 @@ jobs: } sleep 0.1 done - kill -TERM "$app_pid" + stop_bounded "$app_pid" application wait "$app_pid" || test "$?" -eq 143 app_pid="" trap - EXIT @@ -172,6 +186,7 @@ jobs: name: rpm install and launch on Fedora 44 Wayland needs: build runs-on: ubuntu-24.04 + timeout-minutes: 20 container: fedora:44 steps: - uses: actions/download-artifact@v7 @@ -207,11 +222,23 @@ jobs: --log="$diagnostics/weston.log" & weston_pid=$! app_pid="" + stop_bounded() { + local pid="$1" + local label="$2" + kill -TERM "$pid" 2>/dev/null || true + for _ in $(seq 1 200); do + kill -0 "$pid" 2>/dev/null || return 0 + sleep 0.05 + done + kill -KILL "$pid" 2>/dev/null || true + echo "$label did not stop within ten seconds of SIGTERM" >&2 + return 1 + } cleanup() { local status=$? trap - EXIT if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then - kill -TERM "$app_pid" 2>/dev/null || true + stop_bounded "$app_pid" application || status=1 wait "$app_pid" 2>/dev/null || true fi kill "$weston_pid" 2>/dev/null || true @@ -239,7 +266,7 @@ jobs: } sleep 0.1 done - kill -TERM "$app_pid" + stop_bounded "$app_pid" application wait "$app_pid" || test "$?" -eq 143 app_pid="" kill "$weston_pid" 2>/dev/null || true From 1493990b328e30dd6e7b9343afa35b53282921ad Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 21:24:59 +0800 Subject: [PATCH 5/6] ci: query the generated rpm package name --- .github/workflows/linux-installers.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml index e1f3ed67..95479445 100644 --- a/.github/workflows/linux-installers.yml +++ b/.github/workflows/linux-installers.yml @@ -203,8 +203,10 @@ jobs: mkdir -p "$diagnostics" rpm_artifact="$(find /tmp/rivet-installers -type f -name '*.rpm' -print -quit)" test -n "$rpm_artifact" + rpm_package="$(rpm -qp --qf '%{NAME}' "$rpm_artifact")" + test -n "$rpm_package" dnf install --assumeyes "$rpm_artifact" >"$diagnostics/dnf-install.log" 2>&1 - rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' smoke \ + rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' "$rpm_package" \ >"$diagnostics/installed-version.txt" test -x /opt/Smoke/RivetHost test -f /usr/share/applications/Smoke.desktop @@ -243,7 +245,7 @@ jobs: fi kill "$weston_pid" 2>/dev/null || true wait "$weston_pid" 2>/dev/null || true - dnf remove --assumeyes smoke >>"$diagnostics/dnf-remove.log" 2>&1 || true + dnf remove --assumeyes "$rpm_package" >>"$diagnostics/dnf-remove.log" 2>&1 || true exit "$status" } trap cleanup EXIT @@ -271,7 +273,7 @@ jobs: app_pid="" kill "$weston_pid" 2>/dev/null || true wait "$weston_pid" 2>/dev/null || true - dnf remove --assumeyes smoke >"$diagnostics/dnf-remove.log" 2>&1 + dnf remove --assumeyes "$rpm_package" >"$diagnostics/dnf-remove.log" 2>&1 test ! -e /opt/Smoke/RivetHost test ! -e /usr/share/applications/Smoke.desktop trap - EXIT From d7f647406d3a1740b7b913f494a78d65681e84f9 Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Sat, 10 Oct 2026 21:27:29 +0800 Subject: [PATCH 6/6] ci: qualify Debian 13 deb installs on Wayland --- .github/workflows/linux-installers.yml | 120 ++++++++++++++++++++++++- docs/linux-display.md | 7 +- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-installers.yml b/.github/workflows/linux-installers.yml index 95479445..9fc1c555 100644 --- a/.github/workflows/linux-installers.yml +++ b/.github/workflows/linux-installers.yml @@ -13,7 +13,7 @@ concurrency: jobs: build: - name: Build x64 rpm and AppImage + name: Build x64 deb, rpm, and AppImage runs-on: ubuntu-24.04 timeout-minutes: 30 steps: @@ -60,7 +60,7 @@ jobs: '(resources . ())', '(resources . ("assets")) ' '(linux-icon . "assets/icon.png") ' - '(linux-formats . ("rpm" "appimage"))') + '(linux-formats . ("deb" "rpm" "appimage"))') if updated == original: raise SystemExit("failed to configure Linux installer smoke") path.write_text(updated, encoding="utf-8") @@ -70,7 +70,7 @@ jobs: artifacts="$RUNNER_TEMP/rivet-linux-native-artifacts" diagnostics="$RUNNER_TEMP/rivet-linux-build-diagnostics" mkdir -p "$artifacts" "$diagnostics" - cp dist/*.rpm dist/*.AppImage "$artifacts/" + cp dist/*.deb dist/*.rpm dist/*.AppImage "$artifacts/" sha256sum "$artifacts"/* > "$diagnostics/SHA256SUMS" rpm -qpi "$artifacts"/*.rpm > "$diagnostics/rpm-info.txt" rpm -qp --requires "$artifacts"/*.rpm > "$diagnostics/rpm-requires.txt" @@ -182,6 +182,118 @@ jobs: path: ${{ runner.temp }}/rivet-appimage-diagnostics if-no-files-found: warn + debian-deb-wayland: + name: deb install and launch on Debian 13 Wayland + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 20 + container: debian:13 + steps: + - uses: actions/download-artifact@v7 + with: + name: linux-native-installers + path: /tmp/rivet-installers + - name: Install desktop runtime + env: + DEBIAN_FRONTEND: noninteractive + run: | + apt-get update + apt-get install --yes \ + dbus-daemon desktop-file-utils libgtk-4-1 libsecret-1-0 \ + procps weston + - name: Install, launch, and uninstall deb + shell: bash + run: | + set -euo pipefail + diagnostics=/tmp/rivet-debian-diagnostics + mkdir -p "$diagnostics" + deb_artifact="$(find /tmp/rivet-installers -type f -name '*.deb' -print -quit)" + test -n "$deb_artifact" + deb_package="$(dpkg-deb --field "$deb_artifact" Package)" + test -n "$deb_package" + apt-get install --yes "$deb_artifact" >"$diagnostics/apt-install.log" 2>&1 + dpkg-query --show --showformat='${Package} ${Version} ${Architecture}\n' \ + "$deb_package" >"$diagnostics/installed-version.txt" + test -x /opt/Smoke/RivetHost + test -f /usr/share/applications/Smoke.desktop + test -f /usr/share/pixmaps/Smoke.png + desktop-file-validate /usr/share/applications/Smoke.desktop + test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource + + export XDG_RUNTIME_DIR=/tmp/rivet-debian-wayland-runtime + export WAYLAND_DISPLAY=wayland-rivet-debian + export GDK_BACKEND=wayland + unset DISPLAY + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + weston --backend=headless-backend.so \ + --socket="$WAYLAND_DISPLAY" --idle-time=0 \ + --log="$diagnostics/weston.log" & + weston_pid=$! + app_pid="" + stop_bounded() { + local pid="$1" + local label="$2" + kill -TERM "$pid" 2>/dev/null || true + for _ in $(seq 1 200); do + kill -0 "$pid" 2>/dev/null || return 0 + sleep 0.05 + done + kill -KILL "$pid" 2>/dev/null || true + echo "$label did not stop within ten seconds of SIGTERM" >&2 + return 1 + } + cleanup() { + local status=$? + trap - EXIT + if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then + stop_bounded "$app_pid" application || status=1 + wait "$app_pid" 2>/dev/null || true + fi + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + apt-get remove --yes "$deb_package" \ + >>"$diagnostics/apt-remove.log" 2>&1 || true + exit "$status" + } + trap cleanup EXIT + for _ in $(seq 1 100); do + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" && break + kill -0 "$weston_pid" 2>/dev/null || { + cat "$diagnostics/weston.log" + exit 1 + } + sleep 0.05 + done + test -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + /opt/Smoke/RivetHost >"$diagnostics/application.log" 2>&1 & + app_pid=$! + for _ in $(seq 1 50); do + kill -0 "$app_pid" 2>/dev/null || { + wait "$app_pid" || true + cat "$diagnostics/application.log" + exit 1 + } + sleep 0.1 + done + stop_bounded "$app_pid" application + wait "$app_pid" || test "$?" -eq 143 + app_pid="" + kill "$weston_pid" 2>/dev/null || true + wait "$weston_pid" 2>/dev/null || true + apt-get remove --yes "$deb_package" >"$diagnostics/apt-remove.log" 2>&1 + test ! -e /opt/Smoke/RivetHost + test ! -e /usr/share/applications/Smoke.desktop + test ! -e /usr/share/pixmaps/Smoke.png + trap - EXIT + - name: Upload Debian diagnostics + if: always() + uses: actions/upload-artifact@v6 + with: + name: debian-deb-wayland-diagnostics + path: /tmp/rivet-debian-diagnostics + if-no-files-found: warn + fedora-rpm-wayland: name: rpm install and launch on Fedora 44 Wayland needs: build @@ -210,6 +322,7 @@ jobs: >"$diagnostics/installed-version.txt" test -x /opt/Smoke/RivetHost test -f /usr/share/applications/Smoke.desktop + test -f /usr/share/pixmaps/Smoke.png desktop-file-validate /usr/share/applications/Smoke.desktop test "$(cat /opt/Smoke/app/assets/nested/product.txt)" = packaged-resource @@ -276,6 +389,7 @@ jobs: dnf remove --assumeyes "$rpm_package" >"$diagnostics/dnf-remove.log" 2>&1 test ! -e /opt/Smoke/RivetHost test ! -e /usr/share/applications/Smoke.desktop + test ! -e /usr/share/pixmaps/Smoke.png trap - EXIT - name: Upload Fedora diagnostics if: always() diff --git a/docs/linux-display.md b/docs/linux-display.md index 1ccf1bd6..3c599a9a 100644 --- a/docs/linux-display.md +++ b/docs/linux-display.md @@ -57,7 +57,7 @@ unbounded list of historical images: | Family | Qualification policy | Automated release evidence | | --- | --- | --- | | Ubuntu | Supported LTS releases while they receive standard security maintenance | Ubuntu 24.04: deb lifecycle and AppImage launch under headless native Wayland | -| Debian | Current stable release during Debian's regular support period | Debian 13 is the qualification target; it is not promoted until its install gate exists | +| Debian | Current stable release during Debian's regular support period | Debian 13: deb install, launch, and uninstall under headless native Wayland | | Fedora | Current and previous Fedora releases while upstream still maintains them | Fedora 44: rpm install, launch, and uninstall under headless native Wayland | The matrix is reviewed for every Rivet minor release. A distribution enters the @@ -67,6 +67,11 @@ manager, launched outside the build tree through `GDK_BACKEND=wayland` with no dependency closure and replacement policy differ from deb/rpm. CI uploads the package-manager transcript, exact installed version, compositor log, and application log so a failed gate can be reproduced rather than guessed at. +The installed-package gates also validate the desktop entry, packaged icon, +resource payload, graceful SIGTERM handling, and complete metadata removal. +The Linux integration runner separately exercises single-instance forwarding, +autostart persistence, crash/shutdown hooks, and capability-conditioned +notifications, StatusNotifierItem tray, and Secret Service storage. Fedora advances on its roughly thirteen-month upstream lifecycle; CI pins a specific maintained release instead of using `fedora:latest`. Ubuntu LTS and