Руководство — CI/CD

Как настроить self-hosted раннер CircleCI на Mac Mini M4

Полное пошаговое руководство по установке и настройке self-hosted machine раннера CircleCI на выделенном Mac Mini M4. Нативная производительность Apple Silicon, постоянное кеширование и до 80% экономии по сравнению с облачными macOS ресурсами CircleCI.

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

1. Зачем использовать self-hosted раннер CircleCI на Mac?

CircleCI предлагает облачные среды выполнения macOS, но они имеют значительные затраты и ограничения. Ресурсы macOS в CircleCI используют систему кредитов, где каждая минута на macOS executor потребляет 50–100 кредитов в зависимости от класса ресурсов. Для команд, часто выполняющих сборки iOS, это может быстро составить $300–$500 в месяц и более.

Self-hosted раннер на выделенном Mac Mini M4 от MyRemoteMac полностью устраняет поминутную оплату. Вы получаете выделенную машину Apple Silicon с постоянным хранилищем, тёплыми кешами и полным root-доступом за фиксированную месячную плату от $75/месяц. Вот детальное сравнение:

Характеристика Облачный macOS CircleCI Self-hosted MyRemoteMac
Модель оплаты 50–100 кредитов/мин (~$0.06–$0.12/мин) $75/мес фиксированно (неограниченные минуты)
Архитектура Intel x86 или M1 (общий) Apple M4 (выделенный, последняя модель)
Скорость сборки ~14 мин (средний iOS-проект) ~5 мин (тот же проект)
Сохранение кеша Эфемерный (восстановление при каждой задаче) Постоянный на диске (мгновенно)
Время ожидания в очереди 30 с — 5 мин (зависит от тарифа) 0 с (выделенное оборудование)
Конфиденциальность / Контроль данных Общая инфраструктура Выделенная машина, полный контроль
Пользовательское ПО Только предустановленный образ Полный root-доступ, любое ПО

Ключевое преимущество: При использовании постоянного self-hosted раннера DerivedData, кеши Swift Package Manager и CocoaPods сохраняются между сборками. Одно это может сократить время сборки на 50–70% по сравнению с эфемерными облачными macOS окружениями CircleCI, которые каждый раз начинают с нуля. В сочетании с превосходной однопоточной производительностью чипа M4 ваши сборки iOS будут значительно быстрее.

2. Предварительные требования

Прежде чем начать, убедитесь, что у вас есть следующее:

  • Сервер Mac Mini M4 от MyRemoteMac (от $75/мес)
  • Аккаунт CircleCI с тарифом Performance, Scale или Server (self-hosted раннеры требуют платный план)
  • SSH-доступ к вашему Mac Mini (предоставляется с подпиской MyRemoteMac)
  • Аккаунт Apple Developer (для подписи кода и профилей обеспечения)
  • Базовое знакомство с YAML, конфигурацией CircleCI и командами терминала

3. Шаг 1: Подключение к Mac Mini по SSH и установка зависимостей

Сначала подключитесь к Mac Mini M4 через SSH. Вы получили учётные данные при настройке сервера MyRemoteMac. Нам нужно установить Xcode и инструменты, необходимые для сборок iOS, прежде чем настраивать раннер CircleCI.

Подключение через SSH

# Connect to your Mac Mini M4
ssh admin@your-server-ip

# Verify you're on Apple Silicon
uname -m
# Expected output: arm64

# Check macOS version
sw_vers
# ProductName:    macOS
# ProductVersion: 15.2
# BuildVersion:   24C101

Установка Homebrew и Xcode Command Line Tools

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Add Homebrew to PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install Xcode Command Line Tools
xcode-select --install

# Accept the license agreement
sudo xcodebuild -license accept

Установка Xcode (полная версия)

Для сборок iOS вам нужно полное приложение Xcode. Самый быстрый способ установить его на безголовом сервере — использовать xcodes:

# Install xcodes CLI tool
brew install xcodes

# List available Xcode versions
xcodes list

# Install the latest stable Xcode
xcodes install 16.2

# Set it as the active Xcode
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer

# Verify installation
xcodebuild -version
# Xcode 16.2
# Build version 16C5032a

Установка iOS-симуляторов и инструментов сборки

# Install the iOS 18 simulator runtime
xcodebuild -downloadPlatform iOS

# Verify simulator availability
xcrun simctl list runtimes
# == Runtimes ==
# iOS 18.2 (18.2 - 22C150) - com.apple.CoreSimulator.SimRuntime.iOS-18-2

# Install additional build tools
brew install xcbeautify fastlane swiftlint

4. Шаг 2: Установка агента раннера CircleCI

CircleCI использует агент machine runner для подключения вашего Mac Mini к платформе CircleCI. Machine runner получает задачи от CircleCI и выполняет их непосредственно на хосте macOS. Это отличается от container runner (который работает только на Linux).

Создание выделенного пользователя для раннера

Рекомендуется создать выделенного пользователя для запуска агента CircleCI с целью изоляции безопасности:

# Create a circleci user (optional but recommended)
sudo dscl . -create /Users/circleci
sudo dscl . -create /Users/circleci UserShell /bin/zsh
sudo dscl . -create /Users/circleci RealName "CircleCI Runner"
sudo dscl . -create /Users/circleci UniqueID 550
sudo dscl . -create /Users/circleci PrimaryGroupID 20
sudo dscl . -create /Users/circleci NFSHomeDirectory /Users/circleci
sudo mkdir -p /Users/circleci
sudo chown circleci:staff /Users/circleci

# Or simply use your existing admin user (simpler setup)

Скачивание и установка агента раннера

# Create a directory for the runner
sudo mkdir -p /opt/circleci

# Download the latest CircleCI machine runner for macOS ARM64
# Check https://circleci.com/docs/runner-installation-mac/ for latest version
curl -o /tmp/circleci-runner.pkg \
  https://circleci-binary-releases.s3.amazonaws.com/circleci-runner/1.0/circleci-runner_darwin_arm64.pkg

# Install the runner package
sudo installer -pkg /tmp/circleci-runner.pkg -target /

# Verify the installation
circleci-runner --version

Настройка агента раннера

Создайте конфигурационный файл раннера. Вам понадобится токен аутентификации раннера из панели управления CircleCI (описано в Шаге 3). Пока создайте структуру конфигурационного файла:

# Create the configuration directory
sudo mkdir -p /opt/circleci/config

# Create the runner configuration file
sudo tee /opt/circleci/config/runner-agent-config.yaml << 'EOF'
api:
  auth_token: YOUR_RUNNER_TOKEN_HERE

runner:
  name: mac-mini-m4-runner
  working_directory: /opt/circleci/workdir
  cleanup_working_directory: true
  max_run_time: 5h

  # Optional: limit concurrent tasks
  # command_prefix: ["nice", "-n", "10"]

logging:
  file: /opt/circleci/logs/runner.log
EOF

# Create required directories
sudo mkdir -p /opt/circleci/workdir
sudo mkdir -p /opt/circleci/logs

# Set permissions (adjust user if using dedicated circleci user)
sudo chown -R admin:staff /opt/circleci

Создание macOS Launch Agent для автозапуска

Чтобы раннер запускался автоматически при загрузке и перезапускался при сбое, создайте macOS LaunchDaemon:

# Create the LaunchDaemon plist
sudo tee /Library/LaunchDaemons/com.circleci.runner.plist << 'EOF'
<?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>Label</key>
    <string>com.circleci.runner</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/circleci/circleci-runner</string>
        <string>machine</string>
        <string>--config</string>
        <string>/opt/circleci/config/runner-agent-config.yaml</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/opt/circleci/logs/runner-stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/opt/circleci/logs/runner-stderr.log</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>
EOF

# Load and start the service
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# Verify the service is running
sudo launchctl list | grep circleci
# Expected: PID listed with com.circleci.runner

# Check the logs
tail -f /opt/circleci/logs/runner.log

Важно: Агент раннера не подключится, пока вы не добавите действительный токен аутентификации. Сначала выполните Шаг 3 для генерации токена, затем обновите файл runner-agent-config.yaml реальным токеном и перезапустите сервис.

5. Шаг 3: Настройка раннера в панели управления CircleCI

Теперь вам нужно зарегистрировать раннер в веб-интерфейсе CircleCI и сгенерировать токен аутентификации. Это связывает ваш Mac Mini с вашей организацией CircleCI.

Создание класса ресурсов

В CircleCI self-hosted раннеры организованы по классам ресурсов. Класс ресурсов — это метка, которая связывает ваш .circleci/config.yml с определённым набором раннеров.

  1. Перейдите в Панель управления CircleCIНастройки организацииSelf-Hosted Runners
  2. Нажмите "Create Resource Class"
  3. Задайте Namespace — имя вашей организации (например, your-org)
  4. Задайте имя Resource Class — описательное (например, mac-runner)
  5. Это создаст идентификатор класса ресурсов: your-org/mac-runner

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

  1. После создания класса ресурсов нажмите "Create New Token"
  2. Дайте токену описательное имя (например, mac-mini-m4-token)
  3. Скопируйте сгенерированный токен немедленно — он будет показан только один раз

Обновление конфигурации раннера с вашим токеном

# SSH back into your Mac Mini
ssh admin@your-server-ip

# Update the runner configuration with your real token
sudo nano /opt/circleci/config/runner-agent-config.yaml

# Replace YOUR_RUNNER_TOKEN_HERE with the actual token:
# api:
#   auth_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

# Restart the runner service to apply the new token
sudo launchctl unload /Library/LaunchDaemons/com.circleci.runner.plist
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# Verify the runner connects successfully
tail -20 /opt/circleci/logs/runner.log
# Look for: "Runner is ready to receive jobs"

После подключения раннер должен отображаться как "Online" в панели управления CircleCI в разделе Self-Hosted Runners. Если он не появляется в течение 60 секунд, проверьте файл логов на наличие ошибок.

Использование CircleCI CLI (альтернативный способ)

Вы также можете управлять раннерами через CircleCI CLI:

# Install the CircleCI CLI
brew install circleci

# Authenticate with CircleCI
circleci setup

# Create a resource class via CLI
circleci runner resource-class create your-org/mac-runner \
  "Mac Mini M4 Runner" --generate-token

# List your runners
circleci runner instance list your-org/mac-runner

6. Шаг 4: Создание .circleci/config.yml для сборок iOS

Создайте файл .circleci/config.yml в корне вашего репозитория. Ключевое отличие от стандартной конфигурации CircleCI — использование machine: true с вашим пользовательским resource_class для направления задач на self-hosted раннер.

version: 2.1

jobs:
  build-and-test:
    machine: true
    resource_class: your-org/mac-runner

    environment:
      SCHEME: "MyApp"
      DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
      DERIVED_DATA_PATH: "DerivedData"
      SPM_CACHE_PATH: ".spm-cache"

    steps:
      - checkout

      - run:
          name: Select Xcode version
          command: |
            sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
            xcodebuild -version

      - run:
          name: Resolve Swift Package Dependencies
          command: |
            xcodebuild -resolvePackageDependencies \
              -scheme "$SCHEME" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH"

      - run:
          name: Build the app
          command: |
            xcodebuild build \
              -scheme "$SCHEME" \
              -destination "$DESTINATION" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH" \
              -derivedDataPath "$DERIVED_DATA_PATH" \
              | xcbeautify

      - run:
          name: Run unit tests
          command: |
            xcodebuild test \
              -scheme "$SCHEME" \
              -destination "$DESTINATION" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH" \
              -derivedDataPath "$DERIVED_DATA_PATH" \
              -resultBundlePath TestResults.xcresult \
              | xcbeautify

      - store_test_results:
          path: TestResults.xcresult

      - store_artifacts:
          path: TestResults.xcresult
          destination: test-results

workflows:
  ios-pipeline:
    jobs:
      - build-and-test:
          filters:
            branches:
              only:
                - main
                - develop

Примечание: Значение resource_class: your-org/mac-runner должно точно совпадать с классом ресурсов, который вы создали в панели управления CircleCI. Если значение не совпадает, задачи будут оставаться в очереди бесконечно.

7. Шаг 5: Оптимизация с помощью кеширования и параллелизма

Одно из главных преимуществ self-hosted раннера — постоянное кеширование. Поскольку файловая система раннера постоянна, вам не нужно загружать и скачивать кеши, как при использовании облачных ресурсов CircleCI. Однако есть дополнительные оптимизации для максимальной скорости сборки.

Использование постоянного DerivedData

Поскольку диск раннера сохраняется между сборками, DerivedData уже кешируется автоматически. Используйте постоянный путь:

# In your config.yml steps, always use a consistent DerivedData path:
- run:
    name: Build with persistent cache
    command: |
      xcodebuild build \
        -scheme "$SCHEME" \
        -destination "$DESTINATION" \
        -derivedDataPath ~/DerivedData/"$SCHEME" \
        -clonedSourcePackagesDirPath ~/spm-cache \
        | xcbeautify

# Periodically clean old DerivedData on the runner (cron job):
# 0 3 * * 0 find ~/DerivedData -maxdepth 1 -mtime +7 -exec rm -rf {} +

Сохранение рабочего пространства между задачами

Используйте рабочие пространства CircleCI для передачи артефактов между задачами в одном рабочем процессе:

jobs:
  build:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Build
          command: |
            xcodebuild build \
              -scheme "MyApp" \
              -destination "generic/platform=iOS" \
              -derivedDataPath DerivedData
      - persist_to_workspace:
          root: .
          paths:
            - DerivedData

  test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Run tests
          command: |
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -derivedDataPath DerivedData \
              | xcbeautify

workflows:
  build-test:
    jobs:
      - build
      - test:
          requires:
            - build

Параллельное выполнение тестов

- run:
    name: Run tests in parallel
    command: |
      xcodebuild test \
        -scheme "MyApp" \
        -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
        -destination "platform=iOS Simulator,name=iPhone 15,OS=17.5" \
        -parallel-testing-enabled YES \
        -maximum-parallel-testing-workers 4 \
        -derivedDataPath DerivedData \
        | xcbeautify

Разделение тестов CircleCI

Для больших наборов тестов используйте встроенное разделение тестов CircleCI для распределения тестов между параллельными раннерами:

jobs:
  test:
    machine: true
    resource_class: your-org/mac-runner
    parallelism: 3
    steps:
      - checkout
      - run:
          name: Split and run tests
          command: |
            # Generate test plan
            TESTS=$(circleci tests glob "**/*Tests.swift" | \
              circleci tests split --split-by=timings)

            # Run only this container's portion of tests
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -only-testing:$TESTS \
              | xcbeautify

8. Примеры рабочих процессов

Вот полные, готовые к продакшену примеры рабочих процессов для типичных сценариев iOS CI/CD.

Полный пайплайн сборки, тестирования и деплоя iOS

version: 2.1

jobs:
  lint:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Run SwiftLint
          command: swiftlint lint --reporter json > swiftlint-results.json || true
      - store_artifacts:
          path: swiftlint-results.json

  build-and-test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Select Xcode
          command: sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
      - run:
          name: Resolve dependencies
          command: |
            xcodebuild -resolvePackageDependencies \
              -scheme "MyApp" \
              -clonedSourcePackagesDirPath ~/spm-cache
      - run:
          name: Build and test
          command: |
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -clonedSourcePackagesDirPath ~/spm-cache \
              -derivedDataPath ~/DerivedData/MyApp \
              -resultBundlePath TestResults.xcresult \
              -parallel-testing-enabled YES \
              | xcbeautify
      - store_test_results:
          path: TestResults.xcresult
      - store_artifacts:
          path: TestResults.xcresult

  deploy-testflight:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Select Xcode
          command: sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
      - run:
          name: Install certificates with Fastlane Match
          command: |
            fastlane match appstore --readonly
      - run:
          name: Build and upload to TestFlight
          command: |
            fastlane beta
          environment:
            FASTLANE_USER: ${APPLE_ID}
            MATCH_PASSWORD: ${MATCH_PASSWORD}

workflows:
  ios-pipeline:
    jobs:
      - lint
      - build-and-test:
          requires:
            - lint
      - deploy-testflight:
          requires:
            - build-and-test
          filters:
            branches:
              only: main

Пример интеграции с Fastlane

Если вы используете Fastlane для автоматизации сборки, вот как интегрировать его с self-hosted раннером CircleCI:

# Fastfile (fastlane/Fastfile)
default_platform(:ios)

platform :ios do
  desc "Run tests"
  lane :test do
    scan(
      scheme: "MyApp",
      device: "iPhone 16 Pro",
      derived_data_path: "~/DerivedData/MyApp",
      result_bundle: true,
      output_directory: "./test_output"
    )
  end

  desc "Build and push to TestFlight"
  lane :beta do
    match(type: "appstore", readonly: true)
    increment_build_number(
      build_number: ENV["CIRCLE_BUILD_NUM"]
    )
    gym(
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "~/DerivedData/MyApp"
    )
    pilot(skip_waiting_for_build_processing: true)
  end
end
# .circleci/config.yml using Fastlane
version: 2.1

