feat(desktop): wire macOS bundle updates and guarded feed publication
Route packaged macOS bundles and Light through the updater strategy. Use electron-updater 6.8.9 and wait for native signature acceptance before backend teardown. Keep checkout and Store ownership separate. Share Darwin feed paths between packaging, runtime and publication. Validate both native feeds, verify streamed artifact hashes, prevent same-tag artifact replacement, and conditionally update the channel pointer. Protect live feed references during canary retention. Use one notarization owner. Require publishing credentials and validate the stapled app. Keep Windows, Linux and Termux jobs unchanged. Verified with updater/feed unit and transport tests, release-helper tests, desktop typechecks, the desktop JS build, and workflow lint. No E2E, native macOS install, release dispatch or public publication was run.
This commit is contained in:
2
.github/workflows/canary-release.yml
vendored
2
.github/workflows/canary-release.yml
vendored
@@ -76,6 +76,8 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: python3 scripts/release.py --prune-canaries --publish --remote origin
|
||||
- name: Install locked feed tooling
|
||||
run: npm ci --workspaces=false --ignore-scripts --no-audit --no-fund
|
||||
- name: Prune R2 canary objects
|
||||
env:
|
||||
CLOUDFLARE_R2_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_R2_ACCOUNT_ID }}
|
||||
|
||||
58
.github/workflows/desktop-bundled-release.yml
vendored
58
.github/workflows/desktop-bundled-release.yml
vendored
@@ -492,7 +492,7 @@ jobs:
|
||||
needs: validate
|
||||
runs-on: ${{ matrix.target.runner }}
|
||||
environment: release-signing
|
||||
timeout-minutes: 900
|
||||
timeout-minutes: 180
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -513,14 +513,12 @@ jobs:
|
||||
CLOUDFLARE_R2_BUCKET: ${{ vars.CLOUDFLARE_R2_BUCKET }}
|
||||
CLOUDFLARE_R2_PUBLIC_URL: ${{ vars.CLOUDFLARE_R2_PUBLIC_URL }}
|
||||
steps:
|
||||
- name: Disable Spotlight indexing and XProtect
|
||||
- name: Disable Spotlight indexing for DMG staging
|
||||
# Spotlight indexes the freshly-mounted dmg staging image past
|
||||
# hdiutil's detach retries (per-VM, not a cross-job race).
|
||||
shell: bash
|
||||
run: |
|
||||
sudo mdutil -a -i off || true
|
||||
sudo pkill -9 XProtect >/dev/null || true
|
||||
while pgrep XProtect; do sleep 3; done
|
||||
|
||||
# Check out the SHA the validate job admitted — never the tag ref,
|
||||
# which a force-push can move between jobs. This is the privileged
|
||||
@@ -529,6 +527,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ needs.validate.outputs.sha }}
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve toolchain pins from pm/lock.json
|
||||
id: pins
|
||||
@@ -557,7 +556,7 @@ jobs:
|
||||
console.log(`builder=${eb}`)
|
||||
' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-node@v7.0.0 # immutable release. safe to pin.
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ steps.pins.outputs.node }}
|
||||
cache: npm
|
||||
@@ -693,21 +692,12 @@ jobs:
|
||||
uv tool install cmake==3.31.6
|
||||
echo "$(uv tool dir --bin)" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Derive the feed channel from the tag
|
||||
id: channel
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
case "$TAG" in
|
||||
*-canary.*) echo "channel=canary" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "channel=stable" >> "$GITHUB_OUTPUT" ;;
|
||||
esac
|
||||
|
||||
- name: Build and package
|
||||
shell: bash
|
||||
timeout-minutes: 900
|
||||
timeout-minutes: 160
|
||||
env:
|
||||
GITHUB_SHA: ${{ needs.validate.outputs.sha }}
|
||||
GITHUB_REF_NAME: ${{ inputs.tag }}
|
||||
PYTHONUTF8: '1'
|
||||
# electron-osx-sign*/electron-notarize* keep the sign+notarize
|
||||
# phase visible: without them NOTHING logs between "signing
|
||||
@@ -747,11 +737,13 @@ jobs:
|
||||
fi
|
||||
for app in "${apps[@]}"; do
|
||||
codesign --verify --strict --verbose=2 "$app"
|
||||
xcrun stapler validate "$app"
|
||||
spctl -a -vv -t exec "$app"
|
||||
echo "signed + notarized: $app"
|
||||
done
|
||||
|
||||
- name: Rename the feed yml per arch
|
||||
if: inputs.upload_release == true
|
||||
# electron-builder writes the channel feed yml (stable-mac.yml /
|
||||
# canary-mac.yml) with the SAME name on both legs; prefix the arch
|
||||
# so the publish job's merge-multiple download keeps both and
|
||||
@@ -763,24 +755,33 @@ jobs:
|
||||
shopt -s nullglob
|
||||
MATRIX_LABEL="${{ matrix.target.label }}"
|
||||
arch="${MATRIX_LABEL##*-}"
|
||||
channel="${{ steps.channel.outputs.channel }}"
|
||||
for f in apps/desktop/release/*-mac.yml; do
|
||||
mv "$f" "apps/desktop/release/${arch}-${channel}-mac.yml"
|
||||
echo "renamed: $(basename "$f") -> ${arch}-${channel}-mac.yml"
|
||||
done
|
||||
test -n "$(shopt -s nullglob; echo apps/desktop/release/*-mac.yml)"
|
||||
channel="${{ needs.validate.outputs.channel }}"
|
||||
f="apps/desktop/release/${channel}-mac.yml"
|
||||
test -s "$f"
|
||||
mv "$f" "apps/desktop/release/${arch}-${channel}-mac.yml"
|
||||
|
||||
- name: Upload artifacts
|
||||
- name: Upload feed metadata
|
||||
if: inputs.upload_release == true
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
# The per-arch feed ymls for publish-darwin-updater to merge. The
|
||||
# dmg/zip/blockmap binaries go straight to R2 from this leg.
|
||||
name: hermes-bundled-${{ matrix.target.label }}-${{ inputs.tag }}
|
||||
path: |
|
||||
apps/desktop/release/*-mac.yml
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Retain non-publishing build artifacts
|
||||
if: inputs.upload_release != true
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: hermes-bundled-${{ matrix.target.label }}-${{ inputs.tag }}
|
||||
path: |
|
||||
apps/desktop/release/*.dmg
|
||||
apps/desktop/release/*.zip
|
||||
apps/desktop/release/*.blockmap
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stage to Cloudflare R2
|
||||
if: inputs.upload_release == true
|
||||
shell: bash
|
||||
@@ -1131,6 +1132,9 @@ jobs:
|
||||
# Privileged job: pin to the SHA validate admitted, not the tag.
|
||||
ref: ${{ needs.validate.outputs.sha }}
|
||||
|
||||
- name: Install locked feed tooling
|
||||
run: npm ci --workspaces=false --ignore-scripts --no-audit --no-fund
|
||||
|
||||
- name: Download both darwin legs' feed ymls
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
@@ -1146,7 +1150,7 @@ jobs:
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
ymls=(staged/*-mac.yml)
|
||||
if [ ${#ymls[@]} -lt 2 ]; then
|
||||
if [ ${#ymls[@]} -ne 2 ]; then
|
||||
echo "::error::expected the arm64 AND x64 feed ymls in the staged artifacts, found ${#ymls[@]}: ${ymls[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user