22 KiB
Runbook внедрения soft/force update мобильного приложения
1. Назначение и границы
Документ описывает выпуск нативных версий HAN Chat через Google Play, RuStore
и App Store и последующее включение soft/force update через публичную
backend-политику.
Механика не использует EAS OTA Update. Пользователь всегда направляется в магазин, соответствующий каналу установленной сборки:
google_play;rustore;app_store.
Канал встраивается в бинарный файл через
EXPO_PUBLIC_DISTRIBUTION_STORE. Поэтому Android-сборки Google Play и RuStore
нужно собирать отдельно.
Решение принимает мобильный клиент:
current_build < minimum_build— обязательное обновление (force);minimum_build <= current_build < latest_build— мягкое обновление (soft);current_build >= latest_build— карточка не показывается.
latest_version используется только в интерфейсе. Сравнение выполняется по
целому Android versionCode или iOS buildNumber.
2. Ответственные и данные окна выпуска
Перед началом назначьте:
- ответственного за EAS Build;
- ответственного за Google Play Console;
- ответственного за RuStore Console;
- ответственного за App Store Connect, если выпускается iOS;
- оператора ВМ1, применяющего backend settings;
- владельца решения о переводе soft update в force update.
Создайте запись окна выпуска:
Маркетинговая версия:
Git commit/tag мобильного приложения:
Git commit/tag backend:
Google Play EAS build ID:
Google Play versionCode:
RuStore EAS build ID:
RuStore versionCode:
App Store EAS build ID:
App Store buildNumber:
Дата полной доступности каждого релиза:
Минимальная поддерживаемая сборка каждого канала:
Не вычисляйте пороги по порядку запуска команд. Записывайте фактические значения из завершённой EAS-сборки и подтверждайте их в консоли соответствующего магазина.
3. Важная особенность EAS remote version
В проекте используется:
{
"cli": {
"appVersionSource": "remote"
}
}
Android remote versionCode привязан к application ID ru.han.chat. Профили
google-play и rustore используют один application ID и общий счётчик.
Если текущее remote-значение равно 6, последовательная сборка обычно даст:
- первая Android-сборка —
versionCode=7; - вторая Android-сборка —
versionCode=8.
Это допустимо. Политики Google Play и RuStore независимы, поэтому в backend
следует указать latest_build=7 для одного канала и latest_build=8 для
другого, если именно такие артефакты опубликованы.
Локальный android.versionCode в app.config.ts не является источником истины
при remote version source. Источник истины для rollout — опубликованный
артефакт магазина.
4. Предварительные проверки
4.1. Мобильное приложение
На локальной машине:
cd C:\Users\MI\Documents\Assistent\HAN_chat_specification\VM4_Expo-mobile
npm run typecheck
npm test
npx eas-cli whoami
Проверьте:
versionвapp.config.tsсоответствует выпускаемой маркетинговой версии;- профиль
google-playсодержитEXPO_PUBLIC_DISTRIBUTION_STORE=google_play; - профиль
rustoreсодержитEXPO_PUBLIC_DISTRIBUTION_STORE=rustore; - профиль
app-storeсодержитEXPO_PUBLIC_DISTRIBUTION_STORE=app_store; previewиdevelopmentне включают store policy;- production API указывает на
https://chat.han0107.ru.
4.2. Backend
На локальной машине:
cd C:\Users\MI\Documents\Assistent\HAN_chat_specification\VM1_app\codebase\backend\api-backend
python -m pytest
cd ..
python -m pytest tests
Проверьте, что backend-релиз содержит:
- строгий объект
mobile_updateв/api/v1/public/app-config; - поддержку
ETagиIf-None-Match; - TTL app-config 60 секунд;
- валидацию build numbers и store URL;
- актуальный
openapi.yaml; - nginx
proxy_cache_valid 200 60sдля app-config.
5. Безопасное внедрение backend до выпуска приложения
Сначала разверните backend-код и nginx, затем мобильные сборки. Старые клиенты
игнорируют новую секцию mobile_update.
До публикации новой версии политика должна быть безопасной:
- либо канал отключён;
- либо
latest_buildне превышает уже опубликованный build; minimum_buildне должен внезапно исключать поддерживаемые версии.
Начальные latest_build=2 при фактической установленной сборке 6 не показывают
карточку и поэтому безопасны как временное no-op состояние.
Развёртывание backend выполняйте по основному production runbook:
deployment/RUNBOOK.production.ru.md.
После обновления образов и конфигурации оператор ВМ1 выполняет штатный job:
/usr/local/sbin/han-vm1-compose --profile ops run --rm seed-settings
Команда идемпотентна и включает validate_settings. Невалидная комбинация
порогов или URL должна завершить job ошибкой.
Проверка публичного контракта с внешней машины:
curl -fsS https://chat.han0107.ru/api/v1/public/app-config
ETAG="$(curl -fsSI https://chat.han0107.ru/api/v1/public/app-config \
| awk -F': ' 'tolower($1)=="etag" {gsub("\r","",$2); print $2}')"
curl -sS -o /dev/null -w '%{http_code}\n' \
-H "If-None-Match: $ETAG" \
https://chat.han0107.ru/api/v1/public/app-config
Ожидается:
- первый запрос —
200; - в ответе присутствуют три store policy;
- повторный запрос с актуальным ETag —
304; Cache-Controlсодержитmax-age=60.
6. Получение текущего remote build number
На локальной машине:
cd C:\Users\MI\Documents\Assistent\HAN_chat_specification\VM4_Expo-mobile
npx eas-cli build:version:get --platform android --profile google-play
npx eas-cli build:version:get --platform android --profile rustore
npx eas-cli build:version:get --platform ios --profile app-store
Для автоматической обработки:
npx eas-cli build:version:get --platform android --profile google-play --json
Одинаковое значение для двух Android-профилей до сборки ожидаемо: они используют
общий application ID. Каждая последующая Android-сборка с autoIncrement
увеличивает общий счётчик.
6.1. Тестовый APK с каналом RuStore
Профиль preview-rustore создаёт внутренний APK, обращается к
https://dev-chat.han0107.ru и встраивает канал rustore:
npx eas-cli build:version:get --platform android --profile preview-rustore
npx eas-cli build --profile preview-rustore --platform android
У профиля задано autoIncrement: false, поэтому тестовая сборка не расходует
следующий production versionCode. Пусть фактический build APK равен B. Для
проверки на dev-backend задайте:
soft: latest_build = B + 1, minimum_build <= B
force: latest_build = B + 1, minimum_build = B + 1
none: latest_build <= B
Меняйте только RuStore policy dev-окружения и применяйте её через штатный
seed-settings этого окружения. Не используйте тестовые пороги на production.
После отказа от soft update запись сохраняется в SecureStore без TTL. Для
повторной проверки той же политики очистите данные приложения либо увеличьте
latest_build.
APK использует package ru.han.chat и может заменить установленную
store-сборку. Для теста предпочтительно отдельное устройство.
7. Создание store-сборок
Рекомендуется собирать и публиковать магазины по одному, сразу записывая фактический build number:
npx eas-cli build --profile google-play --platform android
npx eas-cli build --profile rustore --platform android
npx eas-cli build --profile app-store --platform ios
App Store-команду не выполняйте, пока не настроены Apple credentials, Apple App
ID и рабочий store_url.
После каждой сборки:
npx eas-cli build:list --platform android --limit 5
npx eas-cli build:view <BUILD_ID>
Зафиксируйте:
- EAS build ID;
- commit;
- channel/profile;
version;- фактический
versionCode/buildNumber; - checksum скачанного артефакта, если он используется в процедуре публикации.
Не запускайте вторую Android-сборку, пока не записан номер первой.
8. Публикация и проверка магазинов
8.1. Google Play
- Загрузите AAB в требуемый track.
- Убедитесь, что Console показывает ожидаемый
versionCode. - Проведите internal/closed testing.
- Проверьте установку и переход по ссылке:
https://play.google.com/store/apps/details?id=ru.han.chat. - Зафиксируйте процент rollout и время полной доступности.
8.2. RuStore
- Загрузите предназначенный для RuStore артефакт.
- Убедитесь, что Console показывает фактический
versionCode. - Проведите тестирование канала.
- Проверьте страницу:
https://www.rustore.ru/catalog/app/ru.han.chat. - Зафиксируйте статус модерации и время доступности.
8.3. App Store
До включения политики:
- получите Apple App ID;
- опубликуйте и проверьте сборку в App Store Connect/TestFlight;
- укажите канонический URL
https://apps.apple.com/.../id<APPLE_ID>; - подтвердите фактический
buildNumber; - только после этого установите
enabled: true.
9. Включение soft update
Изменяйте
deployment/app-settings.production-like.yaml отдельно для каждого магазина.
Пример, если Google Play опубликовал build 7, а RuStore — build 8:
mobile_update.google_play.enabled: {type: boolean, value: true, public: true}
mobile_update.google_play.latest_build: {type: integer, value: 7, public: true}
mobile_update.google_play.minimum_build: {type: integer, value: 1, public: true}
mobile_update.google_play.latest_version: {type: string, value: "1.0.1", public: true}
mobile_update.rustore.enabled: {type: boolean, value: true, public: true}
mobile_update.rustore.latest_build: {type: integer, value: 8, public: true}
mobile_update.rustore.minimum_build: {type: integer, value: 1, public: true}
mobile_update.rustore.latest_version: {type: string, value: "1.0.1", public: true}
Выбор minimum_build требует отдельного решения:
- оставить
1— все более старые builds получают soft update; - установить
6— builds1–5немедленно получают force update, а build6получает soft update; - установить новый build (
7или8) — все предыдущие builds получают force.
Для первого rollout рекомендуется сохранить прежний минимальный поддерживаемый build и включить только soft update.
После review и merge настроек оператор ВМ1 выполняет:
/usr/local/sbin/han-vm1-compose --profile ops run --rm seed-settings
Подождите до 60 секунд и повторно запросите app-config. Если внешний nginx уже имел закешированный ответ, допускайте до двух минут на проверку с разных клиентов, но не продолжайте rollout при значении старше ожидаемого.
10. Приёмка soft update
Используйте реальное устройство со старой store-сборкой каждого канала.
Проверьте:
- При cold start появляется «Доступно обновление».
- Указаны правильные текущая и новая версии.
- Кнопка открывает правильный магазин, а не другой Android-магазин.
- «Позже», крестик и Android Back закрывают карточку.
- После отказа карточка той же
latest_buildне появляется при следующем запуске: отказ хранится в SecureStore без TTL. - После увеличения
latest_buildпоявляется новая карточка. - После установки нового build карточка исчезает.
- При недоступном backend приложение не блокируется.
Если продукту требуется повторное напоминание через интервал, текущую механику
следует изменить отдельно: сейчас soft-dismiss действует до появления нового
latest_build, очистки данных или переустановки.
11. Перевод в force update
Force разрешено включать только когда обязательный build:
- прошёл модерацию;
- доступен в нужном production track;
- доступен всем пользователям, которых затронет
minimum_build; - устанавливается и запускается;
- корректно открывается по
store_url; - backend и store не находятся в инциденте.
Для Google Play build 7:
mobile_update.google_play.latest_build: {type: integer, value: 7, public: true}
mobile_update.google_play.minimum_build: {type: integer, value: 7, public: true}
Для RuStore build 8:
mobile_update.rustore.latest_build: {type: integer, value: 8, public: true}
mobile_update.rustore.minimum_build: {type: integer, value: 8, public: true}
Не повышайте minimum одного магазина только потому, что релиз доступен в другом.
После изменения снова примените seed-settings, проверьте app-config и
протестируйте старую сборку:
- force-карточка не имеет крестика и кнопки «Позже»;
- Android Back не закрывает её;
- кнопка открывает правильный магазин;
- после возврата без установки карточка остаётся;
- после установки поддерживаемого build блокировка исчезает.
12. App Store policy
Пока Apple App ID неизвестен, политика должна оставаться полностью выключенной:
mobile_update.app_store.enabled: {type: boolean, value: false, public: true}
mobile_update.app_store.latest_build: {type: integer, value: null, public: true}
mobile_update.app_store.minimum_build: {type: integer, value: null, public: true}
mobile_update.app_store.latest_version: {type: string, value: "", public: true}
mobile_update.app_store.store_url: {type: string, value: "", public: true}
mobile_update.app_store.release_notes: {type: string, value: "", public: true}
Отключённая политика не должна содержать частично заполненные release-поля: backend отклонит такую конфигурацию.
13. Откат
13.1. Немедленно снять force
Понизьте minimum_build до последнего подтверждённого поддерживаемого значения,
не меняя latest_build, затем примените seed.
Пример:
mobile_update.google_play.latest_build: {type: integer, value: 7, public: true}
mobile_update.google_play.minimum_build: {type: integer, value: 1, public: true}
После успешного получения новой политики клиент снимет force-блокировку. Кратковременная сетевая ошибка сохраняет уже показанный force до следующей успешной проверки, поэтому дополнительно подтвердите доступность app-config.
13.2. Полностью отключить канал
Установите enabled=false, integer-поля в null, строковые release-поля в
пустую строку:
mobile_update.google_play.enabled: {type: boolean, value: false, public: true}
mobile_update.google_play.latest_build: {type: integer, value: null, public: true}
mobile_update.google_play.minimum_build: {type: integer, value: null, public: true}
mobile_update.google_play.latest_version: {type: string, value: "", public: true}
mobile_update.google_play.store_url: {type: string, value: "", public: true}
mobile_update.google_play.release_notes: {type: string, value: "", public: true}
Не откатывайте уже использованный store build number и не публикуйте другой
артефакт с тем же versionCode/buildNumber.
13.3. Дефект новой версии
Если новая версия дефектна:
- не направляйте на неё новых пользователей — отключите policy или верните
latest_buildк безопасному опубликованному build; - остановите rollout в соответствующем магазине;
- выпустите исправленную сборку с новым build number;
- после публикации укажите новый
latest_build; - только после приёмки принимайте решение о новом
minimum_build.
14. Наблюдение после включения
В течение окна наблюдения контролируйте:
5xxи latency/api/v1/public/app-config;- долю
200/304; - ошибки rate limit публичного endpoint;
- доступность страниц магазинов;
- crash/error rate новой мобильной версии;
- обращения о циклической force-карточке;
- соответствие фактического store build политике каждого канала.
Stop conditions:
- URL ведёт не в тот магазин или не на HAN Chat;
- опубликованный build ниже
latest_build; - часть rollout-групп не может скачать minimum build;
- app-config отдаёт старую или частичную политику дольше двух минут;
- новая версия не запускается или не проходит авторизацию;
- force нельзя снять успешным изменением backend policy.
15. Контрольный чек-лист
- Backend с
mobile_updateразвёрнут до мобильного rollout. - ETag/304 и TTL 60 секунд проверены извне.
- Фактические build numbers записаны после каждой EAS-сборки.
- Build numbers подтверждены в консолях магазинов.
- Google Play и RuStore thresholds заполнены независимо.
- Soft update проверен на старой сборке каждого канала.
- Soft-dismiss и повторный показ для нового latest build проверены.
- Force включается только после полной доступности minimum build.
- App Store остаётся disabled до получения Apple App ID.
- Процедура отката проверена до включения force.
- Итоговые значения политики и время применения записаны в журнал выпуска.