Skip to content

Establish stable self-signed macOS releases - #4

Open
amrshawqy wants to merge 1 commit into
bedriyan:mainfrom
amrshawqy:fix/stable-build-identity
Open

Establish stable self-signed macOS releases#4
amrshawqy wants to merge 1 commit into
bedriyan:mainfrom
amrshawqy:fix/stable-build-identity

Conversation

@amrshawqy

Copy link
Copy Markdown

Summary

This PR gives Speaky a stable, reproducible macOS release identity without requiring a paid Apple Developer membership.

Today, ad-hoc or differently signed builds can appear to macOS as different applications even when they share Speaky's name and bundle identifier. In practice, that can leave duplicate Speaky entries in Privacy & Security and force users to grant Accessibility or Microphone access again after an update. The release process is also difficult to reproduce safely across architectures.

The new release path uses one long-lived, self-signed certificate for every public build. Keeping that certificate and application identity stable gives macOS a consistent designated requirement across releases, which makes permission continuity possible for users who replace an existing Speaky installation with a later build from the same release lineage.

No paid Apple account, Developer ID certificate, notarization service, or hosted signing service is required.

What changed

Stable application and signing identity

  • Adds a documented one-time helper for creating Speaky's canonical self-signed release certificate.
  • Makes build.sh fail closed when the stable release identity is unavailable; an ad-hoc build now requires the explicit SPEAKY_ALLOW_ADHOC=1 development override.
  • Signs the application inside-out with the same exact certificate, hardened runtime, and minimal microphone entitlement.
  • Verifies the expected bundle ID, designated requirement, entitlements, nested-code identity, architectures, and DMG contents before accepting a release.
  • Warns before the certificate reaches its expiry window so a maintainer can plan a deliberate identity migration instead of silently shipping a different identity.

Reproducible Apple Silicon and Intel releases

  • Pins FluidAudio to the last verified Intel-compatible version and commits Package.resolved.
  • Builds with locked package resolution and validates the exact requested architecture.
  • Produces native DMGs with an Applications shortcut, per-file checksums, and a root checksum manifest.
  • Adds an explicit release contract version so changes to release assumptions are reviewed intentionally.

Guarded GitHub Actions release workflow

  • Adds a manual-only workflow that releases only the exact commit shared by the selected tag and current upstream main.
  • Runs tests before the protected signing environment is entered.
  • Pins third-party actions and the XcodeGen toolchain, including checksum verification.
  • Imports the signing identity only into a temporary keychain, performs release verification, and removes sensitive temporary material before artifact upload.
  • Keeps publication behind the documented release-signing GitHub Environment approval boundary.

Development and test isolation

  • Gives Debug builds their own product name, bundle identifier, preferences, Keychain namespace, Application Support directory, model directory, and SwiftData store.
  • Uses explicit in-memory persistence in tests to prevent test runs from touching a developer's real Speaky data.
  • Awaits persistent-history cleanup and isolates model paths by build identity.

This prevents local development and test builds from contaminating the release app's permissions or mutable state.

Smaller, honest release surface

  • Removes the incomplete Sparkle integration, placeholder public key, and empty appcast files. Library validation remains enabled and updates are documented as manual GitHub release downloads.
  • Replaces the Python/dmgbuild packaging dependency and its background asset with native macOS tooling.

These removals are intentional: they eliminate non-functional update metadata and a packaging dependency rather than presenting an updater that cannot securely deliver releases.

Security model and tradeoffs

This is deliberately a self-signed release model:

  • It provides stable identity and integrity continuity when maintainers protect and reuse the same private key.
  • It does not provide Apple's Developer ID trust or notarization. On first installation, users still need to use macOS Open Anyway.
  • If the canonical private key is lost or replaced, macOS permissions cannot be expected to carry over. The documentation treats key backup and access control as release-critical.
  • Automatic updates are not included; users replace the app with a verified release from GitHub.

The PR documents these boundaries directly so neither maintainers nor users are given a false sense of Apple-backed trust.

Documentation

The workflow is split by audience:

  • docs/maintainer-setup.md — one-time certificate, backup, repository secret, and protected-environment setup.
  • docs/build-signing.md — repeatable local and CI steps for each release.
  • docs/end-user-installation.md — first installation, Open Anyway, permissions, upgrades, verification, and troubleshooting.

The README links to all three guides.

Validation performed

  • All 35 unit tests pass.
  • Full Release builds pass for both arm64 and x86_64.
  • Both architecture-specific DMGs build and pass hdiutil verify.
  • Exact application and nested-code architectures are asserted.
  • Stable identity continuity was verified across successive builds signed with the same disposable test identity.
  • Missing, ambiguous, ad-hoc, mismatched, and near-expiry identity paths were exercised and rejected as intended.
  • Bundle ID, designated requirement, entitlements, library validation, and nested signing identity were verified.
  • Root and per-artifact checksum manifests were verified.
  • Package.resolved remained unchanged through locked builds.
  • Debug/release preferences, Keychain, paths, and SwiftData isolation are covered by tests.
  • Shell syntax, workflow YAML, plist structure, documentation links, and Git diff checks pass.

The release validation used disposable local certificates and keychains; no test private key or generated release artifact is committed.

Maintainer workflow after merge

  1. Create and securely back up the canonical self-signed identity once, or keep it only on the designated release machine.
  2. If GitHub Actions releases are desired, configure the documented release-signing environment with required reviewers, a main-only deployment rule, and the encrypted certificate secrets.
  3. Tag the exact commit currently on main.
  4. Run the release workflow from main, review the test phase, then approve access to the signing environment.
  5. Publish the already-verified DMGs and checksum manifest produced by that run.

Scope

This PR is intentionally limited to build identity, release safety, and the runtime/test isolation necessary to support them. The separate Show in Dock feature is not included here, which keeps each contribution independently reviewable.

The result is a free release process that is stricter, repeatable, auditable, and substantially less likely to create duplicate permission identities for users.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant