ci: build unsigned desktop artifacts without signing secrets (#1530)
This commit is contained in:
@@ -22,7 +22,7 @@ Kandev uses a **single SemVer** `X.Y.Z` shared across all distribution channels.
|
||||
|
||||
Entirely in CI via `.github/workflows/release.yml`, triggered by a maintainer from the GitHub Actions UI:
|
||||
|
||||
1. Maintainer clicks "Run workflow" → picks `bump` (patch/minor/major) → optional `dry_run`, `allow_unsigned_desktop`, or `desktop_validation_only`.
|
||||
1. Maintainer clicks "Run workflow" → picks `bump` (patch/minor/major) → optional `dry_run` or `desktop_validation_only`.
|
||||
2. `prepare` job bumps version + regenerates CHANGELOG, opens release PR, squash-merges, tags `vX.Y.Z`.
|
||||
3. `build-web` + `build-cli` + `build-bundles` (5 platforms) build the release artifacts.
|
||||
4. `publish-release` creates the GitHub release with platform tarballs + sha256 + auto-generated notes.
|
||||
@@ -31,7 +31,7 @@ Entirely in CI via `.github/workflows/release.yml`, triggered by a maintainer fr
|
||||
|
||||
There is no local release script — the entire flow runs in GHA.
|
||||
|
||||
`allow_unsigned_desktop=true` only disables macOS/Windows desktop signing enforcement. It still creates and publishes the normal release outputs unless `desktop_validation_only=true` is also selected. `desktop_validation_only=true` builds artifacts from the current workflow ref for maintainer inspection and skips the release PR, tag, GitHub release, npm publish, Homebrew update, and public container tags.
|
||||
Desktop signing is automatic. Complete macOS/Windows signing and notarization secrets produce signed artifacts; missing or incomplete signing inputs produce unsigned desktop artifacts and the GitHub release notes get an unsigned-artifact warning. `desktop_validation_only=true` builds artifacts from the current workflow ref for maintainer inspection and skips the release PR, tag, GitHub release, npm publish, Homebrew update, and public container tags.
|
||||
|
||||
## Runtime resolution
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@ name: release
|
||||
# Each package (kandev + 5 @kdlbs/runtime-* packages) must have this workflow
|
||||
# configured as its trusted publisher on npmjs.com (Settings → Publishing access).
|
||||
#
|
||||
# Required secrets:
|
||||
# Required secrets for public releases:
|
||||
# HOMEBREW_TAP_DEPLOY_KEY — SSH private key for kdlbs/homebrew-kandev deploy key
|
||||
# Optional secrets for signed desktop artifacts. If these are incomplete, the
|
||||
# workflow still builds unsigned desktop artifacts and warns in release notes.
|
||||
# APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD / KEYCHAIN_PASSWORD — macOS Developer ID signing
|
||||
# APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID or APPLE_API_KEY / APPLE_API_ISSUER / APPLE_API_KEY_P8 — macOS notarization
|
||||
# WINDOWS_CERTIFICATE / WINDOWS_CERTIFICATE_PASSWORD — Windows installer signing
|
||||
@@ -46,10 +48,6 @@ on:
|
||||
description: "Dry run (no PR, tag, npm publish, or tap update)"
|
||||
type: boolean
|
||||
default: false
|
||||
allow_unsigned_desktop:
|
||||
description: "Allow unsigned macOS/Windows desktop artifacts; still publishes unless validation-only is checked"
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_validation_only:
|
||||
description: "Build desktop artifacts only (no PR, tag, publish, npm, or tap update)"
|
||||
type: boolean
|
||||
@@ -279,10 +277,9 @@ jobs:
|
||||
NEXT: ${{ steps.compute.outputs.version }}
|
||||
TAG: ${{ steps.compute.outputs.tag }}
|
||||
REF: ${{ steps.compute.outputs.ref }}
|
||||
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }}
|
||||
run: |
|
||||
echo "Desktop validation for $TAG / $NEXT."
|
||||
echo "Unsigned desktop artifacts allowed: $ALLOW_UNSIGNED_DESKTOP."
|
||||
echo "Desktop signing is automatic: complete signing inputs produce signed artifacts; otherwise artifacts are unsigned."
|
||||
echo "No release PR, tag, GitHub release, public container tags, npm publish, or Homebrew update will be created."
|
||||
echo "Build jobs will check out $REF."
|
||||
|
||||
@@ -536,7 +533,6 @@ jobs:
|
||||
- name: Configure macOS signing and notarization
|
||||
if: startsWith(matrix.platform, 'macos-')
|
||||
env:
|
||||
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
@@ -550,37 +546,12 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then
|
||||
echo "::notice::Unsigned desktop artifacts requested; skipping macOS signing and notarization setup."
|
||||
if ! bash scripts/release/desktop-signing-ready.sh macos >/dev/null 2>&1; then
|
||||
echo "MACOS_SIGNING_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "::notice::macOS signing/notarization inputs are incomplete; building unsigned desktop artifact."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
missing=()
|
||||
for key in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD KEYCHAIN_PASSWORD; do
|
||||
if [ -z "${!key:-}" ]; then
|
||||
missing+=("$key")
|
||||
fi
|
||||
done
|
||||
|
||||
apple_id_notarization=0
|
||||
if [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then
|
||||
apple_id_notarization=1
|
||||
fi
|
||||
|
||||
api_key_notarization=0
|
||||
if [ -n "${APPLE_API_KEY:-}" ] && [ -n "${APPLE_API_ISSUER:-}" ] && [ -n "${APPLE_API_KEY_P8:-}" ]; then
|
||||
api_key_notarization=1
|
||||
fi
|
||||
|
||||
if [ "$apple_id_notarization" -eq 0 ] && [ "$api_key_notarization" -eq 0 ]; then
|
||||
missing+=("APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID or APPLE_API_KEY/APPLE_API_ISSUER/APPLE_API_KEY_P8")
|
||||
fi
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
echo "::error::Public macOS desktop releases require signing and notarization inputs: ${missing[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
certificate_path="$RUNNER_TEMP/kandev-developer-id.p12"
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
@@ -609,8 +580,9 @@ jobs:
|
||||
fi
|
||||
cert_id="$(echo "$cert_info" | awk -F'"' '{print $2}')"
|
||||
echo "APPLE_SIGNING_IDENTITY=$cert_id" >> "$GITHUB_ENV"
|
||||
echo "MACOS_SIGNING_ENABLED=true" >> "$GITHUB_ENV"
|
||||
|
||||
if [ "$api_key_notarization" -eq 1 ]; then
|
||||
if [ -n "${APPLE_API_KEY:-}" ] && [ -n "${APPLE_API_ISSUER:-}" ] && [ -n "${APPLE_API_KEY_P8:-}" ]; then
|
||||
private_keys_dir="$RUNNER_TEMP/private_keys"
|
||||
mkdir -p "$private_keys_dir"
|
||||
printf '%s\n' "$APPLE_API_KEY_P8" > "$private_keys_dir/AuthKey_${APPLE_API_KEY}.p8"
|
||||
@@ -618,7 +590,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Sign macOS desktop runtime binaries
|
||||
if: startsWith(matrix.platform, 'macos-') && !inputs.allow_unsigned_desktop
|
||||
if: startsWith(matrix.platform, 'macos-') && env.MACOS_SIGNING_ENABLED == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
runtime_bin="apps/desktop/src-tauri/resources/kandev/bin"
|
||||
@@ -632,8 +604,22 @@ jobs:
|
||||
codesign --verify --strict --verbose=2 "$path"
|
||||
done
|
||||
|
||||
- name: Detect Windows signing inputs
|
||||
if: matrix.platform == 'windows-x64'
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if bash scripts/release/desktop-signing-ready.sh windows >/dev/null 2>&1; then
|
||||
echo "WINDOWS_SIGNING_ENABLED=true" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "WINDOWS_SIGNING_ENABLED=false" >> "$GITHUB_ENV"
|
||||
echo "::notice::Windows signing inputs are incomplete; building unsigned desktop artifact."
|
||||
fi
|
||||
|
||||
- name: Sign Windows desktop runtime binaries
|
||||
if: matrix.platform == 'windows-x64' && !inputs.allow_unsigned_desktop
|
||||
if: matrix.platform == 'windows-x64' && env.WINDOWS_SIGNING_ENABLED == 'true'
|
||||
shell: pwsh
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
@@ -662,14 +648,13 @@ jobs:
|
||||
APPLE_PROVIDER_SHORT_NAME: ${{ secrets.APPLE_PROVIDER_SHORT_NAME }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }}
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }}
|
||||
WINDOWS_SIGNTOOL_PATH: ${{ secrets.WINDOWS_SIGNTOOL_PATH }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then
|
||||
if [ "${MACOS_SIGNING_ENABLED:-false}" != "true" ]; then
|
||||
unset APPLE_CERTIFICATE
|
||||
unset APPLE_CERTIFICATE_PASSWORD
|
||||
unset APPLE_ID
|
||||
@@ -680,6 +665,8 @@ jobs:
|
||||
unset APPLE_API_ISSUER
|
||||
unset APPLE_API_KEY_PATH
|
||||
unset APPLE_SIGNING_IDENTITY
|
||||
fi
|
||||
if [ "${WINDOWS_SIGNING_ENABLED:-false}" != "true" ]; then
|
||||
unset WINDOWS_CERTIFICATE
|
||||
unset WINDOWS_CERTIFICATE_PASSWORD
|
||||
unset WINDOWS_TIMESTAMP_URL
|
||||
@@ -693,7 +680,7 @@ jobs:
|
||||
--bundles "${{ matrix.tauri_bundles }}"
|
||||
|
||||
- name: Sign Windows desktop artifacts
|
||||
if: matrix.platform == 'windows-x64' && !inputs.allow_unsigned_desktop
|
||||
if: matrix.platform == 'windows-x64' && env.WINDOWS_SIGNING_ENABLED == 'true'
|
||||
shell: pwsh
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
@@ -701,15 +688,6 @@ jobs:
|
||||
WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }}
|
||||
WINDOWS_SIGNTOOL_PATH: ${{ secrets.WINDOWS_SIGNTOOL_PATH }}
|
||||
run: |
|
||||
$missing = @()
|
||||
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE)) { $missing += "WINDOWS_CERTIFICATE" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) { $missing += "WINDOWS_CERTIFICATE_PASSWORD" }
|
||||
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-Error "Public Windows desktop releases require signing inputs: $($missing -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$certificatePath = Join-Path $env:RUNNER_TEMP "kandev-code-signing.pfx"
|
||||
[IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE))
|
||||
|
||||
@@ -1185,13 +1163,40 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add unsigned desktop warning to release notes
|
||||
if: ${{ inputs.allow_unsigned_desktop }}
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
|
||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
unsigned=()
|
||||
if ! bash scripts/release/desktop-signing-ready.sh macos >/dev/null 2>&1; then
|
||||
unsigned+=("macOS")
|
||||
fi
|
||||
if ! bash scripts/release/desktop-signing-ready.sh windows >/dev/null 2>&1; then
|
||||
unsigned+=("Windows")
|
||||
fi
|
||||
if [ "${#unsigned[@]}" -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
platforms="${unsigned[0]}"
|
||||
for ((i = 1; i < ${#unsigned[@]}; i++)); do
|
||||
platforms="$platforms, ${unsigned[$i]}"
|
||||
done
|
||||
tmp="$(mktemp)"
|
||||
{
|
||||
echo "> [!WARNING]"
|
||||
echo "> macOS and Windows desktop installers in this release are unsigned."
|
||||
echo "> $platforms desktop installers in this release are unsigned development builds."
|
||||
echo "> They may require manual OS security bypasses and are not trusted downloads."
|
||||
echo
|
||||
cat RELEASE_NOTES.md
|
||||
|
||||
@@ -293,13 +293,14 @@ describe("release desktop artifacts", () => {
|
||||
tauri_bundles: deb,rpm`);
|
||||
});
|
||||
|
||||
it("supports unsigned desktop releases separately from validation-only builds", () => {
|
||||
it("builds unsigned desktop releases when signing inputs are incomplete", () => {
|
||||
const workflow = releaseWorkflow();
|
||||
const signingDocs = readRepoFile("docs/desktop-tauri-signing.md");
|
||||
const tauriConfig = readRepoFile("apps/desktop/src-tauri/tauri.conf.json");
|
||||
const windowsSignScript = readRepoFile("apps/desktop/src-tauri/windows-sign.ps1");
|
||||
|
||||
expect(workflow).toContain("allow_unsigned_desktop");
|
||||
expect(workflow).not.toContain("allow_unsigned_desktop");
|
||||
expect(workflow).not.toContain("ALLOW_UNSIGNED_DESKTOP");
|
||||
expect(workflow).toContain("desktop_validation_only");
|
||||
expect(workflow).toContain("ref: ${{ needs.prepare.outputs.ref }}");
|
||||
expect(workflow).toContain("persist-credentials: false");
|
||||
@@ -307,19 +308,24 @@ describe("release desktop artifacts", () => {
|
||||
expect(workflow).toContain("No release PR, tag, GitHub release, public container tags");
|
||||
expect(workflow).toContain("if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}");
|
||||
expect(workflow).toContain('if [ "$DESKTOP_VALIDATION_ONLY" = "true" ]; then');
|
||||
expect(workflow).toContain("scripts/release/desktop-signing-ready.sh macos");
|
||||
expect(workflow).toContain("scripts/release/desktop-signing-ready.sh windows");
|
||||
expect(workflow).toContain("MACOS_SIGNING_ENABLED=false");
|
||||
expect(workflow).toContain("MACOS_SIGNING_ENABLED=true");
|
||||
expect(workflow).toContain("WINDOWS_SIGNING_ENABLED=false");
|
||||
expect(workflow).toContain("WINDOWS_SIGNING_ENABLED=true");
|
||||
expect(workflow).toContain(
|
||||
"Allow unsigned macOS/Windows desktop artifacts; still publishes unless validation-only is checked",
|
||||
"macOS signing/notarization inputs are incomplete; building unsigned desktop artifact.",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"Windows signing inputs are incomplete; building unsigned desktop artifact.",
|
||||
);
|
||||
expect(workflow).toContain("Add unsigned desktop warning to release notes");
|
||||
expect(workflow).toContain(
|
||||
"macOS and Windows desktop installers in this release are unsigned.",
|
||||
);
|
||||
expect(workflow).not.toMatch(
|
||||
/if \[ "\$ALLOW_UNSIGNED_DESKTOP" = "true" \]; then\r?\n\s*echo "ref=\$CURRENT_REF"/,
|
||||
"desktop installers in this release are unsigned development builds.",
|
||||
);
|
||||
expect(workflow).toContain("docker-amd64:");
|
||||
expect(workflow).toContain("docker-universal-manifest:");
|
||||
expect(workflow).toContain('if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then');
|
||||
expect(workflow).toContain("unset APPLE_CERTIFICATE");
|
||||
expect(workflow).toContain("unset APPLE_ID");
|
||||
expect(workflow).toContain("unset WINDOWS_CERTIFICATE");
|
||||
@@ -362,12 +368,14 @@ describe("release desktop artifacts", () => {
|
||||
expect(tauriConfig).toContain("windows-sign.ps1");
|
||||
expect(tauriConfig).not.toContain('"csp": null');
|
||||
expect(windowsSignScript).toContain('"https://timestamp.digicert.com"');
|
||||
expect(windowsSignScript).toContain('WINDOWS_SIGNING_ENABLED -eq "false"');
|
||||
expect(windowsSignScript).not.toContain("ALLOW_UNSIGNED_DESKTOP");
|
||||
expect(windowsSignScript).toContain("Skipping Windows signing for unsigned desktop artifact");
|
||||
expect(windowsSignScript).toContain("Remove-Item -LiteralPath $certificatePath");
|
||||
expect(signingDocs).toContain("Public recommended desktop releases require signing");
|
||||
expect(signingDocs).toContain("allow_unsigned_desktop");
|
||||
expect(signingDocs).toContain("signs desktop artifacts opportunistically");
|
||||
expect(signingDocs).toContain("Missing or incomplete inputs do not block the release");
|
||||
expect(signingDocs).not.toContain("allow_unsigned_desktop");
|
||||
expect(signingDocs).toContain("desktop_validation_only");
|
||||
expect(signingDocs).toContain("publishes the normal release outputs");
|
||||
expect(signingDocs).toContain("does not publish a GitHub release");
|
||||
expect(signingDocs).toContain("public container tags");
|
||||
expect(signingDocs).toContain("Ubuntu 22.04");
|
||||
|
||||
@@ -32,4 +32,4 @@ Use `scripts/release/prepare-desktop-runtime.sh` and `scripts/release/verify-des
|
||||
|
||||
## Release
|
||||
|
||||
Desktop artifacts are built in `.github/workflows/release.yml` after the platform runtime bundles. Public recommended macOS and Windows artifacts require signing unless `allow_unsigned_desktop=true` is explicitly selected. Use `desktop_validation_only=true` for artifact-only validation runs that skip the release PR, tag, publish jobs, and public container tags.
|
||||
Desktop artifacts are built in `.github/workflows/release.yml` after the platform runtime bundles. macOS and Windows signing is automatic: complete signing/notarization secrets produce signed artifacts; missing or incomplete inputs produce unsigned desktop artifacts with a release-notes warning. Use `desktop_validation_only=true` for artifact-only validation runs that skip the release PR, tag, publish jobs, and public container tags.
|
||||
|
||||
@@ -5,7 +5,7 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($env:ALLOW_UNSIGNED_DESKTOP -eq "true") {
|
||||
if ($env:WINDOWS_SIGNING_ENABLED -eq "false") {
|
||||
Write-Warning "Skipping Windows signing for unsigned desktop artifact."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ Add a Tauri v2 desktop app as a thin native shell over the existing Kandev runti
|
||||
|
||||
The installed desktop app will not include a Node.js runtime. Node.js and pnpm remain build-time tooling for Vite, TypeScript, Tauri CLI invocation, tests, and release automation.
|
||||
|
||||
Desktop launches set the backend to a loopback-only bind address and an app-selected local port. Public recommended desktop releases require macOS Developer ID signing/notarization and Windows code signing before the artifacts are presented as trusted downloads; unsigned desktop artifacts require an explicit release workflow override. The first desktop implementation does not include Tauri's in-app updater; updates continue through GitHub release downloads/manual reinstall until a separate updater spec is approved.
|
||||
Desktop launches set the backend to a loopback-only bind address and an app-selected local port. Desktop release automation signs macOS and Windows artifacts when CI signing secrets are configured; otherwise it publishes unsigned desktop development artifacts with a release-notes warning. The first desktop implementation does not include Tauri's in-app updater; updates continue through GitHub release downloads/manual reinstall until a separate updater spec is approved.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+3
-3
@@ -14,9 +14,9 @@ Supported desktop platforms:
|
||||
- `linux-arm64`
|
||||
- `windows-x64`
|
||||
|
||||
Public recommended macOS artifacts must be Developer ID signed and notarized. Public recommended Windows artifacts must be code signed. Linux artifacts are checksum-gated; package-manager signatures can be added later.
|
||||
When signing inputs are configured, macOS artifacts are Developer ID signed and notarized and Windows artifacts are code signed. When those inputs are missing, the release still publishes unsigned desktop development builds with a release-notes warning. Linux artifacts are checksum-gated; package-manager signatures can be added later.
|
||||
|
||||
Unsigned macOS or Windows desktop artifacts may be produced only through an explicit release workflow override. They may require manual OS security bypasses and should not be presented as trusted downloads.
|
||||
Unsigned macOS or Windows desktop artifacts may require manual OS security bypasses and should not be presented as trusted downloads.
|
||||
|
||||
## Runtime Requirements
|
||||
|
||||
@@ -64,4 +64,4 @@ Existing explicit command paths in Kandev settings remain authoritative over `PA
|
||||
|
||||
## Release Trust
|
||||
|
||||
See [desktop-tauri-signing.md](desktop-tauri-signing.md) for the CI secrets and signing expectations used for trusted desktop releases.
|
||||
See [desktop-tauri-signing.md](desktop-tauri-signing.md) for the CI secrets and automatic signing behavior used for desktop releases.
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
# Tauri Desktop Signing
|
||||
|
||||
Public recommended desktop releases require signing on macOS and Windows. The release workflow fails closed by default when those secrets are missing.
|
||||
The release workflow signs desktop artifacts opportunistically. Complete macOS and Windows signing inputs produce signed/notarized artifacts. Missing or incomplete inputs do not block the release; those platform artifacts are built unsigned and the GitHub release notes get an unsigned-artifact warning.
|
||||
|
||||
Use `allow_unsigned_desktop=true` only when intentionally producing unsigned macOS/Windows desktop artifacts. Unless `desktop_validation_only=true` is also selected, that mode still publishes the normal release outputs: the release PR and tag, GitHub release, npm packages, Homebrew update, public container tags, and desktop artifacts. Unsigned desktop artifacts may require manual OS security bypasses and must not be presented as trusted downloads.
|
||||
|
||||
When unsigned desktop artifacts are published in a normal release, the workflow prefixes the GitHub release notes with an unsigned desktop warning.
|
||||
Unsigned desktop artifacts may require manual OS security bypasses and must not be presented as trusted downloads.
|
||||
|
||||
Use `desktop_validation_only=true` for maintainer inspection builds. That mode uploads workflow artifacts but does not publish a GitHub release, npm packages, Homebrew updates, or public container tags.
|
||||
|
||||
## macOS
|
||||
|
||||
Required signing secrets:
|
||||
Signing inputs:
|
||||
|
||||
- `APPLE_CERTIFICATE`: base64 `.p12` Developer ID Application certificate.
|
||||
- `APPLE_CERTIFICATE_PASSWORD`: export password for the `.p12`.
|
||||
- `KEYCHAIN_PASSWORD`: temporary CI keychain password.
|
||||
|
||||
Required notarization secrets, choose one path:
|
||||
Notarization inputs, choose one path:
|
||||
|
||||
- Apple ID path: `APPLE_ID`, `APPLE_PASSWORD`, `APPLE_TEAM_ID`.
|
||||
- App Store Connect API path: `APPLE_API_KEY`, `APPLE_API_ISSUER`, `APPLE_API_KEY_P8`.
|
||||
@@ -27,7 +25,7 @@ Optional:
|
||||
|
||||
## Windows
|
||||
|
||||
Required signing secrets:
|
||||
Signing inputs:
|
||||
|
||||
- `WINDOWS_CERTIFICATE`: base64 `.pfx` code signing certificate.
|
||||
- `WINDOWS_CERTIFICATE_PASSWORD`: export password for the `.pfx`.
|
||||
|
||||
@@ -166,7 +166,7 @@ Add signing inputs/secrets behind conditional release steps:
|
||||
- Windows: certificate/key-vault or custom signing command wiring.
|
||||
- Linux: checksum required; GPG/AppImage signing optional.
|
||||
|
||||
Unsigned desktop builds remain allowed only through explicit workflow overrides, but public recommended artifacts should require signing success on macOS and Windows.
|
||||
Desktop signing should be automatic when secrets are configured. If signing inputs are missing or incomplete, the workflow should still publish unsigned desktop artifacts with a release-notes warning.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ spec: "../../specs/desktop-tauri-app/spec.md"
|
||||
|
||||
- macOS desktop release builds support Developer ID signing and notarization through GitHub Actions secrets.
|
||||
- Windows desktop release builds support code signing through the selected signing mechanism.
|
||||
- Unsigned desktop artifacts require an explicit workflow override and are not treated as trusted public artifacts.
|
||||
- Missing signing inputs produce unsigned desktop artifacts with a release-notes warning.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Users who prefer a desktop workflow should be able to install and open Kandev fr
|
||||
- The desktop app launches the backend with a predictable process environment for GUI launches, including common user binary locations, so configured agent CLIs can be discovered as reliably as they are from terminal-launched Kandev.
|
||||
- Opening a second desktop instance focuses the existing app window or exits without starting a second backend for the same desktop launch context.
|
||||
- GitHub releases include desktop artifacts alongside the existing runtime tarballs and checksums.
|
||||
- Unsigned desktop artifacts may be produced only through explicit development, validation, or release workflow overrides. Public recommended desktop releases require macOS Developer ID signing/notarization and Windows code signing before they are presented as trusted user-facing downloads.
|
||||
- Desktop release automation signs macOS and Windows artifacts when the relevant CI secrets are configured. If signing inputs are missing or incomplete, the release still produces unsigned desktop development artifacts and release notes call out that limitation.
|
||||
- Desktop release notes and install docs tell users which artifact to download for each operating system and call out any unsigned/pre-release limitations.
|
||||
|
||||
## Data model
|
||||
@@ -82,9 +82,9 @@ The desktop shell passes an explicit environment to the native launcher:
|
||||
|
||||
Each released version includes platform desktop artifacts with checksums. Exact file names may follow Tauri's generated naming, but the release must expose these platform groups:
|
||||
|
||||
- macOS arm64: signed/notarized `.dmg` for recommended public release, unsigned `.dmg` allowed only through an explicit override.
|
||||
- macOS x64: signed/notarized `.dmg` for recommended public release, unsigned `.dmg` allowed only through an explicit override.
|
||||
- Windows x64: signed installer (`.exe` or `.msi`) for recommended public release, unsigned installer allowed only through an explicit override.
|
||||
- macOS arm64: signed/notarized `.dmg` when signing inputs are configured; otherwise unsigned `.dmg` with a release-notes warning.
|
||||
- macOS x64: signed/notarized `.dmg` when signing inputs are configured; otherwise unsigned `.dmg` with a release-notes warning.
|
||||
- Windows x64: signed installer (`.exe` or `.msi`) when signing inputs are configured; otherwise unsigned installer with a release-notes warning.
|
||||
- Linux x64: AppImage and/or distro package generated by Tauri, with checksum.
|
||||
- Linux arm64: AppImage and/or distro package generated by Tauri, with checksum.
|
||||
|
||||
@@ -128,7 +128,7 @@ Transitions:
|
||||
- If `/health` does not succeed before the configured timeout, the desktop app terminates the launched child process tree and shows a timeout error.
|
||||
- If the WebView runtime is unavailable or broken on the host OS, the app fails using the platform's normal Tauri/WebView error behavior; the backend process must not remain running.
|
||||
- If an agent CLI cannot be found from a GUI-launched environment, the desktop app/backend surfaces the same setup-health guidance as browser/CLI Kandev rather than failing silently.
|
||||
- If signing/notarization secrets are absent in CI, release automation may still build explicitly unsigned desktop artifacts, but trusted desktop artifacts are not marked complete.
|
||||
- If signing/notarization secrets are absent in CI, release automation builds unsigned desktop artifacts and marks the limitation in release notes.
|
||||
|
||||
## Persistence guarantees
|
||||
|
||||
|
||||
@@ -6,6 +6,19 @@ TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
OUT_FILE="$TMP_DIR/out"
|
||||
ERR_FILE="$TMP_DIR/err"
|
||||
SIGNING_SECRET_ENV=(
|
||||
-u APPLE_CERTIFICATE
|
||||
-u APPLE_CERTIFICATE_PASSWORD
|
||||
-u KEYCHAIN_PASSWORD
|
||||
-u APPLE_ID
|
||||
-u APPLE_PASSWORD
|
||||
-u APPLE_TEAM_ID
|
||||
-u APPLE_API_KEY
|
||||
-u APPLE_API_ISSUER
|
||||
-u APPLE_API_KEY_P8
|
||||
-u WINDOWS_CERTIFICATE
|
||||
-u WINDOWS_CERTIFICATE_PASSWORD
|
||||
)
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
@@ -16,6 +29,10 @@ pass() {
|
||||
echo "PASS: $*"
|
||||
}
|
||||
|
||||
signing_secret_env() {
|
||||
env "${SIGNING_SECRET_ENV[@]}" "$@"
|
||||
}
|
||||
|
||||
write_runtime() {
|
||||
local dir="$1"
|
||||
local helper="${2:-with-helper}"
|
||||
@@ -119,6 +136,48 @@ printf 'desktop artifact\n' > "$artifact"
|
||||
"$ROOT_DIR/scripts/release/verify-desktop-assets.sh" "$assets_dir" linux-x64 >/dev/null
|
||||
pass "verify-desktop-assets accepts matching checksums"
|
||||
|
||||
if signing_secret_env "$ROOT_DIR/scripts/release/desktop-signing-ready.sh" macos >"$OUT_FILE" 2>"$ERR_FILE"; then
|
||||
fail "desktop-signing-ready should report macOS signing not ready without secrets"
|
||||
fi
|
||||
grep -q "APPLE_CERTIFICATE" "$ERR_FILE" || fail "desktop-signing-ready did not report missing Apple certificate"
|
||||
grep -q "APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID" "$ERR_FILE" || fail "desktop-signing-ready did not report missing notarization inputs"
|
||||
pass "desktop-signing-ready reports incomplete macOS inputs"
|
||||
|
||||
signing_secret_env \
|
||||
APPLE_CERTIFICATE=cert \
|
||||
APPLE_CERTIFICATE_PASSWORD=cert-password \
|
||||
KEYCHAIN_PASSWORD=keychain-password \
|
||||
APPLE_ID=apple@example.test \
|
||||
APPLE_PASSWORD=apple-password \
|
||||
APPLE_TEAM_ID=TEAMID123 \
|
||||
"$ROOT_DIR/scripts/release/desktop-signing-ready.sh" macos >"$OUT_FILE"
|
||||
grep -q "complete for macos" "$OUT_FILE" || fail "desktop-signing-ready rejected Apple ID notarization inputs"
|
||||
pass "desktop-signing-ready accepts Apple ID notarization inputs"
|
||||
|
||||
signing_secret_env \
|
||||
APPLE_CERTIFICATE=cert \
|
||||
APPLE_CERTIFICATE_PASSWORD=cert-password \
|
||||
KEYCHAIN_PASSWORD=keychain-password \
|
||||
APPLE_API_KEY=api-key \
|
||||
APPLE_API_ISSUER=api-issuer \
|
||||
APPLE_API_KEY_P8=api-key-p8 \
|
||||
"$ROOT_DIR/scripts/release/desktop-signing-ready.sh" macos >"$OUT_FILE"
|
||||
grep -q "complete for macos" "$OUT_FILE" || fail "desktop-signing-ready rejected App Store Connect API notarization inputs"
|
||||
pass "desktop-signing-ready accepts API key notarization inputs"
|
||||
|
||||
if signing_secret_env "$ROOT_DIR/scripts/release/desktop-signing-ready.sh" windows >"$OUT_FILE" 2>"$ERR_FILE"; then
|
||||
fail "desktop-signing-ready should report Windows signing not ready without secrets"
|
||||
fi
|
||||
grep -q "WINDOWS_CERTIFICATE" "$ERR_FILE" || fail "desktop-signing-ready did not report missing Windows certificate"
|
||||
pass "desktop-signing-ready reports incomplete Windows inputs"
|
||||
|
||||
signing_secret_env \
|
||||
WINDOWS_CERTIFICATE=windows-cert \
|
||||
WINDOWS_CERTIFICATE_PASSWORD=windows-password \
|
||||
"$ROOT_DIR/scripts/release/desktop-signing-ready.sh" windows >"$OUT_FILE"
|
||||
grep -q "complete for windows" "$OUT_FILE" || fail "desktop-signing-ready rejected Windows signing inputs"
|
||||
pass "desktop-signing-ready accepts Windows signing inputs"
|
||||
|
||||
fallback_tools_dir="$TMP_DIR/sha256sum-tools"
|
||||
fallback_assets_dir="$TMP_DIR/sha256sum-assets"
|
||||
mkdir -p "$fallback_tools_dir" "$fallback_assets_dir"
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Return success when desktop signing inputs are complete for a platform family.
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: desktop-signing-ready.sh <macos|windows>
|
||||
|
||||
Checks whether the current environment has enough signing/notarization inputs
|
||||
to produce signed desktop artifacts for the requested platform family. Missing
|
||||
inputs are reported, but incomplete signing is not a workflow error by itself.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PLATFORM="${1:-}"
|
||||
missing=()
|
||||
|
||||
require_secret() {
|
||||
local name="$1"
|
||||
if [ -z "${!name:-}" ]; then
|
||||
missing+=("$name")
|
||||
fi
|
||||
}
|
||||
|
||||
case "$PLATFORM" in
|
||||
macos)
|
||||
require_secret APPLE_CERTIFICATE
|
||||
require_secret APPLE_CERTIFICATE_PASSWORD
|
||||
require_secret KEYCHAIN_PASSWORD
|
||||
|
||||
apple_id_notarization=false
|
||||
if [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then
|
||||
apple_id_notarization=true
|
||||
fi
|
||||
|
||||
api_key_notarization=false
|
||||
if [ -n "${APPLE_API_KEY:-}" ] && [ -n "${APPLE_API_ISSUER:-}" ] && [ -n "${APPLE_API_KEY_P8:-}" ]; then
|
||||
api_key_notarization=true
|
||||
fi
|
||||
|
||||
if [ "$apple_id_notarization" = "false" ] && [ "$api_key_notarization" = "false" ]; then
|
||||
missing+=("APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID or APPLE_API_KEY/APPLE_API_ISSUER/APPLE_API_KEY_P8")
|
||||
fi
|
||||
;;
|
||||
windows)
|
||||
require_secret WINDOWS_CERTIFICATE
|
||||
require_secret WINDOWS_CERTIFICATE_PASSWORD
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
printf 'Desktop signing inputs incomplete for %s: %s\n' "$PLATFORM" "${missing[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Desktop signing inputs complete for %s.\n' "$PLATFORM"
|
||||
Reference in New Issue
Block a user