Skip to content

Feature: In-app auto-update with confirmation (pull DMG from GitHub Releases) #12

Description

@VasenevEA

🎯 Goal

Add a non-intrusive auto-update check that pulls release DMGs from this repository's GitHub Releases. When a new version is available, the app shows a confirmation dialog with release notes; nothing is downloaded or installed without explicit user approval.

📋 UX flow

  1. On launch + once every 24 h the app calls https://api.github.com/repos/VasenevEA/FastWord/releases/latest and compares the returned tag_name against the installed CFBundleShortVersionString.
  2. If a newer version exists → menu-bar status changes to Update available: v0.X.Y (visible only in the trey menu, no popup). Also a new menu item «Install v0.X.Y…».
  3. Clicking that menu item opens a window with:
    • Version number
    • Release notes (parsed from the GitHub Release body markdown)
    • Download & install primary button
    • Skip this version secondary
    • Remind me later dismiss
  4. On Download & install:
    • Download the .dmg asset to ~/Library/Caches/fastword/updates/
    • Verify the DMG's Developer ID signature (spctl -a) against the expected Team ID LPZJYDRZ2K
    • Mount, copy the new .app into a temp location, run a helper that replaces /Applications/FastWord.app and relaunches
    • The user re-enters their hotkey session; settings are preserved
  5. On Skip this version: remember v0.X.Y in UserDefaults so we don't notify about that version again. New versions still notify.

🔒 Privacy considerations

The single privacy-sensitive thing about an auto-updater is what telemetry it sends to the update server. Sparkle's default setup sends OS version, app version, and a UUID. We want strictly none of that.

Our checker will:

  • Only call the public GitHub API endpoint for the releases JSON. No custom server.
  • Include no User-Agent identifier beyond a literal FastWord (GitHub requires some UA). No version, no OS, no machine ID.
  • Use no cookies, no auth headers, no PII.
  • Be togglable off in Settings → General → «Check for updates automatically» (default: on).
  • Be explicit in the about/Settings: "FastWord checks https://api.github.com once a day for new releases. No other telemetry is sent. Source: GitHub-released DMGs only."

⚖️ Implementation options

Option A — Sparkle framework

  • Industry-standard, used by VS Code, IINA, Transmission, etc.
  • Handles signature verification, delta updates, EdDSA signing.
  • Cons: requires us to host an appcast.xml (could use GitHub Pages, but adds infra), needs an EdDSA key setup, ~3 MB of binary in the app.

Option B — Hand-rolled GitHub Releases checker (recommended)

  • ~150 lines of Swift: URLSession, JSON decode, NSWorkspace.
  • Verifies DMG via spctl after download (Apple's Gatekeeper does the heavy lifting since we're already Developer-ID-notarised).
  • No appcast hosting, no extra signing infra, no extra dependencies in the binary.
  • Fits the "no extra moving parts" ethos of the project.

Recommendation: Option B. We already publish notarised DMGs to GitHub Releases as part of the release script; the updater simply reuses them. Gatekeeper is sufficient signature verification.

✅ Definition of done

  • UpdateChecker.swift polling latest release once a day + on launch.
  • Settings → General → «Check for updates automatically» toggle (default on).
  • Settings → General → manual «Check now» button.
  • Update dialog with version, release notes (markdown rendered), and Download/Skip/Remind actions.
  • DMG download with progress, atomic install, app relaunch.
  • Signature verification before install: rejects anything not signed by Team ID LPZJYDRZ2K.
  • No telemetry beyond the unauthenticated GitHub Releases API call.
  • README updated: explicit list of network requests the app makes.
  • User can opt out entirely; opt-out persists across versions.

🛠 Technical notes

  • GitHub API rate limit unauthenticated: 60 req/h per IP, well above our 1 req/day. Need no token.
  • The "skipped versions" list is just a Set<String> in UserDefaults.
  • Replacing a running .app from itself requires a small helper script extracted to /tmp/; standard trick used by Sparkle and ~every other macOS updater.
  • Signature check: shell out to codesign --verify --strict on the downloaded .app and require TeamIdentifier=LPZJYDRZ2K.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions