Руководство — Автоматизация

Fastlane + выделенный Mac-сервер: полное руководство по автоматизации

Автоматизируйте весь процесс релиза iOS — сборки, подпись кода, скриншоты и деплой в TestFlight — используя Fastlane на выделенном сервере Mac Mini M4.

35 мин чтения Обновлено в январе 2025

1. Зачем Fastlane на выделенном Mac?

Fastlane — стандартный инструмент автоматизации для разработки iOS и Android. Он обрабатывает всё — от подписи кода до деплоя в TestFlight. Запуск Fastlane на выделенном Mac Mini M4 даёт вам:

Постоянный Keychain

Сертификаты подписи кода сохраняются между запусками. Не нужно импортировать/экспортировать при каждой сборке.

Быстрые инкрементальные сборки

DerivedData сохраняется, поэтому fastlane build занимает 2–4 минуты вместо 15+.

Скриншоты на симуляторе

Запускайте fastlane snapshot с реальными симуляторами для всех размеров устройств.

Надёжные деплои

Стабильная, выделенная среда означает меньше нестабильных деплоев и проблем с подписью кода.

2. Установка Fastlane

Подключитесь к Mac Mini M4 по SSH и установите Fastlane. Мы рекомендуем Homebrew для простейшей установки:

Вариант A: Установка через Homebrew (рекомендуется)

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install Fastlane
brew install fastlane

# Verify installation
fastlane --version
# fastlane 2.225.0

Вариант B: Установка через RubyGems

# Use the system Ruby or install rbenv for version management
gem install fastlane -NV

# Or with Bundler (recommended for team consistency):
# Create a Gemfile in your project root
cat > Gemfile <<'EOF'
source "https://rubygems.org"

gem "fastlane"
gem "cocoapods"  # if using CocoaPods
EOF

bundle install

Инициализация Fastlane в проекте

# Navigate to your project directory
cd /path/to/your/ios-project

# Initialize Fastlane
fastlane init

# Choose option 4: "Manual setup"
# This creates the fastlane/ directory with Appfile and Fastfile

3. Настройка Match для подписи кода

Fastlane Match хранит сертификаты подписи кода и профили обеспечения в приватном Git-репозитории или облачном хранилище. Это гарантирует, что все машины (и члены команды) используют одну и ту же подпись.

Инициализация Match

# Initialize match (choose "git" for storage)
fastlane match init

# This creates fastlane/Matchfile

Настройка Matchfile

# fastlane/Matchfile

git_url("https://github.com/your-org/ios-certificates.git")

storage_mode("git")

type("appstore")  # default type, can be overridden per lane

app_identifier(["com.yourcompany.myapp"])
username("your-apple-id@example.com")

# For CI environments, use App Store Connect API key instead of username/password
# api_key_path("fastlane/AuthKey.json")

Генерация сертификатов

# Generate development certificates and profiles
fastlane match development

# Generate App Store distribution certificates and profiles
fastlane match appstore

# For ad-hoc distribution
fastlane match adhoc

# On CI, use readonly mode to avoid accidentally creating new certs
fastlane match appstore --readonly

4. Создание Fastfile (лейны сборки, тестирования, деплоя)

Вот полный Fastfile с лейнами для сборки, тестирования и деплоя вашего iOS-приложения:

# fastlane/Fastfile

default_platform(:ios)

platform :ios do

  # ---- SHARED ----

  before_all do
    setup_ci if ENV['CI']  # Configures keychain for CI environments
  end

  # ---- BUILD ----

  desc "Build the app for testing"
  lane :build do
    match(type: "development", readonly: true)

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      configuration: "Debug",
      destination: "generic/platform=iOS Simulator",
      derived_data_path: "DerivedData",
      skip_archive: true,
      skip_codesigning: true
    )
  end

  # ---- TEST ----

  desc "Run all unit and UI tests"
  lane :test do
    run_tests(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      devices: ["iPhone 16 Pro"],
      derived_data_path: "DerivedData",
      result_bundle: true,
      output_directory: "fastlane/test_results",
      parallel_testing: true,
      concurrent_workers: 4
    )
  end

  # ---- BETA ----

  desc "Build and push a new beta to TestFlight"
  lane :beta do
    # Ensure we are on a clean git state
    ensure_git_status_clean

    # Fetch App Store certificates
    match(type: "appstore", readonly: true)

    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )

    # Build the app
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "DerivedData",
      output_directory: "fastlane/builds"
    )

    # Upload to TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      api_key_path: "fastlane/AuthKey.json"
    )

    # Commit the version bump
    commit_version_bump(
      message: "chore: bump build number [skip ci]",
      force: true
    )

    # Tag the release
    add_git_tag(
      tag: "beta/#{lane_context[SharedValues::BUILD_NUMBER]}"
    )

    push_to_git_remote
  end

  # ---- RELEASE ----

  desc "Build and submit to App Store Review"
  lane :release do
    match(type: "appstore", readonly: true)

    # Increment version number (patch)
    increment_version_number(bump_type: "patch")
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )

    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "DerivedData"
    )

    upload_to_app_store(
      submit_for_review: true,
      automatic_release: false,
      api_key_path: "fastlane/AuthKey.json",
      precheck_include_in_app_purchases: false
    )

    commit_version_bump(message: "chore: release #{lane_context[SharedValues::VERSION_NUMBER]}")
    add_git_tag
    push_to_git_remote
  end

  # ---- ERROR HANDLING ----

  error do |lane, exception|
    # Send notification on failure (Slack, email, etc.)
    # slack(
    #   message: "Lane #{lane} failed: #{exception.message}",
    #   success: false
    # )
  end
