- vừa được xem lúc

Flutter CI/CD to TestFlight with Github Actions

0 0 5

Người đăng: Nguyễn Đạt

Theo Viblo Asia

1. Mở đầu

Nếu bạn tìm thấy bài viết này thì khả năng cao bạn cũng đã biết sơ về các khái niệm như Github Actions, CI/CD, TestFlight, Flutter. Ở đây mình sẽ không giải thích thêm mấy từ khóa này.

2. Chuẩn bị

  • Account Apple Developer để có thể đăng nhập được App Store Connect
  • Project Flutter, Github

Bạn chưa có tài khoản ? Đăng ký chứ còn gì nữa ~

  • Tài khoản cá nhân phí hằng năm là 99$
  • Tài khoản doanh nghiệp phí hằng năm là 299$

2.1 App Store Connect API Key

Để có thể đẩy file .ipa lên testflight bằng câu lệnh khi CI/CD, Bạn cần phải có apiKey, apiIssuerfile private key .p8

Truy cập vào link này và tạo key, tải file về sẽ có 1 file .p8 https://appstoreconnect.apple.com/access/integrations

Tên của file .p8 sẽ có dạng AuthKey_$apiKey.p8

2.2 Apple Certificate và Provisioning profile

Bước này rất quan trọng, không có nó không Sign remote được

2.2.1 Tạo CertificateSigningRequest

Nó giống như việc bạn đi xin việc, nhà tuyển dụng cần biết bạn là ai, ở đâu, mail nào, sử dụng thiết bị nào... na ná thế.

Tại máy tính của bạn làm theo thao tác sau:

  • Mở keychain Access.app trong máy Mac của bạn
  • Phía trên bên góc trái Keychain Access -> Certificate Assistant -> Request a Cert ...
  • Nhập mail của bạn (mail đã đăng ký AppleID), tên chữ ký, và chọn Saved to disk
  • Sau khi tạo xong nó sẽ ra 1 file như này CertificateSigningRequest.certSigningRequest

2.2.2 Tạo Certificate

Truy cập vào trang tạo Cert tại Apple Developer

  • nhấn nút + to tổ bố kế bên chữ Certificates
  • Nhấn tiếp tục và Chọn Apple Distribution
  • Nhấn tiếp tục và Upload file .certSigningRequest vừa tạo từ keychain
  • Nhấn tiếp tục và và kết thúc, ở đây bạn ko cần download file .cer về

2.2.3 Tạo Profiles

Bấm qua mục Profiles

  • Nhấn tạo
  • mục Distribution chọn App Store Connect
  • App ID bạn chọn đến App hiện tại của bạn ( cái app đã tạo từ tab Identifiers )
  • Chọn Certificate đã tạo từ bước 2.2.2
  • Provisioning Profile Name -> Đặt tên cho cái Profile của bạn -> Ấn Generate
  • Download file về và bạn sẽ được 1 file .mobileprovision

Video Tham khảo, nó ko chính xác.

2.2.4 Testing Profiles

Bạn phải kiểm tra file .mobileprovision vừa tạo có Sign được không, nếu không được do bạn có vấn đề !

Cách để kiểm tra như sau:

  • Trong project Flutter của bạn Mở Xcode folder IOS
  • Bên trong Xcode tab Signing Bỏ chọn Automatically manage signing
  • Mục IOS -> Provisioning profile -> Nhấn để import file .mobileprovision bạn vừa tạo ở bước 2.2.3
  • Nếu không thấy xuất hiện vấn đề gì như hình thì file đã OK, Sign thành công

Nếu bạn thấy có bất kì lỗi đỏ to đùng nào được hiển thị ra, yếu tố tâm linh nhất đó là reset lại máy, vấn đề sẽ được giải quyết ! (mình đã bị lỗi No signing certificate "iOS Distribution" found, thử reset máy và thành công ) Nếu không giải quyết được thì google lỗi đó 😄

3. Cài đặt

Câu lệnh thần thánh bạn cần biết để convert file sang base64 Sau khi gõ, nội dung base64 sẽ được lưu trong clipboard của bạn (chỉ việc Ctrl+V để paste ra)

base64 -i path/to/file.txt | pbcopy

Truy cập vào Reponsitory -> Settings -> Secrets and variables để tạo mới 1 secret

3.1 Tạo Chữ ký khi build cho bản android (có thể bỏ qua)

