Compare commits

...

4 Commits

Author SHA1 Message Date
e167bd983f ios: use generic xcode signing selector
Some checks failed
TestFlight Release / testflight (push) Failing after 19s
2026-06-25 21:25:13 -07:00
e4dd91564f ios: unlock signing keychain before build
Some checks failed
TestFlight Release / testflight (push) Failing after 17s
2026-06-25 21:20:31 -07:00
3bfde476a6 ios: use single identity signing p12
Some checks failed
TestFlight Release / testflight (push) Failing after 16s
2026-06-25 21:18:54 -07:00
b8676027db ios: trust Apple root in CI signing keychain
Some checks failed
TestFlight Release / testflight (push) Failing after 8s
2026-06-25 21:12:53 -07:00
4 changed files with 80 additions and 4 deletions

View File

@@ -108,7 +108,10 @@ jobs:
-T /usr/bin/xcodebuild
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${keychain_password}" "${keychain_path}"
security find-identity -v -p codesigning "${keychain_path}"
echo "SYBIL_SIGNING_KEYCHAIN_PATH=${keychain_path}" >> "${GITHUB_ENV}"
{
echo "SYBIL_SIGNING_KEYCHAIN_PATH=${keychain_path}"
echo "SYBIL_SIGNING_KEYCHAIN_PASSWORD=${keychain_password}"
} >> "${GITHUB_ENV}"
- name: Build and upload to TestFlight
working-directory: ios
@@ -124,6 +127,10 @@ jobs:
run: |
set -euo pipefail
security unlock-keychain -p "${SYBIL_SIGNING_KEYCHAIN_PASSWORD}" "${SYBIL_SIGNING_KEYCHAIN_PATH}"
security list-keychains -d user -s "${SYBIL_SIGNING_KEYCHAIN_PATH}" $(security list-keychains -d user | sed 's/[ "]//g')
security find-identity -v -p codesigning "${SYBIL_SIGNING_KEYCHAIN_PATH}"
SYBIL_VERSION_TAG="${TAG_NAME}" bundle exec fastlane ios beta
- name: Locate IPA

View File

@@ -6,6 +6,7 @@ SYBIL_APP_STORE_APPLE_ID=6759442828
SYBIL_PROVIDER_PUBLIC_ID=c043d167-ad88-4036-84ea-76c223f1b1b2
SYBIL_PROVISIONING_PROFILE_SPECIFIER=Sybil AppStore CI
SYBIL_CODE_SIGN_IDENTITY=Apple Distribution: James Magahern (DQQH5H6GBD)
SYBIL_XCODE_CODE_SIGN_IDENTITY=Apple Distribution
SYBIL_SIGNING_CERTIFICATE_ID=
SYBIL_SIGNING_KEYCHAIN=

View File

@@ -42,6 +42,11 @@ certificate and provisioning profile values into the repository secrets listed
above. The workflow uses the `Sybil AppStore CI` provisioning profile name by
default.
Fastlane keeps two signing names separate. `SYBIL_CODE_SIGN_IDENTITY` is the
exact certificate common name used when exporting a local p12 for secrets, while
`SYBIL_XCODE_CODE_SIGN_IDENTITY` defaults to the generic `Apple Distribution`
selector that Xcode uses during archive/export.
If the Apple team has reached the Distribution certificate limit, set
`SYBIL_SIGNING_CERTIFICATE_ID` to the portal id for a certificate whose private
key exists in the local login keychain before running `create_ci_signing`. The

View File

