feat: use fastlane sigh to manage signing profiles (#25089)

* feat: use fastlane sigh to manage signing profiles

* remove unused secrects

* remove unused fallback
This commit is contained in:
Alex 2026-01-07 21:02:21 -06:00 committed by GitHub
parent 1d6a9f6e80
commit 1f20b6471c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 75 additions and 63 deletions

View file

@ -30,18 +30,6 @@ on:
required: true required: true
IOS_CERTIFICATE_PASSWORD: IOS_CERTIFICATE_PASSWORD:
required: true required: true
IOS_PROVISIONING_PROFILE:
required: true
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION:
required: true
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION:
required: true
IOS_DEVELOPMENT_PROVISIONING_PROFILE:
required: true
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION:
required: true
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION:
required: true
FASTLANE_TEAM_ID: FASTLANE_TEAM_ID:
required: true required: true
pull_request: pull_request:
@ -240,35 +228,14 @@ jobs:
mkdir -p ~/.appstoreconnect/private_keys mkdir -p ~/.appstoreconnect/private_keys
echo "$API_KEY_CONTENT" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 echo "$API_KEY_CONTENT" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8
- name: Import Certificate and Provisioning Profiles - name: Import Certificate
env: env:
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }} IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }}
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }}
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }}
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
ENVIRONMENT: ${{ inputs.environment || 'development' }}
working-directory: ./mobile/ios working-directory: ./mobile/ios
run: | run: |
# Decode certificate # Decode certificate
echo "$IOS_CERTIFICATE_P12" | base64 --decode > certificate.p12 echo "$IOS_CERTIFICATE_P12" | base64 --decode > certificate.p12
# Decode provisioning profiles based on environment
if [[ "$ENVIRONMENT" == "development" ]]; then
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE" | base64 --decode > profile_dev.mobileprovision
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_dev_share.mobileprovision
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_dev_widget.mobileprovision
ls -lh profile_dev*.mobileprovision
else
echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > profile.mobileprovision
echo "$IOS_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_share.mobileprovision
echo "$IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_widget.mobileprovision
ls -lh profile*.mobileprovision
fi
- name: Create keychain and import certificate - name: Create keychain and import certificate
env: env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}

View file

@ -33,4 +33,5 @@ Runner/GeneratedPluginRegistrant.*
!default.perspectivev3 !default.perspectivev3
fastlane/report.xml fastlane/report.xml
Gemfile.lock Gemfile.lock
certs/

View file

