Nightly Build #141
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 | |
| packages: 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 | |
| BITFUN_RELEASE_CHANNEL: nightly | |
| # Nightly does not publish a Tauri latest.json feed yet. Preserve its | |
| # existing stable updater endpoints until that publishing path exists. | |
| TAURI_UPDATER_ENDPOINT: https://github.com/GCWing/BitFun/releases/latest/download/latest.json | |
| TAURI_UPDATER_FALLBACK_ENDPOINT: https://openbitfun.com/release/latest.json | |
| # 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 Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.14" | |
| - 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: Build plugin Host resources | |
| run: | | |
| bun install --cwd src/apps/extension-host --frozen-lockfile | |
| bun run --cwd src/apps/extension-host build | |
| - name: Generate web API bindings | |
| run: pnpm --dir src/web-ui run gen:types | |
| - 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" | |
| ASSET_VERSION="${NIGHTLY_VERSION%%+*}" | |
| node scripts/set-build-version.mjs --version "$ASSET_VERSION" | |
| 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: Verify Relay image inputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test -f linux-release-assets/bitfun-relay-server-x86_64-unknown-linux-gnu.tar.gz | |
| test -f linux-release-assets/bitfun-relay-server-aarch64-unknown-linux-gnu.tar.gz | |
| for archive in linux-release-assets/bitfun-relay-server-*.tar.gz; do | |
| (cd linux-release-assets && sha256sum --check "$(basename "${archive}").sha256") | |
| done | |
| cp src/apps/relay-server/Dockerfile.release linux-release-assets/Dockerfile.release | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ github.token }} | |
| - name: Resolve nightly image metadata | |
| id: nightly-image-meta | |
| shell: bash | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: echo "asset_version=${NIGHTLY_VERSION%%+*}" >>"$GITHUB_OUTPUT" | |
| - name: Build and push multi-platform Relay image | |
| id: relay-image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: linux-release-assets | |
| file: linux-release-assets/Dockerfile.release | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| provenance: false | |
| sbom: false | |
| build-args: | | |
| VERSION=${{ needs.check-changes.outputs.nightly_version }} | |
| REVISION=${{ github.sha }} | |
| tags: | | |
| ghcr.io/gcwing/bitfun-relay-server:${{ env.NIGHTLY_TAG }} | |
| ghcr.io/gcwing/bitfun-relay-server:${{ steps.nightly-image-meta.outputs.asset_version }} | |
| - name: Smoke-test published Relay image on both platforms | |
| shell: bash | |
| env: | |
| IMAGE_DIGEST: ${{ steps.relay-image.outputs.digest }} | |
| run: bash scripts/relay/smoke-image.sh \ | |
| "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" | |
| - name: Generate signed Relay image descriptor | |
| shell: bash | |
| env: | |
| IMAGE_DIGEST: ${{ steps.relay-image.outputs.digest }} | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| 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 | |
| asset_version="${NIGHTLY_VERSION%%+*}" | |
| [[ "${IMAGE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]] | |
| docker buildx imagetools inspect \ | |
| "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" \ | |
| --raw >relay-image-manifest.json | |
| jq -e ' | |
| [.manifests[].platform | .os + "/" + .architecture] as $platforms | |
| | ($platforms | index("linux/amd64")) != null | |
| and ($platforms | index("linux/arm64")) != null | |
| ' relay-image-manifest.json >/dev/null | |
| jq -n \ | |
| --arg image "ghcr.io/gcwing/bitfun-relay-server" \ | |
| --arg tag "${NIGHTLY_TAG}" \ | |
| --arg version "${asset_version}" \ | |
| --arg digest "${IMAGE_DIGEST}" \ | |
| '{ | |
| schema_version: 1, | |
| image: $image, | |
| tag: $tag, | |
| version: $version, | |
| digest: $digest, | |
| platforms: ["linux/amd64", "linux/arm64"] | |
| }' >relay-image.json | |
| bash scripts/sign-release-assets.sh relay-image.json | |
| test -s relay-image.json.sig | |
| - name: Verify anonymous Relay image access | |
| shell: bash | |
| env: | |
| IMAGE_DIGEST: ${{ steps.relay-image.outputs.digest }} | |
| run: | | |
| set -euo pipefail | |
| docker logout ghcr.io >/dev/null 2>&1 || true | |
| clean_config="$(mktemp -d)" | |
| trap 'rm -rf "$clean_config"' EXIT | |
| DOCKER_CONFIG="$clean_config" docker buildx imagetools inspect \ | |
| "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" >/dev/null | |
| - 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: Prepare versioned Windows installer | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: | | |
| node scripts/prepare-windows-installer-asset.mjs \ | |
| --assets-dir release-assets \ | |
| --version "${NIGHTLY_VERSION%%+*}" \ | |
| --out-dir release-manual-assets | |
| # 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' \) | |
| find release-manual-assets -type f -name '*.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. | |
| node scripts/write-minisign-public-key.mjs \ | |
| --out 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/**/*.sig | |
| release-manual-assets/*.exe | |
| release-manual-assets/*.exe.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 | |
| relay-image.json | |
| relay-image.json.sig | |
| - 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 Relay image descriptor | |
| shell: bash | |
| env: | |
| NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} | |
| run: | | |
| set -euo pipefail | |
| curl -fsSL --retry 5 --retry-delay 3 \ | |
| "https://github.com/GCWing/BitFun/releases/download/${NIGHTLY_TAG}/relay-image.json" \ | |
| -o relay-image.published.json | |
| test "$(jq -r '.tag' relay-image.published.json)" = "${NIGHTLY_TAG}" | |
| test "$(jq -r '.version' relay-image.published.json)" = "${NIGHTLY_VERSION%%+*}" | |
| test "$(jq -r '.image' relay-image.published.json)" = "ghcr.io/gcwing/bitfun-relay-server" | |
| jq -e '.digest | test("^sha256:[0-9a-f]{64}$")' relay-image.published.json >/dev/null | |
| curl -fsSL --retry 5 --retry-delay 3 \ | |
| "https://github.com/GCWing/BitFun/releases/download/${NIGHTLY_TAG}/relay-image.json.sig" \ | |
| -o /dev/null | |
| - 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 |