Содержание статьи
- Архитектура
- Транспорт
- Только уведомления
- Двусторонний канал
- Привязка проекта к каналу уведомлений
- 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 как хранилище, а ссылка будет приходить в уведомлении.
Привязка проекта к каналу уведомлений
Сервис обслуживает несколько приложений. Как он понимает, для какого проекта пришла команда и в какой канал слать уведомление?
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Оформи подписку на материалы по тегу
Если тебе интересна эта тема целиком, можно открыть доступ не к одному материалу, а ко всем платным статьям выбранного тега.
Вариант 3. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»
