name: Build and Release on: push: branches: - "**" - "!ci/linux-build-image" tags: - "*" - "!ios-v*" - "!android-v*" workflow_dispatch: inputs: patch: description: "Hotfix patch number, for example 1 or 2. Use 0 for a normal build." required: false default: "0" sign_windows_test: description: "Submit the Windows installer to SignPath using test-signing." required: false type: boolean default: false permissions: actions: read contents: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} XMAKE_MIN_VERSION: "3.1.1" jobs: build-linux: name: Build Linux (${{ matrix.arch }}) runs-on: ${{ matrix.runner }} strategy: matrix: include: - arch: amd64 runner: ubuntu-24.04 # Keep using the published dependency image; upgrade Xmake if needed. image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0 package_script: ./apps/desktop/scripts/linux/pkg_amd64.sh - arch: arm64 runner: ubuntu-24.04-arm image: crossdesk/ubuntu20.04:buildenv-cuda12.6.3-gcc10-cmake3.31.6-pipewire0.3.48-xmake3.0.9-rust1.92.0 package_script: ./apps/desktop/scripts/linux/pkg_arm64.sh container: image: ${{ matrix.image }} options: --user root steps: - name: Extract version number shell: bash run: | VERSION_REF="${GITHUB_REF##*/}" VERSION_BASE="${VERSION_REF#v}" PATCH_NUMBER="${{ github.event.inputs.patch }}" BUILD_DATE_OVERRIDE="" if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then PATCH_NUMBER=0 fi if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" BUILD_DATE_OVERRIDE="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}" PATCH_NUMBER="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" fi echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_ENV echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV echo "BUILD_DATE_OVERRIDE=${BUILD_DATE_OVERRIDE}" >> $GITHUB_ENV - name: Set legal Debian version shell: bash run: | BUILD_DATE="${BUILD_DATE_OVERRIDE}" if [[ -z "${BUILD_DATE}" ]]; then BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) fi if [[ ! "${VERSION_BASE}" =~ ^[0-9] ]]; then VERSION_BASE="0.0.0-${VERSION_BASE}" fi if [[ "${PATCH_NUMBER}" != "0" ]]; then LEGAL_VERSION="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}" else LEGAL_VERSION="v${VERSION_BASE}-${BUILD_DATE}" fi echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV - name: Checkout code uses: actions/checkout@v5 with: submodules: recursive - name: Ensure minimum Xmake version uses: ./.github/actions/ensure-xmake with: minimum-version: ${{ env.XMAKE_MIN_VERSION }} - name: Verify Linux build image shell: bash env: EXPECTED_ARCH: ${{ matrix.arch }} run: bash docker/linux-build/verify-build-image.sh - name: Ensure Wayland build dependencies shell: bash run: | if pkg-config --exists dbus-1 libdrm && \ test -f /opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/pipewire.h && \ test -f /opt/crossdesk-pipewire-sdk/include/spa-0.2/spa/param/video/format-utils.h; then exit 0 fi apt-get update apt-get install -y --no-install-recommends \ libdbus-1-dev \ libdrm-dev rm -rf /var/lib/apt/lists/* install-crossdesk-pipewire-sdk pkg-config --exists dbus-1 libdrm test -f /opt/crossdesk-pipewire-sdk/include/pipewire-0.3/pipewire/pipewire.h test -f /opt/crossdesk-pipewire-sdk/include/spa-0.2/spa/param/video/format-utils.h - name: Build CrossDesk env: CUDA_PATH: /usr/local/cuda XMAKE_GLOBALDIR: /data run: | xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --USE_WAYLAND=true \ --cc=gcc-10 --cxx=g++-10 --root -y xmake b -vy --root crossdesk - name: Install Linux headless runtime dependencies run: | apt-get update apt-get install -y --no-install-recommends xvfb xfce4 dbus - name: Package run: | chmod +x ${{ matrix.package_script }} ${{ matrix.package_script }} ${LEGAL_VERSION} - name: Verify Ubuntu 20.04 package dependency resolution run: | apt-get update for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do apt-get install --simulate "${GITHUB_WORKSPACE}/${PACKAGE}-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb" done - name: Verify packaged Ubuntu 20.04 glibc baseline run: | for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do PACKAGE_FILE="${GITHUB_WORKSPACE}/${PACKAGE}-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb" VERIFY_DIR="${RUNNER_TEMP}/${PACKAGE}-glibc-${{ matrix.arch }}" install -d "${VERIFY_DIR}" dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}" verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.31 done - name: Stage Linux release artifacts shell: bash run: | mkdir -p linux-artifacts for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do FILE="${PACKAGE}-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb" if [[ ! -s "${FILE}" ]]; then echo "::error::Missing or empty Linux package: ${FILE}" exit 1 fi cp "${FILE}" linux-artifacts/ done - name: Upload artifact uses: actions/upload-artifact@v6 with: name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }} # A single relative directory avoids container translation of multiline # absolute paths and keeps both package filenames at the artifact root. path: linux-artifacts/ if-no-files-found: error # macOS build-macos: name: Build on macOS runs-on: ${{ matrix.runner }} env: MACOSX_DEPLOYMENT_TARGET: "14.0" EXPECTED_MACOS_SDK: "26.5" strategy: matrix: include: - arch: x64 binary_arch: x86_64 runner: macos-26-intel cache-key: intel-macos26-xcode26.6-min14 out-dir: ./build/macosx/x86_64/release/crossdesk package_script: ./apps/desktop/scripts/macos/pkg_x64.sh - arch: arm64 binary_arch: arm64 runner: macos-26 cache-key: arm-macos26-xcode26.6-min14 out-dir: ./build/macosx/arm64/release/crossdesk package_script: ./apps/desktop/scripts/macos/pkg_arm64.sh steps: - name: Extract version number id: version shell: bash run: | VERSION_REF="${GITHUB_REF##*/}" VERSION_BASE="${VERSION_REF#v}" BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) PATCH_NUMBER="${{ github.event.inputs.patch }}" if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then PATCH_NUMBER=0 fi if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" BUILD_DATE="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE="${BASH_REMATCH[3]}" PATCH_NUMBER="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE="${BASH_REMATCH[3]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" fi if [[ "${PATCH_NUMBER}" != "0" ]]; then VERSION_NUM="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}" else VERSION_NUM="v${VERSION_BASE}-${BUILD_DATE}" fi echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV echo "VERSION_NUM=${VERSION_NUM}" echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV - name: Select Xcode 26.6 shell: bash run: | sudo xcode-select -s /Applications/Xcode_26.6.app/Contents/Developer xcodebuild -version SDK_VERSION="$(xcrun --sdk macosx --show-sdk-version)" if [[ "${SDK_VERSION}" != "${EXPECTED_MACOS_SDK}" ]]; then echo "Unexpected macOS SDK: expected ${EXPECTED_MACOS_SDK}, got ${SDK_VERSION}" >&2 exit 1 fi - name: Cache xmake dependencies uses: actions/cache@v5 with: path: ~/.xmake/packages key: "${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ github.run_id }}" restore-keys: | ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}- - name: Checkout code uses: actions/checkout@v5 - name: Initialize submodules run: git submodule update --init --recursive - name: Ensure minimum Xmake version uses: ./.github/actions/ensure-xmake with: minimum-version: ${{ env.XMAKE_MIN_VERSION }} - name: Update xmake repositories run: xmake repo -u - name: Build CrossDesk run: | xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=false -vyD xmake b -vy crossdesk - name: Package CrossDesk app run: | chmod +x ${{ matrix.package_script }} ${{ matrix.package_script }} ${VERSION_NUM} - name: Verify packaged app shell: bash run: | PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-macos-${{ matrix.arch }}-${VERSION_NUM}.pkg" VERIFY_DIR="$(mktemp -d "${RUNNER_TEMP}/crossdesk-macos-pkg.XXXXXX")" trap 'rm -rf "${VERIFY_DIR}"' EXIT pkgutil --expand-full "${PACKAGE_FILE}" "${VERIFY_DIR}/expanded" APP_PATH="$(find "${VERIFY_DIR}/expanded" -type d -name 'CrossDesk.app' -print -quit)" if [[ -z "${APP_PATH}" ]]; then echo "CrossDesk.app is missing from ${PACKAGE_FILE}" >&2 exit 1 fi ./apps/desktop/scripts/macos/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}" "${EXPECTED_MACOS_SDK}" - name: Upload build artifacts uses: actions/upload-artifact@v6 with: name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }} path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg - name: Move files to release dir run: | mkdir -p release cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/ # iOS (also available as a standalone workflow) build-ios: name: Build iOS uses: ./.github/workflows/build-ios.yml with: patch: ${{ inputs.patch || '0' }} permissions: contents: read # Android (also available as a standalone workflow) build-android: name: Build Android uses: ./.github/workflows/build-android.yml with: patch: ${{ inputs.patch || '0' }} permissions: contents: read # Windows build-windows-x64: name: Build on Windows x64 runs-on: windows-2022 env: XMAKE_GLOBALDIR: D:\xmake_global steps: - name: Extract version number shell: pwsh run: | $ref = $env:GITHUB_REF $version = $ref -replace '^refs/(tags|heads)/', '' $version = $version -replace '^v', '' $version = $version -replace '/', '-' $BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd") $PATCH_NUMBER = "${{ github.event.inputs.patch }}" if ($PATCH_NUMBER -notmatch '^[0-9]+$') { $PATCH_NUMBER = "0" } if ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$') { $version = $Matches[1] $PATCH_NUMBER = $Matches[3] $BUILD_DATE = $Matches[4] } elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') { $version = $Matches[1] $BUILD_DATE = $Matches[3] $PATCH_NUMBER = $Matches[4] } elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$') { $version = $Matches[1] $BUILD_DATE = $Matches[3] } elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$' -and $PATCH_NUMBER -eq "0") { $version = $Matches[1] $PATCH_NUMBER = $Matches[3] } if ($PATCH_NUMBER -ne "0") { $VERSION_NUM = "v$version-$PATCH_NUMBER-$BUILD_DATE" } else { $VERSION_NUM = "v$version-$BUILD_DATE" } echo "VERSION_NUM=$VERSION_NUM" >> $env:GITHUB_ENV echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV echo "PATCH_NUMBER=$PATCH_NUMBER" >> $env:GITHUB_ENV - name: Cache xmake dependencies uses: actions/cache@v5 with: path: D:\xmake_global\.xmake\packages key: "${{ runner.os }}-xmake-deps-intel-${{ github.run_id }}" restore-keys: | ${{ runner.os }}-xmake-deps-intel- - name: Checkout code uses: actions/checkout@v5 - name: Initialize submodules run: git submodule update --init --recursive - name: Ensure minimum Xmake version uses: ./.github/actions/ensure-xmake with: minimum-version: ${{ env.XMAKE_MIN_VERSION }} - name: Prepare CUDA dependencies working-directory: ${{ runner.temp }} run: | xmake repo -u xmake create cuda Push-Location cuda xmake g --theme=plain $cudaPath = "" $packagesPath = "D:\xmake_global\.xmake\packages" if (Test-Path $packagesPath) { Write-Host "Packages directory exists: $packagesPath" try { $info = xmake require --info "cuda 12.6.3" 2>$null if ($null -ne $info -and $info -ne "") { $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() } } catch {} } else { Write-Host "Packages directory not found: $packagesPath" Write-Host "Installing CUDA package..." xmake require -vy "cuda 12.6.3" $info = xmake require --info "cuda 12.6.3" $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() } echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV Write-Host "Resolved CUDA_PATH = $cudaPath" Pop-Location - name: Download INetC plugin shell: powershell run: | $url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip" $out = "$env:RUNNER_TEMP\InetC.zip" Invoke-WebRequest -Uri $url -OutFile $out Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force $source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll" $target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll" Write-Host "Copying $source to $target" Copy-Item $source $target -Force - name: Copy nsProcess plugin to NSIS folder run: | $nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode" copy "${{ github.workspace }}\apps\desktop\scripts\windows\nsProcess.dll" $nsisPluginDir - name: Build CrossDesk run: | xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true -y xmake b -vy crossdesk - name: Package shell: pwsh run: | & "${{ github.workspace }}\apps\desktop\scripts\windows\pkg_x64.ps1" $env:VERSION_NUM - name: Upload unsigned Windows installer for test signing if: github.event_name == 'workflow_dispatch' && inputs.sign_windows_test id: upload-unsigned-windows-installer uses: actions/upload-artifact@v7 with: path: ${{ github.workspace }}/apps/desktop/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe archive: false if-no-files-found: error retention-days: 1 - name: Sign Windows installer with SignPath test certificate if: github.event_name == 'workflow_dispatch' && inputs.sign_windows_test uses: signpath/github-action-submit-signing-request@v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} project-slug: crossdesk signing-policy-slug: test-signing github-artifact-id: ${{ steps.upload-unsigned-windows-installer.outputs.artifact-id }} wait-for-completion: true output-artifact-directory: ${{ github.workspace }}\signpath\signed-installer skip-decompress: true parameters: | version: ${{ toJSON(env.VERSION_NUM) }} - name: Verify and use test-signed Windows installer if: github.event_name == 'workflow_dispatch' && inputs.sign_windows_test shell: pwsh run: | $installerName = "crossdesk-win-x64-${{ env.VERSION_NUM }}.exe" $signedInstaller = Join-Path "${{ github.workspace }}\signpath\signed-installer" $installerName $destination = Join-Path "${{ github.workspace }}\apps\desktop\scripts\windows" $installerName if (!(Test-Path $signedInstaller)) { throw "SignPath did not return the signed installer: $signedInstaller" } $signature = Get-AuthenticodeSignature $signedInstaller if (!$signature.SignerCertificate -or $signature.Status -eq "NotSigned") { throw "SignPath returned an installer without an Authenticode signature." } Write-Host "Signer: $($signature.SignerCertificate.Subject)" Write-Host "Signature status: $($signature.Status)" Copy-Item $signedInstaller $destination -Force - name: Build Portable CrossDesk run: | xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true --CROSSDESK_PORTABLE=true -y xmake b -vy crossdesk - name: Package Portable shell: pwsh run: | $buildDir = "${{ github.workspace }}\build\windows\x64\release" $portableDir = "${{ github.workspace }}\portable" New-Item -ItemType Directory -Force -Path $portableDir $portableFiles = @( @("crossdesk.exe", "CrossDesk.exe"), @("crossdesk_service.exe", "crossdesk_service.exe"), @("crossdesk_session_helper.exe", "crossdesk_session_helper.exe"), @("crossdesk_privacy_cursor_helper.exe", "crossdesk_privacy_cursor_helper.exe"), @("crossdesk_privacy_window.dll", "crossdesk_privacy_window.dll"), @("slint_cpp.dll", "slint_cpp.dll") ) foreach ($file in $portableFiles) { $source = Join-Path $buildDir $file[0] $destination = Join-Path $portableDir $file[1] if (!(Test-Path $source)) { throw "Missing portable package file: $source" } Copy-Item $source $destination -Force } Copy-Item (Join-Path $buildDir "*.dll") $portableDir -Force # Bundled Amyuni usbmmidd_v2 virtual display driver (headless hosts) $driverDir = Join-Path $buildDir "usbmmidd_v2" if (!(Test-Path (Join-Path $driverDir "deviceinstaller64.exe"))) { throw "Missing bundled usbmmidd_v2 driver package: $driverDir" } Copy-Item $driverDir (Join-Path $portableDir "usbmmidd_v2") -Recurse -Force foreach ($file in $portableFiles) { $packagedFile = Join-Path $portableDir $file[1] if (!(Test-Path $packagedFile)) { throw "Portable package is missing: $packagedFile" } } Compress-Archive -Path "$portableDir\*" -DestinationPath "${{ github.workspace }}\crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip" - name: Upload artifact uses: actions/upload-artifact@v6 with: name: crossdesk-win-x64-${{ env.VERSION_NUM }} path: ${{ github.workspace }}/apps/desktop/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe - name: Upload portable artifact uses: actions/upload-artifact@v6 with: name: crossdesk-win-x64-portable-${{ env.VERSION_NUM }} path: ${{ github.workspace }}/crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip sign-android: name: Sign Android release needs: build-android if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/workflows/sign-android.yml with: version: ${{ needs.build-android.outputs.version }} secrets: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} release: name: Publish Release if: startsWith(github.ref, 'refs/tags/v') needs: [build-linux, build-macos, build-ios, build-android, sign-android, build-windows-x64] runs-on: ubuntu-latest concurrency: group: publish-release-metadata cancel-in-progress: false steps: - name: Checkout repository uses: actions/checkout@v5 - name: Download all artifacts uses: actions/download-artifact@v8 with: path: artifacts - name: Extract version number id: version shell: bash run: | VERSION_REF="${GITHUB_REF##*/}" VERSION_BASE="${VERSION_REF#v}" BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) PATCH_NUMBER="${{ github.event.inputs.patch }}" if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then PATCH_NUMBER=0 fi if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" BUILD_DATE="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE="${BASH_REMATCH[3]}" PATCH_NUMBER="${BASH_REMATCH[4]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then VERSION_BASE="${BASH_REMATCH[1]}" BUILD_DATE="${BASH_REMATCH[3]}" elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then VERSION_BASE="${BASH_REMATCH[1]}" PATCH_NUMBER="${BASH_REMATCH[3]}" fi BUILD_DATE_ISO="${BUILD_DATE:0:4}-${BUILD_DATE:4:2}-${BUILD_DATE:6:2}" if [[ "${PATCH_NUMBER}" != "0" ]]; then VERSION_NUM="${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}" else VERSION_NUM="${VERSION_BASE}-${BUILD_DATE}" fi VERSION_WITH_V="v${VERSION_NUM}" echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT - name: Prepare release notes run: | cat > "$RUNNER_TEMP/release-notes.md" << EOF ## Corresponding source Source for these binaries: [${GITHUB_REF_NAME}](https://github.com/${GITHUB_REPOSITORY}/tree/${GITHUB_SHA}). Clone that version with recursive submodules. The repository includes the build scripts and dependency patches. [iOS source, modification and rebuild instructions](https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/apps/ios/README.md). The iOS archive also contains the exact source revision and third-party source catalog with archive checksums. [Android source and rebuild instructions](https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/apps/android/README.md). The Android arm64 Release APK is signed with the CrossDesk publishing key and can be installed directly. It includes the exact application and MiniRTC revisions, license documents and native build recipes. ## Linux packages Choose one package for your architecture; both contain the complete CrossDesk application: - Standard (recommended): crossdesk-linux-ARCH-VERSION.deb, for existing desktops. - Virtual desktop: crossdesk-virtual-desktop-xfce-linux-ARCH-VERSION.deb, which additionally installs Xfce, Xvfb and D-Bus session tools through APT. The packages replace each other and preserve user settings. Installation does not switch your default desktop or start a virtual session. [Linux setup and switching packages](https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/docs/HEADLESS_EN.md). ## Code signing policy Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/). EOF - name: Rename artifacts run: | mkdir -p release cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg for ARCH in amd64 arm64; do for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do FILE="${PACKAGE}-linux-${ARCH}-${{ steps.version.outputs.VERSION_WITH_V }}.deb" cp "artifacts/crossdesk-linux-${ARCH}-${{ steps.version.outputs.VERSION_WITH_V }}/${FILE}" "release/${FILE}" done done cp artifacts/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}/* release/crossdesk-ios-arm64-unsigned-${{ needs.build-ios.outputs.version }}.zip cp artifacts/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}/*.apk release/crossdesk-android-arm64-${{ needs.build-android.outputs.version }}.apk cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe cp artifacts/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip - name: List release files run: ls -lh release/ - name: Upload to Versioned GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.version.outputs.VERSION_WITH_V }} # The pushed tag already determines the release source commit. target_commitish: ${{ github.event.repository.default_branch }} name: Release ${{ steps.version.outputs.VERSION_WITH_V }} draft: false prerelease: false files: release/* generate_release_notes: false body_path: ${{ runner.temp }}/release-notes.md - name: Create or update 'latest' tag run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag -f latest git push origin latest --force - name: Upload to GitHub Release (latest) uses: softprops/action-gh-release@v2 with: tag_name: latest name: Latest Release draft: false prerelease: false files: release/* generate_release_notes: false body_path: ${{ runner.temp }}/release-notes.md - name: Upload artifacts to server uses: burnett01/rsync-deployments@5.2 with: switches: -avzr --progress path: release/* remote_path: /var/www/html/downloads/ remote_host: ${{ secrets.SERVER_HOST }} remote_user: ${{ secrets.SERVER_USER }} remote_key: ${{ secrets.SERVER_KEY }} - name: Download current version.json run: | curl --fail --silent --show-error --retry 3 --max-time 30 \ -H 'Cache-Control: no-cache' \ https://version.crossdesk.cn/version.json -o current-version.json - name: Generate version.json for all platforms env: RELEASE_TAG: ${{ steps.version.outputs.VERSION_WITH_V }} run: | python3 .github/scripts/update_version_manifest.py \ --current current-version.json --output version.json --artifacts release \ --tag "${RELEASE_TAG}" --name "Release ${RELEASE_TAG}" \ --notes "${RUNNER_TEMP}/release-notes.md" - name: Upload version.json to server uses: burnett01/rsync-deployments@5.2 with: switches: -avzr path: version.json remote_path: /var/www/html/version/ remote_host: ${{ secrets.SERVER_HOST }} remote_user: ${{ secrets.SERVER_USER }} remote_key: ${{ secrets.SERVER_KEY }}