Files
admin/.tasks/bookva-tenant-cutover-prep.md
vitya ce8bf8a646 tasks(bookva-tenant-cutover-prep): 🟡 progress 4/7 — Steps 1+3-partial+5+7
Session 2026-05-26 закрыто: Steps 1+5+7 done, Step 3 partial (6 volumes
+ 4 populated из corrected templates).

Cross-repo work этой сессии:
- victor/books `2f7b539` — deploy.yml secret rename (`GITEA_TOKEN` → `BOOKVA_OVERLAY_TOKEN`,
  т.к. Gitea резервирует `GITEA_*` prefix для repo secrets).
- victor/books `75ea320` — deploy/web.compose.yml embed-api refs
  (NUXT_AUTH_TOKEN + NUXT_JWT_SECRET_KEY вместо NUXT_PUBLIC_BOOKS_API_KEY).
  Portainer stack 24 уже PUT'нут (env+compose atomic), container recreated,
  smoke green.
- victor/bookva-overlay `5b173de` — config-templates aligned to prod shape
  (jobs[], jobs-new[], docker, mailSettings, ntfy) + design v3 hostname
  (bookseller.kzntsv.site). task-runner template added (был missing).

Pass entry added: `gitea/victor-books-ci-bookva-overlay` (PAT scope=read:repository,
для BOOKVA_OVERLAY_TOKEN secret в victor/books).

Maintenance-window items deferred: Step 2 stacks, Step 3 stateful
(Mongo/MariaDB/MinIO), Step 4 login-gate. Step 6 delayed по spec (1-2 нед).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:08:00 +03:00

