ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Immich iOS 的 fastlane 发布自动化:四个 Lane、多 Target 签名与 CI 集成详解

2026/9/7 16:47:32 拓冰建站 浏览量
Immich iOS 的 fastlane 发布自动化:四个 Lane、多 Target 签名与 CI 集成详解 Immich iOS 的 fastlane 发布自动化四个 Lane、多 Target 签名与 CI 集成详解【免费下载链接】immichHigh performance self-hosted photo and video management solution.项目地址: https://gitcode.com/GitHub_Trending/im/immich本文基于 Immich 移动应用 iOS 端的 fastlane 文档 展开完整讲解仓库中定义的四个 fastlane lane开发/生产 TestFlight 构建、手动发布、仅构建验证的用途与差异并结合 Fastfile 源码剖析多 Target 手动签名、App Store Connect API Key 认证、版本号管理以及 GitHub Actions 工作流 中的证书与密钥管理。读完后你可以理解 Immich iOS 从代码提交到 TestFlight 构建分发的完整发布链路并在自己维护含 Share Extension、Widget Extension 的 iOS 应用时参考这套自动化方案。一、fastlane 在 Immich iOS 工程中的位置Immich 的移动应用Flutter 项目代码位于mobile/目录在 iOS 侧使用 fastlane 完成打包与 TestFlight 分发。相关配置集中在 mobile/ios/fastlane/ 目录README.mdfastlane 自动生成的文档列出全部可用 lane 及其命令与描述每次运行 fastlane 时会自动重新生成Fastfilelane 定义与全部构建/签名逻辑的核心文件Appfilefastlane 的应用级默认配置Gemfilefastlane 的 Ruby 依赖声明。Appfile 内容很简短声明了默认应用标识与 Apple 开发者邮箱app_identifier app.alextran.immich # The bundle identifier of your app apple_id altranfuto.org # Your Apple email addressfastlane 官方文档 开头给出的安装前提是安装 Xcode 命令行工具xcode-select --installfastlane 本体则通过 Ruby 生态安装。Gemfile 声明了以下依赖source https://rubygems.org gem fastlane gem cocoapods gem abbrev # Required for Ruby 3.4 gem multi_json其中abbrev是为兼容 Ruby 3.4 显式补充的依赖。在mobile/ios/目录下执行bundle install即可完成安装CI 中即通过ruby/setup-ruby的bundler-cache: true完成等价操作。二、运行 lane 需要哪些前置条件从 Fastfile 的辅助方法可以看出CI lane 依赖以下环境与文件依赖项说明源码依据APP_STORE_CONNECT_API_KEY_ID环境变量App Store Connect API Key 的 Key IDFastfileget_api_keyAPP_STORE_CONNECT_API_KEY_ISSUER_ID环境变量API Key 的 Issuer ID同上~/.appstoreconnect/private_keys/AuthKey_ID.p8文件API 私钥文件路径由key_id动态拼接同上FASTLANE_TEAM_ID环境变量可选覆盖默认的TEAM_ID常量Fastfileconfigure_code_signing已导入构建 Keychain 的 Distribution 证书GHA 工作流负责导入lane 内不再处理build-mobile.ymlget_api_key方法Fastfile的关键参数app_store_connect_api_key( key_id: ENV[APP_STORE_CONNECT_API_KEY_ID], issuer_id: ENV[APP_STORE_CONNECT_API_KEY_ISSUER_ID], key_filepath: #{Dir.home}/.appstoreconnect/private_keys/AuthKey_#{ENV[APP_STORE_CONNECT_API_KEY_ID]}.p8, duration: 1200, # JWT 有效期 20 分钟 in_house: false )duration: 1200表示签发一次有效期 20 分钟的 JWT token 用于调用 App Store Connect APIin_house: false表明走的是标准 App Store/TestFlight 通道而非企业内部分发。Fastfile 顶部还定义了五个全局常量贯穿所有 laneTEAM_ID 2W7AC6T8T5 CODE_SIGN_IDENTITY Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID}) BASE_BUNDLE_ID app.alextran.immich DEV_BUNDLE_ID tech.futo.immich.testflight DEV_GROUP_ID group.app.immich.share.testflight其中BASE_BUNDLE_ID是生产应用标识与 Appfile 一致DEV_BUNDLE_ID是开发构建专用的独立 bundle IDDEV_GROUP_ID是开发构建专用的 App Group 标识。三、核心辅助方法签名、版本号与构建上传Fastfile 把重复逻辑抽成了四个辅助方法理解它们是理解各 lane 差异的基础。3.1 build_xcargs注入 CUSTOM_GROUP_ID 覆盖build_xcargs 拼装xcodebuild参数def build_xcargs(group_id: nil) args -skipMacroValidation CODE_SIGN_IDENTITY#{CODE_SIGN_IDENTITY} CODE_SIGN_STYLEManual args CUSTOM_GROUP_ID#{group_id} if group_id args end固定部分-skipMacroValidation跳过宏校验并强制手动签名风格CODE_SIGN_STYLEManual与指定 Distribution 证书。可选部分当传入group_id时注入CUSTOM_GROUP_ID构建参数——开发 lane 用它把 App Group ID 覆盖为DEV_GROUP_ID使开发构建与生产构建互不共享 App Group 数据从源码结构看该 ID 最终由 Xcode 工程在构建宏中消费。3.2 get_version_from_pubspec版本号统一来自 pubspec.yamlImmich 移动应用是 Flutter 项目应用版本号统一维护在 mobile/pubspec.yaml 的version字段格式为x.y.zbuild。get_version_from_pubspec 负责解析def get_version_from_pubspec require yaml pubspec_path File.join(Dir.pwd, ../.., pubspec.yaml) pubspec YAML.load_file(pubspec_path) version_string pubspec[version] version_string ? version_string.split().first.split(-).first : nil end注意路径Dir.pwd/../..fastlane 运行时的工作目录是mobile/ios/上溯两级正好是mobile/pubspec.yaml。取值逻辑先按截掉构建号如1.2.345取1.2.3再按-截掉预发布后缀如1.2.3-rc1取1.2.3保证写入 Info.plist 的版本号符合 App Store 的x.y.z规范。3.3 configure_code_signing三个 Target 的手动签名Immich iOS 工程包含三个需要独立签名配置的 Target主应用Runner、分享扩展ShareExtension、小组件扩展WidgetExtension对应 mobile/ios/Runner/ 下的目录结构。configure_code_signing 对每个 Target 各调用一次update_code_signing_settingsupdate_code_signing_settings( use_automatic_signing: false, path: ./Runner.xcodeproj, team_id: ENV[FASTLANE_TEAM_ID] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, bundle_identifier: base_bundle_id, # Runner profile_name: profile_name_main, targets: [Runner] ) # ShareExtensionbundle id #{base_bundle_id}.ShareExtensiontargets: [ShareExtension] # WidgetExtensionbundle id #{base_bundle_id}.Widgettargets: [WidgetExtension]要点子扩展的 bundle ID 由基础标识派生base.ShareExtension与base.Widget三个标识必须各自拥有独立的 Provisioning Profileprofile_name使用 sigh 刚下载安装的 Profile 名称见下文各 lane实现下载即用的 Profile 绑定team_id支持通过FASTLANE_TEAM_ID环境变量覆盖默认团队便于多团队环境。3.4 build_and_upload版本号、构建号、打包与上传build_and_upload 是 dev 与 prod 两个 lane 共用的主干参数为api_key、base_bundle_id、configuration默认Release、distribute_external默认true、version_number、三个 Profile 名称及可选group_id。执行顺序可选设置主版本号若传入version_number调用increment_version_number(version_number:)显式覆盖prod lane 传 pubspec 版本dev lane 不传沿用工程内版本递增构建号increment_build_number的构建号取latest_testflight_build_number(api_key:, app_identifier:) 1即通过 App Store Connect API 查询该 App 在 TestFlight 的最新构建号再 1避免构建号冲突构建build_app使用scheme: Runner、workspace: Runner.xcworkspace、export_method: app-storexcargs来自build_xcargsexport_options中显式列出三个 bundle ID 与 Profile 的映射signingStyle: manual、signingCertificate为 Distribution 证书上传upload_to_testflight(api_key:, skip_waiting_for_build_processing: true, distribute_external:)跳过等待 Apple 处理构建处理过程可稍后在 App Store Connect 查看。四、四个 Lane 逐一解析fastlane 文档 列出了四个可用命令逐一对照 Fastfile 中的实现4.1 ios gha_testflight_dev — 开发构建到 TestFlight[bundle exec] fastlane ios gha_testflight_dev描述iOS Development Build to TestFlight (requires separate bundle ID)对应 gha_testflight_dev lane。流程get_api_key获取 App Store Connect 凭据对DEV_BUNDLE_ID及其.ShareExtension、.Widget三个标识分别调用sigh(api_key:, app_identifier:, force: true)从 App Store Connect 下载/安装 Provisioning Profile并从lane_context[SharedValues::SIGH_NAME]捕获每次 sigh 生成的 Profile 名称configure_code_signing用开发 bundle IDtech.futo.immich.testflight写入三个 Target 的签名配置调用build_and_upload注意三个特殊参数configuration: Profile—— 使用Profile配置而非 Release 配置编译distribute_external: false—— 上传后不自动分发外部测试员group_id: DEV_GROUP_ID—— 通过CUSTOM_GROUP_ID构建参数切换到开发专用 App Group。独立的开发 bundle ID 意味着开发版 App 可与正式版共存于同一设备且互不干扰共享数据。4.2 ios gha_release_prod — 正式版到 TestFlight[bundle exec] fastlane ios gha_release_prod描述iOS Release to TestFlight对应 gha_release_prod lane。与 dev lane 的步骤完全同构差异在于三个 sigh 下载的是生产标识app.alextran.immich及其子扩展的 Profileconfigure_code_signing使用BASE_BUNDLE_IDbuild_and_upload传入version_number: get_version_from_pubspec即把 pubspec.yaml 中的主版本号显式写入 Info.plist保证 TestFlight 上的版本与仓库声明版本严格一致configuration保持默认的Release不注入group_id沿用工程默认 App Groupdistribute_external: false正式版上传同样不自动分发给外部测试员由维护者在 App Store Connect 手动管理测试组。4.3 ios release_manual — 手动发布本地自动签名[bundle exec] fastlane ios release_manual描述iOS Manual Release对应 release_manual lane。这是面向本地开发者、不依赖 App Store Connect API Key 的 laneenable_automatic_code_signing( path: ./Runner.xcodeproj, targets: [Runner, ShareExtension, WidgetExtension] ) increment_version_number(version_number: get_version_from_pubspec) increment_build_number(build_number: latest_testflight_build_number 1) gym( scheme: Runner, workspace: Runner.xcworkspace, configuration: Release, export_method: app-store, skip_package_ipa: false, xcargs: -skipMacroValidation -allowProvisioningUpdates, export_options: { method: app-store, signingStyle: automatic, uploadBitcode: false, uploadSymbols: true, compileBitcode: false } ) upload_to_testflight(skip_waiting_for_build_processing: true)与两个 gha lane 的关键差异使用自动签名enable_automatic_code_signingsigningStyle: automaticxcargs中的-allowProvisioningUpdates允许构建时自动创建/更新 Profile不传 API Key 直接基于本机凭据上传gym的skip_package_ipa: false表示保留打包出的.ipa产物。注意该 lane 中latest_testflight_build_number未显式传递api_key参数与 gha lane 中的调用方式显式传入api_key不同这是源码中的实际差异。4.4 ios gha_build_only — 仅构建、不上传[bundle exec] fastlane ios gha_build_only描述iOS Build Only (no TestFlight upload)对应 gha_build_only lane。源码注释说明其设计意图Use the same build process as the dev TestFlight lane, just skip the upload. This ensures PR builds validate the same way as dev TestFlight builds使用与 dev TestFlight lane 相同的构建流程仅跳过上传确保 PR 构建与 dev TestFlight 构建的验证方式一致。实现上复用 dev lane 的 sigh 三件套与configure_code_signingDEV_BUNDLE_IDconfiguration: Releasedev lane 用的是Profile即 PR 构建按 Release 配置验证build_app加skip_package_ipa: true产出构建产物但不打包 ipa、不上传 TestFlight。五、与 GitHub Actions 的集成build-mobile.yml 中的build-sign-iosjobL198 起是这三个 gha lane 的实际调用方运行于macos-26runner在 main 分支、手动触发或非 fork PR 上执行。完整链路L207-L314环境准备选择 Xcode 26.2sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer用ruby/setup-rubyRuby 3.3bundler-cache: true工作目录./mobile/ios准备 Bundler 环境通过 mise 执行install:ci与codegen并用flutter build ios --config-only --no-codesign解析 Swift 包依赖API Key 落地从 secrets 读取 base64 编码的APP_STORE_CONNECT_API_KEY解码写入~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8——这正是 Fastfile 中key_filepath拼接的路径证书导入将 base64 编码的IOS_CERTIFICATE_P12解码为certificate.p12随后security create-keychain创建临时build.keychain并设为默认导入证书后执行security find-identity -v -p codesigning build.keychain校验身份存在。这一步对应 Fastfile 中 Certificate is imported by GHA workflow into build.keychain 的注释——lane 内部假定证书已在 Keychain 中就位按环境分发 laneL296-L303if [[ $DEPLOY ! true ]]; then bundle exec fastlane gha_build_only elif [[ $ENVIRONMENT development ]]; then bundle exec fastlane gha_testflight_dev else bundle exec fastlane gha_release_prod fi即非部署场景PR 构建走gha_build_only仅验证编译签名部署且环境为 development 走gha_testflight_dev部署且为生产环境走gha_release_prod。该 step 还显式传入APP_STORE_CONNECT_API_KEY_ID、APP_STORE_CONNECT_API_KEY_ISSUER_ID、FASTLANE_TEAM_ID等环境变量并设置FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120、FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6以放宽 xcodebuild settings 查询的超时与重试 5.收尾always()条件下删除build.keychainL305-L308并把产物mobile/ios/Runner.ipa上传为 artifactios-release-ipaL310-L314。六、关键要素速查要素值 / 位置作用生产 Bundle IDapp.alextran.immichAppfile正式 App 标识开发 Bundle IDtech.futo.immich.testflightFastfile开发 TestFlight 构建专用可与正式版共存开发 App Groupgroup.app.immich.share.testflightFastfile经CUSTOM_GROUP_ID注入隔离开发/生产共享数据签名身份Apple Distribution: FUTO Holdings, Inc. (2W7AC6T8T5)FastfileCI 手动签名使用的 Distribution 证书三个签名 TargetRunner / ShareExtension / WidgetExtensionFastfile各自独立 bundle ID 与 Provisioning Profile版本号来源mobile/pubspec.yamlFastfile统一 Flutter 应用的版本声明构建号来源TestFlight 最新构建号 1Fastfile自动避免构建号冲突环境密钥APP_STORE_CONNECT_API_KEY_ID/APP_STORE_CONNECT_API_KEY_ISSUER_ID/IOS_CERTIFICATE_P12/IOS_CERTIFICATE_PASSWORD/FASTLANE_TEAM_ID由 build-mobile.yml secrets 注入七、小结Immich iOS 的 fastlane 配置体现了一套典型的多 bundle ID 手动签名 API Key 驱动的移动发布方案通过gha_build_only/gha_testflight_dev/gha_release_prod三个 lane 分别覆盖 PR 验证、开发分发与正式分发release_manual则保留了本地自动签名的手动发布通道版本号统一收敛到 Flutter 的pubspec.yaml构建号由 TestFlight 远端状态自动推导Provisioning Profile 由 sigh 按需下载并即时绑定到 Runner、ShareExtension、WidgetExtension 三个 Target。配合 build-mobile.yml 中 Keychain 生命周期管理创建—导入—使用—删除与 API Key 落地流程整条链路可在全无状态 runner 上重复执行。对需要管理含扩展 Target 的 iOS 应用发布流程的团队这套常量集中定义、辅助方法复用、lane 按环境切分的 Fastfile 组织方式是一个可直接参考的范式。【免费下载链接】immichHigh performance self-hosted photo and video management solution.项目地址: https://gitcode.com/GitHub_Trending/im/immich创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考