mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-19 22:17:28 +02:00
433 lines
16 KiB
YAML
433 lines
16 KiB
YAML
# Builds the Linux .deb once, repackages it as a Flatpak and as a Snap, and
|
|
# starts each package headless to check that it runs inside its sandbox.
|
|
#
|
|
# The release workflows call it with `release-tag`: it then repackages the
|
|
# amd64 .deb already attached to that release instead of building one, and
|
|
# uploads the Flatpak and the Snap that passed their tests to the release.
|
|
|
|
name: Linux Packages
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- "packaging/**"
|
|
- "snap/**"
|
|
- ".github/workflows/linux-packages.yml"
|
|
- "src-tauri/tauri.conf.json"
|
|
- "src-tauri/Cargo.toml"
|
|
- "src-tauri/donutbrowser.desktop"
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- "packaging/**"
|
|
- "snap/**"
|
|
- ".github/workflows/linux-packages.yml"
|
|
- "src-tauri/tauri.conf.json"
|
|
- "src-tauri/Cargo.toml"
|
|
- "src-tauri/donutbrowser.desktop"
|
|
workflow_call:
|
|
inputs:
|
|
release-tag:
|
|
description: "Release whose amd64 .deb is repackaged; the packages are uploaded to it"
|
|
type: string
|
|
required: true
|
|
workflow_dispatch:
|
|
inputs:
|
|
check-wayfern:
|
|
description: "Also download Wayfern (about 1 GB) and start it inside each sandbox"
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: linux-packages-${{ inputs.release-tag || github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
APP_ID: com.donutbrowser.DonutBrowser
|
|
FLATPAK_MANIFEST: packaging/flatpak/com.donutbrowser.DonutBrowser.yml
|
|
FLATPAK_LINT_EXCEPTIONS: packaging/flatpak/flatpak-builder-lint-exceptions.json
|
|
METAINFO: packaging/linux/com.donutbrowser.DonutBrowser.metainfo.xml
|
|
|
|
jobs:
|
|
deb:
|
|
name: Build .deb
|
|
if: ${{ !inputs.release-tag }}
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 90
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 #v6.1.0
|
|
with:
|
|
run_install: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 #v7.0.0
|
|
with:
|
|
node-version-file: .node-version
|
|
cache: "pnpm"
|
|
|
|
- name: Setup Rust
|
|
uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 #master
|
|
with:
|
|
toolchain: stable
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev pkg-config unzip xdg-utils
|
|
|
|
# `workspaces`: rust-cache v2 has no `workdir` input, and with it the
|
|
# cache would look for Cargo.toml at the repository root and save nothing.
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 #v2.9.2
|
|
with:
|
|
workspaces: "src-tauri -> target"
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
|
|
- name: Install frontend dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# beforeBuildCommand builds the donut-proxy sidecar, downloads the
|
|
# verified Xray-core sidecar and builds the frontend.
|
|
- name: Build the .deb
|
|
env:
|
|
NEXT_PUBLIC_TURNSTILE: ${{ secrets.NEXT_PUBLIC_TURNSTILE }}
|
|
run: |
|
|
BUILD_TAG="nightly-$(git show -s --format=%cs HEAD)-${GITHUB_SHA::7}"
|
|
export BUILD_TAG
|
|
pnpm tauri build --bundles deb
|
|
|
|
- name: Collect the .deb
|
|
run: |
|
|
mkdir -p linux-deb
|
|
cp src-tauri/target/release/bundle/deb/*.deb linux-deb/
|
|
ls -l linux-deb
|
|
test "$(find linux-deb -name '*.deb' | wc -l)" -eq 1
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
with:
|
|
name: donut-deb
|
|
path: linux-deb/*.deb
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
deb-from-release:
|
|
name: Take the .deb from the release
|
|
if: ${{ inputs.release-tag }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Download the amd64 .deb
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ inputs.release-tag }}
|
|
run: |
|
|
mkdir -p linux-deb
|
|
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern 'Donut_*_amd64.deb' --dir linux-deb
|
|
ls -l linux-deb
|
|
test "$(find linux-deb -name '*.deb' | wc -l)" -eq 1
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
with:
|
|
name: donut-deb
|
|
path: linux-deb/*.deb
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
flatpak:
|
|
name: Build Flatpak
|
|
needs: [deb, deb-from-release]
|
|
# Exactly one of the two .deb jobs runs; the other is skipped.
|
|
if: ${{ !cancelled() && (needs.deb.result == 'success' || needs.deb-from-release.result == 'success') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 45
|
|
container:
|
|
# flatpak, flatpak-builder, flatpak-builder-lint and appstreamcli, with
|
|
# org.gnome.Platform//51 and org.gnome.Sdk//51 already installed.
|
|
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-51
|
|
options: --privileged
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
with:
|
|
name: donut-deb
|
|
path: linux-deb
|
|
|
|
- name: Put the .deb where the manifest expects it
|
|
run: mv linux-deb/*.deb packaging/donut.deb
|
|
|
|
- name: Validate the AppStream metadata
|
|
run: appstreamcli validate --no-net --explain "$METAINFO"
|
|
|
|
- name: Lint the manifest
|
|
run: flatpak-builder-lint --exceptions --user-exceptions "$FLATPAK_LINT_EXCEPTIONS" manifest "$FLATPAK_MANIFEST"
|
|
|
|
- name: Restore the flatpak-builder cache
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 #v6.1.0
|
|
with:
|
|
path: .flatpak-builder
|
|
key: flatpak-builder-${{ hashFiles('packaging/flatpak/**') }}
|
|
restore-keys: flatpak-builder-
|
|
|
|
# No --install-deps-from: the image already has the runtime and the SDK,
|
|
# and updating a runtime in place fails on the container's overlay
|
|
# filesystem ("Invalid cross-device link").
|
|
- name: Build the Flatpak
|
|
run: |
|
|
flatpak-builder --repo=repo --disable-rofiles-fuse --force-clean \
|
|
--default-branch=master build-dir "$FLATPAK_MANIFEST"
|
|
|
|
- name: Lint the build
|
|
run: flatpak-builder-lint --exceptions --user-exceptions "$FLATPAK_LINT_EXCEPTIONS" repo repo
|
|
|
|
- name: Create the bundle
|
|
run: |
|
|
flatpak build-bundle --runtime-repo=https://dl.flathub.org/repo/flathub.flatpakrepo \
|
|
repo Donut.flatpak "$APP_ID" master
|
|
ls -l Donut.flatpak
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
with:
|
|
name: donut-flatpak-x86_64
|
|
path: Donut.flatpak
|
|
if-no-files-found: error
|
|
|
|
flatpak-smoke:
|
|
name: Test Flatpak
|
|
needs: flatpak
|
|
# Stated, not implied: without it the job inherits the skip of whichever
|
|
# .deb job did not run, and a release would go out without the Flatpak.
|
|
if: ${{ !cancelled() && needs.flatpak.result == 'success' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
with:
|
|
name: donut-flatpak-x86_64
|
|
path: flatpak-bundle
|
|
|
|
- name: Install Flatpak and the test tools
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y flatpak xvfb imagemagick dbus
|
|
# Ubuntu 24.04 can restrict unprivileged user namespaces, which
|
|
# bubblewrap needs to build the sandbox.
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
|
|
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
|
|
- name: Prepare a user runtime directory
|
|
run: |
|
|
dir="/run/user/$(id -u)"
|
|
sudo install -d -m 700 -o "$(id -u)" -g "$(id -g)" "$dir"
|
|
echo "XDG_RUNTIME_DIR=$dir" >> "$GITHUB_ENV"
|
|
|
|
- name: Install the bundle
|
|
run: |
|
|
flatpak install --user -y --noninteractive flatpak-bundle/Donut.flatpak
|
|
flatpak info --user "$APP_ID"
|
|
|
|
- name: Check that every library resolves inside the sandbox
|
|
run: |
|
|
# shellcheck disable=SC2016 # expanded by the shell inside the sandbox
|
|
dbus-run-session -- flatpak run --command=sh "$APP_ID" -c '
|
|
for f in /app/bin/donutbrowser /app/bin/donut-proxy /app/lib/libayatana-appindicator3.so.1; do
|
|
echo "== $f"
|
|
/lib64/ld-linux-x86-64.so.2 --list "$f"
|
|
done' 2>&1 | tee libraries.txt
|
|
if grep "not found" libraries.txt || ! grep -q "=>" libraries.txt; then
|
|
echo "::error::A library does not resolve inside the Flatpak sandbox."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Start the app on a virtual display
|
|
run: bash packaging/linux/smoke-test.sh smoke 45 flatpak run "$APP_ID"
|
|
|
|
- name: Stop the app and collect its logs
|
|
if: always()
|
|
run: |
|
|
flatpak kill "$APP_ID" 2>/dev/null || true
|
|
mkdir -p smoke
|
|
cp -r "$HOME/.var/app/$APP_ID/data/com.donutbrowser/logs" smoke/app-logs 2>/dev/null || true
|
|
cp libraries.txt smoke/ 2>/dev/null || true
|
|
|
|
- name: Download Wayfern
|
|
if: ${{ inputs.check-wayfern }}
|
|
run: |
|
|
url="$(curl -fsSL https://donutbrowser.com/wayfern.json | jq -r '.downloads["linux-x64"]')"
|
|
curl -fsSL --retry 5 -o "$RUNNER_TEMP/wayfern.tar.xz" "$url"
|
|
|
|
- name: Start Wayfern inside the sandbox
|
|
if: ${{ inputs.check-wayfern }}
|
|
run: bash packaging/linux/wayfern-check.sh flatpak "$RUNNER_TEMP/wayfern.tar.xz" smoke/wayfern
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
if: always()
|
|
with:
|
|
name: flatpak-smoke-test
|
|
path: smoke
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|
|
|
|
snap:
|
|
name: Build and test Snap
|
|
needs: [deb, deb-from-release]
|
|
if: ${{ !cancelled() && (needs.deb.result == 'success' || needs.deb-from-release.result == 'success') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
with:
|
|
name: donut-deb
|
|
path: linux-deb
|
|
|
|
- name: Put the .deb where snapcraft.yaml expects it
|
|
run: mv linux-deb/*.deb packaging/donut.deb
|
|
|
|
- name: Install Snapcraft
|
|
run: sudo snap install snapcraft --classic
|
|
|
|
# The runner is Ubuntu 24.04, the same system as the core24 base, so
|
|
# the snap is built on the host rather than in an LXD instance.
|
|
- name: Build the snap
|
|
run: |
|
|
sudo snapcraft pack --destructive-mode --verbosity=brief
|
|
snap_file="$(ls donutbrowser_*.snap)"
|
|
echo "SNAP_FILE=$snap_file" >> "$GITHUB_ENV"
|
|
ls -l "$snap_file"
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
with:
|
|
name: donut-snap-amd64
|
|
path: ${{ env.SNAP_FILE }}
|
|
if-no-files-found: error
|
|
|
|
# `snap run` moves the app into its own systemd scope and asks the
|
|
# user's systemd for it. The runner has no user session, so the app died
|
|
# with "... is not a snap cgroup". Lingering starts systemd --user and,
|
|
# with dbus-user-session, its bus at /run/user/<uid>/bus.
|
|
- name: Start a user session
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y xvfb imagemagick dbus dbus-user-session
|
|
sudo loginctl enable-linger "$(id -un)"
|
|
uid="$(id -u)"
|
|
for _ in $(seq 1 60); do
|
|
[ -S "/run/user/$uid/bus" ] && break
|
|
sleep 1
|
|
done
|
|
test -S "/run/user/$uid/bus"
|
|
echo "XDG_RUNTIME_DIR=/run/user/$uid" >> "$GITHUB_ENV"
|
|
echo "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus" >> "$GITHUB_ENV"
|
|
|
|
- name: Install the snap
|
|
run: |
|
|
sudo snap install gnome-46-2404 mesa-2404 gtk-common-themes
|
|
sudo snap install --dangerous "$SNAP_FILE"
|
|
snap connections donutbrowser
|
|
|
|
- name: Check that every library resolves inside the snap
|
|
run: |
|
|
# shellcheck disable=SC2016 # expanded by the shell inside the sandbox
|
|
snap run --shell donutbrowser -c '
|
|
for f in "$SNAP/usr/bin/donutbrowser" "$SNAP/usr/bin/donut-proxy"; do
|
|
echo "== $f"
|
|
/lib64/ld-linux-x86-64.so.2 --list "$f"
|
|
done' 2>&1 | tee libraries.txt
|
|
if grep "not found" libraries.txt || ! grep -q "=>" libraries.txt; then
|
|
echo "::error::A library does not resolve inside the snap."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Start the app on a virtual display
|
|
run: bash packaging/linux/smoke-test.sh smoke 45 snap run donutbrowser
|
|
|
|
- name: Stop the app and collect its logs
|
|
if: always()
|
|
run: |
|
|
pkill -f /snap/donutbrowser/ 2>/dev/null || true
|
|
mkdir -p smoke
|
|
cp -r "$HOME/snap/donutbrowser/current/.local/share/com.donutbrowser/logs" smoke/app-logs 2>/dev/null || true
|
|
cp libraries.txt smoke/ 2>/dev/null || true
|
|
sudo journalctl --no-pager -k --grep 'apparmor="DENIED"' 2>/dev/null | grep donutbrowser > smoke/apparmor-denials.txt || true
|
|
echo "AppArmor denials: $(wc -l < smoke/apparmor-denials.txt)"
|
|
|
|
- name: Download Wayfern
|
|
if: ${{ inputs.check-wayfern }}
|
|
run: |
|
|
url="$(curl -fsSL https://donutbrowser.com/wayfern.json | jq -r '.downloads["linux-x64"]')"
|
|
curl -fsSL --retry 5 -o "$RUNNER_TEMP/wayfern.tar.xz" "$url"
|
|
|
|
- name: Start Wayfern inside the snap
|
|
if: ${{ inputs.check-wayfern }}
|
|
run: bash packaging/linux/wayfern-check.sh snap "$RUNNER_TEMP/wayfern.tar.xz" smoke/wayfern
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
|
if: always()
|
|
with:
|
|
name: snap-smoke-test
|
|
path: smoke
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|
|
|
|
publish:
|
|
name: Attach the packages to the release
|
|
needs: [flatpak-smoke, snap]
|
|
# Each package goes up only when its own test passed, so a Snap failure
|
|
# does not hold back a working Flatpak.
|
|
if: ${{ !cancelled() && inputs.release-tag && (needs.flatpak-smoke.result == 'success' || needs.snap.result == 'success') }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
with:
|
|
name: donut-deb
|
|
path: linux-deb
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
if: ${{ needs.flatpak-smoke.result == 'success' }}
|
|
with:
|
|
name: donut-flatpak-x86_64
|
|
path: flatpak-bundle
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
|
|
if: ${{ needs.snap.result == 'success' }}
|
|
with:
|
|
name: donut-snap-amd64
|
|
path: snap-package
|
|
|
|
# Named like the other assets (Donut_<version>_<arch>.<ext>), so the
|
|
# nightly job renames them the same way it renames the rest.
|
|
- name: Upload to the release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ inputs.release-tag }}
|
|
run: |
|
|
deb="$(find linux-deb -name 'Donut_*_amd64.deb' | head -n 1)"
|
|
version="$(basename "$deb" | sed -E 's/^Donut_(.+)_amd64\.deb$/\1/')"
|
|
test -n "$version"
|
|
files=()
|
|
if [ -f flatpak-bundle/Donut.flatpak ]; then
|
|
cp flatpak-bundle/Donut.flatpak "Donut_${version}_x86_64.flatpak"
|
|
files+=("Donut_${version}_x86_64.flatpak")
|
|
fi
|
|
snap="$(find snap-package -name '*.snap' 2>/dev/null | head -n 1)"
|
|
if [ -n "$snap" ]; then
|
|
cp "$snap" "Donut_${version}_amd64.snap"
|
|
files+=("Donut_${version}_amd64.snap")
|
|
fi
|
|
ls -l "${files[@]}"
|
|
gh release upload "$TAG" "${files[@]}" --clobber --repo "$GITHUB_REPOSITORY"
|