Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions .github/workflows/cmake-linux-amd64-appimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,9 @@ jobs:
- os: ubuntu-22.04
qt-ver: 5
qt-pkg: qtbase5-dev qttools5-dev
use-pkexec-launcher: OFF
- os: ubuntu-22.04
qt-ver: 5
qt-pkg: qtbase5-dev qttools5-dev
use-pkexec-launcher: ON
- os: ubuntu-24.04
qt-ver: 6
qt-pkg: qt6-base-dev qt6-tools-dev
use-pkexec-launcher: OFF
- os: ubuntu-24.04
qt-ver: 6
qt-pkg: qt6-base-dev qt6-tools-dev
use-pkexec-launcher: ON
runs-on: ${{matrix.os}}

steps:
Expand All @@ -56,7 +46,7 @@ jobs:
run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-DCMAKE_INSTALL_PREFIX=/usr -DENABLE_PACKAGING=ON \
-DUSE_PKEXEC_LAUNCHER=${{matrix.use-pkexec-launcher}} \
-DUSE_PKEXEC_LAUNCHER=ON \
-DBUILD_CLI_UTILITY=ON

- name: Build
Expand Down Expand Up @@ -89,17 +79,15 @@ jobs:
uses: actions/upload-artifact@v4
with:
# Artifact name
name: QEFI Entry Manager x86_64 AppImage Qt${{matrix.qt-ver}} pkexec-launcher-${{matrix.use-pkexec-launcher}}
name: QEFI Entry Manager x86_64 AppImage Qt${{matrix.qt-ver}}
# A file, directory or wildcard pattern that describes what to upload
path: ${{github.workspace}}/build/EFI_Entry_Manager-x86_64-Qt${{matrix.qt-ver}}.AppImage

- name: Packaging Debian
if: ${{ matrix.use-pkexec-launcher == 'ON' }}
working-directory: ${{github.workspace}}/build
run: cpack -G DEB

- name: Upload a Build Artifact
if: ${{ matrix.use-pkexec-launcher == 'ON' }}
uses: actions/upload-artifact@v4
with:
# Artifact name
Expand Down
218 changes: 218 additions & 0 deletions .github/workflows/uefi-linux-vm-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: UEFI Linux VM smoke tests

on:
push:
branches: [master]
pull_request:
branches: [master]

env:
BUILD_TYPE: Release

jobs:
build-appimage:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: true

- name: Install Qt and build dependencies
run: |
sudo apt update -y
sudo apt install -y qt6-base-dev qt6-tools-dev fuse

- name: Configure CMake
run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-DCMAKE_INSTALL_PREFIX=/usr -DENABLE_PACKAGING=ON \
-DUSE_PKEXEC_LAUNCHER=ON \
-DBUILD_CLI_UTILITY=ON

- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

- name: Package AppImage
working-directory: ${{github.workspace}}/build
run: |
export QMAKE=/usr/lib/qt6/bin/qmake
make install DESTDIR=AppDir
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy*.AppImage
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage
mv EFI_Entry_Manager-x86_64.AppImage EFI_Entry_Manager-x86_64-Qt6.AppImage

- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: qefi-appimage-qt6
path: ${{github.workspace}}/build/EFI_Entry_Manager-x86_64-Qt6.AppImage

uefi-vm-test:
needs: build-appimage
runs-on: ubuntu-24.04
strategy:
matrix:
include:
- distro: fedora
image_url: https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-UEFI-UKI-42-1.1.x86_64.qcow2
- distro: arch
image_url: https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2
steps:
- name: Install QEMU and cloud tooling
run: |
sudo apt update -y
sudo apt install -y qemu-system-x86 qemu-utils ovmf cloud-image-utils genisoimage

- name: Download AppImage artifact
uses: actions/download-artifact@v4
with:
name: qefi-appimage-qt6
path: artifacts

