fix: support unsigned desktop releases (#1520)

This commit is contained in:
Carlos Florêncio
2026-06-28 19:13:16 +01:00
committed by GitHub
parent 5c4fab2bd2
commit 14a5fb2c7f
11 changed files with 109 additions and 49 deletions
+3 -1
View File
@@ -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: 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`. 1. Maintainer clicks "Run workflow" → picks `bump` (patch/minor/major) → optional `dry_run`, `allow_unsigned_desktop`, or `desktop_validation_only`.
2. `prepare` job bumps version + regenerates CHANGELOG, opens release PR, squash-merges, tags `vX.Y.Z`. 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. 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. 4. `publish-release` creates the GitHub release with platform tarballs + sha256 + auto-generated notes.
@@ -31,6 +31,8 @@ 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. 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.
## Runtime resolution ## Runtime resolution
In `apps/cli/src/runtime.ts`, the CLI locates its bundled runtime via: In `apps/cli/src/runtime.ts`, the CLI locates its bundled runtime via:
+59 -27
View File
@@ -47,7 +47,11 @@ on:
type: boolean type: boolean
default: false default: false
allow_unsigned_desktop: allow_unsigned_desktop:
description: "Allow unsigned macOS/Windows desktop artifacts for internal validation only" 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 type: boolean
default: false default: false
@@ -108,7 +112,7 @@ jobs:
id: compute id: compute
env: env:
BUMP: ${{ inputs.bump }} BUMP: ${{ inputs.bump }}
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }} DESKTOP_VALIDATION_ONLY: ${{ inputs.desktop_validation_only }}
CURRENT_REF: ${{ github.sha }} CURRENT_REF: ${{ github.sha }}
run: | run: |
set -euo pipefail set -euo pipefail
@@ -159,7 +163,7 @@ jobs:
echo "Next tag: $TAG" echo "Next tag: $TAG"
echo "version=$NEXT" >> "$GITHUB_OUTPUT" echo "version=$NEXT" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "tag=$TAG" >> "$GITHUB_OUTPUT"
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then if [ "$DESKTOP_VALIDATION_ONLY" = "true" ]; then
echo "ref=$CURRENT_REF" >> "$GITHUB_OUTPUT" echo "ref=$CURRENT_REF" >> "$GITHUB_OUTPUT"
else else
echo "ref=$TAG" >> "$GITHUB_OUTPUT" echo "ref=$TAG" >> "$GITHUB_OUTPUT"
@@ -176,7 +180,7 @@ jobs:
fi fi
- name: Bump version + generate CHANGELOG (in working tree) - name: Bump version + generate CHANGELOG (in working tree)
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
env: env:
NEXT: ${{ steps.compute.outputs.version }} NEXT: ${{ steps.compute.outputs.version }}
TAG: ${{ steps.compute.outputs.tag }} TAG: ${{ steps.compute.outputs.tag }}
@@ -214,7 +218,7 @@ jobs:
git-cliff --tag "$TAG" -o CHANGELOG.md git-cliff --tag "$TAG" -o CHANGELOG.md
- name: Create release PR + squash-merge + tag - name: Create release PR + squash-merge + tag
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEXT: ${{ steps.compute.outputs.version }} NEXT: ${{ steps.compute.outputs.version }}
@@ -269,14 +273,16 @@ jobs:
} }
} }
- name: Unsigned desktop validation summary - name: Desktop validation summary
if: ${{ !inputs.dry_run && inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && inputs.desktop_validation_only }}
env: env:
NEXT: ${{ steps.compute.outputs.version }} NEXT: ${{ steps.compute.outputs.version }}
TAG: ${{ steps.compute.outputs.tag }} TAG: ${{ steps.compute.outputs.tag }}
REF: ${{ steps.compute.outputs.ref }} REF: ${{ steps.compute.outputs.ref }}
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }}
run: | run: |
echo "Unsigned desktop validation for $TAG / $NEXT." echo "Desktop validation for $TAG / $NEXT."
echo "Unsigned desktop artifacts allowed: $ALLOW_UNSIGNED_DESKTOP."
echo "No release PR, tag, GitHub release, public container tags, npm publish, or Homebrew update will be created." 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." echo "Build jobs will check out $REF."
@@ -544,6 +550,11 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then
echo "::notice::Unsigned desktop artifacts requested; skipping macOS signing and notarization setup."
exit 0
fi
missing=() missing=()
for key in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD KEYCHAIN_PASSWORD; do for key in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD KEYCHAIN_PASSWORD; do
if [ -z "${!key:-}" ]; then if [ -z "${!key:-}" ]; then
@@ -566,10 +577,6 @@ jobs:
fi fi
if [ "${#missing[@]}" -gt 0 ]; then if [ "${#missing[@]}" -gt 0 ]; then
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then
echo "::warning::Unsigned desktop artifacts are internal validation only; missing macOS signing inputs: ${missing[*]}"
exit 0
fi
echo "::error::Public macOS desktop releases require signing and notarization inputs: ${missing[*]}" >&2 echo "::error::Public macOS desktop releases require signing and notarization inputs: ${missing[*]}" >&2
exit 1 exit 1
fi fi
@@ -662,6 +669,22 @@ jobs:
WINDOWS_SIGNTOOL_PATH: ${{ secrets.WINDOWS_SIGNTOOL_PATH }} WINDOWS_SIGNTOOL_PATH: ${{ secrets.WINDOWS_SIGNTOOL_PATH }}
run: | run: |
set -euo pipefail set -euo pipefail
if [ "$ALLOW_UNSIGNED_DESKTOP" = "true" ]; then
unset APPLE_CERTIFICATE
unset APPLE_CERTIFICATE_PASSWORD
unset APPLE_ID
unset APPLE_PASSWORD
unset APPLE_TEAM_ID
unset APPLE_PROVIDER_SHORT_NAME
unset APPLE_API_KEY
unset APPLE_API_ISSUER
unset APPLE_API_KEY_PATH
unset APPLE_SIGNING_IDENTITY
unset WINDOWS_CERTIFICATE
unset WINDOWS_CERTIFICATE_PASSWORD
unset WINDOWS_TIMESTAMP_URL
unset WINDOWS_SIGNTOOL_PATH
fi
pnpm -C apps --filter @kandev/desktop build:vite pnpm -C apps --filter @kandev/desktop build:vite
cd apps/desktop cd apps/desktop
pnpm tauri build \ pnpm tauri build \
@@ -670,10 +693,9 @@ jobs:
--bundles "${{ matrix.tauri_bundles }}" --bundles "${{ matrix.tauri_bundles }}"
- name: Sign Windows desktop artifacts - name: Sign Windows desktop artifacts
if: matrix.platform == 'windows-x64' if: matrix.platform == 'windows-x64' && !inputs.allow_unsigned_desktop
shell: pwsh shell: pwsh
env: env:
ALLOW_UNSIGNED_DESKTOP: ${{ inputs.allow_unsigned_desktop }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }} WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }}
@@ -684,10 +706,6 @@ jobs:
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) { $missing += "WINDOWS_CERTIFICATE_PASSWORD" } if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) { $missing += "WINDOWS_CERTIFICATE_PASSWORD" }
if ($missing.Count -gt 0) { if ($missing.Count -gt 0) {
if ($env:ALLOW_UNSIGNED_DESKTOP -eq "true") {
Write-Warning "Unsigned desktop artifacts are internal validation only; missing Windows signing inputs: $($missing -join ', ')"
exit 0
}
Write-Error "Public Windows desktop releases require signing inputs: $($missing -join ', ')" Write-Error "Public Windows desktop releases require signing inputs: $($missing -join ', ')"
exit 1 exit 1
} }
@@ -803,7 +821,7 @@ jobs:
docker-amd64: docker-amd64:
name: Build & push container image (amd64) name: Build & push container image (amd64)
needs: [prepare, build-bundles] needs: [prepare, build-bundles]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -874,7 +892,7 @@ jobs:
docker-arm64: docker-arm64:
name: Build & push container image (arm64) name: Build & push container image (arm64)
needs: [prepare, build-bundles] needs: [prepare, build-bundles]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
permissions: permissions:
contents: read contents: read
@@ -933,7 +951,7 @@ jobs:
docker-manifest: docker-manifest:
name: Assemble multi-arch base manifest name: Assemble multi-arch base manifest
needs: [prepare, docker-amd64, docker-arm64] needs: [prepare, docker-amd64, docker-arm64]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -987,7 +1005,7 @@ jobs:
docker-universal-amd64: docker-universal-amd64:
name: Build & push universal image (amd64) name: Build & push universal image (amd64)
needs: [prepare, docker-manifest] needs: [prepare, docker-manifest]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -1039,7 +1057,7 @@ jobs:
docker-universal-arm64: docker-universal-arm64:
name: Build & push universal image (arm64) name: Build & push universal image (arm64)
needs: [prepare, docker-manifest] needs: [prepare, docker-manifest]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
permissions: permissions:
contents: read contents: read
@@ -1090,7 +1108,7 @@ jobs:
# docker-universal-manifest is included in publish-release's `needs` so # docker-universal-manifest is included in publish-release's `needs` so
# its size gate (and any per-arch build failure) blocks the release. # its size gate (and any per-arch build failure) blocks the release.
needs: [prepare, docker-universal-amd64, docker-universal-arm64] needs: [prepare, docker-universal-amd64, docker-universal-arm64]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -1147,7 +1165,7 @@ jobs:
publish-release: publish-release:
name: Publish GitHub release name: Publish GitHub release
needs: [prepare, build-bundles, build-desktop, docker-universal-manifest] needs: [prepare, build-bundles, build-desktop, docker-universal-manifest]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write # softprops/action-gh-release creates the release contents: write # softprops/action-gh-release creates the release
@@ -1166,6 +1184,20 @@ jobs:
OUTPUT: RELEASE_NOTES.md OUTPUT: RELEASE_NOTES.md
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Add unsigned desktop warning to release notes
if: ${{ inputs.allow_unsigned_desktop }}
run: |
set -euo pipefail
tmp="$(mktemp)"
{
echo "> [!WARNING]"
echo "> macOS and Windows desktop installers in this release are unsigned."
echo "> They may require manual OS security bypasses and are not trusted downloads."
echo
cat RELEASE_NOTES.md
} > "$tmp"
mv "$tmp" RELEASE_NOTES.md
- uses: actions/download-artifact@v5 - uses: actions/download-artifact@v5
with: with:
pattern: bundle-* pattern: bundle-*
@@ -1211,7 +1243,7 @@ jobs:
publish-npm: publish-npm:
name: Publish npm packages name: Publish npm packages
needs: [prepare, publish-release] needs: [prepare, publish-release]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
id-token: write # Required for OIDC trusted publishing id-token: write # Required for OIDC trusted publishing
@@ -1246,7 +1278,7 @@ jobs:
update-homebrew-tap: update-homebrew-tap:
name: Update Homebrew tap name: Update Homebrew tap
needs: [prepare, publish-release] needs: [prepare, publish-release]
if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }} if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
+25 -4
View File
@@ -252,21 +252,39 @@ describe("release desktop artifacts", () => {
tauri_bundles: deb,rpm`); tauri_bundles: deb,rpm`);
}); });
it("fails public macOS and Windows desktop builds closed when signing secrets are absent", () => { it("supports unsigned desktop releases separately from validation-only builds", () => {
const workflow = releaseWorkflow(); const workflow = releaseWorkflow();
const signingDocs = readRepoFile("docs/desktop-tauri-signing.md"); const signingDocs = readRepoFile("docs/desktop-tauri-signing.md");
const tauriConfig = readRepoFile("apps/desktop/src-tauri/tauri.conf.json"); const tauriConfig = readRepoFile("apps/desktop/src-tauri/tauri.conf.json");
const windowsSignScript = readRepoFile("apps/desktop/src-tauri/windows-sign.ps1"); const windowsSignScript = readRepoFile("apps/desktop/src-tauri/windows-sign.ps1");
expect(workflow).toContain("allow_unsigned_desktop"); expect(workflow).toContain("allow_unsigned_desktop");
expect(workflow).toContain("Unsigned desktop artifacts are internal validation only"); expect(workflow).toContain("desktop_validation_only");
expect(workflow).toContain("ref: ${{ needs.prepare.outputs.ref }}"); expect(workflow).toContain("ref: ${{ needs.prepare.outputs.ref }}");
expect(workflow).toContain("persist-credentials: false"); expect(workflow).toContain("persist-credentials: false");
expect(workflow).toContain("Unsigned desktop validation summary"); expect(workflow).toContain("Desktop validation summary");
expect(workflow).toContain("No release PR, tag, GitHub release, public container tags"); expect(workflow).toContain("No release PR, tag, GitHub release, public container tags");
expect(workflow).toContain("if: ${{ !inputs.dry_run && !inputs.allow_unsigned_desktop }}"); expect(workflow).toContain("if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }}");
expect(workflow).toContain('if [ "$DESKTOP_VALIDATION_ONLY" = "true" ]; then');
expect(workflow).toContain(
"Allow unsigned macOS/Windows desktop artifacts; still publishes unless validation-only is checked",
);
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"/,
);
expect(workflow).toContain("docker-amd64:"); expect(workflow).toContain("docker-amd64:");
expect(workflow).toContain("docker-universal-manifest:"); 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");
expect(workflow).toContain("unset WINDOWS_CERTIFICATE_PASSWORD");
expect(workflow).toContain("unset WINDOWS_TIMESTAMP_URL");
expect(workflow).toContain("unset WINDOWS_SIGNTOOL_PATH");
for (const key of [ for (const key of [
"APPLE_CERTIFICATE", "APPLE_CERTIFICATE",
@@ -303,9 +321,12 @@ describe("release desktop artifacts", () => {
expect(tauriConfig).toContain("windows-sign.ps1"); expect(tauriConfig).toContain("windows-sign.ps1");
expect(tauriConfig).not.toContain('"csp": null'); expect(tauriConfig).not.toContain('"csp": null');
expect(windowsSignScript).toContain('"https://timestamp.digicert.com"'); expect(windowsSignScript).toContain('"https://timestamp.digicert.com"');
expect(windowsSignScript).toContain("Skipping Windows signing for unsigned desktop artifact");
expect(windowsSignScript).toContain("Remove-Item -LiteralPath $certificatePath"); expect(windowsSignScript).toContain("Remove-Item -LiteralPath $certificatePath");
expect(signingDocs).toContain("Public recommended desktop releases require signing"); expect(signingDocs).toContain("Public recommended desktop releases require signing");
expect(signingDocs).toContain("allow_unsigned_desktop"); expect(signingDocs).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("does not publish a GitHub release");
expect(signingDocs).toContain("public container tags"); expect(signingDocs).toContain("public container tags");
expect(signingDocs).toContain("Ubuntu 22.04"); expect(signingDocs).toContain("Ubuntu 22.04");
+1 -1
View File
@@ -32,4 +32,4 @@ Use `scripts/release/prepare-desktop-runtime.sh` and `scripts/release/verify-des
## Release ## Release
Desktop artifacts are built in `.github/workflows/release.yml` after the platform runtime bundles. Public macOS and Windows artifacts require signing unless `allow_unsigned_desktop=true` is explicitly selected for internal validation. 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.
+5 -4
View File
@@ -5,15 +5,16 @@ param(
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
if ($env:ALLOW_UNSIGNED_DESKTOP -eq "true") {
Write-Warning "Skipping Windows signing for unsigned desktop artifact."
exit 0
}
$missing = @() $missing = @()
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE)) { $missing += "WINDOWS_CERTIFICATE" } if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE)) { $missing += "WINDOWS_CERTIFICATE" }
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) { $missing += "WINDOWS_CERTIFICATE_PASSWORD" } if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) { $missing += "WINDOWS_CERTIFICATE_PASSWORD" }
if ($missing.Count -gt 0) { if ($missing.Count -gt 0) {
if ($env:ALLOW_UNSIGNED_DESKTOP -eq "true") {
Write-Warning "Skipping Windows signing for internal unsigned desktop validation; missing: $($missing -join ', ')"
exit 0
}
throw "Public Windows desktop releases require signing inputs: $($missing -join ', ')" throw "Public Windows desktop releases require signing inputs: $($missing -join ', ')"
} }
+1 -1
View File
@@ -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. 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 desktop releases require macOS Developer ID signing/notarization and Windows code signing before the artifacts are presented as trusted downloads. 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. 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.
## Consequences ## Consequences
+1 -1
View File
@@ -16,7 +16,7 @@ Supported desktop platforms:
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. 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.
Unsigned macOS or Windows desktop artifacts are for internal validation only. They may require manual OS security bypasses and should not be presented as trusted public downloads. 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.
## Runtime Requirements ## Runtime Requirements
+5 -1
View File
@@ -2,7 +2,11 @@
Public recommended desktop releases require signing on macOS and Windows. The release workflow fails closed by default when those secrets are missing. Public recommended desktop releases require signing on macOS and Windows. The release workflow fails closed by default when those secrets are missing.
Use `allow_unsigned_desktop=true` only for internal validation builds. That mode may upload workflow artifacts for maintainer inspection, but it does not publish a GitHub release, npm packages, Homebrew updates, or public container tags. Unsigned desktop artifacts are internal validation only and must not be presented as trusted public downloads. 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.
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 ## macOS
+1 -1
View File
@@ -166,7 +166,7 @@ Add signing inputs/secrets behind conditional release steps:
- Windows: certificate/key-vault or custom signing command wiring. - Windows: certificate/key-vault or custom signing command wiring.
- Linux: checksum required; GPG/AppImage signing optional. - Linux: checksum required; GPG/AppImage signing optional.
Unsigned desktop builds remain allowed for dry runs and internal validation, but public recommended artifacts should require signing success on macOS and Windows. Unsigned desktop builds remain allowed only through explicit workflow overrides, but public recommended artifacts should require signing success on macOS and Windows.
--- ---
@@ -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. - 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. - Windows desktop release builds support code signing through the selected signing mechanism.
- Unsigned desktop artifacts are clearly limited to dry runs/internal validation and are not treated as trusted public artifacts. - Unsigned desktop artifacts require an explicit workflow override and are not treated as trusted public artifacts.
## Verification ## Verification
+7 -7
View File
@@ -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. - 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. - 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. - GitHub releases include desktop artifacts alongside the existing runtime tarballs and checksums.
- Unsigned desktop artifacts may be produced for development or internal validation, but public recommended desktop releases require macOS Developer ID signing/notarization and Windows code signing before they are presented as trusted user-facing downloads. - 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 notes and install docs tell users which artifact to download for each operating system and call out any unsigned/pre-release limitations. - 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 ## 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: 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 public release, unsigned `.dmg` allowed only for internal validation. - macOS arm64: signed/notarized `.dmg` for recommended public release, unsigned `.dmg` allowed only through an explicit override.
- macOS x64: signed/notarized `.dmg` for public release, unsigned `.dmg` allowed only for internal validation. - 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 public release, unsigned installer allowed only for internal validation. - Windows x64: signed installer (`.exe` or `.msi`) for recommended public release, unsigned installer allowed only through an explicit override.
- Linux x64: AppImage and/or distro package generated by Tauri, with checksum. - Linux x64: AppImage and/or distro package generated by Tauri, with checksum.
- Linux arm64: 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 `/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 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 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 internal artifacts, but public trusted desktop artifacts are not marked complete. - 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.
## Persistence guarantees ## Persistence guarantees
@@ -147,8 +147,8 @@ Transitions:
- **GIVEN** the user quits the desktop app, **WHEN** the app exits normally, **THEN** the launcher/backend child process tree is terminated. - **GIVEN** the user quits the desktop app, **WHEN** the app exits normally, **THEN** the launcher/backend child process tree is terminated.
- **GIVEN** a bundled backend binary is missing, **WHEN** the user opens the desktop app, **THEN** startup fails with a visible error naming the missing binary. - **GIVEN** a bundled backend binary is missing, **WHEN** the user opens the desktop app, **THEN** startup fails with a visible error naming the missing binary.
- **GIVEN** the release workflow builds desktop artifacts for a version, **WHEN** the GitHub release is published, **THEN** desktop installers and checksums are attached alongside the existing runtime tarballs. - **GIVEN** the release workflow builds desktop artifacts for a version, **WHEN** the GitHub release is published, **THEN** desktop installers and checksums are attached alongside the existing runtime tarballs.
- **GIVEN** a public macOS desktop release, **WHEN** the user downloads and opens the `.dmg`, **THEN** the app is signed and notarized so macOS does not report the download as damaged or from an unidentified developer. - **GIVEN** a recommended public macOS desktop release, **WHEN** the user downloads and opens the `.dmg`, **THEN** the app is signed and notarized so macOS does not report the download as damaged or from an unidentified developer.
- **GIVEN** a public Windows desktop release, **WHEN** the user runs the installer, **THEN** the installer is code signed to reduce SmartScreen trust warnings. - **GIVEN** a recommended public Windows desktop release, **WHEN** the user runs the installer, **THEN** the installer is code signed to reduce SmartScreen trust warnings.
## Out of scope ## Out of scope