MediaMagic · Slipstone · отчёт сессии · 7 сентября 2026
iPhone-этапы Slipstone переезжают с GitHub на Mac владельца
Что я сделал, как именно, что получилось, что пока нет, и почему. Написано для человека, который не программирует. Технические подробности спрятаны в раскрывающиеся блоки «Подробности», их можно не открывать.
Коротко
Получилось
- Конвейер теперь умеет отдавать «сборку и проверку игры на iPhone» и «сборку подписанного архива для App Store» вашему Mac, а не GitHub. Mac сам приходит за работой каждые две минуты. Снаружи на Mac никто не заходит.
- Всё это установлено на сервере и проверено вживую: сервер выдаёт заказ, принимает «я живой», принимает файлы, принимает «готово» и «ошибка». Проверка шла настоящим сетевым путём через внутреннюю сеть, а не «на бумаге».
- 241 автоматический тест зелёный. Код прошёл два круга придирчивой проверки независимыми агентами, все найденные проблемы исправлены.
- Для Slipstone уже создан первый заказ, он ждёт, когда на Mac запустят агента. Для этого нужно выполнить три команды из инструкции
ship-game/mac/README.md; на этой публичной странице их нет, потому что в них адрес сервера, они переданы владельцу напрямую.
Пока нет
- Игра ещё не опубликована ни в Google Play, ни в App Store. Это не было задачей этой сессии, но я разобрался, что именно мешает, и это описано ниже честно и по пунктам.
- Mac пока не подключён: агент на нём ещё не установлен, поэтому заказ лежит в очереди.
- Публикация упирается в вещи, которые может сделать только владелец: войти в магазины, создать записи приложений, получить доступы к API магазинов и два HTTP-прокси.
1. В чём была проблема
Чтобы выпустить игру на iPhone, её нужно собрать и проверить на компьютере Apple. До сих пор конвейер заказывал это у GitHub: «дай мне на час виртуальный Mac». С 6 сентября GitHub отказывает: аккаунту нужно оплатить услугу. Конвейер честно пытался каждый час, двадцать раз подряд, и каждый раз получал отказ. Этап «Собрать iPhone-приложение и проверить его в симуляторе» (в конвейере он называется A1) стоял заблокированным.
У владельца есть свой Mac в той же внутренней сети (Tailscale). Но на нём выключен удалённый вход, и выключать его нельзя обратно: так безопаснее. Значит, сервер не может «прийти» на Mac и что-то запустить. Нужна была схема наоборот: Mac сам периодически спрашивает сервер «есть работа?», забирает её, делает и возвращает результат. Это называется pull-модель.
Подробности: почему именно pull, и что уже было в конвейере
В конвейере уже существовали «рабочие заказы» для Android: маленькие файлы-задания, которые главный планировщик кладёт в папку игры, а отдельный рабочий процесс на сервере их забирает, выполняет (собирает APK, гоняет тесты на Android-эмуляторе) и записывает результат. Планировщик потом читает результат и двигает этап дальше. Я расширил именно этот механизм: добавил два новых вида заказов для iPhone и «дверь» на сервере, через которую Mac может эти заказы забирать по сети. Файлы этого механизма существовали только в незакрытом PR #74 другой сессии, поэтому моя ветка построена поверх него.
2. Что я сделал, по шагам
2.1. Два новых вида заказов и запрет их путать
Появились заказы «прогнать игру на iPhone» (для этапа A1) и «собрать подписанный архив» (для этапа A1_5). Рабочий процесс на сервере, который умеет только Android, теперь физически не может взять iPhone-заказ, даже случайно. И наоборот: сервер выдаёт Mac только iPhone-заказы, что бы Mac ни просил.
Подробности
- Виды заказов:
ios-gameplayиios-archive. Linux-рабочий фильтрует по спискуSHIP_GAME_WORK_KINDS, по умолчанию толькоdevice-qaиandroid-release. - Заказ, который планировщик уже «списал» (например, счёл брошенным), больше нельзя взять повторно.
- Запись результата, «я живой» и «ошибка» стали проверять состояние заказа на диске в момент записи: поздний ответ от Mac не может оживить заказ, который планировщик уже закрыл. Взятие заказа тоже перепроверяется, чтобы две стороны не перезаписали друг друга.
2.2. «Дверь» для Mac на сервере
На сервере появились пять адресов, к которым может обращаться только Mac с секретным ключом (токеном) и только из внутренней сети: «дай заказ», «я ещё работаю», «вот файл результата», «готово», «не получилось». Файлы принимаются потоком, без загрузки в память, с ограничением размера, а имена файлов проверяются, чтобы нельзя было записать что-то мимо нужной папки.
Подробности
- Маршруты под
/api/internal/work/:POST claim,POST :id/heartbeat,PUT :id/artifacts/:name,POST :id/complete,POST :id/fail. - Токен отдельный от всех остальных, лежит на сервере в файле с правами «только root» и подгружается службой через
EnvironmentFile. Сравнение токена идёт в постоянном времени (защита от подбора по таймингам). Запрос обязан прийти на внутренний домен Tailscale, иначе 403. - Файлы результата попадают в
evidence/mac-<этап>-<номер заказа>/внутри папки игры. Имена: один безопасный сегмент, без слэшей и без.tmp. Лимит 512 МиБ на файл и 64 файла на заказ. Временный файл создаётся с уникальным именем и в эксклюзивном режиме, чтобы две одновременные загрузки одного имени не перемешались, и никогда не переписывает символические ссылки. - Сервер разрешает длинные загрузки (до двух часов на запрос), потому что видеозапись симулятора может быть большой. Ревью нашло, что стандартный пятиминутный лимит Node.js делал бы такую загрузку невозможной.
- Ответ «готово» записывает список реально лежащих на диске файлов с их контрольными суммами, а вердикт читает с диска, а не из того, что прислал клиент.
2.3. Планировщик: когда и как использовать Mac
В карточке игры (манифесте) появился переключатель: если написано «исполнитель: trusted-mac», планировщик вместо обращения к GitHub кладёт заказ для Mac. Когда Mac возвращает результат, планировщик проверяет его так же строго, как проверял результат GitHub: тесты прошли, собран ровно тот commit исходников, который просили, вердикт скопирован в папку доказательств. Подписанный архив (IPA) дополнительно распаковывается на сервере и проверяется: тот ли идентификатор приложения, та ли версия и номер сборки, совпадает ли контрольная сумма.
Подробности
- Условие:
manifest.metadata.ios.runner === 'trusted-mac'. Без него поведение с GitHub не изменилось ни на байт (это закреплено тестом). - В заказ кладётся
sourceSha(точный commit), ссылка на ветку, путь проекта, схема Xcode, bundle id, версия и билд. - Вердикт должен иметь
ok: true, ту жеsourceSha, что просил заказ, и что у исходников сейчас, и совпадающие идентификаторы. Все PNG из папки результата становятся доказательствами этапа A1; вердикт копируется вevidence/ios-qa/. - IPA перепроверяется на Linux:
unzipнаходитPayload/*.app/Info.plist,python3с модулемplistlibчитает bundle id, версию и билд. Путь к IPA записывается как артефакт этапа A1_5, именно там его ищет этап доставки A2. - Коды блокировок с автоповтором: сбой Maestro (30 мин), сбой архива (30 мин), «результат устарел, исходники сменились» (5 мин), «файлы неверные» (30 мин), «Mac замолчал больше 2 часов» (30 мин), «заказ никто не взял 4 часа» (60 мин). Последние два показываются в Studio как «нужно ваше действие: разбудите Mac».
- Если исходники поменялись между A1 и A1_5, планировщик сам переоткрывает A1, чтобы архив и проверка игры всегда описывали один и тот же commit.
- Если на сервере вдруг нет
unzipилиpython3, готовый заказ не списывается, а перепроверяется позже, чтобы не гонять Mac зря.
2.4. Скрипты для Mac
Написаны и положены в репозиторий четыре скрипта и инструкция. Агент запускается системным планировщиком macOS (launchd) каждые две минуты, спрашивает сервер, есть ли заказ, и если есть, скачивает ровно нужный commit исходников, собирает игру, гоняет сценарии, записывает видео, загружает всё на сервер и отчитывается. Раз в 30 секунд он говорит серверу «я живой». Если агент прервать, он сначала останавливает сборку, потом быстро сообщает серверу об ошибке.
Подробности
worker.sh: единственный экземпляр (замок-папка), проверка всех полей заказа по строгим шаблонам,git fetchиcheckoutточного commit, запуск задания подcaffeinate(Mac не засыпает во время сборки) с 90-минутным сторожевым таймером, загрузка файлов черезcurl -T, heartbeat живёт до самого последнего запроса. По SIGTERM: убить дерево процессов, отправить короткое «не получилось», потом попробовать догрузить лог.ios-gameplay.shповторяет шаги GitHub-сценария:npm ci,npm run verify,pod install, Release-сборка для симулятора с боевой точки входаindex.js, симулятор iPhone SE (3-го поколения), Maestro-сценарии изqa/maestroпод запись видео, файл вердикта в точности того же формата, что писал GitHub. Патч для совместимости с новым Xcode применяется только если нужный файл существует.ios-archive.shоборачивает уже существовавший скрипт подписиmac-ios-backup.sh(подпись ключом App Store Connect, управляемая Xcode) и пишет квитанцию с контрольной суммой. На Mac ничего не отправляется в Apple, доставкой занимается сервер.install.shспрашивает токен один раз (ввод скрыт), пишет файл настроек с правами «только владелец», копирует скрипты в отдельную папку (чтобы смена commit в рабочей копии их не трогала), ставит агент launchd и сразу запускает первый опрос.- Maestro агент никогда не устанавливает сам: на этом Mac лежит ключ подписи, и качать что-либо из сети «в bash» там нельзя. Это ставится один раз руками.
2.5. Как я проверял
Сначала автотесты: их стало 241, все зелёные. Потом два круга «враждебного» ревью: независимые агенты получали задание не похвалить, а сломать код. Первый круг нашёл одну серьёзную и несколько мелких проблем, второй круг проверил исправления и убедился, что каждое покрыто тестом, который без исправления падает.
Затем живая проверка на сервере. Я выпустил два секретных ключа, установил новый код службой установки, переключил карточку Slipstone на Mac и сбросил этап A1. Планировщик создал первый заказ. Потом я сыграл роль «поддельного Mac» обычной программой curl: взял заказ, отправил «я живой», загрузил файл на 1 МиБ (контрольная сумма совпала), попробовал плохое имя файла (отказ), чужой токен (отказ), чужой адрес (отказ), чужое имя рабочего (отказ), и закрыл заказ. Важно: оба репетиционных заказа закрыты как честные ошибки, а не как «проверка пройдена». Ни одной поддельной зелёной галочки в истории игры нет.
Подробности: что нашло ревью и что исправлено
- серьёзно Стандартный лимит Node.js 5 минут на запрос убил бы загрузку большого видео. Исправлено: лимит 2 часа, heartbeat обновляется до и после каждой загрузки.
- Имена вида
*.tmpмогли обойти лимит числа файлов. Исправлено: запрещены. - Две одновременные загрузки одного имени писали в один временный файл. Исправлено: уникальные временные имена, эксклюзивное открытие.
- Поздний ответ Mac мог оживить заказ, который планировщик уже бросил. Исправлено: проверка состояния в момент записи, 409 в ответ.
- При смене исходников архив мог быть собран не из того commit, что проверка игры. Исправлено: A1 переоткрывается.
- В скриптах Mac: остановка по сигналу шла в неправильном порядке, петля heartbeat могла пережить родителя, не распознавался вариант установки Node через nvm, Maestro качался из сети автоматически. Всё исправлено.
- В документации: примеры с токеном в командной строке (виден другим процессам на общем сервере) заменены на чтение из файла; исправлены пути и добавлен пример правильного возврата взятого заказа.
Подробности: что именно сделано на сервере
- Созданы два файла с секретами с правами
0600: токен для Mac и токен для «живого симулятора» в Studio (второго раньше не было, из-за чего Studio получала 401). - Новый код установлен штатным скриптом установки, служба управления перезапущена, она подхватила оба файла.
- В карточке Slipstone поставлено
runner: trusted-mac, этап A1 сброшен в «ожидание». Первый же тик планировщика создал заказ для Mac. - Два репетиционных заказа: первый закрыт как «результат без вердикта» (планировщик заблокировал A1 с кодом «файлы неверные» и повтором через 30 минут), второй как «репетиция» (код сохранён в тексте блокировки). Потом A1 снова сброшен, создан чистый заказ для настоящего Mac.
- В журнал событий игры (то, что Studio показывает как «Что происходило на каждом этапе») записаны события 94–103 с объяснением каждого шага.
- Все проверки шли по настоящему внутреннему адресу через Tailscale, а не через локальный обход. Небольшая находка по пути: с самого сервера его внутреннее имя не резолвится, пришлось подсказать адрес вручную.
2.6. Документация и договорённости с соседними сессиями
Обновлены руководство по конвейеру, главный файл правил проекта и два README. Две соседние сессии (авторы PR #74 и PR #75) предупреждены, что на сервере теперь стоит мой код, чтобы они не откатили его своим деплоем. Обе подтвердили. С автором PR #75 договорились, кто владеет доказательствами для iPhone: Mac-путь даёт настоящий прогон игры, а «витринные» скриншоты остаются делом GitHub-пути. Он добавил именованные скриншоты в сценарии, я проверил, что Studio их правильно сопоставит.
Подробности: PR и коммиты
- Ветка
chief/6e37d2d5e4, PR #76 вmain, семь коммитов: заказы; защита от гонок; маршруты сервера; планировщик; скрипты Mac; документация; запуск Maestro из папки результата. - Ветка стоит поверх PR #74 (там живёт механизм заказов). После слияния #74 в моём PR останутся только мои коммиты. Порядок слияния: #74, затем #75 и #76 в любом порядке, файлы не пересекаются.
- Отдельный доклад для оператора лежит в системе Chief.
3. Что не получилось или не доделано, и почему
Mac ещё не подключён
Агент на Mac может установить только владелец: нужен доступ к самому Mac, а извне на него заходить нельзя по правилам. Поэтому заказ для этапа A1 лежит в очереди. Если его никто не берёт 4 часа, планировщик пометит «Mac офлайн» и будет переставлять заказ каждый час. Это нормальное поведение, а не поломка. Что сделать, написано в разделе 5.
Игра не опубликована, и почему это не решается одним переключателем
Я подробно разобрал оба магазина. Вот что стоит на пути, по-честному:
- Google Play. Этапы «подготовить релиз» и «отправить на проверку» были пройдены в режиме репетиции: конвейер нарочно работает с выключенными боевыми действиями (переключатель
simulate), пока владелец его не включит. До Google ничего не дошло. Кроме этого не хватает: входа в консоль Play через выделенный браузерный профиль (требует код с телефона), записи приложения Slipstone в консоли (её создание требует поставить юридические галочки, что автоматике запрещено), сервисного аккаунта для чтения статуса проверки, и HTTP-прокси для API магазина (код требует, чтобы запросы к Google шли с того же адреса, что и браузерный профиль; у прокси-провайдера есть только SOCKS5-порт, а нужен HTTP). Хорошая новость: аккаунт организационный, поэтому 14-дневное закрытое тестирование, обязательное для личных аккаунтов, не требуется. - App Store. Записи приложения в App Store Connect ещё нет, а Apple не даёт создавать её через API, только через браузер. Не опубликованы страницы поддержки и политики конфиденциальности Slipstone: ссылки из карточек магазинов сейчас ведут на общую страницу проекта на русском языке, что является поводом для отказа у обеих площадок. Сами страницы в репозитории есть, их нужно один раз выложить. Нет скриншотов для 6,9-дюймового iPhone, которые Apple требует. Две вещи Apple принимает только руками в браузере: анкету «Конфиденциальность приложения» и контакт для проверяющих. И наконец, конвейер, как написан в PR #74, не умеет сам довести App Store до конца: он создаёт «запись о релизе» только для Android, а для iOS её нет, поэтому этапы «страница в магазине» и «отправить на проверку» не могут запуститься автоматически. Есть ручной обходной путь, и я могу закрыть эту дыру кодом, если вы скажете.
- Прокси для API. Это касается обоих магазинов: без двух HTTP-прокси не работает ни отслеживание проверки в Google, ни загрузка сборки в Apple. Взять их можно только у провайдера прокси.
Открытый вопрос по Apple, решать вам
Подпись архива на Mac идёт ключом App Store Connect с адреса самого Mac. Так и было спроектировано в существующем скрипте подписи, и так я это оставил. Но один из документов проекта предупреждает: если важно, чтобы Apple-аккаунт был виден только с британского адреса, Mac не должен обращаться к Apple вообще. Это ваш выбор, пока этап A1_5 не запущен. Скажите, и я перестрою подпись.
Мелочи по ходу
- Один прогон агентов-исследователей оборвался на лимите использования сервиса. После сброса лимита я перезапустил его с того же места, повторно ничего не считалось.
- Один мой собственный тест упал из-за комментария в скрипте, который содержал запрещённую тестом строку. Переписал комментарий.
- Первый заказ для Mac привязан к commit, в котором ещё нет именованных скриншотов из PR #75. Поэтому первый прогон даст вердикт, видео и один финальный кадр, а таблица покрытия iPhone в Studio заполнится только после слияния PR и перенастройки карточки игры на основную ветку. Это записано как задача на потом в двух отчётах.
4. Что получилось, одним списком
| Пункт | Состояние |
|---|---|
| Виды заказов для iPhone, фильтр для Linux-рабочего | готово покрыто тестами |
| Пять защищённых маршрутов на сервере | готово проверено вживую |
| Планировщик: заказы вместо GitHub, строгая проверка результата, перепроверка IPA на Linux | готово покрыто тестами |
| Скрипты для Mac и инструкция | готово ждут установки владельцем |
| Автотесты | 241 из 241 |
| Установка на сервер, секреты, переключение Slipstone | сделано |
| Живая проверка круга «заказ → работа → результат» | сделано честными ошибками, без поддельных успехов |
| Первый настоящий прогон игры на Mac | ожидает нужны три команды владельца |
| Публикация в магазинах | не начата список действий в разделе 5 |
| Страницы поддержки и политики Slipstone в интернете | не выложены одна команда, раздел 5 |
5. Что дальше
Полный пошаговый список команд с указанием, где их выполнять, я передал в чате. Здесь только суть, в правильном порядке.
- На Mac. Проверить, что стоят Xcode, симулятор iPhone SE (3-го поколения), Node 22, CocoaPods, Java 17 и Maestro; положить ключ App Store Connect в папку настроек; включить «не засыпать при питании от сети». Затем три команды из инструкции: скачать репозиторий, показать токен с сервера, запустить установщик. Дальше Mac сам заберёт заказ и за 25–60 минут сделает первый прогон, потом ещё 20–40 минут на архив.
- В браузере через выделенные профили. Войти в консоль Google Play и в App Store Connect (коды с телефона), в Play создать приложение «Slipstone», сервисный аккаунт и выдать ему права.
- На сервере. Установить файл сервисного аккаунта и два HTTP-прокси в файл настроек магазинов; создать запись приложения в App Store Connect и загрузить в неё метаданные готовыми командами; выложить страницы поддержки и политики одной командой.
- В браузере. Заполнить у Apple анкету конфиденциальности («данные не собираются»), контакт для проверяющих, категорию «Игры / Головоломки», доступность только в GB, US, CA, AU, NZ; сделать скриншоты для 6,9-дюймового iPhone.
- На сервере. Включить боевой режим одной строкой, переоткрыть этапы Android для настоящего прохода, после подготовки релиза в консоли Play отдать конвейеру кнопку «отправить на проверку». Для App Store после загрузки сборки нажать «Add for Review» руками и отметить это в конвейере.
Сроки: около одного рабочего дня ваших действий, распределённых по шагам, один-два часа сборок на Mac, затем проверка Google до семи дней и проверка Apple один-два дня. Когда обе площадки покажут «опубликовано», в Studio на шаге «Публикация» появятся ссылки на магазины.
6. Цифры
| Что | Сколько |
|---|---|
| Коммитов в PR #76 | 7 |
| Автотестов в пакете конвейера | 241, все зелёные |
| Агентов в двух кругах реализации и ревью | 14 (8 + 6) |
| Агентов в исследовании пути к публикации | 8 (4 искали факты, 4 перепроверяли каждую команду) |
| Найдено и исправлено замечаний ревью | 1 серьёзное, около 20 мелких |
| Записей в журнале событий Slipstone за сессию | события 94–103 |
| Неудачных попыток GitHub до переезда | 20, по одной в час |
| Репетиционных заказов на сервере | 2, оба закрыты как честные ошибки |