jobs:
  fastlane-test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bundle install
      - run:
          name: Run Fastlane tests
          command: bundle exec fastlane test
      - store_test_results:
          path: ./test_output
      - store_artifacts:
          path: ./test_output

  fastlane-deploy:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bundle install
      - run:
          name: Deploy to TestFlight
          command: bundle exec fastlane beta

workflows:
  ios-workflow:
    jobs:
      - fastlane-test
      - fastlane-deploy:
          requires:
            - fastlane-test
          filters:
            branches:
              only: main

9. Сравнение производительности

Вот детальное сравнение производительности и стоимости между облачными macOS ресурсами CircleCI и self-hosted Mac Mini M4 от MyRemoteMac:

Бенчмарки времени сборки

Сценарий Облачный macOS CircleCI Self-hosted Mac Mini M4 Улучшение
Чистая сборка (средний проект) 14 мин 5 мин В 2.8 раза быстрее
Инкрементальная сборка 14 мин (без кеша) 1.5 мин В 9.3 раза быстрее
Разрешение зависимостей SPM 3–5 мин (загрузка каждый раз) 5 сек (кеш на диске) ~В 60 раз быстрее
Набор unit-тестов (500 тестов) 8 мин 2.5 мин В 3.2 раза быстрее
Время ожидания в очереди 30 с — 5 мин 0 сек Мгновенно

Анализ месячных расходов

Размер команды Сборок/месяц Стоимость облака CircleCI Стоимость MyRemoteMac Ежемесячная экономия
Один разработчик 100 сборок (в среднем 10 мин) $100/мес $75/мес $25/мес
Малая команда (5) 500 сборок (в среднем 10 мин) $500/мес $75/мес $425/мес
Средняя команда (15) 1500 сборок (в среднем 10 мин) $1,500/мес $179/мес (M4 Pro) $1,321/мес
Корпоративная (50+) 5000+ сборок $5,000+/мес $358/мес (2x M4 Pro) $4,642+/мес

Итог: Для любой команды, выполняющей более ~75 сборок в месяц, self-hosted Mac Mini M4 окупается мгновенно. Экономия усиливается, потому что сборки на выделенном оборудовании с тёплыми кешами быстрее, что означает, что каждая сборка потребляет меньше минут изначально.

10. Устранение типичных проблем

Раннер отключается или отображается как "Offline"

Агент раннера может потерять соединение из-за сетевых проблем или аварийного завершения процесса. LaunchDaemon должен автоматически перезапустить его, но если этого не происходит:

# Check if the process is running
ps aux | grep circleci-runner

# Check the LaunchDaemon status
sudo launchctl list | grep circleci

# View the error logs
tail -50 /opt/circleci/logs/runner-stderr.log
tail -50 /opt/circleci/logs/runner.log

# Restart the service
sudo launchctl unload /Library/LaunchDaemons/com.circleci.runner.plist
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# If the token expired, generate a new one in CircleCI dashboard
# and update /opt/circleci/config/runner-agent-config.yaml

resource_class не найден или задачи зависают в очереди

Если задачи остаются в очереди с сообщением "No matching runner found", класс ресурсов в вашей конфигурации не совпадает с панелью управления:

# Verify the exact resource class name in CircleCI dashboard:
# Organization Settings > Self-Hosted Runners > Resource Classes

# The resource_class in .circleci/config.yml must match exactly:
# resource_class: your-org/mac-runner  (case-sensitive!)

# Check your runner is online:
circleci runner instance list your-org/mac-runner

# Common mistakes:
# - Wrong namespace (org name vs personal namespace)
# - Typo in resource class name
# - Runner is offline or token is invalid
# - Using 'docker' executor instead of 'machine: true'

Ошибки подписи кода Xcode

Подпись кода на CI-машине требует тщательного управления Keychain. Процесс раннера может не иметь доступа к login keychain:

# Option 1: Use Fastlane Match (recommended)
# In your Fastfile:
match(type: "appstore", readonly: true)

# Option 2: Manual keychain management in your job steps
- run:
    name: Setup code signing
    command: |
      # Decode and import the certificate
      echo "$BUILD_CERTIFICATE_BASE64" | base64 --decode > /tmp/cert.p12

      # Create a temporary keychain
      security create-keychain -p "ci" /tmp/ci.keychain
      security set-keychain-settings -lut 21600 /tmp/ci.keychain
      security unlock-keychain -p "ci" /tmp/ci.keychain

      # Import the certificate
      security import /tmp/cert.p12 -P "$P12_PASSWORD" \
        -A -t cert -f pkcs12 -k /tmp/ci.keychain
      security list-keychain -d user -s /tmp/ci.keychain

      # Install provisioning profile
      mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
      echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode \
        > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision

