docs/RELEASING.md

Releasing Restless

Releases are built on a Mac, not in CI, because they need the Developer ID certificate, Apple's notary service and the Sparkle signing key, all of which live in the login keychain. CI (.github/workflows/ci.yml) only checks that an unsigned build compiles.

One-time setup

  1. Developer ID certificate. "Developer ID Application" for team VW9NDM428H must be in the login keychain. Check with:
    sh
    1security find-identity -v -p codesigning
  2. .env. Copy it from .env.example. It sets the team, the signing identity and NOTARY_PROFILE=Restless.
  3. Notary credentials. Create an app-specific password for the team's Apple ID at appleid.apple.com → Sign-In & Security → App-Specific Passwords. Then store it as the Restless keychain profile:
    sh
    1xcrun notarytool store-credentials "Restless" \
    2 --apple-id "APPLE_ID_EMAIL" --team-id VW9NDM428H
    It prompts for the password, so it never lands in your shell history.
  4. Sparkle signing key. The private EdDSA key is in the login keychain under the account restless; its public half is SUPublicEDKey in project.yml. Back it up somewhere safe, such as a password manager. Run ./build.sh once first, so Sparkle's tools are downloaded, then:
    sh
    1.build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_keys --account restless -x "$HOME/restless-sparkle-key.txt"
    Move that file into your password manager and delete it. Never commit it: anyone with this key can sign updates that every installed copy will accept. Without it, no future release can update existing installs.

Every release

From an up-to-date, clean main:

sh
1./release.sh 1.1.0

It first checks .env, the certificate, the notary profile, gh and the Sparkle key, so a missing credential fails in seconds. The first time, macOS may ask to let generate_keys use the Sparkle key: choose Always Allow. Then it runs these steps:

  1. Builds and signs Restless.app with version 1.1.0. The build number is the commit count on main, which only grows, and that's what Sparkle compares.
  2. Notarizes and staples the app.
  3. Zips the app. Sparkle's generate_appcast then signs the zip and writes appcast.xml.
  4. Publishes the GitHub release v1.1.0 with both files attached.

The app's feed is https://github.com/notkainoa/restless/releases/latest/download/appcast.xml, so every installed copy sees the new release as soon as it's published.

If a step fails, the script says which one. Anything it built is kept in dist/release-<version>/, and nothing is published until the last step.