Управлять небольшим портфолио iOS-приложений через App Store Connect — задача обманчиво трудоёмкая. Каждый релизный цикл — это ротация сертификатов, перегенерация provisioning-профилей, обновление метаданных для четырёх локалей, компоновка скриншотов с рамками устройств, подключение TestFlight-сборок, отправка на ревью и управление поэтапной раскаткой. Умножь это на три приложения — и ручная работа начинает пожирать дни. Я создал mobile-stores-cicd, чтобы свести всё это к единому CLI-тулкиту, который напрямую работает с App Store Connect API, рендерит метаданные из Jinja2-шаблонов, автоматизирует захват скриншотов на Simulator и закрывает всё — от ротации сертификатов до управления поэтапным релизом.
В этой статье я разбираю полную архитектуру: 37 CLI-команд, организованных вокруг управления сертификатами, шаблонизации метаданных, pipeline скриншотов, автоматизации релизов, self-hosted системы сайдлоада IPA через Cloudflare R2 с доставкой в Telegram, синхронизации переводов через Crowdin и опционального Streamlit-дашборда. Тулкит заточен под macOS (требуется Keychain и инструменты Xcode), написан на Python и построен на модели «безопасность прежде всего»: каждая деструктивная операция поддерживает флаг --dry-run. Репозиторий содержит более 13 000 слов документации в 21 файле — настройка, рабочие процессы, диагностика проблем и обеспечение безопасности.
- Зачем ещё один CI/CD-инструмент для мобильных сторов
- Архитектура и принципы проектирования
- Управление сертификатами и provisioning-профилями
- Работа с метаданными: шаблоны, рендеринг и push
- Pipeline скриншотов: от Simulator до App Store
- Автоматизация релизов: от черновика до поэтапной раскатки
- Pipeline сайдлоада: переподпись и инжекция твиков
- Синхронизация переводов через Crowdin
- Опциональный Streamlit-дашборд
- Модель безопасности и философия dry-run
- Тестирование и непрерывная интеграция
- Справочник CLI-команд
- Начало работы
- Текущий охват и планы
- FAQ
- Заключение
Зачем ещё один CI/CD-инструмент для мобильных сторов
Fastlane покрывает многое, но он написан на Ruby, навязывает свою структуру проекта и перегружен годами накопленной сложности ради сценариев, которые к портфолио из трёх приложений не имеют отношения. Мне нужно было что-то более узкое и более глубокое: Python-тулкит, который напрямую оборачивает REST API App Store Connect, поддерживает конкретную модель метаданных моих приложений (Jinja2-шаблоны с YAML-значениями под каждую локаль), автоматизирует компоновку скриншотов с рамками устройств и локализованными заголовками и интегрируется с Cloudflare R2 для self-hosted pipeline сайдлоада.
На данный момент тулкит автоматизирует полный iOS-цикл для трёх продакшн-приложений (два инстанса RocketChat и Flutter-приложение VaultApprover) на четырёх локалях (en-US, ru, ar-SA, zh-Hans). Инфраструктура для Google Play заложена — аутентификация через сервисный аккаунт описана в android/SERVICE_ACCOUNT_SETUP.md, путь к креденшелам стандартизирован, а metadata/apps.yaml уже поддерживает Android-специфичные поля (package name, track, путь к keystore) — но CLI-команды пока не подключены. Текущее покрытие iOS включает:
- Инвентаризация сертификатов с SHA-1 отпечатками и отслеживанием сроков действия по трём слоям: Keychain, локальные профили и состояние на сервере ASC
- Перегенерация provisioning-профилей после ротации сертификатов с опциональным сохранением локально для подхвата Xcode
- Pull/render/push метаданных с пятиуровневым приоритетом шаблонов и fail-fast-рендерингом StrictUndefined
- Захват скриншотов на iOS Simulator через Maestro-сценарии, компоновка через Pillow в двух стилях (маркетинговый и оверлейный), валидация по правилам Apple для размеров устройств и multipart S3-загрузка в ASC
- Создание черновой версии, подключение TestFlight-сборки, отправка на App Review с предварительными проверками и управление поэтапной раскаткой
- Работа с отзывами пользователей, отчёты о продажах и операции с бета-группами TestFlight
- Self-hosted сайдлоад IPA с переподписью кода, инжекцией
.deb-твиков через cyan, публикацией OTA-манифеста и доставкой в Telegram - Синхронизация переводов через Crowdin для метаданных App Store, заголовков скриншотов и строк приложения с ремаппингом локалей
Архитектура и принципы проектирования
Тулкит — это единый Python-пакет с модульной CLI-точкой входа (ios/cli.py), которая загружает 37 подкоманд. Два shell-обёртки обеспечивают доступ: ./apple <command> для прямого вызова и ./launch для интерактивного меню через questionary. Основной клиент (ios/client.py) реализует аутентификацию в App Store Connect API с помощью ES256 JWT-токенов, сгенерированных из .p8-ключа, с автоматической пагинацией для списочных endpoint’ов.
Структура репозитория чётко разделяет зоны ответственности:
ios/ ASC-клиент, CLI, хелперы Keychain, 37 команд
ios/sideload/ Переподпись IPA, OTA-манифест, pipeline публикации в R2
ios/ui/ Опциональный Streamlit-дашборд (8 страниц)
metadata/ Каталог приложений, шаблоны, значения по приложениям, скриншоты
.rendered/ Сгенерированные файлы метаданных (в gitignore)
.cache/ Локальное состояние: настройки UI, эталонные данные ASC, состояние сайдлоада
secrets/ Креденшелы (.p8, .p12, JSON сервисного аккаунта)
tests/ 15+ файлов pytest для регрессионных тестов без сети
docs/ 6 руководств по рабочим процессам + диагностика (1 600+ слов) Каталог приложений (metadata/apps.yaml) — единственный источник истины: он связывает каждый slug приложения с его ASC app ID, bundle ID, отображаемым именем, выбранным шаблоном, целевыми локалями, опциональными категориями, метаданными репозитория и опциональным блоком Android-конфигурации. Каждая команда, работающая с приложением, резолвит его через этот каталог. Streamlit-дашборд показывает зелёный бейдж «Android» рядом с синим «iOS», когда блок android присутствует, даже до реализации Android-команд.
Окружение настраивается через 16 переменных в файле .env: три для креденшелов ASC (ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_PATH), три для Cloudflare R2 (R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY), две для Telegram (TG_BOT_TOKEN, TG_CHAT_ID), токены Crowdin и GitHub, а также несколько операционных путей. Руководство docs/SETUP.md проходит по каждой переменной с шагами верификации.
Управление сертификатами и provisioning-профилями
Ротация сертификатов Apple — одна из тех задач, которые кажутся простыми, пока у тебя не окажется три приложения с общими профилями для сертификатов разработки и дистрибуции. Тулкит предоставляет полный рабочий процесс:
./apple list-certs # SHA-1 отпечатки, типы, срок действия
./apple list-profiles --sha1 <OLD_SHA1> # профили, ссылающиеся на старый сертификат
./apple regenerate-profiles \
--old-sha1 <OLD> --new-sha1 <NEW> \
--type IOS_APP_STORE --save-local # переиздание в ASC + сохранение для Xcode
./apple cleanup --dry-run # предпросмотр удаления устаревших профилей
./apple cleanup # бэкап устаревших профилей, удаление просроченных Команда inventory даёт перекрёстную сверку трёх слоёв в режиме только для чтения: что установлено в Keychain локально, какие файлы .mobileprovision лежат в директории provisioning-профилей Xcode, и что ASC показывает на стороне сервера. Команда cleanup создаёт резервную копию устаревших локальных профилей перед их удалением и удаляет просроченные identity из Keychain — всегда с обязательным первым прогоном через --dry-run. Руководство по диагностике (docs/TROUBLESHOOTING.md) покрывает типичные сбои вроде «Xcode не видит переизданные профили» и процедуру восстановления для каждого случая.
Работа с метаданными: шаблоны, рендеринг и push
Система метаданных — это то, где тулкит расходится с Fastlane сильнее всего. Вместо плоских текстовых файлов по локалям здесь используются Jinja2-шаблоны с пятиуровневым приоритетом значений:
- Общие значения по умолчанию —
metadata/templates/_common/values/<locale>.yaml - Значения шаблона —
metadata/templates/<template>/values/<locale>.yaml - Бренд приложения —
metadata/<slug>/brand.yaml - Релиз приложения —
metadata/<slug>/release.yaml - Локальные переопределения —
metadata/<slug>/locales/<locale>.yaml
Это значит, что общий шаблонный текст (URL политики конфиденциальности, URL поддержки, шаблон копирайта) живёт в одном месте, а специфичные для приложения ключевые слова и заметки к релизу ведутся отдельно по каждому приложению. Шаблоны используют StrictUndefined, чтобы пропущенная переменная немедленно роняла рендеринг, а не молча выдавала пустой результат. Для случаев, когда Jinja2-шаблоны слишком негибки, есть механизм сырых переопределений: файлы, размещённые в metadata/<slug>/overrides/<scope>/<locale>/, отправляются в ASC как есть, минуя рендеринг.
Pipeline выглядит просто:
./apple metadata-pull --app el-hub --version latest # вытянуть текущее состояние из ASC
# отредактировать исходные YAML или Jinja2-шаблоны
./apple metadata-render --app el-hub --show # предпросмотр отрендеренного результата
./apple metadata-push --app el-hub --dry-run # diff с ASC
./apple metadata-push --app el-hub # отправить в ASC Команда push имеет встроенную защиту: она пропускает перезапись непустых значений в ASC пустым результатом рендеринга. Если шаблон случайно выдал пустой результат для обязательного поля, текущий контент в App Store останется нетронутым. Рабочий процесс метаданных покрывает две группы полей: поля app-info (название, подзаголовок, URL конфиденциальности, категории — общие для всех версий) и поля version-localization (описание, ключевые слова, заметки к релизу, промо-текст, скриншоты — привязаны к версии).
Pipeline скриншотов: от Simulator до App Store
Автоматизация скриншотов была самой трудоёмкой частью разработки и самой приятной в использовании. Pipeline покрывает захват raw-снимков на iOS Simulator, локализованную компоновку с рамками устройств и заголовками, валидацию размеров и загрузку в ASC через multipart S3-поток Apple. В результате получается полностью автоматизированный путь от исходного кода до опубликованных скриншотов в App Store по всем локалям и классам устройств.
Ассеты скриншотов каждого приложения лежат в metadata/<slug>/screenshots/ со следующей структурой директорий:
screenshots/
├── _source/ raw-снимки UI (по поддиректориям локалей и устройств)
│ ├── 01_lock_ui.png базовые английские скриншоты
│ ├── ru/ снимки для конкретной локали
│ ├── ar-SA/
│ ├── _ipad/ снимки для конкретного устройства
│ └── README.md инструкции по захвату
├── _capture.yaml контракт захвата: конфиг сборки, Simulator, локали, режимы
├── _content/ YAML заголовков по локалям
│ ├── en-US.yaml headline: "Locked until it's you"
│ ├── ru.yaml headline: "Открывается только для вас"
│ └── ar-SA.yaml headline: "تُفتح لك أنت فقط"
├── _frames/ прозрачные PNG-рамки устройств
├── _maestro/ Maestro-сценарии для автоматического захвата UI
├── screens.yaml конфиг композитора: каталог экранов, цвета, шрифты, отступы
└── en-US/APP_IPHONE_69/ финальные скомпонованные скриншоты, готовые к загрузке Захватчик (screenshots-capture) загружает настроенный Simulator, применяет языковые переопределения через аргументы запуска -AppleLanguages и -AppleLocale, устанавливает приложение, прогоняет Maestro-сценарии для навигации к каждому экрану и сохраняет raw PNG. Для быстрых итераций screenshots-snap делает снимок одного экрана, когда у тебя уже запущена сессия Simulator. Контракт _capture.yaml определяет путь к проекту, команду сборки, конфигурацию Simulator, поддерживаемые локали и режимы захвата для каждого приложения.
Вот что выдаёт pipeline. Слева — raw-снимок UI прямо из iOS Simulator. Справа — скомпонованный результат с рамкой устройства и локализованным маркетинговым заголовком, готовый к загрузке в App Store:


Движок компоновки (screenshots-generate) комбинирует raw-снимки UI с рамками устройств и локализованными заголовками в двух стилях: маркетинговый (заголовок в выделенной полосе сверху, макет телефона ниже на тёмном градиенте) и оверлейный (полноэкранный UI с заголовком на полупрозрачной полосе). Файл screens.yaml определяет каталог экранов, цвета фона, настройки типографики, отступы рамок и радиусы скругления для каждой композиции. Подстановка запасных шрифтов для арабского и китайского происходит автоматически, чтобы нелатинские заголовки корректно рендерились на macOS.
Тот же pipeline работает с несколькими локалями и классами устройств. Вот арабская версия того же экрана (обрати внимание на RTL-заголовок) и композиция для iPad Pro:


Шаг валидации (screenshots-validate) проверяет требования Apple к размерам для каждого класса устройств (включая размер APP_IPHONE_69, который ASC внутренне маршрутизирует как APP_IPHONE_67), верифицирует покрытие локалей и помечает неизвестные директории устройств или наборы, превышающие лимит в 10 скриншотов. Шаг загрузки (screenshots-push) реализует multipart S3-протокол загрузки ASC с флагом --replace для удаления существующих наборов перед загрузкой новых.
Автоматизация релизов: от черновика до поэтапной раскатки
Релизный процесс — это цепочка команд, покрывающая весь путь отправки на App Review. Руководство docs/RELEASE_WORKFLOW.md документирует рекомендуемую сквозную последовательность:
./apple apps-audit --app el-hub # проверить текущее состояние
./apple version-create --app el-hub --version 4.71.4 # создать черновую версию
./apple metadata-push --app el-hub # отправить метаданные
./apple screenshots-push --app el-hub --replace # загрузить скриншоты
./apple testflight-list --app el-hub # посмотреть доступные сборки
./apple build-attach --app el-hub --build-version 23 # подключить сборку
./apple version-submit --app el-hub --dry-run # предварительные проверки
./apple version-submit --app el-hub # отправить на ревью
./apple phased-release --app el-hub --action status # отследить раскатку Команда version-submit запускает предварительные проверки перед отправкой: убеждается, что сборка подключена, обязательные поля version-localization заполнены, whatsNew существует для версий старше 1.0, а контактные данные для App Review доступны. Флаг --force обходит эти проверки, когда ты точно знаешь, что делаешь.
Управление поэтапной раскаткой поддерживает шесть действий: status, start, pause, resume, complete и stop. Пост-релизные операции включают sales-report для выгрузки и агрегации данных о продажах ASC и reviews-list / reviews-respond для работы с отзывами пользователей. Вместе с apps-audit для проверки состояния перед отправкой это покрывает полный жизненный цикл релиза без необходимости открывать App Store Connect в браузере.
Pipeline сайдлоада: переподпись и инжекция твиков
Это фича, по которой приходит больше всего вопросов. Подсистема сайдлоада позволяет взять сторонний IPA (из GitHub-релиза, по прямой ссылке или из .deb-твика, инжектированного в расшифрованное базовое приложение), переподписать его своим собственным сертификатом Apple Developer, загрузить подписанный IPA и OTA-манифест в Cloudflare R2 и доставить ссылку на установку через Telegram с QR-кодом.
Первоначальная настройка выполняется один раз:
./apple sideload-init --email you@example.com # выпустить новый сертификат для подписи
# или: ./apple sideload-import-cert --p12 existing.p12 # использовать существующий .p12
./apple sideload-register-device # зарегистрировать iPhone (автоопределение по USB)
./apple sideload-tg-init # привязать Telegram-чат для доставки После этого публикация готового IPA — одна команда:
# Из GitHub-релиза:
./apple sideload-install --repo https://github.com/SoCuul/SCInsta --refresh-altsource
# По прямой ссылке на IPA:
./apple sideload-install --ipa-url https://example.com/app.ipa Что происходит под капотом: команда скачивает последний IPA из GitHub-релиза, считывает метаданные бандла, создаёт или переиспользует bundle ID в App Store Connect (перезаписывая его в пространство имён ae.ilia.sideload.*, когда Apple отклоняет оригинальный ID), создаёт или переиспользует provisioning-профиль для зарегистрированного устройства, переподписывает IPA через zsign, загружает подписанный IPA и сгенерированный .plist-манифест в Cloudflare R2, опционально пересобирает source.json, совместимый с AltStore/SideStore, через --refresh-altsource и отправляет URL установки и QR-код в Telegram.
Для инжекции твиков — большинство современных твик-репозиториев (моды для Instagram, моды для YouTube) распространяют .deb-пакеты, а не готовые к сайдлоаду IPA. Флаг --build-deb обрабатывает весь этот pipeline: скачивает .deb из GitHub-релиза, берёт расшифрованный базовый IPA (из локального пути или сервиса вроде armconverter.com), инжектирует твик в базовое приложение через cyan (pyzule-rw), опционально переименовывает отображаемое имя бандла через --bundle-name, а затем продолжает стандартный pipeline подписи и публикации. Руководство docs/SIDELOAD_WORKFLOW.md разбирает это подробно с пошаговыми примерами.
Управление устройствами включает sideload-register-device для добавления iPhone (с автоопределением по USB через libimobiledevice), sideload-remove-device для отвязки, sideload-remove для удаления опубликованных приложений из R2 и sideload-list для просмотра текущего состояния сертификата, устройства и опубликованных приложений.
Синхронизация переводов через Crowdin
Тулкит управляет тремя параллельными направлениями перевода через Crowdin:
- Метаданные App Store — YAML-файлы локалей в
metadata/<slug>/locales/ - Заголовки скриншотов — YAML по локалям в
metadata/<slug>/screenshots/_content/ - Строки приложения — зеркальные файлы в
metadata/<slug>/app-strings/для приложений, в которых определён блокapp_strings
GitHub Action (crowdin-sync.yml) каждую ночь в 03:00 UTC скачивает переводы и поддерживает ручной запуск для загрузки, скачивания, предзаполнения переводов (с флагами --auto-approve-imported и --import-eq-suggestions) или двусторонней синхронизации. Action открывает PR с локализацией, формирует commit-сообщения и обрабатывает удаление файлов-сирот через параметр delete_orphan_file_ids.
Ремаппинг локалей решает расхождение в именовании между Crowdin и Apple: Crowdin использует ar, а Apple ожидает ar-SA, и zh-CN маппится в zh-Hans. Workflow синхронизации применяет эти маппинги автоматически. Для строк приложения команда app-strings-sync зеркалирует файлы между репозиторием приложения, директорией метаданных и Crowdin. Флаг --direction push копирует из репозитория приложения в pipeline переводов; --direction pull возвращает переведённые строки обратно. Полный процесс настройки документирован в CROWDIN_SETUP.md в десяти разделах, охватывающих создание проекта, управление токенами, конфигурацию секретов и диагностику.
Опциональный Streamlit-дашборд
Для операторов, которым удобнее визуальный интерфейс, ./apple ui запускает восьмистраничный Streamlit-дашборд: обзор приложений, редактирование и рендеринг метаданных, прогресс переводов, компоновка и загрузка скриншотов, просмотр TestFlight-сборок, управление релизами, состояние сайдлоада и настройки. Дашборд вызывает тот же CLI-бэкенд, поэтому .env и локальное состояние остаются единственным источником истины.
Дашборд намеренно опционален. Он не устанавливается через requirements.txt — streamlit нужно установить отдельно. Это сохраняет ядро CLI лёгким для CI/CD-сред, где веб-интерфейс не имеет смысла. Браузерный smoke-тест в pipeline GitHub Actions проверяет, что дашборд стартует без ошибок при каждом push.
Модель безопасности и философия dry-run
Каждая команда, способная изменить состояние, предоставляет флаг --dry-run. Это относится к отзыву сертификатов, перегенерации профилей, push метаданных, загрузке скриншотов, подключению сборок, отправке версий, управлению поэтапной раскаткой и операциям сайдлоада. Рекомендуемый порядок работы: сначала запуск с --dry-run, просмотр вывода, затем запуск по-настоящему.
Дополнительные меры безопасности:
cleanupсоздаёт резервную копию устаревших provisioning-профилей перед удалением из директории Xcodemetadata-pushотказывается перезаписывать непустые значения в ASC пустым результатом рендеринга- Рендеринг шаблонов использует
StrictUndefined, чтобы пропущенные переменные вызывали ошибку сразу, а не молча выдавали пустоту - Секреты (
.env,.p8-ключи, JSON сервисного аккаунта, сертификаты сайдлоада) хранятся локально и добавлены в gitignore - Отрендеренные данные, резервные копии и кеш-директории тоже в gitignore
- Набор тестов (15+ файлов pytest) покрывает сценарии без сети: правила размеров устройств для скриншотов, хелперы метаданных, кеширование эталонных данных ASC, персистенция состояния синхронизации и настройки UI
- GitHub Actions запускает полный набор pytest плюс браузерный smoke-тест Streamlit при каждом push
- Руководство
docs/TROUBLESHOOTING.md(1 600+ слов) покрывает 20+ сценариев сбоев с описанием симптомов, исправления и корневой причины для каждого
Тестирование и непрерывная интеграция
Набор тестов построен вокруг чёткой границы: всё, что обращается к ASC или требует macOS-специфичных инструментов, исключено из CI. Остаётся практичный набор из 15+ тестовых файлов, покрывающих правила размеров устройств для скриншотов, хелперы рендеринга метаданных, кеширование эталонных данных ASC, персистенцию состояния синхронизации сайдлоада, хранение настроек UI и парсер каталога приложений.
Два workflow GitHub Actions запускаются при каждом push: tests.yml выполняет полный набор pytest на Python 3.11, затем поднимает Streamlit-дашборд в headless-режиме для проверки рендеринга без ошибок (smoke-тест на базе Playwright). crowdin-sync.yml обрабатывает ночное скачивание переводов и может быть запущен вручную для загрузки или предзаполнения.
Справочник CLI-команд
Полный набор команд, организованный по областям:
| Область | Команда | Назначение |
|---|---|---|
| Обзор | inventory | Read-only обзор Keychain, локальных профилей и профилей ASC |
| Обзор | apps-list | Каталог приложений с текущей версией и состоянием локалей с сервера ASC |
| Обзор | apps-audit | Полный аудит: версии, локализации, статус ревью, доступность |
| Сертификаты | list-certs | Сертификаты команды с SHA-1 отпечатками и сроком действия |
| Сертификаты | revoke-cert | Отзыв сертификата в ASC |
| Профили | list-profiles | Provisioning-профили ASC с перекрёстными ссылками на сертификаты |
| Профили | regenerate-profiles | Переиздание профилей после ротации сертификатов |
| Очистка | cleanup | Бэкап устаревших профилей, удаление просроченных identity Keychain |
| Метаданные | metadata-pull | Вытянуть текущие app-info и version-localization из ASC |
| Метаданные | metadata-render | Отрендерить Jinja2-шаблоны в .rendered/ |
| Метаданные | metadata-push | Отправить отрендеренные метаданные в ASC |
| Переводы | app-strings-sync | Зеркалирование строк приложения между репозиторием и Crowdin |
| Версии | version-create | Создать новую черновую AppStoreVersion |
| Скриншоты | screenshots-validate | Валидация структуры папок и размеров по правилам Apple |
| Скриншоты | frames-render | Генерация запасных рамок устройств через Pillow |
| Скриншоты | screenshots-generate | Компоновка локализованных скриншотов из raw UI + рамки + заголовки |
| Скриншоты | screenshots-capture | Захват через Simulator: Maestro-сценарии, переключение локалей, raw-снимки |
| Скриншоты | screenshots-snap | Снимок одного экрана для быстрых итераций |
| Скриншоты | screenshots-push | Загрузка в ASC через multipart S3-поток |
| Операции | sales-report | Выгрузка и агрегация отчётов о продажах ASC |
| Операции | reviews-list | Список отзывов пользователей со статусом ответа |
| Операции | reviews-respond | Публикация ответа разработчика на отзыв |
| Операции | testflight-list | Список TestFlight-сборок и бета-групп |
| Операции | testflight-invite | Создание тестера и приглашение в бета-группу |
| Релиз | build-attach | Подключение TestFlight-сборки к редактируемой версии |
| Релиз | version-submit | Предварительные проверки и отправка на App Review |
| Релиз | phased-release | Просмотр или управление поэтапной раскаткой |
| Сайдлоад | sideload-init | Выпуск нового сертификата для подписи сайдлоада |
| Сайдлоад | sideload-import-cert | Импорт существующего .p12-сертификата для сайдлоада |
| Сайдлоад | sideload-register-device | Регистрация iPhone для сайдлоада (автоопределение по USB) |
| Сайдлоад | sideload-remove-device | Отвязка устройства от профиля сайдлоада |
| Сайдлоад | sideload-install | Скачивание, переподпись и публикация IPA для OTA-установки |
| Сайдлоад | sideload-remove | Удаление опубликованного приложения сайдлоада из R2 |
| Сайдлоад | sideload-list | Текущее состояние сертификата, устройства и опубликованных приложений |
| Сайдлоад | sideload-tg-init | Привязка Telegram-чата для доставки ссылок на установку |
| Дашборд | ui | Запуск Streamlit-дашборда |
Начало работы
Для настройки нужны macOS, Python 3.10+ и API-ключ App Store Connect с соответствующей ролью (App Manager или Admin). Опциональные зависимости: Maestro для захвата скриншотов, zsign для переподписи сайдлоада, libimobiledevice для определения устройств по USB, Crowdin CLI для локальной синхронизации переводов и Playwright для smoke-тестов в браузере.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# настроить ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_PATH в .env
./apple apps-list --no-server # проверить парсинг каталога
./apple list-certs # проверить аутентификацию в ASC
./launch # интерактивное меню оператора Репозиторий включает подробные руководства по настройке и рабочим процессам: docs/SETUP.md покрывает сквозную первоначальную настройку с шагами верификации для каждой зависимости, ios/API_KEY_SETUP.md — создание креденшелов App Store Connect с типичными ошибками, CROWDIN_SETUP.md — конфигурацию синхронизации переводов в десяти разделах, docs/METADATA_WORKFLOW.md документирует систему приоритетов шаблонов и чек-листы для новых локалей и приложений, docs/SCREENSHOTS_WORKFLOW.md — полный pipeline «захват — компоновка — валидация — загрузка», docs/SIDELOAD_WORKFLOW.md проводит через процесс переподписи и публикации, включая инжекцию твиков, docs/RELEASE_WORKFLOW.md документирует последовательность отправки, а docs/TROUBLESHOOTING.md покрывает 20+ сценариев сбоев с процедурами восстановления.
Текущий охват и планы
iOS-часть готова к продакшну и покрывает полный жизненный цикл App Store Connect. Тулкит обслуживает три активных приложения на четырёх локалях с автоматическим рендерингом метаданных, генерацией скриншотов для классов устройств iPhone и iPad и отправкой релизов.
Автоматизация Google Play — основной пробел. Инфраструктура готова: аутентификация через сервисный аккаунт описана в android/SERVICE_ACCOUNT_SETUP.md, точка входа ./droid <cmd> запланирована, а metadata/apps.yaml поддерживает блок Android-конфигурации. Не хватает CLI-команд для инспекции треков, загрузки AAB и продвижения между треками.
Среди других запланированных дополнений: pipeline импорта скриншотов из Figma (screenshots-import-figma) для white-label форков RocketChat, где мастер-шаблон Figma использует переменные бренда для мультибрендовой генерации скриншотов, CI-workflow для линтинга и проверки типов, а также упаковка в устанавливаемый Python-дистрибутив.
FAQ
mobile-stores-cicd заменяет Fastlane? Для небольших iOS-портфолио — да. Он покрывает управление сертификатами, push метаданных, автоматизацию скриншотов, отправку релизов и поэтапную раскатку. Автоматизация Android пока не реализована, а для работы нужна macOS из-за интеграции с Keychain и Xcode.
Можно ли использовать это на CI/CD-серверах? CLI и операции с метаданными работают на любой машине с Python 3.10+ и валидными креденшелами ASC. Захват скриншотов требует macOS с Xcode и Simulator. Набор тестов запускается в GitHub Actions.
Для чего нужен pipeline сайдлоада? Переподпись сторонних iOS-приложений собственным сертификатом Apple Developer, загрузка в Cloudflare R2 и установка через OTA-манифест на зарегистрированный iPhone. Также поддерживается инжекция .deb-твиков через cyan для модифицированных приложений.
Чем шаблонизация метаданных отличается от Fastlane? Fastlane использует плоские текстовые файлы по локалям. Этот тулкит использует Jinja2-шаблоны с пятиуровневым приоритетом значений и рендерингом в режиме StrictUndefined. Общий шаблонный текст хранится в одном месте, а специфичный для приложения контент ведётся отдельно.
Легален ли pipeline сайдлоада? Переподпись приложений собственным сертификатом Apple Developer для личного использования на собственном устройстве соответствует условиям программы Apple Developer. Pipeline использует официальные API ASC для управления сертификатами и профилями.
Какие локали поддерживает pipeline скриншотов? Любые, которые поддерживает App Store Connect. Движок компоновки включает автоматическую подстановку запасных шрифтов для арабского и китайского. Текущая продакшн-конфигурация покрывает en-US, ru, ar-SA и zh-Hans.
Что произойдёт, если переменная шаблона отсутствует? Рендерер использует режим StrictUndefined, поэтому пропущенная переменная немедленно вызывает ошибку. Команда metadata-push также отказывается перезаписывать непустые значения в ASC пустым результатом рендеринга — это второй уровень защиты.
Как работает маппинг локалей Crowdin? Workflow синхронизации автоматически маппит Crowdin ar в Apple ar-SA и Crowdin zh-CN в Apple zh-Hans. Ремаппинг применяется и при загрузке, и при скачивании.
Заключение
mobile-stores-cicd начинался как скрипт для ротации сертификатов и вырос в тулкит из 37 команд, покрывающий полный жизненный цикл iOS-приложения. Система шаблонизации метаданных устраняет дублирование файлов по локалям за счёт пятиуровневого приоритета значений и fail-fast-рендеринга. Pipeline скриншотов сокращает многочасовой ручной процесс до нескольких CLI-команд, создавая маркетинговые скриншоты с рамками устройств и локализованными заголовками для iPhone и iPad с автоматической обработкой RTL и CJK-шрифтов. Подсистема сайдлоада решает по-настоящему раздражающую задачу (установка модифицированных сторонних приложений на iPhone без джейлбрейка) с помощью чистого однокомандного процесса, покрывающего инжекцию .deb-твиков, подпись кода, загрузку в R2 и доставку в Telegram.
Репозиторий содержит 21 файл документации по каждому рабочему процессу — от первоначальной настройки до диагностики проблем, с подробными руководствами по созданию API-ключей, конфигурации Crowdin и pipeline сайдлоада. Описанные здесь архитектура и паттерны рабочих процессов применимы для любой команды, управляющей небольшим портфолио iOS-приложений через App Store Connect.
Если ты сталкиваешься с аналогичными накладными расходами при работе с App Store Connect и хочешь обсудить, как этот подход может подойти твоей команде, обратись за консультацией.


