Files
admin/.wiki/concepts/minio-split-cutover-runbook.md

279 lines
28 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.
---
title: MinIO split — cutover runbook (Track A books-vds 2020→2025 + Track B CMS-media → vds)
type: concept
tags: [minio, vds, books-vds, cutover, runbook, migration, snolla, s3, imgproxy, obsidian]
related: [[../entities/vds-kzntsv], [../entities/books-vds], [on-snolla-vds-deploy-runbook], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [minio-imgproxy-on-vds], [portainer-stack-management-vds]]
updated: 2026-07-30
---
# MinIO split — cutover runbook
> **Режим:** подготовка. **На железе — НИЧЕГО без команды user.** Все команды готовы к выполнению по «го».
> Owner: admin. Создано 2026-07-29. Перенесён из `scripts/minio-split-cutover/` 2026-07-30 (дисциплина: runbook'и → `.wiki/concepts/`).
## Goal
Разнести MinIO-контент на 2 сервера:
- **books-vds shared `minio`** (`minio.kzntsv.site`) → только slovo-бакеты books-приложений. Апгрейд 2020→2025.
- **books-vds `bookva-minio`** → bookva-бакеты (уже там с 2026-05). Апгрейд 2020→2025. Консоль наружу НЕ выставляем.
- **vds `minio`** (`minio.vds.kzntsv.site`, уже 2025-09-07) → CMS-медиа snolla-сайтов + artmone + obsidian + modules + modulair + maxmind + strapi.
- Креды **не трогаем** (один root-ключ на все инстансы; **доступ → `pass show minio-vds/full-env`**, поля `MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`). Креды НЕ в git.
## Два независимых трека
- **Track A — апгрейд books-vds minio (shared + bookva).** Не требует правок apps (transparent: hostname/creds/контент сохраняются). Можно делать отдельно, до/после Track B.
- **Track B — сплит CMS-медиа на vds.** Координированный cutover: миррор → DNS-flip imgproxy → snolla deploy. Требует гейтов.
## Progress (2026-07-30)
| Шаг | Статус | Примечание |
|---|---|---|
| B1 миррор CMS-медиа books-vds → vds minio | ✅ DONE | galleries/pilorama98/themes/assets смиррорены (свежим миррором поверх stale 2026-05) |
| B2 DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 | ✅ LIVE | imgproxy.kzntsv.site/<pilorama98-image> → 200 webp (vds imgproxy ← vds minio); CMS-сайты берут картинки через vds |
| **B3 on-snolla v2-пилот (S3-direct-path)** | ✅ LIVE 2026-07-30 | in-place bump стека 22 `473923e494db→07db7e3` (snolla 0.42.1 v2, config endpoint → minio.vds.kzntsv.site, без бампа package.json). Theme-assets `/themes/c406…/…` → 200 97324 B == live (aws-sdk v2 → vds minio 2025 — compat подтверждена, главный риск закрыт). Sitemap 3/3 parity, `/c` 404 parity. Rollback-тег `473923e494db` в registry. **Промежуточный** шаг — v2 совместим, но v3 — конечная цель (user 2026-07-30). |
| **B3v3 on-snolla v3-пилот** | ✅ LIVE 2026-07-30 | `@snollajs/snolla 0.43.2` published (verdaccio latest). Live MinIO verify ЗЕЛЁНЫЙ (snolla, оба конфига). **v3 cutover 2026-07-30T11:28Z**: bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (root package.json), commit `eb8c143` pushed, build на VDS → `registry.kzntsv.site/on-snolla:eb8c143`, Portainer PUT stack 22 env-preserving (8/8), prune:false pullImage:true, mem_limit 512m. Throwaway staging :5078 parity-gate GREEN (theme asset 200 97324 B byte-identical, sitemapindex+3 sub-sitemaps byte-identical, /c 404 parity). Live smoke GREEN: / 200, robots/sitemap/sub-sitemaps 200, /yandex 200, theme asset 200 97324 B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025). TLS не тронут (LE до Oct 18). Rollback = образ-тег `07db7e3` (v2, жив). |
| **B3v3 kupimknigi.spb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.0→0.43.2` + resolutions (commit `5364353`, apps/web + root). Build на VDS (archive → `~/build/kupimknigi`) → `registry.kzntsv.site/kupimknigi:5364353`(+latest). Bundles a83e2a9 endpoint fix (minio.vds.kzntsv.site) — был в репо, но в бегущем образе 9608ff6 (v2) его не было. Throwaway staging :5079 parity-gate GREEN: `/` byte-identical с real Host, theme asset `toolbox.css` 200 35799 B byte-identical (v3 aws-sdk v3 S3-direct read из vds MinIO 2025), robots 200, `/callback-order/`→301. Portainer PUT stack 21 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200, robots/sitemap 200, H1 «Скупка книг на дому в СПб…», theme asset 200 35799 B. TLS LE до Oct 3 (не тронут). **Rollback = `9608ff6`** (v2, жив в registry). |
| **B3v3 labtools.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `0dc0b4e`, apps/web + root). Endpoint fix `4f3314f` в master, но в бегущем образе `566d41c` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/labtools`) → `registry.kzntsv.site/labtools:0dc0b4e`(+latest). Throwaway staging :5080 parity-gate GREEN: `/` byte-identical (18594B), theme `lato.css` 200 9239B byte-identical (v3 S3-direct), sitemap 38 locs == live (0 diff, 0 bad), section order-parity byte-identical. Portainer PUT stack 17 env-preserving (8/8, node in-memory), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: menu pages 200, theme 200 9239B, TLS CN не тронут (LE до Sep 30). **Rollback = `566d41c`** (v2, жив). См. [[labtools-vds-deploy-runbook]] §0.43.2. |
| **B3v3 labtools.pro** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commits `12f8992` bump + `a106622` yarn.lock реген — обязателен, иначе `yarn install --immutable` падает YN0028). Endpoint fix `6c7ea88` в master, но в бегущем образе `0610432` (v2) его не было. Build на VDS (archive → `~/build/labtools-pro`) → `registry.kzntsv.site/labtools-pro:a106622`(+latest), digest `2152642c4b17`. Throwaway staging :5081 parity-gate GREEN: `/`+nav+catalog 4-байт diff = `og:url`/`og:image` request-host (benign), redirects IDENT, robots/sitemap IDENT (7 детей == live), theme `lato.css` 200 9994B md5-identical (v3 S3-direct), order-parity press-forms 12 изделий. Portainer PUT stack 19 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav+каталог+продукты 200, theme 200 9994B, TLS CN=labtools.pro не тронут (LE до Sep 30). **Rollback = `0610432`** (v2, жив). См. [[labtools.pro-vds-deploy-runbook]] §0.43.2. |
| **B3v3 emspb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `5afd8a1`, apps/web + root). Endpoint fix `218c3ae` в master, но в бегущем образе `95a5c42` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/emspb`) → `registry.kzntsv.site/emspb:5afd8a1`(+latest), digest `72bb59fc…`. Throwaway staging :5082 parity-gate GREEN: robots md5 identical (31B), sitemap 29 locs == live, 29/29 routes 200 (+2B host-context benign), theme assets 7/7 md5 IDENTICAL (v3 S3-direct: snolla-framework/bootstrap/toolbox/theme/slick + images/2.jpg + /images/3.jpg 115618B). Portainer PUT stack 18 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav 200, /services+/index.php 404, theme assets md5 LIVE==staging, redirects 301 byte-identical post-cutover, apex 200. TLS не тронут. **Rollback = `95a5c42`** (v2, жив). См. [[emspb-vds-deploy-runbook]] §0.43.2. |
| **B3v3 tandemmebel.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `9d51c09`, apps/web + root) + реген `yarn.lock` (single `@smithy/signature-v4@5.3.11`). Endpoint fix `f4ddce6` в master, но в бегущем образе `8df10ee` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/tandemmebel`) → `registry.kzntsv.site/tandemmebel:9d51c09`(+latest), digest `d14982c24dc7…`, 476MB (v2 8df10ee 583MB — v3 leaner). Throwaway staging :5020 parity-gate GREEN: robots md5 `a01e4c60…` identical (87B), sitemap 5 sub-sitemaps → **184 locs == live 184 IDENTICAL** (0 prod-only/new-only), self-consistency 183×200 + 1×404 (`/articles` parity benign), `/` +557B host-context benign, theme `Gotham-Pro.css` 200 4436B (v3 S3-direct). Portainer PUT stack 20 env-preserving (8/8, node in-memory put-stack — кириллица compose), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200 45742B, `/projects` 200, sitemap 184 locs, robots md5 identical, theme 200 4436B. TLS CN=tandemmebel.ru не тронут (LE до Oct 10). **Rollback = `8df10ee`** (v2, жив). См. [[tandemmebel-vds-deploy-runbook]] §0.43.2. **6/6 v3 сайтов GREEN — тираж v3 ЗАВЕРШЁН.** |
| Track A (books-vds minio 2020→2025) | ⏳ TODO | transparent, отдельно от Track B |
| B4 .NET catch-all админ s3 endpoint | ⏳ TODO | `C:\sites\snolla\Web.config` serviceURL → minio.vds.kzntsv.site |
| B5 Obsidian Remotely Save endpoint | ✅ DONE 2026-07-30 | endpoint flipped `minio.kzntsv.site``minio.vds.kzntsv.site` в vault `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64 обфускация), bucket `obsidian`, s3Region `us-east-1`, forcePathStyle. Verified деобфускацией config-блока. См. memory `obsidian-remotely-save-endpoint-after-mirror` (менять ТОЛЬКО после B1 миррора — что и было). |
**Решение user (2026-07-30):** сплит без бампа snolla-пакетов — сайты остаются на 0.42.1 + aws-sdk v2, только config endpoint. snolla-евый v3-трек (0.43.1/0.7.8/0.17.2) — HOLD, отдельная история (stostayer). Пилот on-snolla подтвердил: **v2 vs MinIO 2025 работает** (без @smithy pin) — SignatureDoesNotMatch нет.
**Разворот 2026-07-30T11:00Z:** v3 — конечная цель (user). v3 опубликована (см. §B3-v3). Промежуточный v2-пилот доказал совместимость; v3-пилот on-snolla — следующий шаг после live MinIO verify.
---
## §B3-v3 — v3-бамп (resolutions = optional determinism, НЕ mandatory)
`@snollajs/snolla 0.43.2` опубликована 2026-07-30 на verdaccio (latest): `@snollajs/liquid 0.11.0` + `@snollajs/data 0.15.0` + `@snollajs/core 0.26.4` (peerDep `@snollajs/data` widen `<0.15.0``<0.16.0`баг-фикс: кап не расширен в `21c73aa`) + `@snollajs/snolla 0.43.2` (@smithy pin + `@aws-sdk/client-s3 3.1005.0` exact baked в deps). Коммит snolla `a8b198d`. Тесты core 259/259, snolla 120/120.
### Live MinIO verify 2026-07-30 — ЗЕЛЁНЫЙ (оба конфига)
snolla гоняла clean-consumer install `@snollajs/snolla@0.43.2` против живого `minio.vds.kzntsv.site`:
- **Run A** (с resolutions, `@smithy/signature-v4` 5.3.11 на signing-path): LIST/READ-PARITY/PUT/GET/MULTIPART — все OK, EXIT 0.
- **Run B** (БЕЗ resolutions, `@smithy/signature-v4` **5.6.12** на signing-path): те же тесты — **все OK, EXIT 0**.
**Оба зелёных.** 5.6.12 НЕ даёт SignatureDoesNotMatch против vds MinIO 2025. Regression-гипотеза (v3 SigV4 vs MinIO) **не воспроизвелась end-to-end** — была halted-bisect гипотезой, зацементированной как «CONFIRMED» без live-теста (косяк коммуникации, исправлен 2026-07-30). См. admin memory `aws-sdk-v3-smithy-sigv4-minio-regression`.
### Resolutions = optional determinism, НЕ SigV4-необходимость
Dual-instance механика реальна (5.3.11 exact из snolla/core + 5.6.12 из `@aws-sdk/core` рядом, без resolutions), но 5.6.12 на signing-path **работает** против vds MinIO 2025. Поэтому:
- `resolutions`-блок в package.json сайта — **опциональный** determinism-hardening (зафиксировать @smithy от свежего re-resolve, version-stability), НЕ обязательный для SigV4.
- Бамп только версии `@snollajs/snolla` без resolutions — **работает** (verify доказал).
- Рекомендую всё же ставить resolutions (детерминизм, безобидно, 5.3.11 тоже зелёный) — но framing «упадёт без resolutions» **ложен**, убрать.
### Copy-paste resolutions-блок (optional, recommended для determinism)
```json
"resolutions": {
"@aws-sdk/client-s3": "3.1005.0",
"@aws-sdk/lib-storage": "3.1005.0",
"@smithy/signature-v4": "5.3.11",
"@smithy/node-http-handler": "4.4.14",
"@smithy/fetch-http-handler": "5.3.13",
"@smithy/protocol-http": "5.3.11",
"@smithy/middleware-endpoint": "4.4.23",
"@smithy/middleware-retry": "4.4.40"
}
```
### v3 bump procedure (per site, после live MinIO verify ⚪)
1. `package.json`: `@snollajs/snolla``0.43.2` + вставить resolutions-блок выше (optional но recommended).
2. `yarn install` (clean resolve — убедиться `npm ls @smithy/signature-v4` == single instance).
3. Build на VDS из site-репо (`deploy/Dockerfile`, build-arg VERDACCIO_TOKEN) → `registry.kzntsv.site/<site>-web:<sha>`.
4. Throwaway staging `:50XX` из env живого контейнера → parity-gate (theme asset 200 97324 B == live, sitemap 3/3, `/c` 404 parity).
5. Portainer PUT env-preserving (8 секретов), `prune:false, pullImage:true`, mem_limit 512m.
6. Live smoke (crawl menu-страниц + viewModel, не только `/`).
7. Rollback = образ-тег (текущий v2-тег).
### Breaking changes v3 (минимизированы back-compat шимом)
- `s3ForcePathStyle``forcePathStyle` — рантайм **НЕ breaking** (back-compat шим в storage.js v3 читает оба ключа). Config-правка recommended (forward-clean), не обязательна.
- `sslEnabled` (config) + `S3_SSL_ENABLED` (env) — удалены (unused), игнорируются если остались.
- `aws-sdk` / `s3fs` удалены из snolla deps (s3fs был unused). Consumer-код, импортивший их из snolla-deps напрямую (неофициальная поверхность) — сломается; по коду 6 сайтов такого нет.
- `populate-theme-store.mjs` мигрирован v2→v3 (script, не рантайм) — admin-side, рантайм не трогает.
---
## S3-клиент совместимость (audit — сделано)
| consumer | S3 client | версия | риск vs MinIO 2025 |
|---|---|---|---|
| books (api/web/...) | @aws-sdk/client-s3 (v3) | ^3.450.0 | ✅ низкий |
| modules-db | minio (minio-js) | ^8.0.7 | ✅ низкий |
| snolla (7 сайтов + framework) | **aws-sdk v2** | ^2.1257.0 | ⚠️ EOL, тест обязателен |
| modulair-rag | client inside lightrag:latest image | — | проверить (bucket пуст) |
| obsidian Remotely Save | bundled | 0.5.25 | ✅ низкий |
| .NET admin (AWSSDK) | AWSSDK.S3 | ? | проверить (region `local`) |
**snolla ДО кутовера** запускает локальный node-скрипт (aws-sdk v2) → `https://minio.vds.kzntsv.site`, bucket `galleries` (stale-миррор там есть): listObjectsV2 + getObject. 200 = ок. Падение = бамп v2→v3 в фреймворке до кутовера.
---
# Track A — апгрейд books-vds minio 2020→2025
> Все шаги — на books-vds (`root@89.253.255.133`, ключ `~/.ssh/id_ed25519_books_ops`).
> Transparent: НЕ меняем hostname/creds/имя-контейнера для apps. Только swap бэкенда под тем же именем.
> Креды для `mc alias` — из `pass show minio-vds/full-env` (`MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`).
## A1. Shared `minio` 2020→2025
Принцип: новый 2025-контейнер `minio-new` (новый data-dir), `mc mirror` slovo-бакетов, swap traefik route, retire old. Контейнер-имя `minio` + internal DNS `http://minio:9000` сохранить для imgproxy/зависимостей (или imgproxy на books-vds выводится из эксплуатации после Track B DNS-flip — тогда можно переименовать).
**Бакеты, что остаются на shared minio (slovo):** `books` (37442 obj, 1.2 GiB), `ozon` (0B), `manuals` (0B), `test`/`upload`/`imgproxytest`. + почистить bookva-дубли (см. A3).
```bash
# Креды из pass (НЕ хардкодить, НЕ коммитить):
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# 0. Стоп-условие: books apps живы на старом minio (baseline). Снимок логов.
ssh root@89.253.255.133 'docker logs --tail 50 books-api; docker exec minio sh -c "echo ok"'
# 1. Новый 2025 контейнер, новый data-dir, тот же network=proxy, креды из env
ssh root@89.253.255.133 'mkdir -p /usr/docker/minio-new/data'
# compose: image minio/minio:RELEASE.2025-09-07..., command server /data --console-address :9001,
# env MINIO_ROOT_USER/PASSWORD (== $MINIO_ROOT_USER/$MINIO_ROOT_PASSWORD), volume /usr/docker/minio-new/data:/data,
# network proxy, traefik labels minio-console.kzntsv.site→:9001 (новая консоль) — написать compose-файл
# (template ниже; НЕ поднимать до готовности миррора)
# 2. mc mirror slovo-бакетов old(2020) → new(2025)
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set old http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set new http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
for b in books ozon manuals test upload imgproxytest; do mc mb --ignore-existing new/$b; mc mirror --overwrite old/$b new/$b; done
'
# 3. Verify per-bucket object count old==new
# for b in ...; mc ls --recursive old/$b | wc -l ; mc ls --recursive new/$b | wc -l
# 4. Swap: traefik route minio.kzntsv.site → new:9000 (rule в dynamic config / compose labels new-контейнера)
# + minio-console.kzntsv.site → new:9001 (новая веб-консоль 2025; старый /minio/login на :9000 умрёт)
# 5. Smoke: books-api/web/task-runner пишут/читают S3. Picking-list report GET.
# 6. Retire old 2020 minio (docker stop minio; НЕ удалять data 48ч — rollback).
```
**Rollback:** traefik route → old minio:9000; old container ещё running 48ч.
## A2. `bookva-minio` 2020→2025
Контейнер `bookva-minio`, volume `bookva-minio-data` (37074 obj в бакете `books`). НЕ пуст (мигрирован ~2026-05). Console наружу НЕ выставляем.
```bash
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# 1. Новый 2025 контейнер bookva-minio-new (новый volume bookva-minio-data-new), тот же creds
# 2. mc mirror bookva-minio(2020):9001 → bookva-minio-new(2025):900X, бакет books (37074 obj)
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set bvold http://127.0.0.1:9001 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set bvnew http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc mb --ignore-existing bvnew/books; mc mirror --overwrite bvold/books bvnew/books
'
# 3. Verify count: mc ls --recursive bvold/books | wc -l == mc ls --recursive bvnew/books | wc -l (37074)
# 4. Swap: container name bookva-minio (чтобы bookva-api http://bookva-minio:9000 не менялся) → новый
# (down old, up new с именем bookva-minio). Или compose-имя сохранить.
# 5. Smoke: bookva-api report/picking-list GET.
# 6. Retire old (48ч rollback).
```
## A3. Почистить bookva-дубли из shared `books` (после A1, перед mirror-verify)
В shared `books` остались 95+4 bookva-файла от до-миграции (дубли того, что уже на bookva-minio). Bookva-unique ключи вычислить из bookva-db и удалить из shared `books`.
```bash
# Выгрузить bookva-unique s3-пути (на books-vds):
docker exec -i bookva-db mariadb -ubooks -pFNud4Um47y books -N -e \
"SELECT DISTINCT report_remote_path FROM fbs_picking_lists WHERE report_remote_path!=''" > /tmp/bv-paths.txt
# (плюс pdf_remote_path, и из других таблиц — receipts/reports/task-reports — полный набор S3-путей bookva)
# Пересечь с shared-minio books-бакетом; bookva-unique (не в slovo-db) → mc rm из shared books
```
**Гейт:** до удаления убедиться, что КАЖДЫЙ bookva-путь жив на bookva-minio (после A2). Иначе не удалять.
---
# Track B — сплит CMS-медиа на vds
## B0. vds backup (precondition)
`scripts/vds-backup-rsync-kreknin/run.sh` rsync sources — добавить `/opt/stacks/storage/minio-imgproxy/data` (и compose/nginx.conf для воспроизводимости):
```diff
/opt/stacks/owncloud \
+ /opt/stacks/storage/minio-imgproxy/data \
/etc/ssh \
```
+ обновить EMAIL_BODY components. На vds: `sudo systemctl restart cron` не нужен (cron читает файл). **Verify:** дождаться реального прогона (05:00) ИЛИ manual `sudo bash /opt/stacks/backup/scripts/run.sh` → ntfy push на `vds-backup` topic (phone-confirm, был открыт).
## B1. Миррор CMS-медиа books-vds → vds minio (свежий)
vds minio уже 2025-09-07. Stale-копия от 2026-05 — перезаписать свежим миррором.
```bash
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# На books-vds (источник) → vds minio (назначение), через mc. Creds одинаковые.
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set src http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set dst https://minio.vds.kzntsv.site "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --insecure >/dev/null
for b in galleries artmone themes assets pilorama98 maxmind obsidian strapi; do
mc mb --ignore-existing dst/$b
mc mirror --overwrite --preserve src/$b dst/$b --insecure
done
'
# variant-cache НЕ миррорим (кэш imgproxy, перестроится на vds).
# modulair / modules — ВНЕ СПЛИТА (user: забыть). Остаются на books-vds shared minio, потребители не трогаются.
```
**Verify (обязательно):** recursive-count src==dst по каждому бакету (mc ls --recursive | wc -l). Совпадение = ок.
- Ожидаемые объёмы: galleries 9984, artmone 16273, themes 10864, assets 4750, pilorama98 225, maxmind 2, obsidian 179, strapi 0. ~8.9 GiB.
## B2. DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 (reg.ru)
- В reg.ru `kzntsv.site` A-запись `imgproxy``89.253.255.94` (сейчас → books-vds 89.253.255.133).
- vds imgproxy-nginx traefik label: добавить `Host(imgproxy.kzntsv.site)` (сейчас serves `imgproxy.vds.kzntsv.site`).
- TTL дождаться (был 86400 — лучше заранее снизить).
- **Verify:** `curl -sI https://imgproxy.kzntsv.site/` → vds traefik; sample CMS image URL (signed) → 200.
## B3. snolla deploy (6 сайтов) — по "го", после B1+B2
Образы собирает admin на VDS (snolla MCP read-only, нет build-канала; либо user даёт канал). Registry = `registry.kzntsv.site` (НЕ stostayer).
```bash
# На vds, для каждого из 6 репо (on-snolla, kupimknigi, tandemmebel, labtools, labtools.pro, emspb):
cd /tmp && git clone git@git.kzntsv.site:victor/<site>.git && cd <site>
git pull # правка s3.endpoint уже в master (snolla запушил: 07db7e3, a83e2a9, 218c3ae, 4f3314f, 6c7ea88, f4ddce6)
docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<from pass vds-kzntsv/verdaccio-ci> \
-t registry.kzntsv.site/<site>-web:<sha> .
docker push registry.kzntsv.site/<site>-web:<sha>
```
Затем parity-gate (throwaway staging `:50XX`) + Portainer PUT env-preserving (по [[snolla-live-prod-inplace-image-bump]]). Smoke каждого сайта (crawl menu pages + viewModel, не только /).
## B4. .NET catch-all админ (воркстейшн) — s3 endpoint
`C:\sites\snolla\Web.config``<fileStorageClients>` serviceURL: `https://minio.kzntsv.site``https://minio.vds.kzntsv.site` (galleries/assets/themes). imgproxy НЕ трогать (хардкод `imgproxy.kzntsv.site` → DNS-flip решает). IIS auto-recycle по Web.config. Smoke: `/admin/account/login` → Asset Manager превью.
## B5. Obsidian Remotely Save — endpoint (ПОСЛЕ B1 миррора obsidian)
Гейт: бакет `obsidian` смиррорен на vds (B1). Менять ТОЛЬКО тогда.
- Obsidian → Settings → Remotely Save → S3 endpoint: `https://minio.kzntsv.site/``https://minio.vds.kzntsv.site/` (бакет `obsidian`, креды те же). ИЛИ ре-обфусцировать `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64) при ЗАКРЫТОМ Obsidian.
- Verify: sync run → no errors.
---
# P3 — cleanup / verify (после cutover)
- Retire books-vds 2020 minio containers (после 48ч rollback-окна): `docker stop minio` (old), `docker stop bookva-minio` (old). НЕ удалять data до подтверждения бэкапа.
- Retire books-vds imgproxy stack 29 (после DNS-flip — изображения идут через vds imgproxy). Проверить, что никто не ходит на books-vds imgproxy.
- Удалить с shared `books` bookva-дубли (A3).
- vds-backup: подтвердить что `/opt/stacks/storage/minio-imgproxy/data` попадает в nightly snapshot (B0 verify).
- Smoke: все 6 snolla-сайтов (crawl), books slovo reports, bookva reports, obsidian sync, modules-db.
---
# Открытые/verify (до кутовера)
1. **snolla compat-тест aws-sdk v2 vs vds minio** — snolla делает сейчас (без железа).
2. **.NET admin AWSSDK версия + region** — `local` работает с MinIO 2025? (stostayer нужен был `us-west-1`, но там minio настроен `us-west-1`; books-vds minio = `local`). Verify.
3. **modulair-rag lightrag:latest S3-клиент** — compat с MinIO 2025 (bucket пуст, низкий приоритет).
4. **artmone** — деплой неясен (контейнера на vds нет). Бакет миррорим (B1), но потребителя для endpoint-смены нет. Отдельный вопрос.
5. **bookva-minio расход 368 obj** (shared 37442 vs bookva-minio 37074) — проверить, не stranded ли bookva-файлы в shared (не попали в миррор 2026-05). До A3.