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.
Goal
Finish wiring fully-unattended Sparkle auto-update signing so
./build.shproduces 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:
sign_updatethrough the same GUI-session wrapper (/opt/keypath/keypath-sign-update), so it runs as the logged-in user where the keychain is unlocked.sign_updatebinary is ad-hoc signed (TeamIdentifier=not set), so its access to the key cannot be pre-authorized viasecurity 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_updateonce interactively so the keychain prompt appears, and click Always Allow:After "Always Allow", the key's ACL trusts this
sign_updatebinary, 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— theKP_SPARKLE_SIGN_CMDshim.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:
Until then, unattended notarized builds work with
SKIP_SPARKLE=1(no auto-update archive, no hang).Acceptance
sign_updateauthorized for the Sparkle key (Always Allow clicked once)sudo -n /opt/keypath/keypath-sign-update <zip>prints anedSignatureover SSH with no dialog./build.shproduces a notarized app and a valid Sparkle.sigthat validates againstSUPublicEDKeyFallback 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.