- name: Boot UEFI VM and smoke test AppImage
env:
DISTRO: ${{matrix.distro}}
IMAGE_URL: ${{matrix.image_url}}
run: |
set -euo pipefail
APPIMAGE_PATH="${GITHUB_WORKSPACE}/artifacts/EFI_Entry_Manager-x86_64-Qt6.AppImage"
chmod +x "${APPIMAGE_PATH}"
VM_DIR="${RUNNER_TEMP}/uefi-${DISTRO}"
mkdir -p "${VM_DIR}"
curl -fsSL "${IMAGE_URL}" -o "${VM_DIR}/disk.qcow2"
qemu-img resize "${VM_DIR}/disk.qcow2" 10G

ssh-keygen -t ed25519 -N "" -f "${VM_DIR}/id_ed25519"
PUB_KEY="$(cat "${VM_DIR}/id_ed25519.pub")"

cat > "${VM_DIR}/user-data" <<EOF
#cloud-config
users:
- name: tester
groups: [wheel]
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
shell: /bin/bash
ssh_authorized_keys:
- ${PUB_KEY}
runcmd:
- [ sh, -c, "echo ready > /var/tmp/ci-ready" ]
EOF

cat > "${VM_DIR}/meta-data" <<EOF
instance-id: uefi-${DISTRO}
local-hostname: uefi-${DISTRO}
EOF

cloud-localds "${VM_DIR}/seed.iso" "${VM_DIR}/user-data" "${VM_DIR}/meta-data"

OVMF_CODE=""
OVMF_VARS=""
for base in /usr/share/OVMF /usr/share/edk2/ovmf /usr/share/edk2/ovmf/x64; do
if [ -f "${base}/OVMF_CODE_4M.fd" ] && [ -f "${base}/OVMF_VARS_4M.fd" ]; then
OVMF_CODE="${base}/OVMF_CODE_4M.fd"
OVMF_VARS="${base}/OVMF_VARS_4M.fd"
break
fi
if [ -f "${base}/OVMF_CODE.fd" ] && [ -f "${base}/OVMF_VARS.fd" ]; then
OVMF_CODE="${base}/OVMF_CODE.fd"
OVMF_VARS="${base}/OVMF_VARS.fd"
break
fi
done
if [ -z "${OVMF_CODE}" ] || [ -z "${OVMF_VARS}" ]; then
echo "Unable to locate OVMF firmware files"
exit 1
fi
cp "${OVMF_VARS}" "${VM_DIR}/OVMF_VARS.fd"

ACCEL="tcg"
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
ACCEL="kvm"
fi

cleanup() {
if [ -f "${VM_DIR}/qemu.pid" ]; then
kill "$(cat "${VM_DIR}/qemu.pid")" || true
fi
}
trap cleanup EXIT

qemu-system-x86_64 \
-machine accel=${ACCEL} \
-m 4096 -smp 2 \
-drive if=pflash,format=raw,readonly=on,file="${OVMF_CODE}" \
-drive if=pflash,format=raw,file="${VM_DIR}/OVMF_VARS.fd" \
-drive file="${VM_DIR}/disk.qcow2",if=virtio,format=qcow2 \
-drive file="${VM_DIR}/seed.iso",if=virtio,format=raw \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-display none \
-serial file:"${VM_DIR}/serial.log" \
-daemonize \
-pidfile "${VM_DIR}/qemu.pid"

ready=0
for i in $(seq 1 60); do
if ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=5 -i "${VM_DIR}/id_ed25519" -p 2222 \
tester@127.0.0.1 'test -f /var/tmp/ci-ready'; then
ready=1
break
fi
sleep 5
done
if [ "${ready}" -ne 1 ]; then
echo "Timed out waiting for SSH on the UEFI VM"
exit 1
fi

scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-i "${VM_DIR}/id_ed25519" -P 2222 \
"${APPIMAGE_PATH}" tester@127.0.0.1:/home/tester/QEFI.AppImage

ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-i "${VM_DIR}/id_ed25519" -p 2222 tester@127.0.0.1 <<'EOSSH'
set -euo pipefail
if [ ! -d /sys/firmware/efi ]; then
echo "Missing /sys/firmware/efi, not booted in UEFI mode"
exit 1
fi

