跳转至正文
CI/CD

使用 GitHub actions 实现 Capacitor IOS 自动化 Native Build (含证书)

5 分钟上手:使用 fastlane 和 GitHub Actions 为你的 IOS Ionic App 配置 CI/CD 流水线 (2024)

使用 GitHub Actions 实现 Capacitor iOS 自动化构建(含证书)

为 Capacitor 应用配置 CI/CD 可能非常复杂且耗时。以下是您需要了解的内容:

我们现在推荐使用 配合 Capgo CLI 的 Capgo Build 用于原生 Capacitor 构建。 本 Fastlane 指南仅供维护现有 GitHub Actions pipeline 的团队参考。对于新的 iOS 构建,建议使用 Capgo CLI,这样你就无需再手动维护 Fastlane、Xcode runner、证书和上传脚本。

由 Capgo 提供的 CI/CD Capgo Build

无需再维护 Fastlane、Xcode runner、证书、provisioning profile 和上传脚本。 Capgo 构建 通过你现有的 CI/CD pipeline 运行已签名的原生 iOS 构建:

  • 完美适配你的 pipeline:在 Web 构建完成后,通过 GitHub Actions、GitLab CI、Jenkins 或本地脚本触发 Capgo Build,并且 npx cap sync.
  • 使用 CI secrets 进行签名:将 App Store Connect 密钥、证书、provisioning profiles、密码和 Team ID 保存在你自己的 CI secrets 中。
  • 无需维护原生 runner:Capgo Build 提供维护良好的 Apple 构建环境,你无需再管理 macOS runner、Xcode 镜像或 Fastlane lanes。
  • 构建产物与提交: 下载已签名产物用于 QA,或通过 Capgo CLI 提交发布构建。

定价

  • Capgo 方案起售价为 $12/月
  • 包含 OTA 更新,每月约 15 次原生构建
  • 超出部分的构建时长将通过积分按分钟计费

在 CI/CD 中配置 Capgo Build

手动配置指南

你需要执行以下操作:

使用 Fastlane、GitHub Actions 及证书实现 iOS 持续交付

前提条件

在继续本教程之前:

  • 确保你的开发机已安装 Fastlane 已安装 。
  • 请确保你已加入 iOS 开发者计划。

关于价格的重要信息

Price GitHub Action 价格

https://github.com/features/actions

该服务为“免费" 在限额内免费,具体取决于所选机器。
我们将使用 macOS 机器,你可以在截图中看到其价格和限制(价格为教程编写时的价格,未来可能会有变动)

了解完要求和价格后,让我们继续。

注意:本文假设你已在 App Store Connect 中创建了应用。Fastlane 会自动复制重要信息!

你将通过本教程学到什么

本文的操作步骤

  1. 配合 Fastlane 使用 App Store Connect API
    • 要求:
      • 创建 App Store Connect API Key
      • 使用 App Store Connect API Key
  2. 复制 Fastlane 文件
  3. 配置 GitHub Actions

1. 配合 Fastlane 使用 App Store Connect API

自 2021 年 2 月起,所有用户登录 App Store Connect 时都必须进行双重身份验证或两步验证。为您的 Apple ID 增加这一层安全保护,可以确保只有您本人才能访问账号。
来自 Apple 支持

环境要求

为了让 Fastlane 能够使用 App Store Connect API 来上传您的 App,您需要提供以下 三 项内容:

  1. 发行者 ID
  2. Key ID
  3. Key 文件或 Key 内容

获取 App Store Connect API Key

要生成 Key,您必须拥有 App Store Connect 的 Admin 权限。如果您没有该权限,可以将此文章转发给相关人员。

  1. 登录 App Store Connect.

  2. 选择 用户和访问.

App Store Connect 用户访问权限

3 — 选择 Integration 选项卡。

App Store Connect API 集成

  1. 点击 Generate API Key 或 (+) 按钮。

创建 App Store Connect API keys

  1. 为 key 输入一个名称。该名称仅供参考,不属于 key 本身。

创建 App Store Connect API keys 名称

6 — 在 Access 下,为 key 选择角色。适用于 key 的角色与适用于您团队用户的角色相同。请参阅 角色权限. 我们建议选择 应用管理器.

  1. 点击 Generate。

API 密钥的访问权限无法限制在特定 App 内。

页面将显示新密钥的名称、Key ID、下载链接及其他信息。

