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

13 KiB
Raw Blame History

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-mongomongo.compose.yml
  • bookva-eselasticsearch.compose.yml (новый, в-stack ES 7.10.0)
  • bookva-apiapi.compose.yml
  • bookva-webweb.compose.yml
  • bookva-schedulerscheduler.compose.yml
  • bookva-task-runnertask-runner.compose.yml
  • bookva-ozon-mcpozon-mcp.compose.yml
  • bookva-ops-mcpops-mcp.compose.yml
  • bookva-ntfyntfy.compose.yml

Env-vars stack-level (Portainer Stack → Environment):

  • CORE_SHA — последний master tag из registry (например master-5e28fd1).
  • HOSTNAME_API / HOSTNAME_WEBbookseller.kzntsv.site (per design v3).
  • HOSTNAME_NTFYpush.bookseller.kzntsv.site.

После создания каждого stack — Portainer присваивает ID → записать в Gitea secrets (Step 1).

Step 3 — Volume create + copy (stateful split)

См. отдельную задачу stateful-split-volume-copy — 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 booksslovo + 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:

-- В 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.ru89.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_TOKENsecrets.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_KEYNUXT_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 (booksslovo + cp slovo bookva) = separate consideration, требует books-api reconfig (новая bucket).

Completed steps

  • Step 5 — DNS bookseller.kzntsv.site89.253.255.133 (закрыто inspection, было pre-existing)
  • Step 1 — Gitea secret BOOKVA_OVERLAY_TOKEN создан в victor/books + deploy.yml secrets.GITEA_TOKENsecrets.BOOKVA_OVERLAY_TOKEN (books 2f7b539 pushed). Token в pass gitea/victor-books-ci-bookva-overlay. Stack ID secrets отложены до Step 2.
  • 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).
  • Bookva-overlay template correctness fix — api/scheduler/task-runner templates aligned to prod shape + design v3 (commit 5b173de pushed).
  • 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