end

5. Настройка автоматических скриншотов

Fastlane Snapshot автоматически делает скриншоты для App Store на нескольких устройствах и языках. На выделенном Mac это работает надёжно без конкуренции за ресурсы.

# Initialize snapshot
fastlane snapshot init

# This creates:
# - fastlane/Snapfile
# - fastlane/SnapshotHelper.swift (add to UI test target)

Настройка Snapfile

# fastlane/Snapfile

devices([
  "iPhone 16 Pro Max",
  "iPhone 16 Pro",
  "iPhone SE (3rd generation)",
  "iPad Pro 13-inch (M4)"
])

languages([
  "en-US",
  "fr-FR",
  "de-DE",
  "ja"
])

scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)

# Speed up by running in parallel
concurrent_simulators(true)

Добавление Snapshot в UI-тесты

// In your XCUITest file:
import XCTest

class ScreenshotTests: XCTestCase {

    override func setUp() {
        continueAfterFailure = false
        let app = XCUIApplication()
        setupSnapshot(app)
        app.launch()
    }

    func testHomeScreen() {
        snapshot("01_HomeScreen")
    }

    func testDetailScreen() {
        let app = XCUIApplication()
        app.cells.firstMatch.tap()
        snapshot("02_DetailScreen")
    }

    func testSettings() {
        let app = XCUIApplication()
        app.tabBars.buttons["Settings"].tap()
        snapshot("03_Settings")
    }
}

Добавление лейна для скриншотов

  # Add to your Fastfile:
  desc "Capture App Store screenshots"
  lane :screenshots do
    capture_screenshots
    frame_screenshots(white: true)  # Add device frames
    upload_to_app_store(
      skip_binary_upload: true,
      skip_metadata: true,
      api_key_path: "fastlane/AuthKey.json"
    )
  end

6. Деплой в TestFlight

Для CI-окружений используйте API-ключ App Store Connect вместо учётных данных Apple ID. Это позволяет избежать запросов 2FA на безголовых серверах.

Создание API-ключа

  1. Перейдите в App Store Connect > Users and Access > Keys
  2. Нажмите кнопку + для генерации нового API-ключа
  3. Выберите роль App Manager
  4. Скачайте файл .p8
  5. Запишите Key ID и Issuer ID

Создание JSON API-ключа

# fastlane/AuthKey.json
{
  "key_id": "YOUR_KEY_ID",
  "issuer_id": "YOUR_ISSUER_ID",
  "key": "-----BEGIN PRIVATE KEY-----\nYOUR_P8_KEY_CONTENT\n-----END PRIVATE KEY-----",
  "in_house": false
}

# IMPORTANT: Add this to .gitignore!
echo "fastlane/AuthKey.json" >> .gitignore

Запуск деплоя

# Deploy to TestFlight
fastlane beta

# Or run the full release pipeline
fastlane release

7. Интеграция с CI/CD

Fastlane интегрируется с любой CI/CD платформой. Вот пример GitHub Actions с использованием self-hosted раннера Mac Mini M4:

# .github/workflows/deploy.yml
name: Deploy to TestFlight

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: [self-hosted, macOS, ARM64, M4]

    env:
      MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
      MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_TOKEN }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up App Store Connect API Key
        run: |
          mkdir -p fastlane
          echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/AuthKey.json

      - name: Install dependencies
        run: |
          bundle install
          pod install  # if using CocoaPods

      - name: Deploy to TestFlight
        run: bundle exec fastlane beta

      - name: Clean up API key
        if: always()
        run: rm -f fastlane/AuthKey.json

8. Лучшие практики

Используйте Bundler для фиксации версии Fastlane

Добавьте Gemfile с фиксированной версией Fastlane. Используйте bundle exec fastlane для обеспечения согласованности версий во всех окружениях.

Используйте API-ключи App Store Connect, а не учётные данные Apple ID

API-ключи избавляют от запросов 2FA и более безопасны для CI-окружений.

Используйте match --readonly в CI

Предотвращает случайное создание новых сертификатов. Генерируйте сертификаты вручную только по необходимости.

Используйте setup_ci в CI-окружениях

Это создаёт временный Keychain, чтобы не засорять системный Keychain и предотвращает диалоги разрешений.

Сохраняйте DerivedData для более быстрых сборок

На выделенном Mac укажите derived_data_path для повторного использования артефактов сборки между запусками.

Похожие руководства

Автоматизируйте ваши iOS-релизы уже сегодня

Получите выделенный Mac Mini M4 и запускайте Fastlane с неограниченными сборками. От $75/месяц с 7-дневным бесплатным пробным периодом.