Bạn không biết .jks, key.properties của android là gì ? Bạn có thể xem cách tạo từ bài viết gốc flutter tại đây

Khi bạn đã có tạo được 2 trên, chúng sẽ có đường dẫn như sau trong source flutter: android/app/upload-keystore.jks android/key.properties

3.1.1

Tạo 1 secrect và đặt tên là UPLOAD_KEYSTORE_BASE64

Tiếp đó mở terminal command line lên và gõ

base64 -i android/app/upload-keystore.jks | pbcopy

Sau khi gõ xong Ctrl+V vào ô value Secrect, tiếp đó ấn Add là xong.

3.1.2

tạo lần lượt các github secret mang tên STOREPASSWORD, KEYPASSWORD tương ứng với giá trị trong file android/key.properties của bạn

3.2 CERTIFICATE .p12 base64

Bạn có thể lấy từ keychain Access.app cũng được, nhưng ở đây mình sẽ hướng dẫn lấy từ XCode.

  • Xcode -> Settings -> Accounts
  • Chọn Team của bạn (đã được Sign thành công ở bước 2.2.4) -> Click vào Manage Certificates...
  • Ở mục Apple Distribution -> chuột phải vào certificate còn sáng -> nhấn Export
  • Bạn sẽ thấy được file .p12, Đặt tên thành BUILD_CERTIFICATE cho mình, cài đặt password cho file đó -> nhấn Save

Chuyển file BUILD_CERTIFICATE.p12 sang base64 thông qua lệnh

base64 -i BUILD_CERTIFICATE.p12 | pbcopy

  • Tạo github secret BUILD_CERTIFICATE_BASE64 từ file tương tự như 3.1.1
  • Tạo github secret P12_PASSWORD password vừa nhập khi tạo file .p12

3.3 Upload Profile .mobileprovision base64

  • Tạo github secret BUILD_PROVISION_PROFILE_BASE64 Lấy từ Provisioning Profile .mobileprovision ở bước 2.2.3

Ở đây mình đặt nó tên là PROVISIONING_PROFILE.mobileprovision nên dùng lệnh:

base64 -i PROVISIONING_PROFILE.mobileprovision | pbcopy

  • Tạo github secret KEYCHAIN_PASSWORD với pass tùy ý đặt

3.4 ExportOptions.plist

Lưu ý phải Sign được profile trước đó.

Để có được file này chính xác làm theo các bước sau:

  • XCode -> phía trên tab có chữ Product -> chọn Archive
  • Đợi 1 lúc build xong sẽ tự nhảy sang màn hình Organizer -> nhấn vào bản vừa build -> chọn Distribute App
  • Chọn mục Custom -> App Store Connect -> Next chọn Export image.png
  • Đợi nó xoay xoay 1 chút, nó sẽ hiện cho bạn màn hình chọn, cứ giữ nguyên mạc định và bấm Next
  • Chọn đến Profile của bạn -> bấm Next -> Cuối cùng là Export image.png
  • Sau khi Export thành công, sẽ có 1 folder vừa tạo ra, đi vào trong Copy file ExportOptions.plistđó vào folder ios tại project flutter

File ExportOptions.plist sẽ có dạng như sau:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <dict> <key>destination</key> <string>export</string> <key>manageAppVersionAndBuildNumber</key> <true/> <key>method</key> <string>app-store</string> <key>provisioningProfiles</key> <dict> <key>xxxxxxx</key> <string>xxxxxxx</string> </dict> <key>signingCertificate</key> <string>Apple Distribution</string> <key>signingStyle</key> <string>manual</string> <key>stripSwiftSymbols</key> <true/> <key>teamID</key> <string>xxxxxxx</string> <key>uploadSymbols</key> <true/> </dict>
</plist> 

Nếu trong file export đó method ghi là app-store-connect bạn có thể sửa lại thành app-store để tránh lỗi method của flutter Chi tiết thì cứ để nguyên trong lúc CI/CD, nó báo lỗi bạn sẽ biết được từ khóa mình đề cập...

3.5 App Store Connect Private API key .p8 base64

  • Tạo github secret APPSTORE_KEY_P8_BASE64 lấy ở bước 2.1 Chuyển file .p8 thành base64.

Nhớ copy ráp đúng cái tên $apiKey để convert

