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

480 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
Создайте запись окна выпуска:
```text
Маркетинговая версия:
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
В проекте используется:
```json
{
"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. Мобильное приложение
На локальной машине:
```powershell
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
На локальной машине:
```powershell
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:
```sh
/usr/local/sbin/han-vm1-compose --profile ops run --rm seed-settings
```
Команда идемпотентна и включает `validate_settings`. Невалидная комбинация
порогов или URL должна завершить job ошибкой.
Проверка публичного контракта с внешней машины:
```sh
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
На локальной машине:
```powershell
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
```
Для автоматической обработки:
```powershell
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`:
```powershell
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 задайте:
```text
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:
```powershell
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`.
После каждой сборки:
```powershell
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`:
```yaml
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 выполняет:
```sh
/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`:
```yaml
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`:
```yaml
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 неизвестен, политика должна оставаться полностью выключенной:
```yaml
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.
Пример:
```yaml
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-поля в
пустую строку:
```yaml
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.
- [ ] Итоговые значения политики и время применения записаны в журнал выпуска.