From f6753c66cc4ebc5664dc07b49ba0b8381edd840d Mon Sep 17 00:00:00 2001 From: Mike Mueller Date: Tue, 5 May 2026 13:48:06 +0200 Subject: [PATCH] ci: add Linux runner provisioning + build-linux workflow --- .gitea/workflows/build-linux.yml | 172 ++++++++++++++++++++++ ci/runners/linux/provision.sh | 242 +++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 .gitea/workflows/build-linux.yml create mode 100755 ci/runners/linux/provision.sh diff --git a/.gitea/workflows/build-linux.yml b/.gitea/workflows/build-linux.yml new file mode 100644 index 000000000..419d97e85 --- /dev/null +++ b/.gitea/workflows/build-linux.yml @@ -0,0 +1,172 @@ +name: build-linux + +on: + push: + branches: [pro-features] + workflow_dispatch: + inputs: + version_suffix: + description: "Version suffix (e.g. 'cst', 'beta1'). Empty = vanilla." + type: string + default: "cst" + +env: + RUST_VERSION: "1.75" + LLVM_VERSION: "15.0.6" + # Where provision.sh installs LLVM (binary tarball from llvm.org). Same exact + # version as the Windows runner uses, distro-portable. + LLVM_HOME: '/opt/llvm-15.0.6' + FLUTTER_VERSION: "3.24.5" + VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b" + CARGO_EXPAND_VERSION: "1.0.95" + FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" + VERSION_BASE: "1.4.6" + VERSION_SUFFIX: ${{ inputs.version_suffix || 'cst' }} + +jobs: + build-x64: + name: build-linux-x64 + # Distro-agnostic: any Linux runner provisioned by ci/runners/linux/provision.sh + # carries `self-hosted`, `Linux`, `X64`. To target a specific distro + # (e.g. force Debian 13), append its label: [self-hosted, Linux, X64, debian-13]. + runs-on: [self-hosted, Linux, X64] + timeout-minutes: 240 + env: + VCPKG_ROOT: /opt/vcpkg + VCPKG_BINARY_SOURCES: "clear;files,/var/cache/vcpkg,readwrite" + LIBCLANG_PATH: /opt/llvm-15.0.6/lib + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Verify host toolchain + shell: bash + run: | + required=(node git bash python3 rustc cargo rustup clang flutter cmake ninja nasm pkg-config dpkg-deb) + missing=() + for t in "${required[@]}"; do + if command -v "$t" >/dev/null 2>&1; then + printf '%-20s %s\n' "$t" "$(command -v "$t")" + else + missing+=("$t") + printf '%-20s MISSING\n' "$t" + fi + done + if [[ ${#missing[@]} -gt 0 ]]; then + echo "Missing tools: ${missing[*]}. Re-run provision.sh on the runner host." + exit 1 + fi + [[ -d "$VCPKG_ROOT" && -x "$VCPKG_ROOT/vcpkg" ]] || { + echo "VCPKG_ROOT=$VCPKG_ROOT invalid"; exit 1; } + [[ -f "$LIBCLANG_PATH/libclang.so" ]] || { + echo "libclang.so not found at $LIBCLANG_PATH"; exit 1; } + + - name: Compute version strings + shell: bash + run: | + base="${VERSION_BASE}" + suffix="${VERSION_SUFFIX}" + [[ "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "VERSION_BASE '$base' must be major.minor.patch"; exit 1; } + if [[ -n "$suffix" ]]; then display="${base}-${suffix}"; else display="${base}"; fi + echo "Base : $base" + echo "Suffix : $suffix" + echo "Display : $display" + echo "VERSION_DISPLAY=$display" >> "$GITHUB_ENV" + + - name: Patch Cargo.toml with display version + shell: bash + run: | + sed -i -E "0,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/}" Cargo.toml + grep '^version' Cargo.toml | head -1 + + - name: Ensure Rust toolchain configured + shell: bash + run: | + # provision.sh installs Rust machine-wide at /opt/cargo + /opt/rustup, + # so this is normally a no-op verification. Kept as a guardrail. + rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt + rustup default "$RUST_VERSION" + rustup target add x86_64-unknown-linux-gnu + rustc --version + cargo --version + + - name: Install flutter_rust_bridge codegen tools + shell: bash + run: | + # Pin install destination so binaries land in a deterministic path + # regardless of CARGO_HOME. + tools=/opt/cargo-tools + mkdir -p "$tools/bin" + cargo install --root "$tools" cargo-expand --version "$CARGO_EXPAND_VERSION" --locked + cargo install --root "$tools" flutter_rust_bridge_codegen --version "$FLUTTER_RUST_BRIDGE_VERSION" --features uuid --locked + ls -la "$tools/bin" + [[ -x "$tools/bin/flutter_rust_bridge_codegen" ]] || { echo "missing fr_bridge_codegen"; exit 1; } + echo "$tools/bin" >> "$GITHUB_PATH" + + - name: Generate Rust <-> Dart bridge + shell: bash + run: | + set -e + export PATH="/opt/cargo-tools/bin:$PATH" + command -v flutter_rust_bridge_codegen + (cd flutter && flutter pub get) + flutter_rust_bridge_codegen \ + --llvm-path "$LLVM_HOME" \ + --rust-input ./src/flutter_ffi.rs \ + --dart-output ./flutter/lib/generated_bridge.dart \ + --c-output ./flutter/macos/Runner/bridge_generated.h + cp ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h + + - name: vcpkg install dependencies (x64-linux) + shell: bash + env: + VCPKG_DEFAULT_HOST_TRIPLET: x64-linux + run: | + mkdir -p /var/cache/vcpkg + if ! "$VCPKG_ROOT/vcpkg" install \ + --triplet x64-linux \ + --x-install-root="$VCPKG_ROOT/installed"; then + find "$VCPKG_ROOT/" -name '*.log' -exec sh -c 'echo "===== {} ====="; cat "{}"' \; + exit 1 + fi + + - name: Build RustDesk (.deb) + shell: bash + run: | + set -e + # build.py on Linux (no pacman/yum/zypper detected) goes to + # build_flutter_deb() which does: + # - cargo build --features --lib --release + # - flutter build linux --release + # - assembles tmpdeb/ and runs dpkg-deb -b + # Output: ./rustdesk-.deb in the repo root. + python3 build.py --flutter --hwcodec --unix-file-copy-paste + + mkdir -p ./SignOutput + # build.py names the .deb after Cargo.toml's version which we patched + # above, so the file should already carry $VERSION_DISPLAY. + mv "./rustdesk-${VERSION_DISPLAY}.deb" "./SignOutput/rustdesk-${VERSION_DISPLAY}-amd64.deb" + + - name: Report signing status of build artifacts + shell: bash + run: | + # .deb files are typically signed with debsign or via the apt repo + # signing pipeline, not the .deb itself. Just list contents for now. + for f in ./SignOutput/*.deb; do + [[ -f "$f" ]] || continue + size=$(stat -c%s "$f") + printf '[UNSIGNED] %s (%d bytes)\n' "$(basename "$f")" "$size" + done + # Gitea/GHA-style annotation so it surfaces in the run summary. + echo "::warning title=Unsigned .deb::Wire up debsigs / repo signing before distributing." + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: rustdesk-linux-x64-${{ github.sha }} + path: SignOutput/rustdesk-*.deb + if-no-files-found: warn + retention-days: 14 diff --git a/ci/runners/linux/provision.sh b/ci/runners/linux/provision.sh new file mode 100755 index 000000000..606c90b70 --- /dev/null +++ b/ci/runners/linux/provision.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# Provisions an Ubuntu 22.04 LTS or Debian 13 (Trixie) host as a Gitea Actions +# runner for RustDesk desktop (.deb) builds. Idempotent: safe to re-run. +# +# Versions are pinned to .gitea/workflows/build-linux.yml. Bump them there and +# here together. +# +# Build host vs. user host: the resulting .deb links against the host's glibc. +# Build on the OLDEST distro your users have, otherwise the .deb won't install +# on older systems. +# - Ubuntu 22.04 build -> runs on Ubuntu 22.04+, Debian 12+, derivatives +# - Debian 13 build -> runs on Debian 13+, Ubuntu 24.04+ only +# +# Usage: +# sudo ./provision.sh \ +# --gitea-url https://gitea.example.com \ +# --runner-token +# +# All toolchains land in /opt and are readable by the gitea-runner user. +# Service is installed as a systemd unit running as that user. + +set -euo pipefail + +# ---- pinned versions (mirror .gitea/workflows/build-linux.yml env block) ---- +RUST_VERSION="1.75.0" +FLUTTER_VERSION="3.24.5" +LLVM_VERSION="15.0.6" +VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b" +RUNNER_VERSION="0.2.11" + +# ---- defaults ---- +RUNNER_NAME="$(hostname)-rustdesk" +RUNNER_LABELS="" # auto-derived from /etc/os-release if empty +SERVICE_USER="gitea-runner" +GITEA_URL="" +RUNNER_TOKEN="" + +# ---- arg parse ---- +while [[ $# -gt 0 ]]; do + case "$1" in + --gitea-url) GITEA_URL="$2"; shift 2 ;; + --runner-token) RUNNER_TOKEN="$2"; shift 2 ;; + --runner-name) RUNNER_NAME="$2"; shift 2 ;; + --runner-labels) RUNNER_LABELS="$2"; shift 2 ;; + --service-user) SERVICE_USER="$2"; shift 2 ;; + -h|--help) + sed -n '2,18p' "$0" + exit 0 ;; + *) echo "Unknown arg: $1" >&2; exit 2 ;; + esac +done + +[[ "$EUID" -eq 0 ]] || { echo "Run as root (use sudo)." >&2; exit 1; } +[[ -n "$GITEA_URL" && -n "$RUNNER_TOKEN" ]] \ + || { echo "Missing --gitea-url or --runner-token" >&2; exit 2; } + +. /etc/os-release +case "${ID}-${VERSION_ID:-}" in + ubuntu-22.04) DISTRO_LABEL="ubuntu-22.04" ;; + debian-13|debian-trixie) DISTRO_LABEL="debian-13" ;; + *) + echo "WARNING: tested only on Ubuntu 22.04 and Debian 13. You're on $PRETTY_NAME." + echo "Package names may differ; build outputs may not run on user systems." + DISTRO_LABEL="${ID}-${VERSION_ID:-unknown}" + sleep 3 ;; +esac + +# If --runner-labels wasn't passed, derive a sensible default that includes the +# detected distro so workflows can target a specific build host when needed. +if [[ -z "$RUNNER_LABELS" ]]; then + RUNNER_LABELS="${DISTRO_LABEL},self-hosted,X64,Linux" +fi + +log() { printf '\n==> %s\n' "$*"; } + +# ---- 1. apt packages ---- +log "Installing apt packages" +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y --no-install-recommends \ + build-essential clang gcc g++ cmake ninja-build pkg-config nasm yasm \ + libgtk-3-dev libayatana-appindicator3-dev \ + libasound2-dev libpulse-dev libpam0g-dev libssl-dev \ + libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + libva-dev libxdo-dev libxfixes-dev \ + libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ + git curl wget zip unzip tar xz-utils ca-certificates \ + python3 python3-pip \ + rpm tree dpkg-dev sudo + +# Node.js (act_runner spawns node for JS actions like actions/checkout) +if ! command -v node >/dev/null; then + log "Installing Node.js LTS" + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y --no-install-recommends nodejs +fi + +# ---- 2. LLVM (binary tarball; libclang-15-dev was dropped from Debian 13) ---- +LLVM_DIR="/opt/llvm-${LLVM_VERSION}" +if [[ ! -x "$LLVM_DIR/bin/clang" ]]; then + log "Installing LLVM $LLVM_VERSION (binary tarball)" + arch="$(uname -m)" + case "$arch" in + x86_64) llvm_arch="x86_64-linux-gnu-ubuntu-18.04" ;; + aarch64) llvm_arch="aarch64-linux-gnu" ;; + *) echo "Unsupported arch for LLVM tarball: $arch" >&2; exit 1 ;; + esac + tmp="$(mktemp -d)" + curl -fsSL -o "$tmp/llvm.tar.xz" \ + "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${llvm_arch}.tar.xz" + mkdir -p "$LLVM_DIR" + tar --strip-components=1 -xJf "$tmp/llvm.tar.xz" -C "$LLVM_DIR" + rm -rf "$tmp" +fi + +# ---- 3. dedicated runner user ---- +if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then + log "Creating user $SERVICE_USER" + useradd --system --create-home --shell /bin/bash "$SERVICE_USER" +fi +RUNNER_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)" + +# ---- 4. Rust (machine-wide) ---- +export RUSTUP_HOME=/opt/rustup +export CARGO_HOME=/opt/cargo +mkdir -p "$RUSTUP_HOME" "$CARGO_HOME" + +if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then + log "Installing rustup at $RUSTUP_HOME / $CARGO_HOME" + curl -fsSL https://sh.rustup.rs | RUSTUP_HOME="$RUSTUP_HOME" CARGO_HOME="$CARGO_HOME" \ + sh -s -- -y --default-toolchain none --profile minimal --no-modify-path +fi +"$CARGO_HOME/bin/rustup" toolchain install "$RUST_VERSION" --profile minimal --component rustfmt +"$CARGO_HOME/bin/rustup" target add --toolchain "$RUST_VERSION" x86_64-unknown-linux-gnu +"$CARGO_HOME/bin/rustup" default "$RUST_VERSION" + +# ---- 5. Flutter ---- +FLUTTER_DIR=/opt/flutter +if [[ ! -x "$FLUTTER_DIR/bin/flutter" ]]; then + log "Installing Flutter $FLUTTER_VERSION" + tmp="$(mktemp -d)" + curl -fsSL -o "$tmp/flutter.tar.xz" \ + "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" + tar -xJf "$tmp/flutter.tar.xz" -C /opt + rm -rf "$tmp" +fi +"$FLUTTER_DIR/bin/flutter" config --no-analytics >/dev/null +"$FLUTTER_DIR/bin/flutter" precache --linux >/dev/null + +# ---- 6. vcpkg ---- +VCPKG_DIR=/opt/vcpkg +if [[ ! -d "$VCPKG_DIR/.git" ]]; then + log "Cloning vcpkg" + git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR" +fi +git -C "$VCPKG_DIR" fetch --tags origin +git -C "$VCPKG_DIR" -c advice.detachedHead=false checkout "$VCPKG_COMMIT" +[[ -x "$VCPKG_DIR/vcpkg" ]] || "$VCPKG_DIR/bootstrap-vcpkg.sh" -disableMetrics + +# vcpkg binary cache (file-backed -- same scheme as build-windows.yml) +mkdir -p /var/cache/vcpkg +chown -R "$SERVICE_USER:$SERVICE_USER" /var/cache/vcpkg + +# ---- 7. Permissions ---- +log "Setting up permissions for $SERVICE_USER" +chown -R "$SERVICE_USER:$SERVICE_USER" "$CARGO_HOME" +# rustup state needs to be writable too -- toolchain installs touch it. +chown -R "$SERVICE_USER:$SERVICE_USER" "$RUSTUP_HOME" +# Flutter SDK: r/x is enough for builds, but `flutter pub get` writes to its +# own cache subdir so we make it writable as well. +chown -R "$SERVICE_USER:$SERVICE_USER" "$FLUTTER_DIR" +# vcpkg: builds write under installed/, buildtrees/, etc. +chown -R "$SERVICE_USER:$SERVICE_USER" "$VCPKG_DIR" +# LLVM: read+execute is enough; we never write here at build time. +chown -R "$SERVICE_USER:$SERVICE_USER" "$LLVM_DIR" + +# git "dubious ownership": same fix as Windows. Trust system-wide. +git config --system --add safe.directory '*' || true + +# ---- 8. act_runner ---- +RUNNER_DIR=/var/lib/gitea-runner +mkdir -p "$RUNNER_DIR" +chown -R "$SERVICE_USER:$SERVICE_USER" "$RUNNER_DIR" + +if [[ ! -x "$RUNNER_DIR/act_runner" ]]; then + log "Downloading act_runner $RUNNER_VERSION" + curl -fsSL -o "$RUNNER_DIR/act_runner" \ + "https://gitea.com/gitea/act_runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-linux-amd64" + chmod +x "$RUNNER_DIR/act_runner" + chown "$SERVICE_USER:$SERVICE_USER" "$RUNNER_DIR/act_runner" +fi + +if [[ ! -f "$RUNNER_DIR/.runner" ]]; then + log "Registering runner with $GITEA_URL" + sudo -u "$SERVICE_USER" -H bash -c " + cd '$RUNNER_DIR' && \ + ./act_runner register --no-interactive \ + --instance '$GITEA_URL' \ + --token '$RUNNER_TOKEN' \ + --name '$RUNNER_NAME' \ + --labels '$RUNNER_LABELS' + " +fi + +# ---- 9. systemd service ---- +log "Installing systemd unit" +cat > /etc/systemd/system/gitea-act-runner.service < Site Admin > Actions > Runners"