Skip to content

Finish unattended Sparkle update-signing: one-time 'Always Allow' for sign_update key (do at the Mac) #702

@malpern

Description

@malpern

Goal

Finish wiring fully-unattended Sparkle auto-update signing so ./build.sh produces signed + notarized + EdDSA-signed release archives over SSH with zero involvement. Everything is staged; this needs one ~30-second GUI action that can only be done at the Mac (or via screen-share), because the remote SSH session can't click a macOS keychain dialog.

Background

We set up unattended build + sign + notarize over SSH (works today): codesign and notarytool both run via an isolated signing keychain + a scoped-sudo wrapper that runs codesign inside the logged-in GUI session. Notarization is verified end-to-end (Accepted + stapled + Gatekeeper "Notarized Developer ID").

Sparkle update-signing is the last mile. The blocker:

  • The Sparkle EdDSA private key lives in the login keychain; the agent's SSH session has no security session and can't read it.
  • We route sign_update through the same GUI-session wrapper (/opt/keypath/keypath-sign-update), so it runs as the logged-in user where the keychain is unlocked.
  • But the Homebrew sign_update binary is ad-hoc signed (TeamIdentifier=not set), so its access to the key cannot be pre-authorized via security set-(generic-password-)partition-list (partition lists key off a stable code identity). macOS therefore shows a one-time "sign_update wants to use the Sparkle key" → Always Allow dialog on the physical screen, which hangs forever over SSH.

The one-time fix (do this at the Mac / screen-share)

Run sign_update once interactively so the keychain prompt appears, and click Always Allow:

# any zip works; this just triggers the key-access prompt
SU="$(ls -1dt /opt/homebrew/Caskroom/sparkle/*/bin/sign_update | head -1)"
"$SU" /Users/malpern/local-code/KeyPath/dist/sparkle/KeyPath-1.0.0.zip
# → macOS dialog: "sign_update wants to use the 'Sparkle' key" → click **Always Allow**

After "Always Allow", the key's ACL trusts this sign_update binary, and the unattended wrapper signs without prompting — forever (until the sparkle cask is reinstalled at a new version, which would re-trigger it once).

Verify it worked (can be done over SSH afterward)

sudo -n /opt/keypath/keypath-sign-update /Users/malpern/local-code/KeyPath/dist/sparkle/KeyPath-1.0.0.zip
# → should print: sparkle:edSignature="…"  (no hang, no dialog)

Then enable it in unattended builds

The wrapper + shim are already installed:

  • /opt/keypath/keypath-sign-update (root-owned, sudo NOPASSWD) — runs sign_update in the GUI session, relays output via temp file (asuser doesn't forward stdout).
  • ~/.config/keypath-signing/sparkle-sign-cmd — the KP_SPARKLE_SIGN_CMD shim.

Build-script support is in #701 (KP_SPARKLE_SIGN_CMD override, SKIP_SPARKLE, + a real bug fix for the cask-absent hard-crash). Once #701 is merged and the key is authorized, a full unattended release build is:

KC="$HOME/Library/Keychains/keypath-signing.keychain-db"
security unlock-keychain -p "$(cat ~/.config/keypath-signing/keychain-password)" "$KC"
export KP_SIGN_CMD="$HOME/.config/keypath-signing/sign-cmd"
export KP_NOTARY_KEYCHAIN="$KC"
export KP_SPARKLE_SIGN_CMD="$HOME/.config/keypath-signing/sparkle-sign-cmd"
./build.sh

Until then, unattended notarized builds work with SKIP_SPARKLE=1 (no auto-update archive, no hang).

Acceptance

Fallback if the GUI route is ever undesirable

Export the EdDSA private key to a file once (generate_keys -x) and use sign_update's key-file mode — but that export is itself GUI-gated, so it has the same one-time-at-the-Mac requirement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions