Nightly Build #124
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: Nightly Build | |
| on: | |
| schedule: | |
| # Weekdays at 02:00 UTC | |
| - cron: "0 2 * * 1-5" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: nightly | |
| cancel-in-progress: true | |
| env: | |
| NIGHTLY_TAG: nightly | |
| jobs: | |
| # ── Check if there are new commits since last nightly ────────────── | |
| check-changes: | |
| name: Check for Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| nightly_version: ${{ steps.check.outputs.nightly_version }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for recent changes | |
| id: check | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BASE_VERSION="$(jq -r '.version' package.json)" | |
| DATE_SUFFIX="$(date -u '+%Y%m%d')" | |
| SHORT_SHA="$(git rev-parse --short HEAD)" | |
| NIGHTLY_VERSION="${BASE_VERSION}-nightly.${DATE_SUFFIX}+${SHORT_SHA}" | |
| echo "nightly_version=$NIGHTLY_VERSION" >> "$GITHUB_OUTPUT" | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Check if any commits landed in the last 25 hours | |
| LAST_COMMIT_TIME="$(git log -1 --format='%ct')" | |
| NOW="$(date -u '+%s')" | |
| HOURS_AGO=$(( (NOW - LAST_COMMIT_TIME) / 3600 )) | |
| if [[ "$HOURS_AGO" -lt 25 ]]; then | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No new commits in the last 25 hours, skipping nightly build." | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── Patch version for nightly ────────────────────────────────────── | |
| package: | |
| name: Package (${{ matrix.platform.name }}) | |
| runs-on: ${{ matrix.platform.os }} | |
| needs: check-changes | |
| if: needs.check-changes.outputs.should_build == 'true' | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| # Nightly relay archives are signed too (linux-binaries.yml receives the | |
| # key), so nightly Desktop needs the same trust root to verify them. | |
| BITFUN_RELEASE_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} | |
| 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: pnpm run installer:build | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup OpenSSL (Windows, prebuilt) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: ./scripts/ci/setup-openssl-windows.ps1 | |
| - 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: "nightly-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: Patch nightly version | |
| shell: bash | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: | | |
| set -euo pipefail | |
| echo "Patching version to $NIGHTLY_VERSION" | |
| # Patch package.json | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); | |
| pkg.version = process.env.NIGHTLY_VERSION.split('+')[0]; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| # Patch Cargo workspace version (semver: nightly suffix uses hyphen) | |
| # Cargo.toml only accepts: MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH-PRE | |
| CARGO_VERSION="$(echo "$NIGHTLY_VERSION" | sed 's/+.*//')" | |
| sed -i.bak "s/^version = \".*\" # x-release-please-version/version = \"${CARGO_VERSION}\" # x-release-please-version/" Cargo.toml | |
| rm -f Cargo.toml.bak | |
| echo "package.json version: $(jq -r '.version' package.json)" | |
| echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)" | |
| - name: Run Windows CLI terminal contracts | |
| if: runner.os == 'Windows' | |
| env: | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: "0" | |
| CARGO_PROFILE_TEST_DEBUG: "0" | |
| run: | | |
| cargo generate-lockfile | |
| cargo test --locked -p bitfun-cli --test terminal_process_contracts | |
| - name: Build desktop app | |
| run: ${{ matrix.platform.build_command }} | |
| - name: Package macOS CLI for SSH dispatch | |
| if: runner.os == 'macOS' | |
| id: macos-cli | |
| shell: bash | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| TARGET: ${{ matrix.platform.target }} | |
| run: | | |
| set -euo pipefail | |
| ASSET_VERSION="${NIGHTLY_VERSION%%+*}" | |
| cargo build --release --target "$TARGET" -p bitfun-cli | |
| bash scripts/cli/package-unix.sh "$ASSET_VERSION" "$TARGET" | |
| - name: Sign macOS CLI archive and checksum | |
| if: runner.os == 'macOS' | |
| 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 | |
| bash scripts/sign-release-assets.sh \ | |
| "${{ steps.macos-cli.outputs.archive }}" \ | |
| "${{ steps.macos-cli.outputs.checksum }}" | |
| - 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-nightly-${{ matrix.platform.name }}-bundle | |
| if-no-files-found: error | |
| retention-days: 7 | |
| path: | | |
| target/*/release/bundle | |
| target/release/bundle | |
| src/apps/desktop/target/release/bundle | |
| BitFun-Installer/src-tauri/target/release/bitfun-installer.exe | |
| bitfun-cli-*-apple-darwin.tar.gz | |
| bitfun-cli-*-apple-darwin.tar.gz.sha256 | |
| bitfun-cli-*-apple-darwin.tar.gz.sig | |
| bitfun-cli-*-apple-darwin.tar.gz.sha256.sig | |
| linux-binaries: | |
| name: Linux CLI and Relay Server | |
| needs: check-changes | |
| if: needs.check-changes.outputs.should_build == 'true' | |
| 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: ${{ github.sha }} | |
| version: ${{ needs.check-changes.outputs.nightly_version }} | |
| artifact_prefix: nightly | |
| # ── Publish nightly pre-release ──────────────────────────────────── | |
| publish-nightly: | |
| name: Publish Nightly | |
| needs: [check-changes, package, linux-binaries] | |
| if: needs.check-changes.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Download bundled artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: bitfun-nightly-*-bundle | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Download Linux binary artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: bitfun-linux-nightly-* | |
| path: linux-release-assets | |
| merge-multiple: true | |
| - name: List release assets | |
| run: | | |
| echo "Nightly assets:" | |
| find release-assets -type f | sort | |
| echo "Nightly Linux CLI and Relay Server assets:" | |
| find linux-release-assets -type f | sort | |
| - name: Delete previous nightly release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release delete "${{ env.NIGHTLY_TAG }}" --yes --cleanup-tag 2>/dev/null || true | |
| - name: Generate Linux binaries manifest | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: | | |
| set -euo pipefail | |
| # Asset names drop the `+<sha>` build metadata (see linux-binaries.yml). | |
| node scripts/generate-linux-binaries-manifest.mjs \ | |
| --assets-dir linux-release-assets \ | |
| --version "${NIGHTLY_VERSION%%+*}" \ | |
| --tag "${{ env.NIGHTLY_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: Create nightly release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ env.NIGHTLY_TAG }} | |
| name: "Nightly Build (${{ needs.check-changes.outputs.nightly_version }})" | |
| body: | | |
| Automated nightly build from `main` branch. | |
| **Version**: `${{ needs.check-changes.outputs.nightly_version }}` | |
| **Commit**: ${{ github.sha }} | |
| **Date**: ${{ github.event.head_commit.timestamp || github.event.repository.updated_at }} | |
| > **Warning**: Nightly builds are untested and may be unstable. | |
| prerelease: true | |
| files: | | |
| release-assets/**/*.AppImage | |
| release-assets/**/*.deb | |
| release-assets/**/*.dmg | |
| release-assets/**/*.rpm | |
| release-assets/**/*bitfun-installer.exe | |
| release-assets/**/*.sig | |
| release-assets/minisign.pub | |
| release-assets/**/bitfun-cli-*-apple-darwin.tar.gz | |
| release-assets/**/bitfun-cli-*-apple-darwin.tar.gz.sha256 | |
| 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/*.tar.gz.sha256.sig | |
| linux-release-assets/linux-binaries.json | |
| - name: Verify published Linux CLI signatures | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| while IFS= read -r cli_url; do | |
| curl -fsSL --retry 5 --retry-delay 3 "${cli_url}.sig" -o /dev/null | |
| curl -fsSL --retry 5 --retry-delay 3 "${cli_url}.sha256.sig" -o /dev/null | |
| done < <(jq -r '.platforms[].cli.url' linux-release-assets/linux-binaries.json) | |
| - name: Verify published macOS CLI assets | |
| shell: bash | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: | | |
| set -euo pipefail | |
| ASSET_VERSION="${NIGHTLY_VERSION%%+*}" | |
| RELEASE_ROOT="https://github.com/GCWing/BitFun/releases/download/${{ env.NIGHTLY_TAG }}" | |
| for target in aarch64-apple-darwin x86_64-apple-darwin; do | |
| archive="${RELEASE_ROOT}/bitfun-cli-${ASSET_VERSION}-${target}.tar.gz" | |
| curl -fsSL --retry 5 --retry-delay 3 "$archive" -o /dev/null | |
| curl -fsSL --retry 5 --retry-delay 3 "${archive}.sha256" -o /dev/null | |
| curl -fsSL --retry 5 --retry-delay 3 "${archive}.sig" -o /dev/null | |
| curl -fsSL --retry 5 --retry-delay 3 "${archive}.sha256.sig" -o /dev/null | |
| done |