Files
admin/.wiki/concepts/tandemmebel-vds-deploy-runbook.md
vitya 69d3d0dd3f feat(tandemmebel): v3 cutover LIVE — snolla 0.42.1→0.43.2 (aws-sdk v2→v3)
6-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro, emspb).
6/6 v3 сайтов GREEN — тираж v3 ЗАВЕРШЁН.

- Bump: @snollajs/snolla 0.42.1→0.43.2 (apps/web) + resolutions verbatim labtools.ru
  (@aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11 — optional determinism) + реген
  yarn.lock (single @smithy/signature-v4@5.3.11 deduped). Commit 9d51c09 в victor/tandemmebel.ru.
  Заодно нёс на live s3.endpoint fix f4ddce6 (→ minio.vds.kzntsv.site), был в master с 2026-07-29.
- Build на VDS: registry.kzntsv.site/tandemmebel:9d51c09, digest d14982c24dc7, 476MB (v2 583MB), EXIT=0.
- Staging :5020 parity-gate GREEN: robots md5 identical, sitemap 184=184 locs IDENTICAL,
  self-consistency 183×200 + 1×404 (/articles parity), Gotham-Pro.css 200 4436B (v3 S3-direct).
- Portainer PUT stack 20 (node in-memory, кириллица compose), env 8/8, pullImage:true, mem_limit 512m. PUT 200.
- Live smoke GREEN: / 200 45742B, /projects 200, sitemap 184, theme 200 4436B. TLS CN не тронут (LE Oct 10).
- Rollback = 8df10ee (v2, жив в registry).

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

18 KiB
Raw Blame History

title: tandemmebel.ru snolla-app — VDS deploy runbook (cutover + 0.42.1 bumps + 0.43.2 v3 cutover) type: concept tags: [tandemmebel, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, blog-portfolio, v3, aws-sdk] related: ../entities/vds-kzntsv, ../entities/ruvds-iis-host, portainer-stack-management-vds, minio-imgproxy-on-vds, snolla-live-prod-inplace-image-bump, labtools-vds-deploy-runbook, labtools.pro-vds-deploy-runbook, emspb-vds-deploy-runbook, minio-split-cutover-runbook updated: 2026-07-30

tandemmebel.ru → VDS deploy runbook

Вынос tandemmebel.ru (snolla-приложение, @snollajs/snolla 0.42.1, server-side Liquid, блог-портфолио БЕЗ e-commerce каталога) с ../entities/ruvds-iis-host (catch-all CMS, 80.64.31.36) в отдельный docker-контейнер на ../entities/vds-kzntsv (89.253.255.94), за traefik. Модель — snolla-app: читает БД-контент MoreThenCms (mssql.kzntsv.site)

  • ассеты из MinIO (minio.kzntsv.site). In-process sharp (resize+data-driven watermark), delivery=serve-bytes, variant-cache бакет в MinIO. imgproxy ИЗ ПУТИ tandem УБРАН (сырой /imgproxy → 404 норма).

Паттерн = emspb-vds-deploy-runbook / labtools.pro-vds-deploy-runbook. Отличие: блог-портфолио (не каталожный → order-парити секций НЕ применимо — см. 0.42.1 bump ниже). Culture ru-RU.

Артефакты

  • Код: victor/tandemmebel.ru @ 0cd9351 (apps/web, snolla 0.42.1 / core 0.24.1 / liquid 0.10.2 / data 0.14.1). deploy/Dockerfile (multi-stage node:22-slim, yarn install --immutable, non-root uid node, healthcheck /robots.txt).
  • Образ: registry.kzntsv.site/tandemmebel:0cd9351 (+:latest). Имя tandemmebel (= имя стека/контейнера). Собран НА VDS (обход traefik-499). digest sha256:f29c187fe6114d6ab3926132f4b3eb09df9b606d448cd8fd873780687cb46b8f.
  • Стек Portainer: tandemmebel (Id 20, endpoint 1). Source-of-truth compose: admin/host-stacks/vds-kzntsv/tandemmebel.compose.yml.
  • siteId: 78080707-F6E0-4330-BA30-7922354C2CEF (non-secret, в production.jsonНЕ env).
  • siteUrl: https://www.tandemmebel.ruproduction.json).
  • Runtime env (8 секретов): DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD — Portainer stack env (переиспользованы verbatim из labtools стека 17). НЕ в образе, НЕ в git.
  • mem_limit: 512m (guardrail; см. portainer-stack-management-vds § Convention).

