Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f0d0fff2 |
@@ -0,0 +1,250 @@
|
|||||||
|
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
|
||||||
|
# Pinned to ubuntu-22.04 because flutter_rust_bridge_codegen 1.80.1 emits
|
||||||
|
# broken Dart on Debian 13 (matches upstream rustdesk's bridge.yml host).
|
||||||
|
# provision.sh tags Ubuntu 22.04 hosts with the `ubuntu-22.04` label.
|
||||||
|
runs-on: [self-hosted, Linux, X64, ubuntu-22.04]
|
||||||
|
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 (with Flutter 3.22.3)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# flutter_rust_bridge_codegen 1.80.1 + freezed produces broken Dart
|
||||||
|
# output (wrong FFI types, unprefixed Int/Pointer in part files) when
|
||||||
|
# run under Flutter 3.24.5 on Linux. Upstream's bridge.yml works around
|
||||||
|
# this by running the bridge generation under Flutter 3.22.3 -- the
|
||||||
|
# produced .dart/.freezed.dart files are then compatible with the
|
||||||
|
# 3.24.5 build that follows.
|
||||||
|
#
|
||||||
|
# provision.sh installs Flutter 3.22.3 at /opt/flutter-bridge alongside
|
||||||
|
# /opt/flutter (3.24.5). Use the bridge SDK ONLY for pub get + codegen,
|
||||||
|
# then unset PATH overrides so subsequent steps use the build SDK.
|
||||||
|
export PATH="/opt/cargo-tools/bin:/opt/flutter-bridge/bin:$PATH"
|
||||||
|
flutter --version
|
||||||
|
command -v flutter_rust_bridge_codegen
|
||||||
|
|
||||||
|
# CRITICAL: use the apt-installed libclang (libclang-dev), NOT the
|
||||||
|
# LLVM 15.0.6 tarball at /opt/llvm-15.0.6 that the rest of the build
|
||||||
|
# uses. fr_bridge_codegen 1.80.1 emits broken Dart (stray
|
||||||
|
# `typedef bool = NativeFunction<...>`, unprefixed Int/Pointer in
|
||||||
|
# part files) when its internal ffigen runs against the tarball
|
||||||
|
# libclang. Upstream bridge.yml uses apt's libclang and the same
|
||||||
|
# codegen produces clean output. Don't pass --llvm-path either; let
|
||||||
|
# ffigen find libclang via system defaults.
|
||||||
|
unset LIBCLANG_PATH
|
||||||
|
|
||||||
|
# extended_text 14.0.0 requires Dart >=3.5 (Flutter 3.24+). Flutter
|
||||||
|
# 3.22.3 has Dart 3.4.4, so downgrade to 13.0.0 for the duration of
|
||||||
|
# bridge generation. Mirrors upstream bridge.yml.
|
||||||
|
(cd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml)
|
||||||
|
(cd flutter && flutter pub get)
|
||||||
|
|
||||||
|
# flutter_rust_bridge_codegen invokes `flutter pub run ffigen`
|
||||||
|
# internally, which re-validates pubspec.yaml -- so pubspec.yaml must
|
||||||
|
# still be the downgraded version when this runs.
|
||||||
|
flutter_rust_bridge_codegen \
|
||||||
|
--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
|
||||||
|
|
||||||
|
# Now bridge gen is done. Restore the original pubspec.yaml and
|
||||||
|
# re-resolve under the build SDK (3.24.5) so pubspec.lock has the
|
||||||
|
# correct entries for the final flutter build linux step.
|
||||||
|
(cd flutter && git checkout -- pubspec.yaml)
|
||||||
|
(cd flutter && /opt/flutter/bin/flutter pub get)
|
||||||
|
|
||||||
|
- name: Diagnose generated bridge files
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
echo "============================================================"
|
||||||
|
echo " generated_bridge.dart (first 80 lines)"
|
||||||
|
echo "============================================================"
|
||||||
|
head -80 flutter/lib/generated_bridge.dart 2>/dev/null || echo "(missing)"
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " generated_bridge.freezed.dart (first 80 lines)"
|
||||||
|
echo "============================================================"
|
||||||
|
head -80 flutter/lib/generated_bridge.freezed.dart 2>/dev/null || echo "(missing)"
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Search: typedef declarations that might shadow bool/Int/Pointer"
|
||||||
|
echo "============================================================"
|
||||||
|
grep -nE 'typedef (bool|Int|Pointer|Bool)' \
|
||||||
|
flutter/lib/generated_bridge.dart \
|
||||||
|
flutter/lib/generated_bridge.freezed.dart 2>/dev/null || echo "(no shadowing typedefs)"
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Search: imports that might pull weird symbols"
|
||||||
|
echo "============================================================"
|
||||||
|
grep -nE '^import|^export|^part ' \
|
||||||
|
flutter/lib/generated_bridge.dart \
|
||||||
|
flutter/lib/generated_bridge.freezed.dart 2>/dev/null
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " store_dart_post_cobject signature in generated_bridge.dart"
|
||||||
|
echo "============================================================"
|
||||||
|
grep -nA2 'store_dart_post_cobject' flutter/lib/generated_bridge.dart 2>/dev/null | head -30
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Line 25 of generated_bridge.freezed.dart (the failing one)"
|
||||||
|
echo "============================================================"
|
||||||
|
sed -n '20,30p' flutter/lib/generated_bridge.freezed.dart 2>/dev/null
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " flutter_rust_bridge package version actually resolved"
|
||||||
|
echo "============================================================"
|
||||||
|
grep -A2 'flutter_rust_bridge:' flutter/pubspec.lock | head -10
|
||||||
|
|
||||||
|
- 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 <features> --lib --release
|
||||||
|
# - flutter build linux --release
|
||||||
|
# - assembles tmpdeb/ and runs dpkg-deb -b
|
||||||
|
# Output: ./rustdesk-<version-from-Cargo.toml>.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
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
name: build-macos
|
||||||
|
|
||||||
|
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.81" # MAC_RUST_VERSION upstream (cidre needs >=1.81)
|
||||||
|
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-macos-x64
|
||||||
|
# Intel Mac runner. provision.sh tags x86_64 macOS hosts with the X64 label.
|
||||||
|
runs-on: [self-hosted, macOS, X64]
|
||||||
|
timeout-minutes: 240
|
||||||
|
env:
|
||||||
|
VCPKG_ROOT: /opt/vcpkg
|
||||||
|
VCPKG_BINARY_SOURCES: "clear;files,/var/cache/vcpkg,readwrite"
|
||||||
|
VCPKG_DEFAULT_TRIPLET: x64-osx
|
||||||
|
VCPKG_DEFAULT_HOST_TRIPLET: x64-osx
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Verify host toolchain
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
required=(git bash python3 rustc cargo rustup clang flutter cmake ninja nasm pkg-config create-dmg)
|
||||||
|
missing=()
|
||||||
|
for t in "${required[@]}"; do
|
||||||
|
if command -v "$t" >/dev/null 2>&1; then
|
||||||
|
printf '%-15s %s\n' "$t" "$(command -v "$t")"
|
||||||
|
else
|
||||||
|
missing+=("$t")
|
||||||
|
printf '%-15s 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; }
|
||||||
|
|
||||||
|
- 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 "Display : $display"
|
||||||
|
echo "VERSION_DISPLAY=$display" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Patch Cargo.toml with display version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# BSD sed (macOS): -i requires an empty backup-suffix arg.
|
||||||
|
sed -i '' -E "1,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/;}" Cargo.toml
|
||||||
|
grep '^version' Cargo.toml | head -1
|
||||||
|
|
||||||
|
# No deployment-target patch on x86_64: build.py's build_flutter_dmg()
|
||||||
|
# already exports MACOSX_DEPLOYMENT_TARGET=10.14 internally, matching the
|
||||||
|
# Flutter Xcode project. Upstream only patches the target for arm64
|
||||||
|
# (which needs 12.3+).
|
||||||
|
|
||||||
|
- name: Ensure Rust toolchain configured
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
|
||||||
|
rustup default "$RUST_VERSION"
|
||||||
|
rustup target add x86_64-apple-darwin
|
||||||
|
rustc --version
|
||||||
|
cargo --version
|
||||||
|
|
||||||
|
- name: Install flutter_rust_bridge codegen tools
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
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 (with Flutter 3.22.3)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# Same dual-SDK trick as Linux: bridge codegen 1.80.1 produces broken
|
||||||
|
# Dart on Flutter 3.24.5; provision.sh installs 3.22.3 at
|
||||||
|
# /opt/flutter-bridge for codegen only. Switch back to 3.24.5 after.
|
||||||
|
export PATH="/opt/cargo-tools/bin:/opt/flutter-bridge/bin:$PATH"
|
||||||
|
flutter --version
|
||||||
|
command -v flutter_rust_bridge_codegen
|
||||||
|
|
||||||
|
# Match the Linux fix: don't pass --llvm-path, let ffigen find Xcode
|
||||||
|
# CLT's libclang via system defaults. Passing brew's llvm or a custom
|
||||||
|
# libclang path triggered fr_bridge_codegen 1.80.1's bad-emission bug
|
||||||
|
# (stray `typedef bool = NativeFunction<...>`) on Linux; same logic
|
||||||
|
# carries over here.
|
||||||
|
unset LIBCLANG_PATH
|
||||||
|
|
||||||
|
(cd flutter && sed -i '' -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml)
|
||||||
|
(cd flutter && flutter pub get)
|
||||||
|
|
||||||
|
flutter_rust_bridge_codegen \
|
||||||
|
--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
|
||||||
|
|
||||||
|
(cd flutter && git checkout -- pubspec.yaml)
|
||||||
|
(cd flutter && /opt/flutter/bin/flutter pub get)
|
||||||
|
|
||||||
|
- name: Diagnose generated bridge files
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
echo "============================================================"
|
||||||
|
echo " generated_bridge.dart (first 80 lines)"
|
||||||
|
echo "============================================================"
|
||||||
|
head -80 flutter/lib/generated_bridge.dart 2>/dev/null || echo "(missing)"
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo " Search: typedef declarations that might shadow bool/Int/Pointer"
|
||||||
|
echo "============================================================"
|
||||||
|
grep -nE 'typedef (bool|Int|Pointer|Bool)' \
|
||||||
|
flutter/lib/generated_bridge.dart \
|
||||||
|
flutter/lib/generated_bridge.freezed.dart 2>/dev/null || echo "(no shadowing typedefs)"
|
||||||
|
|
||||||
|
- name: vcpkg install dependencies (x64-osx)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p /var/cache/vcpkg
|
||||||
|
if ! "$VCPKG_ROOT/vcpkg" install \
|
||||||
|
--triplet x64-osx \
|
||||||
|
--x-install-root="$VCPKG_ROOT/installed"; then
|
||||||
|
find "$VCPKG_ROOT/" -name '*.log' -exec sh -c 'echo "===== {} ====="; cat "{}"' \;
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build RustDesk (.app + .dmg)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# build.py -> build_flutter_dmg() does:
|
||||||
|
# - MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features <features> --release
|
||||||
|
# - cp liblibrustdesk.dylib librustdesk.dylib
|
||||||
|
# - flutter build macos --release
|
||||||
|
# - cp -rf ../target/release/service .../RustDesk.app/Contents/MacOS/
|
||||||
|
# It does NOT package the .dmg (the create-dmg call is commented out).
|
||||||
|
# We package below.
|
||||||
|
#
|
||||||
|
# No --screencapturekit on x86_64: upstream's matrix only enables it
|
||||||
|
# on aarch64 (cidre's ScreenCaptureKit bindings target arm64-only APIs).
|
||||||
|
python3 build.py --flutter --hwcodec --unix-file-copy-paste
|
||||||
|
|
||||||
|
# Ad-hoc re-sign the whole bundle in one pass.
|
||||||
|
# `flutter build macos --release` ad-hoc signs the main binary, but
|
||||||
|
# FlutterMacOS.framework already carries its own ad-hoc signature
|
||||||
|
# from Flutter's engine artifacts. dyld on Apple Silicon (macOS 13+)
|
||||||
|
# enforces Team ID match between the main process and every loaded
|
||||||
|
# framework -- two ad-hoc signatures from different signing passes
|
||||||
|
# have different per-binary cdhashes and fail the check, producing
|
||||||
|
# `mapping process and mapped file have different Team IDs` at
|
||||||
|
# launch time on M-series Macs. `codesign --deep --sign -` re-signs
|
||||||
|
# every nested binary/framework/dylib with the same ad-hoc identity
|
||||||
|
# in one pass, so all components share a consistent signing context.
|
||||||
|
# When we wire up real Developer ID + notarization later, replace
|
||||||
|
# `-` with the cert identity and drop --deep in favor of inside-out
|
||||||
|
# signing.
|
||||||
|
codesign --force --deep --sign - \
|
||||||
|
./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||||
|
codesign --verify --deep --strict --verbose=2 \
|
||||||
|
./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||||
|
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
# Use hdiutil (not create-dmg) because the runner is a LaunchDaemon
|
||||||
|
# with no GUI/Finder session. create-dmg drives Finder via AppleScript
|
||||||
|
# for icon layout and fails with `-10810` in daemon context. hdiutil
|
||||||
|
# produces a fully functional compressed DMG with no GUI calls.
|
||||||
|
dmg_staging="$(mktemp -d -t rustdesk-dmg)"
|
||||||
|
cp -R ./flutter/build/macos/Build/Products/Release/RustDesk.app "$dmg_staging/"
|
||||||
|
ln -s /Applications "$dmg_staging/Applications"
|
||||||
|
hdiutil create \
|
||||||
|
-volname "RustDesk" \
|
||||||
|
-srcfolder "$dmg_staging" \
|
||||||
|
-ov \
|
||||||
|
-format UDZO \
|
||||||
|
"./SignOutput/rustdesk-${VERSION_DISPLAY}-x86_64.dmg"
|
||||||
|
rm -rf "$dmg_staging"
|
||||||
|
|
||||||
|
- name: Report signing status of build artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
for f in ./SignOutput/*.dmg; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
size=$(stat -f%z "$f")
|
||||||
|
sig=$(codesign -dv "$f" 2>&1 | grep -E '^Authority' | head -1 || true)
|
||||||
|
if [[ -z "$sig" ]]; then
|
||||||
|
printf '[UNSIGNED] %s (%d bytes)\n' "$(basename "$f")" "$size"
|
||||||
|
else
|
||||||
|
printf '[ SIGNED ] %s (%d bytes) %s\n' "$(basename "$f")" "$size" "$sig"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "::warning title=Unsigned .dmg::Wire up codesign + notarytool before distributing."
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rustdesk-macos-x64-${{ github.sha }}
|
||||||
|
path: SignOutput/rustdesk-*.dmg
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 14
|
||||||
@@ -0,0 +1,487 @@
|
|||||||
|
name: build-windows
|
||||||
|
|
||||||
|
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.ps1 installs LLVM. ffigen and flutter_rust_bridge_codegen need
|
||||||
|
# the install root (they append bin\libclang.dll themselves).
|
||||||
|
LLVM_HOME: 'C:\tools\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"
|
||||||
|
# Numeric base, must match Cargo.toml's <major>.<minor>.<patch>.
|
||||||
|
# MSI ProductVersion is forced to this (Windows Installer rejects non-numeric).
|
||||||
|
VERSION_BASE: "1.4.6"
|
||||||
|
# Default suffix on push events. workflow_dispatch can override per-run.
|
||||||
|
VERSION_SUFFIX: ${{ inputs.version_suffix || 'cst' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-x64:
|
||||||
|
name: build-windows-x64
|
||||||
|
runs-on: [self-hosted, windows-10]
|
||||||
|
timeout-minutes: 240
|
||||||
|
env:
|
||||||
|
VCPKG_ROOT: C:\vcpkg
|
||||||
|
VCPKG_BINARY_SOURCES: "clear;files,C:\\vcpkg-cache,readwrite"
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Verify host toolchain
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$required = 'node','pwsh','git','bash','python','rustc','cargo','rustup','clang','flutter','nuget','cmake','ninja','dotnet'
|
||||||
|
$missing = @()
|
||||||
|
foreach ($tool in $required) {
|
||||||
|
$cmd = Get-Command $tool -ErrorAction SilentlyContinue
|
||||||
|
if (-not $cmd) { $missing += $tool; continue }
|
||||||
|
$ver = & $tool --version 2>&1 | Select-Object -First 1
|
||||||
|
Write-Host ("{0,-10} {1} ({2})" -f $tool, $cmd.Source, $ver)
|
||||||
|
}
|
||||||
|
if ($missing.Count -gt 0) {
|
||||||
|
Write-Error ("Missing tools on runner: {0}. Re-run provision.ps1 or install manually." -f ($missing -join ', '))
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if (-not $env:VCPKG_ROOT -or -not (Test-Path "$env:VCPKG_ROOT\vcpkg.exe")) {
|
||||||
|
Write-Error "VCPKG_ROOT not set or vcpkg.exe missing at $env:VCPKG_ROOT"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "VCPKG_ROOT $env:VCPKG_ROOT"
|
||||||
|
|
||||||
|
- name: Compute version strings
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$base = "${env:VERSION_BASE}"
|
||||||
|
$suffix = "${env:VERSION_SUFFIX}"
|
||||||
|
if ($base -notmatch '^\d+\.\d+\.\d+$') {
|
||||||
|
Write-Error "VERSION_BASE '$base' must be major.minor.patch numeric"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if ($suffix) { $display = "$base-$suffix" } else { $display = $base }
|
||||||
|
Write-Host ("Base : {0} (used for MSI ProductVersion)" -f $base)
|
||||||
|
Write-Host ("Suffix : {0}" -f $suffix)
|
||||||
|
Write-Host ("Display : {0} (used for exe filename + Cargo.toml)" -f $display)
|
||||||
|
"VERSION_DISPLAY=$display" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
|
||||||
|
- name: Patch Cargo.toml with display version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Cargo accepts SemVer-style suffix with a hyphen (e.g. 1.4.6-cst).
|
||||||
|
sed -i -E "0,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/}" Cargo.toml
|
||||||
|
echo "--- Cargo.toml [package] ---"
|
||||||
|
awk '/^\[package\]/{f=1} f; /^\[/&&!/^\[package\]/{f=0}' Cargo.toml | head -10
|
||||||
|
|
||||||
|
- name: Ensure Rust toolchain configured for runner user
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# provision.ps1 installs rust machine-wide (CARGO_HOME=C:\cargo,
|
||||||
|
# RUSTUP_HOME=C:\rustup) so this is normally a no-op verification.
|
||||||
|
# Kept as a guardrail in case the runner was provisioned with an
|
||||||
|
# older script or by hand.
|
||||||
|
rustup --version
|
||||||
|
rustup toolchain install $env:RUST_VERSION --profile minimal --component rustfmt
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "rustup toolchain install failed ($LASTEXITCODE)" }
|
||||||
|
rustup default $env:RUST_VERSION
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "rustup default failed ($LASTEXITCODE)" }
|
||||||
|
rustup target add x86_64-pc-windows-msvc
|
||||||
|
rustc --version
|
||||||
|
cargo --version
|
||||||
|
|
||||||
|
- name: Install flutter_rust_bridge codegen tools
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Pin the install destination with --root so the resulting binaries land
|
||||||
|
# in a deterministic, machine-wide path regardless of which user the
|
||||||
|
# runner service is configured to run as.
|
||||||
|
$tools = 'C:\cargo-tools'
|
||||||
|
New-Item -ItemType Directory -Force -Path "$tools\bin" | Out-Null
|
||||||
|
|
||||||
|
cargo install --root $tools cargo-expand --version "$env:CARGO_EXPAND_VERSION" --locked
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cargo install cargo-expand failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
cargo install --root $tools flutter_rust_bridge_codegen --version "$env:FLUTTER_RUST_BRIDGE_VERSION" --features uuid --locked
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cargo install flutter_rust_bridge_codegen failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
Write-Host "--- $tools\bin ---"
|
||||||
|
Get-ChildItem "$tools\bin" | Format-Table Name, Length, LastWriteTime
|
||||||
|
|
||||||
|
$expected = Join-Path $tools 'bin\flutter_rust_bridge_codegen.exe'
|
||||||
|
if (-not (Test-Path $expected)) { throw "missing: $expected" }
|
||||||
|
|
||||||
|
Add-Content -Path $env:GITHUB_PATH -Value "$tools\bin"
|
||||||
|
|
||||||
|
- name: Generate Rust <-> Dart bridge
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
LIBCLANG_PATH: '${{ env.LLVM_HOME }}\bin'
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Force-prepend cargo-tools and Git so they win the lookup race even if
|
||||||
|
# something earlier in PATH owns the same name.
|
||||||
|
$env:PATH = "C:\cargo-tools\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\bin;$env:PATH"
|
||||||
|
|
||||||
|
Write-Host "== Tool resolution =="
|
||||||
|
foreach ($t in 'git','flutter','flutter_rust_bridge_codegen','dart') {
|
||||||
|
$cmd = Get-Command $t -ErrorAction SilentlyContinue
|
||||||
|
if ($cmd) { Write-Host ("{0,-30} {1}" -f $t, $cmd.Source) }
|
||||||
|
else { Write-Host ("{0,-30} MISSING" -f $t) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# `where git` shows ALL git.exe matches on PATH. If multiple, the first
|
||||||
|
# is what CreateProcess will pick -- and it might be a broken shim.
|
||||||
|
Write-Host "`n== where git =="
|
||||||
|
where.exe git 2>&1
|
||||||
|
|
||||||
|
Write-Host "`n== git --version =="
|
||||||
|
git --version
|
||||||
|
|
||||||
|
# Diagnose the real reason flutter pub get says "Unable to find git in
|
||||||
|
# your PATH". On a self-hosted runner where the Flutter SDK at
|
||||||
|
# C:\tools\flutter was provisioned by a different user, git refuses to
|
||||||
|
# touch it with "fatal: detected dubious ownership". Flutter's tool
|
||||||
|
# catches that ProcessException and (misleadingly) reports it as a
|
||||||
|
# missing-git-on-PATH error.
|
||||||
|
Write-Host "`n== git ops on flutter SDK (probe for dubious ownership) =="
|
||||||
|
git -C 'C:\tools\flutter' rev-parse HEAD 2>&1 | Out-Host
|
||||||
|
Write-Host "`n== Configuring safe.directory globally for runner user =="
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
git config --global --get-all safe.directory
|
||||||
|
|
||||||
|
# Workaround: stage git.exe next to dart.exe. CreateProcessW searches
|
||||||
|
# the calling exe's directory before PATH -- so dropping git.exe here
|
||||||
|
# bypasses any PATH oddities the dart child may inherit from its
|
||||||
|
# parents (powershell -> cmd -> flutter.bat -> dart.exe).
|
||||||
|
$dartBin = 'C:\tools\flutter\bin\cache\dart-sdk\bin'
|
||||||
|
$stagedGit = Join-Path $dartBin 'git.exe'
|
||||||
|
$sourceGit = 'C:\Program Files\Git\cmd\git.exe'
|
||||||
|
if ((Test-Path $dartBin) -and -not (Test-Path $stagedGit)) {
|
||||||
|
Write-Host "`nStaging $sourceGit -> $stagedGit"
|
||||||
|
Copy-Item -Force $sourceGit $stagedGit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path "$env:LIBCLANG_PATH\libclang.dll")) {
|
||||||
|
throw "libclang.dll not found at $env:LIBCLANG_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n== flutter pub get =="
|
||||||
|
Push-Location flutter
|
||||||
|
flutter pub get
|
||||||
|
$rc = $LASTEXITCODE
|
||||||
|
Pop-Location
|
||||||
|
if ($rc -ne 0) { throw "flutter pub get failed ($rc)" }
|
||||||
|
|
||||||
|
Write-Host "`n== flutter_rust_bridge_codegen =="
|
||||||
|
flutter_rust_bridge_codegen `
|
||||||
|
--llvm-path "$env:LLVM_HOME" `
|
||||||
|
--rust-input ./src/flutter_ffi.rs `
|
||||||
|
--dart-output ./flutter/lib/generated_bridge.dart `
|
||||||
|
--c-output ./flutter/macos/Runner/bridge_generated.h
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "flutter_rust_bridge_codegen failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
Copy-Item -Force `
|
||||||
|
./flutter/macos/Runner/bridge_generated.h `
|
||||||
|
./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|
||||||
|
- name: Replace Flutter engine with rustdesk custom engine
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
|
||||||
|
Expand-Archive -Force -Path windows-x64-release.zip -DestinationPath windows-x64-release
|
||||||
|
$engineDir = "C:\tools\flutter\bin\cache\artifacts\engine\windows-x64-release"
|
||||||
|
New-Item -ItemType Directory -Force -Path $engineDir | Out-Null
|
||||||
|
Move-Item -Force windows-x64-release\* $engineDir\
|
||||||
|
|
||||||
|
- name: Patch Flutter (dropdown_menu enableFilter)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
patch_file=".github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff"
|
||||||
|
if [ -f "$patch_file" ]; then
|
||||||
|
flutter_root="$(dirname "$(dirname "$(which flutter)")")"
|
||||||
|
cp "$patch_file" "$flutter_root/"
|
||||||
|
(cd "$flutter_root" && git apply "$(basename "$patch_file")" || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: vcpkg install dependencies (x64-windows-static)
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
VCPKG_DEFAULT_HOST_TRIPLET: x64-windows-static
|
||||||
|
run: |
|
||||||
|
mkdir -p /c/vcpkg-cache
|
||||||
|
if ! "$VCPKG_ROOT/vcpkg" install \
|
||||||
|
--triplet x64-windows-static \
|
||||||
|
--x-install-root="$VCPKG_ROOT/installed"; then
|
||||||
|
find "$VCPKG_ROOT/" -name "*.log" -exec sh -c 'echo "===== {} ====="; cat "{}"' \;
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Diagnose + restore flutter/windows if missing
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "== pwd =="
|
||||||
|
pwd
|
||||||
|
echo
|
||||||
|
echo "== flutter/ (top level) =="
|
||||||
|
ls -la flutter/ 2>/dev/null || echo "flutter/ missing"
|
||||||
|
echo
|
||||||
|
if [ -d flutter/windows ]; then
|
||||||
|
echo "== flutter/windows/ exists, file count =="
|
||||||
|
find flutter/windows -type f | wc -l
|
||||||
|
ls -la flutter/windows/
|
||||||
|
else
|
||||||
|
echo "!! flutter/windows MISSING !!"
|
||||||
|
echo "git status (filtered to flutter/windows):"
|
||||||
|
git status --porcelain | grep -E "flutter/windows" | head -30 || echo "(no entries)"
|
||||||
|
echo
|
||||||
|
echo "Restoring flutter/windows from git index..."
|
||||||
|
git checkout HEAD -- flutter/windows
|
||||||
|
if [ -d flutter/windows ]; then
|
||||||
|
echo "Restored: $(find flutter/windows -type f | wc -l) files"
|
||||||
|
else
|
||||||
|
echo "RESTORE FAILED -- check git ls-files:"
|
||||||
|
git ls-files flutter/windows | head
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The upstream GitHub workflow runs `python build.py` which chains cargo +
|
||||||
|
# `flutter build windows` in one step. We split that here so we can:
|
||||||
|
# - get a checkpoint between cargo and flutter (~14 min apart)
|
||||||
|
# - restore flutter/windows from git index if anything has clobbered it
|
||||||
|
# (early in our self-hosted bring-up, flutter/windows occasionally
|
||||||
|
# vanished during the cargo phase under LocalSystem; the restore is
|
||||||
|
# defensive and a no-op once the runner is on a normal user)
|
||||||
|
|
||||||
|
- name: Cargo build (virtual_display dylib + main lib)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$features = "hwcodec,vram,flutter"
|
||||||
|
|
||||||
|
Push-Location libs\virtual_display\dylib
|
||||||
|
cargo build --release
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cargo build (virtual_display) failed" }
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
cargo build --features $features --lib --release
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "cargo build (rustdesk lib) failed" }
|
||||||
|
if (-not (Test-Path target\release\librustdesk.dll)) {
|
||||||
|
throw "target\release\librustdesk.dll missing after cargo build"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore + build are intentionally one step. Closes any potential window
|
||||||
|
# for flutter/windows to vanish between a separate restore step and the
|
||||||
|
# build, regardless of root cause.
|
||||||
|
- name: Flutter build windows (with last-second restore)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$cmake = '.\flutter\windows\CMakeLists.txt'
|
||||||
|
|
||||||
|
Write-Host "== Pre-flight check =="
|
||||||
|
Write-Host "pwd: $(Get-Location)"
|
||||||
|
if (Test-Path .\flutter\windows) {
|
||||||
|
Write-Host "flutter/windows present: $((Get-ChildItem -Recurse .\flutter\windows -File).Count) files"
|
||||||
|
} else {
|
||||||
|
Write-Host "flutter/windows MISSING"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $cmake)) {
|
||||||
|
Write-Host "`n== Restoring flutter/windows from git index =="
|
||||||
|
git status --porcelain | Where-Object { $_ -match 'flutter/windows' } | Select-Object -First 10
|
||||||
|
git checkout HEAD -- flutter/windows
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "git checkout HEAD -- flutter/windows failed" }
|
||||||
|
if (-not (Test-Path $cmake)) {
|
||||||
|
Write-Host "git ls-files flutter/windows:"
|
||||||
|
git ls-files flutter/windows | Select-Object -First 5
|
||||||
|
throw "Restore did not produce $cmake"
|
||||||
|
}
|
||||||
|
Write-Host "Restored: $((Get-ChildItem -Recurse .\flutter\windows -File).Count) files"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Touch a marker so we can confirm later (in logs) that this guard ran.
|
||||||
|
$marker = ".\flutter\windows\.restored-at"
|
||||||
|
Set-Content -Path $marker -Value (Get-Date -Format o)
|
||||||
|
|
||||||
|
Write-Host "`n== Running flutter build windows --release =="
|
||||||
|
Push-Location flutter
|
||||||
|
flutter build windows --release
|
||||||
|
$rc = $LASTEXITCODE
|
||||||
|
Pop-Location
|
||||||
|
if ($rc -ne 0) {
|
||||||
|
Write-Host "`n!! flutter build failed -- post-mortem state of flutter/windows: !!"
|
||||||
|
if (Test-Path .\flutter\windows) { Get-ChildItem .\flutter\windows -Force }
|
||||||
|
else { Write-Host "(does not exist)" }
|
||||||
|
throw "flutter build windows failed ($rc)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mirror what build.py's build_flutter_windows() does after flutter build.
|
||||||
|
Copy-Item -Force `
|
||||||
|
target\release\deps\dylib_virtual_display.dll `
|
||||||
|
flutter\build\windows\x64\runner\Release\
|
||||||
|
|
||||||
|
Move-Item -Force flutter\build\windows\x64\runner\Release rustdesk
|
||||||
|
|
||||||
|
- name: Bundle usbmmidd_v2 + printer driver
|
||||||
|
shell: pwsh
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
|
||||||
|
Expand-Archive -Force usbmmidd_v2.zip -DestinationPath .
|
||||||
|
Remove-Item -Recurse -Force usbmmidd_v2\Win32
|
||||||
|
Remove-Item -Force usbmmidd_v2\deviceinstaller64.exe, usbmmidd_v2\deviceinstaller.exe, usbmmidd_v2\usbmmidd.bat
|
||||||
|
Move-Item -Force .\usbmmidd_v2 .\rustdesk\
|
||||||
|
|
||||||
|
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/rustdesk_printer_driver_v4-1.4.zip -OutFile printer.zip
|
||||||
|
Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/printer_driver_adapter.zip -OutFile printer_adapter.zip
|
||||||
|
Expand-Archive -Force printer.zip -DestinationPath .
|
||||||
|
Expand-Archive -Force printer_adapter.zip -DestinationPath .
|
||||||
|
New-Item -ItemType Directory -Force -Path .\rustdesk\drivers | Out-Null
|
||||||
|
Move-Item -Force .\rustdesk_printer_driver_v4-1.4 .\rustdesk\drivers\RustDeskPrinterDriver
|
||||||
|
Move-Item -Force .\printer_driver_adapter.dll .\rustdesk\
|
||||||
|
|
||||||
|
- name: Copy Runner.res for portable packer
|
||||||
|
shell: bash
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
runner_res=$(find . -name Runner.res | head -1)
|
||||||
|
[ -n "$runner_res" ] && cp "$runner_res" ./libs/portable/Runner.res
|
||||||
|
|
||||||
|
- name: Build portable self-extracting exe
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sed -i '/dpiAware/d' res/manifest.xml
|
||||||
|
pushd ./libs/portable
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python ./generate.py -f ../../rustdesk/ -o . -e ../../rustdesk/rustdesk.exe
|
||||||
|
popd
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
mv ./target/release/rustdesk-portable-packer.exe "./SignOutput/rustdesk-${VERSION_DISPLAY}-x86_64.exe"
|
||||||
|
|
||||||
|
- name: Build MSI installer
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Push-Location .\res\msi
|
||||||
|
# Pass numeric VERSION_BASE explicitly: MSI ProductVersion must be numeric,
|
||||||
|
# so we cannot let preprocess.py auto-detect from rustdesk.exe (which now
|
||||||
|
# carries the suffixed VERSION_DISPLAY).
|
||||||
|
python preprocess.py --arp -d ..\..\rustdesk -v "${env:VERSION_BASE}"
|
||||||
|
|
||||||
|
# Resolve MSBuild from the installed VS Build Tools.
|
||||||
|
# Resolve vswhere.exe explicitly. ${env:ProgramFiles(x86)} interpolation
|
||||||
|
# has parser-version quirks; use [Environment] to avoid them.
|
||||||
|
$pfx86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)')
|
||||||
|
$vswhere = Join-Path $pfx86 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||||
|
if (-not (Test-Path $vswhere)) {
|
||||||
|
throw "vswhere.exe not found at $vswhere -- VS Installer missing?"
|
||||||
|
}
|
||||||
|
Write-Host "vswhere: $vswhere"
|
||||||
|
|
||||||
|
# Diagnostic: show what VS installs vswhere can see.
|
||||||
|
Write-Host "`n== vswhere installations =="
|
||||||
|
& $vswhere -all -prerelease -products '*' -property installationPath
|
||||||
|
Write-Host "==="
|
||||||
|
|
||||||
|
# Try the targeted -requires first; fall back to a broader search if the
|
||||||
|
# component name doesn't match (e.g. on Build Tools the component IDs differ).
|
||||||
|
$msbuild = & $vswhere -latest -products '*' `
|
||||||
|
-requires Microsoft.Component.MSBuild `
|
||||||
|
-find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1
|
||||||
|
if (-not $msbuild) {
|
||||||
|
Write-Host "Targeted lookup failed; trying broader search."
|
||||||
|
$msbuild = & $vswhere -latest -products '*' -prerelease `
|
||||||
|
-find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1
|
||||||
|
}
|
||||||
|
if (-not $msbuild) {
|
||||||
|
# Last resort: any MSBuild.exe under any VS install path.
|
||||||
|
$vsRoots = & $vswhere -all -prerelease -products '*' -property installationPath
|
||||||
|
foreach ($r in $vsRoots) {
|
||||||
|
$candidate = Get-ChildItem -Path $r -Recurse -Filter 'MSBuild.exe' -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FullName -like '*\Bin\MSBuild.exe' -and $_.FullName -notlike '*\amd64\*' } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($candidate) { $msbuild = $candidate.FullName; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $msbuild) { throw "MSBuild not found via vswhere or filesystem search" }
|
||||||
|
Write-Host "msbuild: $msbuild"
|
||||||
|
|
||||||
|
# Two-stage restore covers both project flavors in this solution:
|
||||||
|
# - CustomActions.vcxproj uses old-style packages.config -> nuget restore
|
||||||
|
# - Package.wixproj is SDK-style PackageReference -> msbuild -t:Restore
|
||||||
|
nuget restore msi.sln
|
||||||
|
& $msbuild msi.sln -t:Restore -p:Configuration=Release -p:Platform=x64
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "MSBuild restore failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
# WiX's WindowsInstallerValidation target invokes ICEs through the
|
||||||
|
# local Windows Installer service (msiserver). When the runner user
|
||||||
|
# lacks the COM/RPC rights to that service, every ICE check fails
|
||||||
|
# with WIX0217. Skip ICE validation -- it's a development-time lint,
|
||||||
|
# not a functional requirement, and the produced MSI is identical.
|
||||||
|
# Defensively also nudge the service in case some other validation
|
||||||
|
# path (e.g. signing tools) needs it.
|
||||||
|
try { Start-Service msiserver -ErrorAction SilentlyContinue } catch {}
|
||||||
|
|
||||||
|
& $msbuild msi.sln `
|
||||||
|
-p:Configuration=Release -p:Platform=x64 `
|
||||||
|
/p:TargetVersion=Windows10 `
|
||||||
|
/p:SuppressValidation=true
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "MSBuild build failed ($LASTEXITCODE)" }
|
||||||
|
|
||||||
|
Move-Item -Force .\Package\bin\x64\Release\en-us\Package.msi "..\..\SignOutput\rustdesk-${env:VERSION_DISPLAY}-x86_64.msi"
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
- name: Report signing status of build artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$artifacts = Get-ChildItem .\SignOutput -Include *.exe,*.msi -File
|
||||||
|
if (-not $artifacts) {
|
||||||
|
Write-Warning "No artifacts found in SignOutput\"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$unsigned = @()
|
||||||
|
foreach ($f in $artifacts) {
|
||||||
|
$sig = Get-AuthenticodeSignature -FilePath $f.FullName
|
||||||
|
$size = '{0,8:N0}' -f $f.Length
|
||||||
|
switch ($sig.Status) {
|
||||||
|
'Valid' {
|
||||||
|
Write-Host ("[ SIGNED ] {0} ({1} bytes) signed by: {2}" -f $f.Name, $size, $sig.SignerCertificate.Subject)
|
||||||
|
}
|
||||||
|
'NotSigned' {
|
||||||
|
Write-Host ("[UNSIGNED] {0} ({1} bytes)" -f $f.Name, $size)
|
||||||
|
$unsigned += $f.Name
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Write-Host ("[ {0,-7} ] {1} ({2} bytes) -- {3}" -f $sig.Status, $f.Name, $size, $sig.StatusMessage)
|
||||||
|
$unsigned += $f.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($unsigned.Count -gt 0) {
|
||||||
|
# Render a Gitea/GHA-style annotation so it shows up prominently in the run summary.
|
||||||
|
$list = $unsigned -join ', '
|
||||||
|
Write-Host "::warning title=Unsigned artifacts::$list -- SmartScreen will warn end users. Wire up signing before distributing."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rustdesk-windows-x64-${{ github.sha }}
|
||||||
|
path: |
|
||||||
|
SignOutput/rustdesk-*.exe
|
||||||
|
SignOutput/rustdesk-*.msi
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 14
|
||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
[submodule "libs/hbb_common"]
|
[submodule "libs/hbb_common"]
|
||||||
path = libs/hbb_common
|
path = libs/hbb_common
|
||||||
url = https://github.com/rustdesk/hbb_common
|
url = https://gitea.cstudio.ch/mike/hbb_common.git
|
||||||
|
branch = pro-features
|
||||||
|
|||||||
Executable
+264
@@ -0,0 +1,264 @@
|
|||||||
|
#!/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 <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" # used for `flutter build linux`
|
||||||
|
FLUTTER_BRIDGE_VERSION="3.22.3" # used for `flutter pub get` + flutter_rust_bridge_codegen
|
||||||
|
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 \
|
||||||
|
autoconf automake libtool libtool-bin \
|
||||||
|
libclang-dev llvm-dev \
|
||||||
|
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 (two SDKs: 3.24.5 for build, 3.22.3 for bridge gen) ----
|
||||||
|
# Why two: the bridge codegen (flutter_rust_bridge_codegen 1.80.1 + freezed)
|
||||||
|
# produces broken Dart output when run under newer Flutter SDKs on Linux.
|
||||||
|
# Upstream's bridge.yml uses 3.22.3 specifically; we mirror that. The .deb
|
||||||
|
# build itself uses 3.24.5.
|
||||||
|
install_flutter() {
|
||||||
|
local ver="$1" dir="$2"
|
||||||
|
if [[ ! -x "$dir/bin/flutter" ]]; then
|
||||||
|
log "Installing Flutter $ver -> $dir"
|
||||||
|
local tmp; tmp="$(mktemp -d)"
|
||||||
|
local parent; parent="$(dirname "$dir")"
|
||||||
|
curl -fsSL -o "$tmp/flutter.tar.xz" \
|
||||||
|
"https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${ver}-stable.tar.xz"
|
||||||
|
# Tarball extracts into a top-level `flutter/` dir; rename to target.
|
||||||
|
tar -xJf "$tmp/flutter.tar.xz" -C "$tmp"
|
||||||
|
mkdir -p "$parent"
|
||||||
|
mv "$tmp/flutter" "$dir"
|
||||||
|
rm -rf "$tmp"
|
||||||
|
fi
|
||||||
|
"$dir/bin/flutter" config --no-analytics >/dev/null
|
||||||
|
"$dir/bin/flutter" precache --linux >/dev/null
|
||||||
|
}
|
||||||
|
install_flutter "$FLUTTER_VERSION" /opt/flutter
|
||||||
|
install_flutter "$FLUTTER_BRIDGE_VERSION" /opt/flutter-bridge
|
||||||
|
FLUTTER_DIR=/opt/flutter
|
||||||
|
|
||||||
|
# ---- 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"
|
||||||
|
chown -R "$SERVICE_USER:$SERVICE_USER" /opt/flutter-bridge
|
||||||
|
# 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"
|
||||||
|
# /opt/cargo-tools: workflow installs cargo-expand and flutter_rust_bridge_codegen
|
||||||
|
# here via `cargo install --root`. Pre-create with the right owner so the first
|
||||||
|
# job doesn't try to mkdir under root-owned /opt.
|
||||||
|
mkdir -p /opt/cargo-tools
|
||||||
|
chown -R "$SERVICE_USER:$SERVICE_USER" /opt/cargo-tools
|
||||||
|
|
||||||
|
# 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 <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Gitea Actions runner (RustDesk)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${SERVICE_USER}
|
||||||
|
WorkingDirectory=${RUNNER_DIR}
|
||||||
|
ExecStart=${RUNNER_DIR}/act_runner daemon
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Toolchain locations -- needed because services don't inherit a login shell's PATH.
|
||||||
|
Environment=RUSTUP_HOME=${RUSTUP_HOME}
|
||||||
|
Environment=CARGO_HOME=${CARGO_HOME}
|
||||||
|
Environment=VCPKG_ROOT=${VCPKG_DIR}
|
||||||
|
Environment=LIBCLANG_PATH=${LLVM_DIR}/lib
|
||||||
|
Environment=PATH=${CARGO_HOME}/bin:${FLUTTER_DIR}/bin:${LLVM_DIR}/bin:${VCPKG_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
# Resource limits for builds
|
||||||
|
LimitNOFILE=65535
|
||||||
|
TasksMax=infinity
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable gitea-act-runner.service
|
||||||
|
systemctl restart gitea-act-runner.service
|
||||||
|
|
||||||
|
log "Done."
|
||||||
|
echo " Verify with: systemctl status gitea-act-runner"
|
||||||
|
echo " Tail logs with: journalctl -u gitea-act-runner -f"
|
||||||
|
echo " Runner should appear (online) at $GITEA_URL > Site Admin > Actions > Runners"
|
||||||
Executable
+291
@@ -0,0 +1,291 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Provisions a macOS host (Apple Silicon, macOS 14+) as a Gitea Actions runner
|
||||||
|
# for RustDesk desktop (.dmg) builds. Idempotent: safe to re-run.
|
||||||
|
#
|
||||||
|
# Versions are pinned to .gitea/workflows/build-macos.yml. Bump them there and
|
||||||
|
# here together.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sudo ./provision.sh \
|
||||||
|
# --gitea-url https://gitea.example.com \
|
||||||
|
# --runner-token <token>
|
||||||
|
#
|
||||||
|
# Toolchains land in /opt/* (chowned to the runner user). Service is installed
|
||||||
|
# as a LaunchDaemon running as that user.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---- pinned versions (mirror .gitea/workflows/build-macos.yml env block) ----
|
||||||
|
RUST_VERSION="1.81.0" # MAC_RUST_VERSION upstream (cidre crate needs >=1.81)
|
||||||
|
FLUTTER_VERSION="3.24.5" # used for `flutter build macos`
|
||||||
|
FLUTTER_BRIDGE_VERSION="3.22.3" # used for `flutter pub get` + flutter_rust_bridge_codegen
|
||||||
|
VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b"
|
||||||
|
NASM_VERSION="2.16.03" # 3.x has incompatible CLI; aom/dav1d need 2.x
|
||||||
|
RUNNER_VERSION="0.2.11"
|
||||||
|
|
||||||
|
# ---- defaults ----
|
||||||
|
RUNNER_NAME="$(hostname -s)-rustdesk"
|
||||||
|
RUNNER_LABELS=""
|
||||||
|
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,15p' "$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; }
|
||||||
|
|
||||||
|
# ---- arch + macOS version detection ----
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
case "$ARCH" in
|
||||||
|
arm64) HOMEBREW_PREFIX="/opt/homebrew"; ARCH_LABEL="ARM64" ;;
|
||||||
|
x86_64) HOMEBREW_PREFIX="/usr/local"; ARCH_LABEL="X64" ;;
|
||||||
|
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
OS_MAJOR="$(sw_vers -productVersion | cut -d. -f1)"
|
||||||
|
[[ "$OS_MAJOR" -ge 14 ]] || {
|
||||||
|
echo "WARNING: tested only on macOS 14+. You're on $(sw_vers -productVersion)."
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
DISTRO_LABEL="macos-${OS_MAJOR}"
|
||||||
|
|
||||||
|
if [[ -z "$RUNNER_LABELS" ]]; then
|
||||||
|
RUNNER_LABELS="${DISTRO_LABEL},self-hosted,${ARCH_LABEL},macOS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log() { printf '\n==> %s\n' "$*"; }
|
||||||
|
|
||||||
|
# ---- 1. Xcode Command Line Tools ----
|
||||||
|
log "Verifying Xcode Command Line Tools"
|
||||||
|
if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
|
||||||
|
echo "Xcode Command Line Tools not installed. Run:" >&2
|
||||||
|
echo " xcode-select --install" >&2
|
||||||
|
echo "Then re-run this script." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " $(xcode-select -p)"
|
||||||
|
|
||||||
|
# ---- 2. Homebrew (machine-wide) ----
|
||||||
|
# Homebrew refuses to install under root (its installer aborts with
|
||||||
|
# "Don't run this as root!"). It must be installed manually by a regular
|
||||||
|
# user before this script runs.
|
||||||
|
log "Verifying Homebrew"
|
||||||
|
if [[ ! -x "$HOMEBREW_PREFIX/bin/brew" ]]; then
|
||||||
|
echo "Homebrew not installed at $HOMEBREW_PREFIX." >&2
|
||||||
|
echo "Install it as your regular user (NOT root), then re-run this script:" >&2
|
||||||
|
echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export PATH="$HOMEBREW_PREFIX/bin:$PATH"
|
||||||
|
echo " $(brew --version | head -1)"
|
||||||
|
|
||||||
|
# brew install must also run as a non-root user. Determine which user invoked
|
||||||
|
# sudo so we can drop privileges for brew commands below.
|
||||||
|
BREW_USER="${SUDO_USER:-}"
|
||||||
|
if [[ -z "$BREW_USER" || "$BREW_USER" == "root" ]]; then
|
||||||
|
echo "Could not determine the non-root user that ran sudo (SUDO_USER unset)." >&2
|
||||||
|
echo "Re-run with: sudo ./provision.sh ..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
brew_as_user() { sudo -u "$BREW_USER" -H "$HOMEBREW_PREFIX/bin/brew" "$@"; }
|
||||||
|
|
||||||
|
# ---- 3. brew packages ----
|
||||||
|
log "Installing brew packages"
|
||||||
|
brew_pkgs=(node cocoapods llvm create-dmg pkg-config cmake ninja yasm autoconf automake libtool wget)
|
||||||
|
for p in "${brew_pkgs[@]}"; do
|
||||||
|
if brew_as_user list --versions "$p" >/dev/null 2>&1; then
|
||||||
|
echo " $p (already installed)"
|
||||||
|
else
|
||||||
|
brew_as_user install "$p"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- 4. NASM 2.16.x (NOT brew's nasm 3.x; aom/dav1d need 2.x) ----
|
||||||
|
if ! /usr/local/bin/nasm --version 2>/dev/null | grep -q "version $NASM_VERSION"; then
|
||||||
|
log "Installing NASM $NASM_VERSION"
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
curl -fsSL -o "$tmp/nasm.zip" \
|
||||||
|
"https://www.nasm.us/pub/nasm/releasebuilds/${NASM_VERSION}/macosx/nasm-${NASM_VERSION}-macosx.zip"
|
||||||
|
unzip -q "$tmp/nasm.zip" -d "$tmp"
|
||||||
|
install -m 0755 "$tmp/nasm-${NASM_VERSION}/nasm" /usr/local/bin/nasm
|
||||||
|
rm -rf "$tmp"
|
||||||
|
fi
|
||||||
|
/usr/local/bin/nasm --version | head -1
|
||||||
|
|
||||||
|
# ---- 5. dedicated runner user ----
|
||||||
|
if ! /usr/bin/id -u "$SERVICE_USER" >/dev/null 2>&1; then
|
||||||
|
log "Creating user $SERVICE_USER"
|
||||||
|
# Find an unused UID >= 600
|
||||||
|
uid=600
|
||||||
|
while dscl . -list /Users UniqueID | awk '{print $2}' | grep -qx "$uid"; do
|
||||||
|
uid=$((uid + 1))
|
||||||
|
done
|
||||||
|
dscl . -create "/Users/$SERVICE_USER"
|
||||||
|
dscl . -create "/Users/$SERVICE_USER" UserShell /bin/bash
|
||||||
|
dscl . -create "/Users/$SERVICE_USER" RealName "Gitea Runner"
|
||||||
|
dscl . -create "/Users/$SERVICE_USER" UniqueID "$uid"
|
||||||
|
dscl . -create "/Users/$SERVICE_USER" PrimaryGroupID 20
|
||||||
|
dscl . -create "/Users/$SERVICE_USER" NFSHomeDirectory "/Users/$SERVICE_USER"
|
||||||
|
mkdir -p "/Users/$SERVICE_USER"
|
||||||
|
chown "$SERVICE_USER:staff" "/Users/$SERVICE_USER"
|
||||||
|
fi
|
||||||
|
RUNNER_HOME="/Users/$SERVICE_USER"
|
||||||
|
|
||||||
|
# ---- 6. 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" aarch64-apple-darwin x86_64-apple-darwin
|
||||||
|
"$CARGO_HOME/bin/rustup" default "$RUST_VERSION"
|
||||||
|
|
||||||
|
# ---- 7. Flutter (two SDKs: 3.24.5 for build, 3.22.3 for bridge gen) ----
|
||||||
|
# Same rationale as Linux: bridge codegen 1.80.1 + freezed produces broken Dart
|
||||||
|
# under newer Flutter. Run codegen under 3.22.3, build under 3.24.5.
|
||||||
|
install_flutter() {
|
||||||
|
local ver="$1" dir="$2"
|
||||||
|
if [[ ! -x "$dir/bin/flutter" ]]; then
|
||||||
|
log "Installing Flutter $ver -> $dir"
|
||||||
|
local tmp; tmp="$(mktemp -d)"
|
||||||
|
local parent; parent="$(dirname "$dir")"
|
||||||
|
# Flutter URL pattern differs between archs: Apple Silicon has an
|
||||||
|
# `_arm64_` infix, Intel has no arch infix at all.
|
||||||
|
local flutter_url_base="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos"
|
||||||
|
local flutter_url
|
||||||
|
case "$ARCH" in
|
||||||
|
arm64) flutter_url="${flutter_url_base}_arm64_${ver}-stable.zip" ;;
|
||||||
|
x86_64) flutter_url="${flutter_url_base}_${ver}-stable.zip" ;;
|
||||||
|
esac
|
||||||
|
curl -fsSL -o "$tmp/flutter.zip" "$flutter_url"
|
||||||
|
mkdir -p "$parent"
|
||||||
|
unzip -q "$tmp/flutter.zip" -d "$tmp"
|
||||||
|
mv "$tmp/flutter" "$dir"
|
||||||
|
rm -rf "$tmp"
|
||||||
|
fi
|
||||||
|
"$dir/bin/flutter" config --no-analytics >/dev/null
|
||||||
|
"$dir/bin/flutter" precache --macos >/dev/null
|
||||||
|
}
|
||||||
|
install_flutter "$FLUTTER_VERSION" /opt/flutter
|
||||||
|
install_flutter "$FLUTTER_BRIDGE_VERSION" /opt/flutter-bridge
|
||||||
|
|
||||||
|
# ---- 8. 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
|
||||||
|
|
||||||
|
mkdir -p /var/cache/vcpkg
|
||||||
|
chown -R "$SERVICE_USER:staff" /var/cache/vcpkg
|
||||||
|
|
||||||
|
# ---- 9. Permissions ----
|
||||||
|
log "Setting up permissions for $SERVICE_USER"
|
||||||
|
chown -R "$SERVICE_USER:staff" "$CARGO_HOME" "$RUSTUP_HOME" \
|
||||||
|
/opt/flutter /opt/flutter-bridge "$VCPKG_DIR"
|
||||||
|
mkdir -p /opt/cargo-tools
|
||||||
|
chown -R "$SERVICE_USER:staff" /opt/cargo-tools
|
||||||
|
|
||||||
|
git config --system --add safe.directory '*' || true
|
||||||
|
|
||||||
|
# ---- 10. act_runner ----
|
||||||
|
RUNNER_DIR="/usr/local/var/gitea-runner"
|
||||||
|
mkdir -p "$RUNNER_DIR"
|
||||||
|
chown -R "$SERVICE_USER:staff" "$RUNNER_DIR"
|
||||||
|
|
||||||
|
if [[ ! -x "$RUNNER_DIR/act_runner" ]]; then
|
||||||
|
log "Downloading act_runner $RUNNER_VERSION"
|
||||||
|
case "$ARCH" in
|
||||||
|
arm64) rarch="arm64" ;;
|
||||||
|
x86_64) rarch="amd64" ;;
|
||||||
|
esac
|
||||||
|
curl -fsSL -o "$RUNNER_DIR/act_runner" \
|
||||||
|
"https://gitea.com/gitea/act_runner/releases/download/v${RUNNER_VERSION}/act_runner-${RUNNER_VERSION}-darwin-${rarch}"
|
||||||
|
chmod +x "$RUNNER_DIR/act_runner"
|
||||||
|
chown "$SERVICE_USER:staff" "$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
|
||||||
|
|
||||||
|
# ---- 11. launchd service ----
|
||||||
|
log "Installing LaunchDaemon"
|
||||||
|
PLIST=/Library/LaunchDaemons/com.rustdesk.gitea-runner.plist
|
||||||
|
cat > "$PLIST" <<EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.rustdesk.gitea-runner</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>${SERVICE_USER}</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>${RUNNER_DIR}</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>${RUNNER_DIR}/act_runner</string>
|
||||||
|
<string>daemon</string>
|
||||||
|
</array>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>RUSTUP_HOME</key> <string>${RUSTUP_HOME}</string>
|
||||||
|
<key>CARGO_HOME</key> <string>${CARGO_HOME}</string>
|
||||||
|
<key>VCPKG_ROOT</key> <string>${VCPKG_DIR}</string>
|
||||||
|
<key>HOMEBREW_PREFIX</key> <string>${HOMEBREW_PREFIX}</string>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>${CARGO_HOME}/bin:/opt/flutter/bin:/opt/cargo-tools/bin:${HOMEBREW_PREFIX}/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||||
|
</dict>
|
||||||
|
<key>RunAtLoad</key> <true/>
|
||||||
|
<key>KeepAlive</key> <true/>
|
||||||
|
<key>StandardOutPath</key><string>${RUNNER_DIR}/stdout.log</string>
|
||||||
|
<key>StandardErrorPath</key><string>${RUNNER_DIR}/stderr.log</string>
|
||||||
|
<key>SoftResourceLimits</key>
|
||||||
|
<dict>
|
||||||
|
<key>NumberOfFiles</key> <integer>65535</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$PLIST"
|
||||||
|
|
||||||
|
launchctl bootout system "$PLIST" 2>/dev/null || true
|
||||||
|
launchctl bootstrap system "$PLIST"
|
||||||
|
launchctl enable "system/com.rustdesk.gitea-runner"
|
||||||
|
|
||||||
|
log "Done."
|
||||||
|
echo " Verify with: sudo launchctl print system/com.rustdesk.gitea-runner | head"
|
||||||
|
echo " Tail logs with: tail -F $RUNNER_DIR/stderr.log"
|
||||||
|
echo " Runner should appear (online) at $GITEA_URL > Site Admin > Actions > Runners"
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
# Provisions a Windows host (Windows 10/11 or Server 2019+) as a Gitea Actions
|
||||||
|
# runner for RustDesk desktop builds. Idempotent: safe to re-run.
|
||||||
|
#
|
||||||
|
# Versions are pinned to .gitea/workflows/build-windows.yml. Bump them there and
|
||||||
|
# here together.
|
||||||
|
#
|
||||||
|
# Usage (Administrator PowerShell):
|
||||||
|
# Set-ExecutionPolicy -Scope Process Bypass -Force
|
||||||
|
# .\provision.ps1 -GiteaUrl https://gitea.example.com -RunnerToken <token>
|
||||||
|
#
|
||||||
|
# By default the runner service is created under a dedicated local user
|
||||||
|
# (`gitea-runner`) -- LocalSystem has been observed to break flutter pub get,
|
||||||
|
# symlink creation, and git's "dubious ownership" check on this codebase. To
|
||||||
|
# opt out, pass `-ServiceAccount LocalSystem` (not recommended).
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)] [string] $GiteaUrl,
|
||||||
|
[Parameter(Mandatory = $true)] [string] $RunnerToken,
|
||||||
|
[string] $RunnerName = "$env:COMPUTERNAME-rustdesk",
|
||||||
|
[string] $RunnerLabels = "windows-10,self-hosted,X64",
|
||||||
|
[string] $RunnerVersion = "0.2.11",
|
||||||
|
[string] $ServiceAccount = "gitea-runner",
|
||||||
|
[SecureString] $ServiceAccountPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
# Must run elevated -- nearly every step writes Machine env, HKLM, or service config.
|
||||||
|
$me = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
if (-not (New-Object Security.Principal.WindowsPrincipal $me).IsInRole(
|
||||||
|
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
throw 'Run this script in an elevated (Administrator) PowerShell session.'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- pinned versions (mirror .gitea/workflows/build-windows.yml env block) ---
|
||||||
|
$RUST_VERSION = '1.75.0'
|
||||||
|
$RUST_NIGHTLY = 'nightly-2023-10-13'
|
||||||
|
$LLVM_VERSION = '15.0.6'
|
||||||
|
$FLUTTER_VERSION = '3.24.5'
|
||||||
|
$VCPKG_COMMIT = '120deac3062162151622ca4860575a33844ba10b'
|
||||||
|
|
||||||
|
$ToolsRoot = 'C:\tools'
|
||||||
|
New-Item -ItemType Directory -Force -Path $ToolsRoot | Out-Null
|
||||||
|
|
||||||
|
# Exact-segment-match version of PATH augmentation. Substring matching would
|
||||||
|
# falsely find C:\bin when C:\binaries is on PATH.
|
||||||
|
function Add-MachinePath([string]$Dir) {
|
||||||
|
$cur = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
||||||
|
$segments = $cur -split ';' | Where-Object { $_ }
|
||||||
|
if ($segments -notcontains $Dir) {
|
||||||
|
[Environment]::SetEnvironmentVariable('Path', "$cur;$Dir", 'Machine')
|
||||||
|
}
|
||||||
|
if (($env:Path -split ';') -notcontains $Dir) { $env:Path = "$env:Path;$Dir" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. Chocolatey (used for git, python, nuget, 7zip, node, dotnet, ...) ---
|
||||||
|
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host '==> Installing Chocolatey'
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
Invoke-Expression ((New-Object Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host '==> Installing base packages'
|
||||||
|
# nodejs-lts: act_runner spawns Node to execute JavaScript actions.
|
||||||
|
# powershell-core: workflows use `shell: pwsh` (PS 7), not the OS's PS 5.1.
|
||||||
|
# dotnet-sdk: WiX 4 SDK-style projects (.wixproj) need it for the MSI build.
|
||||||
|
choco install -y --no-progress `
|
||||||
|
git python311 nuget.commandline 7zip cmake ninja `
|
||||||
|
nodejs-lts powershell-core dotnet-sdk
|
||||||
|
Add-MachinePath 'C:\Program Files\Git\cmd'
|
||||||
|
Add-MachinePath 'C:\Program Files\Git\bin' # bash.exe + posix tools (sed, find, ...)
|
||||||
|
Add-MachinePath 'C:\Python311'
|
||||||
|
Add-MachinePath 'C:\Python311\Scripts'
|
||||||
|
Add-MachinePath 'C:\Program Files\nodejs'
|
||||||
|
Add-MachinePath 'C:\Program Files\PowerShell\7'
|
||||||
|
Add-MachinePath 'C:\Program Files\dotnet'
|
||||||
|
|
||||||
|
# --- 2. Visual Studio 2022 Build Tools (MSVC v143 + Win10 SDK) ---
|
||||||
|
# Use [Environment]::GetEnvironmentVariable to avoid the PowerShell parser quirk
|
||||||
|
# that mis-tokenises `$env:ProgramFiles(x86)` as `$env:ProgramFiles` + `(x86)`.
|
||||||
|
$pfx86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)')
|
||||||
|
$vsInstaller = Join-Path $pfx86 'Microsoft Visual Studio\Installer\vswhere.exe'
|
||||||
|
$vsPresent = (Test-Path $vsInstaller) -and `
|
||||||
|
((& $vsInstaller -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) -ne $null)
|
||||||
|
if (-not $vsPresent) {
|
||||||
|
Write-Host '==> Installing VS 2022 Build Tools (this takes a while)'
|
||||||
|
$vsBootstrapper = "$env:TEMP\vs_buildtools.exe"
|
||||||
|
Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_buildtools.exe' -OutFile $vsBootstrapper
|
||||||
|
$vsArgs = @(
|
||||||
|
'--quiet','--wait','--norestart','--nocache',
|
||||||
|
'--add','Microsoft.VisualStudio.Workload.VCTools',
|
||||||
|
'--add','Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
||||||
|
'--add','Microsoft.VisualStudio.Component.VC.ATL',
|
||||||
|
'--add','Microsoft.VisualStudio.Component.Windows10SDK.20348',
|
||||||
|
'--add','Microsoft.VisualStudio.Component.VC.CMake.Project',
|
||||||
|
'--includeRecommended'
|
||||||
|
)
|
||||||
|
$p = Start-Process -FilePath $vsBootstrapper -ArgumentList $vsArgs -Wait -PassThru
|
||||||
|
if ($p.ExitCode -notin 0,3010) { throw "VS Build Tools installer exit $($p.ExitCode)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 3. Rust (stable + nightly with i686 target) ---
|
||||||
|
# Install machine-wide so any user (including the dedicated runner account)
|
||||||
|
# shares one toolchain registry. Without this, rustup state lives in the
|
||||||
|
# installing user's profile and the service user has no default toolchain.
|
||||||
|
$rustupHome = 'C:\rustup'
|
||||||
|
$cargoHome = 'C:\cargo'
|
||||||
|
[Environment]::SetEnvironmentVariable('RUSTUP_HOME', $rustupHome, 'Machine')
|
||||||
|
[Environment]::SetEnvironmentVariable('CARGO_HOME', $cargoHome, 'Machine')
|
||||||
|
$env:RUSTUP_HOME = $rustupHome
|
||||||
|
$env:CARGO_HOME = $cargoHome
|
||||||
|
Add-MachinePath "$cargoHome\bin"
|
||||||
|
|
||||||
|
if (-not (Test-Path "$cargoHome\bin\rustup.exe")) {
|
||||||
|
Write-Host '==> Installing rustup (machine-wide at C:\rustup, C:\cargo)'
|
||||||
|
Invoke-WebRequest -Uri 'https://win.rustup.rs/x86_64' -OutFile "$env:TEMP\rustup-init.exe"
|
||||||
|
& "$env:TEMP\rustup-init.exe" -y --default-toolchain none --profile minimal
|
||||||
|
}
|
||||||
|
rustup toolchain install $RUST_VERSION --profile minimal --component rustfmt
|
||||||
|
rustup target add --toolchain $RUST_VERSION x86_64-pc-windows-msvc
|
||||||
|
rustup toolchain install $RUST_NIGHTLY --profile minimal --component rustfmt
|
||||||
|
rustup target add --toolchain $RUST_NIGHTLY i686-pc-windows-msvc
|
||||||
|
rustup default $RUST_VERSION
|
||||||
|
|
||||||
|
# --- 4. LLVM/Clang (matches KyleMayes/install-llvm-action layout) ---
|
||||||
|
$llvmDir = "$ToolsRoot\llvm-$LLVM_VERSION"
|
||||||
|
if (-not (Test-Path "$llvmDir\bin\clang.exe")) {
|
||||||
|
Write-Host "==> Installing LLVM $LLVM_VERSION"
|
||||||
|
$llvmExe = "$env:TEMP\LLVM-$LLVM_VERSION-win64.exe"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/LLVM-$LLVM_VERSION-win64.exe" -OutFile $llvmExe
|
||||||
|
& $llvmExe /S "/D=$llvmDir" | Out-Null
|
||||||
|
}
|
||||||
|
[Environment]::SetEnvironmentVariable('LIBCLANG_PATH', "$llvmDir\bin", 'Machine')
|
||||||
|
Add-MachinePath "$llvmDir\bin"
|
||||||
|
|
||||||
|
# --- 5. Flutter (stable channel, with windows precache) ---
|
||||||
|
$flutterDir = "$ToolsRoot\flutter"
|
||||||
|
if (-not (Test-Path "$flutterDir\bin\flutter.bat")) {
|
||||||
|
Write-Host "==> Installing Flutter $FLUTTER_VERSION"
|
||||||
|
$flutterZip = "$env:TEMP\flutter.zip"
|
||||||
|
Invoke-WebRequest -Uri "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_$FLUTTER_VERSION-stable.zip" -OutFile $flutterZip
|
||||||
|
Expand-Archive -Force -Path $flutterZip -DestinationPath $ToolsRoot
|
||||||
|
}
|
||||||
|
Add-MachinePath "$flutterDir\bin"
|
||||||
|
& "$flutterDir\bin\flutter.bat" config --no-analytics | Out-Null
|
||||||
|
& "$flutterDir\bin\flutter.bat" precache --windows | Out-Null
|
||||||
|
|
||||||
|
# --- 6. vcpkg pinned to commit ---
|
||||||
|
$vcpkgDir = 'C:\vcpkg'
|
||||||
|
if (-not (Test-Path "$vcpkgDir\.git")) {
|
||||||
|
Write-Host '==> Cloning vcpkg'
|
||||||
|
git clone https://github.com/microsoft/vcpkg.git $vcpkgDir
|
||||||
|
}
|
||||||
|
Push-Location $vcpkgDir
|
||||||
|
git fetch --tags origin
|
||||||
|
git -c advice.detachedHead=false checkout $VCPKG_COMMIT
|
||||||
|
if (-not (Test-Path "$vcpkgDir\vcpkg.exe")) { & "$vcpkgDir\bootstrap-vcpkg.bat" -disableMetrics }
|
||||||
|
Pop-Location
|
||||||
|
[Environment]::SetEnvironmentVariable('VCPKG_ROOT', $vcpkgDir, 'Machine')
|
||||||
|
Add-MachinePath $vcpkgDir
|
||||||
|
|
||||||
|
# --- 7. CI prerequisites that aren't tools, but environmental switches ---
|
||||||
|
|
||||||
|
# git's "dubious ownership" check (>= 2.35.2) refuses to operate on a repo whose
|
||||||
|
# .git directory is owned by a different user than the one running git. The
|
||||||
|
# Flutter SDK at C:\tools\flutter is provisioned by this script as Administrator
|
||||||
|
# but the runner service runs as a non-admin user. Trust everything system-wide.
|
||||||
|
git config --system --add safe.directory '*' 2>$null
|
||||||
|
|
||||||
|
# Flutter on Windows needs SeCreateSymbolicLinkPrivilege to build plugins.
|
||||||
|
# Enable Developer Mode (registry) AND grant the privilege via Local Security
|
||||||
|
# Policy to the built-in "Users" group (SID S-1-5-32-545). Either alone has been
|
||||||
|
# observed to not take effect until logon-token refresh; doing both is
|
||||||
|
# belt-and-suspenders. The privilege only reaches a long-running service after
|
||||||
|
# a reboot or a fresh service-token issuance.
|
||||||
|
$devKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock'
|
||||||
|
if (-not (Test-Path $devKey)) { New-Item -Path $devKey -Force | Out-Null }
|
||||||
|
New-ItemProperty -Path $devKey -Name 'AllowDevelopmentWithoutDevLicense' `
|
||||||
|
-PropertyType DWORD -Value 1 -Force | Out-Null
|
||||||
|
|
||||||
|
$secCfg = "$env:TEMP\sec-symlink.cfg"
|
||||||
|
secedit /export /cfg $secCfg | Out-Null
|
||||||
|
$secContent = Get-Content $secCfg -Raw
|
||||||
|
if ($secContent -match 'SeCreateSymbolicLinkPrivilege\s*=\s*([^\r\n]*)') {
|
||||||
|
$cur = $matches[1]
|
||||||
|
if ($cur -notmatch '\*S-1-5-32-545') {
|
||||||
|
$secContent = $secContent -replace `
|
||||||
|
'(SeCreateSymbolicLinkPrivilege\s*=\s*)([^\r\n]*)', `
|
||||||
|
'$1$2,*S-1-5-32-545'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$secContent = $secContent -replace `
|
||||||
|
'(\[Privilege Rights\][\r\n]+)', `
|
||||||
|
"`$1SeCreateSymbolicLinkPrivilege = *S-1-5-32-545`r`n"
|
||||||
|
}
|
||||||
|
$secContent | Set-Content $secCfg
|
||||||
|
secedit /configure /db "$env:TEMP\sec-symlink.sdb" /cfg $secCfg /areas USER_RIGHTS /quiet
|
||||||
|
Remove-Item $secCfg, "$env:TEMP\sec-symlink.sdb" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# --- 8. Dedicated runner user ---
|
||||||
|
# Running as LocalSystem causes a cascade of issues:
|
||||||
|
# - $USERPROFILE = C:\Windows\System32\config\systemprofile, which Flutter,
|
||||||
|
# dart pub, and other POSIX-leaning tools mis-handle.
|
||||||
|
# - cargo install lands binaries in that systemprofile path -> not on PATH.
|
||||||
|
# - flutter/windows occasionally vanishes during long cargo builds.
|
||||||
|
# A normal local user fixes all of these.
|
||||||
|
if ($ServiceAccount -ne 'LocalSystem') {
|
||||||
|
if (-not (Get-LocalUser -Name $ServiceAccount -ErrorAction SilentlyContinue)) {
|
||||||
|
if (-not $ServiceAccountPassword) {
|
||||||
|
# Generate a 32-byte random password using the OS RNG. Encoded as
|
||||||
|
# base64 (alphanumeric + +/) and trimmed of padding -- meets local
|
||||||
|
# password complexity without needing System.Web (which is missing
|
||||||
|
# on Server Core).
|
||||||
|
$bytes = New-Object byte[] 24
|
||||||
|
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
|
||||||
|
$plain = ([Convert]::ToBase64String($bytes)).TrimEnd('=') + 'A1!'
|
||||||
|
$ServiceAccountPassword = ConvertTo-SecureString $plain -AsPlainText -Force
|
||||||
|
Remove-Variable plain, bytes
|
||||||
|
}
|
||||||
|
Write-Host "==> Creating local user '$ServiceAccount'"
|
||||||
|
New-LocalUser -Name $ServiceAccount -Password $ServiceAccountPassword `
|
||||||
|
-PasswordNeverExpires -AccountNeverExpires `
|
||||||
|
-Description 'Gitea Actions runner service account' | Out-Null
|
||||||
|
Add-LocalGroupMember -Group 'Users' -Member $ServiceAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grant "Log on as a service" via secedit (no PS native cmdlet for this).
|
||||||
|
$sid = (Get-LocalUser $ServiceAccount).SID.Value
|
||||||
|
$svcCfg = "$env:TEMP\sec-svc.cfg"
|
||||||
|
secedit /export /cfg $svcCfg | Out-Null
|
||||||
|
$svcContent = Get-Content $svcCfg -Raw
|
||||||
|
if ($svcContent -match "SeServiceLogonRight\s*=\s*([^\r\n]*)") {
|
||||||
|
if ($matches[1] -notmatch [regex]::Escape($sid)) {
|
||||||
|
$svcContent = $svcContent -replace `
|
||||||
|
'(SeServiceLogonRight\s*=\s*)([^\r\n]*)', `
|
||||||
|
"`$1`$2,*$sid"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$svcContent = $svcContent -replace `
|
||||||
|
'(\[Privilege Rights\][\r\n]+)', `
|
||||||
|
"`$1SeServiceLogonRight = *$sid`r`n"
|
||||||
|
}
|
||||||
|
$svcContent | Set-Content $svcCfg
|
||||||
|
secedit /configure /db "$env:TEMP\sec-svc.sdb" /cfg $svcCfg /areas USER_RIGHTS /quiet
|
||||||
|
Remove-Item $svcCfg, "$env:TEMP\sec-svc.sdb" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Ensure the user can read/write everything it needs for builds.
|
||||||
|
foreach ($p in @('C:\actions-runner','C:\cargo','C:\cargo-tools','C:\vcpkg','C:\vcpkg-cache')) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $p | Out-Null
|
||||||
|
icacls $p /grant "${ServiceAccount}:(OI)(CI)F" /T 2>$null | Out-Null
|
||||||
|
}
|
||||||
|
foreach ($p in @('C:\rustup','C:\tools')) {
|
||||||
|
if (Test-Path $p) { icacls $p /grant "${ServiceAccount}:(OI)(CI)RX" /T 2>$null | Out-Null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 9. Gitea act_runner ---
|
||||||
|
$runnerDir = 'C:\actions-runner'
|
||||||
|
New-Item -ItemType Directory -Force -Path $runnerDir | Out-Null
|
||||||
|
$runnerExe = "$runnerDir\act_runner.exe"
|
||||||
|
if (-not (Test-Path $runnerExe)) {
|
||||||
|
Write-Host "==> Downloading act_runner $RunnerVersion"
|
||||||
|
Invoke-WebRequest -Uri "https://gitea.com/gitea/act_runner/releases/download/v$RunnerVersion/act_runner-$RunnerVersion-windows-amd64.exe" -OutFile $runnerExe
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location $runnerDir
|
||||||
|
if (-not (Test-Path "$runnerDir\.runner")) {
|
||||||
|
Write-Host '==> Registering runner'
|
||||||
|
& $runnerExe register --no-interactive `
|
||||||
|
--instance $GiteaUrl `
|
||||||
|
--token $RunnerToken `
|
||||||
|
--name $RunnerName `
|
||||||
|
--labels $RunnerLabels
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reconfigure the service every run so re-running with a different
|
||||||
|
# -ServiceAccount actually takes effect.
|
||||||
|
$svc = Get-Service -Name 'gitea-act-runner' -ErrorAction SilentlyContinue
|
||||||
|
if ($svc) {
|
||||||
|
if ($svc.Status -eq 'Running') { Stop-Service gitea-act-runner }
|
||||||
|
} else {
|
||||||
|
Write-Host '==> Installing runner as Windows service'
|
||||||
|
choco install -y --no-progress nssm
|
||||||
|
nssm install gitea-act-runner $runnerExe daemon | Out-Null
|
||||||
|
}
|
||||||
|
nssm set gitea-act-runner AppDirectory $runnerDir | Out-Null
|
||||||
|
nssm set gitea-act-runner Start SERVICE_AUTO_START | Out-Null
|
||||||
|
nssm set gitea-act-runner AppStdout "$runnerDir\runner.log" | Out-Null
|
||||||
|
nssm set gitea-act-runner AppStderr "$runnerDir\runner.log" | Out-Null
|
||||||
|
|
||||||
|
if ($ServiceAccount -eq 'LocalSystem') {
|
||||||
|
nssm set gitea-act-runner ObjectName 'LocalSystem' | Out-Null
|
||||||
|
} else {
|
||||||
|
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ServiceAccountPassword)
|
||||||
|
try {
|
||||||
|
$plain = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($bstr)
|
||||||
|
nssm set gitea-act-runner ObjectName ".\$ServiceAccount" $plain | Out-Null
|
||||||
|
} finally {
|
||||||
|
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
||||||
|
Remove-Variable plain -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start may fail before reboot if the new SeServiceLogonRight hasn't reached
|
||||||
|
# SCM yet -- that's expected; the service will start cleanly after reboot.
|
||||||
|
try {
|
||||||
|
Start-Service gitea-act-runner
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Could not start gitea-act-runner now ($($_.Exception.Message)). It will start on reboot."
|
||||||
|
}
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '==> Done.'
|
||||||
|
Write-Host ' A reboot is REQUIRED before the first build run, so:'
|
||||||
|
Write-Host ' - the runner service inherits the new SeCreateSymbolicLinkPrivilege token'
|
||||||
|
Write-Host ' - all PATH/env changes propagate to the SCM-launched service'
|
||||||
|
Write-Host ' After reboot, verify the runner shows up in Gitea > Site Admin > Actions > Runners.'
|
||||||
|
if ($ServiceAccount -eq 'LocalSystem') {
|
||||||
|
Write-Warning 'Service is running as LocalSystem. RustDesk builds have been observed to fail in this configuration (Flutter pub get, symlinks, dubious ownership). Re-run with -ServiceAccount gitea-runner to switch.'
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# RustDesk Client vs OSS Server — Feature Gap Analysis
|
||||||
|
|
||||||
|
This document compares the RustDesk **client** ([/Users/sn0/Desktop/rustdesk](../)) against the OSS **server** ([/Users/sn0/Desktop/rustdesk-server](../../rustdesk-server/)) and lists every feature the client implements or expects, but that the OSS server (`hbbs` + `hbbr` + `rustdesk-utils`) does **not** provide.
|
||||||
|
|
||||||
|
The OSS server is, by upstream's own description in its README, deliberately minimal:
|
||||||
|
|
||||||
|
> Self-host your own RustDesk server, it is free and open source.
|
||||||
|
> If you want extra features, **RustDesk Server Pro** might suit you better.
|
||||||
|
|
||||||
|
Almost every gap below is filled by RustDesk Server Pro (closed source). What follows is the concrete list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
| Area | OSS server | Client expects |
|
||||||
|
|-----------------------------------------------------|-----------------|----------------------------------------------|
|
||||||
|
| Rendezvous protocol (UDP/TCP/WS) | ✅ implemented | ✅ |
|
||||||
|
| Relay protocol (`hbbr`) | ✅ implemented | ✅ |
|
||||||
|
| `RegisterPk` over **TCP** | ❌ `NOT_SUPPORT`| Uses UDP — non-issue if UDP reachable |
|
||||||
|
| `HttpProxyRequest` / `HttpProxyResponse` (HTTP-via-rendezvous tunnel) | ❌ no handler | Used when `USE_RAW_TCP_FOR_API=Y` |
|
||||||
|
| **Entire `/api/*` HTTP surface** (35 endpoints in [CONSOLE_API.md](CONSOLE_API.md)) | ❌ no HTTP server | All login, AB, group, audit, sysinfo, etc. |
|
||||||
|
| User / password / token authentication | ❌ | Bearer-token model |
|
||||||
|
| Address book (legacy or shared) | ❌ | Personal AB, shared AB, tags, peers |
|
||||||
|
| Device groups, users, accessible peers list | ❌ | "Group" tab in UI |
|
||||||
|
| Audit logging (conn, file, alarm, note) | ❌ | Background fire-and-forget |
|
||||||
|
| Session recording upload | ❌ | Chunked uploader |
|
||||||
|
| Sysinfo / heartbeat-based device tracking | ❌ | Every 15 s (3 s when active) |
|
||||||
|
| Strategy/policy push via heartbeat | ❌ | `config_options` / `disconnect` / `sysinfo` flag |
|
||||||
|
| 2FA, email-code, SMS-code, OIDC/SSO | ❌ | Login challenge variants |
|
||||||
|
| Plugin signing (`/lic/web/api/plugin-sign`) | ❌ | Optional, only if signed plugins shipped |
|
||||||
|
| CLI bulk device assignment (`rustdesk --assign`) | ❌ | `POST /api/devices/cli` |
|
||||||
|
| Per-tenant licensing / Pro status flag | ❌ | Inferred from sysinfo success |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Rendezvous-protocol gaps
|
||||||
|
|
||||||
|
The OSS server *does* speak the rendezvous protocol — it accepts and responds to all of the variants the client sends in the normal connection path. There are exactly **two** variant-level gaps:
|
||||||
|
|
||||||
|
### 1.1 `RegisterPk` over TCP — explicitly rejected
|
||||||
|
|
||||||
|
[src/rendezvous_server.rs:556-563](../../rustdesk-server/src/rendezvous_server.rs#L556) returns `RegisterPkResponse { result: NOT_SUPPORT }` for any TCP-arriving `RegisterPk`. UDP works fine. The client has UDP `RegisterPk` as the primary path ([src/rendezvous_mediator.rs:685-693](../src/rendezvous_mediator.rs#L685-L693)), so this is only an issue in fully UDP-blocked environments where the client falls back to TCP for everything.
|
||||||
|
|
||||||
|
### 1.2 `HttpProxyRequest` / `HttpProxyResponse` — no handler
|
||||||
|
|
||||||
|
When `USE_RAW_TCP_FOR_API` is enabled (and WebSocket is off), the client tunnels its HTTP API calls **as protobuf messages** over the rendezvous server's TCP socket — see [src/common.rs:1188-1250](../src/common.rs#L1188-L1250) (`tcp_proxy_request`).
|
||||||
|
|
||||||
|
- **Wire format:** an encrypted `KeyExchange` handshake, then a `RendezvousMessage::set_http_proxy_request(HttpProxyRequest { method, path, headers, body })`, expecting `HttpProxyResponse { status_code, headers, body }` back.
|
||||||
|
- **OSS handling:** there is no match arm for `HttpProxyRequest` in [/Users/sn0/Desktop/rustdesk-server/src/rendezvous_server.rs](../../rustdesk-server/src/rendezvous_server.rs); it falls into the `_ => {}` catch-all and is silently dropped.
|
||||||
|
- **Impact:** any client that has been pushed `OPTION_USE_RAW_TCP_FOR_API=Y` (typical for restricted networks) cannot reach the HTTP API at all on an OSS-only deployment, even if the API itself were implemented.
|
||||||
|
|
||||||
|
### 1.3 What the OSS server *does* handle (not gaps, for completeness)
|
||||||
|
|
||||||
|
- UDP: `RegisterPeer`, `RegisterPk`, `PunchHoleRequest`, `PunchHoleSent`, `LocalAddr`, `ConfigureUpdate` (loopback only), `SoftwareUpdate`.
|
||||||
|
- TCP: `PunchHoleRequest`, `RequestRelay`, `RelayResponse`, `PunchHoleSent`, `LocalAddr`, `TestNatRequest`.
|
||||||
|
- TCP listener2 (`port - 1`): `TestNatRequest`, `OnlineRequest`, plus a loopback admin CLI.
|
||||||
|
- WebSocket on `port + 2` (default 21118): same handler set as TCP.
|
||||||
|
- The relay (`hbbr`) listens on `port` (default 21117) and `port + 2` (WebSocket, default 21119), and handles `RequestRelay` for tunnelling.
|
||||||
|
|
||||||
|
So the **rendezvous + relay surface for plain peer-to-peer use is complete in the OSS server.** All the actual gaps are above the rendezvous layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. HTTP API — none of it exists in OSS
|
||||||
|
|
||||||
|
[/Users/sn0/Desktop/rustdesk-server/Cargo.toml](../../rustdesk-server/Cargo.toml) pulls in `axum` and `tower-http`, but they are unused — no `Router::new`, no route definitions, no HTTP listener bound. **All 35 endpoints documented in [CONSOLE_API.md](CONSOLE_API.md) are gaps.**
|
||||||
|
|
||||||
|
Grouped by feature area:
|
||||||
|
|
||||||
|
### 2.1 Authentication & session (`/api/login`, `/api/login-options`, `/api/logout`, `/api/currentUser`)
|
||||||
|
|
||||||
|
- No user table in the OSS schema (which is a single `peer` table — see §4 below). No password storage, hashing, salting.
|
||||||
|
- No bearer-token issuance or validation.
|
||||||
|
- No `email_check` / `tfa_check` challenge types (no email sender, no TOTP store).
|
||||||
|
- No login-options registry — client always gets an empty `oidc/...` list.
|
||||||
|
- No OIDC / SSO device flow (`/api/oidc/auth`, `/api/oidc/auth-query`).
|
||||||
|
|
||||||
|
### 2.2 Address book — both modes missing
|
||||||
|
|
||||||
|
The client supports two AB modes ([CONSOLE_API.md §4](CONSOLE_API.md)) and the OSS server implements neither:
|
||||||
|
|
||||||
|
- **Legacy single-blob mode** (`GET/POST /api/ab`) — needs a per-user blob store + gzip handling.
|
||||||
|
- **Shared mode** (`/api/ab/settings`, `/api/ab/personal`, `/api/ab/shared/profiles`, `/api/ab/peers`, `/api/ab/tags/{guid}`, plus the per-peer and per-tag CRUD on `{guid}`) — needs a normalized AB / peer / tag / share-rule schema with `read | read/write | full control` ACLs.
|
||||||
|
|
||||||
|
Without this, the client falls back to its local-only "Recents" / "Favorites" lists; nothing syncs across devices.
|
||||||
|
|
||||||
|
### 2.3 Device groups, users, accessible peers (`/api/device-group/accessible`, `/api/users`, `/api/peers`)
|
||||||
|
|
||||||
|
The "Group" tab in the desktop UI populates from these three endpoints. With OSS the tab is empty; the client logs `get accessible device groups: <error>` and silently swallows it.
|
||||||
|
|
||||||
|
### 2.4 Heartbeat / sysinfo / strategy push (`/api/heartbeat`, `/api/sysinfo`, `/api/sysinfo_ver`)
|
||||||
|
|
||||||
|
This is the agent-management heartbeat loop ([CONSOLE_API.md §6](CONSOLE_API.md)).
|
||||||
|
|
||||||
|
OSS gaps:
|
||||||
|
|
||||||
|
- No device-tracking table (last-seen, OS, hostname, version, IP, online state).
|
||||||
|
- No sysinfo cache or version string.
|
||||||
|
- No mechanism to **push back** in the heartbeat response:
|
||||||
|
- `sysinfo: <truthy>` to force an immediate sysinfo re-upload.
|
||||||
|
- `disconnect: [conn_id, ...]` to force a remote session to close.
|
||||||
|
- `modified_at` / `strategy.config_options` to push policy.
|
||||||
|
- Without strategy push, the operator cannot remotely set:
|
||||||
|
- `whitelist`, `relay-server`, `rendezvous-servers`, `direct-access-port`,
|
||||||
|
`stop-service`, `OPTION_DISABLE_UDP`, `OPTION_ENABLE_UDP_PUNCH`,
|
||||||
|
`OPTION_ENABLE_IPV6_PUNCH`, `OPTION_USE_RAW_TCP_FOR_API`,
|
||||||
|
`OPTION_DIRECT_SERVER`, etc.
|
||||||
|
- Or any of the `OPTION_PRESET_*` keys that pre-fill the address book / username / device group / strategy on a freshly enrolled client.
|
||||||
|
- `is_pro()` is set to `true` only when `/api/sysinfo` returns `SYSINFO_UPDATED` ([src/hbbs_http/sync.rs:219](../src/hbbs_http/sync.rs#L219)) — with OSS, `is_pro()` is permanently `false`, so any client behavior gated on it is disabled.
|
||||||
|
|
||||||
|
### 2.5 Audit (`/api/audit/conn`, `/api/audit/file`, `/api/audit/alarm`, `PUT /api/audit`)
|
||||||
|
|
||||||
|
The client emits these fire-and-forget on every:
|
||||||
|
|
||||||
|
- new remote session ([src/server/connection.rs:1248-1252](../src/server/connection.rs#L1248-L1252)),
|
||||||
|
- file send/receive ([src/server/connection.rs:1297-1330](../src/server/connection.rs#L1297-L1330)),
|
||||||
|
- security alarm — IP whitelist hit, brute-force thresholds ([src/server/connection.rs:1332-1349](../src/server/connection.rs#L1332-L1349)), and
|
||||||
|
- the operator-typed end-of-session note (PUT, [flutter/lib/common/widgets/dialog.dart:1656-1687](../flutter/lib/common/widgets/dialog.dart#L1656-L1687)).
|
||||||
|
|
||||||
|
Even though the client doesn't *block* on these, OSS silently swallows all of them, so:
|
||||||
|
|
||||||
|
- No central session log.
|
||||||
|
- No file-transfer log (incl. the top-10-by-size summary the client computes).
|
||||||
|
- No alarm notifications for IP-whitelist hits or brute-force attempts.
|
||||||
|
- No audit-row GUID exists, so the "leave a note when the session ends" dialog has nothing to attach to.
|
||||||
|
|
||||||
|
### 2.6 Session recording upload (`POST /api/record`)
|
||||||
|
|
||||||
|
Chunked uploader ([src/hbbs_http/record_upload.rs](../src/hbbs_http/record_upload.rs)) with `?type=new|part|tail|remove`. OSS has no `/api/record` route and no on-disk recording store, so server-side recording is impossible.
|
||||||
|
|
||||||
|
(Local-only recording on the controlling side still works — that's not a server feature.)
|
||||||
|
|
||||||
|
### 2.7 OIDC / SSO device flow (`/api/oidc/auth`, `/api/oidc/auth-query`)
|
||||||
|
|
||||||
|
The polled device-code flow ([src/hbbs_http/account.rs](../src/hbbs_http/account.rs)) requires an OIDC client implementation, browser-flow URL generation, and a poll-for-token side. None of this is in OSS.
|
||||||
|
|
||||||
|
### 2.8 CLI bulk assign (`POST /api/devices/cli`)
|
||||||
|
|
||||||
|
[src/core_main.rs:519-616](../src/core_main.rs#L519-L616). Used by `rustdesk --assign --token ...` for mass-deploy scripts to register a freshly-installed agent into a tenant, optionally setting `user_name`, `strategy_name`, `address_book_*`, `device_group_name`, `device_username`, `device_name`, `note`. Requires user/group/AB tables, none of which exist in OSS.
|
||||||
|
|
||||||
|
### 2.9 Plugin signature service (`POST /lic/web/api/plugin-sign`)
|
||||||
|
|
||||||
|
[src/plugin/callback_msg.rs:282-296](../src/plugin/callback_msg.rs#L282-L296). Required only if the deployment ships signed plugins. OSS has no plugin infrastructure of any kind.
|
||||||
|
|
||||||
|
### 2.10 Generic file downloader
|
||||||
|
|
||||||
|
`HEAD` then `GET` against an arbitrary URL with a required `Content-Length` ([src/hbbs_http/downloader.rs](../src/hbbs_http/downloader.rs)). Works against any static file server — OSS doesn't *need* to serve this, but a complete Pro-replacement backend usually exposes installer/plugin/recording downloads via this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Schema gaps
|
||||||
|
|
||||||
|
The OSS database is a single SQLite table called `peer`:
|
||||||
|
|
||||||
|
```
|
||||||
|
guid (PK), id (UNIQUE), uuid, pk, created_at, user (unused),
|
||||||
|
status (unused), note (unused), info (JSON: { ip })
|
||||||
|
```
|
||||||
|
|
||||||
|
[/Users/sn0/Desktop/rustdesk-server/src/database.rs:71-144](../../rustdesk-server/src/database.rs#L71-L144).
|
||||||
|
|
||||||
|
To support the client's HTTP surface a backend needs at minimum:
|
||||||
|
|
||||||
|
- `users` (id, name, display_name, avatar, email, note, password_hash, status, is_admin, totp_secret, oidc_subject, …)
|
||||||
|
- `tokens` (token_hash, user_id, expires_at)
|
||||||
|
- `oidc_sessions` (poll_code, state, created_at, access_token, …)
|
||||||
|
- `address_books` (guid, owner_user_id, name, note, kind=personal|shared)
|
||||||
|
- `address_book_shares` (ab_guid, user_or_group_id, rule={1,2,3})
|
||||||
|
- `address_book_peers` (ab_guid, peer_id, alias, tags[], note, password|hash, username, hostname, platform)
|
||||||
|
- `address_book_tags` (ab_guid, name, color)
|
||||||
|
- `device_groups` (id, name)
|
||||||
|
- `device_group_members` (device_group_id, user_or_group_id)
|
||||||
|
- `peers_extended` (peer_id, user_id, device_group_id, last_seen, version, sysinfo_blob, sysinfo_hash, sysinfo_ver, online, …)
|
||||||
|
- `audit_conn` (guid, peer_id, conn_id, session_id, action, ip, started_at, ended_at, note)
|
||||||
|
- `audit_file` (peer_id, peer_remote, type, path, is_file, info_json)
|
||||||
|
- `audit_alarm` (peer_id, typ, info_json)
|
||||||
|
- `recordings` (filename, peer_id, size, header_blob, started_at, finished_at)
|
||||||
|
- `strategies` (id, name, modified_at, config_options_json, extra_json)
|
||||||
|
- `peer_strategy_assignment` / `device_group_strategy_assignment`
|
||||||
|
|
||||||
|
The OSS schema covers exactly **one row** of one of those tables (`peers_extended.peer_id` plus `pk`/`uuid`). Everything else is a gap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Authentication / authorization gaps
|
||||||
|
|
||||||
|
- **No user/password.** The OSS server identifies a peer entirely by `(id, uuid, pk)`. There is no concept of a logged-in *human user*, no password, no session, no role.
|
||||||
|
- **No bearer tokens.** The client adds `Authorization: Bearer <access_token>` to every authenticated HTTP call ([flutter/lib/common.dart:2691-2695](../flutter/lib/common.dart#L2691-L2695)). With no HTTP API and no user store, OSS has nothing to validate against.
|
||||||
|
- **No 2FA.** The client supports TOTP challenge (`type: tfa_check`, `tfa_type`, `secret`) and per-device 2FA-trust ([src/ui_session_interface.rs](../src/ui_session_interface.rs)). Not present in OSS.
|
||||||
|
- **No email/SMS verification.** The `email_check` challenge type and `verificationCode` field have no sender on the OSS side.
|
||||||
|
- **No SSO / OIDC.** No identity-provider integration.
|
||||||
|
- **No admin/role concept.** The `UserPayload.is_admin` flag (used to gate the user-management UI) has no source.
|
||||||
|
- **No per-AB ACL.** `AbProfile.rule` (read / read-write / full control) has no enforcement layer.
|
||||||
|
- **No IP allowlisting / per-IP rate-limiting on HTTP endpoints.** OSS rate-limits `RegisterPk` per source IP ([src/rendezvous_server.rs:891-919](../../rustdesk-server/src/rendezvous_server.rs#L891-L919)) but that's at the rendezvous layer only.
|
||||||
|
|
||||||
|
What the OSS server *does* offer in this space:
|
||||||
|
|
||||||
|
- Optional symmetric server key (`-k`) checked against `licence_key` in `PunchHoleRequest` and `RequestRelay`. This is shared-secret deployment lockdown, not user auth.
|
||||||
|
- Ed25519 signing of `RelayResponse` payloads using the server's private key.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Operations / fleet management gaps
|
||||||
|
|
||||||
|
These are conveniences a Pro server offers via the strategy/heartbeat channel; OSS has no equivalent because heartbeat itself is not implemented.
|
||||||
|
|
||||||
|
- **Force-disconnect a remote session** from the admin console (heartbeat returns `disconnect: [conn_id]` — [src/hbbs_http/sync.rs:251-254](../src/hbbs_http/sync.rs#L251-L254)).
|
||||||
|
- **Force-refresh sysinfo** (`sysinfo` truthy in heartbeat).
|
||||||
|
- **Push global config** to all enrolled agents (the `strategy.config_options` map). Without this, every option must be set per-machine.
|
||||||
|
- **Pre-seed an agent** at install time with an address-book entry, alias, password, note, strategy, device group, custom hostname/username (`OPTION_PRESET_ADDRESS_BOOK_*`, `OPTION_PRESET_USERNAME`, `OPTION_PRESET_STRATEGY_NAME`, `OPTION_PRESET_DEVICE_GROUP_NAME`, …). Client emits these preset values on every sysinfo, but OSS discards them.
|
||||||
|
- **Operator end-of-session notes** (PUT `/api/audit`).
|
||||||
|
- **`rustdesk --assign --token …`** for mass deployment.
|
||||||
|
- **Brute-force / IP-whitelist alarms** to a central log.
|
||||||
|
|
||||||
|
What OSS *does* offer for ops:
|
||||||
|
|
||||||
|
- A loopback-only TCP admin CLI on `port - 1` (default 21115) for hbbs and on `port` for hbbr ([src/rendezvous_server.rs:1102-1116](../../rustdesk-server/src/rendezvous_server.rs#L1102-L1116), [src/relay_server.rs:152-323](../../rustdesk-server/src/relay_server.rs#L152-L323)) — `relay-servers`, `ip-blocker`, `ip-changes`, `punch-requests`, `always-use-relay`, `test-geo`, `blacklist-add`, `blocklist-add`, `total-bandwidth`, `usage`, etc.
|
||||||
|
- A `ConfigureUpdate` push *only from loopback* — the operator can update the rendezvous-server list pushed to clients, but only by `nc 127.0.0.1 21115` on the server box itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Client-side features that work fine against OSS
|
||||||
|
|
||||||
|
For balance — these features in the client need no Pro-server support at all:
|
||||||
|
|
||||||
|
- All in-session protocol (after the relay/direct connection is established): screen sharing, file transfer, terminal, RDP / VNC tunnel, port forward, voice call, view-only mode, whiteboard, printer, clipboard, multi-monitor, mouse/keyboard injection. These are negotiated on the session stream itself and never touch the management server.
|
||||||
|
- LAN discovery (when both ends are reachable on the same LAN, no rendezvous server needed at all).
|
||||||
|
- The client's local 2FA on the *controlled* side ("ask the operator for a one-time code"). That's a peer-to-peer protocol negotiation, not a server feature.
|
||||||
|
- IP-whitelist enforcement on the controlled side ([src/server/connection.rs:1202-1228](../src/server/connection.rs#L1202-L1228)) — done locally against the `whitelist` config option. (But the *operator UX* of pushing that whitelist to a fleet is missing — see §5.)
|
||||||
|
- Self-update — the client checks a hardcoded URL on the public update server, not the configured rendezvous/API server.
|
||||||
|
- Custom-server bootstrap via filename (`rustdesk-host=…,key=…,api=…,relay=….exe`, [src/custom_server.rs](../src/custom_server.rs)) — works against OSS as long as the `api=` field is left empty / public.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. What you'd need to build to fully replace Pro
|
||||||
|
|
||||||
|
Given the analysis above, a full Pro-replacement backend on top of OSS would need:
|
||||||
|
|
||||||
|
1. **Add an HTTP server** (axum is already in the Cargo.toml of OSS, unused). Implement the 35 routes in [CONSOLE_API.md](CONSOLE_API.md).
|
||||||
|
2. **Add a `HttpProxyRequest` handler** in `rendezvous_server.rs` so that locked-down clients can reach the HTTP API through the rendezvous TCP port (decode the protobuf, replay the request internally, wrap the response).
|
||||||
|
3. **Extend the schema** along the lines of §3.
|
||||||
|
4. **Add user / token / OIDC / 2FA layers**, plus an email sender for `email_check`.
|
||||||
|
5. **Implement the strategy / push-config side of `/api/heartbeat`** and the sysinfo cache for `/api/sysinfo*`.
|
||||||
|
6. **Add audit + recording stores** with retention and access-control.
|
||||||
|
7. (Optional) **Plugin signing service** if you're shipping signed plugins.
|
||||||
|
|
||||||
|
The rendezvous + relay protocol itself does not need to change — OSS is correct and complete there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Source-of-truth references
|
||||||
|
|
||||||
|
- Client HTTP API the server must serve: [docs/CONSOLE_API.md](CONSOLE_API.md).
|
||||||
|
- Client rendezvous receive loop: [src/rendezvous_mediator.rs](../src/rendezvous_mediator.rs).
|
||||||
|
- Client HTTP-via-TCP fallback: [src/common.rs:1188-1250](../src/common.rs#L1188-L1250).
|
||||||
|
- Client heartbeat loop: [src/hbbs_http/sync.rs](../src/hbbs_http/sync.rs).
|
||||||
|
- OSS rendezvous handler: [/Users/sn0/Desktop/rustdesk-server/src/rendezvous_server.rs](../../rustdesk-server/src/rendezvous_server.rs).
|
||||||
|
- OSS relay handler: [/Users/sn0/Desktop/rustdesk-server/src/relay_server.rs](../../rustdesk-server/src/relay_server.rs).
|
||||||
|
- OSS schema: [/Users/sn0/Desktop/rustdesk-server/src/database.rs](../../rustdesk-server/src/database.rs).
|
||||||
+1038
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user