Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions .github/workflows/create-ios-certs.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,86 @@
name: Create iOS Certificates

run-name: "Create iOS Certificates - ${{ inputs.bundle-id }}"

# Creates an iOS app's signing certificates / provisioning profiles on CI, so developers don't
# need Ruby / fastlane / CocoaPods locally.
#
# PREREQUISITE: the App ID and the App Store Connect app record must already exist — create both
# by hand first. `match` won't create the identifier (it fails with "Couldn't find bundle
# identifier") and fastlane `produce` can't authenticate with the API key.

permissions:
contents: read

on:
workflow_dispatch:
inputs:
bundle-id:
description: "App bundle identifier (e.g. com.reown.merchantpos)"
required: true
type: string
certs-repo:
description: "Match certificates repo (owner/repo)"
required: true
default: 'reown-com/mobile-match'
type: string
match-types:
description: "Comma-separated match types to create"
required: true
default: 'appstore,development'
type: string

jobs:
placeholder:
name: Placeholder Job
runs-on: ubuntu-latest
create-certs:
runs-on: macos-latest-xlarge
steps:
- name: Do nothing
run: echo "Placeholder to enable manual dispatch from feature branches. The real workflow lives on the feature branch — run it via 'Use workflow from'."
- name: Checkout
uses: actions/checkout@v4

- name: webfactory/ssh-agent
uses: webfactory/ssh-agent@v0.9.0
Comment thread
ignaciosantise marked this conversation as resolved.
with:
ssh-private-key: ${{ secrets.MATCH_SSH_KEY }}

- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
bundler-cache: true

# Certs go to a branch over SSH (MATCH_SSH_KEY), no GitHub token. A teammate opens and
# merges the PR in the certs repo — see the job summary.
- name: Create certificates
env:
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }}
# Inputs via env, never inline in run: — ${{ }} expands before the shell parses,
# so a quote in an input would break out of the command.
INPUT_BUNDLE_ID: ${{ inputs.bundle-id }}
INPUT_CERTS_REPO: ${{ inputs.certs-repo }}
INPUT_MATCH_TYPES: ${{ inputs.match-types }}
run: |
chmod +x scripts/create-certificates.sh
IFS=',' read -ra TYPES <<< "$INPUT_MATCH_TYPES"
{
echo "### ✅ Certificates created — open and merge the PR(s)"
echo "This is the expected next step, not a failure: the workflow pushes the cert"
echo "branches over SSH and intentionally does **not** open PRs (it holds no write"
echo "token for \`$INPUT_CERTS_REPO\`)."
echo ""
echo "Open each PR below and merge it into \`master\` — the release workflow will"
echo "fail until you do:"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
for type in "${TYPES[@]}"; do
type="$(echo "$type" | xargs)" # trim whitespace
[ -z "$type" ] && continue # tolerate a doubled comma in the input
echo "::group::Creating ${type} certificates"
./scripts/create-certificates.sh "$INPUT_CERTS_REPO" "$INPUT_BUNDLE_ID" "" "$type" --no-pr
echo "::endgroup::"
branch="certs/add-${INPUT_BUNDLE_ID}-${type}"
compare="https://github.com/${INPUT_CERTS_REPO}/compare/master...${branch}?expand=1"
echo "- **${type}**: [open PR for \`${branch}\`](${compare})" >> "$GITHUB_STEP_SUMMARY"
done
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,22 @@ bundle install

### Creating Certificates for a New App

Use the provided script to create new certificates and provisioning profiles. The script handles creating a branch, running fastlane match, and creating a PR (required since the certificates repo has branch protection):
**Preferred: do it on CI.** Run the **Create iOS Certificates** GitHub Action
(`.github/workflows/create-ios-certs.yaml`) — it creates the app's signing certificates and
provisioning profiles via the App Store Connect API key (no Apple ID / 2FA), so you don't need
Ruby, fastlane, or CocoaPods locally. It pushes a branch to the certs repo over SSH and links a
compare URL in the job summary; a teammate opens and merges that PR.

> **Prerequisite:** the App ID must already be registered in the Developer Portal and the App
> Store Connect app record must already exist. `match` does not create the identifier, and
> fastlane `produce` can't authenticate with the API key — so both are created manually
> (Developer Portal → Identifiers, then App Store Connect → Apps → +).

**Local fallback.** Use the provided script to create new certificates and provisioning profiles.
The script handles creating a branch, running fastlane match, and creating a PR (required since
the certificates repo has branch protection). When the App Store Connect API key env vars
(`APPLE_KEY_ID`, `APPLE_ISSUER_ID`, `APPLE_KEY_CONTENT`) are set it uses API-key auth (no 2FA);
otherwise it falls back to interactive Apple ID auth:

```bash
# Make the script executable (first time only)
Expand All @@ -76,11 +91,18 @@ chmod +x scripts/create-certificates.sh
**Example:**

```bash
./scripts/create-certificates.sh reown-com/mobile-certificates com.reown.myapp dev@reown.com appstore
./scripts/create-certificates.sh reown-com/mobile-certificates com.reown.myapp dev@reown.com development
./scripts/create-certificates.sh reown-com/mobile-match com.reown.myapp dev@reown.com appstore
./scripts/create-certificates.sh reown-com/mobile-match com.reown.myapp dev@reown.com development

# With API-key auth (no 2FA), the Apple email is optional — pass "" instead:
./scripts/create-certificates.sh reown-com/mobile-match com.reown.myapp "" appstore --auto-merge
```

> **Note:** Requires [GitHub CLI](https://cli.github.com/) (`gh`) to be installed and authenticated. By default, the script creates a PR that requires manual merge. Use `--auto-merge` to automatically merge.
> **Note:** These local invocations require [GitHub CLI](https://cli.github.com/) (`gh`) to be
> installed and authenticated, because the script opens the PR for you — by default one that
> needs a manual merge; pass `--auto-merge` to merge it automatically. The CI workflow instead
> passes `--no-pr`, which pushes the branch over SSH and makes no GitHub API calls at all, so it
> needs no `gh` and no token; you open that PR from the link in the job summary.

### Downloading Certificates Locally

Expand Down
32 changes: 32 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,38 @@ platform :ios do
UI.success("✅ Simulator .app: #{app_path}")
end

# No `create_app` lane on purpose: fastlane `produce` only supports Apple ID + 2FA auth and
# ignores the API key, so it can't run unattended. Create the app record by hand instead.

# Sync certs / profiles to a branch in the match repo, using the API key so it needs no 2FA.
# Opening and merging that branch is handled by scripts/create-certificates.sh.
lane :create_certs do
# match needs a keychain to import the certificate into.
setup_ci()

api_key = app_store_connect_api_key(
key_id: ENV["APPLE_KEY_ID"],
issuer_id: ENV["APPLE_ISSUER_ID"],
key_content: ENV["APPLE_KEY_CONTENT"],
duration: 1200,
in_house: false,
)

match_type = ENV["MATCH_TYPE"] || "appstore"

match(
readonly: false,
type: match_type,
app_identifier: ENV["BUNDLE_ID"],
git_url: ENV["MATCH_GIT_URL"],
git_branch: ENV["MATCH_GIT_BRANCH"],
api_key: api_key,
include_all_certificates: true,
)

UI.success("✅ #{match_type} certificates synced for #{ENV['BUNDLE_ID']}")
end

lane :test_code_signing do
ENV['XCODE_PROJECT_PATH'] = 'dapps/pos-app/ios/WPay.xcodeproj'
ENV['BUNDLE_ID'] = 'com.reown.mobilepos'
Expand Down
Loading