feat(FN-5592): merge fusion/fn-5592

This commit is contained in:
gsxdsm
2026-05-26 08:29:28 -07:00
parent 40f7cad3e0
commit a4628c8649
5 changed files with 79 additions and 2 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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`)

View File

@@ -70,6 +70,11 @@ win:
target:
- target: nsis
- target: portable
signtoolOptions:
signingHashAlgorithms:
- sha256
rfc3161TimeStampServer: http://timestamp.digicert.com
publisherName: Fusion
nsis:
oneClick: false

View File

@@ -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");
});
});