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>
211 lines
17 KiB
Markdown
211 lines
17 KiB
Markdown
# 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
|
||
|
||
🟢 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 — handshake `10.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 `75ea320` push). 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` уже резолвится 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 -->
|
||
|