From 82d625660dd10b6c663d755d7382af57fc068d0f Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 17:56:55 +0530 Subject: [PATCH 1/9] Added support of Ubumtu 22+, Fedora 39+ --- .github/workflows/ci.yml | 82 +++++++++++++++++ .github/workflows/release.yml | 18 +++- README.md | 15 ++-- scripts/build-release.sh | 161 +++++++++++++++++++++++++++++----- scripts/dependencies.sh | 92 ++++++++++++------- 5 files changed, 309 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 299e379..f6e1727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,9 @@ on: - master pull_request: +env: + APPIMAGE_EXTRACT_AND_RUN: "1" + jobs: build-qtwebengine: runs-on: ubuntu-24.04 @@ -109,3 +112,82 @@ jobs: ldd artifacts/webkitgtk/safe-exam-browser | grep -q 'libwebkit2gtk-4.1' ! ldd artifacts/webkitgtk/safe-exam-browser | grep -q 'libQt6WebEngine' artifacts/webkitgtk/safe-exam-browser --version + + # Portable AppImages are built on Ubuntu 22.04 (glibc 2.35) for Ubuntu 22.04+ / Fedora 39+. + # Full AppImage packaging is heavy; run on main/master pushes (not every PR). + build-appimage: + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + runs-on: ubuntu-latest + container: + image: ubuntu:22.04 + steps: + - name: Bootstrap container packages + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + git \ + wget \ + sudo \ + binutils + + - uses: actions/checkout@v7 + + - name: Build AppImage artifacts + run: | + chmod +x scripts/build-release.sh + scripts/build-release.sh "0.0.0-ci" + + - name: Upload AppImage artifacts + uses: actions/upload-artifact@v7 + with: + name: safe-exam-browser-appimage + path: | + dist/*.AppImage + if-no-files-found: error + + appimage-smoke-ubuntu: + needs: + - build-appimage + runs-on: ubuntu-24.04 + steps: + - name: Download AppImage artifacts + uses: actions/download-artifact@v8 + with: + name: safe-exam-browser-appimage + path: dist + + - name: Smoke-test AppImages on Ubuntu + env: + QT_QPA_PLATFORM: offscreen + APPIMAGE_EXTRACT_AND_RUN: "1" + run: | + chmod +x dist/*.AppImage + for appimage in dist/*.AppImage; do + echo "Running ${appimage} --version" + "${appimage}" --version + done + + appimage-smoke-fedora: + needs: + - build-appimage + runs-on: ubuntu-latest + container: + image: fedora:latest + steps: + - name: Download AppImage artifacts + uses: actions/download-artifact@v8 + with: + name: safe-exam-browser-appimage + path: dist + + - name: Smoke-test AppImages on Fedora + env: + QT_QPA_PLATFORM: offscreen + APPIMAGE_EXTRACT_AND_RUN: "1" + run: | + chmod +x dist/*.AppImage + for appimage in dist/*.AppImage; do + echo "Running ${appimage} --version" + "${appimage}" --version + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f986c4..a7fb786 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,24 @@ on: permissions: contents: write +env: + APPIMAGE_EXTRACT_AND_RUN: "1" + jobs: build-linux: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest + container: + image: ubuntu:22.04 steps: + - name: Bootstrap container packages + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates \ + git \ + wget \ + sudo + - uses: actions/checkout@v7 - name: Resolve release version @@ -53,6 +67,8 @@ jobs: body: | Thanks to everyone who contributed to this release. + Supported targets: Ubuntu 22.04+, Fedora 39+ (x86_64), glibc >= 2.35. + GitHub generated release notes below include the commit summary and new contributors automatically. files: | dist/*.AppImage diff --git a/README.md b/README.md index aa67a1c..6409f9c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,11 @@ so you can just focus on your exam. ### Running Everywhere Safe Exam Browser has options for QtWebEngine and WebKitGTK. -We've also packaged all of the dependencies inside AppImage files so -Safe Exam Browser for Linux really can run anywhere. +We've also packaged dependencies inside AppImage files for portable installs. + +**Supported AppImage targets (x86_64):** Ubuntu 22.04+, Fedora 39+, and other +distributions with **glibc ≥ 2.35**. Release AppImages are built on Ubuntu 22.04 +so they stay compatible across those systems. ## Getting Started @@ -35,10 +38,10 @@ and `libgtk-3-dev` installed so we can use the fallback engine. ### How to Build -Note: This is for binary builds, for AppImage builds you can use -prebuilt images or utilize `./scripts/build-release.sh`, the dependencies should -be installed automatically on debian systems. It is recommended to build inside -of a debian system or a debian container. +Note: This is for binary builds. For AppImage builds use prebuilt releases or +`./scripts/build-release.sh` (dependencies install automatically on Debian/Ubuntu). +Build AppImages on **Ubuntu 22.04** (or an Ubuntu 22.04 container) so the glibc +floor stays at 2.35 and the result runs on Ubuntu 22.04+ and Fedora 39+. For binary builds, it's pretty simple: diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 3f20970..ac872af 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -9,32 +9,41 @@ PROJECT_FILE="${PROJECT_DIR}/seb-linux-qt.pro" ARTIFACT_DIR="${PROJECT_DIR}/dist" TOOLS_DIR="${PROJECT_DIR}/build-tools" PACKAGE_NAME="safe-exam-browser" +# AppImages inherit the build host glibc. Ubuntu 22.04 sets the floor at 2.35. +MAX_GLIBC_VERSION="${MAX_GLIBC_VERSION:-2.35}" LINUXDEPLOY_VERSION="1-alpha-20250213-2" LINUXDEPLOY_PLUGIN_QT_VERSION="1-alpha-20250213-1" LINUXDEPLOY_SHA256="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a" LINUXDEPLOY_PLUGIN_QT_SHA256="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724" LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/${LINUXDEPLOY_VERSION}/linuxdeploy-x86_64.AppImage" LINUXDEPLOY_PLUGIN_QT_URL="https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/${LINUXDEPLOY_PLUGIN_QT_VERSION}/linuxdeploy-plugin-qt-x86_64.AppImage" -LINUXBUILD_EXTRA_ARGS=( - -l /usr/lib/x86_64-linux-gnu/libssl.so.3 - -l /usr/lib/x86_64-linux-gnu/libsoftokn3.so - -l /usr/lib/x86_64-linux-gnu/libatomic.so.1 - -l /usr/lib/x86_64-linux-gnu/libdeflate.so.0 - -l /usr/lib/x86_64-linux-gnu/libjbig.so.0 - -l /usr/lib/x86_64-linux-gnu/libjpeg.so.62 - -l /usr/lib/x86_64-linux-gnu/libLerc.so.4 - -l /usr/lib/x86_64-linux-gnu/libQt6Pdf.so.6 - -l /usr/lib/x86_64-linux-gnu/libQt6QmlWorkerScript.so.6 - -l /usr/lib/x86_64-linux-gnu/libsmime3.so - -l /usr/lib/x86_64-linux-gnu/libssh2.so.1 - -l /usr/lib/x86_64-linux-gnu/libtiff.so.6 - -l /usr/lib/x86_64-linux-gnu/libxcb-cursor.so.0 - -l /usr/lib/x86_64-linux-gnu/libxcb-xinput.so.0 + +# Prefer extract-and-run so nested AppImages work without FUSE (CI containers). +export APPIMAGE_EXTRACT_AND_RUN="${APPIMAGE_EXTRACT_AND_RUN:-1}" + +# Library basenames to bundle via linuxdeploy -l (resolved at runtime). +REQUIRED_EXTRA_LIBS=( + libssl.so.3 + libsoftokn3.so + libatomic.so.1 + libdeflate.so.0 + libjbig.so.0 + libjpeg.so.62 + libQt6Pdf.so.6 + libQt6QmlWorkerScript.so.6 + libsmime3.so + libssh2.so.1 + libtiff.so.6 + libxcb-cursor.so.0 + libxcb-xinput.so.0 ) -# -l /usr/lib/x86_64-linux-gnu/libnghttp3.so.9 -# -l /usr/lib/x86_64-linux-gnu/libngtcp2.so.16 -l /usr/lib/x86_64-linux-gnu/libngtcp2_crypto_gnutls.so.8 -# -l /usr/lib/x86_64-linux-gnu/libQt6QmlMeta.so.6 -# -l /usr/lib/x86_64-linux-gnu/libxml2.so.16 + +# Optional: Jammy has libLerc.so.3, Noble+ has libLerc.so.4. +OPTIONAL_EXTRA_LIBS=( + libLerc.so.4 + libLerc.so.3 +) + echo "Variables set" verify_sha256() { @@ -86,6 +95,113 @@ run_logged() { fi } +resolve_library_path() { + local lib_name="$1" + local resolved="" + + if command -v ldconfig >/dev/null; then + resolved="$(ldconfig -p 2>/dev/null | awk -v name="${lib_name}" ' + $1 == name { + for (i = 1; i <= NF; i++) { + if ($i ~ /^\//) { + print $i + exit + } + } + } + ')" + fi + + if [[ -z "${resolved}" ]]; then + local candidate + for candidate in \ + "/usr/lib/x86_64-linux-gnu/${lib_name}" \ + "/usr/lib64/${lib_name}" \ + "/usr/lib/${lib_name}"; do + if [[ -e "${candidate}" ]]; then + resolved="${candidate}" + break + fi + done + fi + + if [[ -n "${resolved}" ]]; then + # Prefer the real file over a dangling/relative ldconfig entry. + if [[ -e "${resolved}" ]]; then + readlink -f "${resolved}" 2>/dev/null || printf '%s\n' "${resolved}" + return 0 + fi + fi + + return 1 +} + +build_linuxdeploy_extra_args() { + local lib_name + local lib_path + local found_lerc=0 + + LINUXBUILD_EXTRA_ARGS=() + + for lib_name in "${REQUIRED_EXTRA_LIBS[@]}"; do + if ! lib_path="$(resolve_library_path "${lib_name}")"; then + echo "Required library not found: ${lib_name}" >&2 + exit 1 + fi + LINUXBUILD_EXTRA_ARGS+=(-l "${lib_path}") + done + + for lib_name in "${OPTIONAL_EXTRA_LIBS[@]}"; do + if lib_path="$(resolve_library_path "${lib_name}")"; then + LINUXBUILD_EXTRA_ARGS+=(-l "${lib_path}") + found_lerc=1 + break + fi + done + + if [[ "${found_lerc}" -eq 0 ]]; then + echo "Warning: libLerc not found (optional); continuing without it" >&2 + fi +} + +version_gt() { + # Return 0 if $1 > $2 (dotted numeric versions). + local left="$1" + local right="$2" + [[ "$(printf '%s\n' "${left}" "${right}" | sort -V | tail -n1)" == "${left}" && "${left}" != "${right}" ]] +} + +check_glibc_symbols() { + local app_dir="$1" + local max_version="$2" + local elf_file + local symbol + local version + local too_new=0 + + if ! command -v objdump >/dev/null; then + echo "objdump not found; cannot verify glibc symbol versions" >&2 + exit 1 + fi + + echo "Checking glibc symbol versions in ${app_dir} (max ${max_version})" + + while IFS= read -r -d '' elf_file; do + while IFS= read -r symbol; do + version="${symbol#GLIBC_}" + if version_gt "${version}" "${max_version}"; then + echo "glibc symbol ${symbol} in ${elf_file} exceeds ${max_version}" >&2 + too_new=1 + fi + done < <(objdump -T "${elf_file}" 2>/dev/null | grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' | sort -u || true) + done < <(find "${app_dir}" -type f \( -perm -111 -o -name '*.so*' \) -print0) + + if [[ "${too_new}" -ne 0 ]]; then + echo "AppDir requires glibc newer than ${max_version}; rebuild on an older base" >&2 + exit 1 + fi +} + build_appimage() { local config="$1" local build_dir="$2" @@ -117,6 +233,8 @@ build_appimage() { "${LINUXBUILD_EXTRA_ARGS[@]}" popd >/dev/null + check_glibc_symbols "${app_dir}" "${MAX_GLIBC_VERSION}" + appimage_path="$(find "${appimage_output_dir}" -maxdepth 1 -type f -name '*.AppImage' -print -quit)" if [[ -z "${appimage_path}" ]]; then echo "linuxdeploy did not produce an AppImage" >&2 @@ -127,7 +245,7 @@ build_appimage() { } echo "Installing dependencies" -${ROOT_DIR}/scripts/dependencies.sh +"${ROOT_DIR}/scripts/dependencies.sh" unset QML_SOURCES_PATHS unset QMAKE @@ -150,6 +268,9 @@ download_verified_tool \ "${TOOLS_DIR}/linuxdeploy-plugin-qt-x86_64.AppImage" \ "${LINUXDEPLOY_PLUGIN_QT_SHA256}" +build_linuxdeploy_extra_args +echo "Resolved ${#LINUXBUILD_EXTRA_ARGS[@]} linuxdeploy extra library args" + echo "Building for QtWebEngine" build_appimage \ "force_qtwebengine" \ diff --git a/scripts/dependencies.sh b/scripts/dependencies.sh index 8edf098..321884a 100755 --- a/scripts/dependencies.sh +++ b/scripts/dependencies.sh @@ -1,37 +1,65 @@ #!/bin/bash +set -euo pipefail -if command -v apt >/dev/null; then - echo "installing dependencies" - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - desktop-file-utils \ - dpkg-dev \ - libqt6svg6-dev \ - qt6-base-dev \ - qt6-tools-dev-tools \ - qt6-webengine-dev \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - shared-mime-info \ - zlib1g-dev \ - libssl-dev \ - file \ - libatomic1 \ - libdeflate0 \ - libjbig0 \ - liblerc4 \ - libngtcp2-dev \ - libngtcp2-crypto-gnutls-dev \ - libqt6pdf6 \ - libqt6qmlworkerscript6 \ - libnss3 \ - libssh2-1 \ - libssl3 \ - libtiff-dev \ - libxcb-cursor0 \ - libxcb-xinput0 \ +# Release AppImage builds are expected on Debian/Ubuntu (preferably Ubuntu 22.04). +# Non-Debian systems skip install; use a Debian/Ubuntu container for packaging. + +if ! command -v apt-get >/dev/null; then + echo "detected non-debian system, skipping dependency install" + exit 0 +fi + +run_as_root() { + if [[ "${EUID}" -eq 0 ]]; then + "$@" + else + sudo "$@" + fi +} + +echo "installing dependencies" + +run_as_root apt-get update + +PACKAGES=( + binutils + build-essential + desktop-file-utils + dpkg-dev + libqt6svg6-dev + qt6-base-dev + qt6-tools-dev-tools + qt6-webengine-dev + libwebkit2gtk-4.1-dev + libgtk-3-dev + shared-mime-info + zlib1g-dev + libssl-dev + file + libatomic1 + libdeflate0 + libjbig0 + libngtcp2-dev + libngtcp2-crypto-gnutls-dev + libqt6pdf6 + libqt6qmlworkerscript6 + libnss3 + libssh2-1 + libssl3 + libtiff-dev + libxcb-cursor0 + libxcb-xinput0 libjpeg62 + wget +) + +# Jammy provides liblerc3; Noble+ provides liblerc4. Install at most one. +if apt-cache show liblerc4 >/dev/null 2>&1; then + PACKAGES+=(liblerc4) +elif apt-cache show liblerc3 >/dev/null 2>&1; then + PACKAGES+=(liblerc3) else - echo "detected non-debian system, skipping dependency install" + echo "optional package not available, skipping: liblerc*" fi + +run_as_root apt-get install -y "${PACKAGES[@]}" From 96fa18679cfd4da09c0e348c7f8cf3b99442ed99 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 18:25:29 +0530 Subject: [PATCH 2/9] Added support of Ubumtu 22+, Fedora 39+ --- scripts/build-release.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/build-release.sh b/scripts/build-release.sh index ac872af..4461e06 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -98,6 +98,7 @@ run_logged() { resolve_library_path() { local lib_name="$1" local resolved="" + local candidate if command -v ldconfig >/dev/null; then resolved="$(ldconfig -p 2>/dev/null | awk -v name="${lib_name}" ' @@ -112,11 +113,15 @@ resolve_library_path() { ')" fi - if [[ -z "${resolved}" ]]; then - local candidate + # NSS modules (e.g. libsoftokn3.so) live under .../nss/ and are often absent + # from ldconfig -p / the top-level multiarch lib dir. + if [[ -z "${resolved}" || ! -e "${resolved}" ]]; then for candidate in \ + "/usr/lib/x86_64-linux-gnu/nss/${lib_name}" \ "/usr/lib/x86_64-linux-gnu/${lib_name}" \ + "/usr/lib64/nss/${lib_name}" \ "/usr/lib64/${lib_name}" \ + "/usr/lib/nss/${lib_name}" \ "/usr/lib/${lib_name}"; do if [[ -e "${candidate}" ]]; then resolved="${candidate}" @@ -125,12 +130,9 @@ resolve_library_path() { done fi - if [[ -n "${resolved}" ]]; then - # Prefer the real file over a dangling/relative ldconfig entry. - if [[ -e "${resolved}" ]]; then - readlink -f "${resolved}" 2>/dev/null || printf '%s\n' "${resolved}" - return 0 - fi + if [[ -n "${resolved}" && -e "${resolved}" ]]; then + readlink -f "${resolved}" 2>/dev/null || printf '%s\n' "${resolved}" + return 0 fi return 1 From c5ce38373349b9550f00833255dbe87b95958ccc Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 18:32:49 +0530 Subject: [PATCH 3/9] Added support of Ubumtu 22+, Fedora 39+ --- scripts/build-release.sh | 33 ++++++++++++++++++--------------- scripts/dependencies.sh | 10 ++++++++-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 4461e06..7b44eee 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -33,15 +33,14 @@ REQUIRED_EXTRA_LIBS=( libQt6QmlWorkerScript.so.6 libsmime3.so libssh2.so.1 - libtiff.so.6 libxcb-cursor.so.0 libxcb-xinput.so.0 ) -# Optional: Jammy has libLerc.so.3, Noble+ has libLerc.so.4. -OPTIONAL_EXTRA_LIBS=( - libLerc.so.4 - libLerc.so.3 +# Prefer newer sonames first. Jammy: libtiff.so.5 / libLerc.so.3; Noble+: .6 / .4. +ALTERNATE_EXTRA_LIBS=( + "libtiff.so.6|libtiff.so.5" + "libLerc.so.4|libLerc.so.3" ) echo "Variables set" @@ -141,7 +140,8 @@ resolve_library_path() { build_linuxdeploy_extra_args() { local lib_name local lib_path - local found_lerc=0 + local alt_group + local found LINUXBUILD_EXTRA_ARGS=() @@ -153,17 +153,20 @@ build_linuxdeploy_extra_args() { LINUXBUILD_EXTRA_ARGS+=(-l "${lib_path}") done - for lib_name in "${OPTIONAL_EXTRA_LIBS[@]}"; do - if lib_path="$(resolve_library_path "${lib_name}")"; then - LINUXBUILD_EXTRA_ARGS+=(-l "${lib_path}") - found_lerc=1 - break + for alt_group in "${ALTERNATE_EXTRA_LIBS[@]}"; do + found=0 + IFS='|' read -r -a alt_libs <<< "${alt_group}" + for lib_name in "${alt_libs[@]}"; do + if lib_path="$(resolve_library_path "${lib_name}")"; then + LINUXBUILD_EXTRA_ARGS+=(-l "${lib_path}") + found=1 + break + fi + done + if [[ "${found}" -eq 0 ]]; then + echo "Warning: none of these libraries found (optional): ${alt_group}" >&2 fi done - - if [[ "${found_lerc}" -eq 0 ]]; then - echo "Warning: libLerc not found (optional); continuing without it" >&2 - fi } version_gt() { diff --git a/scripts/dependencies.sh b/scripts/dependencies.sh index 321884a..d232e8c 100755 --- a/scripts/dependencies.sh +++ b/scripts/dependencies.sh @@ -46,14 +46,20 @@ PACKAGES=( libnss3 libssh2-1 libssl3 - libtiff-dev libxcb-cursor0 libxcb-xinput0 libjpeg62 wget ) -# Jammy provides liblerc3; Noble+ provides liblerc4. Install at most one. +# Jammy: libtiff5 / liblerc3; Noble+: libtiff6 / liblerc4. +if apt-cache show libtiff6 >/dev/null 2>&1; then + PACKAGES+=(libtiff6) +elif apt-cache show libtiff5 >/dev/null 2>&1; then + PACKAGES+=(libtiff5) +fi +PACKAGES+=(libtiff-dev) + if apt-cache show liblerc4 >/dev/null 2>&1; then PACKAGES+=(liblerc4) elif apt-cache show liblerc3 >/dev/null 2>&1; then From abb134af68218538167e1b018f20cb4253b1ac29 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 18:43:08 +0530 Subject: [PATCH 4/9] Added support of Ubumtu 22+, Fedora 39+ --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ scripts/build-release.sh | 1 + scripts/dependencies.sh | 1 + 3 files changed, 28 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6e1727..2d65b08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,6 +151,20 @@ jobs: - build-appimage runs-on: ubuntu-24.04 steps: + - name: Install AppImage host runtime libs + run: | + # libEGL/GL stay on the host (not bundled). Fonts/GTK bits help the WebKit AppImage. + sudo apt-get update + sudo apt-get install -y \ + libegl1 \ + libgl1 \ + libgles2 \ + libfreetype6 \ + fontconfig \ + libxkbcommon0 \ + libgtk-3-0t64 \ + libwebkit2gtk-4.1-0 + - name: Download AppImage artifacts uses: actions/download-artifact@v8 with: @@ -175,6 +189,18 @@ jobs: container: image: fedora:latest steps: + - name: Install AppImage host runtime libs + run: | + dnf install -y \ + libglvnd-egl \ + mesa-libEGL \ + mesa-libGL \ + freetype \ + fontconfig \ + libxkbcommon \ + gtk3 \ + webkit2gtk4.1 + - name: Download AppImage artifacts uses: actions/download-artifact@v8 with: diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 7b44eee..36e1d5b 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -27,6 +27,7 @@ REQUIRED_EXTRA_LIBS=( libsoftokn3.so libatomic.so.1 libdeflate.so.0 + libfreetype.so.6 libjbig.so.0 libjpeg.so.62 libQt6Pdf.so.6 diff --git a/scripts/dependencies.sh b/scripts/dependencies.sh index d232e8c..1ca136b 100755 --- a/scripts/dependencies.sh +++ b/scripts/dependencies.sh @@ -39,6 +39,7 @@ PACKAGES=( libatomic1 libdeflate0 libjbig0 + libfreetype6 libngtcp2-dev libngtcp2-crypto-gnutls-dev libqt6pdf6 From 94d1a2f04f3e654065cbf04f31a7d350ec08d9ef Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 18:51:54 +0530 Subject: [PATCH 5/9] Added support of Ubumtu 22+, Fedora 39+ --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d65b08..efb056b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,12 +153,13 @@ jobs: steps: - name: Install AppImage host runtime libs run: | - # libEGL/GL stay on the host (not bundled). Fonts/GTK bits help the WebKit AppImage. + # GL/EGL/OpenGL stay on the host (not bundled). Fonts/GTK bits help the WebKit AppImage. sudo apt-get update sudo apt-get install -y \ libegl1 \ libgl1 \ libgles2 \ + libopengl0 \ libfreetype6 \ fontconfig \ libxkbcommon0 \ @@ -193,6 +194,8 @@ jobs: run: | dnf install -y \ libglvnd-egl \ + libglvnd-glx \ + libglvnd-opengl \ mesa-libEGL \ mesa-libGL \ freetype \ From dcd5b3f32d60d2ac7416b5ecd19376cd4ee12d15 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Tue, 21 Jul 2026 19:01:25 +0530 Subject: [PATCH 6/9] Added support of Ubumtu 22+, Fedora 39+ --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efb056b..1009a67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,6 +154,7 @@ jobs: - name: Install AppImage host runtime libs run: | # GL/EGL/OpenGL stay on the host (not bundled). Fonts/GTK bits help the WebKit AppImage. + # AppImages only ship the xcb Qt plugin, so smoke under Xvfb. sudo apt-get update sudo apt-get install -y \ libegl1 \ @@ -164,7 +165,8 @@ jobs: fontconfig \ libxkbcommon0 \ libgtk-3-0t64 \ - libwebkit2gtk-4.1-0 + libwebkit2gtk-4.1-0 \ + xvfb - name: Download AppImage artifacts uses: actions/download-artifact@v8 @@ -174,13 +176,13 @@ jobs: - name: Smoke-test AppImages on Ubuntu env: - QT_QPA_PLATFORM: offscreen + QT_QPA_PLATFORM: xcb APPIMAGE_EXTRACT_AND_RUN: "1" run: | chmod +x dist/*.AppImage for appimage in dist/*.AppImage; do echo "Running ${appimage} --version" - "${appimage}" --version + xvfb-run -a "${appimage}" --version done appimage-smoke-fedora: @@ -202,7 +204,8 @@ jobs: fontconfig \ libxkbcommon \ gtk3 \ - webkit2gtk4.1 + webkit2gtk4.1 \ + xorg-x11-server-Xvfb - name: Download AppImage artifacts uses: actions/download-artifact@v8 @@ -212,11 +215,11 @@ jobs: - name: Smoke-test AppImages on Fedora env: - QT_QPA_PLATFORM: offscreen + QT_QPA_PLATFORM: xcb APPIMAGE_EXTRACT_AND_RUN: "1" run: | chmod +x dist/*.AppImage for appimage in dist/*.AppImage; do echo "Running ${appimage} --version" - "${appimage}" --version + xvfb-run -a "${appimage}" --version done From e1fe6670500ad31d352ff06f527d12e57a2b86a8 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Wed, 22 Jul 2026 15:09:53 +0530 Subject: [PATCH 7/9] Added support of Ubumtu 22+, Fedora 39+ --- src/main.cpp | 69 ++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a645f8b..d00d5e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,32 @@ static void appendPkexecEnvironmentVariable(QStringList &args, const char *name) } } +// Build pkexec argv without --keep-cwd (unsupported on Ubuntu 22.04 / pkexec 0.105). +// Preserve the caller's cwd via /bin/sh so AppImage and relative paths keep working. +static QStringList buildPkexecArguments(const QStringList &appArgs, + const QStringList &extraEnvAssignments = {}) +{ + QStringList pkexecArgs; + pkexecArgs << QStringLiteral("env"); + appendPkexecEnvironmentVariable(pkexecArgs, "DISPLAY"); + appendPkexecEnvironmentVariable(pkexecArgs, "WAYLAND_DISPLAY"); + appendPkexecEnvironmentVariable(pkexecArgs, "XAUTHORITY"); + appendPkexecEnvironmentVariable(pkexecArgs, "XDG_RUNTIME_DIR"); + appendPkexecEnvironmentVariable(pkexecArgs, "XDG_SESSION_TYPE"); + appendPkexecEnvironmentVariable(pkexecArgs, "DBUS_SESSION_BUS_ADDRESS"); + appendPkexecEnvironmentVariable(pkexecArgs, "QT_QPA_PLATFORM"); + pkexecArgs << extraEnvAssignments; + + pkexecArgs << QStringLiteral("/bin/sh"); + pkexecArgs << QStringLiteral("-c"); + pkexecArgs << QStringLiteral("cd -- \"$1\" && shift && exec \"$@\""); + pkexecArgs << QStringLiteral("sh"); + pkexecArgs << QDir::currentPath(); + pkexecArgs << QCoreApplication::applicationFilePath(); + pkexecArgs << appArgs; + return pkexecArgs; +} + static int runNonDetachedPkexecChild(QProcess &child) { // Do not detach. Forward outputs to parent. @@ -460,21 +487,7 @@ int main(int argc, char *argv[]) QProcess child; child.setProgram(QStringLiteral("pkexec")); - - QStringList pkexecArgs; - pkexecArgs << QStringLiteral("--keep-cwd"); - pkexecArgs << QStringLiteral("env"); - appendPkexecEnvironmentVariable(pkexecArgs, "DISPLAY"); - appendPkexecEnvironmentVariable(pkexecArgs, "WAYLAND_DISPLAY"); - appendPkexecEnvironmentVariable(pkexecArgs, "XAUTHORITY"); - appendPkexecEnvironmentVariable(pkexecArgs, "XDG_RUNTIME_DIR"); - appendPkexecEnvironmentVariable(pkexecArgs, "XDG_SESSION_TYPE"); - appendPkexecEnvironmentVariable(pkexecArgs, "DBUS_SESSION_BUS_ADDRESS"); - appendPkexecEnvironmentVariable(pkexecArgs, "QT_QPA_PLATFORM"); - pkexecArgs << QCoreApplication::applicationFilePath(); - pkexecArgs << args; - - child.setArguments(pkexecArgs); + child.setArguments(buildPkexecArguments(args)); return runNonDetachedPkexecChild(child); } @@ -498,27 +511,15 @@ int main(int argc, char *argv[]) if (!args.contains(QStringLiteral("--anti-cheat"))) { args.prepend(QStringLiteral("--anti-cheat")); } - - QProcess child; - child.setProgram(QStringLiteral("pkexec")); - - QStringList pkexecArgs; - pkexecArgs << QStringLiteral("--keep-cwd"); - pkexecArgs << QStringLiteral("env"); - appendPkexecEnvironmentVariable(pkexecArgs, "DISPLAY"); - appendPkexecEnvironmentVariable(pkexecArgs, "WAYLAND_DISPLAY"); - appendPkexecEnvironmentVariable(pkexecArgs, "XAUTHORITY"); - appendPkexecEnvironmentVariable(pkexecArgs, "XDG_RUNTIME_DIR"); - appendPkexecEnvironmentVariable(pkexecArgs, "XDG_SESSION_TYPE"); - appendPkexecEnvironmentVariable(pkexecArgs, "DBUS_SESSION_BUS_ADDRESS"); - appendPkexecEnvironmentVariable(pkexecArgs, "QT_QPA_PLATFORM"); + + QStringList extraEnv; if (usedPassword) { - pkexecArgs << (QStringLiteral("SEB_PASSWORD=") + userPassword); + extraEnv << (QStringLiteral("SEB_PASSWORD=") + userPassword); } - pkexecArgs << QCoreApplication::applicationFilePath(); - pkexecArgs << args; - - child.setArguments(pkexecArgs); + + QProcess child; + child.setProgram(QStringLiteral("pkexec")); + child.setArguments(buildPkexecArguments(args, extraEnv)); return runNonDetachedPkexecChild(child); } } From b9c3ba39295072705d88610c824f329ac65a7658 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Wed, 22 Jul 2026 15:49:38 +0530 Subject: [PATCH 8/9] Added support of Ubumtu 22+, Fedora 39+ --- src/main.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d00d5e8..0c8ddc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -149,6 +149,21 @@ static void appendPkexecEnvironmentVariable(QStringList &args, const char *name) } } +// When running from an AppImage, applicationFilePath() is under the FUSE mount +// (/tmp/.mount_*/usr/bin/...). Root cannot execute that path after pkexec. +// Prefer $APPIMAGE so elevation re-runs the outer AppImage file instead. +static QString elevatedExecutablePath() +{ + const QByteArray appImage = qgetenv("APPIMAGE"); + if (!appImage.isEmpty()) { + const QString path = QString::fromLocal8Bit(appImage); + if (QFileInfo::exists(path)) { + return path; + } + } + return QCoreApplication::applicationFilePath(); +} + // Build pkexec argv without --keep-cwd (unsupported on Ubuntu 22.04 / pkexec 0.105). // Preserve the caller's cwd via /bin/sh so AppImage and relative paths keep working. static QStringList buildPkexecArguments(const QStringList &appArgs, @@ -163,6 +178,12 @@ static QStringList buildPkexecArguments(const QStringList &appArgs, appendPkexecEnvironmentVariable(pkexecArgs, "XDG_SESSION_TYPE"); appendPkexecEnvironmentVariable(pkexecArgs, "DBUS_SESSION_BUS_ADDRESS"); appendPkexecEnvironmentVariable(pkexecArgs, "QT_QPA_PLATFORM"); + // Keep AppImage runtime hints for the elevated re-exec. + appendPkexecEnvironmentVariable(pkexecArgs, "APPIMAGE"); + appendPkexecEnvironmentVariable(pkexecArgs, "APPDIR"); + appendPkexecEnvironmentVariable(pkexecArgs, "OWD"); + appendPkexecEnvironmentVariable(pkexecArgs, "ARGV0"); + appendPkexecEnvironmentVariable(pkexecArgs, "APPIMAGE_EXTRACT_AND_RUN"); pkexecArgs << extraEnvAssignments; pkexecArgs << QStringLiteral("/bin/sh"); @@ -170,7 +191,7 @@ static QStringList buildPkexecArguments(const QStringList &appArgs, pkexecArgs << QStringLiteral("cd -- \"$1\" && shift && exec \"$@\""); pkexecArgs << QStringLiteral("sh"); pkexecArgs << QDir::currentPath(); - pkexecArgs << QCoreApplication::applicationFilePath(); + pkexecArgs << elevatedExecutablePath(); pkexecArgs << appArgs; return pkexecArgs; } From 15a63b24a82ecb3f7f5c683e59d01d2cf9b422a0 Mon Sep 17 00:00:00 2001 From: Amol Paunikar Date: Wed, 22 Jul 2026 16:29:27 +0530 Subject: [PATCH 9/9] Added support of Ubumtu 22+, Fedora 39+ --- scripts/build-release.sh | 3 +++ scripts/dependencies.sh | 1 + src/main.cpp | 39 +++++++++++++++++++++++++++++++++------ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 36e1d5b..9d4883a 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -256,9 +256,12 @@ echo "Installing dependencies" unset QML_SOURCES_PATHS unset QMAKE unset EXTRA_QT_MODULES +unset EXTRA_PLATFORM_PLUGINS export QML_SOURCES_PATHS="$PROJECT_DIR"/src export QMAKE=$(which qmake6) export EXTRA_QT_MODULES="libssl;libcrypto" +# Menu/anti-cheat lockdown uses QT_QPA_PLATFORM=linuxfb on a bare VT. +export EXTRA_PLATFORM_PLUGINS="libqlinuxfb.so" echo "Environment variables set" mkdir -p "${BUILD_DIR}" "${ARTIFACT_DIR}" "${TOOLS_DIR}" diff --git a/scripts/dependencies.sh b/scripts/dependencies.sh index 1ca136b..3b07a33 100755 --- a/scripts/dependencies.sh +++ b/scripts/dependencies.sh @@ -50,6 +50,7 @@ PACKAGES=( libxcb-cursor0 libxcb-xinput0 libjpeg62 + qt6-qpa-plugins wget ) diff --git a/src/main.cpp b/src/main.cpp index 0c8ddc0..c8bd9b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -141,6 +141,29 @@ bool hasArgument(int argc, char *argv[], const QString &value) return false; } +// QApplication is not created yet, so probe known plugin locations (AppImage APPDIR + distro paths). +static bool qtPlatformPluginAvailable(const QString &platformName) +{ + const QString fileName = QStringLiteral("libq") + platformName + QStringLiteral(".so"); + QStringList dirs; + + const QByteArray appDir = qgetenv("APPDIR"); + if (!appDir.isEmpty()) { + dirs << (QString::fromLocal8Bit(appDir) + QStringLiteral("/usr/plugins/platforms")); + } + + dirs << QStringLiteral("/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms") + << QStringLiteral("/usr/lib/qt6/plugins/platforms") + << QStringLiteral("/usr/lib64/qt6/plugins/platforms"); + + for (const QString &dir : dirs) { + if (QFileInfo::exists(dir + QLatin1Char('/') + fileName)) { + return true; + } + } + return false; +} + static void appendPkexecEnvironmentVariable(QStringList &args, const char *name) { const QByteArray value = qgetenv(name); @@ -177,7 +200,7 @@ static QStringList buildPkexecArguments(const QStringList &appArgs, appendPkexecEnvironmentVariable(pkexecArgs, "XDG_RUNTIME_DIR"); appendPkexecEnvironmentVariable(pkexecArgs, "XDG_SESSION_TYPE"); appendPkexecEnvironmentVariable(pkexecArgs, "DBUS_SESSION_BUS_ADDRESS"); - appendPkexecEnvironmentVariable(pkexecArgs, "QT_QPA_PLATFORM"); + // Do not forward QT_QPA_PLATFORM from the parent: lockdown mode chooses linuxfb/xcb itself. // Keep AppImage runtime hints for the elevated re-exec. appendPkexecEnvironmentVariable(pkexecArgs, "APPIMAGE"); appendPkexecEnvironmentVariable(pkexecArgs, "APPDIR"); @@ -292,12 +315,16 @@ void applyEarlyEnvironment(int argc, char *argv[]) if (isExamAntiCheat || isMenuLockdown) { if (isMenuLockdown || (isExamAntiCheat && hasResource)) { - if (!setup_barebones_vt()) { - qCritical() << "Anti-cheat VT setup failed; aborting."; - _exit(1); + // Lockdown prefers a bare VT + linuxfb. AppImages often only ship xcb unless + // EXTRA_PLATFORM_PLUGINS includes libqlinuxfb.so — fall back instead of aborting. + if (qtPlatformPluginAvailable(QStringLiteral("linuxfb")) && setup_barebones_vt()) { + qputenv("QT_QPA_PLATFORM", "linuxfb"); + qputenv("QT_QUICK_BACKEND", "software"); + } else { + qWarning() << "VT/linuxfb lockdown unavailable; continuing with the default Qt platform " + "(xcb). Rebuild the AppImage with EXTRA_PLATFORM_PLUGINS=libqlinuxfb.so " + "for full bare-VT lockdown."; } - qputenv("QT_QPA_PLATFORM", "linuxfb"); - qputenv("QT_QUICK_BACKEND", "software"); } #if SEB_HAS_QTWEBENGINE qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox");