name: Build and Release on: push: branches: - "**" - "!ci/linux-build-image" tags: - "*" - "!ios-v*" - "!android-v*" workflow_dispatch: inputs: desktop_version: description: "Desktop version for branch builds, e.g. 1.7.2. Leave blank for 0.0.0; tags use their own version." required: false type: string 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 sign_macos: description: "Sign and notarize macOS installers (automatic for v* release tags)." required: false type: boolean default: false permissions: actions: read contents: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} XMAKE_MIN_VERSION: "3.1.1" CROSSDESK_UPDATE_PUBLIC_KEY: ${{ vars.CROSSDESK_UPDATE_PUBLIC_KEY }} CROSSDESK_DESKTOP_VERSION: ${{ inputs.desktop_version }} jobs: prepare-version: name: Resolve desktop version runs-on: ubuntu-24.04 outputs: version: ${{ steps.version.outputs.version }} # Resolve the date once for every desktop build and the release job. steps: - name: Resolve desktop version id: version shell: bash env: PATCH_NUMBER: ${{ inputs.patch || '0' }} run: | VERSION_REF="${GITHUB_REF##*/}" if [[ "${GITHUB_REF}" == refs/heads/* ]]; then VERSION_REF="${CROSSDESK_DESKTOP_VERSION:-0.0.0}" if [[ ! "${VERSION_REF}" =~ ^v?[0-9]+(\.[0-9]+){1,3}$ ]]; then echo "::error::desktop_version must be a numeric version such as 1.7.2" exit 1 fi elif [[ -n "${CROSSDESK_DESKTOP_VERSION}" ]]; then echo "::error::desktop_version is only supported for branch builds; tags determine release versions" exit 1 fi VERSION_BASE="${VERSION_REF#v}" BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) 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=${VERSION_NUM}" >> "$GITHUB_OUTPUT" echo "Desktop version: ${VERSION_NUM}" build-linux: name: Build Linux (${{ matrix.arch }}) needs: prepare-version runs-on: ${{ matrix.runner }} env: LEGAL_VERSION: ${{ needs.prepare-version.outputs.version }} 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: 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 needs: prepare-version runs-on: ${{ matrix.runner }} env: VERSION_NUM: ${{ needs.prepare-version.outputs.version }} MACOSX_DEPLOYMENT_TARGET: "14.0" EXPECTED_MACOS_SDK: "26.5" MACOS_SIGNING_REQUIRED: ${{ startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.sign_macos) }} strategy: fail-fast: false 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: Validate macOS signing secrets if: env.MACOS_SIGNING_REQUIRED == 'true' shell: bash env: MACOS_APP_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_APP_CERTIFICATE_P12_BASE64 }} MACOS_INSTALLER_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_INSTALLER_CERTIFICATE_P12_BASE64 }} MACOS_P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | missing=0 for name in MACOS_APP_CERTIFICATE_P12_BASE64 MACOS_INSTALLER_CERTIFICATE_P12_BASE64 \ MACOS_P12_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do if [[ -z "${!name:-}" ]]; then echo "::error::Missing required Actions secret: ${name}" missing=1 fi done exit "$missing" - 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: Import Developer ID certificates and notarization credentials id: macos_signing if: env.MACOS_SIGNING_REQUIRED == 'true' shell: bash env: MACOS_APP_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_APP_CERTIFICATE_P12_BASE64 }} MACOS_INSTALLER_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_INSTALLER_CERTIFICATE_P12_BASE64 }} MACOS_P12_PASSWORD: ${{ secrets.MACOS_P12_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: bash .github/scripts/setup_macos_signing.sh - name: Package CrossDesk app id: macos_package 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: Notarize and staple macOS installer id: macos_notarization if: env.MACOS_SIGNING_REQUIRED == 'true' timeout-minutes: 65 shell: bash env: CROSSDESK_MACOS_NOTARY_PROFILE: ${{ steps.macos_signing.outputs.notary_profile }} CROSSDESK_MACOS_NOTARY_TIMEOUT: 60m CROSSDESK_MACOS_NOTARY_S3_ACCELERATION: "false" run: | bash ./apps/desktop/scripts/macos/notarize_pkg.sh \ "crossdesk-macos-${{ matrix.arch }}-${VERSION_NUM}.pkg" - name: Upload macOS notarization diagnostics if: always() && env.MACOS_SIGNING_REQUIRED == 'true' uses: actions/upload-artifact@v6 with: name: macos-notarization-${{ matrix.arch }}-${{ env.VERSION_NUM }} path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg.notary*.json if-no-files-found: ignore retention-days: 14 - name: Preserve signed installer after notarization failure if: failure() && env.MACOS_SIGNING_REQUIRED == 'true' && steps.macos_package.outcome == 'success' uses: actions/upload-artifact@v6 with: name: macos-pending-notarization-${{ matrix.arch }}-${{ env.VERSION_NUM }} path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg if-no-files-found: error retention-days: 14 - 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 if-no-files-found: error - name: Move files to release dir run: | mkdir -p release cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/ - name: Remove macOS signing credentials if: always() && env.MACOS_SIGNING_REQUIRED == 'true' shell: bash run: | security delete-keychain "${RUNNER_TEMP}/crossdesk-macos-signing/signing.keychain-db" 2>/dev/null || true rm -rf "${RUNNER_TEMP:?}/crossdesk-macos-signing" # 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 needs: prepare-version runs-on: windows-2022 env: VERSION_NUM: ${{ needs.prepare-version.outputs.version }} XMAKE_GLOBALDIR: D:\xmake_global steps: - 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: [prepare-version, 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: 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-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-macos-x64-${{ needs.prepare-version.outputs.version }}.pkg cp artifacts/crossdesk-macos-arm64-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-macos-arm64-${{ needs.prepare-version.outputs.version }}.pkg for ARCH in amd64 arm64; do for PACKAGE in crossdesk crossdesk-virtual-desktop-xfce; do FILE="${PACKAGE}-linux-${ARCH}-${{ needs.prepare-version.outputs.version }}.deb" cp "artifacts/crossdesk-linux-${ARCH}-${{ needs.prepare-version.outputs.version }}/${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-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-win-x64-${{ needs.prepare-version.outputs.version }}.exe cp artifacts/crossdesk-win-x64-portable-${{ needs.prepare-version.outputs.version }}/* release/crossdesk-win-x64-portable-${{ needs.prepare-version.outputs.version }}.zip - name: List release files run: ls -lh release/ - name: Upload to Versioned GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare-version.outputs.version }} # The pushed tag already determines the release source commit. target_commitish: ${{ github.event.repository.default_branch }} name: Release ${{ needs.prepare-version.outputs.version }} 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: ${{ needs.prepare-version.outputs.version }} CROSSDESK_UPDATE_PRIVATE_KEY: ${{ secrets.CROSSDESK_UPDATE_PRIVATE_KEY }} 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 }}