Симулятор не запускается или превышает время ожидания

Симуляторы могут перейти в нерабочее состояние на долго работающих CI-машинах. Сбрасывайте их между сборками:

# Add a pre-build step to clean simulators
- run:
    name: Reset simulators
    command: |
      xcrun simctl shutdown all 2>/dev/null || true
      xcrun simctl erase all 2>/dev/null || true

# If a specific runtime is missing, reinstall it:
xcodebuild -downloadPlatform iOS

# List available simulators
xcrun simctl list devices available

Заканчивается дисковое пространство

Сборки Xcode генерируют значительные объёмы данных. Настройте автоматическую очистку на вашем раннере:

# Check available disk space
df -h /

# Clean old DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*

# Remove old simulator runtimes
xcrun simctl runtime delete all

# Clean Homebrew cache
brew cleanup --prune=7

# Remove old CircleCI working directories
find /opt/circleci/workdir -maxdepth 1 -mtime +3 -exec rm -rf {} +

# Set up a weekly cron job for automatic cleanup
(crontab -l 2>/dev/null; echo "0 4 * * 0 rm -rf ~/Library/Developer/Xcode/DerivedData/* && brew cleanup --prune=7") | crontab -

11. Часто задаваемые вопросы

Сколько стоит self-hosted раннер CircleCI на Mac Mini M4?

Выделенный Mac Mini M4 от MyRemoteMac стоит от $75/месяц с неограниченным временем сборки. Это значительно дешевле облачных macOS раннеров CircleCI, которые стоят примерно $0.06–$0.12 в минуту (около $300–500/месяц для активных команд). Self-hosted раннеры не имеют поминутной оплаты, поэтому ваши расходы предсказуемы вне зависимости от объёма сборок.

Можно ли использовать self-hosted раннер CircleCI для сборок iOS и macOS?

Да. Self-hosted раннер на Mac Mini M4 может выполнять любые задачи macOS, включая сборку iOS, сборку приложений macOS, тестирование Swift-пакетов, UI-тесты Xcode и автоматизацию с Fastlane. Поскольку он работает нативно на Apple Silicon, сборки быстрее, чем на эмулированных или Intel-раннерах в облаке.

В чём разница между machine runner и container runner в CircleCI?

CircleCI machine runner выполняет задачи непосредственно в операционной системе хост-машины, что необходимо для сборок macOS/iOS, требующих Xcode, симуляторов и фреймворков Apple. Container runner выполняет задачи внутри контейнеров Docker и доступен только на Linux. Для сборок Mac необходимо использовать machine runner.

Как обновлять self-hosted раннер CircleCI?

Агент machine runner CircleCI поддерживает автоматические обновления по умолчанию. Вы также можете обновить вручную, скачав последний релиз с CircleCI и заменив бинарный файл. Рекомендуется проверять обновления ежемесячно и поддерживать Xcode и macOS в актуальном состоянии.

Быстрее ли self-hosted раннер, чем облачный macOS CircleCI?

Да, обычно в 2–3 раза быстрее для чистых сборок и до 9 раз быстрее для инкрементальных сборок. Облачные macOS раннеры CircleCI используют общее оборудование Intel или M1 с эфемерными окружениями, что означает, что каждая сборка начинается с холодного кеша. Self-hosted Mac Mini M4 обладает выделенной производительностью Apple Silicon, постоянными кешами DerivedData и SPM, а также отсутствием очередей ожидания.

Можно ли запускать несколько задач CircleCI одновременно на одном Mac Mini?

Да. Mac Mini M4 имеет 10-ядерный CPU и 16 ГБ или более оперативной памяти, что позволяет комфортно выполнять 2–3 параллельных задачи сборки. Для более тяжёлых нагрузок Mac Mini M4 Pro с 14 ядрами и 24 ГБ RAM справляется с 4–6 параллельными задачами. Вы можете настроить максимальное количество параллельных задач в конфигурационном файле раннера.

Начните с Mac Mini M4 для ваших пайплайнов CircleCI

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