Files
han-app/VM1_app/codebase/backend/deployment/RUNBOOK.mobile-updates.ru.md
T

22 KiB
Raw Blame History

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, последовательная сборка обычно даст:

  1. первая Android-сборка — versionCode=7;
  2. вторая 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

  1. Загрузите AAB в требуемый track.
  2. Убедитесь, что Console показывает ожидаемый versionCode.
  3. Проведите internal/closed testing.
  4. Проверьте установку и переход по ссылке: https://play.google.com/store/apps/details?id=ru.han.chat.
  5. Зафиксируйте процент rollout и время полной доступности.

8.2. RuStore

  1. Загрузите предназначенный для RuStore артефакт.
  2. Убедитесь, что Console показывает фактический versionCode.
  3. Проведите тестирование канала.
  4. Проверьте страницу: https://www.rustore.ru/catalog/app/ru.han.chat.
  5. Зафиксируйте статус модерации и время доступности.

8.3. App Store

До включения политики:

  1. получите Apple App ID;
  2. опубликуйте и проверьте сборку в App Store Connect/TestFlight;
  3. укажите канонический URL https://apps.apple.com/.../id<APPLE_ID>;
  4. подтвердите фактический buildNumber;
  5. только после этого установите 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 — builds 15 немедленно получают force update, а build 6 получает 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-сборкой каждого канала.

Проверьте:

  1. При cold start появляется «Доступно обновление».
  2. Указаны правильные текущая и новая версии.
  3. Кнопка открывает правильный магазин, а не другой Android-магазин.
  4. «Позже», крестик и Android Back закрывают карточку.
  5. После отказа карточка той же latest_build не появляется при следующем запуске: отказ хранится в SecureStore без TTL.
  6. После увеличения latest_build появляется новая карточка.
  7. После установки нового build карточка исчезает.
  8. При недоступном 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. Дефект новой версии

Если новая версия дефектна:

  1. не направляйте на неё новых пользователей — отключите policy или верните latest_build к безопасному опубликованному build;
  2. остановите rollout в соответствующем магазине;
  3. выпустите исправленную сборку с новым build number;
  4. после публикации укажите новый latest_build;
  5. только после приёмки принимайте решение о новом 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.
  • Итоговые значения политики и время применения записаны в журнал выпуска.