# AgentShore Desktop - Code Signing & Installer Builds

This document is the maintainer runbook for producing desktop release builds of
AgentShore. The build entrypoint is the Python spine in `scripts/buildkit/`,
run from the repo root: `uv run python -m scripts.buildkit macos` (signing +
notarization) and `uv run python -m scripts.buildkit windows` (machine-wide Inno
Setup installer with optional Authenticode signing when `signtool.exe` and a
code-signing certificate are available). CI release automation is not wired yet.

It complements `docs/design/desktop/DESIGN.md` section 6.5, "Code signing &
trust".

## 1. Overview

AgentShore Desktop bundles a Tauri 2 shell around a Python sidecar. The
AgentShore core is provisioned at install time into a managed venv from a wheel
shipped inside the platform installer.

The current shipping version is `0.2.1`
(`desktop/src-tauri/tauri.conf.json` version).

macOS `uv run python -m scripts.buildkit macos` produces:

| Stage | Tool / command | Notes |
| --- | --- | --- |
| Resolve identity | `security find-identity -v -p codesigning` | Auto-detects the first Developer ID Application cert |
| Build and sign `.app` | `npx tauri build` | Hardened runtime plus entitlements; also emits the `.dmg` |
| Verify `.app` | `codesign --verify --deep --strict` | Only when an Application cert was found |
| Build `.pkg` | `pkgbuild` plus `productbuild` | Three components; signed with Developer ID Installer if present |
| Notarize, optional | `xcrun notarytool submit --wait` plus `xcrun stapler staple` | Requires Installer cert and notarytool profile |
| Publish | Upload manually to GitHub Releases | `.app`, `.dmg`, `.pkg` |
| Update manifest | `scripts/generate_update_manifest.py` to `latest.json` | Signed with the Tauri updater key |

Windows `uv run python -m scripts.buildkit windows` produces:

| Stage | Tool / command | Notes |
| --- | --- | --- |
| Build dashboard | `npm run build`, `npm run build:lib` | Same dashboard artifacts as macOS |
| Build provisioner | `cargo build -p agentshore-provisioner --locked` | Standalone workspace crate; compiled Windows postinstall helper |
| Stage uv | pinned `uv.exe` | Build fails unless `uv --version` starts with the pinned baseline |
| Provision bd | provisioner | Windows provisions pinned `bd.exe` into `%ProgramData%\AgentShore\bin` |
| Build wheel | `uv build --wheel` | Bundled into the installer |
| Build app executable | `npx tauri build --no-bundle -- --locked` | Inno wraps the executable instead of Tauri NSIS/MSI |
| Sign app/provisioner executables, optional | `signtool sign` | Auto-detects a current-user code-signing cert, or use `-CertificateThumbprint` |
| Build `.exe` wizard | Inno Setup 6 `ISCC.exe` | Emits `desktop\dist\AgentShoreSetup-<version>-x64.exe` |
| Sign installer, optional | `signtool sign` | Same certificate and timestamp settings as the app executable |

For local Windows installer testing, `uv run python -m scripts.buildkit windows --self-sign`
creates or reuses a current-user self-signed Authenticode code-signing
certificate named `CN=AgentShore Local Dev Code Signing` and uses it for the
app, provisioner, and setup executable. Add `-TrustSelfSignedCertificate` if the
local machine should trust that development certificate for verification. Use
`-SetupSelfSignedCertificateOnly` to provision the certificate without building.

Self-signed builds are for local QA only. They do not establish publisher
reputation, they do not reduce SmartScreen warnings for public users, and they
must not be shipped as public releases.

## 2. macOS Developer ID

Two Developer ID certificates are used:

- **Developer ID Application** signs the `.app` through the Tauri bundler.
- **Developer ID Installer** signs the `.pkg` through `productbuild --sign`.
  It is required for notarization.

To provision them:

1. Enroll in the Apple Developer Program.
2. In Xcode Settings, Accounts, Manage Certificates, create both Developer ID
   certificate types.
3. Confirm they resolve from the login Keychain with `security find-identity`.

If no Application cert is present, the macOS build proceeds unsigned. Pass
`--no-sign` to skip detection deliberately.

## 3. Notarization

Notarization is opt-in via `--notarize` and uses a stored `notarytool`
keychain profile. The default profile name is `agentshore-notary`.

Create the profile once with `xcrun notarytool store-credentials`, using either
an App Store Connect API key or an Apple ID plus app-specific password.

The build then runs:

