Gitea Actions & Flutter CI/CD 完整指南
Gitea Actions 与 Flutter 双端自动化构建完整指南
本指南涵盖了从 Gitea Actions 环境搭建、macOS Runner 配置、Fastlane Match 证书管理、Fastlane firebase_app_distribution Android 自动分发,到 Flutter 工程并行构建工作流配置的全流程。
一、Gitea Actions 安装与配置
1.1 版本要求
Gitea 1.19.0 及以上版本支持 Actions 功能,1.21.0 及以上版本默认启用。
如果你使用的是 1.21.0 之前的版本,需在 Gitea 配置文件 app.ini 中手动启用:
[actions]
ENABLED = true
修改后重启 Gitea 服务。
1.2 获取注册 Token
Runner 注册到 Gitea 实例需要 Token,Token 有三种级别:
| 级别 | 获取路径 | 适用范围 |
|---|---|---|
| 实例级 | <Gitea地址>/admin/actions/runners | 所有仓库可用 |
| 组织级 | <Gitea地址>/<org>/settings/actions/runners | 该组织下仓库可用 |
| 仓库级 | <Gitea地址>/<owner>/<repo>/settings/actions/runners | 仅该仓库可用 |
注意: Token 只能使用一次,注册后即失效。
1.3 安装 Act Runner(macOS 二进制方式,推荐)
由于 iOS 构建强制依赖 macOS 宿主机环境(Xcode、钥匙串、签名证书等),必须使用二进制方式安装 Runner 并配置为 host 模式,使 Job 直接在宿主机上执行而非 Docker 容器中。
Step 1:下载 Runner 二进制文件
在 macOS 机器上根据芯片架构下载对应版本:
# Apple Silicon Mac (M1/M2/M3)
curl -L https://dl.gitea.com/act_runner/latest/act_runner-latest-darwin-arm64 -o act_runner
# Intel Mac
curl -L https://dl.gitea.com/act_runner/latest/act_runner-latest-darwin-amd64 -o act_runner
chmod +x act_runner
./act_runner --version
Step 2:生成并修改配置文件
./act_runner generate-config > config.yaml
编辑 config.yaml,将 labels 部分修改为 host 模式:
# 关键:将默认的 docker 模式改为 host 模式
labels:
- "macos-latest:host"
- "macos-13:host"
默认标签类似
ubuntu-latest:docker://node:16-bullseye,表示用 Docker 容器运行。改为:host后缀后,Job 将直接在 macOS 宿主机上执行,可以访问完整的 Xcode、Android SDK、钥匙串等环境。
Step 3:注册 Runner
./act_runner register \
--no-interactive \
--instance http://<你的Gitea服务器IP>:3000 \
--token <你的Token> \
--name flutter-macos-runner \
--labels "macos-latest:host" \
--config config.yaml
关键参数说明:
--instance:必须使用局域网 IP 或域名,禁止使用127.0.0.1或localhost--labels:设置 Runner 标签,工作流中通过runs-on匹配该标签
注册信息会存储在 .runner 文件中,不要手动编辑。
Step 4:启动 Runner
./act_runner daemon --config config.yaml
建议配合 launchd 或 pm2 实现开机自启和进程守护。
1.4 在仓库中启用 Actions
即使 Gitea 实例启用了 Actions,每个仓库默认仍是禁用状态。需要手动开启:
- 进入仓库设置页面:
<Gitea地址>/<owner>/<repo>/settings - 勾选 "Enable Repository Actions"
1.5 验证 Runner 注册成功
访问 Gitea 管理后台的 Runners 页面,确认新注册的 Runner 状态为 在线,且标签显示为 macos-latest:host。
二、macOS 机器配置要求
由于 iOS 构建强制依赖 macOS 环境,而 macOS 同时也能构建 Android,因此推荐使用一台 macOS 机器作为 Runner 来同时承担双端构建任务。
2.1 硬件配置要求
| 配置项 | 最低要求 | 推荐配置 |
|---|---|---|
| CPU | 4 核 | 8 核(Apple Silicon M1/M2/M3 更佳) |
| 内存 | 8 GB | 16 GB 及以上 |
| 磁盘空间 | 44 GB | 70 GB 及以上(SSD) |
| 芯片架构 | Intel x64 或 Apple Silicon arm64 | Apple Silicon(M系列构建 Android 速度提升约 50%) |
重要提示: 双端同时构建对内存和磁盘 I/O 要求较高,8GB 内存容易因内存不足触发页面交换,导致构建失败。
2.2 软件环境要求
| 软件 | 版本要求 | 说明 |
|---|---|---|
| macOS | 12 (Monterey) 或更高 | Flutter 3.47+ 最低要求 macOS 12 |
| Xcode | 15.0 或更高 | 从 App Store 下载,安装后执行 sudo xcodebuild -runFirstLaunch |
| Android Studio | 最新版 | 通过 SDK Manager 安装 Android SDK、Build-Tools、Platform-Tools |
| Flutter SDK | stable 频道 | 根据芯片选择 arm64(Apple Silicon)或 x64(Intel)版本 |
| CocoaPods | 最新版 | iOS 原生依赖管理,brew install cocoapods |
| Java JDK | 17 | Android 构建必需,brew install --cask temurin |
| Ruby | 3.0+ | Fastlane 运行环境,brew install ruby |
| Fastlane | 最新版 | 自动化构建和上传工具,brew install fastlane |
| Docker | 最新版 | 部分 Action 内部可能依赖 Docker 功能 |
| Firebase CLI | 最新版 | Fastlane firebase_app_distribution 插件依赖,brew install firebase-cli |
2.3 环境配置步骤
Step 1:安装 Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Step 2:安装 Flutter SDK
# Apple Silicon Mac 下载 arm64 版本
brew install --cask flutter
Step 3:安装 Xcode 并配置
# 从 App Store 安装 Xcode 后执行
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
Step 4:安装 CocoaPods
brew install cocoapods
pod setup
Step 5:安装 Android Studio 和 SDK
从官网下载 macOS 版本的 Android Studio,打开后通过 SDK Manager 安装:
- Android SDK Build-Tools
- Android SDK Platform-Tools
- Android SDK Platforms(目标 API 版本)
- Android SDK Command-line Tools
Step 6:安装 Java 17
brew install --cask temurin
Step 7:安装 Ruby 和 Fastlane
brew install ruby
brew install fastlane
验证 Fastlane 安装:
fastlane --version
Step 8:安装 Firebase CLI(Fastlane firebase_app_distribution 插件依赖)
brew install firebase-cli
firebase login:ci
执行
firebase login:ci后会输出一个长字符串(Token),请妥善保存,后续需配置到 Gitea Secrets 中。Fastlane firebase_app_distribution 插件在 CI 环境中依赖 Firebase CLI 进行身份验证。
Step 9:接受 Android 许可证
flutter doctor --android-licenses
Step 10:配置环境变量
编辑 ~/.zshrc(macOS 默认 shell),添加以下内容:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$HOME/flutter/bin
# 国内网络加速(可选)
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
使配置生效:
source ~/.zshrc
Step 11:验证环境
flutter doctor -v
确认输出中 Android toolchain 和 iOS toolchain 均显示绿色对勾。
三、Fastlane Match 证书管理
Fastlane Match 的核心思想是将所有 iOS 签名证书和描述文件加密后存储在一个私有 Git 仓库中,团队成员和 CI 服务器通过 Match 同步,实现:
| 优势 | 说明 |
|---|---|
| 团队共享 | 所有人共用同一套证书,避免重复生成 |
| 自动修复 | 证书过期或缺失时自动重新生成 |
| 版本控制 | 所有变更记录可追溯,支持回滚 |
| CI 友好 | 无需手动导入证书,CI 环境一键同步 |
3.1 创建私有证书仓库
在 GitHub / GitLab / Gitea 上创建一个私有仓库,用于存储加密后的证书和描述文件:
# 例如在 GitHub 上创建
# https://github.com/yourteam/ios-certificates
务必设为私有仓库,证书文件即使加密也不应公开。
3.2 初始化 Match
进入项目 iOS 目录并初始化:
cd /path/to/your/flutter-project/ios
fastlane match init
执行后会提示选择存储方式,选择 Git,然后输入证书仓库 URL:
? Please choose the storage method for your certificates and profiles:
> git
> google_cloud
> s3
? Please enter the URL of the Git repository:
git@github.com:yourteam/ios-certificates.git
3.3 配置 Matchfile
初始化后会在 ios/fastlane/ 目录下生成 Matchfile,编辑如下:
git_url("git@github.com:yourteam/ios-certificates.git")
storage_mode("git")
type("appstore") # 默认类型
app_identifier(["com.yourcompany.yourapp"]) # 替换为你的 Bundle ID
username("your_apple_id@example.com") # Apple Developer 账号
如果项目有多个 App(如主应用、扩展、Watch 等),在 app_identifier 数组中添加:
app_identifier([
"com.yourcompany.yourapp",
"com.yourcompany.yourapp.NotificationService"
])
3.4 生成证书和描述文件
首次使用 Match 需要生成证书并上传到仓库:
# 生成 App Store 发布证书
fastlane match appstore
# 生成开发证书(可选)
fastlane match development
# 生成 Ad-hoc 证书(可选,用于内部测试分发)
fastlane match adhoc
执行过程:
- 首次会要求输入 加密密码(用于加密仓库中的证书文件),务必妥善保管
- Match 会自动登录 Apple Developer 后台,创建证书和描述文件
- 证书和描述文件加密后推送到 Git 仓库
- 本地自动安装到钥匙串
提示: 如果已有证书,Match 会检测并复用;如果证书过期或缺失,会自动重新生成。
3.5 配置 Appfile
在 ios/fastlane/Appfile 中配置 App 基本信息:
app_identifier("com.yourcompany.yourapp")
apple_id("your_apple_id@example.com")
team_id("YOUR_TEAM_ID") # Apple Developer Team ID
itc_team_id("YOUR_ITC_TEAM_ID") # iTunes Connect Team ID(如果与 team_id 不同)
Team ID 可在 Apple Developer 后台查看。
3.6 生成 App Store Connect API Key(推荐)
相比 Apple ID + 密码,API Key 更安全且支持自动化:
- 登录 App Store Connect
- 进入 Users and Access > Keys
- 点击 Generate API Key
- 设置角色为 App Manager(最小权限原则)
- 下载
.p8文件,记录 Key ID 和 Issuer ID
将 .p8 文件转为 Base64 编码(避免文件传输问题):
cat AuthKey_XXXXX.p8 | base64
四、Fastfile 配置
在 ios/fastlane/Fastfile 中定义构建和上传流程:
default_platform(:ios)
# 全局前置:创建临时钥匙链并同步证书
before_all do
# CI 环境下创建临时钥匙链(避免污染系统钥匙串)
if ENV["CI"]
create_keychain(
name: "app_signing_keychain",
password: "temporary_password",
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
end
# 从 Git 仓库同步证书和描述文件
match(
type: "appstore",
readonly: true, # CI 上设为 true,避免意外创建新证书
keychain_name: "app_signing_keychain",
keychain_password: "temporary_password",
git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"]
)
end
# 全局后置:清理临时钥匙链
after_all do
if ENV["CI"]
delete_keychain(name: "app_signing_keychain")
end
end
# 发生错误时也清理
error do |lane, exception|
if ENV["CI"]
delete_keychain(name: "app_signing_keychain")
end
end
platform :ios do
# ==================== 构建并上传到 TestFlight ====================
desc "Build and upload to TestFlight"
lane :beta do
# 1. 自动递增 build 号
increment_build_number(xcodeproj: "Runner.xcodeproj")
# 2. 构建 IPA
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
export_method: "app-store", # App Store / TestFlight
clean: true,
# 显式指定签名配置(关键!避免 gym 自动检测失败)
export_options: {
signingStyle: "manual",
provisioningProfiles: Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING]
}
)
# 3. 生成 App Store Connect API Key
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_BASE64"],
duration: 1200,
in_house: false
)
# 4. 上传到 TestFlight
pilot(
api_key: api_key,
skip_waiting_for_build_processing: true, # 不等待 Apple 处理完成
skip_submission: true # 不自动提交审核
)
end
# ==================== 构建并上传到 App Store ====================
desc "Build and upload to App Store"
lane :release do
increment_build_number(xcodeproj: "Runner.xcodeproj")
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
export_method: "app-store",
clean: true,
export_options: {
signingStyle: "manual",
provisioningProfiles: Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING]
}
)
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_BASE64"],
duration: 1200,
in_house: false
)
upload_to_app_store(
api_key: api_key,
skip_metadata: true,
skip_screenshots: true,
force: true
)
end
# ==================== 仅同步证书(不构建)====================
desc "Sync certificates from match repo"
lane :sync_certs do
match(
type: "appstore",
readonly: true,
git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"]
)
end
end
==================== Android 平台配置 ====================
platform :android do desc "Build Android APK/AAB and distribute to Firebase App Distribution" lane :android_beta do
1. 构建 Release APK
gradle( task: "app:assembleRelease", build_type: "Release" )
2. 构建 App Bundle (AAB)
gradle( task: "app:bundleRelease", build_type: "Release" )
3. 分发 APK 到 Firebase App Distribution
firebase_app_distribution( app: ENV["FIREBASE_ANDROID_APP_ID"], firebase_cli_token: ENV["FIREBASE_TOKEN"], android_artifact_path: "app/build/outputs/apk/release/app-release.apk", release_notes: ENV["RELEASE_NOTES"] || "Auto build from CI", testers: ENV["FIREBASE_TESTERS"] || "tester1@example.com,tester2@example.com" )
4. 分发 AAB 到 Firebase App Distribution(推荐用于生产环境)
firebase_app_distribution(
app: ENV["FIREBASE_ANDROID_APP_ID"],
firebase_cli_token: ENV["FIREBASE_TOKEN"],
android_app_bundle: "app/build/outputs/bundle/release/app-release.aab",
release_notes: ENV["RELEASE_NOTES"] || "Auto build from CI",
testers: ENV["FIREBASE_TESTERS"] || "tester1@example.com,tester2@example.com"
)
end
desc "Sync certificates and prepare Android release" lane :android_release do
构建 Release AAB
gradle( task: "app:bundleRelease", build_type: "Release" )
可选:上传到 Google Play Store
play_deploy(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end end
4.1 关键配置说明
| 配置项 | 说明 |
|---|---|
readonly: true | CI 环境必须设为 true,避免意外创建新证书 |
signingStyle: "manual" | 禁用 Xcode 自动签名,使用 Match 提供的描述文件 |
MATCH_PROVISIONING_PROFILE_MAPPING | Match 自动生成的描述文件映射,直接传给 export_options |
skip_waiting_for_build_processing | TestFlight 上传后不等待 Apple 处理,节省 CI 时间 |
4.2 Xcode 项目配置
使用 Match 后,需要在 Xcode 中禁用自动签名:
- 打开
ios/Runner.xcworkspace - 选择 Runner Target > Signing & Capabilities
- 取消勾选 Automatically manage signing
- 将 Code Signing Style 设为 Manual
或者在 Fastfile 中通过 update_code_signing_settings 自动修改(推荐,避免手动操作):
# 在 match 之后、build_app 之前添加
update_code_signing_settings(
use_automatic_signing: false,
path: "Runner.xcodeproj",
code_sign_identity: "iPhone Distribution",
profile_name: Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING].values.first,
bundle_identifier: ENV["APP_IDENTIFIER"] || "com.yourcompany.yourapp"
)
五、Flutter 工程并行构建配置
5.1 工作流文件结构
在 Flutter 项目根目录创建 .gitea/workflows/flutter-dual-build.yml 文件。Gitea Actions 完全兼容 GitHub Actions 语法。
5.2 并行构建工作流(含 Fastlane 集成 + Firebase App Distribution)
以下配置将 Android 和 iOS 构建拆分为两个独立的 Job,在同一台 macOS Runner 上并行执行。iOS 端集成 Fastlane Match 自动管理证书并上传 TestFlight;Android 端集成 Fastlane firebase_app_distribution 插件实现 Firebase App Distribution 自动分发:
name: Flutter Dual Platform Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
release:
types: [published]
# 在仓库 Settings > Secrets 中配置以下变量:
# MATCH_GIT_BASIC_AUTHORIZATION
# MATCH_PASSWORD
# APP_STORE_CONNECT_API_KEY_ID
# APP_STORE_CONNECT_API_ISSUER_ID
# APP_STORE_CONNECT_API_KEY_BASE64
# FIREBASE_TOKEN
jobs:
# ==================== Android 构建任务 ====================
build-android:
name: Build Android APK/AAB & Distribute via Fastlane
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 'stable'
channel: stable
cache: true
- name: Install dependencies
run: flutter pub get
- name: Code analysis
run: flutter analyze
- name: Run tests
run: flutter test
- name: Build release APK
run: flutter build apk --release
- name: Build App Bundle (for Google Play)
run: flutter build appbundle --release
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: android-release-apk
path: build/app/outputs/flutter-apk/app-release.apk
retention-days: 7
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with:
name: android-release-aab
path: build/app/outputs/bundle/release/app-release.aab
retention-days: 7
- name: Set up Ruby and Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
working-directory: android
- name: Add firebase_app_distribution plugin
run: |
cd android
fastlane add_plugin firebase_app_distribution
- name: Distribute to Firebase App Distribution
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
FIREBASE_ANDROID_APP_ID: ${{ secrets.FIREBASE_ANDROID_APP_ID }}
FIREBASE_TESTERS: ${{ secrets.FIREBASE_TESTERS }}
RELEASE_NOTES: "Auto build from $(git rev-parse --short HEAD)"
run: |
cd android
bundle exec fastlane android_beta
# ==================== iOS 构建任务 ====================
build-ios:
name: Build iOS & Upload to TestFlight
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 'stable'
channel: stable
cache: true
- name: Install dependencies
run: flutter pub get
- name: Install CocoaPods dependencies
run: |
cd ios
pod install
- name: Set up Ruby and Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
working-directory: ios
- name: Configure Git for Match repo
run: |
git config --global user.email "ci@example.com"
git config --global user.name "CI Bot"
- name: Build and Upload to TestFlight
env:
CI: true
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
run: |
cd ios
bundle exec fastlane beta
- name: Upload IPA artifact (optional)
if: always()
uses: actions/upload-artifact@v4
with:
name: ios-release-ipa
path: ios/build/Runner.ipa
retention-days: 7
5.3 工作流说明
| 配置项 | 说明 |
|---|---|
runs-on: macos-latest | 两个 Job 都指定 macOS Runner,Gitea 会自动并行调度 |
cache: true | 启用 Flutter 依赖缓存,显著加速后续构建 |
--no-codesign | iOS 构建时不签名(Fastlane 中通过 Match 处理签名) |
retention-days: 7 | 构建产物保留 7 天,可根据需要调整 |
FIREBASE_TOKEN | Firebase CLI 认证 Token(Fastlane 插件用于身份验证) |
5.4 触发条件
工作流支持以下触发方式:
- 推送代码到
main或develop分支时自动构建 - 提交 Pull Request 到
main分支时自动构建 - 发布 Release 时自动构建(适合正式打包场景)
六、Fastlane firebase_app_distribution 插件配置
Fastlane 的 firebase_app_distribution 插件提供了在 Fastlane 流水线中直接调用 Firebase App Distribution 分发的能力。相比直接在 CI 工作流中执行 Firebase CLI 命令,使用 Fastlane 插件的优势在于:
| 优势 | 说明 |
|---|---|
| 统一管理 | iOS (TestFlight) 和 Android (Firebase) 的分发逻辑都在 Fastfile 中定义 |
| 链式调用 | 可以在同一个 lane 中完成构建、签名、分发全流程 |
| 环境隔离 | CI 环境下通过环境变量传递凭据,无需手动登录 |
| 错误处理 | Fastlane 内置的错误处理和日志输出更友好 |
6.1 安装插件
在 Android 项目根目录(即 android/ 目录)执行以下命令安装插件:
cd android
fastlane add_plugin firebase_app_distribution
执行后会提示选择安装源,选择 Option 3: RubyGems.org 即可。
该命令会自动完成以下操作:
- 在
fastlane/Pluginfile中添加gem 'fastlane-plugin-firebase_app_distribution' - 更新
Gemfile和Gemfile.lock - 安装插件 gem 包
注意: 确保
android/fastlane/目录已初始化(运行过fastlane init)。如果尚未初始化,先执行fastlane init。
6.2 配置 Pluginfile
插件安装后,android/fastlane/Pluginfile 应包含以下内容:
gem 'fastlane-plugin-firebase_app_distribution'
如果文件不存在,请手动创建并添加上述内容。
6.3 Firebase 身份验证配置
Fastlane firebase_app_distribution 插件支持两种身份验证方式:
方式一:使用 Firebase CLI 登录(推荐用于 CI)
在 macOS Runner 上执行以下命令生成认证 Token:
firebase login:ci
执行后会输出一个长字符串(Refresh Token),请妥善保存并配置到 Gitea Secrets 中,命名为 FIREBASE_TOKEN。
重要: Fastlane 插件底层仍然依赖 Firebase CLI。因此 Runner 上必须安装 Firebase CLI(
brew install firebase-cli),但具体的登录和分发操作由 Fastlane 插件封装,无需在工作流 YAML 中直接调用 firebase 命令。
方式二:使用 Service Account 凭据(推荐用于生产环境)
Service Account 方式无需 Token 过期问题,更适合生产环境:
Step 1:创建 Service Account
- 进入 Firebase 控制台 > 项目设置 > 服务账户
- 点击 生成新的私钥
- 下载 JSON 文件,保存为
firebase-service-account.json
Step 2:将私钥配置为 Gitea Secret
# 读取 JSON 文件内容并转为 Base64
cat firebase-service-account.json | base64
将 Base64 编码后的内容添加为 Gitea Secret,命名为 FIREBASE_SERVICE_ACCOUNT_JSON。
Step 3:在 Fastfile 中使用 Service Account
在 Fastfile 的 firebase_app_distribution 调用中,使用 service_credentials_file 参数替代 firebase_cli_token:
firebase_app_distribution(
app: ENV["FIREBASE_ANDROID_APP_ID"],
service_credentials_file: "/path/to/service-account.json",
android_artifact_path: "app/build/outputs/apk/release/app-release.apk",
release_notes: "Auto build from CI",
testers: "tester1@example.com"
)
6.4 Fastfile 完整配置(含 Android)
以下是包含 iOS (TestFlight) 和 Android (Firebase App Distribution) 的完整 Fastfile 配置:
default_platform(:ios)
# 全局前置:创建临时钥匙链并同步证书
before_all do
# CI 环境下创建临时钥匙链(避免污染系统钥匙串)
if ENV["CI"]
create_keychain(
name: "app_signing_keychain",
password: "temporary_password",
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
end
# 从 Git 仓库同步证书和描述文件
match(
type: "appstore",
readonly: true,
keychain_name: "app_signing_keychain",
keychain_password: "temporary_password",
git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"]
)
end
# 全局后置:清理临时钥匙链
after_all do
if ENV["CI"]
delete_keychain(name: "app_signing_keychain")
end
end
# 发生错误时也清理
error do |lane, exception|
if ENV["CI"]
delete_keychain(name: "app_signing_keychain")
end
end
platform :ios do
# ==================== 构建并上传到 TestFlight ====================
desc "Build and upload to TestFlight"
lane :beta do
# 1. 自动递增 build 号
increment_build_number(xcodeproj: "Runner.xcodeproj")
# 2. 构建 IPA
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
export_method: "app-store",
clean: true,
export_options: {
signingStyle: "manual",
provisioningProfiles: Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING]
}
)
# 3. 生成 App Store Connect API Key
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_BASE64"],
duration: 1200,
in_house: false
)
# 4. 上传到 TestFlight
pilot(
api_key: api_key,
skip_waiting_for_build_processing: true,
skip_submission: true
)
end
# ==================== 构建并上传到 App Store ====================
desc "Build and upload to App Store"
lane :release do
increment_build_number(xcodeproj: "Runner.xcodeproj")
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
export_method: "app-store",
clean: true,
export_options: {
signingStyle: "manual",
provisioningProfiles: Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING]
}
)
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_BASE64"],
duration: 1200,
in_house: false
)
upload_to_app_store(
api_key: api_key,
skip_metadata: true,
skip_screenshots: true,
force: true
)
end
# ==================== 仅同步证书(不构建)====================
desc "Sync certificates from match repo"
lane :sync_certs do
match(
type: "appstore",
readonly: true,
git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"]
)
end
end
# ==================== Android 平台配置 ====================
platform :android do
desc "Build Android APK/AAB and distribute to Firebase App Distribution"
lane :android_beta do
# 1. 构建 Release APK
gradle(
task: "app:assembleRelease",
build_type: "Release"
)
# 2. 构建 App Bundle (AAB)
gradle(
task: "app:bundleRelease",
build_type: "Release"
)
# 3. 分发 APK 到 Firebase App Distribution
firebase_app_distribution(
app: ENV["FIREBASE_ANDROID_APP_ID"],
firebase_cli_token: ENV["FIREBASE_TOKEN"],
android_artifact_path: "app/build/outputs/apk/release/app-release.apk",
release_notes: ENV["RELEASE_NOTES"] || "Auto build from CI",
testers: ENV["FIREBASE_TESTERS"] || "tester1@example.com,tester2@example.com"
)
# 4. 分发 AAB 到 Firebase App Distribution(推荐用于生产环境)
# firebase_app_distribution(
# app: ENV["FIREBASE_ANDROID_APP_ID"],
# firebase_cli_token: ENV["FIREBASE_TOKEN"],
# android_app_bundle: "app/build/outputs/bundle/release/app-release.aab",
# release_notes: ENV["RELEASE_NOTES"] || "Auto build from CI",
# testers: ENV["FIREBASE_TESTERS"] || "tester1@example.com,tester2@example.com"
# )
end
desc "Sync certificates and prepare Android release"
lane :android_release do
# 构建 Release AAB
gradle(
task: "app:bundleRelease",
build_type: "Release"
)
# 可选:上传到 Google Play Store
# play_deploy(
# track: "internal",
# aab: "app/build/outputs/bundle/release/app-release.aab"
# )
end
end
6.5 firebase_app_distribution 参数说明
| 参数 | 说明 | 必填 |
|---|---|---|
app | Firebase 应用 ID(格式:1:project-id:android:app-id),可在 Firebase 控制台 > 项目设置 > 常规中获取 | 是 |
firebase_cli_token | Firebase CLI 登录时输出的 Refresh Token(CI 环境认证用) | 二选一 |
service_credentials_file | Google Service Account JSON 文件路径(生产环境推荐) | 二选一 |
android_artifact_path | APK 文件路径 | 否 |
android_app_bundle | AAB 文件路径(推荐用于生产环境) | 否 |
release_notes | 版本说明文本 | 否 |
release_notes_file | 版本说明文件路径 | 否 |
testers | 测试人员邮箱列表(逗号分隔) | 否 |
testers_file | 测试人员邮箱文件路径 | 否 |
groups | 测试组名称(逗号分隔,需先在 Firebase 控制台创建) | 否 |
groups_file | 测试组名称文件路径 | 否 |
6.6 配置 Gitea Secrets
进入仓库设置 > Secrets,添加以下变量:
| Secret 名称 | 值 | 说明 |
|---|---|---|
FIREBASE_TOKEN | firebase login:ci 输出的 Token | Firebase CLI 认证(Fastlane 插件使用) |
FIREBASE_ANDROID_APP_ID | Android 应用的 Firebase App ID | 格式:1:project-id:android:app-id |
FIREBASE_TESTERS | 测试人员邮箱(逗号分隔) | 可选,也可在 Fastfile 中配置 |
MATCH_GIT_BASIC_AUTHORIZATION | Base64 编码的 Git 认证 | 访问证书仓库 |
MATCH_PASSWORD | Match 加密密码 | 解密证书文件 |
APP_STORE_CONNECT_API_KEY_ID | Key ID | App Store Connect API |
APP_STORE_CONNECT_API_ISSUER_ID | Issuer ID | App Store Connect API |
APP_STORE_CONNECT_API_KEY_BASE64 | Base64 编码的 .p8 文件 | API 私钥 |
6.7 首次本地验证
在本地开发机器上首次配置 Fastlane firebase_app_distribution 插件:
# 1. 进入 Android 目录
cd android
# 2. 安装插件(如果尚未安装)
fastlane add_plugin firebase_app_distribution
# 3. 登录 Firebase(本地开发用)
firebase login
# 4. 测试构建和分发
fastlane android_beta
# 5. 指定测试人员和版本说明
fastlane android_beta env:{
"FIREBASE_ANDROID_APP_ID":"your-app-id",
"FIREBASE_TESTERS":"your-email@example.com",
"RELEASE_NOTES":"Local test build"
}
6.8 进阶:Service Account 认证(生产环境推荐)
对于生产环境的 CI/CD,推荐使用 Service Account 而非 firebase login:ci Token,因为 Token 会过期而 Service Account 不会:
Step 1:创建 Service Account
- 进入 Firebase 控制台 > 项目设置 > 服务账户
- 点击 生成新的私钥
- 下载 JSON 文件,保存为
firebase-service-account.json
Step 2:将私钥配置为 Gitea Secret
# 读取 JSON 文件内容并转为 Base64
cat firebase-service-account.json | base64
将 Base64 编码后的内容添加为 Gitea Secret,命名为 FIREBASE_SERVICE_ACCOUNT_JSON。
Step 3:在工作流中写入 Service Account 文件并配置 Fastlane
- name: Write Service Account file
env:
FIREBASE_SERVICE_ACCOUNT_JSON: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }}
run: |
echo "$FIREBASE_SERVICE_ACCOUNT_JSON" | base64 -d > /tmp/firebase-service-account.json
echo "Service Account file created: $(ls -la /tmp/firebase-service-account.json)"
- name: Build and Distribute via Fastlane
env:
FIREBASE_SERVICE_ACCOUNT_PATH: /tmp/firebase-service-account.json
run: |
cd android
# 设置环境变量供 Fastlane 插件使用
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/firebase-service-account.json
bundle exec fastlane android_beta
Step 4:更新 Fastfile 使用 Service Account
在 android/fastlane/Fastfile 的 android_beta lane 中,将 firebase_cli_token 替换为 service_credentials_file:
firebase_app_distribution(
app: ENV["FIREBASE_ANDROID_APP_ID"],
service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_PATH"],
android_artifact_path: "app/build/outputs/apk/release/app-release.apk",
release_notes: ENV["RELEASE_NOTES"] || "Auto build from CI",
testers: ENV["FIREBASE_TESTERS"] || "tester1@example.com,tester2@example.com"
)
注意: 使用 Service Account 方式时,无需安装 Firebase CLI(
brew install firebase-cli可省略),因为插件直接通过 Google API 进行认证,不依赖 CLI 工具。
七、常见问题与优化建议
7.1 磁盘空间清理
双端构建会占用大量磁盘空间,建议定期清理缓存:
# 清理 Gradle 缓存
rm -rf ~/.gradle/caches
# 清理 Xcode DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData
# 清理 Flutter 构建缓存
flutter clean
7.2 iOS 签名配置
上述工作流中 iOS 端通过 Fastlane Match 自动管理签名。如需手动配置:
- 在 Apple Developer 后台配置 Bundle ID、证书和描述文件
- 将证书和描述文件导入 macOS Runner 的钥匙串
- 在 Fastfile 中确保
signingStyle设为manual
7.3 构建产物分发
| 平台 | 分发方式 | 说明 |
|---|---|---|
| Android | Fastlane firebase_app_distribution 插件 | 通过 Fastlane 统一管理,支持 APK/AAB |
| iOS | TestFlight(通过 Fastlane) | Apple 官方测试分发 |
| 两者 | Gitea Artifacts | 构建产物保存在 Gitea 中供下载 |
7.4 Runner 性能优化
- 并行度控制:在 Runner 配置中设置
capacity参数控制并发 Job 数量,双端并行构建建议设为 2 - 缓存优势:host 模式下 Flutter 的
cache: true直接缓存到宿主机~/.pub-cache,比 Docker 模式更持久,构建速度更快 - Apple Silicon 优势:M 系列芯片 Mac 在 Android Gradle 构建上比 Intel Mac 快约 50%,推荐作为构建机
7.5 安全性注意事项
host 模式没有容器隔离保护,Job 可以直接访问宿主机文件系统。建议:
- 仅对可信仓库启用 Actions
- 避免 fork PR 自动触发构建
- 定期审计工作流文件
- Firebase Service Account 密钥不要提交到代码仓库
7.6 网络加速
如果 Runner 位于中国大陆网络环境,建议在 macOS 机器上配置 Flutter 国内镜像(见 2.3 节 Step 10),否则 flutter pub get 可能因网络问题超时失败。
八、总结
| 步骤 | 关键操作 |
|---|---|
| 1. 启用 Actions | Gitea 1.21.0+ 默认启用,旧版本需在 app.ini 中配置 |
| 2. 注册 Runner | 使用二进制方式安装,配置为 host 模式,使用局域网 IP |
| 3. 配置 macOS 环境 | 安装 Xcode、Android Studio、Flutter SDK、Java 17、CocoaPods、Fastlane、Firebase CLI(Fastlane 插件依赖) |
| 4. 配置 Fastlane Match | 创建私有证书仓库,初始化 Match,生成证书和描述文件 |
| 5. 配置 Fastfile | 定义 TestFlight 构建和上传 lane |
| 6. 配置 Fastlane firebase_app_distribution | 安装插件,配置 Firebase 项目,生成认证 Token 或 Service Account |
| 7. 编写工作流 | 创建 .gitea/workflows/flutter-dual-build.yml,拆分为 Android + iOS 两个并行 Job |
| 8. 配置 Secrets | 在 Gitea 中添加 MATCH、API Key、Firebase 等 Secret |
| 9. 启用仓库 Actions | 在仓库设置中勾选 "Enable Repository Actions" |
| 10. 推送代码触发构建 | 推送代码后在 Actions 页面查看构建状态 |
按照以上步骤配置完成后,每次推送代码即可在 Gitea Actions 中自动并行构建:
- Android:生成 APK/AAB 并通过 Fastlane firebase_app_distribution 插件自动分发到 Firebase App Distribution
- iOS:通过 Fastlane Match 管理签名证书,构建 IPA 并上传到 TestFlight
大幅提升交付效率。

