GitHub Actions

Amore's release-action runs the full release on a GitHub-hosted Mac runner: it archives, code signs, notarizes, signs the update for Sparkle, updates your appcast.xml and publishes, no local Mac required.

Push a version tag like v1.2.0 and a signed, notarized release ships automatically. Tag a prerelease like v1.2.0-beta.1 and it goes out on your beta channel instead.

Video

The full CI release: a version tag triggers archive, code signing, notarization and publishing on a GitHub-hosted Mac runner, no local Mac. About seven and a half minutes.

The workflow file

Create .github/workflows/release.yml in your app's repo. Set the scheme to yours and pin xcode-path/runs-on to the toolchain your app builds with; the rest works as-is:

name: Amore Release
run-name: Release ${{ github.ref_name }}

on:
  push:
    tags: ['v*']

concurrency:
  group: amore-release
  cancel-in-progress: true

jobs:
  release:
    runs-on: macos-26
    steps:
      - uses: actions/checkout@v4
      - name: Read version from tag
        id: version
        run: |
          ref="${GITHUB_REF_NAME#v}"
          echo "number=${ref%%-*}" >> "$GITHUB_OUTPUT"
      - uses: AmoreComputer/release-action@v1
        with:
          # Pin Xcode to the version your project builds with, so a runner
          # default drifting can't silently break your build.
          scheme: Pomodoro
          codesign-identity: ${{ secrets.CODESIGN_IDENTITY }}
          dev-id-cert-p12: ${{ secrets.DEV_ID_CERT_P12 }}
          dev-id-cert-password: ${{ secrets.DEV_ID_CERT_PASSWORD }}
          sparkle-private-key: ${{ secrets.SPARKLE_PRIVATE_KEY }}
          asc-api-key-id: ${{ secrets.ASC_API_KEY_ID }}
          asc-api-issuer: ${{ secrets.ASC_API_ISSUER }}
          asc-api-key: ${{ secrets.ASC_API_KEY }}
          amore-token: ${{ secrets.AMORE_TOKEN }}
          channel: ${{ contains(github.ref_name, '-') && 'beta' || '' }}
          marketing-version: ${{ steps.version.outputs.number }}

The release-action builds a DMG installer, which carries a Built with amore.computer watermark. It is not removed automatically: with Amore+ you can turn it off (see DMG watermark).

Add your secrets

Add each of the values below in your repo under SettingsSecrets and variablesActionsNew repository secret. See using secrets in GitHub Actions for the details.

App Store Connect API key (notarization)

Notarization uses an App Store Connect API key. It gives you three secrets in one go.

  1. Open App Store Connect → Users and Access → Integrations and select the App Store Connect API tab.
  2. Click the + to generate a key. Name it (e.g. Amore CI) and give it the Developer role.
  3. Copy the Issuer ID shown above the table → ASC_API_ISSUER.
  4. Copy the Key ID from the new row → ASC_API_KEY_ID.
  5. Click Download to get the AuthKey_XXXXXXXX.p8 file. You can only download it once. Base64-encode it and paste the result into ASC_API_KEY:
base64 -i AuthKey_XXXXXXXX.p8 | pbcopy

Apple's guide to creating API keys.

Developer ID certificate (code signing)

Signing uses your Developer ID Application certificate. If you don't have one yet, create it first (see Codesigning).

Export it straight from Xcode, no Keychain Access needed:

  1. In Xcode, open SettingsAccounts, select your Apple ID, then pick your team and click Manage Certificates.
  2. Control-click your Developer ID Application certificate → Export Certificate. Save it as a .p12 and set a password. That password is DEV_ID_CERT_PASSWORD.
  3. Base64-encode the .p12 and paste it into DEV_ID_CERT_P12:
base64 -i Certificate.p12 | pbcopy

Your CODESIGN_IDENTITY is the full Developer ID Application: Your Name (TEAMID) string. List yours in Terminal and copy it from the output:

security find-identity -v -p codesigning | grep "Developer ID Application"

Sparkle signing key (updates)

SPARKLE_PRIVATE_KEY is the base64 Ed25519 private key that signs your updates so existing installs trust them. It is the same key Amore stores in your keychain, do not generate a new one or users won't be able to update.

Get it either way and paste the result into the secret:

  • From the CLI:

    amore export sparkle-key --bundle-id com.example.YourApp
  • From the app: copy the base64 private key from Key Management.

Amore token (publishing)

AMORE_TOKEN is a scoped API key that lets CI publish releases to your Amore-hosted app.

Create it in the app: open AmoreSettings… (⌘,) and scroll to API Keys. Click New Key, name it (e.g. GitHub Actions), pick the Release scope (best for CI), and click Create. Copy the secret token and paste it into AMORE_TOKEN.

Running a release

Tag a commit and push the tag:

git tag v1.2.0
git push origin v1.2.0
  • v1.2.0 → stable release, published as version 1.2.0.
  • v1.2.0-beta.1 → beta release on your beta channel.

The version comes from the tag and the build number is assigned automatically, one past your last release, so there is nothing to bump by hand.

Self-hosted S3 storage

By default your releases are hosted by Amore. To publish to your own S3-compatible bucket (AWS S3, Cloudflare R2, MinIO) instead, add these inputs to the release-action step:

          s3-bucket: my-app-releases
          s3-region: us-east-1
          s3-public-url: https://cdn.example.com
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as repository secrets, and keep amore-token; it stays required in S3 mode.

For Cloudflare R2 or MinIO, set s3-region: auto and add s3-endpoint: https://<account-id>.r2.cloudflarestorage.com. See Self-Managed S3-Bucket for the full bucket setup.