```bash
xcrun notarytool submit "$PKG" --keychain-profile agentshore-notary --wait
xcrun stapler staple "$PKG"
```

Use `--keychain-profile NAME` to choose a different profile.

## 4. Hardened Runtime

macOS signing config lives in `desktop/src-tauri/tauri.conf.json`
(`bundle.macOS`). Hardened-runtime entitlements live in
`desktop/src-tauri/entitlements.plist`.

The app enables entitlements needed by the embedded Python sidecar:

| Key | Reason |
| --- | --- |
| `com.apple.security.cs.allow-jit` | Python and downstream libraries may JIT |
| `com.apple.security.cs.allow-unsigned-executable-memory` | Python/native deps may map executable memory |
| `com.apple.security.cs.disable-library-validation` | Allows loading Python dylibs not signed by the same team |
| `com.apple.security.cs.allow-dyld-environment-variables` | Lets the sidecar set `DYLD_*` for its managed venv |

## 5. Tauri Updater Keypair

Tauri's auto-updater verifies `latest.json` against a public key embedded in
the app. Generate the keypair once with `tauri signer generate`.

Put the public key in `desktop/src-tauri/tauri.conf.json` at
`plugins.updater.pubkey`. Store the private key off-host. Losing it forces
existing installs to re-bootstrap.

The updater fetches:

```text
https://github.com/SuperSwinkAI/Swink-AgentShore/releases/latest/download/latest.json
```

After a Tauri build, sign each artifact with `tauri signer sign` and assemble
the manifest with `scripts/generate_update_manifest.py`.

## 6. Installer Component Layout

Both platform installers expose the same deliberate choices:

| Component | Payload | Default |
| --- | --- | --- |
| AgentShore Desktop | Desktop app plus managed sidecar venv from bundled wheel | Required |
| Timelapse Capture | Runs `install_timelapse()` for ffmpeg, Node, capture CLI, and browser deps | Opt-in |
| AgentShore CLI | Installs `agentshore` from the bundled wheel via `uv tool install --force --reinstall --python 3.12` | Opt-out |

The Windows installer is machine-wide (`PrivilegesRequired=admin`) and installs
the desktop app under `%ProgramFiles%\AgentShore`. The managed venv is under
`%ProgramData%\AgentShore\venv`, matching the Rust supervisor's Windows lookup
path. The provisioner also installs the pinned desktop `bd.exe` under
`%ProgramData%\AgentShore\bin` and writes install logs under
`%ProgramData%\AgentShore\install-logs`.

## 7. Windows Local Self-Signing

Install the Windows SDK first so `signtool.exe` is available. Then run:

```powershell
uv run python -m scripts.buildkit windows `
  --self-sign `
  --trust-self-signed-certificate `
  --setup-self-signed-certificate-only
```

Build and sign with the local development certificate:

```powershell
uv run python -m scripts.buildkit windows `
  --self-sign `
  --trust-self-signed-certificate
```

The build creates the certificate in `Cert:\CurrentUser\My` with a non-
exportable private key, then signs the Tauri app, the provisioner, and the final
Inno setup executable. With `--trust-self-signed-certificate`, it also adds the
certificate to `Cert:\CurrentUser\Root` so local verification succeeds. With
`--setup-self-signed-certificate-only`, it exits after certificate setup. (These
options are handled by the native signing carve-out `scripts/buildkit/_win_signing.ps1`.)

To use a different local-dev subject:

```powershell
uv run python -m scripts.buildkit windows `
  --self-sign `
  --self-signed-subject "CN=AgentShore Dev Build Signing"
```

To verify the resulting installer:

```powershell
signtool verify /pa /all /v desktop\dist\AgentShoreSetup-0.2.1-x64.exe
```

To remove the local trust entry later:

```powershell
$thumbprint = "<thumbprint shown during the build>"
Remove-Item -LiteralPath "Cert:\CurrentUser\Root\$thumbprint"
```

Remove the private-key-bearing certificate only after confirming no local build
flow still needs it:

```powershell
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$thumbprint"
```

## 8. Secrets Handling

- Never commit `.p12`, `.pfx`, `.cer`, `.p8`, `.mobileprovision`, or the Tauri
  private updater key.
- `desktop/src-tauri/.gitignore` excludes certificate material by extension.
- Developer ID private keys and notarization profiles live in Keychain or
  off-host secret storage.
- Treat unsigned Windows `.exe` artifacts as internal test builds only. Public
  releases should be Authenticode-signed and timestamped.
