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
This commit is contained in:
Psychotoxical
2026-05-30 23:32:45 +02:00
parent 941562481f
commit 7a6215d03d
6 changed files with 42 additions and 24 deletions
@@ -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 }}
+3
View File
@@ -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/
+16 -10
View File
@@ -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.
---
+1
View File
@@ -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"
-11
View File
@@ -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"
}
@@ -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"
]
}
}
}
}