diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8628bf75..52ae2102 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,13 +87,28 @@ jobs: - name: Build renderer assets run: pnpm run build + # electron-builder takes APPLE_API_KEY as a file path, not the key text. + - name: Write App Store Connect API key + if: matrix.platform == 'mac' + env: + APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }} + run: | + KEY_PATH="$RUNNER_TEMP/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8" + printf '%s' "$APPLE_API_KEY_P8" > "$KEY_PATH" + echo "APPLE_API_KEY=$KEY_PATH" >> "$GITHUB_ENV" + shell: bash + - name: Build release packages - # No signing certificate yet: mac.identity "-" in package.json forces ad-hoc signing so - # Apple Silicon builds launch. electron-builder only consults CSC_IDENTITY_AUTO_DISCOVERY - # when identity is unset, so it has no effect here - left unset for clarity, not because - # it changes this build. + # The Windows job must not see CSC_LINK: it would try to sign the NSIS installer with + # the Apple .p12. run: ${{ matrix.build_command }} shell: bash + env: + CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }} + CSC_KEY_PASSWORD: ${{ matrix.platform == 'mac' && secrets.CSC_KEY_PASSWORD || '' }} + APPLE_API_KEY_ID: ${{ matrix.platform == 'mac' && secrets.APPLE_API_KEY_ID || '' }} + APPLE_API_ISSUER: ${{ matrix.platform == 'mac' && secrets.APPLE_API_ISSUER || '' }} + APPLE_TEAM_ID: ${{ matrix.platform == 'mac' && secrets.APPLE_TEAM_ID || '' }} # The mac job packages x64 and arm64 from one node_modules, and pnpm only materialises # the runner's own architecture, so a missing native binary is invisible until a user @@ -102,14 +117,54 @@ jobs: run: node test/verify-packaged-sharp.mjs release shell: bash - - name: Upload build artifacts - uses: actions/upload-artifact@v4 + # Each installer is its own unzipped artifact so one build can be downloaded and tested + # without pulling the whole platform set. archive: false takes exactly one file and names + # the artifact after it, hence one step per glob. + - name: Upload Windows installer + if: matrix.platform == 'win' + uses: actions/upload-artifact@v7 + with: + path: release/*.exe + archive: false + if-no-files-found: error + + - name: Upload macOS arm64 DMG + if: matrix.platform == 'mac' + uses: actions/upload-artifact@v7 + with: + path: release/*-arm64.dmg + archive: false + if-no-files-found: error + + - name: Upload macOS x64 DMG + if: matrix.platform == 'mac' + uses: actions/upload-artifact@v7 with: - name: release-${{ matrix.platform }} + path: release/*-x64.dmg + archive: false + if-no-files-found: error + + - name: Upload macOS arm64 update zip + if: matrix.platform == 'mac' + uses: actions/upload-artifact@v7 + with: + path: release/*-arm64.zip + archive: false + if-no-files-found: error + + - name: Upload macOS x64 update zip + if: matrix.platform == 'mac' + uses: actions/upload-artifact@v7 + with: + path: release/*-x64.zip + archive: false + if-no-files-found: error + + - name: Upload update metadata + uses: actions/upload-artifact@v7 + with: + name: release-meta-${{ matrix.platform }} path: | - release/*.exe - release/*.dmg - release/*.zip release/*.yml release/*.yaml release/*.blockmap @@ -128,10 +183,22 @@ jobs: with: ref: ${{ needs.prepare.outputs.sha }} - - name: Download artifacts - uses: actions/download-artifact@v4 + # The installers were uploaded unzipped. Without skip-decompress the mac update .zip files + # would be extracted instead of uploaded to the release as-is. + - name: Download installers + uses: actions/download-artifact@v8 + with: + pattern: Power* + path: release-artifacts + merge-multiple: true + skip-decompress: true + + - name: Download update metadata + uses: actions/download-artifact@v8 with: + pattern: release-meta-* path: release-artifacts + merge-multiple: true - name: Tag and publish release env: diff --git a/README.md b/README.md index fc39b4d0..41036cc3 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,10 @@ Power Interview desktop client is supported on: Release binaries are published on the [GitHub Releases](https://github.com/PowerInterviewAI/client-app/releases) page. -### Unsigned builds +### Code signing -Release artifacts are not yet code-signed, so the OS will warn on first launch: - -- **macOS**: the app is ad-hoc signed (so it runs on Apple Silicon) but not notarized. If you see "app is damaged" or it is blocked, run `xattr -cr "/Applications/Power Interview AI.app"`, or right-click the app and choose Open. -- **Windows**: SmartScreen shows "Windows protected your PC". Click "More info" then "Run anyway". +- **macOS**: release builds are signed with a Developer ID Application certificate and notarized by Apple, so they open without a Gatekeeper warning. The release workflow needs the `CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_API_KEY_P8`, `APPLE_API_KEY_ID`, `APPLE_API_ISSUER` and `APPLE_TEAM_ID` repository secrets; `mac.forceCodeSigning` fails the build if the certificate is missing. Releases up to v1.9.4 were ad-hoc signed only: if one shows "app is damaged", run `xattr -cr "/Applications/Power Interview AI.app"`. +- **Windows**: the installer is not code-signed yet, so SmartScreen shows "Windows protected your PC". Click "More info" then "Run anyway". ## Architecture diff --git a/package.json b/package.json index ba82f037..41108e23 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "concurrently": "^9.2.1", "cross-env": "^10.1.0", "electron": "^40.1.0", - "electron-builder": "^26.15.3", + "electron-builder": "^26.17.0", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", @@ -147,9 +147,9 @@ "artifactName": "${productName}-${version}-${arch}.${ext}", "category": "public.app-category.productivity", "minimumSystemVersion": "14.4.0", - "identity": "-", - "hardenedRuntime": false, - "notarize": false, + "hardenedRuntime": true, + "notarize": true, + "forceCodeSigning": true, "entitlements": "build/entitlements.mac.plist", "entitlementsInherit": "build/entitlements.mac.inherit.plist", "extendInfo": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0275456..33bf5464 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,8 +142,8 @@ importers: specifier: ^40.1.0 version: 40.9.1 electron-builder: - specifier: ^26.15.3 - version: 26.15.3(electron-builder-squirrel-windows@26.15.3) + specifier: ^26.17.0 + version: 26.17.0(electron-builder-squirrel-windows@26.17.0) eslint: specifier: ^9.39.1 version: 9.39.2(jiti@2.7.0) @@ -751,9 +751,9 @@ packages: resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} - engines: {node: '>= 20.19.0'} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} '@peculiar/asn1-schema@2.8.0': resolution: {integrity: sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==} @@ -2044,6 +2044,7 @@ packages: '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version abbrev@4.0.0: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} @@ -2092,12 +2093,12 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - app-builder-lib@26.15.3: - resolution: {integrity: sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==} + app-builder-lib@26.17.0: + resolution: {integrity: sha512-wfL7EhujuODDW3uMSD8RPcr/MLzmT0dmpVfWV0wFUWXI0rIdaukzAJ7EFUSyHXOpxuBZqaIGlQt6VeQhtHhetw==} engines: {node: '>=14.0.0'} peerDependencies: - dmg-builder: 26.15.3 - electron-builder-squirrel-windows: 26.15.3 + dmg-builder: 26.17.0 + electron-builder-squirrel-windows: 26.17.0 argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2208,8 +2209,8 @@ packages: resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==} engines: {node: '>=12.0.0'} - builder-util@26.15.3: - resolution: {integrity: sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==} + builder-util@26.16.0: + resolution: {integrity: sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg==} engines: {node: '>=14.0.0'} bytestreamjs@2.0.1: @@ -2415,8 +2416,8 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dmg-builder@26.15.3: - resolution: {integrity: sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==} + dmg-builder@26.17.0: + resolution: {integrity: sha512-EAw1f2iGkICmUvAVBOjm7v5mxJdB7/+OZSycHFhKBimiTkCXtvkCC6wBKzHo7ZQYi58C2MmwIleRbgRnROlK2Q==} docx@9.5.1: resolution: {integrity: sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==} @@ -2454,16 +2455,16 @@ packages: peerDependencies: electron: '>=31.0.1' - electron-builder-squirrel-windows@26.15.3: - resolution: {integrity: sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==} + electron-builder-squirrel-windows@26.17.0: + resolution: {integrity: sha512-uyh+D+B0m12qCRp9c/7dwBirHTh7JcvAzj5eTEbb4dXPPGAhYPx1nozzbu1fC6AdULfat6ZNqgWhipTFpb82lQ==} - electron-builder@26.15.3: - resolution: {integrity: sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==} + electron-builder@26.17.0: + resolution: {integrity: sha512-iYHBRiagS9sDIbZx1ZD113f5rEGQvtpvTvHf70ovHKJ8mRvxwIkWX7JCwfmVQmVS4eX735p7TZmoxHnBPwF3vA==} engines: {node: '>=14.0.0'} hasBin: true - electron-publish@26.15.3: - resolution: {integrity: sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==} + electron-publish@26.16.0: + resolution: {integrity: sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q==} electron-store@11.0.2: resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==} @@ -3014,9 +3015,6 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} @@ -4729,7 +4727,7 @@ snapshots: '@noble/hashes@1.4.0': {} - '@noble/hashes@2.2.0': {} + '@noble/hashes@1.8.0': {} '@peculiar/asn1-schema@2.8.0': dependencies: @@ -6058,7 +6056,7 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 - app-builder-lib@26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3): + app-builder-lib@26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0): dependencies: '@electron/asar': 3.4.1 '@electron/fuses': 1.8.0 @@ -6068,23 +6066,23 @@ snapshots: '@electron/rebuild': 4.2.0 '@electron/universal': 2.0.3 '@malept/flatpak-bundler': 0.4.0 - '@noble/hashes': 2.2.0 + '@noble/hashes': 1.8.0 '@peculiar/webcrypto': 1.7.1 '@types/fs-extra': 9.0.13 ajv: 8.20.0 asn1js: 3.0.10 async-exit-hook: 2.0.1 - builder-util: 26.15.3 + builder-util: 26.16.0 builder-util-runtime: 9.7.0 chromium-pickle-js: 0.2.0 ci-info: 4.3.1 debug: 4.4.3(supports-color@5.5.0) - dmg-builder: 26.15.3(electron-builder-squirrel-windows@26.15.3) + dmg-builder: 26.17.0(electron-builder-squirrel-windows@26.17.0) dotenv: 16.6.1 dotenv-expand: 11.0.7 ejs: 3.1.10 - electron-builder-squirrel-windows: 26.15.3(dmg-builder@26.15.3) - electron-publish: 26.15.3 + electron-builder-squirrel-windows: 26.17.0(dmg-builder@26.17.0) + electron-publish: 26.16.0 fs-extra: 10.1.0 hosted-git-info: 4.1.0 isbinaryfile: 5.0.7 @@ -6219,7 +6217,7 @@ snapshots: transitivePeerDependencies: - supports-color - builder-util@26.15.3: + builder-util@26.16.0: dependencies: '@types/debug': 4.1.13 builder-util-runtime: 9.7.0 @@ -6444,10 +6442,10 @@ snapshots: minimatch: 3.1.5 p-limit: 3.1.0 - dmg-builder@26.15.3(electron-builder-squirrel-windows@26.15.3): + dmg-builder@26.17.0(electron-builder-squirrel-windows@26.17.0): dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3) - builder-util: 26.15.3 + app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0) + builder-util: 26.16.0 fs-extra: 10.1.0 js-yaml: 4.3.0 transitivePeerDependencies: @@ -6493,23 +6491,23 @@ snapshots: dependencies: electron: 40.9.1 - electron-builder-squirrel-windows@26.15.3(dmg-builder@26.15.3): + electron-builder-squirrel-windows@26.17.0(dmg-builder@26.17.0): dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3) - builder-util: 26.15.3 + app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0) + builder-util: 26.16.0 electron-winstaller: 5.4.0 transitivePeerDependencies: - dmg-builder - supports-color - electron-builder@26.15.3(electron-builder-squirrel-windows@26.15.3): + electron-builder@26.17.0(electron-builder-squirrel-windows@26.17.0): dependencies: - app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@26.15.3) - builder-util: 26.15.3 + app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0) + builder-util: 26.16.0 builder-util-runtime: 9.7.0 chalk: 4.1.2 ci-info: 4.4.0 - dmg-builder: 26.15.3(electron-builder-squirrel-windows@26.15.3) + dmg-builder: 26.17.0(electron-builder-squirrel-windows@26.17.0) fs-extra: 10.1.0 lazy-val: 1.0.5 simple-update-notifier: 2.0.0 @@ -6518,11 +6516,11 @@ snapshots: - electron-builder-squirrel-windows - supports-color - electron-publish@26.15.3: + electron-publish@26.16.0: dependencies: '@types/fs-extra': 9.0.13 aws4: 1.13.2 - builder-util: 26.15.3 + builder-util: 26.16.0 builder-util-runtime: 9.7.0 chalk: 4.1.2 form-data: 4.0.6 @@ -6818,7 +6816,7 @@ snapshots: fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-extra@11.3.1: @@ -7160,12 +7158,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -7696,11 +7688,11 @@ snapshots: node-abi@4.33.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 node-api-version@0.2.1: dependencies: - semver: 7.7.4 + semver: 7.8.5 node-gyp@12.4.0: dependencies: @@ -7709,7 +7701,7 @@ snapshots: graceful-fs: 4.2.11 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.4 + semver: 7.8.5 tar: 7.5.19 tinyglobby: 0.2.17 undici: 6.27.0 @@ -8228,7 +8220,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.5 sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: diff --git a/src/main/services/auto-updater.service.ts b/src/main/services/auto-updater.service.ts index f8c3fbbf..6af23146 100644 --- a/src/main/services/auto-updater.service.ts +++ b/src/main/services/auto-updater.service.ts @@ -150,10 +150,11 @@ class AutoUpdaterService { } } - // Squirrel.Mac (electron-updater's mac install mechanism) requires the app to be signed with - // a real Developer ID certificate. This build only ad-hoc signs (see package.json mac.identity), - // so silent apply is not viable on macOS - this checks GitHub releases directly instead and - // leaves installing to the user via a downloaded .dmg (see quitAndInstall). + // Squirrel.Mac (electron-updater's mac install mechanism) requires the installed app to be + // signed with a real Developer ID certificate. Releases are Developer ID signed since #145, but + // installs from before that are ad-hoc signed and cannot apply an update silently - this checks + // GitHub releases directly instead and leaves installing to the user via a downloaded .dmg (see + // quitAndInstall), which is the path those installs take to reach a signed build. private async checkForUpdatesMac(): Promise { this.updateCheckInProgress = true; this.notifyRenderer(UpdateStatus.Checking, null);