base64 -i AuthKey_$apiKey.p8 | pbcopy

  • Tạo github secret APPSTORE_APIKEY lấy ở bước 2.1, với apiKey tương ứng
  • Tạo github secret APPSTORE_APIISSUER với apiIssuer

3.6 Github Repository Token

Token dùng để tự tạo 1 bản Release sau khi build apk, ipa và upload thành công lên testflight.

  • Truy cập vào Github -> Developer Setting -> Personal Access Token hoặc nhấn Tại đây cho lẹ
  • Chọn Generate New Token (Classic)
  • Chỉ cần chọn quyền Repo là được
  • Quay lại repository của project, tạo github secret REPOSITORY_TOKEN với token vừa tạo

3.7 Full workflows

Bên trong source code của bạn, hãy tạo 1 github workflow với đường dẫn như sau .github/workflows/.main.yaml

name: Build & Release on: # sự kiện khi push lên nhánh master hoặc tag mới bắt đầu chữ 'v' sẽ chạy job push: # branches:  # - master tags: - "v*" jobs: build: name: Build # chạy trên hệ điều hành macos runs-on: macos-latest steps: # Bắt đầu clone repository về máy - name: Clone repository uses: actions/checkout@v4 # Setup Java để chạy - name: Set up Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 # Setup Flutter - name: Set up Flutter uses: subosito/flutter-action@v2 with: channel: stable # version flutter sẽ được lấy từ chính file pubspec.yaml của bạn flutter-version-file: pubspec.yaml architecture: x64 # Xem lại log version Flutter và XCodeBuild - name: Check Flutter Version run: flutter --version - name: Check XCodeBuild Version run: xcodebuild -version # Cài đặt các dependencies - name: Install dependencies run: flutter pub get # Download keystore upload-keystore.jks được tạo từ keytool lúc release Google Play - name: Download Android Keystore run: | echo ${{ secrets.UPLOAD_KEYSTORE_BASE64 }} | base64 --decode > android/app/upload-keystore.jks # Tạo key.properties lúc release Google Play - name: Create key.properties run: | # Download keystore first (ensure success before creating key.properties) if [[ $? -eq 0 ]]; then echo "storePassword=${{ secrets.STOREPASSWORD }}" >> android/key.properties echo "keyPassword=${{ secrets.KEYPASSWORD }}" >> android/key.properties echo "keyAlias=upload" >> android/key.properties echo "storeFile=upload-keystore.jks" >> android/key.properties else echo "Error: Downloading keystore failed. Skipping key.properties creation." exit 1 fi # Tạo chứng chỉ và provisioning profile cho iOS để XCodeBuild có thể build - name: Install the Apple certificate and provisioning profile env: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} # lấy từ keychain access -> export -> export as p12 P12_PASSWORD: ${{ secrets.P12_PASSWORD }} # mật khẩu khi export p12 BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} # lấy từ xcode -> export -> export as provisioning profile KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} # mật khẩu keychain run: | # create variables CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db # import certificate and provisioning profile from secrets echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH # create temporary keychain security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # import certificate to keychain security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH # apply provisioning profile mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles # Bắt đầu build các file cho Android # khi split-per-abi  # - app-armeabi-v7a-release.apk: đại đa số các máy thường dùng bản này (file nhẹ) # - app-arm64-v8a.apk: dành cho máy mới (Samsung đời mới chẵn hạn, file vừa)  # - app-x86_64-release.apk: đồ cổ, intel (file vừa) - name: Build Android run: | # Bản gom lại cho tất cả các máy, máy nào cũng cài được flutter build apk --release # Bản chia nhỏ theo từng loại máy, máy nào cần cài thì cài flutter build apk --release --split-per-abi # Bản cần để upload lên Google Play flutter build appbundle # Pod install cho iOS - name: Pod install run: cd ios && pod install --repo-update && cd .. # Bắt đầu build file ipa cho iOS # ExportOptions.plist được tạo từ XCode -> Product -> Archive -> Export -> Development -> Next -> Next -> Save - name: Build iOS run: | flutter build ipa --release --export-options-plist=ios/ExportOptions.plist # Upload các file đã build lên GitHub artifacts - name: Collect the file and upload as artifact uses: actions/upload-artifact@v4.3.3 with: # Đặt tên là app-release  name: app-release path: | build/app/outputs/flutter-apk/*.apk build/app/outputs/bundle/release/*.aab build/ios/ipa/*.ipa # Này rất cần thiết, Xóa keychain và provisioning profile sau khi build xong - name: Clean up keychain and provisioning profile if: ${{ always() }} run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision # Release job, upload the ipa to App Distribution release: name: Release IPA # Cần job [build] trước đó needs: [build] runs-on: macos-latest steps: # Download các file đã build và được upload lên artifact từ job [build] trước đó - name: Get app-release from artifacts uses: actions/download-artifact@v4.1.7 with: # Lấy từ app-release name: app-release # Lưu vào thư mục build path: build merge-multiple: true # Cài đặt private key .p8 vào máy để xcode có thể nhận diện được - name: Install private API key P8 env: APPSTORE_KEY_P8_BASE64: ${{ secrets.APPSTORE_KEY_P8_BASE64 }} APPSTORE_APIKEY: ${{ secrets.APPSTORE_APIKEY }} run: | mkdir -p ~/private_keys echo -n "$APPSTORE_KEY_P8_BASE64" | base64 --decode > ~/private_keys/AuthKey_$APPSTORE_APIKEY.p8 # Log ra cấu trúc của các file hiện tại - name: Display structure of downloaded files run: ls -R - name: Upload to AppStore env: APPSTORE_APIKEY: ${{ secrets.APPSTORE_APIKEY }} # lấy từ appstore connect -> users and access -> keys -> create key APPSTORE_APIISSUER: ${{ secrets.APPSTORE_APIISSUER }} # lấy từ appstore connect -> users and access -> keys -> create key # khi chạy lệnh này cần phải có file AuthKey_$APPSTORE_APIKEY.p8 đã tải vô trong private_keys trước đó run: | xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey $APPSTORE_APIKEY --apiIssuer $APPSTORE_APIISSUER # Đóng gói file đã build và upload lên GitHub Releases - name: Push to Releases file uses: ncipollo/release-action@v1.14.0 with: name: ${{ github.ref_name }} # tên release artifacts: | build/app/outputs/flutter-apk/*.apk build/app/outputs/bundle/release/*.aab build/ios/ipa/*.ipa tag: ${{ github.ref_name }} # tag release token: "${{ secrets.REPOSITORY_TOKEN }}" # token repo, tạo từ setting -> developer settings -> personal access token 