从 App Store Connect 下载密钥

你可以在这里获取所有三项必要信息。
<1> 问题 ID。 (APPLE_ISSUER_ID secret
<2> Key ID。(APPLE_KEY_ID secret
<3> 点击“Download API Key”来下载你的 API 私钥。只有在私钥尚未被下载时,下载链接才会显示。Apple 不会保留私钥副本,因此你只能下载一次。

🔴 请将私钥妥善保存。切勿分享你的密钥,不要将密钥存储在 code 仓库中,也不要将密钥包含在客户端 code 中。

使用 App Store Connect API Key

为了创建用于授权的 JWT token,你需要 API Key 文件(下载的 p8 文件)、Key ID 和 Issuer ID。有多种方式可以将这些信息传递给 Fastlane,我选择了使用 Fastlane 的新 action app_store_connect_api_key。你可以在 Fastlane 文档中了解其他方式。我推荐这种方法,因为我认为对于大多数支持设置环境变量的 CI 来说,这是最简单的方式。

请将下载的 p8 文件转换为 Base64 格式,并将其作为 Secret 存储(APPLE_KEY_CONTENT).

base64 -i APPLE_KEY_CONTENT.p8 | pbcopy

现在我们可以使用 API key 通过 Fastlane 来管理 App Store Connect 了,太棒了!

2. 证书

打开 Xcode 并前往 设置 > 账号 > Apple ID > 团队 并选择您的团队。

Code 签名标识

点击 管理证书.

如果您尚未创建证书,可以创建一个新证书。

点击 + 并选择 Apple Distribution

Apple Distribution

然后,你需要前往 Keychain Access,将证书下载为 .p12 文件。

为此,你需要打开 Keychain Access,切换到 login 钥匙串,然后点击 我的证书.

我的证书

选项卡。接着选择你想要下载的证书(可以通过证书日期进行查找)。

然后右键点击证书上的私钥,并选择 导出.

选择文件格式 个人信息交换 (.p12).

这将以以下格式下载证书: .p12 文件。

请在终端中打开该文件,并使用以下命令将其转换为 Base64:

base64 -i BUILD_CERTIFICATE.p12 | pbcopy

这将成为你的 BUILD_CERTIFICATE_BASE64 secret。此外,当系统提示时,请输入证书密码。该密码即为你的 P12_PASSWORD secret。

3. Provisioning profiles

打开 Apple Developer 并选择正确的团队。

然后点击 +

创建新 Profile

并选择 App Store Connect.

选择 App Store Connect

接着你需要选择正确的 App,请注意不要使用通配符,否则签名会失败。

选择正确的 App

选择你之前创建的正确证书(查看过期日期,应与今天的月日一致)并点击 继续.

选择正确的证书

最后输入 Profile 名称并点击 生成.

该名称将用于在 Fastlane 中标识配置文件,其值为 APPLE_PROFILE_NAME.

生成配置文件

您可以将配置文件下载为 .mobileprovision 文件。

下载配置文件

请将配置文件转换为 Base64 格式,并将其作为 secret 存储(BUILD_PROVISION_PROFILE_BASE64).

base64 -i BUILD_PROVISION_PROFILE.mobileprovision | pbcopy

4. 复制 Fastlane 文件

Fastlane 是一个旨在自动化移动开发常用任务的 Ruby 库。通过 Fastlane,您可以配置自定义“lanes”,这些 lanes 封装了一系列“actions”,用于执行您通常在 Android Studio 中手动完成的任务。Fastlane 功能强大,但在本教程中,我们仅使用其中几个核心 actions。

在您的 Capacitor/Ionic 项目根目录下创建 Fastlane 文件夹,并在其中添加 Fastfile:

  • 文件夹: <project-root>/fastlane/
  • 文件: <project-root>/fastlane/Fastfile

这与 ... 处于同一层级 package.json, capacitor.config.*,并且 ios/ 文件夹。请勿在 ... 内部创建它 ios/App/.

platform :ios do
  desc 'Export ipa and submit to TestFlight'
  lane :beta do
    keychain_info = { keychain_name: "ios-build-#{Time.now.to_i}.keychain", keychain_password: SecureRandom.uuid }
    
    begin
      setup_signing(keychain_info)
      bump_build_number
      build_app_with_signing(keychain_info)
      submit_to_testflight
    ensure
      cleanup_keychain(keychain_info)
    end
  end

  private_lane :setup_signing do |options|
    create_keychain(
      name: options[:keychain_name],
      password: options[:keychain_password],
      unlock: true,
      timeout: 0,
      lock_when_sleeps: false, 
      add_to_search_list: true
    )
    import_cert(options)
    install_profile
    update_project_settings
  end

  lane :bump_build_number do
		file = File.read('../package.json')
		data_hash = JSON.parse(file)
		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'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
		build_num = app_store_build_number(
      api_key: api_key,
			app_identifier: ENV['BUNDLE_IDENTIFIER'],
			live: false
    )
		build_num = build_num + 1
		UI.message("Bumped build number to #{build_num}")
		increment_build_number(
			build_number: build_num,
			xcodeproj: "./ios/App/App.xcodeproj",
			skip_info_plist: true
		)
	end

  private_lane :import_cert do |options|
    cert_path = "#{Dir.tmpdir}/build_certificate.p12"
    File.write(cert_path, Base64.decode64(ENV['BUILD_CERTIFICATE_BASE64']))
    import_certificate(
      certificate_path: cert_path,
      certificate_password: ENV['P12_PASSWORD'] || "",
      keychain_name: options[:keychain_name],
      keychain_password: options[:keychain_password],
      log_output: true
    )
    File.delete(cert_path)
  end  
  
  private_lane :cleanup_keychain do |options|
    delete_keychain(
      name: options[:keychain_name]
    )
  end  

  private_lane :install_profile do
    profile_path = "#{Dir.tmpdir}/build_pp.mobileprovision"
    File.write(profile_path, Base64.decode64(ENV['BUILD_PROVISION_PROFILE_BASE64']))
    UI.user_error!("Failed to create provisioning profile at #{profile_path}") unless File.exist?(profile_path)
    ENV['PROVISIONING_PROFILE_PATH'] = profile_path
    install_provisioning_profile(path: profile_path)
    File.delete(profile_path)
  end

  private_lane :update_project_settings do
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "./ios/App/App.xcodeproj",
      code_sign_identity: "iPhone Distribution",
      profile_name: ENV['APPLE_PROFILE_NAME'],
      bundle_identifier: ENV['BUNDLE_IDENTIFIER'],
      team_id: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
    update_project_team(
      path: "./ios/App/App.xcodeproj",
      teamid: ENV['APP_STORE_CONNECT_TEAM_ID']
    )
  end

  private_lane :build_app_with_signing do |options|
    unlock_keychain(
      path: options[:keychain_name],
      password: options[:keychain_password],
      set_default: false
    )
    build_app(
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      configuration: "Release",
      export_method: "app-store",
      output_name: "App.ipa",
      export_options: {
        provisioningProfiles: {
          ENV['BUNDLE_IDENTIFIER'] => ENV['APPLE_PROFILE_NAME']
        }
      },
      xcargs: "-verbose",
      buildlog_path: "./build_logs",
      export_xcargs: "-allowProvisioningUpdates",
    )
  end   

  private_lane :submit_to_testflight do
    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'],
      is_key_content_base64: true,
      duration: 1200,
      in_house: false
    )
    pilot(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )
  end
end

5. 配置 Secrets

GitHub Actions 会使用你在下一步中配置的仓库 Secrets。只有当你需要在本地机器上运行或测试 Fastlane 时,才需要本地 .env 文件。

如需本地测试,请在 <project-root>/fastlane/.env 旁边创建 Fastfile。请勿提交此文件。将 fastlane/.env 添加到你的 .gitignore 中(或确认它已被忽略)。示例如下:

APP_STORE_CONNECT_TEAM_ID=UVTJ336J2D
BUNDLE_IDENTIFIER=ee.forgr.testfastlane
# See previous section for these secrets
BUILD_CERTIFICATE_BASE64=
BUILD_PROVISION_PROFILE_BASE64=
APPLE_KEY_ID=
APPLE_ISSUER_ID=
APPLE_KEY_CONTENT=
P12_PASSWORD=
APPLE_PROFILE_NAME=

获取 APP_STORE_CONNECT_TEAM_ID

前往 开发者中心 并向下滚动到 Membership details 部分。 该 Team ID 是您需要设置到 APP_STORE_CONNECT_TEAM_ID secret 中的值。

app-store-connect-team-id

获取 BUNDLE_IDENTIFIER

  1. 打开 Xcode
  2. 双击 App 在项目导航器中
  3. 然后点击该标签页 Signing and Capabilities
  4. 复制 Bundle identifier的值。这是你需要在 BUNDLE_IDENTIFIER secret 中的值。
bundle-identifier-xcode

6. 构建处理

在 GitHub Actions 中, 费用将根据你运行 CI/CD 工作流所消耗的 分钟数进行计费。根据我的经验,构建在 App Store Connect 中处理完成大约需要 10–15 分钟。

对于私有项目,每次构建的预估成本可能高达 $0.08/分钟 x 15 分钟 = $1.2,甚至更高,具体取决于项目的配置和依赖项。

如果你担心私有项目的成本,可以设置 skip_waiting_for_build_processing 至 true. 这将通过跳过等待 App Store Connect 完成构建处理的过程,来节省构建时长。

不过,这也有一定的权衡——在向用户分发构建版本之前,你需要手动在 App Store Connect 中更新应用的合规信息。

这种优化主要适用于构建时长需要付费的私有项目。对于公开或免费项目,构建时长是免费的,因此无需开启此设置。详情请参阅 GitHub 的 定价页面 了解更多信息。

7. 设置 GitHub Actions

配置 GitHub secrets

请从以下文件中复制 secrets, .env 并将其粘贴到 GitHub 仓库的 secrets 中。

前往 设置 &gt; Secrets 和 variables &gt; Actions &gt; 新建 repository secret

github-secrets

2. BUILD_CERTIFICATE_BASE64 - Base64 编码的证书。

3. BUILD_PROVISION_PROFILE_BASE64 - Base64 编码的 provisioning profile。

4. BUNDLE_IDENTIFIER - 应用的 bundle identifier。

5. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID。

6. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID。

7. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key 内容为 .p8, 检查它

8. 配置 GitHub workflow 文件

创建一个 GitHub workflow 目录。

cd .github/workflows

在 workflow 文件夹内,创建一个名为 build-upload-ios.yml的文件并添加以下内容。

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
          bundler-cache: true
      - uses: maierj/fastlane-action@v3.1.0
        env:
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          BUNDLE_IDENTIFIER: ${{ secrets.BUNDLE_IDENTIFIER }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          APPLE_PROFILE_NAME: ${{ secrets.APPLE_PROFILE_NAME }}
        with:
          lane: ios beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v6
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 10

该 workflow 应在每次 GitHub tag后触发。如果你需要实现 tag 自动化,请参考 使用 GitHub actions 实现自动构建与发布 首先。

接着,该 workflow 会拉取 NodeJS 依赖、进行安装并构建你的 JavaScript 应用。

每次你提交新的 commit,系统都会在 TestFlight 中构建一个 release。

你的 App 不一定非要使用 Ionic,只需以 Capacitor 为基础即可。虽然可以保留旧的 Cordova 模块,但建议优先使用 Capacitor JS 插件。

8. 触发 workflow

创建一个 Commit

进行一次 commit,你应该能在仓库中看到正在运行的 workflow。

触发 workflow

将新的 commits 推送到分支 main 或 development 以触发工作流。

提交后启动

几分钟后,构建版本即可在 App Store Connect dashboard 中查看。

TestFlight Dashboard

9. 我可以从本地机器进行部署吗?

可以,而且非常简单。

你可以像往常一样使用 Xcode 来构建并签名你的 App。

CI/CD 设置指南

其他 CI/CD 平台

Live Updates 与部署

感谢

本文参考了以下文章:

进阶指南:使用 GitHub actions 和证书实现自动 Capacitor iOS build

如果你正在使用 使用 GitHub actions 和证书实现自动 Capacitor iOS build 若要规划 CI/CD 自动化,请将其与 Capgo CI/CD 连接,以实现 Capgo CI/CD 中的产品工作流; Capgo Native Build 连接,以实现 Capgo Native Builds 中的产品工作流; Capgo 集成 连接,以实现 Capgo 集成中的产品工作流; CI/CD 集成 了解 CI/CD 集成的实现细节,并且 GitHub Actions 集成 有关 GitHub Actions 集成的实现细节。

为 Capacitor 应用提供实时更新

当 Web 层 Bug 已上线时,通过 Capgo 推送修复,不必等 App Store 审核数天。用户会在后台获取更新,而原生变更仍走常规审核流程。

博客最新文章

Capgo 为你提供打造真正专业移动应用所需的深度洞察。