@@ -20,6 +20,7 @@ APP_STORE_APPLE_ID = ENV.fetch("SYBIL_APP_STORE_APPLE_ID", "6759442828")
PROVIDER_PUBLIC_ID = ENV.fetch("SYBIL_PROVIDER_PUBLIC_ID", "c043d167-ad88-4036-84ea-76c223f1b1b2")
PROFILE_SPECIFIER = ENV["SYBIL_PROVISIONING_PROFILE_SPECIFIER"].to_s.strip.empty? ? "Sybil AppStore CI" : ENV["SYBIL_PROVISIONING_PROFILE_SPECIFIER"]
SIGNING_CERTIFICATE_NAME = ENV["SYBIL_CODE_SIGN_IDENTITY"].to_s.strip.empty? ? "Apple Distribution: James Magahern (DQQH5H6GBD)" : ENV["SYBIL_CODE_SIGN_IDENTITY"]
XCODE_CODE_SIGN_IDENTITY = ENV["SYBIL_XCODE_CODE_SIGN_IDENTITY"].to_s.strip.empty? ? "Apple Distribution" : ENV["SYBIL_XCODE_CODE_SIGN_IDENTITY"]
IOS_ROOT = File.expand_path("..", __dir__)
PROJECT_FILE = File.join(IOS_ROOT, "Sybil.xcodeproj")
PROJECT_SPEC = File.join(IOS_ROOT, "project.yml")
@@ -77,7 +78,7 @@ def release_version
end
def xcode_build_setting(key, value)
"#{key}=#{value.to_s.shellescape}"
"#{key.to_s.shellescape}=#{value.to_s.shellescape}"
end
def env_line(key, value)
@@ -188,6 +189,67 @@ def recreate_app_store_profile(certificate_id)
profile_path
end
def signing_identity_p12(p12_path, p12_password)
work_dir = File.join(SIGNING_OUTPUT_DIR, "single-identity")
FileUtils.rm_rf(work_dir)
FileUtils.mkdir_p(work_dir)
all_pem = File.join(work_dir, "all.pem")
stdout, stderr, status = Open3.capture3(
"openssl", "pkcs12",
"-in", p12_path,
"-nodes",
"-passin", "pass:#{p12_password}",
"-out", all_pem
)
UI.user_error!("Could not inspect exported p12\n#{stderr}\n#{stdout}") unless status.success?
records = File.read(all_pem).split(/(?=Bag Attributes\n)/)
cert_record = records.find do |record|
record.include?("friendlyName: #{SIGNING_CERTIFICATE_NAME}") && record.include?("-----BEGIN CERTIFICATE-----")
end
UI.user_error!("Could not find #{SIGNING_CERTIFICATE_NAME} certificate in exported p12") unless cert_record
local_key_id = cert_record[/localKeyID: (.+)/, 1].to_s.strip
UI.user_error!("Could not resolve localKeyID for #{SIGNING_CERTIFICATE_NAME}") unless present?(local_key_id)
key_record = records.find do |record|
record.include?("localKeyID: #{local_key_id}") && record.include?("-----BEGIN PRIVATE KEY-----")
end
UI.user_error!("Could not find private key for #{SIGNING_CERTIFICATE_NAME}") unless key_record
cert_path = File.join(work_dir, "identity.cer.pem")
key_path = File.join(work_dir, "identity.key.pem")
File.write(cert_path, cert_record[/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m])
File.write(key_path, key_record[/-----BEGIN PRIVATE KEY-----.*?-----END PRIVATE KEY-----/m])
root_cer = File.join(work_dir, "AppleIncRootCertificate.cer")
wwdr_cer = File.join(work_dir, "AppleWWDRCAG3.cer")
root_pem = File.join(work_dir, "AppleIncRootCertificate.pem")
wwdr_pem = File.join(work_dir, "AppleWWDRCAG3.pem")
chain_pem = File.join(work_dir, "chain.pem")
run_silent("curl", "-fsSL", "https://www.apple.com/appleca/AppleIncRootCertificate.cer", "-o", root_cer, error_message: "Could not download Apple root certificate")
run_silent("curl", "-fsSL", "https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer", "-o", wwdr_cer, error_message: "Could not download Apple WWDR G3 certificate")
run_silent("openssl", "x509", "-inform", "DER", "-in", root_cer, "-out", root_pem, error_message: "Could not convert Apple root certificate")
run_silent("openssl", "x509", "-inform", "DER", "-in", wwdr_cer, "-out", wwdr_pem, error_message: "Could not convert Apple WWDR G3 certificate")
File.write(chain_pem, File.read(wwdr_pem) + File.read(root_pem))
single_p12_path = File.join(work_dir, "appstore-signing.p12")
run_silent(
"openssl", "pkcs12", "-export",
"-inkey", key_path,
"-in", cert_path,
"-certfile", chain_pem,
"-name", SIGNING_CERTIFICATE_NAME,
"-out", single_p12_path,
"-passout", "pass:#{p12_password}",
error_message: "Could not create single-identity p12"
)
FileUtils.cp(single_p12_path, p12_path)
ensure
FileUtils.rm_rf(work_dir) if present?(work_dir)
end
def app_store_connect_key_options
key_id = ENV["APP_STORE_CONNECT_API_KEY_ID"]
issuer_id = ENV["APP_STORE_CONNECT_API_ISSUER_ID"]
@@ -293,6 +355,7 @@ platform :ios do
end
UI.user_error!("Could not find exported p12 at #{p12_path}") unless File.exist?(p12_path)
signing_identity_p12(p12_path, p12_password)
profile_path = recreate_app_store_profile(cert_id)
UI.user_error!("Could not resolve generated provisioning profile path") unless present?(profile_path) && File.exist?(profile_path)
@@ -348,7 +411,7 @@ platform :ios do
xcode_build_setting("CODE_SIGN_STYLE", "Manual"),
xcode_build_setting("DEVELOPMENT_TEAM", TEAM_ID),
xcode_build_setting("PROVISIONING_PROFILE_SPECIFIER", PROFILE_SPECIFIER),
xcode_build_setting("CODE_SIGN_IDENTITY", SIGNING_CERTIFICATE_NAME)
xcode_build_setting("CODE_SIGN_IDENTITY", XCODE_CODE_SIGN_IDENTITY)
]
if present?(ENV["SYBIL_SIGNING_KEYCHAIN_PATH"])
xcode_args << xcode_build_setting("OTHER_CODE_SIGN_FLAGS", "--keychain #{ENV.fetch("SYBIL_SIGNING_KEYCHAIN_PATH")}")
@@ -371,7 +434,7 @@ platform :ios do
provisioningProfiles: {
APP_IDENTIFIER => PROFILE_SPECIFIER
},
signingCertificate: SIGNING_CERTIFICATE_NAME,
signingCertificate: XCODE_CODE_SIGN_IDENTITY,
teamID: TEAM_ID,
manageAppVersionAndBuildNumber: false,
uploadSymbols: true,