App Store Connect 업로드: Xcode 로그인 없이 API 키로
아카이브는 만들어졌습니다. 이제 IPA 로 뽑아 올리기만 하면 되는데, 여기서 Xcode 에 애플 계정을 붙이라는 요구가 세 번 나옵니다. 세 번 다 피할 수 있습니다.
손에 있는 것은 배포 인증서와 프로비저닝 프로파일, 그리고 App Store Connect API 키뿐입니다. 그것만으로 끝까지 갔습니다.
수동 서명이라고 적어야 수동이 되는 내보내기
xcodebuild -exportArchive 는 ExportOptions.plist 를 읽습니다. 이 파일에 서명 방식을 적지 않으면 Xcode 가 알아서 하겠다고 나서고, 그러려면 계정이 필요합니다.
<key>method</key><string>app-store-connect</string>
<key>signingStyle</key><string>manual</string>
<key>teamID</key><string>…</string>
<key>signingCertificate</key><string>iPhone Distribution: …</string>
<key>provisioningProfiles</key>
<dict>
<key>번들ID</key><string>프로파일 이름</string>
</dict>provisioningProfiles 는 번들ID 를 열쇠로 두고 프로파일의 이름을 값으로 적습니다. 파일 경로가 아니라 이름입니다. 프로파일 안에 들어 있는 Name 필드를 그대로 써야 하는데, EAS 가 만든 프로파일은 이름이 *[expo] <번들ID> AppStore <타임스탬프> 처럼 생겼습니다. 옮겨 적다 한 글자만 틀려도 못 찾습니다.
security cms -D -i <프로파일> > /tmp/pp.plist
/usr/libexec/PlistBuddy -c "Print :Name" /tmp/pp.plist파일이 있는 것만으로는 안 되는 프로파일
프로파일을 어딘가에 잘 보관해 두었다고 해서 빌드가 찾는 것은 아닙니다. Xcode 는 정해진 폴더만 봅니다.
security cms -D -i <프로파일> > /tmp/pp.plist
UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" /tmp/pp.plist)
cp <프로파일> ~/Library/MobileDevice/Provisioning\ Profiles/$UUID.mobileprovision파일 이름이 UUID 여야 합니다. 보관본은 보관본대로 두고 이 자리에는 복사본을 넣습니다.
키를 이름으로 찾는 altool
업로드는 xcrun altool 이 하고, 계정 대신 App Store Connect API 키를 받습니다.
xcrun altool --upload-app -f <ipa> -t ios --apiKey <KEY_ID> --apiIssuer <ISSUER_ID>--apiKey 에 넘기는 것은 파일 경로가 아니라 키 ID 입니다. 파일은 정해진 자리에서 정해진 이름으로 찾습니다.
~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8경로를 지정하는 옵션이 없습니다. 그래서 키를 다른 곳에 보관하고 있다면 사본이 하나 더 살게 됩니다. 저는 그 사본을 지우지 않고 두되 권한만 지켜보기로 했습니다. 매 업로드마다 필요한 파일을 매번 복사했다 지우는 것보다, 있는 자리를 알고 600 으로 두는 편이 낫다고 봤습니다.
올리기 전에 하는 검증
altool 에는 업로드하지 않고 같은 검사만 도는 모드가 있습니다.
xcrun altool --validate-app -f <ipa> -t ios --apiKey <KEY_ID> --apiIssuer <ISSUER_ID>VERIFY SUCCEEDED with no errors이걸 먼저 도는 이유는 업로드가 되돌릴 수 없기 때문입니다. 빌드 번호가 소모되고 TestFlight 에 뜹니다. 검증이 통과하면 그다음에 --upload-app 으로 같은 파일을 올립니다.
UPLOAD SUCCEEDED with no errors
Transferred 19786228 bytes in 2.688 seconds계정을 요구하던 세 자리
정리하면 이렇습니다. 내보내기에서 한 번, 서명할 프로파일을 찾을 때 한 번, 업로드에서 한 번입니다. 셋 다 자동으로 두면 Xcode 가 애플 서버에 물어보려 하고, 그러려면 로그인한 계정이 있어야 합니다.
수동으로 바꾸면 대신 내가 알려줘야 할 것이 늘어납니다. 프로파일의 정확한 이름, 그 파일이 있어야 하는 자리, API 키의 이름 규칙. 셋 다 틀리면 조용히 실패하지 않고 그 자리에서 멈추기 때문에 찾기는 어렵지 않았습니다.
이 흐름은 클라우드 빌드를 떠나면서 필요해진 것입니다. 떠날 때 무엇이 딸려 있었는지는 따로 적었습니다.
함께 읽기
- Expo config plugin이 반영되지 않는 이유: prebuild와 mod 순서생명시계 앱에 Face ID 잠금을 붙였습니다. expo-local-authentication 을 설치하고 app.json 에 플러그인과 권한 문구를 적었습니다.
- WWDR 중간 인증서: 코드 서명 신원이 0개로 나오는 이유$ security import dist-cert.p12 -k /Library/Keychains/login.keychain-db
- EAS 없이 Expo 로컬 빌드: 서명 자료를 모으고도 남는 다섯 가지클라우드 빌드를 떠나기로 하고(왜 옮기는지는 따로 적었습니다) 서명 자료부터 모았습니다. iOS 배포 인증서와 프로비저닝 프로파일, 안드로이드 업로드 키스토어와 비밀번호 두 개, 스토어 API 키까지 한자리에 놓고 확인했습니다. 코드 서명 신원도 유효하게 잡혔습니다.
- Android Studio 없이 실기기 빌드: 최소 환경 838MB와 각 구성요소의 역할Expo SDK 57 앱을 안드로이드 실기기에 올리려고 맥을 열었는데, 빌드에 필요한 것이 하나도 없었습니다. java -version은 "Unable to locate a Java Runtime"을 냈고 ANDROIDHOME은 비어 있었으며 adb는 명령어 자체가 없었습니다. Xcode만 들어 있는 기계였습니다.
- Expo 로컬 릴리스 빌드: 17분 만에 죽고 3분 48초에 끝난 이유클라우드 빌드를 떠나 로컬에서 릴리스를 만들어 보기로 했습니다. 서명 자료는 다 모아 두었고 도구도 깔았습니다. 첫 빌드는 17분 51초를 돌고 나서 죽었습니다.