diff --git a/.github/workflows/bundle-desktop-intel.yml b/.github/workflows/bundle-desktop-intel.yml index 54d76434ed..311ec965da 100644 --- a/.github/workflows/bundle-desktop-intel.yml +++ b/.github/workflows/bundle-desktop-intel.yml @@ -152,6 +152,10 @@ jobs: fi working-directory: ui/desktop + - name: Verify macOS updater resources + run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-x64/Goose.app" + working-directory: ui/desktop + - name: Clean up signing keychain if: always() run: | diff --git a/.github/workflows/bundle-desktop.yml b/.github/workflows/bundle-desktop.yml index 4b0073198d..c60df16a96 100644 --- a/.github/workflows/bundle-desktop.yml +++ b/.github/workflows/bundle-desktop.yml @@ -184,6 +184,10 @@ jobs: fi working-directory: ui/desktop + - name: Verify macOS updater resources + run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-arm64/Goose.app" + working-directory: ui/desktop + - name: Clean up signing keychain if: always() run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2af367473..158d940aec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,11 @@ permissions: pull-requests: write # Required for npm publish workflow attestations: write # Required for SLSA build provenance attestations +env: + # Set this repository Actions variable to "true" in GitHub Settings > Secrets and variables + # > Actions > Variables after a release containing desktop app-update.yml has shipped. + ENABLE_MAC_NATIVE_AUTO_UPDATE: ${{ vars.ENABLE_MAC_NATIVE_AUTO_UPDATE || 'false' }} + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -119,8 +124,15 @@ jobs: merge-multiple: true - name: Generate macOS update manifest + if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }} run: node ui/desktop/scripts/generate-mac-update-manifest.js --version "${GITHUB_REF_NAME}" --directory . + - name: Attest macOS update manifest + if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }} + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: latest-mac.yml + - name: Attest build provenance uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: @@ -132,7 +144,6 @@ jobs: *.deb *.rpm *.flatpak - latest-mac.yml download_cli.sh # Create/update the versioned release @@ -148,7 +159,6 @@ jobs: *.deb *.rpm *.flatpak - latest-mac.yml download_cli.sh allowUpdates: true omitBody: true @@ -169,8 +179,15 @@ jobs: *.deb *.rpm *.flatpak - latest-mac.yml download_cli.sh allowUpdates: true omitBody: true omitPrereleaseDuringUpdate: true + + - name: Upload macOS update manifest + if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${GITHUB_REF_NAME}" latest-mac.yml --clobber + gh release upload stable latest-mac.yml --clobber diff --git a/ui/desktop/forge.config.ts b/ui/desktop/forge.config.ts index c68ee433cd..6b88b98282 100644 --- a/ui/desktop/forge.config.ts +++ b/ui/desktop/forge.config.ts @@ -6,7 +6,7 @@ const isLinuxVulkanBuild = process.env.GOOSE_DESKTOP_LINUX_VARIANT === 'vulkan'; let cfg = { asar: true, - extraResource: ['src/bin', 'src/images'], + extraResource: ['src/bin', 'src/images', 'src/app-update.yml'], icon: 'src/images/icon', // Windows specific configuration win32: { diff --git a/ui/desktop/scripts/verify-mac-update-resources.js b/ui/desktop/scripts/verify-mac-update-resources.js new file mode 100644 index 0000000000..e75f9adc37 --- /dev/null +++ b/ui/desktop/scripts/verify-mac-update-resources.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const path = require('node:path'); + +function fail(message) { + console.error(message); + process.exit(1); +} + +const appPath = process.argv[2]; +if (!appPath) { + fail('Usage: node scripts/verify-mac-update-resources.js '); +} + +const updateConfigPath = path.join(appPath, 'Contents', 'Resources', 'app-update.yml'); +if (!fs.existsSync(updateConfigPath)) { + fail(`Missing ${updateConfigPath}`); +} + +const updateConfig = fs.readFileSync(updateConfigPath, 'utf8'); +const requiredLines = [ + 'provider: github', + 'owner: aaif-goose', + 'repo: goose', + 'updaterCacheDirName: goose-updater', +]; + +for (const line of requiredLines) { + if (!updateConfig.split(/\r?\n/).includes(line)) { + fail(`${updateConfigPath} is missing "${line}"`); + } +} + +console.log(`${updateConfigPath} is present and valid`); diff --git a/ui/desktop/src/app-update.yml b/ui/desktop/src/app-update.yml new file mode 100644 index 0000000000..47fc62049f --- /dev/null +++ b/ui/desktop/src/app-update.yml @@ -0,0 +1,4 @@ +provider: github +owner: aaif-goose +repo: goose +updaterCacheDirName: goose-updater