name: Build SubnetDesk native packages on: workflow_call: inputs: upload-artifact: type: boolean default: true publish-release: type: boolean default: false build-mobile: type: boolean default: true upload-tag: type: string default: "nightly" # NOTE: F-Droid builder script 'flutter/build_fdroid.sh' reads environment # variables from this workflow! # # It does NOT read build steps, however, so please fix 'flutter/build_fdroid.sh # whenever you add changes to Android CI build action ('build-rustdesk-android') # in this file! env: SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html RUST_VERSION: "1.82" # axum-server 0.7.3 requires Rust 1.82; legacy Sciter keeps its separate toolchain above. MAC_RUST_VERSION: "1.82" CARGO_NDK_VERSION: "3.1.2" SCITER_ARMV7_CMAKE_VERSION: "3.29.7" SCITER_NASM_DEBVERSION: "2.15.05-1" LLVM_VERSION: "15.0.6" FLUTTER_VERSION: "3.44.1" DART_VERSION: "3.12.1" ANDROID_FLUTTER_VERSION: "3.44.1" TAG_NAME: "${{ inputs.upload-tag }}" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # vcpkg version: 2025.08.27 # If we change the `VCPKG COMMIT_ID`, please remember: # 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`. # Or we may face build issue like # https://github.com/rustdesk/rustdesk/actions/runs/14414119794/job/40427970174 # 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`. VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b" ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version VERSION: "1.3.0" NDK_VERSION: "r28c" #signing keys env variable checks ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" ANDROID_SIGNING_CERT_SHA256: "${{ secrets.ANDROID_SIGNING_CERT_SHA256 }}" MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}" UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" PUBLISH_RELEASE: "${{ inputs.publish-release }}" SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}-2" SUBNETDESK_UPDATE_PUBLIC_KEY: "${{ secrets.SUBNETDESK_UPDATE_PUBLIC_KEY }}" jobs: generate-bridge: uses: ./.github/workflows/bridge.yml build-RustDeskTempTopMostWindow: uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml strategy: fail-fast: false matrix: job: - { target: windows-2022, platform: x64, } - { target: windows-11-arm, platform: ARM64, } with: upload-artifact: ${{ inputs.upload-artifact }} target: ${{ matrix.job.target }} configuration: Release platform: ${{ matrix.job.platform }} target_version: Windows10 build-for-windows-flutter: name: ${{ matrix.job.target }} needs: [build-RustDeskTempTopMostWindow, generate-bridge] runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: # - { target: i686-pc-windows-msvc , os: windows-2022 } # - { target: x86_64-pc-windows-gnu , os: windows-2022 } - { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64, flutter-arch: x64, vcpkg-triplet: x64-windows-static, build-args: "--vram", } - { target: aarch64-pc-windows-msvc, os: windows-11-arm, arch: aarch64, flutter-arch: arm64, vcpkg-triplet: arm64-windows-static, # vram is x86/x64-only (NVENC needs CUDA, Intel MediaSDK needs __rdtsc); # no NV/Intel/AMD hardware exists on Windows-on-ARM, so vram stays disabled here. build-args: "", } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Install LLVM and Clang uses: KyleMayes/install-llvm-action@ebc0426251bc40c7cd31162802432c68818ab8f0 # v2.0.9 with: version: ${{ env.LLVM_VERSION }} - name: Install flutter id: flutter # subosito only ships an x64 Windows SDK (Flutter's release manifest lists x64 only), # so it installs x64 on both arches. The arm64 runner re-bootstraps Dart to arm64 in # the next step. uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225 # v2.12.0; https://github.com/subosito/flutter-action/issues/277 with: channel: "stable" flutter-version: ${{ env.FLUTTER_VERSION }} architecture: x64 - name: Force arm64 Dart SDK + engine # The x64 SDK subosito installs bundles an x64 Dart with a matching engine-dart-sdk.stamp, # so update_dart_sdk.ps1 short-circuits (stamp matches -> return) and keeps x64 Dart; # `flutter build windows` then targets the Dart VM's arch = x64, even on this arm64 host. # On this native-arm64 runner (PROCESSOR_ARCHITECTURE=ARM64), deleting the stamp and # re-running update_dart_sdk.ps1 pulls the arm64 Dart (available since Flutter 3.44.0). # https://github.com/flutter/flutter/issues/186730#issuecomment-4573214964 if: ${{ matrix.job.arch == 'aarch64' }} run: | $flutterRoot = "${{ steps.flutter.outputs['CACHE-PATH'] }}" Write-Host "PROCESSOR_ARCHITECTURE=$env:PROCESSOR_ARCHITECTURE" Write-Host "Flutter root: $flutterRoot" Remove-Item -Force "$flutterRoot\bin\cache\engine-dart-sdk.stamp" -ErrorAction SilentlyContinue & "$flutterRoot\bin\internal\update_dart_sdk.ps1" # Confirm the Dart we ended up with is arm64 ("on windows_arm64"); fail loudly if not. $dartVer = & "$flutterRoot\bin\dart.bat" --version 2>&1 | Out-String Write-Host $dartVer if ($dartVer -notmatch "windows_arm64") { Write-Error "Expected an arm64 Dart SDK but got: $dartVer" exit 1 } & "$flutterRoot\bin\flutter.bat" precache --windows # Fail fast if precache pulled the wrong-arch Windows engine: an arm64 Dart should # fetch windows-arm64 engine artifacts. Bailing here saves the ~25min Rust build. $engineDir = "$flutterRoot\bin\cache\artifacts\engine" Write-Host "Engine artifacts present:" Get-ChildItem $engineDir -Directory | Select-Object -ExpandProperty Name | Write-Host if (-not (Test-Path "$engineDir\windows-arm64-release")) { Write-Error "Expected windows-arm64-release engine artifacts but they are missing (wrong-arch SDK)." exit 1 } - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.RUST_VERSION }} targets: ${{ matrix.job.target }} components: "rustfmt" - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: ${{ matrix.job.os }} - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgDirectory: C:\vcpkg vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies env: VCPKG_DEFAULT_HOST_TRIPLET: ${{ matrix.job.vcpkg-triplet }} run: | if ! $VCPKG_ROOT/vcpkg \ install \ --triplet ${{ matrix.job.vcpkg-triplet }} \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true shell: bash - name: Set SODIUM_LIB_DIR (arm64) # libsodium-sys ships no arm64 Windows prebuilt lib; point it at the vcpkg-built one # (only for arm64 — leaving it unset lets x64 use the crate's bundled lib). if: ${{ matrix.job.arch == 'aarch64' }} shell: bash run: echo "SODIUM_LIB_DIR=$VCPKG_ROOT/installed/${{ matrix.job.vcpkg-triplet }}/lib" >> "$GITHUB_ENV" - name: Build SubnetDesk run: | # Windows: build RustDesk # --hwcodec is shared by all Windows targets; per-target extras (e.g. --vram) come from the matrix python3 .\build.py --portable --flutter --skip-portable-pack --hwcodec ${{ matrix.job.build-args }} mv ./flutter/build/windows/${{ matrix.job.flutter-arch }}/runner/Release ./rustdesk Rename-Item ./rustdesk/rustdesk.exe SubnetDesk.exe # Download usbmmidd_v2.zip and extract it to ./rustdesk Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip Expand-Archive usbmmidd_v2.zip -DestinationPath . Remove-Item -Path usbmmidd_v2\Win32 -Recurse Remove-Item -Path "usbmmidd_v2\deviceinstaller64.exe", "usbmmidd_v2\deviceinstaller.exe", "usbmmidd_v2\usbmmidd.bat" mv -Force .\usbmmidd_v2 ./rustdesk - name: find Runner.res # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res # Runner.rc does not contain actual version, but Runner.res does continue-on-error: true shell: bash run: | runner_res=$(find . -name "Runner.res"); if [ "$runner_res" == "" ]; then echo "Runner.res: not found"; else echo "Runner.res: $runner_res"; cp $runner_res ./libs/portable/Runner.res; echo "list ./libs/portable/Runner.res"; ls -l ./libs/portable/Runner.res; fi - name: Download RustDeskTempTopMostWindow artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: ${{ inputs.upload-artifact }} with: name: ${{ matrix.job.arch == 'aarch64' && 'topmostwindow-artifacts-ARM64' || 'topmostwindow-artifacts-x64' }} path: "./rustdesk" - name: Upload unsigned if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-unsigned-windows-${{ matrix.job.arch }} path: rustdesk - name: Sign SubnetDesk files if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' shell: bash run: | pip3 install requests argparse BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./rustdesk/ - name: Build self-extracted executable shell: bash if: env.UPLOAD_ARTIFACT == 'true' run: | sed -i '/dpiAware/d' res/manifest.xml pushd ./libs/portable pip3 install -r requirements.txt python3 ./generate.py -f ../../rustdesk/ -o . -e ../../rustdesk/SubnetDesk.exe popd mkdir -p ./SignOutput mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.exe - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 - name: Build msi # Builds the MSI for the matrix arch. res/msi (WiX v4 + native CustomActions) carries # both x64 and ARM64 platform configs; WcaUtil/DUtil ship arm64 libs. msbuild platform # is x64 / ARM64; the produced Package.msi is globbed since its bin// dir varies. if: env.UPLOAD_ARTIFACT == 'true' run: | pushd ./res/msi python preprocess.py --arp --app-name SubnetDesk --manufacturer SubnetDesk -d ../../rustdesk if ($LASTEXITCODE -ne 0) { throw "MSI preprocessing failed with exit code $LASTEXITCODE" } nuget restore msi.sln $msiPlatform = if ('${{ matrix.job.arch }}' -eq 'aarch64') { 'ARM64' } else { 'x64' } msbuild msi.sln -p:Configuration=Release -p:Platform=$msiPlatform /p:TargetVersion=Windows10 $msi = Get-ChildItem ./Package/bin/*/Release/en-us/Package.msi | Select-Object -First 1 mv $msi.FullName ../../SignOutput/subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi sha256sum ../../SignOutput/subnetdesk-*.msi - name: Sign SubnetDesk self-extracted file if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' shell: bash run: | BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput - name: Upload Windows installers if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-windows-${{ matrix.job.arch }} path: | ./SignOutput/subnetdesk-*.msi ./SignOutput/subnetdesk-*.exe - name: Publish Release uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 if: env.PUBLISH_RELEASE == 'true' with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ./SignOutput/subnetdesk-*.msi ./SignOutput/subnetdesk-*.exe # The fallback for the flutter version, we use Sciter for 32bit Windows. build-for-windows-sciter: if: ${{ false }} # Legacy UI is not a LAN-only release target. name: ${{ matrix.job.target }} (${{ matrix.job.os }}) runs-on: ${{ matrix.job.os }} # Temporarily disable this action due to additional test is needed. # if: false strategy: fail-fast: false matrix: job: # - { target: i686-pc-windows-msvc , os: windows-2022 } # - { target: x86_64-pc-windows-gnu , os: windows-2022 } - { target: i686-pc-windows-msvc, os: windows-2022, arch: x86, vcpkg-triplet: x86-windows-static, } # - { target: aarch64-pc-windows-msvc, os: windows-2022 } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install LLVM and Clang uses: rustdesk-org/install-llvm-action-32bit@6aa7d9ad3df84dff01cd4596dd0fc880a7f47fce # no release tag; commit 2026-05-26 with: version: ${{ env.LLVM_VERSION }} - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: nightly-2023-10-13-${{ matrix.job.target }} # must use nightly here, because of abi_thiscall feature required targets: ${{ matrix.job.target }} components: "rustfmt" - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: ${{ matrix.job.os }}-sciter - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgDirectory: C:\vcpkg vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies env: VCPKG_DEFAULT_HOST_TRIPLET: ${{ matrix.job.vcpkg-triplet }} run: | if ! $VCPKG_ROOT/vcpkg \ install \ --triplet ${{ matrix.job.vcpkg-triplet }} \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true shell: bash - name: Build SubnetDesk id: build shell: bash run: | python3 res/inline-sciter.py # Patch sciter x86 sed -i 's/branch = "dyn"/branch = "dyn_x86"/g' ./Cargo.toml cargo update -p sciter-rs --precise 674e07d3066ca9a92ced3816203ab6b652629d1e cargo build --locked --features inline,vram,hwcodec --release --bins mkdir -p ./Release mv ./target/release/rustdesk.exe ./Release/rustdesk.exe curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll echo "output_folder=./Release" >> $GITHUB_OUTPUT curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip unzip usbmmidd_v2.zip # Do not remove x64 files, because the user may run the 32bit version on a 64bit system. # Do not remove ./usbmmidd_v2/deviceinstaller64.exe, as x86 exe cannot install and uninstall drivers when running on x64, # we need the x64 exe to install and uninstall the driver. rm -rf ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/usbmmidd.bat mv ./usbmmidd_v2 ./Release || true - name: find Runner.res # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res # Runner.rc does not contain actual version, but Runner.res does continue-on-error: true shell: bash run: | runner_res=$(find . -name "Runner.res"); if [ "$runner_res" == "" ]; then echo "Runner.res: not found"; else echo "Runner.res: $runner_res"; cp $runner_res ./libs/portable/Runner.res; echo "list ./libs/portable/Runner.res"; ls -l ./libs/portable/Runner.res; fi - name: Upload unsigned if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-unsigned-windows-${{ matrix.job.arch }} path: Release - name: Sign SubnetDesk files if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' shell: bash run: | pip3 install requests argparse BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./Release/ - name: Build self-extracted executable shell: bash run: | sed -i '/dpiAware/d' res/manifest.xml pushd ./libs/portable pip3 install -r requirements.txt python3 ./generate.py -f ../../Release/ -o . -e ../../Release/rustdesk.exe popd mkdir -p ./SignOutput mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.exe - name: Sign SubnetDesk self-extracted file if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' shell: bash run: | BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput/ - name: Publish Release uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 if: env.PUBLISH_RELEASE == 'true' with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ./SignOutput/subnetdesk-*.exe build-rustdesk-ios: if: ${{ inputs.upload-artifact && inputs.build-mobile }} name: Build SubnetDesk ios ipa runs-on: ${{ matrix.job.os }} needs: [generate-bridge] strategy: fail-fast: false matrix: job: - { arch: aarch64, target: aarch64-apple-ios, os: macos-latest, vcpkg-triplet: arm64-ios, } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Install dependencies run: | brew install nasm yasm - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install flutter uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: ${{ env.FLUTTER_VERSION }} - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies run: | if ! $VCPKG_ROOT/vcpkg \ install \ --triplet ${{ matrix.job.vcpkg-triplet }} \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true shell: bash - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.RUST_VERSION }} targets: ${{ matrix.job.target }} components: "rustfmt" - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rustdesk-lib-cache-ios key: ${{ matrix.job.target }} - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Build SubnetDesk lib run: | rustup target add ${{ matrix.job.target }} cargo build --locked --features flutter,hwcodec --release --target aarch64-apple-ios --lib - name: Upload liblibrustdesk.a Artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: liblibrustdesk.a path: target/aarch64-apple-ios/release/liblibrustdesk.a - name: Build SubnetDesk shell: bash run: | pushd flutter # flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info --no-codesign # for easy debugging flutter build ipa --release --no-codesign # - name: Upload Artifacts # # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' # uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # with: # name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk # path: flutter/build/ios/ipa/*.ipa # - name: Publish ipa package # # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' # uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 # with: # prerelease: true # tag_name: ${{ env.TAG_NAME }} # files: | # flutter/build/ios/ipa/*.ipa build-for-macOS: name: ${{ matrix.job.target }} runs-on: ${{ matrix.job.os }} needs: [generate-bridge] strategy: fail-fast: false matrix: job: - { target: x86_64-apple-darwin, os: macos-15-intel, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel extra-build-args: "", arch: x86_64, vcpkg-triplet: x64-osx, } - { target: aarch64-apple-darwin, os: macos-14, # extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296 extra-build-args: "--screencapturekit", arch: aarch64, vcpkg-triplet: arm64-osx, } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Import the codesign cert if: env.MACOS_P12_BASE64 != '' uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 # v1 with: p12-file-base64: ${{ secrets.MACOS_P12_BASE64 }} p12-password: ${{ secrets.MACOS_P12_PASSWORD }} keychain: rustdesk - name: Check sign and import sign key if: env.MACOS_P12_BASE64 != '' run: | security default-keychain -s rustdesk.keychain security find-identity -v - name: Import notarize key if: env.MACOS_P12_BASE64 != '' uses: timheuer/base64-to-file@adaa40c0c581f276132199d4cf60afa07ce60eac # v1.2 with: # https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_rcodesign.html#notarizing-and-stapling fileName: rustdesk.json fileDir: ${{ github.workspace }} encodedString: ${{ secrets.MACOS_NOTARIZE_JSON }} - name: Install rcodesign tool if: env.MACOS_P12_BASE64 != '' shell: bash run: | pushd /tmp wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-macos-universal.tar.gz tar -zxvf apple-codesign-0.22.0-macos-universal.tar.gz mv apple-codesign-0.22.0-macos-universal/rcodesign /usr/local/bin popd - name: Install build runtime run: | brew install llvm create-dmg # pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner if command -v pkg-config &>/dev/null; then echo "pkg-config is already installed" else brew install pkg-config fi - name: Install NASM run: | # Install NASM 2.16.x from official release. # Do NOT use `brew install nasm` which installs NASM 3.x. # NASM 3.x is a complete rewrite with incompatible CLI options and removed features. # aom and other multimedia libraries require NASM 2.x for x86/x86_64 assembly. wget https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/macosx/nasm-2.16.03-macosx.zip unzip nasm-2.16.03-macosx.zip sudo cp nasm-2.16.03/nasm /usr/local/bin/nasm nasm --version - name: Install flutter uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: ${{ env.FLUTTER_VERSION }} - name: Workaround for flutter issue shell: bash run: | cd "$(dirname "$(which flutter)")" # https://github.com/flutter/flutter/issues/133533 sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.MAC_RUST_VERSION }} targets: ${{ matrix.job.target }} components: "rustfmt" - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: ${{ matrix.job.os }} - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies run: | if ! $VCPKG_ROOT/vcpkg \ install \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - name: Show version information (Rust, cargo, Clang) shell: bash run: | clang --version || true rustup -V rustup toolchain list rustup default cargo -V rustc -V - name: Build SubnetDesk run: | if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then MIN_MACOS_VERSION="12.3" sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj fi ./build.py --flutter --hwcodec --unix-file-copy-paste ${{ matrix.job.extra-build-args }} - name: create unsigned dmg if: env.UPLOAD_ARTIFACT == 'true' run: | CREATE_DMG="$(command -v create-dmg)" CREATE_DMG="$(readlink -f "$CREATE_DMG")" sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" create-dmg --icon "SubnetDesk.app" 200 190 --hide-extension "SubnetDesk.app" --window-size 800 400 --app-drop-link 600 185 subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/SubnetDesk.app - name: Upload unsigned macOS app if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-unsigned-macos-${{ matrix.job.arch }} path: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg # can not upload the directory directly or tar.gz, which destroy the link structure, causing the codesign failed - name: Codesign app and create signed dmg if: env.MACOS_P12_BASE64 != '' && env.UPLOAD_ARTIFACT == 'true' run: | # Patch create-dmg to give more attempts to unmount image CREATE_DMG="$(command -v create-dmg)" CREATE_DMG="$(readlink -f "$CREATE_DMG")" sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" # Unlock keychain security default-keychain -s rustdesk.keychain security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain # start sign the rustdesk.app and dmg rm -rf *.dmg || true codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict --entitlements ./flutter/macos/Runner/Release.entitlements ./flutter/build/macos/Build/Products/Release/SubnetDesk.app -vvv create-dmg --icon "SubnetDesk.app" 200 190 --hide-extension "SubnetDesk.app" --window-size 800 400 --app-drop-link 600 185 subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/SubnetDesk.app codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg -vvv # notarize the architecture-specific SubnetDesk DMG rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg - name: Upload macOS installer if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-macos-${{ matrix.job.arch }} path: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg - name: Publish DMG package if: env.PUBLISH_RELEASE == 'true' env: GH_TOKEN: ${{ github.token }} run: bash scripts/upload_release_asset_with_retry.sh "$TAG_NAME" "subnetdesk-${VERSION}-${{ matrix.job.arch }}.dmg" publish_unsigned: needs: - build-for-macOS - build-for-windows-flutter runs-on: ubuntu-latest if: ${{ inputs.publish-release }} steps: - name: Download artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-unsigned-macos-x86_64 path: ./ - name: Download Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-unsigned-macos-aarch64 path: ./ - name: Download Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-unsigned-windows-x86_64 path: ./windows-x86_64/ - name: Download Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-unsigned-windows-aarch64 path: ./windows-aarch64/ - name: Combine unsigned app run: | tar czf subnetdesk-${{ env.VERSION }}-unsigned.tar.gz *.dmg windows-x86_64 windows-aarch64 - name: Publish unsigned app uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: subnetdesk-${{ env.VERSION }}-unsigned.tar.gz build-rustdesk-android: if: ${{ inputs.build-mobile }} needs: [generate-bridge] name: Build SubnetDesk android apk ${{ matrix.job.target }} runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - { arch: aarch64, target: aarch64-linux-android, os: ubuntu-24.04, reltype: release, suffix: "", } - { arch: armv7, target: armv7-linux-androideabi, os: ubuntu-24.04, reltype: release, suffix: "", } - { arch: x86_64, target: x86_64-linux-android, os: ubuntu-24.04, reltype: release, suffix: "", } steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: tool-cache: false android: false dotnet: true haskell: true large-packages: false docker-images: true swap-storage: false - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y \ clang \ cmake \ curl \ gcc-multilib \ git \ g++ \ g++-multilib \ libayatana-appindicator3-dev \ libasound2-dev \ libc6-dev \ libclang-dev \ libunwind-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libpam0g-dev \ libpulse-dev \ libva-dev \ libxcb-randr0-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxdo-dev \ libxfixes-dev \ llvm-dev \ nasm \ ninja-build \ openjdk-17-jdk-headless \ pkg-config \ tree \ wget - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install flutter uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }} - uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1 id: setup-ndk with: ndk-version: ${{ env.NDK_VERSION }} add-to-path: true - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgDirectory: /opt/artifacts/vcpkg vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies run: | case ${{ matrix.job.target }} in aarch64-linux-android) ANDROID_TARGET=arm64-v8a ;; armv7-linux-androideabi) ANDROID_TARGET=armeabi-v7a ;; x86_64-linux-android) ANDROID_TARGET=x86_64 ;; i686-linux-android) ANDROID_TARGET=x86 ;; esac if ! ./flutter/build_android_deps.sh "${ANDROID_TARGET}"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi shell: bash - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.RUST_VERSION }} components: "rustfmt" - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rustdesk-lib-cache-android # TODO: drop '-android' part after caches are invalidated key: ${{ matrix.job.target }} - name: Build SubnetDesk lib env: ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} run: | rustup target add ${{ matrix.job.target }} cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked case ${{ matrix.job.target }} in aarch64-linux-android) ./flutter/ndk_arm64.sh mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so ;; armv7-linux-androideabi) ./flutter/ndk_arm.sh mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so ;; x86_64-linux-android) ./flutter/ndk_x64.sh mkdir -p ./flutter/android/app/src/main/jniLibs/x86_64 cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so ;; i686-linux-android) ./flutter/ndk_x86.sh mkdir -p ./flutter/android/app/src/main/jniLibs/x86 cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so ;; esac - name: Upload Rustdesk library to Artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: librustdesk.so.${{ matrix.job.target }} path: ./target/${{ matrix.job.target }}/release/liblibrustdesk.so - name: Build SubnetDesk shell: bash env: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 run: | export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH # Increase Gradle JVM memory for CI builds sed -i "s/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g" ./flutter/android/gradle.properties # Use a temporary build signature; the APK is re-signed and fingerprint-verified below. sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle case ${{ matrix.job.target }} in aarch64-linux-android) mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/ cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so # build flutter pushd flutter flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-arm64 --split-per-abi mv build/app/outputs/flutter-apk/app-arm64-v8a-${{ matrix.job.reltype }}.apk ../subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk ;; armv7-linux-androideabi) mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/ cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so # build flutter pushd flutter flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-arm --split-per-abi mv build/app/outputs/flutter-apk/app-armeabi-v7a-${{ matrix.job.reltype }}.apk ../subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk ;; x86_64-linux-android) mkdir -p ./flutter/android/app/src/main/jniLibs/x86_64 cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86_64/ cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so # build flutter pushd flutter flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-x64 --split-per-abi mv build/app/outputs/flutter-apk/app-x86_64-${{ matrix.job.reltype }}.apk ../subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk ;; i686-linux-android) mkdir -p ./flutter/android/app/src/main/jniLibs/x86 cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86/ cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so # build flutter pushd flutter flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-x86 --split-per-abi mv build/app/outputs/flutter-apk/app-x86-${{ matrix.job.reltype }}.apk ../subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk ;; esac popd mkdir -p signed-apk; pushd signed-apk mv ../subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk . # https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075 - name: Setup sign tool version variable shell: bash run: | BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1) echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV echo Last build tool version is: $BUILD_TOOL_VERSION - name: Require fixed Android signing identity for releases if: env.PUBLISH_RELEASE == 'true' shell: bash env: ANDROID_ALIAS: ${{ secrets.ANDROID_ALIAS }} ANDROID_KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | for value in \ "$ANDROID_SIGNING_KEY" \ "$ANDROID_ALIAS" \ "$ANDROID_KEY_STORE_PASSWORD" \ "$ANDROID_KEY_PASSWORD" \ "$ANDROID_SIGNING_CERT_SHA256" do if [ -z "$value" ]; then echo "Android release signing secrets are incomplete; refusing to publish." >&2 exit 1 fi done - uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1 name: Sign app APK if: env.ANDROID_SIGNING_KEY != '' id: sign-rustdesk with: releaseDirectory: ./signed-apk signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} env: # env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable" BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }} - name: Verify fixed Android signing certificate if: env.ANDROID_SIGNING_KEY != '' shell: bash env: SIGNED_APK: ${{ steps.sign-rustdesk.outputs.signedReleaseFile }} run: | expected=$(printf '%s' "$ANDROID_SIGNING_CERT_SHA256" | tr '[:lower:]' '[:upper:]' | tr -d ': ') if ! printf '%s' "$expected" | grep -Eq '^[0-9A-F]{64}$'; then echo "ANDROID_SIGNING_CERT_SHA256 must contain one SHA-256 certificate fingerprint." >&2 exit 1 fi actual=$("$ANDROID_HOME/build-tools/$ANDROID_SIGN_TOOL_VERSION/apksigner" verify --print-certs "$SIGNED_APK" \ | bash scripts/extract_android_cert_sha256.sh \ | tr '[:lower:]' '[:upper:]' \ | tr -d ': ') if [ "$actual" != "$expected" ]; then echo "Android signing certificate does not match the pinned fingerprint." >&2 echo "Expected: $expected" >&2 echo "Actual: $actual" >&2 exit 1 fi - name: Upload Artifacts if: env.ANDROID_SIGNING_KEY != '' && env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk path: ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - name: Publish signed apk package if: env.ANDROID_SIGNING_KEY != '' && env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ${{steps.sign-rustdesk.outputs.signedReleaseFile}} build-rustdesk-android-universal: needs: [build-rustdesk-android] name: Build SubnetDesk android universal apk if: ${{ inputs.upload-artifact && inputs.build-mobile }} runs-on: ubuntu-24.04 env: reltype: release x86_target: "" # can be ",android-x86" suffix: "" steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: tool-cache: false android: false dotnet: true haskell: true large-packages: false docker-images: true swap-storage: false - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y \ clang \ cmake \ curl \ gcc-multilib \ git \ g++ \ g++-multilib \ libayatana-appindicator3-dev \ libasound2-dev \ libc6-dev \ libclang-dev \ libunwind-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libpam0g-dev \ libpulse-dev \ libva-dev \ libxcb-randr0-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxdo-dev \ libxfixes-dev \ llvm-dev \ nasm \ ninja-build \ openjdk-17-jdk-headless \ pkg-config \ tree \ wget - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install flutter uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2 with: channel: "stable" flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }} - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Download Rustdesk library from Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: librustdesk.so.aarch64-linux-android path: ./flutter/android/app/src/main/jniLibs/arm64-v8a - name: Download Rustdesk library from Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: librustdesk.so.armv7-linux-androideabi path: ./flutter/android/app/src/main/jniLibs/armeabi-v7a - name: Download Rustdesk library from Artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: librustdesk.so.x86_64-linux-android path: ./flutter/android/app/src/main/jniLibs/x86_64 - name: Download Rustdesk library from Artifacts if: ${{ env.reltype == 'debug' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: librustdesk.so.i686-linux-android path: ./flutter/android/app/src/main/jniLibs/x86 - name: Build SubnetDesk shell: bash env: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 run: | export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH # Increase Gradle JVM memory for CI builds sed -i "s/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g" ./flutter/android/gradle.properties # Use a temporary build signature; the APK is re-signed and fingerprint-verified below. sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle mv ./flutter/android/app/src/main/jniLibs/arm64-v8a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/ mv ./flutter/android/app/src/main/jniLibs/armeabi-v7a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/ mv ./flutter/android/app/src/main/jniLibs/x86_64/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86_64/ if [ "${{ env.reltype }}" = "debug" ]; then mv ./flutter/android/app/src/main/jniLibs/x86/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86/ fi # build flutter pushd flutter flutter build apk "--${{ env.reltype }}" --target-platform android-arm64,android-arm,android-x64${{ env.x86_target }} popd mkdir -p signed-apk mv ./flutter/build/app/outputs/flutter-apk/app-${{ env.reltype }}.apk signed-apk/subnetdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk # https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075 - name: Setup sign tool version variable shell: bash run: | BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1) echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV echo Last build tool version is: $BUILD_TOOL_VERSION - name: Require fixed Android signing identity for releases if: env.PUBLISH_RELEASE == 'true' shell: bash env: ANDROID_ALIAS: ${{ secrets.ANDROID_ALIAS }} ANDROID_KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | for value in \ "$ANDROID_SIGNING_KEY" \ "$ANDROID_ALIAS" \ "$ANDROID_KEY_STORE_PASSWORD" \ "$ANDROID_KEY_PASSWORD" \ "$ANDROID_SIGNING_CERT_SHA256" do if [ -z "$value" ]; then echo "Android release signing secrets are incomplete; refusing to publish." >&2 exit 1 fi done - uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1 name: Sign app APK if: env.ANDROID_SIGNING_KEY != '' id: sign-rustdesk with: releaseDirectory: ./signed-apk signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} alias: ${{ secrets.ANDROID_ALIAS }} keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} env: # env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable" BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }} - name: Verify fixed Android signing certificate if: env.ANDROID_SIGNING_KEY != '' shell: bash env: SIGNED_APK: ${{ steps.sign-rustdesk.outputs.signedReleaseFile }} run: | expected=$(printf '%s' "$ANDROID_SIGNING_CERT_SHA256" | tr '[:lower:]' '[:upper:]' | tr -d ': ') if ! printf '%s' "$expected" | grep -Eq '^[0-9A-F]{64}$'; then echo "ANDROID_SIGNING_CERT_SHA256 must contain one SHA-256 certificate fingerprint." >&2 exit 1 fi actual=$("$ANDROID_HOME/build-tools/$ANDROID_SIGN_TOOL_VERSION/apksigner" verify --print-certs "$SIGNED_APK" \ | bash scripts/extract_android_cert_sha256.sh \ | tr '[:lower:]' '[:upper:]' \ | tr -d ': ') if [ "$actual" != "$expected" ]; then echo "Android signing certificate does not match the pinned fingerprint." >&2 echo "Expected: $expected" >&2 echo "Actual: $actual" >&2 exit 1 fi - name: Upload Artifacts if: env.ANDROID_SIGNING_KEY != '' && env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk path: ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - name: Publish signed apk package if: env.ANDROID_SIGNING_KEY != '' && env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ${{steps.sign-rustdesk.outputs.signedReleaseFile}} build-rustdesk-linux: needs: [generate-bridge] name: Build SubnetDesk linux ${{ matrix.job.target }} runs-on: ${{ matrix.job.on }} strategy: fail-fast: false matrix: # use a high level qemu-user-static job: - { arch: x86_64, target: x86_64-unknown-linux-gnu, distro: ubuntu18.04, on: ubuntu-22.04, deb_arch: amd64, vcpkg-triplet: x64-linux, } - { arch: aarch64, target: aarch64-unknown-linux-gnu, distro: ubuntu18.04, on: ubuntu-22.04-arm, deb_arch: arm64, vcpkg-triplet: arm64-linux, } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Maximize build space run: | sudo rm -rf /opt/ghc sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/share/dotnet sudo apt-get update -y sudo apt-get install -y nasm if [[ "${{ matrix.job.arch }}" == "x86_64" ]]; then sudo apt-get install -y qemu-user-static fi - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Set Swap Space if: ${{ matrix.job.arch == 'x86_64' }} uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0 with: swap-size-gb: 12 - name: Free Space run: | df -h free -m - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' with: toolchain: ${{ env.RUST_VERSION }} targets: ${{ matrix.job.target }} components: "rustfmt" - name: Save Rust toolchain version run: | RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}') echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV - name: Disable rust bridge build run: | # only build cdylib sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml - name: Restore bridge files if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Setup vcpkg with Github Actions binary cache if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgDirectory: /opt/artifacts/vcpkg vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' run: | sudo apt install -y libva-dev && apt show libva-dev if ! $VCPKG_ROOT/vcpkg \ install \ --triplet ${{ matrix.job.vcpkg-triplet }} \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true shell: bash - name: Restore bridge files if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26 name: Build SubnetDesk id: vcpkg if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' with: arch: ${{ matrix.job.arch }} distro: ${{ matrix.job.distro }} githubToken: ${{ github.token }} setup: | ls -l "${PWD}" ls -l /opt/artifacts/vcpkg/installed dockerRunArgs: | --volume "${PWD}:/workspace" --volume "/opt/artifacts:/opt/artifacts" shell: /bin/bash install: | apt-get update -y echo -e "installing deps" apt-get install -y \ build-essential \ clang \ cmake \ curl \ gcc \ git \ g++ \ libayatana-appindicator3-dev \ libasound2-dev \ libclang-10-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libpam0g-dev \ libpulse-dev \ libva-dev \ libxcb-randr0-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxdo-dev \ libxfixes-dev \ llvm-10-dev \ nasm \ ninja-build \ pkg-config \ tree \ python3 \ rpm \ unzip \ wget \ xz-utils \ libssl-dev # we have libopus compiled by us. apt-get remove -y libopus-dev || true # output devs ls -l ./ tree -L 3 /opt/artifacts/vcpkg/installed run: | # disable git safe.directory git config --global --add safe.directory "*" # rust pushd /opt # do not use rustup, because memory overflow in qemu wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }}.tar.gz tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz cd rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} && ./install.sh rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} # edit config mkdir -p ~/.cargo/ echo """ [source.crates-io] registry = 'https://github.com/rust-lang/crates.io-index' """ > ~/.cargo/config cat ~/.cargo/config # start build pushd /workspace export VCPKG_ROOT=/opt/artifacts/vcpkg if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then export JOBS="--jobs 3" else export JOBS="" fi echo $JOBS cargo build --locked --lib $JOBS --features hwcodec,flutter,unix-file-copy-paste --release rm -rf target/release/deps target/release/build rm -rf ~/.cargo # Setup Flutter # disable git safe.directory git config --global --add safe.directory "*" pushd /workspace case ${{ matrix.job.arch }} in aarch64) export PATH=/opt/flutter/bin:$PATH sed -i "s/flutter build linux --release/flutter build linux --release --target-platform linux-arm64/g" ./build.py sed -i "s/x64\/release/arm64\/release/g" ./build.py ;; x86_64) export PATH=/opt/flutter/bin:$PATH ;; esac popd pushd /opt wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz tar xf flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then rm -rf flutter/bin/cache/dart-sdk wget -O dart-sdk.zip https://storage.googleapis.com/dart-archive/channels/stable/release/${{ env.DART_VERSION }}/sdk/dartsdk-linux-arm64-release.zip unzip -q dart-sdk.zip -d flutter/bin/cache rm -f flutter/bin/cache/flutter_tools.snapshot flutter/bin/cache/flutter_tools.stamp flutter/bin/flutter precache --linux --force fi flutter doctor -v # build flutter pushd /workspace export CARGO_INCREMENTAL=0 export DEB_ARCH=${{ matrix.job.deb_arch }} python3 ./build.py --flutter --skip-cargo for name in subnetdesk*??.deb; do mv "$name" "${name%%.deb}-${{ matrix.job.arch }}.deb" done # rpm package echo -e "start packaging fedora package" pushd /workspace case ${{ matrix.job.arch }} in aarch64) sed -i "s/linux\/x64/linux\/arm64/g" ./res/rpm-flutter.spec ;; esac HBB=`pwd` rpmbuild ./res/rpm-flutter.spec -bb pushd ~/rpmbuild/RPMS/${{ matrix.job.arch }} for name in subnetdesk-*.rpm; do mv "$name" /workspace/"$name" done # rpm suse package echo -e "start packaging suse package" pushd /workspace case ${{ matrix.job.arch }} in aarch64) sed -i "s/linux\/x64/linux\/arm64/g" ./res/rpm-flutter-suse.spec ;; esac HBB=`pwd` rpmbuild ./res/rpm-flutter-suse.spec -bb pushd ~/rpmbuild/RPMS/${{ matrix.job.arch }} for name in subnetdesk-*.rpm; do base="${name#subnetdesk-}" mv "$name" "/workspace/subnetdesk-${base%%.rpm}-suse.rpm" done - name: Publish debian/rpm package if: env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | subnetdesk-*.deb subnetdesk-*.rpm - name: Upload deb uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: env.UPLOAD_ARTIFACT == 'true' with: name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb path: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb - name: Upload rpm packages uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: env.UPLOAD_ARTIFACT == 'true' with: name: subnetdesk-linux-rpm-${{ matrix.job.arch }} path: subnetdesk-*.rpm # only x86_64 for arch since we can not find newest arm64 docker image to build # old arch image does not make sense for arch since it is "arch" which always update to date # and failed to makepkg arm64 on x86_64 - name: Patch archlinux PKGBUILD if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' run: | sed -i "s/x86_64/${{ matrix.job.arch }}/g" res/PKGBUILD if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then sed -i "s/x86_64/aarch64/g" ./res/PKGBUILD fi - name: Build archlinux package if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' uses: rustdesk-org/arch-makepkg-action@04200739ed1d0bf6f2188b6736b26a767c57a7f9 # no release tag; commit 2026-05-26 with: packages: scripts: >- cd res && HBB="$(pwd)/.." FLUTTER=1 makepkg -f - name: Upload Arch Linux package if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-archlinux-x86_64 path: res/subnetdesk-${{ env.VERSION }}*.zst - name: Publish archlinux package if: matrix.job.arch == 'x86_64' && env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | res/subnetdesk-${{ env.VERSION }}*.zst # Same build as build-rustdesk-linux x86_64 -- same vcpkg/ffmpeg, same ubuntu18.04 container, same # rust and flutter -- only with the drm feature on, so it ships as the separate # subnetdesk-unattended-wayland deb. libdrmtap is built on the runner because bionic's meson is too # old for it. A separate job rather than a matrix entry of build-rustdesk-linux: appimage and # flatpak need that job, and a failure here must not skip them. build-rustdesk-linux-drm: needs: [generate-bridge] name: build subnetdesk linux drm x86_64 runs-on: ubuntu-22.04 steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Maximize build space run: | sudo rm -rf /opt/ghc sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/share/dotnet sudo apt-get update -y sudo apt-get install -y nasm sudo apt-get install -y qemu-user-static - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Set Swap Space uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0 with: swap-size-gb: 12 - name: Free Space run: | df -h free -m - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.RUST_VERSION }} targets: x86_64-unknown-linux-gnu components: "rustfmt" - name: Save Rust toolchain version run: | RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}') echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV - name: Disable rust bridge build run: | # only build cdylib sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml - name: Restore bridge files uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: bridge-artifact path: ./ - name: Setup vcpkg with Github Actions binary cache uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11 with: vcpkgDirectory: /opt/artifacts/vcpkg vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} doNotCache: false - name: Install vcpkg dependencies run: | sudo apt install -y libva-dev && apt show libva-dev if ! $VCPKG_ROOT/vcpkg \ install \ --triplet x64-linux \ --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-x64-linux-rel-out.log" || true shell: bash # The container's meson is too old to build libdrmtap, so build it here from the pin in # build.py and hand the .so to the container below via DRMTAP_PREBUILT_DIR. - name: Build libdrmtap run: | sudo apt-get install -y meson ninja-build pkg-config \ libdrm-dev libegl1-mesa-dev libgles2-mesa-dev python3 - <<'PY' import importlib.util, sys spec = importlib.util.spec_from_file_location("b", "build.py") b = importlib.util.module_from_spec(spec) sys.argv = ["build.py"] spec.loader.exec_module(b) print(f"::notice::built {b.build_libdrmtap_so()}") PY shell: bash - uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26 name: Build subnetdesk id: vcpkg with: arch: x86_64 distro: ubuntu18.04 githubToken: ${{ github.token }} setup: | ls -l "${PWD}" ls -l /opt/artifacts/vcpkg/installed dockerRunArgs: | --volume "${PWD}:/workspace" --volume "/opt/artifacts:/opt/artifacts" shell: /bin/bash install: | apt-get update -y echo -e "installing deps" apt-get install -y \ build-essential \ clang \ cmake \ curl \ gcc \ git \ g++ \ libayatana-appindicator3-dev \ libasound2-dev \ libclang-10-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ libpam0g-dev \ libpulse-dev \ libva-dev \ libxcb-randr0-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxdo-dev \ libxfixes-dev \ llvm-10-dev \ nasm \ ninja-build \ pkg-config \ tree \ python3 \ rpm \ unzip \ wget \ xz-utils \ libssl-dev # we have libopus compiled by us. apt-get remove -y libopus-dev || true # output devs ls -l ./ tree -L 3 /opt/artifacts/vcpkg/installed run: | # disable git safe.directory git config --global --add safe.directory "*" # rust pushd /opt # do not use rustup, because memory overflow in qemu wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu.tar.gz tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz cd rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu && ./install.sh rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu # edit config mkdir -p ~/.cargo/ echo """ [source.crates-io] registry = 'https://github.com/rust-lang/crates.io-index' """ > ~/.cargo/config cat ~/.cargo/config # start build pushd /workspace export VCPKG_ROOT=/opt/artifacts/vcpkg # use the .so built on the runner; build.py checks it is the pinned checkout export DRMTAP_PREBUILT_DIR=/workspace/third_party/libdrmtap/build-pkg # ask build.py for the features so this line and the packaging line cannot drift FEATURES=$(python3 ./build.py --flutter --drm --hwcodec --unix-file-copy-paste --print-features) # an empty or error-shaped value would silently build a stock binary for want in drm drm-wake; do case ",$FEATURES," in *",$want,"*) ;; *) echo "::error::build.py returned no '$want' feature: $FEATURES"; exit 1 ;; esac done cargo build --locked --lib --features "$FEATURES" --release rm -rf target/release/deps target/release/build rm -rf ~/.cargo # Setup Flutter # disable git safe.directory git config --global --add safe.directory "*" pushd /workspace export PATH=/opt/flutter/bin:$PATH popd pushd /opt wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz tar xf flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz flutter doctor -v # build flutter pushd /workspace export CARGO_INCREMENTAL=0 export DEB_ARCH=amd64 python3 ./build.py --flutter --drm --hwcodec --unix-file-copy-paste --skip-cargo for name in subnetdesk*??.deb; do mv "$name" "${name%%.deb}-x86_64.deb" done # build.py can exit 0 on some inner failures, so check the artifact rather than the status. # The package name is the informed consent for consent-free capture, so a stock binary must # never ship under it: assert the bundled library AND the dlopen path in the binary. - name: Check the deb is a drm build run: | set -euo pipefail # Resolve by glob, not from env.VERSION: build.py names the deb from Cargo.toml, so a # hardcoded name fails with a bare exit 1 the first time those two drift. shopt -s nullglob debs=(subnetdesk-unattended-wayland-*-x86_64.deb) if [ "${#debs[@]}" -ne 1 ]; then echo "::error::expected one subnetdesk-unattended-wayland-*-x86_64.deb, found ${#debs[@]}: ${debs[*]-none}" exit 1 fi deb="${debs[0]}" echo "DRM_DEB=$deb" >> "$GITHUB_ENV" contents="$(dpkg -c "$deb")" if [[ ! "$contents" =~ usr/lib/rustdesk/libdrmtap\.so\.0\.[0-9]+\.[0-9]+ ]]; then echo "::error::$deb has no versioned libdrmtap.so.0.x.y" exit 1 fi if [[ "$contents" != *"usr/lib/rustdesk/libdrmtap.so.0 ->"* ]]; then echo "::error::$deb has no libdrmtap.so.0 soname symlink" exit 1 fi rm -rf /tmp/deb && dpkg-deb -R "$deb" /tmp/deb if ! grep -qF /usr/lib/rustdesk/libdrmtap.so.0 /tmp/deb/usr/share/subnetdesk/lib/librustdesk.so; then echo "::error::$deb was not built with the drm feature" exit 1 fi shell: bash - name: Publish debian package if: env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ${{ env.DRM_DEB }} # No UPLOAD_ARTIFACT gate: on a PR this is the only way to get at the deb that was just built. # always(), because a deb that failed the check above is the one most worth downloading. - name: Upload deb if: always() && env.DRM_DEB != '' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ env.DRM_DEB }} path: ${{ env.DRM_DEB }} build-rustdesk-linux-sciter: if: ${{ false }} # Legacy UI is not a LAN-only release target. runs-on: ${{ matrix.job.on }} name: build-rustdesk-linux-sciter ${{ matrix.job.target }} strategy: fail-fast: false matrix: # use a high level qemu-user-static job: - { arch: x86_64, target: x86_64-unknown-linux-gnu, on: ubuntu-22.04, distro: ubuntu18.04, deb_arch: amd64, sciter_arch: x64, vcpkg-triplet: x64-linux, extra_features: ",hwcodec,unix-file-copy-paste", } - { arch: armv7, target: armv7-unknown-linux-gnueabihf, on: ubuntu-22.04-arm, distro: ubuntu18.04-rustdesk, deb_arch: armhf, sciter_arch: arm32, vcpkg-triplet: arm-linux, extra_features: ",unix-file-copy-paste", } steps: - name: Export GitHub Actions cache environment variables uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Modify vcpkg.json for armv7 if: matrix.job.vcpkg-triplet == 'arm-linux' run: | # Replace the baseline in vcpkg.json with ARMV7_VCPKG_COMMIT_ID for armv7 builds sed -i 's/"baseline": ".*"/"baseline": "${{ env.ARMV7_VCPKG_COMMIT_ID }}"/' vcpkg.json echo "Modified vcpkg.json for armv7 build:" grep -A 2 -B 2 '"baseline"' vcpkg.json - name: Free Space run: | df -h free -m - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 with: toolchain: ${{ env.SCITER_RUST_VERSION }} targets: ${{ matrix.job.target }} components: "rustfmt" - name: Save Rust toolchain version run: | RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}') echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV - uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26 name: Build SubnetDesk sciter binary for ${{ matrix.job.arch }} id: vcpkg with: arch: ${{ matrix.job.arch }} distro: ${{ matrix.job.distro }} githubToken: ${{ github.token }} setup: | ls -l "${PWD}" dockerRunArgs: | --volume "${PWD}:/workspace" shell: /bin/bash install: | apt-get update apt-get install -y \ build-essential \ clang \ curl \ gcc \ git \ g++ \ libayatana-appindicator3-dev \ libasound2-dev \ libclang-dev \ libdbus-1-dev \ libglib2.0-dev \ libgstreamer1.0-dev \ libgstreamer-plugins-base1.0-dev \ libgtk-3-dev \ liblzma-dev \ libpam0g-dev \ libpulse-dev \ libva-dev \ libxcb-randr0-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxdo-dev \ libxfixes-dev \ ninja-build \ pkg-config \ python3 \ python3.7 \ rpm \ unzip \ wget \ xz-utils \ zip \ libssl-dev # arm-linux needs CMake and vcokg built from source as there # are no prebuilts available from Kitware and Microsoft if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then # install gcc/g++ 8 for vcpkg and OpenSSL headers for CMake apt-get install -y gcc-8 g++-8 # bootstrap CMake amd add it to PATH git clone --depth 1 https://github.com/kitware/cmake -b "v${{ env.SCITER_ARMV7_CMAKE_VERSION }}" /tmp/cmake pushd /tmp/cmake ./bootstrap --generator='Unix Makefiles' "--prefix=/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf" make -j1 install popd rm -rf /tmp/cmake export PATH="/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf/bin:$PATH" fi # bootstrap vcpkg and set VCPKG_ROOT export VCPKG_ROOT=/opt/artifacts/vcpkg mkdir -p /opt/artifacts pushd /opt/artifacts rm -rf vcpkg git clone https://github.com/microsoft/vcpkg pushd vcpkg # build vcpkg helper executable with gcc-8 for arm-linux but use prebuilt one on x64-linux if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then git reset --hard ${{ env.ARMV7_VCPKG_COMMIT_ID }} CC=/usr/bin/gcc-8 CXX=/usr/bin/g++-8 sh bootstrap-vcpkg.sh -disableMetrics else git reset --hard ${{ env.VCPKG_COMMIT_ID }} sh bootstrap-vcpkg.sh -disableMetrics fi popd popd # rust pushd /opt # do not use rustup, because memory overflow in qemu wget --output-document rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }}.tar.gz tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz pushd rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} ./install.sh popd rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} popd # install newer nasm for aom wget --output-document nasm.deb "http://ftp.us.debian.org/debian/pool/main/n/nasm/nasm_${{ env.SCITER_NASM_DEBVERSION }}_${{ matrix.job.deb_arch }}.deb" dpkg -i nasm.deb rm -f nasm.deb run: | # disable git safe.directory git config --global --add safe.directory "*" # set python3.7 as default python3 update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1 # add built CMake to PATH and set VCPKG_FORCE_SYSTEM_BINARIES Afor arm-linux if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then export PATH="/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf/bin:$PATH" export VCPKG_FORCE_SYSTEM_BINARIES=1 fi # edit cargo config mkdir -p ~/.cargo/ echo """ [source.crates-io] registry = 'https://github.com/rust-lang/crates.io-index' """ > ~/.cargo/config cat ~/.cargo/config # install dependencies from vcpkg export VCPKG_ROOT=/opt/artifacts/vcpkg # remove this when support higher version export USE_AOM_391=1 if ! $VCPKG_ROOT/vcpkg install --triplet ${{ matrix.job.vcpkg-triplet }} --x-install-root="$VCPKG_ROOT/installed"; then find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do echo "$_1:" echo "======" cat "$_1" echo "======" echo "" done exit 1 fi head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true # build rustdesk python3 ./res/inline-sciter.py export CARGO_INCREMENTAL=0 cargo build --locked --features inline${{ matrix.job.extra_features }} --release --bins --jobs 1 # make debian package mkdir -p ./Release mv ./target/release/rustdesk ./Release/rustdesk wget -O ./Release/libsciter-gtk.so https://github.com/c-smile/sciter-sdk/raw/master/bin.lnx/${{ matrix.job.sciter_arch }}/libsciter-gtk.so export DEB_ARCH=${{ matrix.job.deb_arch }} ./build.py --package ./Release - name: Rename SubnetDesk shell: bash run: | for name in subnetdesk-*.deb; do # use cp to duplicate deb files to fit other packages. cp "$name" "${name%%.deb}-${{ matrix.job.arch }}-sciter.deb" done - name: Publish debian package if: env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb - name: Upload deb uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: env.UPLOAD_ARTIFACT == 'true' with: name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb path: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb build-appimage: name: Build appimage ${{ matrix.job.target }} needs: [build-rustdesk-linux] runs-on: ubuntu-22.04 if: ${{ inputs.upload-artifact }} strategy: fail-fast: false matrix: job: - { target: x86_64-unknown-linux-gnu, arch: x86_64 } - { target: aarch64-unknown-linux-gnu, arch: aarch64 } steps: - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Download Binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb path: . - name: Rename Binary run: | mv subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb appimage/subnetdesk.deb - name: Build appimage package shell: bash run: | # install libarchive-tools for bsdtar command used in AppImageBuilder.yml sudo apt-get update -y # https://github.com/AppImage/AppImageKit/wiki/FUSE sudo apt-get install -y libarchive-tools libfuse2 # set-up appimage-builder # https://github.com/AppImage/AppImageKit/issues/1395 sudo pip3 install "setuptools_scm<10" sudo pip3 install git+https://github.com/rustdesk-org/appimage-builder.git # run appimage-builder pushd appimage sudo appimage-builder --skip-tests --recipe ./AppImageBuilder-${{ matrix.job.arch }}.yml sudo chown "$(id -u):$(id -g)" subnetdesk-*.AppImage - name: Publish appimage package if: env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | ./appimage/subnetdesk-${{ env.VERSION }}-*.AppImage - name: Upload AppImage package if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-appimage-${{ matrix.job.arch }} path: ./appimage/subnetdesk-${{ env.VERSION }}-*.AppImage build-flatpak: name: Build flatpak ${{ matrix.job.target }}${{ matrix.job.suffix }} needs: - build-rustdesk-linux runs-on: ${{ matrix.job.on }} if: ${{ inputs.upload-artifact }} strategy: fail-fast: false matrix: job: - { target: x86_64-unknown-linux-gnu, # https://github.com/ostreedev/ostree/commit/4bac96a8c817beda37448f9b8c662162bb619981 distro: ubuntu22.04, on: ubuntu-22.04, arch: x86_64, suffix: "", } - { target: aarch64-unknown-linux-gnu, # try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub" distro: ubuntu22.04, on: ubuntu-22.04-arm, arch: aarch64, suffix: "", } steps: - name: Checkout source code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Download Binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.deb path: . - name: Rename Binary run: | mv subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.deb flatpak/subnetdesk.deb - uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26 name: Build SubnetDesk flatpak package for ${{ matrix.job.arch }} id: flatpak with: arch: ${{ matrix.job.arch }} distro: ${{ matrix.job.distro }} githubToken: ${{ github.token }} setup: | ls -l "${PWD}" dockerRunArgs: | --volume "${PWD}:/workspace" shell: /bin/bash install: | apt-get update -y apt-get install -y git flatpak flatpak-builder run: | # disable git safe.directory git config --global --add safe.directory "*" pushd /workspace # flatpak deps flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo # package pushd flatpak git clone https://github.com/flathub/shared-modules.git --depth=1 flatpak-builder --user --install-deps-from=flathub -y --force-clean --repo=repo ./build ./rustdesk.json flatpak build-bundle ./repo subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak com.zibochen.SubnetDesk - name: Publish flatpak package if: env.PUBLISH_RELEASE == 'true' uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 with: prerelease: ${{ inputs.upload-tag == 'nightly' || contains(inputs.upload-tag, '-') }} generate_release_notes: true tag_name: ${{ env.TAG_NAME }} files: | flatpak/subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak - name: Upload flatpak package if: env.UPLOAD_ARTIFACT == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: subnetdesk-flatpak-${{ matrix.job.arch }}${{ matrix.job.suffix }} path: flatpak/subnetdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak publish-download-table: name: Organize release downloads by system and architecture if: ${{ always() && inputs.publish-release }} needs: - build-for-windows-flutter - build-rustdesk-ios - build-for-macOS - publish_unsigned - build-rustdesk-android - build-rustdesk-android-universal - build-rustdesk-linux - build-appimage - build-flatpak runs-on: ubuntu-24.04 steps: - name: Build the release download table uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 env: RELEASE_TAG: ${{ env.TAG_NAME }} with: script: | const tag = process.env.RELEASE_TAG; const { data: release } = await github.rest.repos.getReleaseByTag({ ...context.repo, tag, }); const assets = await github.paginate( github.rest.repos.listReleaseAssets, { ...context.repo, release_id: release.id, per_page: 100, }, ); const architecture = (name) => { const lower = name.toLowerCase(); if (lower.includes('universal')) return 'universal'; if (/(^|[-_.])(aarch64|arm64)([-_.]|$)/.test(lower)) return 'arm64'; if (/(^|[-_.])armv7([-_.]|$)/.test(lower)) return 'armv7'; if (/(^|[-_.])(x86_64|amd64)([-_.]|$)/.test(lower)) return 'x86_64'; return 'other'; }; const platform = (name) => { const lower = name.toLowerCase(); if (lower.endsWith('.exe') || lower.endsWith('.msi')) return 'windows'; if (lower.endsWith('.dmg')) return 'macos'; if (lower.endsWith('.apk')) return 'android'; if (lower.endsWith('.ipa')) return 'ios'; if ( lower.endsWith('.deb') || lower.endsWith('.rpm') || lower.endsWith('.appimage') || lower.endsWith('.flatpak') || lower.endsWith('.zst') ) return 'linux'; return 'other'; }; const label = (name) => { const lower = name.toLowerCase(); if (lower.endsWith('-suse.rpm')) return 'RPM (SUSE)'; if (lower.endsWith('.pkg.tar.zst')) return 'Arch'; if (lower.endsWith('.appimage')) return 'AppImage'; const extension = name.split('.').pop(); return extension ? extension.toUpperCase() : '下载'; }; const cell = (targetArchitecture, targetPlatform) => { const matches = assets .filter((asset) => architecture(asset.name) === targetArchitecture && platform(asset.name) === targetPlatform ) .sort((left, right) => left.name.localeCompare(right.name)); if (matches.length === 0) return '—'; return matches .map((asset) => `[${label(asset.name)}](${asset.browser_download_url})`) .join(' · '); }; const rows = [ ['x86-64 (64 位)', 'x86_64'], ['AArch64 / ARM64', 'arm64'], ['ARMv7 (32 位)', 'armv7'], ['通用包', 'universal'], ]; const table = [ '', tag === 'nightly' ? '> ⚠️ Nightly 是主分支自动构建的测试版本,仅保留最新一次构建产物。' : '> 请根据操作系统和 CPU 架构选择安装包。', '', '| 架构 | Windows | macOS | Linux | Android | iOS |', '| --- | --- | --- | --- | --- | --- |', ...rows.map(([title, arch]) => `| ${title} | ${cell(arch, 'windows')} | ${cell(arch, 'macos')} | ${cell(arch, 'linux')} | ${cell(arch, 'android')} | ${cell(arch, 'ios')} |` ), '', '
', '如何选择架构?', '', '- 普通 Intel / AMD 电脑请选择 **x86-64**。', '- Windows on ARM、Apple Silicon 或 ARM Linux 请选择 **ARM64**。', '- Android 不确定架构时优先选择 **通用包**。', '', '
', '', ].join('\n'); const start = ''; const end = ''; const oldBody = release.body || ''; const startIndex = oldBody.indexOf(start); const endIndex = oldBody.indexOf(end); let notes = oldBody; if (startIndex !== -1 && endIndex !== -1 && endIndex >= startIndex) { notes = [ oldBody.slice(0, startIndex), oldBody.slice(endIndex + end.length), ].join('').trim(); } if (tag === 'nightly' && notes.includes('正在为最新的主分支提交构建测试包。')) { notes = ''; } const body = notes ? `${table}\n\n---\n\n${notes}` : table; await github.rest.repos.updateRelease({ ...context.repo, release_id: release.id, name: tag === 'nightly' ? 'SubnetDesk Nightly' : release.name, body, prerelease: tag === 'nightly' || tag.includes('-'), }); publish-update-manifest: name: Publish signed stable update manifest if: ${{ always() && inputs.publish-release && inputs.upload-tag != 'nightly' && !contains(inputs.upload-tag, '-') }} needs: - build-for-windows-flutter - build-for-macOS runs-on: ubuntu-24.04 env: GH_TOKEN: ${{ github.token }} UPDATE_SIGNING_KEY_B64: ${{ secrets.SUBNETDESK_UPDATE_SIGNING_KEY_B64 }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Detect update signing configuration id: update_signing shell: bash run: | if [[ -z "$SUBNETDESK_UPDATE_PUBLIC_KEY" || -z "$UPDATE_SIGNING_KEY_B64" ]]; then echo "::notice::Skipping signed update manifest because update signing keys are not configured." echo "enabled=false" >> "$GITHUB_OUTPUT" exit 0 fi if [[ "$SIGN_BASE_URL" == "-2" || -z "$MACOS_P12_BASE64" ]]; then echo "::notice::Skipping signed update manifest because Windows or macOS package signing is not configured." echo "enabled=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "enabled=true" >> "$GITHUB_OUTPUT" - name: Download desktop release assets if: steps.update_signing.outputs.enabled == 'true' shell: bash run: | mkdir update-assets gh release download "$TAG_NAME" \ --dir update-assets \ --pattern "subnetdesk-${VERSION}-x86_64.msi" \ --pattern "subnetdesk-${VERSION}-x86_64.exe" \ --pattern "subnetdesk-${VERSION}-aarch64.msi" \ --pattern "subnetdesk-${VERSION}-aarch64.exe" \ --pattern "subnetdesk-${VERSION}-x86_64.dmg" \ --pattern "subnetdesk-${VERSION}-aarch64.dmg" - name: Generate and sign update manifest if: steps.update_signing.outputs.enabled == 'true' shell: bash run: | python3 scripts/generate_update_manifest.py \ --asset-dir update-assets \ --repository "$GITHUB_REPOSITORY" \ --tag "$TAG_NAME" \ --version "$VERSION" \ --sequence "${{ github.run_id }}" \ --output update-stable.json umask 077 signing_key="$RUNNER_TEMP/subnetdesk-update-private.pem" signature_bin="$RUNNER_TEMP/subnetdesk-update-signature.bin" printf '%s' "$UPDATE_SIGNING_KEY_B64" | base64 --decode > "$signing_key" derived_public_key=$(openssl pkey -in "$signing_key" -pubout -outform DER | tail -c 32 | xxd -p -c 64) if [[ "$derived_public_key" != "$SUBNETDESK_UPDATE_PUBLIC_KEY" ]]; then echo "The configured update public and private keys do not match." >&2 exit 1 fi openssl pkeyutl -sign -rawin \ -inkey "$signing_key" \ -in update-stable.json \ -out "$signature_bin" xxd -p -c 128 "$signature_bin" > update-stable.json.sig - name: Upload signed update manifest if: steps.update_signing.outputs.enabled == 'true' shell: bash run: | gh release upload "$TAG_NAME" \ update-stable.json \ update-stable.json.sig \ --clobber