From 7a6215d03d36cbdc23b904384e4337a538eafbc8 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Sat, 30 May 2026 23:32:45 +0200 Subject: [PATCH] refactor(windows-signing): make signCommand opt-in via override config Pinning bundle.windows.signCommand in the base tauri.conf.json broke from-source builds: Tauri tries to invoke a signtool path that only exists on the maintainer's machine, with a cert thumbprint they don't have. The build-windows-check CI job already worked around this by passing --config '{"bundle":{"windows":{"signCommand":null}}}'. If CI needs that override, every external builder needs it too. Invert: base config has no signCommand. Local signed builds opt in by copying tauri.windows.signing.example.json to tauri.windows.signing.json (gitignored), filling in local values, and running tauri:build:signed-win which deep-merges the override via --config. - Remove signCommand from src-tauri/tauri.conf.json - Add src-tauri/tauri.windows.signing.example.json (template, committed) - Gitignore src-tauri/tauri.windows.signing.json - Add npm script tauri:build:signed-win - Drop the --config signCommand:null workaround from build-windows-check - Rewrite README signing section to reflect the override flow --- .../workflows/reusable-channel-publish.yml | 7 ++--- .gitignore | 3 +++ README.md | 26 ++++++++++++------- package.json | 1 + src-tauri/tauri.conf.json | 11 -------- src-tauri/tauri.windows.signing.example.json | 18 +++++++++++++ 6 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 src-tauri/tauri.windows.signing.example.json diff --git a/.github/workflows/reusable-channel-publish.yml b/.github/workflows/reusable-channel-publish.yml index 27eca640..05b115b5 100644 --- a/.github/workflows/reusable-channel-publish.yml +++ b/.github/workflows/reusable-channel-publish.yml @@ -328,8 +328,9 @@ jobs: # Compile + bundle the Windows NSIS installer on every release to catch breakage # (Windows-only Rust cfg, Tauri config drift, NSIS template issues). Nothing is - # uploaded and signing is disabled — see comment on build-macos for why CI cannot - # sign Windows. The maintainer produces and uploads the signed installer locally. + # uploaded — see comment on build-macos for why CI cannot sign Windows. The base + # tauri.conf.json has no signCommand, so this produces an unsigned installer + # naturally; the maintainer signs and uploads locally via npm run tauri:build:signed-win. build-windows-check: if: ${{ inputs.build_platform_artifacts }} needs: create-release @@ -359,7 +360,7 @@ jobs: VITE_LASTFM_API_KEY: ${{ secrets.VITE_LASTFM_API_KEY }} VITE_LASTFM_API_SECRET: ${{ secrets.VITE_LASTFM_API_SECRET }} run: | - npm run tauri:build -- --bundles nsis --config '{"bundle":{"windows":{"signCommand":null}}}' + npm run tauri:build -- --bundles nsis generate-manifest: if: ${{ inputs.build_platform_artifacts }} diff --git a/.gitignore b/.gitignore index 25277893..6d075017 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ src-tauri/target/ src-tauri/gen/ src-tauri/lcov.info +# Local Windows signing override (copy from tauri.windows.signing.example.json) +src-tauri/tauri.windows.signing.json + # Frontend test coverage coverage/ diff --git a/README.md b/README.md index e4c2126d..e5ad1eaa 100644 --- a/README.md +++ b/README.md @@ -184,21 +184,27 @@ npm run tauri:build ## Signed Windows installer (maintainer, manual) -CI builds a Windows bundle on every release as a compile check, but does not sign or publish it — the Certum SimplySign Cloud OV cert requires interactive OTP login and cannot be driven from a hosted runner. The signed installer that ships in a release is therefore produced locally by the maintainer: +CI builds a Windows bundle on every release as a compile check, but does not sign or publish it — the code-signing cert this project uses (Certum SimplySign Cloud OV) requires interactive OTP login and cannot be driven from a hosted runner. The signed installer that ships in a release is therefore produced locally by the maintainer. -1. Start **SimplySign Desktop** and log in (OTP via SimplySign Mobile). This makes the cloud cert available in `Cert:\CurrentUser\My`. -2. `npm install && npm run tauri:build -- --bundles nsis`. The `--bundles nsis` flag is required — Tauri's default would also build an MSI, which rejects the `-dev` pre-release identifier. Tauri's `bundle.windows.signCommand` invokes `signtool` for the app `.exe`, every NSIS plugin DLL, and the final installer — all come out signed in one go. -3. Verify the installer (which contains the signed app `.exe`): +`src-tauri/tauri.conf.json` intentionally does **not** pin a `signCommand`, so a regular `npm run tauri:build` works for everyone building from source (just produces an unsigned installer). Signing is opt-in via a separate override config: - ```powershell - & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.28000.0\x64\signtool.exe" verify /pa /v ` - "src-tauri\target\release\bundle\nsis\Psysonic_*-setup.exe" +1. Copy `src-tauri/tauri.windows.signing.example.json` to `src-tauri/tauri.windows.signing.json` (gitignored) and fill in your local `signtool` path, timestamp URL and cert SHA1 thumbprint. The object form (`{ "cmd": ..., "args": [...] }`) is required — Tauri's string form splits on whitespace and breaks if the `signtool` path contains spaces. +2. Make the cert available. For SimplySign, start **SimplySign Desktop** and complete OTP login so the cert shows up under `Cert:\CurrentUser\My`. +3. Build with the override merged in: + + ```bash + npm run tauri:build:signed-win ``` - Note: `src-tauri\target\release\psysonic.exe` ends up unsigned on disk — Tauri rewrites that file as a build artifact *after* signing. The signed copy is the one packed into the installer (verify by extracting the installer with 7-Zip if needed). -4. Upload the signed `*-setup.exe` to the GitHub release. + That's a shortcut for `npm run tauri:build -- --bundles nsis --config src-tauri/tauri.windows.signing.json`. `--bundles nsis` is required because Tauri's default would also build an MSI, which rejects the `-dev` pre-release identifier. `signtool` is invoked for the app `.exe`, every NSIS plugin DLL, and the final installer — all come out signed in one pass. +4. Verify the signed installer: -The signtool path and cert thumbprint are pinned in `src-tauri/tauri.conf.json` under `bundle.windows.signCommand` (object form — required because the signtool path contains whitespace). The cert renews ~2027-04-27. + ```powershell + signtool verify /pa /v "src-tauri\target\release\bundle\nsis\Psysonic_*-setup.exe" + ``` + + Note: `src-tauri\target\release\psysonic.exe` ends up unsigned on disk — Tauri rewrites that file as a build artifact *after* signing. The signed copy is the one packed into the installer (extract with 7-Zip if you need to verify it directly). +5. Upload the signed `*-setup.exe` to the GitHub release. --- diff --git a/package.json b/package.json index 6b584aa6..6737080f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "tauri": "tauri", "tauri:dev": "tauri dev", "tauri:build": "tauri build", + "tauri:build:signed-win": "tauri build --bundles nsis --config src-tauri/tauri.windows.signing.json", "test": "vitest run && npm run check:css-imports", "test:watch": "vitest", "test:coverage": "vitest run --coverage && npm run check:css-imports" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d54aae7f..b36e9b5c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -68,17 +68,6 @@ "minimumSystemVersion": "10.15" }, "windows": { - "signCommand": { - "cmd": "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\10.0.28000.0\\x64\\signtool.exe", - "args": [ - "sign", - "/tr", "http://time.certum.pl", - "/td", "sha256", - "/fd", "sha256", - "/sha1", "76992CED8F2236C5D674FBD8B111CD625F19269E", - "%1" - ] - }, "nsis": { "installMode": "currentUser" } diff --git a/src-tauri/tauri.windows.signing.example.json b/src-tauri/tauri.windows.signing.example.json new file mode 100644 index 00000000..815f940b --- /dev/null +++ b/src-tauri/tauri.windows.signing.example.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "bundle": { + "windows": { + "signCommand": { + "cmd": "REPLACE_WITH_FULL_PATH_TO_SIGNTOOL_EXE", + "args": [ + "sign", + "/tr", "REPLACE_WITH_TIMESTAMP_URL", + "/td", "sha256", + "/fd", "sha256", + "/sha1", "REPLACE_WITH_CERT_THUMBPRINT_HEX", + "%1" + ] + } + } + } +}