if command -v dnf >/dev/null 2>&1; then
sudo dnf install -y polkit fuse fuse3 mesa-libEGL mesa-dri-drivers libglvnd libglvnd-opengl mesa-libGL fontconfig freetype libX11 libXext libXrender libXfixes libXcursor libXi libXrandr harfbuzz xorg-x11-server-Xvfb
elif command -v pacman >/dev/null 2>&1; then
sudo pacman -Sy --noconfirm polkit fuse2 mesa libglvnd fontconfig freetype2 libx11 libxext libxrender libxfixes libxcursor libxi libxrandr harfbuzz xorg-server-xvfb
fi

sudo tee /etc/polkit-1/rules.d/49-ci-pkexec.rules >/dev/null <<'POLKIT'
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.policykit.exec" &&
subject.user == "tester") {
return polkit.Result.YES;
}
});
POLKIT
sudo chmod 0644 /etc/polkit-1/rules.d/49-ci-pkexec.rules

chmod +x /home/tester/QEFI.AppImage
export QT_QPA_PLATFORM=xcb
export APPIMAGE_EXTRACT_AND_RUN=1

set +e
timeout 10s xvfb-run -a /home/tester/QEFI.AppImage > /tmp/qefi.log 2>&1
status=$?
set -e

if [ "${status}" -ne 0 ] && [ "${status}" -ne 124 ]; then
echo "AppImage exited with status ${status}"
cat /tmp/qefi.log
exit 1
fi

if grep -q "No such file or directory" /tmp/qefi.log; then
cat /tmp/qefi.log
exit 1
fi
EOSSH
56 changes: 46 additions & 10 deletions qefientrymanager-launcher
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
#!/usr/bin/env bash

env_array=(
"XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP}"
"QT_QPA_PLATFORM=${QT_QPA_PLATFORM}"
"QT_QPA_PLATFORMTHEME=${QT_QPA_PLATFORMTHEME}"
"QT_STYLE_OVERRIDE=${QT_STYLE_OVERRIDE}"
)
# Resolve the bundled binary path first; avoid relying on PATH after pkexec
launcher_path="$(readlink -f "${BASH_SOURCE[0]}")"
launcher_dir="$(dirname "${launcher_path}")"
target="${launcher_dir}/QEFIEntryManager"
if [[ ! -x "${target}" ]]; then
target="QEFIEntryManager"
fi

env_array=()
append_env() {
local key="$1"
local value="${!key-}"
if [[ -n "${value}" ]]; then
env_array+=("${key}=${value}")
fi
}

if [[ -z "${WAYLAND_DISPLAY}" ]]; then
env_array+=("DISPLAY=${DISPLAY}" "XAUTHORITY=${XAUTHORITY}")
# Preserve the minimum GUI/session vars that pkexec strips
append_env XDG_CURRENT_DESKTOP
append_env XDG_SESSION_TYPE
append_env QT_QPA_PLATFORM
append_env QT_QPA_PLATFORMTHEME
append_env QT_STYLE_OVERRIDE
append_env DBUS_SESSION_BUS_ADDRESS

if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
# Wayland: pass both runtime dir and display socket
append_env XDG_RUNTIME_DIR
append_env WAYLAND_DISPLAY
else
env_array+=("WAYLAND_DISPLAY=${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}")
# X11: pass display and auth cookie
append_env DISPLAY
append_env XAUTHORITY
fi

if [[ "${EUID}" -eq 0 ]]; then
# If already root, run directly without re-exec
exec "${target}" "$@"
fi

if [[ -n "${APPIMAGE:-}" && -x "${APPIMAGE}" ]]; then
# AppImages mount their SquashFS via FUSE under the calling user.
# After pkexec, root often cannot read that user FUSE mount unless
# allow_root/allow_other is set, so we re-exec the AppImage as root
# and force extract+run to bypass the mount.
exec pkexec env "${env_array[@]}" APPIMAGE_EXTRACT_AND_RUN=1 "${APPIMAGE}" "$@"
fi

exec pkexec env "${env_array[@]}" QEFIEntryManager "$@"
# Fallback for non-AppImage installs
exec pkexec env "${env_array[@]}" "${target}" "$@"