From 5ffca969349acae90036d9f42ece8b9128bde907 Mon Sep 17 00:00:00 2001 From: vitya Date: Wed, 17 Jun 2026 19:21:56 +0300 Subject: [PATCH] docs(wiki): runbook stostayer-web deploy + park complaint-form task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ingest .wiki/concepts/stostayer-web-deploy-runbook.md — канал деплоя легаси web (build offline → push docker.stostayer.ru → Portainer-стек 16 через container-IP API с хоста, мимо Angie BA) + rollback + гоча VPN-IP бан хостером при retry-push-шторме. БЛОКЕР задокументирован: легаси packages/web (node16/CJS/Nuxt2) не пересобрать ни с какого свежего дерева — 5 ESM-ставших депов (@snollajs/snolla, @snollajs/content-api, @stostayer/api, @stostayer/data ×2). 0.3.18 заморожен. Фикс формы (120bc07) едет с web4-cutover. Таска stostayer-web-complaint-form-deploy → 🔵 park (blocker=web4-cutover); попытка деплоя 0.3.19 откатана на 0.3.18, сайт восстановлен. Дохлый 0.3.19 оставлен в registry по решению vitya. + index.md, log.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- .tasks/STATUS.md | 9 +- .../concepts/stostayer-web-deploy-runbook.md | 104 ++++++++++++++++++ .wiki/index.md | 1 + .wiki/log.md | 2 + 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 .wiki/concepts/stostayer-web-deploy-runbook.md diff --git a/.tasks/STATUS.md b/.tasks/STATUS.md index 6d4a354..1ff305b 100644 --- a/.tasks/STATUS.md +++ b/.tasks/STATUS.md @@ -607,7 +607,7 @@ vehicles-loader (stostayer) деплоится в Docker у клиента (lite --- -## ⚪ [stostayer-web-complaint-form-deploy] — Раскатать на прод (www.stostayer.ru) фикс формы жалоб. Коммит **120bc07** уже в `origin/master` репо `victor/stostayer.new`. +## 🔵 [stostayer-web-complaint-form-deploy] — Раскатать на прод (www.stostayer.ru) фикс формы жалоб. Коммит **120bc07** уже в `origin/master` репо `victor/stostayer.new`. ## Контекст @@ -634,9 +634,10 @@ vehicles-loader (stostayer) деплоится в Docker у клиента (lite **weight:** needs-human **notify:** victor/stostayer.new -**Status:** ready -**Where I stopped:** (not started) -**Next action:** Pull origin/master в проде (victor/stostayer.new, commit 120bc07), пересобрать legacy web и рестартнуть, затем smoke-проверить форму жалоб. +**Status:** blocked +**Where I stopped:** Cherry-pick `120bc07` на `d02f740` (pre-ESM, рецепт stostayer.new) → `7d88a02`. **Собралось** offline (nuxt build прошёл) → push 0.3.19 в `docker.stostayer.ru` (с 1 попытки на новом VPN; прежние retry-штормы засветили egress → хостинг забанил наш VPN-IP, vitya сменил VPN) → передеплой Portainer-стека `stostayer-web` (Id 16, EndpointId 3) на 0.3.19 через Portainer API по IP контейнера `172.18.0.2:9000` (мимо Angie BA). **Но контейнер краш-лупит в рантайме:** `ERR_REQUIRE_ESM` — `packages/web/server/index.js:6` `require('@snollajs/snolla')`, а он ESM (lockfile d02f740 тянет ESM-версию). d02f740 собирается, но НЕ runtime-чистая. **Откатил стек на 0.3.18** (тот же PUT, тег назад) — контейнер Up, штатный Nuxt2, сайт восстановлен. vitya: остаёмся на 0.3.18, апдейт отставлен. +**Next action:** ПАРКНУТО. Деплой невозможен до web4-cutover. Вердикт (stostayer.new 14-55, подтверждён): легаси `packages/web` require-ит **5** ESM-ставших пакетов (`@snollajs/snolla` 0.7.4, `@snollajs/content-api` 0.8.0, `@stostayer/api`, `@stostayer/data` ×2 места) → чейз CJS-базы бесполезен (надо откатиться к ~0.3.18 и потерять всё). `0.3.18` = последний собираемый артефакт, заморожен. Фикс формы (`120bc07`) едет вместе с переездом формы в web4. Ранбук: `.wiki/concepts/stostayer-web-deploy-runbook.md`. Дохлый `0.3.19` в `docker.stostayer.ru` — **оставлен по решению vitya (2026-06-17)**, не деплоить. +**Blocker:** web4-cutover (планирование на стороне vitya, отдельным решением) — НЕ на stostayer.new и НЕ канал доставки (он рабочий). Прод остаётся на 0.3.18, сайт восстановлен. **Branch:** n/a **Notify:** victor/stostayer.new diff --git a/.wiki/concepts/stostayer-web-deploy-runbook.md b/.wiki/concepts/stostayer-web-deploy-runbook.md new file mode 100644 index 0000000..547c27e --- /dev/null +++ b/.wiki/concepts/stostayer-web-deploy-runbook.md @@ -0,0 +1,104 @@ +--- +title: stostayer-web — деплой легаси web на прод клиента (runbook) +status: live +tags: [stostayer, docker, portainer, deployment, nuxt2, esm, ops, rollback] +related: [[portainer-stack-management-vds]] +updated: 2026-06-17 +--- + +# stostayer-web — деплой легаси web на прод клиента + +Прод `https://www.stostayer.ru` = легаси `packages/web` (Nuxt 2, CJS, SSR) из монорепо `victor/stostayer.new`. web4 (Nuxt 4) ещё НЕ переключён. Хостится контейнером, управляется Portainer-стеком у клиента. + +> **Машина клиента, настраивает их админ — мы только деплоим свой web-образ, конфиги хоста не трогаем.** Деталь доступов/инфры — в `stostayer.new/.wiki/concepts/client-infra-access.md`. Все креды — `pass stostayer/client`. + +## Параметры + +| Что | Значение | +|---|---| +| Хост | `91.222.236.225` (SSH `:20435`, юзер `victor` + sudo-с-паролем; парольный SSH) | +| Reverse-proxy | **Angie** (форк nginx) на `:443`, навешивает **Basic-auth** на сервисные сабдомены | +| Registry | `docker.stostayer.ru` (BA те же, что Angie BA) — образы `stostayer-web:` | +| Стек | Portainer **`stostayer-web` Id 16, EndpointId 3**, compose в Portainer-volume `/data/compose/16/docker-compose.yml` | +| Прод-тег (2026-06-17) | `0.3.18`. На хосте лежат старые теги `0.3.6…0.3.18` под откат | +| Образ | `packages/web/Dockerfile`: `node:16` → `yarn install` → `yarn build` (nuxt build) → `CMD yarn start`. pm2/`ecosystem.config.js` в проде НЕ используется | + +## Канал деплоя (build здесь → registry → Portainer) + +### 1. Build образа (локально, offline) + +Тег образа независим от `packages/web/package.json` version — это чисто image-тег, инкремент от прода (`0.3.18` → `0.3.19`). Кода/версий не бампать. + +```bash +# в чекауте нужного коммита, .yarn/cache populated +docker build -f packages/web/Dockerfile -t docker.stostayer.ru/stostayer-web: . # контекст = корень репо +``` + +Гочи build: +- **`.dockerignore` обязателен** (его нет в репо) — иначе `node_modules`/`.git` улетают в build-контекст. Создать временный: `node_modules **/node_modules .git apps docs build *.zip` и т.п. (на явные `COPY` не влияет). +- **Локальная `npmAuthToken: "${VERDACCIO_TOKEN}"` в `.yarnrc.yml`** (рабочая модификация, не в коммите) **ломает build**: yarn внутри образа падает `Usage Error: Environment variable not found (VERDACCIO_TOKEN)`. Убрать эту строку из build-копии (потом вернуть). +- **Offline против `.yarn/cache`:** добавить `enableNetwork: false` в `.yarnrc.yml` — depы пекутся из cache, verdaccio-токен в образе не нужен. (Cache содержит third-party зипы; их версии ESM-миграция не меняла, так что cache от master-tip обычно покрывает и более старые коммиты.) + +### 2. Push в registry + +```bash +echo '' | docker login docker.stostayer.ru -u Victor --password-stdin +docker push docker.stostayer.ru/stostayer-web: +``` + +> ⚠️ **ОДИН push, без retry-циклов.** Инцидент 2026-06-17: retry-шторм (6 попыток, образ 3.29GB) на `docker.stostayer.ru` засветил egress нашего **VPN** → хостинг-провайдер забанил VPN-IP → ВЕСЬ HTTPS дом→хост (`:443`: сайт + registry + portainer) стал TLS-fail (`schannel: failed to receive handshake`), выглядело как «сайт лёг» (а TCP 443/SSH 20435 — открыты). Лечится сменой VPN. **На client-инфру: одна попытка, сбой → стоп и к человеку, не долбить.** (см. [[verify-on-real-client-not-own-curl-tests]]) + +### 3. Передеплой стека — с ХОСТА, мимо Angie BA + +Portainer published **только в docker-сеть** (на host-localhost его НЕТ — там MinIO на `:9000`). Прямой Portainer-API из дома ломает Angie BA (Bearer затирает Basic → 401; auth-cookie не ставится). Решение — бить API по **IP контейнера portainer с самого хоста**: + +```bash +# SSH (PuTTY plink; host-key уже известен) +plink -ssh -batch -hostkey SHA256:6Zk14J/UakVqBYm/fMPFrL3gosdBdyZOKZnbA0GVnD0 \ + -P 20435 -pw '' victor@91.222.236.225 "bash -s" <<'EOF' +base=http://172.18.0.2:9000 # docker inspect portainer → его IP в сети bridge +JWT=$(curl -s $base/api/auth -H 'Content-Type: application/json' \ + -d '{"username":"victor","password":""}' | jq -r .jwt) +FILE=$(curl -s -H "Authorization: Bearer $JWT" $base/api/stacks/16/file | jq -r .StackFileContent) +NEW=$(printf '%s' "$FILE" | sed 's#stostayer-web:0.3.18#stostayer-web:#') +PAYLOAD=$(jq -n --arg c "$NEW" '{stackFileContent:$c, env:[], prune:false, pullImage:true}') +curl -s -w "\nHTTP=%{http_code}\n" -X PUT -H "Authorization: Bearer $JWT" \ + -H "Content-Type: application/json" -d "$PAYLOAD" "$base/api/stacks/16?endpointId=3" +EOF +``` + +- `docker` на хосте — через `sudo` (victor не в группе docker): `echo '' | sudo -S `. Sudo-пароль = SSH-пароль victor. +- `jq` на хосте есть. Скрипт через `bash -s`/stdin — чисто с кавычками; UTF-8 round-trip compose не портит (в отличие от PowerShell Invoke-RestMethod, [[portainer-stack-management-vds]] gotcha #9). +- `pullImage:true` → Portainer сам тянет новый тег из `docker.stostayer.ru` (хост-docker уже авторизован в registry). + +### 4. Verify (read-only, без нагрузки на публичный сайт) + +```bash +docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}' | grep stostayer-web # образ = новый тег, Up +docker logs --tail 20 stostayer-web # чистый старт Nuxt, без ошибок/краш-лупа +``` + +### Откат + +Тот же PUT с прежним тегом (`` → `0.3.18`), `pullImage:true`. 0.3.18 на хосте есть. RTO ~30-60с (пересоздание контейнера). + +## ⛔ БЛОКЕР: легаси web НЕ пересобирается с текущего master (ESM-стена) + +> На 2026-06-17 фикс формы жалоб (`120bc07`, задача `stostayer-web-complaint-form-deploy`) **выкатить не удалось** — два ESM-барьера: + +1. **Build-time:** с master-tip `yarn build` (nuxt) падает `SyntaxError: await is only valid in async functions...` — `@stostayer/data` после таски `esm-data-dual` грузит модели через **top-level await**; цепочка `nuxt.config.js → @stostayer/api → @stostayer/data`, Nuxt2 читает конфиг через jiti → top-level await недопустим. Корень — коммит `c805e7e` (ESM-миграция data/api). +2. **Runtime:** cherry-pick `120bc07` на `d02f740` (родитель c805e7e, «последний CJS-собираемый master») **собирается**, но контейнер **краш-лупит**: `ERR_REQUIRE_ESM` — `packages/web/server/index.js:6` `require('@snollajs/snolla')`, а версия `@snollajs/snolla` из lockfile d02f740 уже **ESM**. Т.е. d02f740 не runtime-чистая. + +Прод `0.3.18` жив только потому, что собран на ещё более старом стейте (CJS `@snollajs/snolla`, CJS data/api). + +**Вердикт (stostayer.new подтвердил 2026-06-17):** легаси-web `require()`-ит **5** ESM-ставших пакетов — `@snollajs/snolla` (0.7.4, `server/index.js:6`), `@stostayer/api` (`nuxt.config.js:3`), `@snollajs/content-api` (0.8.0, `nuxt.config.js:4`), `@stostayer/data` (`serverMiddleware/oldPagesRedirections.js:14` + `redirections.js:14`). Чтобы найти base где ВСЕ пять ещё CJS — надо к ~0.3.18 и потерять всё с тех пор. **Чейз базы бесполезен. `packages/web` (node16/CJS/Nuxt2) не пересобираем ни с какого свежего дерева — весь dep-граф pure-ESM. `0.3.18` заморожен (последний собираемый артефакт).** + +**Путь:** фикс формы (`120bc07`) едет вместе с **переездом формы в web4** (Opt 3). Ретрофит `require`→dynamic `import()` в 5 местах технически возможен на node16, но это часы на стек, который удаляется web4 — не рекомендуется. До web4-cutover — **остаёмся на 0.3.18**, admin-сессия код не правит ([[verify-on-real-client-not-own-curl-tests]]). + +> Образ `0.3.19` (краш-лупный) лежит в `docker.stostayer.ru` — **НЕ деплоить**. Оставлен в registry по решению vitya (2026-06-17), не удаляем. + +## Связи + +- [[portainer-stack-management-vds]] — родственный паттерн (наш VDS), оттуда PowerShell-кодировочные гочи и redeploy-рецепт. +- Доступы/инфра клиента (детально): `stostayer.new/.wiki/concepts/client-infra-access.md`. +- Задача: `.tasks/stostayer-web-complaint-form-deploy.md` (🔵 blocked на victor/stostayer.new). diff --git a/.wiki/index.md b/.wiki/index.md index a83c9cf..68f70e0 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -39,6 +39,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [portainer-2.21-admin-password-regression](concepts/portainer-2.21-admin-password-regression.md) — Portainer 2.21 `--admin-password` regression + min 12-char policy - [portainer-stack-management-books-vds](concepts/portainer-stack-management-books-vds.md) — Portainer-managed stacks на books VDS — pattern application + migration log 2026-05-25 - [portainer-stack-management-vds](concepts/portainer-stack-management-vds.md) — Portainer-managed stacks на VDS — canonical pattern + migration script + stack-redeploy recipe (новый тег) + gotchas (вкл. PS 5.1 ISO-8859-1 коррапт кириллицы при API round-trip) +- [stostayer-web-deploy-runbook](concepts/stostayer-web-deploy-runbook.md) — деплой легаси web (`www.stostayer.ru`) на прод клиента: build→registry→Portainer-стек 16 через container-IP API с хоста; **БЛОКЕР: легаси не пересобрать с master (ESM-стена: top-level await @stostayer/data на build, ERR_REQUIRE_ESM @snollajs/snolla в рантайме)**; гоча — один push без retry (VPN-IP банится хостером) - [proxy-debugging-test-the-real-client](concepts/proxy-debugging-test-the-real-client.md) — анти-паттерн: свои curl/standalone-тесты «работают», а боевой клиент пользователя нет; источник истины — реальный клиент - [reality-pq-mldsa65-dest-incompatibility](concepts/reality-pq-mldsa65-dest-incompatibility.md) — REALITY+ML-DSA-65 (PQ) не работает с не-PQ dest (Akamai/intel шлёт HRR); fix = PQ-совместимый dest или выключить PQ; **caveat: снятие PQ ≠ рабочий Reality у GUI-клиентов** - [recovery-architecture-snapshot](concepts/recovery-architecture-snapshot.md) — текущая recovery architecture (2026-05-19/21, attempt 2) diff --git a/.wiki/log.md b/.wiki/log.md index cdadeaf..1516613 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -2,6 +2,8 @@ Append-only log of wiki operations (ingests, promotions, lints, migrations). +## [2026-06-17] ingest | concepts/stostayer-web-deploy-runbook.md (new) — ранбук деплоя легаси web на прод stostayer: канал build(offline)→push docker.stostayer.ru→Portainer-стек 16 через container-IP API (172.18.0.2:9000) с хоста мимо Angie BA; rollback; БЛОКЕР ESM-стена (top-level await @stostayer/data на build + ERR_REQUIRE_ESM @snollajs/snolla в рантайме d02f740) → 0.3.18 остаётся; гоча VPN-IP бан хостером при retry-push-шторме. Источник: попытка деплоя `stostayer-web-complaint-form-deploy` 2026-06-17 (откат на 0.3.18). +index.md. + ## [2026-06-17] update | entities/windows-recovery-host.md — уточнён live-state двух stostayer-IIS-сайтов: добавлена таблица порт→connection string→БД (`stostayer` :8090→`www.stostayer.ru,1433` внешний прод + S3 minio.stostayer.ru; `stostayer.old` :8091→`mssql.kzntsv.site,1433` наш VDS), путь админок `/admin`, live-проверка 2026-06-17 (W3SVC Running, :8090/:8091 LISTENING, :80 нет). Пофикшена стейловая строка в «Ключевые папки» (stostayer.old conn localhost→mssql.kzntsv.site). Источник: live-аудит IIS на DESKTOP-NSEF0UK. ## [2026-06-17] update | concepts/portainer-stack-management-vds.md +раздел «Stack redeploy (новый image tag)» (JWT→GET file байтами→подмена тега→PUT pullImage:true, env=[] плейсхолдером, тело UTF-8) + gotcha #9 (PS 5.1 Invoke-RestMethod декодит /file как ISO-8859-1 → кириллические compose-комментарии mojibake → PUT падает `yaml: could not find expected ':'`; fix = байты+UTF8) + #10 (пустой env схлопывается). Источник: redeploy-pilonuxt-gsc-structured-data (19a4a84 в проде, stack 16).