From 6b2b73fdf91362b1fabf85b7997ba20978412fad Mon Sep 17 00:00:00 2001 From: Orca Date: Mon, 10 Aug 2026 23:10:08 +0800 Subject: [PATCH 1/3] ci(nightly): rolling nightly desktop build pinned to main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scheduled workflow (18:17 UTC ≈ 02:17 TPE) + manual dispatch. Builds the macOS app from the tip of main and republishes a single rolling `nightly` pre-release (durable download URL, unlike 7-day run artifacts). Forward-compatible: builds + stages the oab-mcp sidecar (unused on pre-sidecar main revisions). Not auto-update: that additionally needs a Tauri updater signing key + latest.json. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/nightly.yml | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/nightly.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..c7a4a79 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,75 @@ +name: nightly + +# Nightly desktop build, pinned to the tip of the default branch (main). +# Scheduled workflows run only from the default branch and check out main by +# default, so this always builds "latest master". Publishes a single rolling +# `nightly` pre-release (durable download URL, unlike 7-day run artifacts). +# +# Auto-update (the installed app pulling this) is a follow-on: it additionally +# needs a Tauri updater signing key + a latest.json on the release. This job +# only builds + publishes the .dmg. + +on: + schedule: + - cron: "17 18 * * *" # 18:17 UTC ≈ 02:17 Asia/Taipei + workflow_dispatch: + +concurrency: + group: nightly + cancel-in-progress: true + +permissions: + contents: write # publish the rolling nightly release + +env: + CARGO_TERM_COLOR: always + +jobs: + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 # default ref = main (scheduled) + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Build the web skin + run: | + npm --prefix console ci + npm --prefix console run build + - uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-apple-darwin + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . -> target + src-tauri -> target + # Build the oab-mcp sidecar and stage it for `externalBin`. Harmless on + # main revisions that predate the sidecar (the binary is simply unused). + - name: Build oab-mcp sidecar (arm64) + run: | + cargo build --release -p oab-mcp --target aarch64-apple-darwin + mkdir -p src-tauri/binaries + cp target/aarch64-apple-darwin/release/oab-mcp \ + src-tauri/binaries/oab-mcp-aarch64-apple-darwin + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli@^2 + - name: Bundle (.app + .dmg) + working-directory: src-tauri + run: tauri build --target aarch64-apple-darwin + - name: Publish rolling nightly pre-release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + sha="$(git rev-parse --short HEAD)" + stamp="$(date -u +%Y-%m-%dT%H:%MZ)" + dmg="$(ls src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg | head -1)" + echo "publishing $dmg for main@$sha" + # Roll the single `nightly` release forward to this commit. + gh release delete nightly --yes --cleanup-tag 2>/dev/null || true + gh release create nightly "$dmg" \ + --prerelease \ + --target "$GITHUB_SHA" \ + --title "Nightly ($sha)" \ + --notes "Automated nightly build of \`main\` @ \`$sha\` ($stamp). Native arm64, unsigned/ad-hoc — first launch: right-click → Open, or \`xattr -dr com.apple.quarantine\`." From 2671a3798ecdce2e45f97a522c9ee0e1102405fb Mon Sep 17 00:00:00 2001 From: Brett Chien Date: Mon, 10 Aug 2026 23:32:26 +0800 Subject: [PATCH 2/3] =?UTF-8?q?ci(nightly):=20also=20trigger=20on=20push?= =?UTF-8?q?=20to=20main=20(every=20merge=20=E2=86=92=20rebuild=20nightly)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A merged PR is a push to main, so add a push trigger; the daily schedule stays as a safety net and workflow_dispatch for manual runs. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/nightly.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c7a4a79..f4fc0c6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -10,8 +10,10 @@ name: nightly # only builds + publishes the .dmg. on: + push: + branches: [main] # every merge to main → rebuild the rolling nightly schedule: - - cron: "17 18 * * *" # 18:17 UTC ≈ 02:17 Asia/Taipei + - cron: "17 18 * * *" # 18:17 UTC ≈ 02:17 Asia/Taipei (safety net) workflow_dispatch: concurrency: From 66c324c24ed4e78ad2869f36aa0a33d0724ca45b Mon Sep 17 00:00:00 2001 From: Brett Chien Date: Mon, 10 Aug 2026 23:43:24 +0800 Subject: [PATCH 3/3] ci(nightly): sign bundle + publish latest.json for in-app updater MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the checked-out app enables updater artifacts (createUpdaterArtifacts + pubkey), sign the .app.tar.gz with the repo signing key and publish latest.json on the rolling nightly release so the in-app 檢查更新 button can pull it. Stamps a monotonic 0.1.0-nightly. version so the updater can compare builds. Forward-compatible: inert on pre-updater main (no tarball → latest.json skipped). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/nightly.yml | 64 +++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f4fc0c6..69df513 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,9 +5,11 @@ name: nightly # default, so this always builds "latest master". Publishes a single rolling # `nightly` pre-release (durable download URL, unlike 7-day run artifacts). # -# Auto-update (the installed app pulling this) is a follow-on: it additionally -# needs a Tauri updater signing key + a latest.json on the release. This job -# only builds + publishes the .dmg. +# When the checked-out app carries updater config (bundle.createUpdaterArtifacts +# + plugins.updater.pubkey — landing with the remote-upgrade feature), this also +# signs the bundle and publishes `latest.json` so the installed app's "檢查更新" +# button can pull it. On pre-updater revisions of main those steps are inert (no +# .app.tar.gz is produced), so the workflow stays forward-compatible. on: push: @@ -34,6 +36,16 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + # Give each nightly a monotonic version so the updater can tell builds + # apart (a static 0.1.0 would never compare as "newer"). Prerelease-tagged + # so it sorts below any real 0.1.0 release. No-op for update detection on + # pre-updater main, but harmless. + - name: Stamp a monotonic nightly version + run: | + ver="0.1.0-nightly.$(date -u +%Y%m%d%H%M)" + node -e "const fs=require('fs');const f='src-tauri/tauri.conf.json';const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version='$ver';fs.writeFileSync(f,JSON.stringify(j,null,2)+'\n')" + echo "NIGHTLY_VERSION=$ver" >> "$GITHUB_ENV" + echo "stamped $ver" - name: Build the web skin run: | npm --prefix console ci @@ -56,22 +68,52 @@ jobs: src-tauri/binaries/oab-mcp-aarch64-apple-darwin - name: Install Tauri CLI run: npm install -g @tauri-apps/cli@^2 - - name: Bundle (.app + .dmg) + # Signing keys are only consumed when the app opts into updater artifacts + # (bundle.createUpdaterArtifacts). Absent that, they're ignored. + - name: Bundle (.app + .dmg, signed when updater-enabled) working-directory: src-tauri + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: tauri build --target aarch64-apple-darwin - - name: Publish rolling nightly pre-release + - name: Publish rolling nightly pre-release (+ latest.json when signed) env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail sha="$(git rev-parse --short HEAD)" - stamp="$(date -u +%Y-%m-%dT%H:%MZ)" - dmg="$(ls src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg | head -1)" - echo "publishing $dmg for main@$sha" - # Roll the single `nightly` release forward to this commit. + stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + base="src-tauri/target/aarch64-apple-darwin/release/bundle" + dmg="$(ls "$base"/dmg/*.dmg | head -1)" + # The updater consumes the .app.tar.gz (+ .sig), produced only when the + # app enables updater artifacts. May be absent on pre-updater main. + tarball="$(ls "$base"/macos/*.app.tar.gz 2>/dev/null | head -1 || true)" + sig="${tarball:+${tarball}.sig}" + + assets=("$dmg") + [ -n "$tarball" ] && assets+=("$tarball") + + echo "publishing for main@$sha: ${assets[*]}" gh release delete nightly --yes --cleanup-tag 2>/dev/null || true - gh release create nightly "$dmg" \ + gh release create nightly "${assets[@]}" \ --prerelease \ --target "$GITHUB_SHA" \ --title "Nightly ($sha)" \ - --notes "Automated nightly build of \`main\` @ \`$sha\` ($stamp). Native arm64, unsigned/ad-hoc — first launch: right-click → Open, or \`xattr -dr com.apple.quarantine\`." + --notes "Automated nightly build of \`main\` @ \`$sha\` ($stamp). Native arm64, ad-hoc signed — first launch: right-click → Open, or \`xattr -dr com.apple.quarantine\`." + + # Updater manifest: only when we actually produced a signed tarball. + if [ -n "$tarball" ] && [ -f "$sig" ]; then + url="$(gh release view nightly --json assets \ + -q '.assets[] | select(.name|endswith(".app.tar.gz")) | .url')" + jq -n \ + --arg v "${NIGHTLY_VERSION:-0.1.0}" \ + --arg d "$stamp" \ + --arg u "$url" \ + --arg s "$(cat "$sig")" \ + '{version:$v, pub_date:$d, platforms:{"darwin-aarch64":{signature:$s, url:$u}}}' \ + > latest.json + gh release upload nightly latest.json --clobber + echo "published latest.json ($NIGHTLY_VERSION) → $url" + else + echo "no updater artifacts (pre-updater build) — skipped latest.json" + fi