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>
13 KiB
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.mdrev v3 (2026-05-25). - Overlay-репо:
victor/bookva-overlay+victor/slovo-overlay(compose'ы + config-templates). - Code-side done в master:
5e28fd1embed-api в web (server/api/*) + zod^4 fix.4a9cafcES endpoint per-tenant черезcustom-environment-variables.json.c1e58cfBookvaRedirectBanner — PWA handoff modal для user.id=1.88df172deploy.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.ymlbookva-mongo—mongo.compose.ymlbookva-es—elasticsearch.compose.yml(новый, в-stack ES 7.10.0)bookva-api—api.compose.ymlbookva-web—web.compose.ymlbookva-scheduler—scheduler.compose.ymlbookva-task-runner—task-runner.compose.ymlbookva-ozon-mcp—ozon-mcp.compose.ymlbookva-ops-mcp—ops-mcp.compose.ymlbookva-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 — 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-renamebooks→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:
-- В 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уже резолвится authoritativens1.reg.ru→89.253.255.133. User либо добавил A-record ранее, либо wildcard. Zero work needed. - 2026-05-26 — Step 1: Gitea reserves
GITEA_*prefix. Попытка PUTGITEA_TOKENsecret вернула 400 "invalid variable or secret name". Создан новый PAT scope=read:repositoryдля OpeItcLoc03 (UI generate, т.к.POST /users/.../tokensтребует basic auth, не token). Token в passgitea/victor-books-ci-bookva-overlay. Записан в victor/books secret под именемBOOKVA_OVERLAY_TOKEN. deploy.yml line 80 patched (secrets.GITEA_TOKEN→secrets.BOOKVA_OVERLAY_TOKEN) — commit2f7b539pushed. - 2026-05-26 — bookva-overlay templates stale + missing fields. При populate выяснилось что
config-templates/api/default.json+scheduler/default.jsonкороче prod (missingjobs[],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 commit5b173depushed. - 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. books75ea320pushed. Гонка 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 separatestateful-split-volume-copytask (under maintenance window). MinIO rename (books→slovo+cp slovo bookva) = separate consideration, требует books-api reconfig (новая bucket).
Completed steps
- Step 5 — DNS
bookseller.kzntsv.site→89.253.255.133(закрыто inspection, было pre-existing) - Step 1 — Gitea secret
BOOKVA_OVERLAY_TOKENсоздан в victor/books + deploy.ymlsecrets.GITEA_TOKEN→secrets.BOOKVA_OVERLAY_TOKEN(books2f7b539pushed). Token в passgitea/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
5b173depushed). - 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. books75ea320pushed. 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-copytask. 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/*объекты в newbookva/*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_URLenv в 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