Files
admin/.wiki/concepts/minio-split-cutover-runbook.md
vitya 84c2d6e981 meta(handoff): MinIO-split B4 DONE — .NET catch-all админ Web.config endpoint → vds minio
Web.config 6/6 fileStorageClients endpoint minio.kzntsv.site → minio.vds.kzntsv.site
(AWSSDK 1.3.19, us-east-1, forcePathStyle). Pre-flip verify: MINIO_ROOT_USER ==
Web.config accessKey (reuse, no rotation); mc read galleries/assets/themes OK
(B1 mirror live). IIS auto-recycle; https://internal.snolla.com/admin/account/login
200 <title>SNOLLA</title>, login form renders (app boots clean). Asset Manager
preview (authenticated) — user-confirm pending. Backup: Web.config.b4.bak.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-30 19:33:28 +03:00

28 KiB
Raw Blame History

title, type, tags, related, updated
title type tags related updated
MinIO split — cutover runbook (Track A books-vds 2020→2025 + Track B CMS-media → vds) concept
minio
vds
books-vds
cutover
runbook
migration
snolla
s3
imgproxy
obsidian
../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
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/ → 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 DONE 2026-07-30 C:\sites\snolla\Web.config <fileStorageClients> 6/6 endpoint minio.kzntsv.siteminio.vds.kzntsv.site (galleries/assets/themes×4). AWSSDK 1.3.19 (legacy monolith), region us-east-1, forcePathStyle. Pre-flip verify: MINIO_ROOT_USER==Web.config accessKey AKIAJ2YJP72W6ZHCRE6Q (reuse, no rotation) → mc alias Web.config-кредами → minio.vds.kzntsv.site → galleries/assets/themes все читаются (B1 mirror жив). IIS auto-recycle по Web.config. Smoke: https://internal.snolla.com/admin/account/login → 200 <title>SNOLLA</title>, login-форма рендерится (app boots clean, no config error). Asset Manager превью (authenticated) — user-confirm pending. Backup: Web.config.b4.bak.
B5 Obsidian Remotely Save endpoint DONE 2026-07-30 endpoint flipped minio.kzntsv.siteminio.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» ложен, убрать.
"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/snolla0.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 шимом)

  • s3ForcePathStyleforcePathStyle — рантайм НЕ 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).

# Креды из 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 наружу НЕ выставляем.

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.

# Выгрузить 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 для воспроизводимости):

   /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 — перезаписать свежим миррором.

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-запись imgproxy89.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).

# На 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.sitehttps://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 версия + regionlocal работает с 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.