Auto-updates via Sparkle#44
Conversation
Replace the homegrown "check GitHub releases, open the browser" updater with Sparkle, so Lineup updates in place and verifies each update with an EdDSA signature (a tampered or MITM'd download is rejected). - Package.swift: add Sparkle (2.x); add an @executable_path/../Frameworks rpath so the bundled app loads the embedded framework. - Updater.swift: one SPUStandardUpdaterController for the app, started at launch for scheduled background checks. The menu "Check for Updates…" and the Settings → About button target it directly, so Sparkle owns the update UI and the item's enabled state. Removes UpdateChecker.swift (SemVer stays in LineupCore for tests). - Info.plist: SUFeedURL (https://lineup.caiano.com/appcast.xml), SUPublicEDKey (PLACEHOLDER — replace via sparkle-keygen before the next release), SUEnableAutomaticChecks, daily SUScheduledCheckInterval. - build-app.sh: embed Sparkle.framework (ditto preserves the Versions symlinks) and re-sign it INSIDE-OUT (XPC services, Autoupdate, Updater.app, then the framework) with the app's identity + hardened runtime, before signing the app. No --deep (mis-signs Sparkle). Verified: deep signature valid, app launches with the framework loaded, satisfies its designated requirement. - web/appcast.xml: the feed (empty until the first signed release). - Scripts/sparkle-keygen.sh: one-time EdDSA keygen (private key -> Keychain). - Scripts/sparkle-appcast.sh: per-release, EdDSA-signs the DMG and regenerates the appcast. Documented end-to-end in BUILDING.md. Security: the EdDSA private key and notary credential live only in the developer's Keychain, never in this (public) repo. swift build clean, 316 tests.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8121a5d193
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <key>SUFeedURL</key> | ||
| <string>https://lineup.caiano.com/appcast.xml</string> | ||
| <key>SUPublicEDKey</key> | ||
| <string>REPLACE_WITH_SUPublicEDKey_FROM_generate_keys</string> |
There was a problem hiding this comment.
Don't ship a placeholder Sparkle public key
This value is copied verbatim into Contents/Info.plist by build-app.sh, and AppUpdater starts Sparkle at launch. With this placeholder instead of a base64 EdDSA key, any released build will fail update validation/checks and cannot accept future signed appcast updates, so affected users would need to reinstall manually; make the build fail while the placeholder remains or omit SUPublicEDKey until the real key is available.
Useful? React with 👍 / 👎.
| if [ -n "${DEVID}" ]; then | ||
| SIGN_ID="${DEVID}"; sig_kind="developer-id"; TIMESTAMP_FLAG="--timestamp" | ||
| echo "==> signing with Developer ID (notarizable): ${DEVID}" | ||
| elif security find-identity -p codesigning -v 2>/dev/null | grep -q "${SIGN_IDENTITY}"; then |
There was a problem hiding this comment.
Use the signing probe for self-signed builds
This detection now relies on security find-identity -p codesigning -v, but the repo's own Scripts/setup-signing.sh:27-29 notes that this reports zero valid identities for the untrusted Lineup Self-Signed cert even though codesign can use it. In the documented local flow (setup-signing.sh then build-app.sh without a Developer ID cert), this branch is skipped and the app falls back to ad-hoc signing, losing the stable Accessibility requirement and making REQUIRE_STABLE_SIGNATURE=1/DMG packaging fail; keep the previous probe that actually attempts to sign with the self-signed identity.
Useful? React with 👍 / 👎.
generate_keys stores one EdDSA private key per Keychain, not per app, so the key created for Synclock already exists locally. Drop its public key into Info.plist (replacing the placeholder); the same private key signs Lineup's updates too. No new key generated. Verified: sign_update signs with the Keychain key, the public key is a 32-byte ed25519 value, Info.plist lints clean.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f1e86782a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fi | ||
|
|
||
| # Sign the app LAST (identifier pinned; hardened runtime). This seals the embedded framework. | ||
| codesign --force --options runtime ${TIMESTAMP_FLAG} --sign "${SIGN_ID}" --identifier "${BUNDLE_ID}" "${APP}" |
There was a problem hiding this comment.
Avoid hardening non-Team-ID Sparkle builds
When this script falls back to the documented local/self-signed path or to SIGN_ID="-", the main app is still signed with the hardened runtime while the embedded Sparkle framework has no Apple Team ID. Hardened Runtime enables library validation, which only allows Apple-signed code or code with the same Team ID, so these non-Developer-ID builds can pass codesign --verify but fail at launch when dyld tries to load Sparkle.framework; either skip --options runtime or sign the app with a disable-library-validation entitlement for non-Apple local builds.
Useful? React with 👍 / 👎.
Replaces the homegrown "check GitHub releases, open the browser" updater with Sparkle, so Lineup updates in place and verifies each update with an EdDSA signature (a tampered or man-in-the-middled download is rejected).
App
Package.resolved); add an@executable_path/../Frameworksrpath so the bundled app loads the embedded framework.SPUStandardUpdaterController, started at launch for scheduled background checks. The menu "Check for Updates…" and the Settings → About button target it directly.UpdateChecker.swiftremoved (SemVerstays inLineupCorefor its tests).SUFeedURL(the site),SUPublicEDKey,SUEnableAutomaticChecks, daily check interval.Packaging
Sparkle.framework(viaditto, preserving theVersionssymlinks) and re-signs it inside-out (XPC services,Autoupdate,Updater.app, then the framework) with the app's identity + hardened runtime, before signing the app. No--deep. Verified:codesign --verify --deep --strictvalid, app launches with the framework loaded and satisfies its designated requirement.Release tooling
Signing key — reused from Synclock
generate_keysstores one EdDSA private key per Keychain (not per app), so the key already created for Synclock is reused: its public key is in Info.plist, and the same private key (in the Keychain) signs Lineup's updates. No new key, nothing else to set up. Verifiedsign_updatesigns with it. The private key and notary credential stay only in the Keychain — never in this repo.swift buildclean,swift run lineup-tests→ 316 checks passed. App build + inside-out signing + deep-verify + launch all verified locally.