4. Kết quả

Sau cả tỉ lần mình thử nghiệm thì cuối cùng cũng chạy được image.png

5. Tổng kết

Hi vọng qua bài viết này bạn sẽ nắm được đại khái các cách làm:

  • Tạo Cert, Profile cho Xcode để có thể Sign được và đem sang máy khác
  • Biết cách lấy Key của App Store Connect API
  • Hiểu thêm về github secret, github token
  • Tự động tạo ra được github artifact, github release, tự upload file ipa lên testflight

6. Tài liệu tham khảo

https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development

https://stackoverflow.com/questions/74869907/trying-to-set-certificate-and-provisioning-profile-in-github-actions-for-xcodebu

https://github.com/Apple-Actions/upload-testflight-build/issues/27

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 276

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?

Lời mở đầu. Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter.

0 0 200

- vừa được xem lúc

Flutter Animation: Creating medium’s clap animation in flutte Part II

Trong phần 1 mình đã giới thiệu với các bạn cơ bản về Animation trong Flutter. Score Widget Size Animation.

0 0 60

- vừa được xem lúc

Flutter - GetX - Using GetConnect to handle API request (Part 4)

Giới thiệu. Xin chào các bạn, lại là mình với series về GetX và Flutter.

0 0 345

- vừa được xem lúc

StatefulWidget và StatelessWidget trong Flutter

I. Mở đầu. Khi các bạn build một ứng dụng với Flutter thì Widgets là thứ không thể thiếu đúng không ạ. Và 2 loại Widget không thể thiếu đó là StatefullWidget và StatelessWidget.

0 0 138

- vừa được xem lúc

Tìm hiểu về Riverpod - Provider nhưng không hắn :v

Trong Flutter có rất nhiều các quản lý state: Provider, Bloc, GetX, Redux,... khó mà nói cái nào tốt hơn cái nào. Tuy nhiên nếu bạn đã làm quen với Provider thì không ngại để tìm hiểu thêm về Riverpod. Một bản nâng cấp của Provider. Nếu bạn để ý thì cái tên "Riverpod" là các chữ cái của "Provider" đ

0 0 63