feat(FN-5605): add linux gpg signing for release artifacts

Implements Linux GPG signing for the release pipeline by adding a `sign-linux.sh` helper, wiring it into the release and test-release workflows, including `.asc` signature files in release collectors, and documenting the full signing workflow in CODE_SIGNING.md with a note in the desktop README.

Fusion-Task-Id: FN-5605

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5605
This commit is contained in:
gsxdsm
2026-05-27 02:17:40 -07:00
parent d767e2ecbd
commit 294efb02fd
6 changed files with 241 additions and 7 deletions

View File

@@ -357,8 +357,20 @@ Desktop packaging is configured in `electron-builder.yml`.
- macOS: `Fusion-<version>-mac-arm64.dmg`, `Fusion-<version>-mac-x64.dmg`, matching `.zip` variants, `.sha256` sidecars, and `.blockmap` files.
- Linux: `Fusion-<version>-linux-x64.AppImage` with `.sha256`, plus best-effort `.deb` and `.tar.gz` outputs (and sidecars) when available on the runner image.
- Tag-less release rehearsal workflow (`.github/workflows/test-release.yml`) mirrors that artifact collection path without publishing a real GitHub Release.
- macOS Developer ID signing/notarization and Linux signing are not wired in these release jobs yet; release-attached macOS/Linux artifacts are currently unsigned and follow-up tasks will add signing flows.
- Linux desktop artifacts now include detached GPG signature sidecars: `*.AppImage.asc`, `*.deb.asc`, and `*.tar.gz.asc` when Linux signing secrets are configured in CI.
- Linux `.deb` and `.tar.gz` outputs are best-effort and may be absent on some runner images without failing the release.
### Linux signing
Every signed Linux desktop release includes `*.asc` detached signature sidecars alongside the binary artifacts.
Verification example:
```bash
gpg --import KEYS && gpg --verify Fusion-<version>-linux-x64.AppImage.asc Fusion-<version>-linux-x64.AppImage
```
The public key (`KEYS`) must be distributed out-of-band. See `docs/CODE_SIGNING.md` for canonical setup, key publication, and troubleshooting guidance.
- Isolated manual Windows build path: `.github/workflows/desktop-windows.yml` (`workflow_dispatch` on `windows-latest`) runs `electron-builder --win --x64 --arm64 --publish never`.
- ARM64 artifacts are cross-built on the `windows-latest` x64 runner; execution/validation still requires a Windows ARM64 device or emulator.
- Deep link protocol: `fusion://`

View File

@@ -59,3 +59,44 @@ describe("desktop release workflow wiring", () => {
);
});
});
describe("desktop linux signing wiring", () => {
it("wires Linux GPG secret-guarded signing and asc uploads in both workflows", async () => {
const release = await readRepoFile(".github/workflows/release.yml");
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
for (const workflow of [release, testRelease]) {
expect(workflow).toContain("secrets.LINUX_GPG_PRIVATE_KEY");
expect(workflow).toContain("secrets.LINUX_GPG_PASSPHRASE");
expect(workflow).toContain("secrets.LINUX_GPG_KEY_ID");
expect(workflow).toContain("LINUX_GPG_PRIVATE_KEY != ''");
expect(workflow).toContain("scripts/sign-linux.sh");
expect(workflow).toContain("Fusion-*-linux-*.AppImage.asc");
expect(workflow).toContain("Fusion-*-linux-*.deb.asc");
expect(workflow).toContain("Fusion-*-linux-*.tar.gz.asc");
expect(workflow).not.toMatch(/echo\s+["']?\$\{?\s*(secrets\.)?LINUX_GPG_PASSPHRASE/);
expect(workflow).not.toMatch(/cat\s+["']?\$\{?\s*(secrets\.)?LINUX_GPG_PASSPHRASE/);
}
});
it("collectors in both workflows include asc signatures", async () => {
const release = await readRepoFile(".github/workflows/release.yml");
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
expect(release).toContain('-name "*.asc"');
expect(testRelease).toContain('-name "*.asc"');
});
it("sign-linux.sh contains expected guarded gpg sign and verify shape", async () => {
const script = await readRepoFile("scripts/sign-linux.sh");
expect(script).toContain("set -euo pipefail");
expect(script).toContain("LINUX_GPG_PRIVATE_KEY");
expect(script).toContain("LINUX_GPG_PASSPHRASE");
expect(script).toContain("LINUX_GPG_KEY_ID");
expect(script).toContain("gpg --verify");
expect(script).toContain("--detach-sign");
expect(script).toContain("--armor");
expect(script).toContain("Linux signing skipped (LINUX_GPG_PRIVATE_KEY not set)");
});
});