Desktop Package #40
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Desktop Package | |
| on: | |
| release: | |
| types: | |
| - published | |
| workflow_dispatch: | |
| inputs: | |
| tag_name: | |
| description: "Tag name to build (e.g. v0.2.0). Leave empty to build from HEAD." | |
| required: false | |
| type: string | |
| upload_to_release: | |
| description: "Upload built artifacts to the release specified by tag_name." | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: desktop-package-${{ github.event.release.tag_name || inputs.tag_name || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ── Resolve version info ─────────────────────────────────────────── | |
| prepare: | |
| name: Prepare | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.meta.outputs.version }} | |
| release_tag: ${{ steps.meta.outputs.release_tag }} | |
| upload_to_release: ${{ steps.meta.outputs.upload_to_release }} | |
| checkout_ref: ${{ steps.meta.outputs.checkout_ref }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Resolve version metadata | |
| id: meta | |
| shell: bash | |
| env: | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| GITHUB_SHA: ${{ github.sha }} | |
| RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} | |
| INPUT_TAG_NAME: ${{ inputs.tag_name }} | |
| INPUT_UPLOAD_TO_RELEASE: ${{ inputs.upload_to_release }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then | |
| TAG="${RELEASE_TAG_NAME}" | |
| VERSION="${TAG#v}" | |
| UPLOAD="true" | |
| CHECKOUT_REF="${TAG}" | |
| elif [[ -n "${INPUT_TAG_NAME}" ]]; then | |
| TAG="${INPUT_TAG_NAME}" | |
| VERSION="${TAG#v}" | |
| CHECKOUT_REF="${TAG}" | |
| if [[ "${INPUT_UPLOAD_TO_RELEASE}" == "true" ]]; then | |
| UPLOAD="true" | |
| else | |
| UPLOAD="false" | |
| fi | |
| else | |
| VERSION="$(jq -r '.version' package.json)" | |
| TAG="v${VERSION}" | |
| UPLOAD="false" | |
| CHECKOUT_REF="${GITHUB_SHA}" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "release_tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "upload_to_release=$UPLOAD" >> "$GITHUB_OUTPUT" | |
| echo "checkout_ref=$CHECKOUT_REF" >> "$GITHUB_OUTPUT" | |
| # ── Build per platform ───────────────────────────────────────────── | |
| package: | |
| name: Package (${{ matrix.platform.name }}) | |
| runs-on: ${{ matrix.platform.os }} | |
| needs: prepare | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| BITFUN_ENABLE_UPDATER_ARTIFACTS: ${{ needs.prepare.outputs.upload_to_release }} | |
| TAURI_UPDATER_ENDPOINT: https://github.com/GCWing/BitFun/releases/latest/download/latest.json | |
| TAURI_UPDATER_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} | |
| # Same trust root, compiled into the Desktop binary so one-click relay | |
| # deploy can verify the signed checksum locally and hand the remote host | |
| # a hash it does not have to trust the mirror for. | |
| BITFUN_RELEASE_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - os: ubuntu-latest | |
| name: linux-x64 | |
| target: x86_64-unknown-linux-gnu | |
| build_command: pnpm run desktop:build:linux -- --target x86_64-unknown-linux-gnu --bundles deb,rpm,appimage | |
| - os: ubuntu-24.04-arm | |
| name: linux-arm64 | |
| target: aarch64-unknown-linux-gnu | |
| # Fat LTO exhausts the hosted ARM runner while linking bitfun-desktop. | |
| build_command: CARGO_PROFILE_RELEASE_LTO=thin pnpm run desktop:build:linux -- --target aarch64-unknown-linux-gnu --bundles deb,rpm,appimage | |
| - os: macos-15 | |
| name: macos-arm64 | |
| target: aarch64-apple-darwin | |
| build_command: pnpm run desktop:build:arm64 | |
| - os: macos-15-intel | |
| name: macos-x64 | |
| target: x86_64-apple-darwin | |
| build_command: pnpm run desktop:build:x86_64 | |
| - os: windows-latest | |
| name: windows-x64 | |
| target: x86_64-pc-windows-msvc | |
| build_command: | | |
| $ErrorActionPreference = 'Stop' | |
| pnpm run desktop:build:nsis --target x86_64-pc-windows-msvc --verbose | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| $desktopExe = "target/x86_64-pc-windows-msvc/release/bitfun-desktop.exe" | |
| if (-not (Test-Path $desktopExe)) { | |
| throw "Desktop executable was not found after NSIS build: $desktopExe" | |
| } | |
| pnpm run installer:build:only | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ needs.prepare.outputs.checkout_ref }} | |
| - name: Setup OpenSSL (Windows, prebuilt) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: ./scripts/ci/setup-openssl-windows.ps1 | |
| - name: Install NSIS (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| choco install nsis -y --no-progress | |
| $nsisRoot = "${env:ProgramFiles(x86)}\NSIS" | |
| $nsisBin = "${env:ProgramFiles(x86)}\NSIS\Bin" | |
| if (Test-Path $nsisRoot) { | |
| Add-Content $env:GITHUB_PATH $nsisRoot | |
| } | |
| if (Test-Path $nsisBin) { | |
| Add-Content $env:GITHUB_PATH $nsisBin | |
| } | |
| - name: Verify NSIS (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| where.exe makensis | |
| makensis /VERSION | |
| - name: Install Linux system dependencies (Tauri bundler) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| if apt-cache show libwebkit2gtk-4.1-dev >/dev/null 2>&1; then | |
| WEBKIT_PKG=libwebkit2gtk-4.1-dev | |
| else | |
| WEBKIT_PKG=libwebkit2gtk-4.0-dev | |
| fi | |
| if apt-cache show libappindicator3-dev >/dev/null 2>&1; then | |
| APPINDICATOR_PKG=libappindicator3-dev | |
| else | |
| APPINDICATOR_PKG=libayatana-appindicator3-dev | |
| fi | |
| # Tauri pins AppImage GTK input methods to its bundled cache, so the | |
| # fcitx5 GTK3 bridge must be present before linuxdeploy builds it. | |
| sudo apt-get install -y --no-install-recommends \ | |
| pkg-config \ | |
| xdg-utils \ | |
| libglib2.0-dev \ | |
| libgtk-3-dev \ | |
| fcitx5-frontend-gtk3 \ | |
| libxdo-dev \ | |
| "$WEBKIT_PKG" \ | |
| "$APPINDICATOR_PKG" \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| fakeroot \ | |
| rpm \ | |
| libleptonica-dev \ | |
| libtesseract-dev \ | |
| tesseract-ocr \ | |
| tesseract-ocr-eng | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Setup Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.platform.target }} | |
| - name: Cache Rust build | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "package-v2-${{ matrix.platform.name }}" | |
| cache-bin: false | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Type-check web UI | |
| run: pnpm run type-check:web | |
| - name: Build desktop app | |
| run: ${{ matrix.platform.build_command }} | |
| - name: Verify AppImage fcitx5 GTK module | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: bash scripts/ci/verify-appimage-fcitx.sh "${{ matrix.platform.target }}" | |
| - name: Upload bundles | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: bitfun-${{ needs.prepare.outputs.release_tag }}-${{ matrix.platform.name }}-bundle | |
| if-no-files-found: error | |
| path: | | |
| target/*/release/bundle | |
| target/release/bundle | |
| src/apps/desktop/target/release/bundle | |
| BitFun-Installer/src-tauri/target/release/bitfun-installer.exe | |
| linux-binaries: | |
| name: Linux CLI and Relay Server | |
| needs: prepare | |
| uses: ./.github/workflows/linux-binaries.yml | |
| secrets: | |
| release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| release_signing_password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| release_pubkey: ${{ secrets.TAURI_UPDATER_PUBKEY }} | |
| with: | |
| checkout_ref: ${{ needs.prepare.outputs.checkout_ref }} | |
| version: ${{ needs.prepare.outputs.version }} | |
| artifact_prefix: ${{ needs.prepare.outputs.release_tag }} | |
| # ── Upload assets to GitHub Release ──────────────────────────────── | |
| upload-release-assets: | |
| name: Upload Release Assets | |
| needs: [prepare, package, linux-binaries] | |
| if: needs.prepare.outputs.upload_to_release == 'true' | |
| runs-on: ubuntu-latest | |
| env: | |
| REQUIRED_UPDATER_PLATFORMS: windows-x86_64,darwin-x86_64,darwin-aarch64,linux-x86_64,linux-aarch64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Download bundled artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: bitfun-${{ needs.prepare.outputs.release_tag }}-*-bundle | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Download Linux binary artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: bitfun-linux-${{ needs.prepare.outputs.release_tag }}-* | |
| path: linux-release-assets | |
| merge-multiple: true | |
| - name: List release assets | |
| run: | | |
| echo "Release assets:" | |
| find release-assets -type f | sort | |
| echo "Linux CLI and Relay Server assets:" | |
| find linux-release-assets -type f | sort | |
| - name: Collect updater assets | |
| run: | | |
| node scripts/collect-tauri-updater-assets.mjs \ | |
| --assets-dir release-assets \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --out-dir release-updater-assets \ | |
| --required-platforms "${REQUIRED_UPDATER_PLATFORMS}" | |
| - name: Generate updater manifest | |
| run: | | |
| node scripts/generate-tauri-latest-json.mjs \ | |
| --assets-dir release-updater-assets \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --tag "${{ needs.prepare.outputs.release_tag }}" \ | |
| --repo "GCWing/BitFun" \ | |
| --out release-updater-assets/latest.json \ | |
| --required-platforms "${REQUIRED_UPDATER_PLATFORMS}" | |
| - name: Verify updater manifest | |
| run: | | |
| node scripts/verify-tauri-latest-json.mjs \ | |
| --manifest release-updater-assets/latest.json \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --required-platforms "${REQUIRED_UPDATER_PLATFORMS}" | |
| - name: Generate Linux binaries manifest | |
| run: | | |
| node scripts/generate-linux-binaries-manifest.mjs \ | |
| --assets-dir linux-release-assets \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --tag "${{ needs.prepare.outputs.release_tag }}" \ | |
| --repo "GCWing/BitFun" \ | |
| --out linux-release-assets/linux-binaries.json | |
| - name: Setup Rust toolchain (minisign fallback) | |
| uses: dtolnay/rust-toolchain@stable | |
| # The Tauri bundler signs the five updater artifacts during `tauri build`, | |
| # but the installers people download by hand from the release page — dmg, | |
| # deb, rpm, the Windows installer and the direct AppImages — shipped with | |
| # no signature at all. Sign them with the same key so every published | |
| # artifact is verifiable. (This is not OS-level code signing: Gatekeeper | |
| # and SmartScreen still need Apple/Authenticode certificates.) | |
| - name: Sign installer packages | |
| shell: bash | |
| env: | |
| BITFUN_SIGNING_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| BITFUN_SIGNING_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| BITFUN_SIGNING_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} | |
| run: | | |
| set -euo pipefail | |
| mapfile -t assets < <( | |
| find release-assets -type f \ | |
| \( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \ | |
| -o -name '*.dmg' -o -name '*bitfun-installer.exe' \) | sort | |
| ) | |
| if [[ "${#assets[@]}" -eq 0 ]]; then | |
| echo "No installer packages found to sign." | |
| exit 0 | |
| fi | |
| bash scripts/sign-release-assets.sh "${assets[@]}" | |
| # Publish the public key alongside the signatures: a signature nobody | |
| # can fetch a key for is not verifiable. | |
| printf '%s' "${BITFUN_SIGNING_PUBKEY}" | base64 -d >release-assets/minisign.pub | |
| - name: Upload to release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.release_tag }} | |
| generate_release_notes: true | |
| files: | | |
| release-updater-assets/* | |
| release-assets/**/*.AppImage | |
| release-assets/**/*.deb | |
| release-assets/**/*.dmg | |
| release-assets/**/*.rpm | |
| release-assets/**/*bitfun-installer.exe | |
| release-assets/**/*.sig | |
| release-assets/minisign.pub | |
| linux-release-assets/bitfun-cli-*.tar.gz | |
| linux-release-assets/bitfun-cli-*.tar.gz.sha256 | |
| linux-release-assets/bitfun-relay-server-*.tar.gz | |
| linux-release-assets/bitfun-relay-server-*.tar.gz.sha256 | |
| linux-release-assets/*.tar.gz.sig | |
| linux-release-assets/linux-binaries.json | |
| fail_on_unmatched_files: true | |
| - name: Verify published updater manifest | |
| run: | | |
| curl -fsSL --retry 5 --retry-delay 3 \ | |
| "https://github.com/GCWing/BitFun/releases/download/${{ needs.prepare.outputs.release_tag }}/latest.json" \ | |
| -o latest.published.json | |
| node scripts/verify-tauri-latest-json.mjs \ | |
| --manifest latest.published.json \ | |
| --version "${{ needs.prepare.outputs.version }}" \ | |
| --required-platforms "${REQUIRED_UPDATER_PLATFORMS}" \ | |
| --check-urls true | |
| - name: Verify published Linux binaries manifest | |
| run: | | |
| curl -fsSL --retry 5 --retry-delay 3 \ | |
| "https://github.com/GCWing/BitFun/releases/download/${{ needs.prepare.outputs.release_tag }}/linux-binaries.json" \ | |
| -o linux-binaries.published.json | |
| test "$(jq -r '.version' linux-binaries.published.json)" = "${{ needs.prepare.outputs.version }}" | |
| # Nudge the openbitfun.com mirror to sync now instead of on its next | |
| # 10-minute cron tick. Until the mirror has these bytes, CN clients have | |
| # only the GitHub origin to fall back to. Best effort: the cron run is | |
| # still the source of truth, so a failed or unconfigured ping never fails | |
| # the release. Receiver setup: scripts/openbitfun-release-sync.sh. | |
| - name: Request openbitfun mirror sync | |
| continue-on-error: true | |
| env: | |
| SYNC_WEBHOOK_URL: ${{ secrets.OPENBITFUN_SYNC_WEBHOOK_URL }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${SYNC_WEBHOOK_URL:-}" ]]; then | |
| echo "OPENBITFUN_SYNC_WEBHOOK_URL is not configured; the mirror will pick this up on its next cron run." | |
| exit 0 | |
| fi | |
| curl -fsSL -X POST --retry 3 --retry-delay 5 --max-time 30 \ | |
| -H 'Content-Type: application/json' \ | |
| -d "{\"tag\":\"${RELEASE_TAG}\"}" \ | |
| "${SYNC_WEBHOOK_URL}" >/dev/null | |
| echo "Mirror sync requested for ${RELEASE_TAG}." |