feat(FN-5592): merge fusion/fn-5592
This commit is contained in:
29
.github/workflows/desktop-windows.yml
vendored
29
.github/workflows/desktop-windows.yml
vendored
@@ -25,13 +25,38 @@ jobs:
|
||||
- name: Build desktop package
|
||||
run: pnpm --filter @fusion/desktop build
|
||||
|
||||
# Code-signing via WINDOWS_CERTIFICATE_BASE64 / WINDOWS_CERTIFICATE_PASSWORD is intentionally deferred to a follow-up task.
|
||||
- name: Package Windows artifacts
|
||||
- name: Package signed Windows EXE
|
||||
if: ${{ env.WINDOWS_CERTIFICATE_BASE64 != '' }}
|
||||
run: pnpm --filter @fusion/desktop exec electron-builder --win --publish never
|
||||
env:
|
||||
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
|
||||
CSC_LINK: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Package unsigned Windows EXE
|
||||
if: ${{ env.WINDOWS_CERTIFICATE_BASE64 == '' }}
|
||||
run: pnpm --filter @fusion/desktop exec electron-builder --win --publish never
|
||||
env:
|
||||
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify signed artifacts
|
||||
if: ${{ env.WINDOWS_CERTIFICATE_BASE64 != '' }}
|
||||
shell: pwsh
|
||||
env:
|
||||
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
|
||||
run: |
|
||||
$exes = Get-ChildItem packages/desktop/dist-electron -Filter *.exe
|
||||
if ($exes.Count -eq 0) { Write-Error "No EXE artifacts produced"; exit 1 }
|
||||
foreach ($exe in $exes) {
|
||||
$sig = Get-AuthenticodeSignature $exe.FullName
|
||||
Write-Host "$($exe.Name): $($sig.Status)"
|
||||
if ($sig.Status -ne 'Valid') { Write-Error "Signature invalid: $($exe.Name) ($($sig.Status))"; exit 1 }
|
||||
}
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -109,6 +109,8 @@ Paste the result as the `WINDOWS_CERTIFICATE_BASE64` secret.
|
||||
|
||||
The test-release workflow (`workflow_dispatch`) includes the same signing steps but guards them with secret-availability checks — signing is skipped if secrets are not configured.
|
||||
|
||||
Desktop Windows packaging (`.github/workflows/desktop-windows.yml`) now uses the same `WINDOWS_CERTIFICATE_BASE64` / `WINDOWS_CERTIFICATE_PASSWORD` secrets to sign NSIS and portable EXE artifacts via electron-builder (`CSC_LINK` / `CSC_KEY_PASSWORD`) and validates signatures with `Get-AuthenticodeSignature` when signing is enabled.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### macOS: "The signature of the binary is invalid"
|
||||
@@ -141,3 +143,9 @@ The test-release workflow (`workflow_dispatch`) includes the same signing steps
|
||||
|
||||
- In the test-release workflow, signing is intentionally skipped when secrets are not configured
|
||||
- Verify the secrets are set at the repository level in **Settings → Secrets and variables → Actions**
|
||||
|
||||
### Desktop EXE signing skipped
|
||||
|
||||
- The desktop Windows workflow intentionally falls back to unsigned artifacts when `WINDOWS_CERTIFICATE_BASE64` is empty
|
||||
- In that unsigned path, `Verify signed artifacts` is skipped by design
|
||||
- Set both `WINDOWS_CERTIFICATE_BASE64` and `WINDOWS_CERTIFICATE_PASSWORD` at the repository level to enable signed desktop EXE output
|
||||
|
||||
@@ -357,6 +357,18 @@ Desktop packaging is configured in `electron-builder.yml`.
|
||||
|
||||
Run `pnpm --filter @fusion/desktop build` before `pack`/`dist` to ensure `dist/` assets are up to date.
|
||||
|
||||
### Windows code-signing
|
||||
|
||||
The Windows desktop workflow (`.github/workflows/desktop-windows.yml`) supports conditional Authenticode signing for NSIS + portable EXE outputs.
|
||||
|
||||
- Required CI secrets:
|
||||
- `WINDOWS_CERTIFICATE_BASE64` (base64-encoded `.pfx`)
|
||||
- `WINDOWS_CERTIFICATE_PASSWORD`
|
||||
- Signing is handled directly by electron-builder using `CSC_LINK` and `CSC_KEY_PASSWORD`; no separate `signtool` wrapper script is invoked in this workflow.
|
||||
- If signing secrets are not available (for example, forked PR contexts), the workflow still succeeds and uploads unsigned artifacts; the `Verify signed artifacts` step is skipped.
|
||||
- Signing policy is pinned in `electron-builder.yml` (`sha256` digest + `http://timestamp.digicert.com`) to match `scripts/sign-windows.ps1` used by CLI binaries.
|
||||
- Local signing is opt-in: developers who need signed local Windows builds must set `CSC_LINK`/`CSC_KEY_PASSWORD` in their own environment before invoking electron-builder.
|
||||
|
||||
## Environment
|
||||
|
||||
- `FUSION_DASHBOARD_URL` — override the default dashboard URL in development mode (`http://localhost:5173`)
|
||||
|
||||
@@ -70,6 +70,11 @@ win:
|
||||
target:
|
||||
- target: nsis
|
||||
- target: portable
|
||||
signtoolOptions:
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
rfc3161TimeStampServer: http://timestamp.digicert.com
|
||||
publisherName: Fusion
|
||||
|
||||
nsis:
|
||||
oneClick: false
|
||||
|
||||
@@ -27,6 +27,16 @@ describe("electron-builder windows config", () => {
|
||||
expect(builderConfig).toMatch(/productName:\s*Fusion/m);
|
||||
});
|
||||
|
||||
it("locks windows signing policy without baked certificate paths", async () => {
|
||||
const builderConfig = await readDesktopFile("electron-builder.yml");
|
||||
|
||||
expect(builderConfig).toMatch(/signtoolOptions:\s*[\s\S]*?signingHashAlgorithms:\s*[\s\S]*?-\s*sha256/m);
|
||||
expect(builderConfig).toMatch(/signtoolOptions:\s*[\s\S]*?rfc3161TimeStampServer:\s*http:\/\/timestamp\.digicert\.com/m);
|
||||
expect(builderConfig).toMatch(/signtoolOptions:\s*[\s\S]*?publisherName:\s*Fusion/m);
|
||||
expect(builderConfig).not.toContain("certificateFile:");
|
||||
expect(builderConfig).not.toContain("certificateSubjectName:");
|
||||
});
|
||||
|
||||
it("exposes a dedicated dist:win script", async () => {
|
||||
const packageJsonRaw = await readDesktopFile("package.json");
|
||||
const packageJson = JSON.parse(packageJsonRaw) as { scripts?: Record<string, string> };
|
||||
@@ -34,3 +44,20 @@ describe("electron-builder windows config", () => {
|
||||
expect(packageJson.scripts?.["dist:win"]).toBe("electron-builder --win");
|
||||
});
|
||||
});
|
||||
|
||||
describe("desktop windows workflow signing guards", () => {
|
||||
it("references signing secrets and verification flow", async () => {
|
||||
const workflow = await readFile(
|
||||
path.resolve(desktopRoot, "../../.github/workflows/desktop-windows.yml"),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
expect(workflow).toContain("secrets.WINDOWS_CERTIFICATE_BASE64");
|
||||
expect(workflow).toContain("secrets.WINDOWS_CERTIFICATE_PASSWORD");
|
||||
expect(workflow).toContain("CSC_LINK:");
|
||||
expect(workflow).toContain("CSC_KEY_PASSWORD:");
|
||||
expect(workflow).toContain("WINDOWS_CERTIFICATE_BASE64 != ''");
|
||||
expect(workflow).toContain("Get-AuthenticodeSignature");
|
||||
expect(workflow).not.toContain("intentionally deferred");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user