@ -44,7 +44,7 @@ def get_version_from_pubspec
end end
# Helper method to configure code signing for all targets # Helper method to configure code signing for all targets
def configure_code_signing(bundle_id_suffix: "") def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:)
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
# Runner (main app) # Runner (main app)
@ -54,7 +54,7 @@ end
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}", bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}",
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix} AppStore", profile_name: profile_name_main,
targets: ["Runner"] targets: ["Runner"]
) )
@ -65,7 +65,7 @@ end
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension", bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension AppStore", profile_name: profile_name_share,
targets: ["ShareExtension"] targets: ["ShareExtension"]
) )
@ -76,7 +76,7 @@ end
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
code_sign_identity: CODE_SIGN_IDENTITY, code_sign_identity: CODE_SIGN_IDENTITY,
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget", bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget AppStore", profile_name: profile_name_widget,
targets: ["WidgetExtension"] targets: ["WidgetExtension"]
) )
end end
@ -87,7 +87,10 @@ end
bundle_id_suffix: "", bundle_id_suffix: "",
configuration: "Release", configuration: "Release",
distribute_external: true, distribute_external: true,
version_number: nil version_number: nil,
profile_name_main:,
profile_name_share:,
profile_name_widget:
) )
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}" app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}"
@ -115,9 +118,9 @@ end
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
export_options: { export_options: {
provisioningProfiles: { provisioningProfiles: {
"#{app_identifier}" => "#{app_identifier} AppStore", "#{app_identifier}" => profile_name_main,
"#{app_identifier}.ShareExtension" => "#{app_identifier}.ShareExtension AppStore", "#{app_identifier}.ShareExtension" => profile_name_share,
"#{app_identifier}.Widget" => "#{app_identifier}.Widget AppStore" "#{app_identifier}.Widget" => profile_name_widget
}, },
signingStyle: "manual", signingStyle: "manual",
signingCertificate: CODE_SIGN_IDENTITY signingCertificate: CODE_SIGN_IDENTITY
@ -136,20 +139,35 @@ end
lane :gha_testflight_dev do lane :gha_testflight_dev do
api_key = get_api_key api_key = get_api_key
# Install development provisioning profiles # Download and install provisioning profiles from App Store Connect
install_provisioning_profile(path: "profile_dev.mobileprovision") # Certificate is imported by GHA workflow into build.keychain
install_provisioning_profile(path: "profile_dev_share.mobileprovision") # Capture profile names after each sigh call
install_provisioning_profile(path: "profile_dev_widget.mobileprovision") sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
configure_code_signing(bundle_id_suffix: "development") share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs using the downloaded profile names
configure_code_signing(
bundle_id_suffix: "development",
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
# Build and upload # Build and upload
build_and_upload( build_and_upload(
api_key: api_key, api_key: api_key,
bundle_id_suffix: "development", bundle_id_suffix: "development",
configuration: "Profile", configuration: "Profile",
distribute_external: false distribute_external: false,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
) )
end end
@ -157,20 +175,33 @@ end
lane :gha_release_prod do lane :gha_release_prod do
api_key = get_api_key api_key = get_api_key
# Install provisioning profiles # Download and install provisioning profiles from App Store Connect
install_provisioning_profile(path: "profile.mobileprovision") # Certificate is imported by GHA workflow into build.keychain
install_provisioning_profile(path: "profile_share.mobileprovision") sigh(api_key: api_key, app_identifier: BASE_BUNDLE_ID, force: true)
install_provisioning_profile(path: "profile_widget.mobileprovision") main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for production bundle IDs # Configure code signing for production bundle IDs
configure_code_signing configure_code_signing(
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
# Build and upload with version number # Build and upload with version number
build_and_upload( build_and_upload(
api_key: api_key, api_key: api_key,
version_number: get_version_from_pubspec, version_number: get_version_from_pubspec,
distribute_external: false, distribute_external: false,
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
) )
end end
@ -215,13 +246,26 @@ end
# Use the same build process as production, just skip the upload # Use the same build process as production, just skip the upload
# This ensures PR builds validate the same way as production builds # This ensures PR builds validate the same way as production builds
# Install provisioning profiles (use development profiles for PR builds) api_key = get_api_key
install_provisioning_profile(path: "profile_dev.mobileprovision")
install_provisioning_profile(path: "profile_dev_share.mobileprovision") # Download and install provisioning profiles from App Store Connect
install_provisioning_profile(path: "profile_dev_widget.mobileprovision") # Certificate is imported by GHA workflow into build.keychain
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true)
main_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true)
share_profile_name = lane_context[SharedValues::SIGH_NAME]
sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true)
widget_profile_name = lane_context[SharedValues::SIGH_NAME]
# Configure code signing for dev bundle IDs # Configure code signing for dev bundle IDs
configure_code_signing(bundle_id_suffix: "development") configure_code_signing(
bundle_id_suffix: "development",
profile_name_main: main_profile_name,
profile_name_share: share_profile_name,
profile_name_widget: widget_profile_name
)
# Build the app (same as gha_testflight_dev but without upload) # Build the app (same as gha_testflight_dev but without upload)
build_app( build_app(
@ -233,9 +277,9 @@ end
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
export_options: { export_options: {
provisioningProfiles: { provisioningProfiles: {
"#{BASE_BUNDLE_ID}.development" => "#{BASE_BUNDLE_ID}.development AppStore", "#{BASE_BUNDLE_ID}.development" => main_profile_name,
"#{BASE_BUNDLE_ID}.development.ShareExtension" => "#{BASE_BUNDLE_ID}.development.ShareExtension AppStore", "#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name,
"#{BASE_BUNDLE_ID}.development.Widget" => "#{BASE_BUNDLE_ID}.development.Widget AppStore" "#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name
}, },
signingStyle: "manual", signingStyle: "manual",
signingCertificate: CODE_SIGN_IDENTITY signingCertificate: CODE_SIGN_IDENTITY