Cutover 2026-07-12 (первый вынос на VDS — DNS-gated)

Последовательность (LE-критичность соблюдена) — см. NEXT_SESSION.md:

  1. Verify авторит. NSnslookup tandemmebel.ru ns1/ns2.reg.ru = оба 89.253.255.94 (apex + www), не только резолвер.
  2. GET стек 20 — образ tandemmebel:ed96b18 (0.42.0, staging cutover-подготовлен), env 8/8, rule staging Host(tandemmebel.vds.kzntsv.site).
  3. PUT стек 20Host(tandemmebel.ru) || Host(www.tandemmebel.ru), env-preserving, prune:false, pullImage:false. Python urllib UTF-8 (НЕ PS Invoke-RestMethod — gotcha кириллицы, см. portainer-stack-management-vds).
  4. Poll — контейнер healthy, LE-серт issued on first hit: CN=tandemmebel.ru, SAN оба, issuer YR2, until 2026-10-10.
  5. Live-smoke С VDS — GREEN.
  6. Compose source-of-truth + борд обновлены.

Латентный прод-баг починен cutover'ом: Gotham-Pro.css был 0B на RUVDS → теперь 200/4436B.

In-place bump 2026-07-12: 0.42.0 → 0.42.1 (order-tag Drop-field fix)