178 lines
13 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.
# bookva-tenant-cutover-prep
Подготовительные ops-шаги на books VDS перед cutover Bookva tenant в отдельный стек на `bookseller.kzntsv.site`. Code/CI part сделан в victor/books master (см. dev-side ниже).
## Контекст dev-source
- Дизайн: victor/books `.wiki/concepts/tenant-split.md` rev v3 (2026-05-25).
- Overlay-репо: `victor/bookva-overlay` + `victor/slovo-overlay` (compose'ы + config-templates).
- Code-side done в master:
- `5e28fd1` embed-api в web (server/api/*) + zod^4 fix.
- `4a9cafc` ES endpoint per-tenant через `custom-environment-variables.json`.
- `c1e58cf` BookvaRedirectBanner — PWA handoff modal для user.id=1.
- `88df172` deploy.yml per-tenant (tenant input + per-tenant stack IDs + bookva-overlay clone).
## Goal
Bookva стек готов взлететь — все secrets/stacks/volumes/DNS/PWA-redirect на месте. Сам `compose up` для bookva = последний шаг (отдельная задача `bookva-tenant-cutover` будет создана когда юзер скажет).
## Plan
### Step 1 — Gitea secrets (admin via gitea UI или API)
Добавить в repo `victor/books` settings → Secrets:
```
PORTAINER_STACK_ID_BOOKVA_API
PORTAINER_STACK_ID_BOOKVA_WEB
PORTAINER_STACK_ID_BOOKVA_SCHEDULER
PORTAINER_STACK_ID_BOOKVA_OPS_MCP
GITEA_TOKEN # read-access к victor/bookva-overlay
HEALTHCHECK_BOOKVA_API_URL # опционально, http healthcheck
HEALTHCHECK_BOOKVA_WEB_URL
```
Значения stack ID — после Step 2.
После Phase 2 cutover (отдельная задача) — переименовать `PORTAINER_STACK_ID_{API,WEB,SCHEDULER,OPS_MCP}``PORTAINER_STACK_ID_SLOVO_*` для symmetry. До тех пор deploy.yml использует fallback на legacy имена.
### Step 2 — Portainer bookva stacks
В Portainer (https://portainer.kzntsv.site) создать stacks из overlay-compose:
- `bookva-db``~/projects/bookva-overlay/deploy/db.compose.yml`
- `bookva-mongo``mongo.compose.yml`
- `bookva-es``elasticsearch.compose.yml` (новый, в-stack ES 7.10.0)
- `bookva-api``api.compose.yml`
- `bookva-web``web.compose.yml`
- `bookva-scheduler``scheduler.compose.yml`
- `bookva-task-runner``task-runner.compose.yml`
- `bookva-ozon-mcp``ozon-mcp.compose.yml`
- `bookva-ops-mcp``ops-mcp.compose.yml`
- `bookva-ntfy``ntfy.compose.yml`
Env-vars stack-level (Portainer Stack → Environment):
- `CORE_SHA` — последний master tag из registry (например `master-5e28fd1`).
- `HOSTNAME_API` / `HOSTNAME_WEB``bookseller.kzntsv.site` (per design v3).
- `HOSTNAME_NTFY``push.bookseller.kzntsv.site`.
После создания каждого stack — Portainer присваивает ID → записать в Gitea secrets (Step 1).
### Step 3 — Volume create + copy (stateful split)
См. отдельную задачу [stateful-split-volume-copy](stateful-split-volume-copy.md) — MariaDB.
Дополнительно для Phase 2 нужны (текущая задача охватывает только MariaDB):
- `bookva-mongo-data` — copy с `books-job-scheduler-mongo_data` через `cp -a` + `deleteMany({'data.idSeller': 2})` для cleanup чужих agenda jobs.
- `bookva-es-data` — пустой volume; данные через ES snapshot+restore из shared kzntsv ES в Phase 2.
- `bookva-minio-data` ИЛИ FS-rename `books``slovo` + `cp -a slovo bookva` в shared MinIO контейнере.
- Config volumes — `bookva-{api,scheduler,task-runner}-config` + `bookva-web-branding` + `bookva-ntfy-data` (см. README в bookva-overlay для bootstrap).
### Step 4 — bookva-db login-gate
После volume copy + bookva-db up:
```sql
-- В bookva-db только (slovo-db не трогать!):
UPDATE users SET password = SHA2(UUID(), 256) WHERE id NOT IN (1, 2);
```
(точная команда — после проверки актуального password storage format в `packages/api/server/services/auth/*` или embedded `packages/web/server/api/auth/login.post.js`.)
Эффект: только id=1 (Bookva-учредитель) + id=2 (Slovo-учредитель) могут логиниться в `bookseller.kzntsv.site`. Остальные user-записи остаются, но без работающего пароля.
### Step 5 — DNS
Добавить A-record `bookseller.kzntsv.site` → books-vds IP (тот же что `bookva.kzntsv.site` сейчас).
После cutover (отдельная задача) — `bookva.kzntsv.site` остаётся на тот же IP (роутит на slovo стек).
### Step 6 — PWA redirect handoff (за 1-2 недели до cutover)
В Portainer для текущего `books-web` stack (= future slovo-web после cutover) добавить env:
```
NUXT_PUBLIC_BOOKVA_REDIRECT_URL=https://bookseller.kzntsv.site
```
После DEPLOY_AT bump → recreate web container → новый SW bundle подхватывает `BookvaRedirectBanner` компонент. user.id=1 видит modal «Bookva теперь на bookseller.kzntsv.site → Перейти».
### Step 7 — books-web embedded-api env (pending step из embed-api handoff)
В Portainer `books-web` stack environment:
ADD:
```
NUXT_AUTH_TOKEN=<значение из books-api.NITRO_AUTH_TOKEN>
NUXT_JWT_SECRET_KEY=<значение из books-api.NITRO_JWT_SECRET_KEY>
```
CHANGE/REMOVE:
```
NUXT_PUBLIC_BOOKS_API_URL=/api # было https://books.kzntsv.site, переключаем на embedded
NUXT_PUBLIC_BOOKS_API_KEY= # удалить (legacy)
```
После DEPLOY_AT bump → recreate. Embedded api начнёт обслуживать `/api/*` (вместо старого books-api стека).
После 24-48ч стабильности — остановить `books-api` stack (отдельная micro-task).
## Acceptance
- Gitea secrets выставлены (Step 1).
- Portainer stacks `bookva-*` созданы (Step 2), stack IDs в Gitea secrets.
- Volumes `bookva-*` существуют + data populated (Step 3).
- bookva-db login-gate применён (Step 4).
- DNS `bookseller.kzntsv.site` → books-vds (Step 5).
- `NUXT_PUBLIC_BOOKVA_REDIRECT_URL` в books-web env (Step 6) — за 1-2 недели до cutover.
- books-web env обновлён для embedded api (Step 7).
- Acceptance не включает `compose up bookva-*` — это **отдельная** задача `bookva-tenant-cutover` (создаётся когда юзер скажет «давай поднимать Bookva»).
## Status
paused (4/7 steps done — остальное ждёт maintenance window)
## Where I stopped
2026-05-26 — Steps 1, 5, 7 done; Step 3 partial (6 empty volumes + 4 populated из corrected templates). Steps 2, 3-rest (Mongo/MariaDB/MinIO), 4 deferred к coordinated maintenance window с user'ом. Step 6 delayed по spec (1-2 нед до cutover).
## Decisions log
- **2026-05-26 — Step 5 closed by inspection.** `bookseller.kzntsv.site` уже резолвится authoritative `ns1.reg.ru``89.253.255.133`. User либо добавил A-record ранее, либо wildcard. Zero work needed.
- **2026-05-26 — Step 1: Gitea reserves `GITEA_*` prefix.** Попытка PUT `GITEA_TOKEN` secret вернула 400 "invalid variable or secret name". Создан новый PAT scope=`read:repository` для OpeItcLoc03 (UI generate, т.к. `POST /users/.../tokens` требует basic auth, не token). Token в pass `gitea/victor-books-ci-bookva-overlay`. Записан в victor/books secret под именем `BOOKVA_OVERLAY_TOKEN`. deploy.yml line 80 patched (`secrets.GITEA_TOKEN``secrets.BOOKVA_OVERLAY_TOKEN`) — commit `2f7b539` pushed.
- **2026-05-26 — bookva-overlay templates stale + missing fields.** При populate выяснилось что `config-templates/api/default.json` + `scheduler/default.json` короче prod (missing `jobs[]`, `jobs-new[]`, `docker`, `mailSettings`, `ntfy`), а `api.url`/`web.url` в scheduler template использовали `api.bookva.kzntsv.site`/`bookva.kzntsv.site` (старая нотация, до design v3 hostname swap). `task-runner/default.json` отсутствовал вовсе. Переписал из prod baseline + per-tenant overrides (`bookva-db`, `mongodb://bookva-mongo:27017/agendaDb`, `https://bookseller.kzntsv.site`, `s3.bucket=bookva`, `http://bookva-es:9200`). README hostname table → design v3. bookva-overlay commit `5b173de` pushed.
- **2026-05-26 — Step 7 atomic env+compose PUT.** books-web compose template references меняем legacy `NUXT_PUBLIC_BOOKS_API_KEY``NUXT_AUTH_TOKEN` + `NUXT_JWT_SECRET_KEY` (server-side, не публикуется в client bundle). Values из books-api container env (`NITRO_AUTH_TOKEN=81e123d0-...`, `NITRO_JWT_SECRET_KEY=505172e5...`). Portainer stack 24 updated via API (PUT compose+env atomically, container recreated, DEPLOY_AT bumped). Smoke: `/api/healthz=200`, `/api/auth/login.post=401 "JWT-required"` matches books-api behavior. books `75ea320` pushed. **Гонка race:** PUT в Portainer перед push в git, чтобы next CI auto-deploy не подхватил stale compose без NUXT_AUTH_TOKEN ref.
- **2026-05-26 — Step 3 scope split.** Этот task охватывает 6 volumes (api-config, scheduler-config, task-runner-config, web-branding, ntfy-data, es-data) + populate из corrected templates. **Stateful data volumes** (bookva-db-data via cp -a `books-db`, bookva-mongo-data via cp -a books-job-scheduler-mongo) = scope of separate `stateful-split-volume-copy` task (under maintenance window). MinIO rename (`books``slovo` + `cp slovo bookva`) = separate consideration, требует books-api reconfig (новая bucket).
## Completed steps
- [x] Step 5 — DNS `bookseller.kzntsv.site``89.253.255.133` (закрыто inspection, было pre-existing)
- [x] Step 1 — Gitea secret `BOOKVA_OVERLAY_TOKEN` создан в victor/books + deploy.yml `secrets.GITEA_TOKEN``secrets.BOOKVA_OVERLAY_TOKEN` (books `2f7b539` pushed). Token в pass `gitea/victor-books-ci-bookva-overlay`. Stack ID secrets отложены до Step 2.
- [x] Step 3 (config volumes part) — 6 empty external volumes созданы (`bookva-{api-config,scheduler-config,task-runner-config,web-branding,ntfy-data,es-data}`). 4 populated с corrected templates: `bookva-api-config`, `bookva-scheduler-config`, `bookva-task-runner-config`, `bookva-web-branding`. 2 intentionally empty: `bookva-ntfy-data` (чистый старт), `bookva-es-data` (Phase 2 snapshot/restore).
- [x] Bookva-overlay template correctness fix — api/scheduler/task-runner templates aligned to prod shape + design v3 (commit `5b173de` pushed).
- [x] Step 7 — books-web embed-api env switch live. Portainer stack 24: env={`NUXT_PUBLIC_BOOKS_API_URL=/api`, `NUXT_AUTH_TOKEN=81e123d0-...`, `NUXT_JWT_SECRET_KEY=505172e5...`}, compose template updated. Smoke green. books `75ea320` pushed. books-api stack продолжает работать как fallback — отдельной micro-task'ой stop после 24-48ч soak.
## Remaining (maintenance-window or delayed)
- **Step 2 — Portainer bookva-* stacks (10 stacks).** Требует все volumes existing (включая stateful). Запускается после `stateful-split-volume-copy` task. Env stack-level: `CORE_SHA=master-<latest>`, `HOSTNAME_API=HOSTNAME_WEB=bookseller.kzntsv.site`, `HOSTNAME_NTFY=push.bookseller.kzntsv.site`.
- **Step 3 remaining — Mongo cp + MariaDB cp + MinIO rename.** Mongo + MariaDB → блок stateful-split-volume-copy. MinIO rename — disruptive (books-api config change нужен), пока defer; alternative — copy `books/*` объекты в new `bookva/*` bucket без rename (write-only path для bookva-API).
- **Step 1 finish — записать `PORTAINER_STACK_ID_BOOKVA_{API,WEB,SCHEDULER,OPS_MCP}` после Step 2.** Без них deploy.yml skip'ает bookva tenant с warning (graceful).
- **Step 4 — bookva-db login-gate SQL.** `UPDATE users SET password=SHA2(UUID(),256) WHERE id NOT IN (1,2)` против bookva-db. Точную команду подтвердить по `packages/api/server/services/auth/*` (password storage format в prod).
- **Step 6 — PWA redirect handoff (delayed).** Per spec — за 1-2 нед до cutover bump `NUXT_PUBLIC_BOOKVA_REDIRECT_URL` env в books-web (= future slovo-web) → SW bundle подхватит `BookvaRedirectBanner` модал для user.id=1.
## Next action
Coordinate с user'ом 1-2h maintenance window slot — execute `stateful-split-volume-copy` playbook (bookva-db + bookva-mongo data via `cp -a`), затем Step 2 Portainer stacks create (через Portainer API с volumes уже existing), затем Step 1 finish (stack IDs → Gitea secrets), затем Step 4 login-gate. Step 6 — отдельный заход за 1-2 нед до cutover.
## Blocker
`stateful-split-volume-copy` task — не started. Без MariaDB+Mongo volumes — Step 2 stacks deploy fail (volume not found). Maintenance-window window needs user signoff.
## Branch
master
<!-- created-by: vitya / 2026-05-26 / Phase 2 cutover prep после tenant-split code-side done -->
<!-- progress-2026-05-26: Steps 1, 3-partial, 5, 7 closed; bookva-overlay templates fixed + pushed; victor/books deploy.yml + web.compose.yml fixed + pushed -->