# GitHub Actions

Amore's [release-action](https://github.com/AmoreComputer/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

{% youtube "9TOZkZyRnow", "Automating macOS releases with GitHub Actions and Amore", "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:

{% raw %}
```yaml
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 }}
```
{% endraw %}

The release-action builds a DMG installer, which carries a `Built with amore.computer` watermark. It is not removed automatically: with [Amore+](/pricing/) you can turn it off (see [DMG watermark](/help/dmg-creation/#watermark)).

## Add your secrets

Add each of the values below in your repo under `Settings` → `Secrets and variables` → `Actions` → `New repository secret`. See [using secrets in GitHub Actions](https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/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](https://appstoreconnect.apple.com/access/integrations/api) 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`:

```sh
base64 -i AuthKey_XXXXXXXX.p8 | pbcopy
```

[Apple's guide to creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api).

### Developer ID certificate (code signing)

Signing uses your `Developer ID Application` certificate. If you don't have one yet, create it first (see [Codesigning](/help/codesigning/#codesign-identity)).

Export it straight from Xcode, no Keychain Access needed:

1. In Xcode, open `Settings` → `Accounts`, 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`:

```sh
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:

```sh
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](/help/command-line/):**

  ```sh
  amore export sparkle-key --bundle-id com.example.YourApp
  ```

- **From the app:** copy the base64 private key from [Key Management](/help/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 `Amore` → `Settings…` (`⌘,`) and scroll to `API Keys`. Click `New Key`, name it (e.g. `GitHub Actions`), pick the `Release` scope (enough for CI), and click `Create`. Copy the secret token and paste it into `AMORE_TOKEN`. See [API Keys](/help/api-keys/) for scopes and how to revoke a key.

### Provisioning profile

If your app uses a restricted entitlement like Associated Domains, iCloud or Push Notifications, the export additionally needs a Developer ID provisioning profile. Create it once, add it base64-encoded as `AMORE_PROVISIONING_PROFILE`, and pass it in the step:

{% raw %}
```yaml
provisioning-profile: ${{ secrets.AMORE_PROVISIONING_PROFILE }}
```
{% endraw %}

See [Provisioning Profiles](/help/provisioning-profiles/) for more information.

## Running a release

Tag a commit and push the tag:

```sh
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:

{% raw %}
```yaml
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 }}
```
{% endraw %}

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](/help/s3-bucket/) for the full bucket setup.