Содержание статьи
- Архитектура
- Транспорт
- Только уведомления
- Двусторонний канал
- Привязка проекта к каналу уведомлений
- Telegram: команды, polling, кнопки
- Многопоточность
- Сборка
- Сборка на хосте
- Облачные CI/CD
- Где хостить бот
- Статус треков Google Play
- Promote: из беты в продакшн
- Уведомления о публикации: только почта
- Синхронизация почты
- Цикл обработки писем
- Извлечение package name из HTML-письма
- Поиск нужного канала и отправка уведомления
- Почтовый ящик для мониторинга
- Безопасность
- Кто может отправлять команды
- Секреты не в коде
- Минимальные права Service Account
- Логи
- Что еще можно докрутить
- Постепенный rollout
- Очередь сборок
- Плановые сборки
- Webhook вместо polling
- Интеграция с Firebase Crashlytics
- Выводы
Я собрал CI/CD-сервер для Android-приложений на голом Bash. Он запускает сборки через облачный CI или Gradle напрямую, публикует результат в Google Play, мониторит уведомления о публикации через IMAP. В качестве интерфейса управления я взял Telegram, но на его месте мог бы быть Slack, Discord, ntfy, email или обычный SSH — транспорт здесь сменная деталь.
Возможно, ты скажешь, что делать все на Bash странно. Но ведь это как раз язык для склейки инструментов: файловый ввод‑вывод, работа с переменными окружения, HTTP-запросы через curl, изменение JSON через jq. Получается, что мы просто применим классический «путь Unix» к веб-API, а не только к локальным программам. И заодно обойдемся без докеров и кубернетесов.
А главное, изменить можно будет что угодно и в любой момент без страданий с деплоем. Теперь идея кажется более здравой? Давай опробуем ее на примере, и станет понятно, применимо ли это в жизни.
Для начала посмотрим, как будет работать полный цикл публикации. Чтобы его запустить, просто нажимаешь «Publish Beta» — и через несколько минут смотришь, что появилось в чате.

Бот скачал AAB с Codemagic, загрузил его в Google Play, зафиксировал edit-транзакцию — и потом, когда Google завершил модерацию, почтовый монитор поймал письмо и прислал финальное уведомление. Все без участия человека.
А теперь расскажу, как я это построил. Если захочешь повторить, вот, что тебе понадобится:
- токен Telegram-бота (запроси у @BotFather);
- токен Codemagic API или локальный Android SDK с Gradle;
- Google Play Service Account JSON с правами Release Manager;
- почтовый ящик с доступом по IMAP (Gmail подойдет);
- Docker или любой Linux-хост;
- зависимости: curl, jq, openssl, mbsync;
Архитектура
Система состоит из двух независимых процессов в одном Docker-контейнере.
Оркестратор сборок (publish_bot.) — принимает команды от оператора, запускает сборку через CI API или локальный Gradle, отдает артефакт, публикует в Google Play, отвечает на запросы статуса. Обслуживает несколько проектов одновременно — у каждого свой воркер‑процесс.
Монитор почты (imap_to_telegram.) — каждые 30 секунд читает входящие по IMAP, ищет письма от Google Play Console, пересылает уведомления в нужный канал.
Между ними — файл channel_ids.: маппинг «package name → идентификатор канала». Оба процесса читают его независимо.

Транспорт — верхний блок (команды) и нижний (уведомления) — намеренно отделен от логики. Хочешь переехать со Slack на Telegram или добавить параллельную отправку в ntfy — правишь три функции, не затрагивая ядро.
Каждый остальной блок тоже заменяем независимо: поменять CI-сервис — одна функция, добавить трек публикации — один case в switch. Архитектура намеренно плоская: никаких абстракций, просто функции.
Деплой — одна команда:
docker run -d \ --name cicd-server \ --restart unless-stopped \ -e IMAP_PASSWORD='пароль' \ cicd-server
Скрипт start. запускает оба процесса:
./publish_bot.sh &
./imap_to_telegram.sh &
waitДальше разберем конкретные задачи по порядку.
Транспорт
Прежде чем погружаться в детали — важный архитектурный момент. В системе есть ровно три точки взаимодействия с оператором:
- Входящие команды — откуда сервис получает задание (запустить сборку, показать статус).
- Исходящие уведомления — куда сообщать о результате.
- Доставка артефактов — куда отдавать собранный APK/AAB.
Все это реализовано в трех‑четырех функциях. Поменять транспорт — значит переписать эти функции.
Только уведомления
Если интерактив не нужен — варианты простые.
ntfy.sh — минималистичный вариант. Публичный сервис, бесплатный, с мобильным приложением. Отправить уведомление:
curl -d "✅ Сборка ${PACKAGE} завершена" \ "https://ntfy.sh/my-secret-build-topic"Топик — просто строка в URL. Подписываешься в приложении — получаешь push. Никакой регистрации, никаких токенов. Можно поднять собственный ntfy-сервер для приватности.
Slack/Discord webhooks — если команда уже использует его. Нам достаточно сделать один POST-запрос с JSON:
curl -X POST "$SLACK_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "{"text": "✅ Сборка *${PACKAGE}* завершена"}"У Discord поле называется content вместо text, URL другой, но паттерн тот же.
Email через msmtp — работает совсем без зависимости от сторонних сервисов:
echo -e "Subject: Сборка готова\n\n${PACKAGE} собрался успешно" \ | msmtp recipient@example.com
Двусторонний канал
Если нужны и команды, и уведомления, то выбор следующий.
Telegram — самый удобный вариант для этой задачи: бесплатный API, long polling без публичного IP, файлы до 50 Мбайт можно слать прямо в чат, кнопки делаются одним JSON. Подробно разберем ниже.
Slack slash-команды — рабочий вариант для командной разработки. Slash-команда отправляет POST на твой endpoint — значит нужен публичный URL, в отличие от Telegram polling. Ответы принимаем через webhook.
Matrix/Element — открытый протокол с REST API, поддерживает polling (/), можно захостить сервер у себя. Схема та же что у Telegram: loop, затем curl /, обработка события, и следом curl /.
SSH — для личного использования экзотично, но зато зависимости нулевые:
ssh user@cicd-host "./publish_bot.sh build_release com.example.app"cron — подойдет, если интерактив вообще не нужен. Если у тебя ночные сборки и еженедельные бета‑релизы, то можно вообще всё автоматизировать и обойтись без мессенджеров.
Что действительно сложно заменить — это отправку файлов. Telegram принимает APK прямо в чат, файл скачивается одним тапом. У Slack лимит на загрузку в бесплатном плане, у ntfy файлов нет. Альтернатива: S3 или Cloudflare R2 как хранилище, а ссылка будет приходить в уведомлении.
Привязка проекта к каналу уведомлений
Сервис обслуживает несколько приложений. Как он понимает, для какого проекта пришла команда и в какой канал слать уведомление?
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
