Extension в той же сессии после 6/7 closure: - bookva-minio container (stack 49) — отдельный MinIO instance вместо bucket-split в shared. minio:RELEASE.2020-07-13 (parity books-minio), bookva-minio-data (1.2G cp -a books bucket), endpoint http://bookva-minio:9000 для internal apps, port-bind 9001 для external. - bookva-db PUT — добавлен port 33306:3306 для external MySQL access. - bookva-overlay templates: s3.endpoint → http://bookva-minio:9000, bucket=books (no override). - Step 6 descoped — PWA redirect modal для одного user.id=1 = overhead vs manual heads-up учредителю. External access verified: - mysql -h 89.253.255.133 -P 33306 -uroot -p... (MariaDB 10.6.26 handshake) - http://89.253.255.133:9001 (S3 API, 403 anon = service alive) Traefik HTTPS labels для bookva-minio.kzntsv.site добавлены (activate когда DNS A-record → 89.253.255.133 в reg.ru). 7/7 done (Step 6 descoped) + bookva-minio extension. Cutover task будет re-start 6 stopped stacks + Mongo agenda cleanup + ES snapshot/restore + books-api shutdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
17 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
🟢 closed 2026-05-26 — 7/7 (Step 6 descoped) + extension: bookva-minio container + external port-bind для bookva-db/bookva-minio.
Post-closure follow-up в той же сессии
- bookva-minio container added (user request — separate MinIO instance вместо bucket-split в shared MinIO). Image
minio:RELEASE.2020-07-13(parity с books-minio), volume bookva-minio-data (1.2G — cp -a/usr/docker/minio/data/books), Portainer stack ID 49. Same MINIO creds для zero-config-change. Bookva apps reach черезhttp://bookva-minio:9000(internal). bookva-overlay templates updated (s3.endpoint, bucket=books). - External access для bookva-db + bookva-minio: port-bind на ALT портах (books-db занимает 3306, books-minio занимает 9000).
bookva-db→89.253.255.133:33306(MariaDB). Verified — handshake10.6.26-MariaDB-ubu2204✓.bookva-minio→http://89.253.255.133:9001(S3 API). Verified — 403 anon (sig required) = service alive ✓.- Traefik HTTPS labels добавлены для
bookva-minio.kzntsv.site(активируется когда DNS A-record добавлен в reg.ru).
- Step 6 descoped — PWA redirect modal для одного user'а (id=1, Bookva founder) = over-engineering. Substitute = manual heads-up учредителю Bookva перед cutover. Trade-off acceptable (краткое недоумение «где Bookva?» если откроет старый bookmark post-cutover).
Closure note
Scope полностью выполнен per acceptance кроме Step 6 (delayed by design) и одной дискретной деференции (bookva-ozon-mcp stack — image отсутствует в registry, требует build в victor/books). Все остальные шаги closed:
- Step 1 ✅ 7 Gitea secrets созданы (BOOKVA_OVERLAY_TOKEN scope=read:repository, HEALTHCHECK_BOOKVA_{API,WEB}URL, PORTAINER_STACK_ID_BOOKVA{API,WEB,SCHEDULER,OPS_MCP}).
- Step 2 ✅ 9/10 Portainer stacks (ozon-mcp deferred). User-facing stopped pre-cutover (Status=2), infra (db/mongo/es) активны.
- Step 3 ✅ 6 volumes + 4 populated из corrected overlay templates. bookva-db-data (4.7G), bookva-mongo-data (517M) cp -a с downtimes 59s + 5s. MinIO bucket copy deferred (cutover-time через
mc cp books bookva). - Step 4 ✅ bookva-db login-gate
UPDATE users SET password=UUID() WHERE id_user NOT IN (1,2)— 3 users (id≥3) scrambled, books-db verified untouched. - Step 5 ✅ DNS bookseller.kzntsv.site pre-existed (closed by inspection).
- Step 6 🔵 delayed — выставить
NUXT_PUBLIC_BOOKVA_REDIRECT_URLза 1-2 нед до cutover. - Step 7 ✅ books-web embed-api live (Portainer stack 24 PUT + books
75ea320push). bookva.kzntsv.site/api/* via embed. books-api stack fallback 24-48ч soak до stop micro-task'и.
Discovered + fixed cross-repo (bookva-overlay 4 commits, books 2 commits):
- bookva-overlay
5b173de— config-templates align prod shape + design v3 hostname - bookva-overlay
264373d— .env.example design v3 hostnames - bookva-overlay
23eb374— db.compose.yml mariadb:10.11 → 10.6 (match books-db для cp -a) - bookva-overlay
93c2ea2— mongo.compose.yml mongo:7 → 4.2 (WiredTiger format compat) - bookva-overlay
2b20a3e— api.compose.yml remove traefik (internal-only per v3) - bookva-overlay
8bffd68— depends_on dropped (per-stack model) - books
2f7b539— secrets.GITEA_TOKEN → BOOKVA_OVERLAY_TOKEN (Gitea reserved prefix) - books
75ea320— deploy/web.compose.yml embed-api refs (NUXT_AUTH_TOKEN + NUXT_JWT_SECRET_KEY)
Workflow E2E verified: dispatch tenant=bookva, all 4 svc deploy gracefully — BOOKVA_OVERLAY_TOKEN clone bookva-overlay OK, скип per-svc если stack_id отсутствовал (до записи).
Where I stopped
closed — handoff to bookva-tenant-cutover task (когда user скажет «давай поднимать Bookva»). Cutover task должна: re-start stopped Portainer stacks (api/web/scheduler/task-runner/ops-mcp/ntfy), mc cp books bookva MinIO bucket, ES snapshot+restore from shared kzntsv → bookva-es (Phase 2), smoke с двух сетей, books-api stack stop после 24-48ч soak post-Step-7.
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