diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 54f22c7ce7..87705c9a1c 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -155,8 +155,22 @@ jobs: # → notarytool authentication for the notarization upload # When the secrets aren't set (forks, local), electron-builder # silently produces an unsigned build. - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + # + # CSC_LINK / CSC_KEY_PASSWORD MUST stay scoped to the mac legs. + # Despite the name used here, CSC_LINK is electron-builder's + # cross-platform certificate variable: WinPackager falls back to it + # whenever WIN_CSC_LINK is unset, via + # platformPackager.getCscLink("WIN_CSC_LINK"). Handing the Apple + # Developer ID .p12 to the windows leg made electron-builder read its + # subject and write `publisherName: Developer ID Application: ...` + # into app-update.yml, so every Windows client rejected the update it + # had just downloaded with "New version is not signed by the + # application owner". Empty string is the documented "no certificate" + # value — WindowsSignToolManager treats `cscLink === ""` exactly like + # unset. The APPLE_* vars below need no such guard: they are only + # read by notarytool on darwin and are inert elsewhere. + CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }} + CSC_KEY_PASSWORD: ${{ matrix.platform == 'mac' && secrets.CSC_KEY_PASSWORD || '' }} APPLE_API_KEY: ${{ env.APPLE_API_KEY_PATH }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} diff --git a/apps/desktop/electron-builder.config.ts b/apps/desktop/electron-builder.config.ts index 2766833954..831b92a5a8 100644 --- a/apps/desktop/electron-builder.config.ts +++ b/apps/desktop/electron-builder.config.ts @@ -46,6 +46,27 @@ const config: Configuration = { // artifact only exists once a leg stages an arm64 executor for it. win: { target: ["nsis"], + // There is no Windows code-signing certificate yet, so these installers + // ship unsigned, and this flag records that fact for the updater. + // + // Left at its `true` default, electron-builder writes a `publisherName` + // into app-update.yml derived from the signing certificate's subject CN + // (PublishManager.getAppUpdatePublishConfiguration, gated on + // WinPackager.isForceCodeSigningVerification). electron-updater then + // requires every downloaded installer to carry an Authenticode signature + // matching that name and refuses the update otherwise + // (NsisUpdater.verifySignature). An unsigned build that still claims a + // publisher can therefore never update itself. + // + // With this false no `publisherName` is emitted, `verifySignature` + // returns null early, and unsigned installers update as intended. It does + // not disable signing: `isForceCodeSigningVerification` is only ever read + // when computing the updater manifest. + // + // Delete this line when a real Windows certificate lands (signtoolOptions + // or azureSignOptions); leaving it would keep verification off for a + // signed build. + verifyUpdateCodeSignature: false, }, nsis: { oneClick: true,