Azimjon Abdurasulov

DevOps 1: iOS CI/CD 2026 — Xcode Cloud, GitHub Actions va kalitsiz imzolash

🛠️ DevOps Engineering

DevOps 1: iOS CI/CD 2026 — Xcode Cloud, GitHub Actions va kalitsiz imzolash

Holat: 2026-yil sentabr (Xcode 26.x, Xcode 27, iOS 27). Narxlar, runner nomlari va versiyalar tez o'zgaradi — har bir raqam yonida manba bor, ishlatishdan oldin rasmiy sahifani tekshiring.

Kursdagi CI/CD — Fastlane darsi asoslarni (Fastfile, match, gym, pilot) o'rgatadi. Bu dars bir qadam chuqurroq: 2026-yildagi haqiqiy pipeline qanday quriladi.

Mundarija

  1. Pipeline anatomiyasi
  2. Xcode Cloud — chuqurroq
  3. GitHub Actions macOS runner'lari (2026)
  4. Kalitsiz code signing: App Store Connect API key
  5. [Build'ni yuklash: xcodebuild -exportArchive](#upload)
  6. fastlane 2026-yilda
  7. TestFlight chegaralari
  8. Privacy Manifest — CI'ni to'xtatadigan darvoza
  9. To'liq workflow: PR'da test, tag'da TestFlight
  10. Qaysi CI qachon?
  11. Xulosa
  12. Manbalar

1. Pipeline anatomiyasi {#pipeline}

iOS uchun CI/CD — bu bir xil bosqichlarni har safar, bir xil tartibda, odam aralashmasdan bajarish:

push / PR  →  lint  →  build  →  test  →  archive  →  sign  →  upload  →  TestFlight / App Store
  • CI (Continuous Integration) — har bir PR'da lint + build + test. Maqsad: main doim yig'iladigan (buildable) holatda bo'lsin.
  • CD (Continuous Delivery) — tag yoki release branch'da archive + imzolash + App Store Connect'ga yuklash.

Qoida: pipeline deterministik bo'lishi kerak — bir xil commit har doim bir xil natija beradi. Buning uchun Xcode versiyasi, dependency versiyalari (Package.resolved, Gemfile.lock) va tool versiyalari qat'iy belgilab qo'yiladi.


2. Xcode Cloud — chuqurroq {#xcode-cloud}

Xcode Cloud — Apple'ning App Store Connect'ga ulangan CI/CD xizmati. Workflow to'rt qismdan iborat:

QismNima qiladi
EnvironmentXcode va macOS versiyasi, environment variable'lar (secret sifatida belgilash mumkin — log'da yashiriladi)
Start ConditionsQachon ishga tushadi: branch o'zgarishi, pull request, tag, jadval (schedule) yoki qo'lda
ActionsBuild, Test, Analyze, Archive
Post-ActionsTestFlight'ga tarqatish, xabarnoma yuborish

Narx (2026)

Apple Developer Program a'zoligi bilan oyiga 25 compute-soat bepul. Undan ko'pi obuna bilan:

Oylik soatNarx
100$49.99 / oy
250$99.99 / oy
1 000$399.99 / oy
10 000$3 999.99 / oy

Ishlatilmagan soat keyingi oyga o'tmaydi. Manba: Apple — Get started with Xcode Cloud.

Custom build script'lar: ci_scripts/

Loyiha (yoki workspace) turgan papkada ci_scripts papkasini yarating. Xcode Cloud uch nomdagi script'ni o'zi taniydi:

FaylQachon ishlaydi
ci_post_clone.shrepo klonlangandan keyin, dependency'lar (SPM) yechilishidan oldin
ci_pre_xcodebuild.shhar bir xcodebuild chaqiruvidan oldin
ci_post_xcodebuild.shxcodebuild tugagandan keyin (build yiqilsa ham)

Script bajariladigan bo'lishi shart:

chmod +x ci_scripts/ci_post_clone.sh

Namuna — lint'ni build'dan oldin "darvoza" qilish:

#!/bin/sh
# ci_scripts/ci_post_clone.sh
# Xcode Cloud bu faylni repo klonlangandan keyin ishga tushiradi.
set -eu

# Script ci_scripts papkasidan ishga tushadi — loyiha ildiziga o'tamiz.
cd "$(dirname "$0")/.."

echo "Xcode Cloud build #${CI_BUILD_NUMBER:-local}"

# Lint xatosi bo'lsa build shu yerda to'xtaydi.
brew install swiftlint
swiftlint lint --strict

Muhim: CI_BUILD_NUMBER, CI_XCODEBUILD_ACTION, CI_BRANCH, CI_TAG kabi o'zgaruvchilarni Xcode Cloud o'zi beradi. To'liq ro'yxat: Environment variable reference. Script ichida sudo ishlatib bo'lmaydi.

Cheklovlar

  • Faqat Apple platformalari (iOS, macOS, tvOS, watchOS, visionOS) — backend yoki Android uchun emas.
  • O'z serveringizda (self-hosted) ishlatib bo'lmaydi, build mashinasiga SSH yo'q.
  • Kuchli tomoni: signing va TestFlight'ga tarqatish App Store Connect ichida, deyarli sozlashsiz.

3. GitHub Actions macOS runner'lari (2026) {#github-actions}

LabelHolat
macos-15Apple Silicon, faol
macos-26Apple Silicon, 2026-02-26 dan GA
macos-15-intel, macos-26-intelIntel varianti ham bor
macos-132025-12-04 da butunlay o'chirilgan — eski workflow'larni ko'chiring

Standart Apple Silicon runner: 3 core (M1), 7 GB RAM, 14 GB SSD. Manba: GitHub-hosted runners, macOS 26 GA, macOS 13 retirement.

Narx: macOS daqiqasi ~10 barobar qimmat

RunnerNarx (1 daqiqa)
Linux 2-core$0.006
Windows 2-core$0.010
macOS$0.062

Manba: About billing for GitHub Actions. Amaliy xulosa: Linux'da bajarsa bo'ladigan ishni (masalan backend testlari, markdown lint) macOS runner'ga qo'ymang.

Xcode versiyasini tanlash

Har bir runner image'da bir nechta Xcode o'rnatilgan. Qaysilari borligini image'ning README faylidan ko'ring (runner-images) va aniq versiyani tanlang:

sudo xcode-select -s /Applications/Xcode_26.4.app
xcodebuild -version

Cache

actions/cache bilan SPM paketlarini saqlash build'ni tezlashtiradi. Bitta repo uchun standart limit 10 GB, 7 kun ishlatilmagan cache o'chiriladi (cache limitlari). To'liq DerivedData odatda bir necha GB bo'lgani uchun faqat SPM paketlarini cache qilish ko'proq foyda beradi.


4. Kalitsiz code signing: App Store Connect API key {#signing}

Eski usul — CI'da Apple ID + parol + 2FA sessiyasi. Bu mo'rt: sessiya muddati tugaydi, 2FA kodi so'raladi. Zamonaviy usul — App Store Connect API key (.p8 fayl):

  1. App Store Connect → Users and Access → Integrations → App Store Connect API → yangi Team Key. Kalitni faqat Account Holder yoki Admin yarata oladi.
  2. Kalitga eng kichik yetarli rolni bering: TestFlight'ga yuklash uchun odatda App Manager yetarli.
  3. Uch qiymatni CI secret sifatida saqlang: ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_P8_BASE64 (.p8 faylning base64 ko'rinishi). .p8 fayl faqat bir marta yuklab olinadi — uni xavfsiz joyda saqlang.

Shundan keyin xcodebuild o'zi sertifikat va profillarni yaratadi yoki yangilaydi:

xcodebuild archive \
  -project MyApp.xcodeproj -scheme MyApp -configuration Release \
  -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
  -allowProvisioningUpdates \
  -authenticationKeyPath "$HOME/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
  -authenticationKeyID "$ASC_KEY_ID" \
  -authenticationKeyIssuerID "$ASC_ISSUER_ID"

-allowProvisioningUpdates — xcodebuildga Apple Developer sayti bilan gaplashishga ruxsat beradi. Automatic signing target'larida profil, App ID va sertifikatni yaratadi yoki yangilaydi; manual signing'da esa yetishmayotgan profillarni yuklab oladi. Bu ma'lumot xcodebuild -help chiqishidan olingan (Xcode 26.5).

Qo'lda boshqariladigan sertifikat bo'lsa: vaqtinchalik keychain

Agar jamoa .p12 sertifikatni o'zi boshqarsa, CI'da uni vaqtinchalik keychain'ga import qilib, ish tugagach o'chiriladi:

#!/bin/bash
# CI: .p12 sertifikatni vaqtinchalik keychain'ga import qilish
set -euo pipefail

KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
CERT_PATH="$RUNNER_TEMP/cert.p12"

echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$CERT_PATH"

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"

Ish oxirida (xato bo'lsa ham) keychain'ni o'chiring: security delete-keychain "$KEYCHAIN_PATH".


5. Build'ni yuklash: xcodebuild -exportArchive {#upload}

ExportOptions.plistda destination = upload bo'lsa, xcodebuild -exportArchive archive'ni eksport qiladi va App Store Connect'ga yuklaydi. Alohida Transporter chaqirish shart emas.

<?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>method</key>
    <string>app-store-connect</string>
    <key>destination</key>
    <string>upload</string>
    <key>manageAppVersionAndBuildNumber</key>
    <true/>
</dict>
</plist>

Xcode 15.3'dan boshlab method qiymatlari yangilandi. app-store → **app-store-connect**, ad-hoc → **release-testing**, development → **debugging**. Eskilari hali ishlaydi, lekin deprecated. Qo'shimcha foydali kalitlar:

  • manageAppVersionAndBuildNumber — standart qiymati YES: yuklashda build raqamini Xcode o'zi boshqaradi.
  • testFlightInternalTestingOnly — YES bo'lsa, bu build'ni external TestFlight yoki App Store'ga chiqarib bo'lmaydi. PR va dev branch build'lari uchun qulay.

Barcha kalitlarni o'zingiz ko'rishingiz mumkin: xcodebuild -help → "Available keys for -exportOptionsPlist".

altool haqida: xcrun altool --notarize-app 2023-11-01 da o'chirilgan — bu faqat macOS notarizatsiya funksiyasi, uning o'rniga notarytool ishlatiladi. App Store Connect'ga yuklash uchun xcodebuild -exportArchive (yoki Transporter) — eng sodda yo'l. Manba: Apple — notarization tool deprecation.


6. fastlane 2026-yilda {#fastlane}

  • 2021-yil oxirida Google homiyligi to'xtadi. Loyiha 2023-10-27 dan Mobile Native Foundation (MNF) qaramog'ida. Hamjamiyat uni faol qo'llab-quvvatlaydi. Manba: MNF discussion #194.
  • CI'da autentifikatsiya uchun app_store_connect_api_key action ishlatiladi — Apple ID/2FA kerak emas:
# fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build yig'ib TestFlight'ga yuklash"
  lane :beta do
    api_key = app_store_connect_api_key(
      key_id: ENV.fetch("ASC_KEY_ID"),
      issuer_id: ENV.fetch("ASC_ISSUER_ID"),
      key_content: ENV.fetch("ASC_KEY_P8_BASE64"),
      is_key_content_base64: true
    )
    build_app(scheme: "MyApp", export_method: "app-store")
    upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
  end
end
  • Xavfsizlik darsi (2026): rubyzip kutubxonasida path-traversal zaifligi topilgan (CVE-2026-85396, HIGH). Uning tuzatilgan versiyasi fastlane'ning dependency cheklovi bilan to'qnashadi. 2026-sentabr holatiga ko'ra muammo issue #30175da hali ochiq. Xulosa: fastlane'ni doim Bundler orqali, versiyasi Gemfile.lockda qotirilgan holda ishga tushiring (bundle exec fastlane beta) va dependency yangilanishlarini kuzatib boring.

Qachon fastlane kerak? Screenshot generatsiyasi (snapshot), metadata yuklash (deliver) yoki ko'p lane'li murakkab jarayonlar bo'lsa — foydali. Faqat "archive + upload" kerak bo'lsa, fastlane'siz xcodebuild yetarli (4–5 bo'limlar).


7. TestFlight chegaralari {#testflight}

ParametrQiymat
Internal tester'lar100 tagacha (App Store Connect jamoa a'zolari), har biri 30 tagacha qurilmada
External tester'lar10 000 tagacha, 100 tagacha guruhga bo'linadi
Beta App ReviewExternal tester'larga versiyaning birinchi build'ini berishdan oldin talab qilinadi
Build muddatiYuklangandan 90 kun keyin build ishlamay qoladi

Manbalar: TestFlight limits 2026, TestFlight overview.


8. Privacy Manifest — CI'ni to'xtatadigan darvoza {#privacy-manifest}

2024-yil 1-maydan App Store Connect quyidagi holatlardagi yuklashni rad etadi:

  • ilova yoki SDK Required Reason API ishlatib, sababini PrivacyInfo.xcprivacyda ko'rsatmagan bo'lsa (masalan UserDefaults, fayl timestamp'lari, disk hajmi, system boot time);
  • Apple'ning "keng tarqalgan uchinchi tomon SDK'lari" ro'yxatidagi kutubxona o'z privacy manifest'i va imzosisiz qo'shilgan bo'lsa.

Manba: Apple — Privacy manifest requirements.

Minimal namuna — ilova UserDefaultsdan faqat o'z ma'lumotlari uchun foydalanadi (sabab kodi CA92.1):

<?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>NSPrivacyTracking</key>
    <false/>
    <key>NSPrivacyTrackingDomains</key>
    <array/>
    <key>NSPrivacyCollectedDataTypes</key>
    <array/>
    <key>NSPrivacyAccessedAPITypes</key>
    <array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
</dict>
</plist>

CI uchun amaliy qoida: yangi SDK qo'shadigan PR'da manifest'ni ham ko'rib chiqing. Xcode Organizer'da archive'ni o'ng tugma bilan bosib Generate Privacy Report tanlasangiz, barcha manifest'lar jamlangan hisobot chiqadi.


9. To'liq workflow: PR'da test, tag'da TestFlight {#workflow}

Fayl: .github/workflows/ios.yml. Kerakli secret'lar: ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_P8_BASE64. Loyiha automatic signingda bo'lsin va repo'da ExportOptions.plist (5-bo'lim) tursin.

name: iOS CI

on:
  pull_request:
    branches: [main]
  push:
    tags: ["v*"]

concurrency:
  group: ios-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    if: github.event_name == 'pull_request'
    runs-on: macos-26
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode_26.4.app

      - name: Cache SPM packages
        uses: actions/cache@v4
        with:
          path: ~/Library/Developer/Xcode/DerivedData/**/SourcePackages
          key: spm-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}
          restore-keys: spm-${{ runner.os }}-

      - name: Test
        run: |
          set -o pipefail
          xcodebuild test \
            -project MyApp.xcodeproj -scheme MyApp \
            -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
            -resultBundlePath TestResults.xcresult

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: TestResults.xcresult

  testflight:
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: macos-26
    timeout-minutes: 45
    env:
      ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
      ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode_26.4.app

      - name: Write App Store Connect API key
        env:
          ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
        run: |
          mkdir -p "$HOME/private_keys"
          echo -n "$ASC_KEY_P8_BASE64" | base64 --decode -o "$HOME/private_keys/AuthKey_${ASC_KEY_ID}.p8"

      - name: Archive
        run: |
          set -o pipefail
          xcodebuild archive \
            -project MyApp.xcodeproj -scheme MyApp -configuration Release \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            -allowProvisioningUpdates \
            -authenticationKeyPath "$HOME/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
            -authenticationKeyID "$ASC_KEY_ID" \
            -authenticationKeyIssuerID "$ASC_ISSUER_ID"

      - name: Export and upload to App Store Connect
        run: |
          xcodebuild -exportArchive \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            -exportOptionsPlist ExportOptions.plist \
            -exportPath "$RUNNER_TEMP/export" \
            -allowProvisioningUpdates \
            -authenticationKeyPath "$HOME/private_keys/AuthKey_${ASC_KEY_ID}.p8" \
            -authenticationKeyID "$ASC_KEY_ID" \
            -authenticationKeyIssuerID "$ASC_ISSUER_ID"

      - name: Remove API key
        if: always()
        run: rm -f "$HOME/private_keys/AuthKey_${ASC_KEY_ID}.p8"

Tekshiruv ro'yxati:

  • Xcode_26.4.app va iPhone 17 nomlari runner image'da borligini README'dan tekshiring. Kerak bo'lsa bitta qadam qo'shib, xcrun simctl list devices available chiqishini ko'ring.
  • concurrency — bitta branch'da yangi push kelsa, eski ish bekor qilinadi (pul tejaydi).
  • Tag push qilish: git tag v1.4.0 && git push origin v1.4.0.

10. Qaysi CI qachon? {#tanlash}

VaziyatTavsiya
Kichik jamoa, faqat Apple platformalari, oyiga ≤ 25 soat buildXcode Cloud — sozlash eng kam
Backend/web ham GitHub'da, bitta joyda boshqarmoqchisizGitHub Actions
Faqat mobil, tayyor "step"lar va avtomatik signing kerakCodemagic (500 bepul M2 daqiqa/oy) yoki Bitrise (Hobby: 300 kredit/oy) — narxni rasmiy sahifada tekshiring
Juda ko'p build, macOS daqiqalari qimmatga tushyaptiO'z Mac mini'laringiz + virtualizatsiya (masalan, ochiq manbali Tart). Loyiha jamoasi 2026-yilda OpenAI'ga o'tgani uchun uning kelajagini kuzatib boring

Manbalar: Codemagic pricing, Bitrise pricing, Tart.


11. Xulosa {#xulosa}

  • Pipeline bosqichlari: lint → build → test → archive → sign → upload. Har birining versiyasini qotiring.
  • **App Store Connect API key + -allowProvisioningUpdates** — 2026-yilda CI'da imzolashning eng sodda yo'li.
  • ExportOptions.plistda app-store-connect + destination: upload — eksport va yuklash bitta qadamda.
  • macOS runner daqiqasi Linux'dan ~10 barobar qimmat. macos-13 yo'q; macos-15/macos-26 ishlating.
  • Privacy manifest'siz SDK butun release'ni to'xtatadi. Uni PR darajasida tekshiring.

Manbalar {#manbalar}

  1. Apple — Get started with Xcode Cloud
  2. Apple — Xcode Cloud environment variable reference
  3. GitHub — GitHub-hosted runners
  4. GitHub — About billing for GitHub Actions
  5. GitHub changelog — macOS 13 runner closing down
  6. GitHub changelog — macOS 26 GA
  7. Apple — altool notarization deprecation
  8. Apple — Privacy manifest requirements
  9. fastlane — App Store Connect API
  10. Mobile Native Foundation — fastlane transfer

Markdown manba

iOS Lessons