По рецепту snolla-live-prod-inplace-image-bump (поверх cutover'а того же дня).

Консюмер-бамп (решён оператором, без dev-source)

Тот же блокер-паттерн 2026-07-04 (заявленная snolla-версия ≠ консюмер-пин) — на этот раз решён самим оператором:

  • apps/web/package.json:120.42.00.42.1.
  • yarn install в корне монорепы → обновил корневой yarn.lock (snolla 0.42.1 / core 0.24.1 / liquid 0.10.2 / data 0.14.1). Dockerfile yarn install --immutable — lock обязан совпадать с пином.
  • commit 0cd9351 + push origin (git.kzntsv.site/victor/tandemmebel), подтверждён ls-remote.
  • Тег образа = sha монорепы 0cd9351.

Build → staging → gate → swap

  1. Build на VDS (archive 0cd9351~/build/tandemmebeldocker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN -t .../tandemmebel:0cd9351 -t .../tandemmebel:latest . && push). Guard: config/default.json ABSENT в архиве (.dockerignore), пин = 0.42.1.
  2. Throwaway-staging :5020 из env живого контейнера (docker inspect tandemmebel.staging.env, фильтр ^(HOSTNAME|PATH|NODE_VERSION|YARN_VERSION|HOME)=). docker run -d --name tandemmebel-staging --env-file .staging.env -p 127.0.0.1:5020:5000 .../tandemmebel:0cd9351 → healthy. robots.txt+/+sitemap 200.
  3. Completeness-gate С VDS — sitemapindex разворот (expand()), два чека:
    • NEW==PROD locs: 184 = 184 IDENTICAL (вкл. 0.42.x sitemap-реструктуризацию — parity сохранился).
    • self-consistency + content-not-lost (locs идентичны → один проход): 183×2xx + 1×404.
    • Единственный 404 /articlesидентичен прод-оракулу (404 и на live https://www.tandemmebel.ru/articles) → benign (пустая секция-без-индекса, не регрессия). Tandemmebel НЕ каталожный → order-парити не применимо (0.42.1 order-fix инертен на этой теме — как archive-роуты на 0.40→0.42).
    • GREEN = 0 регрессий в 404/5xx.
  4. Боевой swap — operator-gated, node put-stack.js (env-preserving: 8/8 name+value, prune:false, pullImage:true): META=st20.json node put-stack.js 20 1 ed96b18 0cd9351 → PUT 200. Контейнер 0cd9351+healthy за ~8s.
  5. Live-smokehttps://www.tandemmebel.ru/{robots.txt,/,/projects,/sitemap.xml} = 200. TLS-серт CN=tandemmebel.ru не дёрнут (in-place swap серт не трогает). docker rm -f tandemmebel-staging.

Rollback

  • Тег ed96b18 (0.42.0) в registry (+ b02ca18, 0.16.2-staging) → PUT стека 20 назад.
  • ИЛИ revert DNS → 80.64.31.36 (RUVDS IIS жив, не тронут — rollback всех мигрированных).

In-place bump 2026-07-13: 0.42.1 → 0.42.1 (sha 0cd9351 → 8df10ee, template-only)

Ops-handoff от tandemmebel-сессии (inbox-запрос): убрать FB/Twitter/Google+ share-кнопки (экстремистистская символика РФ), оставить ВК+Одноклассники. Коммит 8df10ee в victor/tandemmebel.ru master, меняет только apps/web/views/social_buttons.liquid (1 file, 12 deletions), snolla pin 0.42.1 НЕ менялся → in-place bump на той же 0.42.1 (не консюмер-бамп).

Предсборочная верификация (поймала расхождение)

Записка от tandemmebel-сессии утверждала: «сейчас живой на стеке 20 — ed96b18/0.42.0». Фактически на проде крутился 0cd9351/0.42.1 (in-place bump 0.42.0→0.42.1 был 2026-07-12). Без проверки это не повлияло бы (см. ниже), но вслепую строить нельзя. Проверки С VDS + gitea API (токен gitea/admin-token):

  • docker inspect tandemmebelregistry.kzntsv.site/tandemmebel:0cd9351, healthy, started 2026-07-12.
  • gitea compare/0cd9351...8df10eetotal_commits:1: фикс ровно один коммит поверх 0.42.1 (правильная база).
  • git show 8df10ee:apps/web/package.json@snollajs/snolla: 0.42.1 (pin не менялся); yarn.lock snolla 0.42.1 / core 0.24.1 / liquid 0.10.2 / data 0.14.1 — идентично live 0cd9351.
  • git show 8df10ee --stat → только apps/web/views/social_buttons.liquid (modified).
  • 8df10ee full sha 8df10eedcba9979ac91dce2025b2ff827d60e808, master HEAD = 8df10ee.

Если бы 8df10ee был на базе 0.42.0 (172 locs) → completeness-gate поймал бы регрессию 184→172. Он на 0.42.1 → parity 184=184.

Build → staging → gate → swap

  1. Build на VDS из чистого архива 8df10ee (gitea API /archive/<fullsha>.tar.gz, token- auth oauth2:- НЕ работает, Authorization: token-header на API endpoints работает; git clone по HTTPS фейлится даже с oauth2:token — gitea admin-token = API-only, не git-transport). Extract --strip-components=1 в ~/build/tandemmebel-8df10ee. docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<books-ci JWT> -t registry.kzntsv.site/tandemmebel:8df10ee -t .../tandemmebel:latest . → digest sha256:30a7e5ab82f2bf371042f1b5fa0cd7ac5ceda71379d1da59e148dee8166bd560, layer-cache hit (package.json+ yarn.lock идентичны 0cd9351), push OK.
  2. Throwaway-staging :5020 из env живого контейнера (docker inspect tandemmebel.staging.env, фильтр ^(HOSTNAME|PATH|NODE_VERSION|YARN_VERSION|HOME|PORT|NODE_ENV)=). docker run -d --name tandemmebel-staging --env-file .staging.env -p 127.0.0.1:5020:5000 .../tandemmebel:8df10ee → healthy 8s.
  3. Completeness-gate С VDS — sitemapindex разворот. Важно: staging sitemapindex содержит АБСОЛЮТНЫЕ prod-URLs (siteUrl=prod в production.json) → sub-sitemaps надо фетчить со staging ПО ПУТЯМ (http://127.0.0.1:5020/sitemap-pages-1.xml), а не следовать абсолютным URL (иначе сравнишь PROD==PROD).
    • NEW==PROD locs: 184 = 184 IDENTICAL (0 prod-only, 0 new-only).
    • self-consistency: 183×200 + 1×404 (/articles benign parity staging==prod).
    • share-block: staging vk+ok / fb-tw-gp=0 vs prod-before (0cd915 live ещё имел) vk+ok+fb+tw+gp — фикс убирает ровно лишнее, остальное не трогает.
    • GREEN = 0 регрессий.
  4. Боевой swap — Portainer JWT auth (POST /api/auth vitya/Pryakhin9-VDS-2026; API-key ptr_* даёт 401, workaround = JWT). GET /api/stacks/20 (env 8/8) + /api/stacks/20/file (compose). Python urllib UTF-8 (НЕ PS Invoke-RestMethod — gotcha кириллицы #9). Замена tandemmebel:0cd93518df10ee в StackFileContent (ровно 1 замена, traefik rule line не тронута). PUT /api/stacks/20?endpointId=1 {stackFileContent, env: <8 preserved>, prune:false, pullImage:true} → HTTP 200. Контейнер 8df10ee+healthy ~8s.
  5. Live-smoke С VDS — robots///sitemap 200, /articles 404 parity, sitemap 184 locs, 4 share-block страницы (project-post ×2 /projects/2011/..., /furniture/bedrooms, /furniture/kitchens/classic) все 200 → vk+ok на месте, fb/tw/gp=0. TLS-серт CN=tandemmebel.ru (LE YR2, until 2026-10-10) не дёрнут. docker rm -f tandemmebel-staging.

Rollback (8df10ee)

  • Образный (предпочт): PUT стека 20 назад на 0cd9351 (0.42.1, {% order %} fix) — template-only фикс, откатится чисто. Тег в registry.
  • ed96b18 (0.42.0) / b02ca18 — в registry (ed96b18 стёрт с VDS при disk-cleanup 2026-07-13, pullImage:true дотянет из registry).
  • DNS: revert reg.ru → 80.64.31.36 (RUVDS IIS жив, не тронут).

Hygiene-заметка

Portainer stack 20 file несёт устаревшие STAGING-комменты (строки 16-17 «Домен: STAGING», 53 «STAGING-RULE. Cutover: после flip DNS сменить на Host(...)») — но сама traefik...rule строка (55) уже LIVE (Host(tandemmebel.ru) || Host(www.tandemmebel.ru), совпадает с label'ом живого контейнера). Косметика, функционально нейтральна. При деплое 8df10ee обновлён только image-line коммент; шапочные STAGING-комменты не трогались (минимальное изменение). Source-of-truth compose host-stacks/vds-kzntsv/tandemmebel.compose.yml актуализирован полностью (8df10ee + LIVE-комменты + история bump'ов). При следующем full-sync можно выровнять Portainer file под git-source.

Гочи (специфичные)

  • Блокер-паттерн консюмер-пина — snolla-репо релизит версию, но консюмер-пин в tandemmebel-репо отстаёт. На 0.42.0 (2026-07-04) ждали dev-source; на 0.42.1 (2026-07-12) оператор сделал сам. Всегда byte-verify пин+yarn.lock+ls-remote перед build.
  • yarn install --immutable в Dockerfile — корневой yarn.lock обязан быть снапшотом целевого пина, иначе build падает на checksum-mismatch. Бамп = package.json + yarn install (обновляет lock) + commit обоих.
  • 0.42.x sitemap-реструктуризация — parity сверять по page-locs (развёрнутый sitemapindex), НЕ по именам под-sitemap'ов. 184 тут (было 172 на 0.42.0 — выросло, но NEW==PROD).
  • /articles 404 benign — loc из sitemap отдаёт 404, если прод-оракул идентичен → не дефект (см. рецепт § Completeness-gate).
  • Staging-порт 5020 — проверять docker ps | grep 127.0.0.1:50 перед bind (stale throwaway от прошлого тиража).
  • Smoke гнать С VDS (воркстейшн ловит LAN-DNS-перехват прод-доменов — см. memory workstation-lan-dns-serves-local-cms-copy).
  • build-secret (VERDACCIO_TOKEN в ARG/ENV build-стадии) — известный follow-up, НЕ блокер (runtime-стадия отдельная, токена в финальном образе нет).

Тираж snolla 0.42.1 — статус

Все 5 snolla-сайтов на VDS:

  • labtools.ru (17), emspb.ru (18), labtools.pro (19) — 0.42.1 in-place bump 2026-07-05.
  • kupimknigi (21), tandemmebel (20) — 0.42.0 cutover, tandemmebel in-place bump до 0.42.1 2026-07-12. (kupimknigi остался на 0.42.0 по решению оператора — см. NEXT_SESSION.md.)

0.43.2 v3 cutover (aws-sdk v2→v3) — ВЫПОЛНЕН 2026-07-30

6-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro, emspb — 5 GREEN). In-place bump стека 20 (DNS на VDS с 2026-07-12, не трогали). Рецепт — minio-split-cutover-runbook §B3-v3. 6/6 v3 сайтов GREEN = тираж завершён.

  • Bump: @snollajs/snolla 0.42.1→0.43.2 в apps/web/package.json (commit 9d51c09) + resolutions-блок в root package.json (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11 — optional determinism, live verify доказал 5.6.12 работает vs vds MinIO 2025) + реген yarn.lock (single @smithy/signature-v4@5.3.11 deduped, no dual-instance). Pushed victor/tandemmebel.ru master f4ddce6..9d51c09. Endpoint fix f4ddce6 (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе 8df10ee (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
  • Build на VDS: git archive 9d51c09~/build/tandemmebeldocker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKENregistry.kzntsv.site/tandemmebel:9d51c09(+latest), digest sha256:d14982c24dc7c61a334c8973e6f1c48147c7d3cbcc772b28af6c629431947b16, 476MB (v2 8df10ee был 583MB — v3 leaner), EXIT=0. yarn install --immutable в Docker прошёл (yarn.lock matches).
  • Staging :5020 parity-gate GREEN (throwaway контейнер из env живого стека 20, 8 секретов, --network proxy):
    • robots.txt md5 a01e4c60… byte-identical (87 B).
    • sitemap index→5 sub-sitemaps → 184 locs == live 184 IDENTICAL (0 prod-only / 0 new-only — regression нет).
    • self-consistency 183×200 + 1×404 (/articles — prod=404 parity benign, не регрессия). 0×301/5xx.
    • / 200 staging 45738B vs prod 45181B (+557B = og:url/og:image request-host context — benign, byte-identical post-cutover).
    • theme Gotham-Pro.css 200 4436B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025 — латентный прод-баг 0B→4436B починен ещё cutover'ом 2026-07-12, сохранён).
  • Portainer PUT stack 20 LIVE: node in-memory put-stack (НЕ PS — кириллица compose; Node 22 global fetch, GET /stacks/20 для Env + /stacks/20/file для StackFileContent), env 8/8 preserved, prune:false pullImage:true mem_limit 512m. PUT 200. Контейнер 9d51c09 healthy ~9s.
  • Live smoke GREEN (via traefik): / 200 45742B, /projects 200 90758B, /sitemap.xml 200 (184 locs == parity), robots md5 a01e4c60…, /articles 404 parity, theme Gotham-Pro.css 200 4436B (v3 S3-direct). TLS CN=tandemmebel.ru, expire Oct 10 2026 (не тронут — in-place swap). Лог чист (tedious deprecation + tandemmebel.ru (snolla) listening).
  • Rollback: tandemmebel:8df10ee (0.42.1 v2, template-only share-buttons, жив в registry) — Portainer PUT назад / compose revert. 0cd9351 (0.42.1) + ed96b18 (0.42.0) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний.
  • Compose (admin source-of-truth) синхронно закоммичен 8df10ee9d51c09. 6/6 v3 сайтов GREEN — тираж v3 завершён.