mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat(mobile): switch iOS code signing to fastlane match
- Replace manual certificate and provisioning profile handling with fastlane match - Match automatically syncs certificates and profiles from a private git repository - Simplifies CI/CD workflow by removing 8 secrets (replaced with 2: MATCH_PASSWORD and MATCH_GIT_BASIC_AUTHORIZATION) - Add new lanes: sync_certificates and regenerate_certificates for easier maintenance - When certificates expire, just run 'fastlane regenerate_certificates' locally Benefits: - Single source of truth for code signing - Automatic certificate/profile management - Easier onboarding for new team members - Simpler secret rotation when certificates expire Required new GitHub secrets: - MATCH_PASSWORD: Encryption password for the match repository - MATCH_GIT_BASIC_AUTHORIZATION: base64(username:token) for repo access Removed secrets (no longer needed): - IOS_CERTIFICATE_P12 - IOS_CERTIFICATE_PASSWORD - IOS_PROVISIONING_PROFILE - IOS_PROVISIONING_PROFILE_SHARE_EXTENSION - IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION - IOS_DEVELOPMENT_PROVISIONING_PROFILE - IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION - IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION
This commit is contained in:
@@ -21,6 +21,20 @@ platform :ios do
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})"
|
||||
BASE_BUNDLE_ID = "app.alextran.immich"
|
||||
|
||||
# App identifiers for production
|
||||
PROD_APP_IDENTIFIERS = [
|
||||
"app.alextran.immich",
|
||||
"app.alextran.immich.ShareExtension",
|
||||
"app.alextran.immich.Widget"
|
||||
]
|
||||
|
||||
# App identifiers for development
|
||||
DEV_APP_IDENTIFIERS = [
|
||||
"app.alextran.immich.development",
|
||||
"app.alextran.immich.development.ShareExtension",
|
||||
"app.alextran.immich.development.Widget"
|
||||
]
|
||||
|
||||
# Helper method to get App Store Connect API key
|
||||
def get_api_key
|
||||
app_store_connect_api_key(
|
||||
@@ -32,6 +46,17 @@ platform :ios do
|
||||
)
|
||||
end
|
||||
|
||||
# Helper method to sync certificates and profiles using match
|
||||
def sync_code_signing(app_identifiers:, readonly: true)
|
||||
match(
|
||||
type: "appstore",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: readonly,
|
||||
keychain_name: ENV["KEYCHAIN_NAME"] || "login.keychain",
|
||||
keychain_password: ENV["KEYCHAIN_PASSWORD"] || ""
|
||||
)
|
||||
end
|
||||
|
||||
# Helper method to get version from pubspec.yaml
|
||||
def get_version_from_pubspec
|
||||
require 'yaml'
|
||||
@@ -54,7 +79,7 @@ end
|
||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}",
|
||||
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix} AppStore",
|
||||
profile_name: "match AppStore #{BASE_BUNDLE_ID}#{bundle_suffix}",
|
||||
targets: ["Runner"]
|
||||
)
|
||||
|
||||
@@ -65,7 +90,7 @@ end
|
||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
|
||||
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension AppStore",
|
||||
profile_name: "match AppStore #{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension",
|
||||
targets: ["ShareExtension"]
|
||||
)
|
||||
|
||||
@@ -76,7 +101,7 @@ end
|
||||
team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID,
|
||||
code_sign_identity: CODE_SIGN_IDENTITY,
|
||||
bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
|
||||
profile_name: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget AppStore",
|
||||
profile_name: "match AppStore #{BASE_BUNDLE_ID}#{bundle_suffix}.Widget",
|
||||
targets: ["WidgetExtension"]
|
||||
)
|
||||
end
|
||||
@@ -115,9 +140,9 @@ end
|
||||
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
"#{app_identifier}" => "#{app_identifier} AppStore",
|
||||
"#{app_identifier}.ShareExtension" => "#{app_identifier}.ShareExtension AppStore",
|
||||
"#{app_identifier}.Widget" => "#{app_identifier}.Widget AppStore"
|
||||
"#{app_identifier}" => "match AppStore #{app_identifier}",
|
||||
"#{app_identifier}.ShareExtension" => "match AppStore #{app_identifier}.ShareExtension",
|
||||
"#{app_identifier}.Widget" => "match AppStore #{app_identifier}.Widget"
|
||||
},
|
||||
signingStyle: "manual",
|
||||
signingCertificate: CODE_SIGN_IDENTITY
|
||||
@@ -136,10 +161,8 @@ end
|
||||
lane :gha_testflight_dev do
|
||||
api_key = get_api_key
|
||||
|
||||
# Install development provisioning profiles
|
||||
install_provisioning_profile(path: "profile_dev.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_dev_share.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_dev_widget.mobileprovision")
|
||||
# Sync certificates and profiles using match
|
||||
sync_code_signing(app_identifiers: DEV_APP_IDENTIFIERS)
|
||||
|
||||
# Configure code signing for dev bundle IDs
|
||||
configure_code_signing(bundle_id_suffix: "development")
|
||||
@@ -157,11 +180,8 @@ end
|
||||
lane :gha_release_prod do
|
||||
api_key = get_api_key
|
||||
|
||||
# Install provisioning profiles
|
||||
install_provisioning_profile(path: "profile.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_share.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_widget.mobileprovision")
|
||||
|
||||
# Sync certificates and profiles using match
|
||||
sync_code_signing(app_identifiers: PROD_APP_IDENTIFIERS)
|
||||
|
||||
# Configure code signing for production bundle IDs
|
||||
configure_code_signing
|
||||
@@ -215,10 +235,8 @@ end
|
||||
# Use the same build process as production, just skip the upload
|
||||
# This ensures PR builds validate the same way as production builds
|
||||
|
||||
# Install provisioning profiles (use development profiles for PR builds)
|
||||
install_provisioning_profile(path: "profile_dev.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_dev_share.mobileprovision")
|
||||
install_provisioning_profile(path: "profile_dev_widget.mobileprovision")
|
||||
# Sync certificates and profiles using match
|
||||
sync_code_signing(app_identifiers: DEV_APP_IDENTIFIERS)
|
||||
|
||||
# Configure code signing for dev bundle IDs
|
||||
configure_code_signing(bundle_id_suffix: "development")
|
||||
@@ -233,9 +251,9 @@ end
|
||||
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
"#{BASE_BUNDLE_ID}.development" => "#{BASE_BUNDLE_ID}.development AppStore",
|
||||
"#{BASE_BUNDLE_ID}.development.ShareExtension" => "#{BASE_BUNDLE_ID}.development.ShareExtension AppStore",
|
||||
"#{BASE_BUNDLE_ID}.development.Widget" => "#{BASE_BUNDLE_ID}.development.Widget AppStore"
|
||||
"#{BASE_BUNDLE_ID}.development" => "match AppStore #{BASE_BUNDLE_ID}.development",
|
||||
"#{BASE_BUNDLE_ID}.development.ShareExtension" => "match AppStore #{BASE_BUNDLE_ID}.development.ShareExtension",
|
||||
"#{BASE_BUNDLE_ID}.development.Widget" => "match AppStore #{BASE_BUNDLE_ID}.development.Widget"
|
||||
},
|
||||
signingStyle: "manual",
|
||||
signingCertificate: CODE_SIGN_IDENTITY
|
||||
@@ -243,4 +261,30 @@ end
|
||||
)
|
||||
end
|
||||
|
||||
desc "Sync all certificates and profiles (run locally to update match repo)"
|
||||
lane :sync_certificates do
|
||||
# Sync production certificates and profiles
|
||||
match(
|
||||
type: "appstore",
|
||||
app_identifier: PROD_APP_IDENTIFIERS,
|
||||
readonly: false
|
||||
)
|
||||
|
||||
# Sync development certificates and profiles
|
||||
match(
|
||||
type: "appstore",
|
||||
app_identifier: DEV_APP_IDENTIFIERS,
|
||||
readonly: false
|
||||
)
|
||||
end
|
||||
|
||||
desc "Regenerate all certificates and profiles (use when expired)"
|
||||
lane :regenerate_certificates do
|
||||
# Nuke existing certificates
|
||||
match_nuke(type: "appstore")
|
||||
|
||||
# Generate new ones
|
||||
sync_certificates
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
git_url(ENV["MATCH_GIT_URL"] || "https://github.com/immich-app/ios-certs")
|
||||
|
||||
storage_mode("git")
|
||||
|
||||
type("appstore")
|
||||
|
||||
team_id("2F67MQ8R79")
|
||||
|
||||
app_identifier([
|
||||
"app.alextran.immich",
|
||||
"app.alextran.immich.ShareExtension",
|
||||
"app.alextran.immich.Widget",
|
||||
"app.alextran.immich.development",
|
||||
"app.alextran.immich.development.ShareExtension",
|
||||
"app.alextran.immich.development.Widget"
|
||||
])
|
||||
|
||||
# For all available options run `fastlane match --help`
|
||||
# The docs are available on https://docs.fastlane.tools/actions/match
|
||||
@@ -39,6 +39,30 @@ iOS Release to TestFlight
|
||||
|
||||
iOS Manual Release
|
||||
|
||||
### ios gha_build_only
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios gha_build_only
|
||||
```
|
||||
|
||||
iOS Build Only (no TestFlight upload)
|
||||
|
||||
### ios sync_certificates
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios sync_certificates
|
||||
```
|
||||
|
||||
Sync all certificates and profiles (run locally to update match repo)
|
||||
|
||||
### ios regenerate_certificates
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios regenerate_certificates
|
||||
```
|
||||
|
||||
Regenerate all certificates and profiles (use when expired)
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
Reference in New Issue
Block a user