From 4709b35b13d157a6d0ed0322b1b61136149a0c10 Mon Sep 17 00:00:00 2001 From: Ryazanov Alexander <45588152+RyazanovAlexander@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:00:42 -0500 Subject: [PATCH] 1 --- RUN.md | 54 +++++++++++------ docs/live/01-first-deploy.md | 109 ++++++++++++++++++++++------------- docs/self-hosting.md | 10 ++-- 3 files changed, 112 insertions(+), 61 deletions(-) diff --git a/RUN.md b/RUN.md index b0a086d..d8d8df9 100644 --- a/RUN.md +++ b/RUN.md @@ -142,7 +142,7 @@ | [x] | T09 | Решения для поставки | я + Claude | — | T01 | | [x] | T10 | Terraform | Claude | да | T04, T09 | | [x] | T11 | CD | Claude | да | T06, T07, T08, T10 | -| [ ] | T12 | Первый деплой | я + Claude | — | T11 | +| [x] | T12 | Первый деплой | я + Claude | — | T11 | **Почему такой порядок.** Начало задал я. Сначала правила (T01), простейший сервер (T02) и сразу вся DevX-часть (T03–T12). К первой строчке продукта уже работают проверки, релиз и выкладка, и каждая следующая задача проходит через тот же конвейер. @@ -262,6 +262,7 @@ | Д12 | shellcheck скриптов composite actions в `.github/actions/` — в CI. actionlint берёт у action только объявленные входы, а скрипты не читает | T11 | Ошибка в скрипте `deployment-target`, `gcloud`, `firebase` или `docker-mirror` проявится только в прогоне после мержа. Сейчас их проверяют shellcheck руками, при правке | Со следующей правкой этих action | | Д13 | actionlint закреплён руками, 1.7.12 — последняя на 2026-10-14; Dependabot образы из `justfile` не читает. Новая версия, которая знает `queue`, снимет исключение в `.github/actionlint.yaml` | T11 | Новые проверки actionlint не приходят, а исключение может пережить свою причину | При выходе actionlint новее 1.7.12 | | Д14 | Выкладка ждёт только токен нового аккаунта (`ci-identity-ready`), но не роли, которые тот же apply ему выдаёт: запись в реестр, роль деплоя на сервисе, роли Hosting. В задании SPA Firebase CLI получает токен заново, не тот, что дождался gcloud | T12 | На свежем развёртывании или с новым аккаунтом push, выкатка или публикация могут получить 403, пока IAM не разошёлся по своим серверам. Лечит «Re-run failed jobs» | Если повтор понадобится снова: проверять права шага через `testIamPermissions`, прежде чем шаг их потребует | +| Д15 | Выкладка собирает образ релиза заново, а время сборки и время файла сервера в слое дают новый digest: повторная выкладка того же релиза — возврат кнопкой, «Re-run failed jobs» — переносит тег и добавляет ещё одну версию образа | T12, вторая выкладка | Образ прошлого релиза может раньше времени выпасть из пяти `keep-recent`, а через 30 дней его удалит `delete-old`; откат на ревизию с удалённым образом не поднимет экземпляр | До ближайшего отката с возвратом кнопкой. Проще всего — выкатывать образ, который уже называет тег релиза, если он есть; иначе собирать воспроизводимо: `SOURCE_DATE_EPOCH` и `rewrite-timestamp` вместе | --- @@ -1370,11 +1371,11 @@ - На коммите 0538208 `plan` зелёный. Это первое живое доказательство subject PR и условия пула: старой привязки уже нет. План — `31 to add`; лишний против первого прогона — бюджет, который без ID платёжного аккаунта не планировался. ID в логе нет: `***` и `(sensitive value)`. - Задание «Workflows» зелёное. «Release build» и «Terraform» автор внёс в обязательные проверки ruleset «main». - **Открыто:** - - «Workflows» — в обязательные проверки ruleset «main»: до первого прогона GitHub его не предлагал (R50); + - «Workflows» — в обязательные проверки ruleset «main»: до первого прогона GitHub его не предлагал (R50) — внесено автором 2026-10-14 (T12); - мерж — первая выкладка. «Как проверить» после мержа сверяется в T12, откат и возврат — на второй выкладке (T12); - пересборка devcontainer автором: в нём появится `firebase`; - `tee /dev/stderr` в `ci-toolchain-image` — в таблице 4 (Д6); неизменяемые релизы и правило на теги — в таблице 3 (В1); - - concurrency вызванного workflow держится на опыте сообщества, а не на документации — сверить на первых выкладках (T12). + - concurrency вызванного workflow держится на опыте сообщества, а не на документации — сверить на первых выкладках (T12) — сверено в T12: кнопка ждала, пока выкладка релиза не кончится. #### T12. Первый деплой @@ -1482,21 +1483,40 @@ - пять запросов `usage` по очереди — рецепт запускают руками, а параллельные запросы усложнили бы разбор ошибок. Исправления второго круга прошли проверки, третьего ревью нет. -- **Открыто — часть 2, после мержа:** - - шаги автора до неё: +- **Сделано (2026-10-14) — часть 2, вторая выкладка:** + - **шаги автора:** - секрет `TF_VAR_operator_email`; - - мерж — вторая выкладка; - - Rebuild Container; - - узкое разрешение в `.claude/settings.local.json` на `gh workflow run deploy.yml --ref main`, `gcloud run services update-traffic` и `firebase hosting:clone`; - - **очередь `cd`:** кнопка Deploy во время выкладки релиза должна ждать её конца. Затем она выкатывает тот же релиз, и её apply без изменений — проверка `ignore_changes` и `traffic`; - - **чек-лист на v0.1.4,** с алертом и каналом; - - **откат и возврат** по командам `docs/self-hosting.md`: ревизия и выпуск SPA v0.1.3, затем снова v0.1.4; - - **итог:** - - `just usage`; - - удалить файл разрешения; - - отчёт со статусом «закрыто»; - - T12 `[x]`; - - второй PR. + - Rebuild Container — в нём `firebase` 15.33.0; + - «Workflows» — в обязательные проверки ruleset «main»; + - узкое разрешение в `.claude/settings.local.json` на кнопку, `update-traffic` и `hosting:clone`; + - мерж PR #16. После прогона Claude файл разрешения удалил; + - **вторая выкладка v0.1.4 (09938cd)** — зелёная с первой попытки: + - apply — 2 ресурса, алерт и канал; + - `ci-identity-ready` в обоих заданиях получил токен с первой попытки; + - smoke идёт по адресу по номеру проекта; + - **очередь `cd` проверена.** + - Кнопка Deploy нажата в 20:29:17 UTC, во время выкладки релиза. Её прогон стоял в `pending` и начал первое задание в 20:31:23 — через секунду после последнего задания выкладки релиза. + - Затем кнопка выкатила тот же v0.1.4. Её apply — `0 added, 0 changed, 0 destroyed`: это проверка `ignore_changes`, `traffic` и исправления sink; + - **чек-лист на v0.1.4** — «да» по всем пунктам. Алерт `canvas-api load` и канал `canvas-api operator` включены; + - **откат и возврат** — командами `docs/self-hosting.md`: + - ревизия `canvas-api-00002-p84` и выпуск Hosting v0.1.3: `/health` и скрипт страницы назвали v0.1.3; + - затем `--to-latest` и выпуск v0.1.4 — снова v0.1.4; + - около полуминуты в каждую сторону; + - **находка:** `hosting:clone` пишет выпуск `ROLLBACK` без сообщения — дописано в `docs/self-hosting.md`; + - **расход:** `just usage 1d` — 31,1 vCPU-с за сутки, 0,017 % лимита месяца; + - **отчёт** `docs/live/01-first-deploy.md` — «закрыто»; в «Открыто» T11 закрыты «Workflows» и очередь `cd`; + - **ревью `/code-review high` итоговой правки** — первый круг, 8 находок, все исправлены: + - повторная выкладка того же релиза меняет digest образа и тратит место автоочистки — находка 5 отчёта и долг Д15; + - список выпусков Hosting не показывал вид выпуска и брал 5 строк — теперь вид и 20 строк; + - в RUN.md стояла заглушка вместо итога ревью, а «эпик E1 закрыт» спорил с правилом о сделанных эпиках; + - в отчёте — команда `hosting:clone` без проекта квоты, «выпуск» вместо версии, строка старта без версии и времени, два разных времени начала заданий кнопки; + - **второй круг, 8 находок, все исправлены:** у Д15 названа и вторая причина нового digest — время файла в слое — и повод вернуться: возврат кнопкой и «Re-run» тоже повторные выкладки; отчёт больше не говорит, что это «не вредит»; токен в списке выпусков Hosting — заголовком из дескриптора, а не аргументом `curl`; выпуск без версии не роняет `jq`; полное имя раздела чек-листа; шаг автора с «Workflows» вписан; лишняя пустая строка в списке. Исправления второго круга прошли проверки, третьего ревью нет. +- **Открыто:** + - из «Как проверить» T11 вживую не проверено: план на PR Dependabot, коммит с красным CI, кнопка на другой ветке, перезапуск CI старого коммита. Их держат условия workflow и проверки без облака; + - письмо алерта не проверено: такой нагрузки не было; + - что откат не отменяет следующий apply, вживую не проверялось: трафика в конфигурации нет; + - долги Д14 — роли нового аккаунта выкладка не ждёт, и Д15 — повторная выкладка того же релиза даёт новый образ; + - все задачи таблицы 1 отмечены. Перенести E1 в «Сделанные эпики» и выбрать следующий эпик — шаг автора («Как устроен план»). > **Точка проверки эпика E1.** > - Правила работы и версии сверены с образцом от 2026-10-11. diff --git a/docs/live/01-first-deploy.md b/docs/live/01-first-deploy.md index bee0072..d23a9db 100644 --- a/docs/live/01-first-deploy.md +++ b/docs/live/01-first-deploy.md @@ -1,44 +1,49 @@ # Первая выкладка (T12) -**Статус: идёт.** Первая выкладка прошла 2026-10-14 со второй попытки. Релиз v0.1.3 работает на своих адресах, и разворачивал его пайплайн. Откат и возврат проверяются на второй выкладке — это мерж T12; её итог допишется ниже. +**Статус: закрыто.** Первая выкладка прошла 2026-10-14 со второй попытки, вторая — в тот же день с первой. Сервис и SPA живут на своих адресах, и разворачивает их пайплайн. Очередь `cd`, откат и возврат проверены вживую. > Репозиторий публичный, поэтому в отчёте нет ни почты, ни ID платёжного аккаунта, ни токенов. Проект, его номер, адреса и имена ревизий и так названы в `infra/` и в ответах сервиса. ## Что выкатывалось -- Релиз `v0.1.3`, коммит `0bf1483` — мерж PR #15 (T11). Это и называет `/health`. -- API — `https://canvas-api-796865465273.us-east4.run.app`. Cloud Run отвечает и по второму адресу, со случайной частью. -- SPA — `https://mathtrail-canvas-prod.web.app`. +- **Первая выкладка** — релиз `v0.1.3`, коммит `0bf1483`: мерж PR #15 (T11). +- **Вторая выкладка** — релиз `v0.1.4`, коммит `09938cd`: мерж PR #16 (T12, часть 1). Её же повторно выкатила кнопка Deploy. +- **Адреса:** + - API — `https://canvas-api-796865465273.us-east4.run.app`. Cloud Run отвечает и по второму адресу, со случайной частью; + - SPA — `https://mathtrail-canvas-prod.web.app`. ## Роли - **Автор:** - - создал окружения `prod` и `prod-hosting` и секрет `TF_VAR_billing_account`; + - создал окружения `prod` и `prod-hosting` и секреты `TF_VAR_billing_account` и `TF_VAR_operator_email`; - повторил bootstrap; - внёс «Release build», «Terraform» и «Workflows» в обязательные проверки; - - смержил; - - разрешил повтор выкладки. + - смержил оба PR; + - разрешил повтор первой выкладки и дал узкое разрешение на части 2. - **Claude:** - сверил окружения и привязки только чтением; - - следил за прогоном и разобрал причину сбоя; - - перезапустил упавшие задания по разрешению автора; + - следил за прогонами и разобрал причину сбоя; + - перезапустил упавшие задания; + - нажал кнопку Deploy, сделал откат и возврат; - прошёл чек-лист и написал этот отчёт. ## Стоимость - **Оценка до прогона:** ≈ $0, всё в бесплатных лимитах. -- **Факт:** `just usage 1d` в 19:21 UTC: +- **Факт:** `just usage 1d` в 20:35 UTC, за сутки с обеими выкладками, повтором кнопки, откатом и возвратом: | vCPU-секунды | ГиБ-секунды | Запросы | Оплачиваемые секунды экземпляров | Больше всего экземпляров в минуту | |---:|---:|---:|---:|---:| - | 12,8 (0,007 % лимита месяца) | 4,5 (0,001 %) | 5 (0,000 %) | 3,4 | 1 | + | 31,1 (0,017 % лимита месяца) | 9,3 (0,003 %) | 10 (0,001 %) | 6,6 | 1 | - Писем бюджета не было. + Писем бюджета не было. Реестр образов — 9,4 МБ. ## Ход прогона Время — UTC, 2026-10-14. +**Первая выкладка, v0.1.3:** + 1. **18:31:02.** Мерж PR #15, коммит `0bf1483`. 2. **18:31:05–18:34:08.** CI на `main`: все 13 заданий зелёные. 3. **18:34:14–18:35:11.** Сборка, подпись и публикация. Релиз `v0.1.3` вышел в 18:35:08 и стал Latest. @@ -59,43 +64,66 @@ От мержа до зелёной выкладки — 12,5 минуты, из них около четырёх ушло на решение о повторе. +**Вторая выкладка, v0.1.4, и очередь `cd`:** + +11. **20:28:02–20:29:04.** Прогон «Release and deploy» после зелёного CI. Релиз `v0.1.4` вышел в 20:28:49, «Which release» выбрал его. +12. **20:29:08–20:29:53.** `terraform apply`: `Apply complete! Resources: 2 added, 0 changed, 0 destroyed` — алерт нагрузки и его канал. Задан адрес оператора. +13. **20:29:17.** Claude нажал кнопку Deploy на `main` (`gh workflow run deploy.yml --ref main`). Её прогон встал в статус `pending`, и ни одно его задание не началось. +14. **20:29:57–20:30:45.** Сервис: + - `ci-identity-ready` — токен получен с первой попытки; + - ревизия `canvas-api-00003-bw6`, smoke — `v0.1.4 at 09938cd` по адресу по номеру проекта. +15. **20:30:49–20:31:22.** SPA: токен с первой попытки, выпуск Hosting в 20:31:17, страница совпала с архивом. +16. **20:31:23.** Прогон кнопки начал задания через секунду после последнего задания выкладки релиза: пропущенный план отметился в 20:31:23, «Which release» начался в 20:31:25. Очередь `cd` держит и выкладку, которую вызывает `release.yml`. +17. **20:31:25–20:33:28.** Выкладка кнопкой: + - «Which release» выбрал тот же `v0.1.4` — он на вершине `main`; + - apply — `Apply complete! Resources: 0 added, 0 changed, 0 destroyed`. Конфигурация совпадает с облаком: ни образ и клиент gcloud, которые пишет выкладка, ни трафик apply не трогает, а sink `_Default` больше не «меняется»; + - ревизия `canvas-api-00004-8mk` — образ из того же подписанного бинарника, но с другим digest: в образе другое время сборки. Тег `v0.1.4` в реестре переехал на новый образ (ниже, «Что нашёл прогон»); + - выпуск Hosting в 20:33:24. + +**Откат и возврат — командами `docs/self-hosting.md`:** + +18. **20:34:44.** `gcloud run services update-traffic … --to-revisions=canvas-api-00002-p84=100`. В 20:34:58 `/health` — `v0.1.3 at 0bf1483`. +19. **20:35:03.** `GOOGLE_CLOUD_QUOTA_PROJECT=mathtrail-canvas-prod firebase hosting:clone mathtrail-canvas-prod@e8edf33829c41cd3 mathtrail-canvas-prod:live --project mathtrail-canvas-prod` — версия Hosting первой выкладки, v0.1.3. В 20:35:11 страница называет скрипт `index-e6yTJfJc.js`, а он несёт `v0.1.3`. Страница отдаётся с `no-cache`. +20. **20:35:15.** `update-traffic --to-latest`. В 20:35:32 `/health` — `v0.1.4 at 09938cd`, весь трафик на `canvas-api-00004-8mk`. +21. **20:35:37.** Та же команда с версией `736c82fe18f9a0e7` — последняя выкладка v0.1.4, кнопкой в 20:33:24. В 20:35:39 страница — `index-D6U2BkXp.js`, `v0.1.4`. + +Откат сервиса и SPA занял около 30 секунд, возврат — столько же. + ## Проверки -Чек-лист «Первая выкладка» из `docs/self-hosting.md`, пройден 2026-10-14 на `v0.1.3`. +Чек-лист «Первая выкладка: чек-лист» из `docs/self-hosting.md`, пройден на `v0.1.3` и на `v0.1.4`. -| Пункт | Результат | -|---|---| -| `/health` называет релиз и коммит | Да: `v0.1.3 at 0bf1483`, по обоим адресам, с `Cache-Control: no-store` | -| Строка старта называет версию и коммит | Да: 18:42:45 и 18:42:47, ревизия `canvas-api-00002-p84`, `v0.1.3`, `0bf1483` | -| Автоочистка Artifact Registry действует | Да: `keep-recent` (5 версий) и `delete-old` (2592000 с, любые теги). Поля `cleanupPolicyDryRun` нет — пробного режима нет. Реестр — 3,7 МБ | -| Бюджет есть и смотрит на этот проект | Да: `canvas-api spend`, 1 USD, `projects/796865465273`, месяц, пороги 0.5, 0.9 и 1.0 | -| Алерт нагрузки и его канал | Нет, и так и ожидалось: адрес оператора ещё не задан | -| SPA без кеша и называет релиз | Да: `no-cache`, скрипт `index-e6yTJfJc.js` несёт `v0.1.3`; `/__/firebase/init.json` отвечает 200 | -| Конфигурация совпадает с облаком | Нет, исправлено в T12. План после первого apply хотел менять sink `_Default` (ниже). С исправлением план не меняет ни одного ресурса, только выход `service_url` | -| Расход в бесплатных лимитах | Да, «Стоимость» выше | +| Пункт | v0.1.3 | v0.1.4 | +|---|---|---| +| `/health` называет релиз и коммит | Да: `v0.1.3 at 0bf1483`, с `Cache-Control: no-store` | Да: `v0.1.4 at 09938cd` | +| Строка старта называет версию и коммит | Да: 18:42:45 и 18:42:47, ревизия `canvas-api-00002-p84`, `v0.1.3`, `0bf1483` | Да: 20:30:34, ревизия `canvas-api-00003-bw6`, и 20:32:45, ревизия `canvas-api-00004-8mk`, обе — `v0.1.4`, `09938cd` | +| Автоочистка Artifact Registry действует | Да: `keep-recent` (5), `delete-old` (2592000 с), поля `cleanupPolicyDryRun` нет | Да, то же | +| Бюджет есть и смотрит на этот проект | Да: `canvas-api spend`, 1 USD, `projects/796865465273`, 0.5, 0.9 и 1.0 | Да, то же | +| Алерт нагрузки и его канал | Нет: адреса ещё не было — ожидаемо | Да: `canvas-api load` включён, канал `canvas-api operator` (email) включён | +| SPA без кеша и называет релиз | Да: `no-cache`, `index-e6yTJfJc.js` несёт `v0.1.3` | Да: `index-D6U2BkXp.js` несёт `v0.1.4` | +| Конфигурация совпадает с облаком | Нет: sink `_Default` «менялся» в каждом плане — исправлено в T12 | Да: apply кнопки — 0 added, 0 changed, 0 destroyed | +| Расход в бесплатных лимитах | Да: 12,8 vCPU-с за сутки | Да: 31,1 vCPU-с за сутки | ## Что нашёл прогон 1. **Новый аккаунт не может войти сразу.** - Задание входит аккаунтом, привязку которого apply этой же выкладки создал секундами раньше. - - Исправлено в T12: задания `deploy` и `hosting` после входа ждут, пока аккаунт даст токен — `just ci-identity-ready`, до 8 минут. - - Без нового аккаунта ожидание занимает одну попытку. + - Исправлено в T12: задания `deploy` и `hosting` после входа ждут, пока аккаунт даст токен, — `just ci-identity-ready`, до 8 минут. + - Во второй выкладке ожидание заняло одну попытку. + - Роли, которые тот же apply выдаёт новому аккаунту, ожидание не покрывает — долг Д14 в RUN.md. 2. **Sink `_Default` менялся в каждом плане.** - Конфигурация просила `unique_writer_identity = false`. Провайдер 8.3.0 читает sink без идентичности как sink с уникальной и показывал `true → false` в каждом плане и apply. - У sink, чей бакет в его же проекте, идентичности не бывает (справочник Cloud Logging API, прочитан 2026-10-14). - - Теперь в конфигурации `true`, и тест держит это значение. -3. **Адрес сервиса.** - - Выход `service_url` давал адрес со случайной частью (`…-uk.a.run.app`), а R55 и документы называют адрес по номеру проекта. - - Теперь выход — адрес по номеру проекта, из атрибута `urls`; тест на это есть. - - По нему же идут smoke выкладки и ссылка окружения `prod`. + - Теперь в конфигурации `true`. Apply кнопки во второй выкладке ничего не менял. +3. **Адрес сервиса.** Выход `service_url` давал адрес со случайной частью. Теперь он даёт адрес по номеру проекта, и по нему идут smoke и ссылка окружения `prod`. +4. **Выпуск отката без номера релиза.** `hosting:clone` пишет выпуск типа `ROLLBACK` без сообщения. После отката верхние строки списка выпусков релиз не называют, его видно у строк `DEPLOY`. Дописано в `docs/self-hosting.md`. +5. **Повторная выкладка того же релиза даёт новый образ.** + - `ci-image-push` собирает образ релиза заново при каждой выкладке, а время сборки в образе и время файла сервера в его слое меняют digest. Кнопка дала `canvas-api-00004-8mk` с другим digest, чем у `canvas-api-00003-bw6`, и тег `v0.1.4` переехал на новый образ. + - Каждая такая выкладка занимает одно из пяти мест автоочистки `keep-recent`. Образ, на который нужен откат, может раньше времени выпасть из этих пяти, а через 30 дней его удалит `delete-old`. + - Работающей ревизии это не вредит: она ссылается на образ по digest. Вредит откату на старую ревизию, если её образ уже удалён: экземпляр не поднимется. Повторная выкладка — это и возврат кнопкой, и «Re-run failed jobs». Долг Д15 в RUN.md. ## Открыто -- **Вторая выкладка — мерж T12.** После неё дописываются: - - очередь `cd` при выкладке, которую вызывает `release.yml`; - - откат и возврат по командам `docs/self-hosting.md`; - - алерт с адресом оператора; - - чек-лист на `v0.1.4`. - **Из «Как проверить» T11 вживую не проверено:** - план на PR Dependabot — такого PR, который трогал бы `infra/` или `deploy.yml`, ещё не было; - коммит с красным CI; @@ -103,15 +131,18 @@ - перезапуск CI старого коммита. Их держат условия workflow и проверки рецептов без облака (T11). +- **Письмо алерта** не приходило и не проверялось: нагрузки в 300 запросов в минуту не было. +- **Что откат не отменяет следующий apply,** вживую не проверялось. Трафика в конфигурации нет, а следующая выкладка сама возвращает его на новую ревизию (`ci-deploy`). ## Как повторить Развёртывание с нуля описывает `docs/self-hosting.md`: bootstrap, секреты, окружения, затем мерж в `main`. ```bash -gh run watch <прогон «Release and deploy»> # выкладка после мержа -gh run rerun <прогон> --failed # если упало задание выкладки -just usage 1d # расход против бесплатного лимита +gh run watch <прогон «Release and deploy»> # выкладка после мержа +gh workflow run deploy.yml --repo MathTrail/canvas-api --ref main # кнопка, во время выкладки — проверка очереди +gh run rerun <прогон> --failed # если упало задание выкладки +just usage 1d # расход против бесплатного лимита ``` -Остальное — пункты раздела «Первая выкладка: чек-лист» в `docs/self-hosting.md`, по порядку. +Чек-лист, откат и возврат — разделы «Первая выкладка: чек-лист» и «Откат и возврат» в `docs/self-hosting.md`, по порядку. diff --git a/docs/self-hosting.md b/docs/self-hosting.md index bd88e7f..7ee507c 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -182,13 +182,13 @@ curl -fsS https://canvas-api-796865465273.us-east4.run.app/health Пока трафик закреплён, новые ревизии его не получают (документация Cloud Run, прочитана 2026-10-14). Следующая выкладка возвращает его на новую ревизию сама (`ci-deploy`). -**SPA.** Выпуски сайта, новые сверху, с номером релиза в сообщении; прошлый выпуск — снова в live: +**SPA.** Выпуски сайта, новые сверху, с их версией, временем и видом: выкладка пишет выпуск `DEPLOY` с номером релиза в сообщении, а откат и возврат — `ROLLBACK` без сообщения, поэтому нужная версия ищется по строкам `DEPLOY`. `hosting:clone` берёт версию и снова делает её live: ```bash -curl -fsS -H "Authorization: Bearer $(gcloud auth print-access-token)" \ +curl -fsS -H @<(printf 'Authorization: Bearer %s\n' "$(gcloud auth print-access-token)") \ -H "x-goog-user-project: mathtrail-canvas-prod" \ - "https://firebasehosting.googleapis.com/v1beta1/sites/mathtrail-canvas-prod/releases?pageSize=5" \ - | jq -r '.releases[] | "\(.version.name | split("/") | last) \(.releaseTime) \(.message // "")"' + "https://firebasehosting.googleapis.com/v1beta1/sites/mathtrail-canvas-prod/releases?pageSize=20" \ + | jq -r '.releases[] | "\((.version.name // "-") | split("/") | last) \(.releaseTime) \(.type) \(.message // "")"' GOOGLE_CLOUD_QUOTA_PROJECT=mathtrail-canvas-prod firebase hosting:clone \ mathtrail-canvas-prod@<версия> mathtrail-canvas-prod:live --project mathtrail-canvas-prod ``` @@ -203,7 +203,7 @@ GOOGLE_CLOUD_QUOTA_PROJECT=mathtrail-canvas-prod firebase hosting:clone \ mathtrail-canvas-prod@<версия последнего релиза> mathtrail-canvas-prod:live --project mathtrail-canvas-prod ``` -Откат и возврат проверяет T12 на второй выкладке: после первой прошлая ревизия — образ `hello`, а прошлого выпуска SPA нет. +Откат и возврат проверены 2026-10-14 этими командами: на второй выкладке сервис и SPA вернулись к v0.1.3, а затем к v0.1.4, около полуминуты в каждую сторону (`docs/live/01-first-deploy.md`). ## Переменные