5-й сайт тиража v3 (5/6 GREEN). In-place bump стека 18 (DNS на VDS с 2026-07-02). Commit victor/emspb.ru 5afd8a1 (bump + resolutions + yarn.lock реген). Build на VDS → registry.kzntsv.site/emspb:5afd8a1 digest 72bb59fc. Staging :5082 parity-gate GREEN (29/29 routes 200, theme assets 7/7 md5 IDENTICAL — v3 S3-direct read из vds MinIO 2025). Portainer PUT stack 18 env-preserving (8/8, node in-memory put-stack), prune:false pullImage:true. Live smoke GREEN. Rollback = 95a5c42 (v2). - emspb.compose.yml: 95a5c42→5afd8a1 - emspb-vds-deploy-runbook: §0.43.2 - minio-split-cutover-runbook: progress-table emspb ✅, остался tandemmebel - NEXT_SESSION: 5/6 GREEN, остался tandemmebel (стек 20, был BLOCKED на sharp-staging дефекте — проверить перед бампом) Остался 1 сайт: tandemmebel. Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
emspb.ru → VDS deploy runbook
Вынос emspb.ru (snolla-приложение, @snollajs/snolla 0.28.4, server-side Liquid) с ../entities/ruvds-iis-host
(catch-all CMS) в отдельный docker-контейнер на ../entities/vds-kzntsv (89.253.255.94), за traefik.
Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (mssql.kzntsv.site)
- ассеты из MinIO (
minio.kzntsv.site) через imgproxy. Зеркало labtools-vds-deploy-runbook — тот же паттерн.
Артефакты
- Код:
victor/emspb.ru@b6e361a(apps/web, ре-ревью PASS, 29/29 parity, snolla 0.28.4 final).deploy/Dockerfile(multi-stage node:22-slim, non-root, healthcheck/robots.txt). - Образ:
registry.kzntsv.site/emspb:b6e361a(+:latest). Собран НА VDS (обход traefik-499). digestsha256:8f5ba02651b71f340fa9bc3b079fd2853b01b1461e6c479fb2cf788691d31a74. - Стек Portainer:
emspb(Id 18, endpoint 1). Source-of-truth compose:admin/host-stacks/vds-kzntsv/emspb.compose.yml. - siteId:
96EBC481-D26A-47BE-B660-13D49E7D0A61, activeThemeDD8D6F7A-CEDC-4D5A-8BFB-E5F9BE9DC035(theme store MinIOthemes/dd8d6f7acedc4d5a8bfbe5f9be9dc035/). Non-secret — вproduction.json, НЕ env.
Сборка образа (на VDS)
# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
git -C ~/projects/emspb.ru archive --format=tar b6e361a \
| ssh vitya@89.253.255.94 'rm -rf ~/build/emspb && mkdir -p ~/build/emspb && tar -x -C ~/build/emspb'
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), передаётся через env (не в argv), push из VDS
ssh vitya@89.253.255.94 "cd ~/build/emspb && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/emspb:b6e361a -t registry.kzntsv.site/emspb:latest . \
&& docker push registry.kzntsv.site/emspb:b6e361a && docker push registry.kzntsv.site/emspb:latest"
- VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only ×2 — ожидаемо).
config/default.json(dev-секреты) исключён.dockerignore— в образе толькоproduction.json+custom-environment-variables.json(guard проверен на архиве: default.json ABSENT).
Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
Маппинг env→config: apps/web/config/custom-environment-variables.json. Non-secret (host/siteId/endpoints) — в production.json.
Значения идентичны labtools (все snolla-тенанты читают одну MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив переиспользован verbatim из labtools stack (Id 17) через Portainer API.
| ENV | Значение | Источник |
|---|---|---|
DB_USER |
snolla |
web.config боя MoreThenCmsEntities |
DB_PASSWORD |
⟨секрет⟩ | web.config боя MoreThenCmsEntities (User Id=snolla) |
IMGPROXY_KEY |
⟨128hex⟩ | pass minio-vds/full-env (== сервер imgproxy books-vds — HMAC совпадает) |
IMGPROXY_SALT |
⟨128hex⟩ | pass minio-vds/full-env |
S3_ACCESS_KEY_ID |
⟨секрет⟩ | pass minio-vds/full-env (MINIO_ROOT_USER) |
S3_SECRET_ACCESS_KEY |
⟨секрет⟩ | pass minio-vds/full-env (MINIO_ROOT_PASSWORD) |
SMTP_USER |
noreply@snolla.com |
pass snolla-smtp/full-env |
SMTP_PASSWORD |
⟨секрет⟩ | pass snolla-smtp/full-env (SMTP_PASS) |
PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck /robots.txt на $PORT). traefik service-port = 5000.
Создание стека (Portainer API, на VDS — bash+curl+jq, обходит PS-кириллица-гочу)
# JWT (pass vds-kzntsv/full-env PORTAINER_PASS); env переиспользован из labtools stack 17
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth -d '{"username":"vitya","password":"<pw>"}' | jq -r .jwt)
ENV=$(curl -ksS -H "Authorization: Bearer $JWT" https://portainer.vds.kzntsv.site/api/stacks/17 | jq '.Env')
COMPOSE=$(cat ~/build/emspb.compose.yml)
PAYLOAD=$(jq -n --arg name emspb --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
curl -ksS -X POST "https://portainer.vds.kzntsv.site/api/stacks/create/standalone/string?endpointId=1" \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" --data "$PAYLOAD" # → Id=18
Предусловие pull: registry.kzntsv.site зарегистрирован в Portainer как Custom registry Id 1 (иначе no basic auth).
Runtime egress (наружу из proxy-сети)
mssql.kzntsv.site:1433 (БД-контент, обязателен) · minio.kzntsv.site:443 (ассеты) · imgproxy.kzntsv.site:443 · smtp.yandex.ru:465 (формы).
Staging smoke (2026-07-02, GREEN) — emspb.vds.kzntsv.site vs бой www.emspb.ru (RUVDS IIS 80.64.31.36, через --resolve)
- Status-паритет 23/23: 5 nav (
/ /contacts /portfolio /prices) + 17 service-детальных (/ustanovka-*,/almaznoe-burenie-*…) все 200/200;/services404/404 (nav-якорь, не страница);/index.php404/404;/robots.txt200 байт-в-байт (31 B). - Дельта размера контент-страниц стабильна +8 B, полностью объяснена: +9 host-строка в canonical/og itemprop (
emspb.vds.kzntsv.siteна 9 символов длиннееwww.emspb.ru), −1 — новый snolla почистил битый двойной слэш боя/images//3.jpg→/images/3.jpg(оба варианта → 200, картинка не сломана)./— байт-в-байт (canonical из DB-домена, не request-host). - theme CSS ×3 (framework/bootstrap/slick) из MinIO — md5 IDENTICAL с боем;
Cache-Control: max-age=86400, public, must-revalidate,text/css. - theme image
/images/3.jpgиз MinIO — md5 IDENTICAL (115618 B, image/jpeg). - Редиректы:
/contacts/→301→/contacts(trailing-slash),/Contacts→301→lowercase,/Portfolio→301→lowercase — как бой. - Контейнер healthy (t+10s), MSSQL (tedious) + S3 (aws-sdk) подключены,
listening at http://localhost:5000, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
Порядок был:
- (оператор) reg.ru: A-записи
emspb.ru+www.emspb.ru→89.253.255.94(с RUVDS80.64.31.36). — сделал оператор (сигнал «поменял DNS у провайдера»). Проверено внешним резолвером (8.8.8.8): оба хоста → VDS; labtools остался на RUVDS (не путать домены!). - (ops) В стеке
emspb(Id 18) traefik-rule расширен:Host(\emspb.ru`) || Host(`www.emspb.ru`)— Portainer PUT (env сохранён, pullImage=false). LE HTTP-01 выпустил cert на первом хите после flip (~t+20-70s). **staging-хостemspb.vds.kzntsv.site` убран из rule post-cutover** (проверено: → 404). Cert перевыпустится на 2 боевых SAN при renewal. - Live-smoke GREEN:
www.emspb.ru+emspb.ru→ 200ssl_verify=0(cert доверенный); все страницы 200; canonical нормализован наwww.emspb.ru; live VDS/contacts== старый RUVDS байт-в-байт (host уравнялся).
⚠️ Порядок критичен: Host-правило добавлено ТОЛЬКО ПОСЛЕ flip DNS (иначе LE HTTP-01 challenge упал бы на RUVDS → сожгли бы rate-limit). Перед PUT — обязательно проверить внешним резолвером, что DNS реально на VDS.
Guard (как labtools)
- НЕ добавлять
Host(emspb.ru)в traefik-rule ДО флипа DNS — traefik на reload проактивно тянет LE-cert (HTTP-01), challenge упадёт на RUVDS → сожжём LE rate-limit. - НЕ выводить RUVDS IIS emspb из эксплуатации — это rollback-путь.
- НЕ флипать DNS самим — это делает хозяин домена/оператор.
Rollback
- DNS: вернуть A-записи
emspb.ru/www→80.64.31.36(RUVDS IIS живой, нетронут). Откат = смена DNS. - Стек: Portainer → stack
emspb(Id 18) → remove (или откат тега образа). Образ в registry остаётся.
0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
Тираж snolla 0.42.1. emspb.ru обновлён на живом стеке 18 in-place (домен уже на VDS, DNS не трогали). Рецепт — snolla-live-prod-inplace-image-bump. Оператор дал отмашку на боевой apply.
- Образ:
registry.kzntsv.site/emspb:95a5c42(snolla 0.28.4→0.42.1, digestd6299cf…). Собран на VDS. - Acceptance С VDS (throwaway-staging из env стека 18): 29 sitemap-роутов все 200 (0 non-2xx/3xx), content-not-lost 29/29 (0 потерь). Sitemap НЕ реструктурировался (29==29, в отличие от каталожного labtools.ru).
- TLS: серт — SAN покрывает
emspb.ru/www.emspb.ru/emspb.vds.kzntsv.site, CN косметически = staging-хост (curl validatessslverify=0). Это тот самый серт с cutover 07-02 (см. выше: «Cert перевыпустится на 2 боевых SAN при renewal») — swap его не тронул. При renewal можно пере-выпустить с CN=emspb.ru, опционально. - Swap: Portainer PUT стека 18 (env 8/8 сохранён) → healthy → live-smoke GREEN, sitemap 29 вживую.
- Rollback: тег
emspb:b6e361a(0.28.4, в registry) / стек 18 PUT назад / DNS→RUVDS. - Compose обновлён на
95a5c42. Таска[emspb-deploy-snolla-0-42-1]🟢. Notify=victor/emspb.ru.
0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
5-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro — 4 GREEN). In-place bump стека 18 (DNS на VDS с 2026-07-02, не трогали). Рецепт — minio-split-cutover-runbook §B3-v3.
- Bump:
@snollajs/snolla 0.42.1→0.43.2вapps/web/package.json(commit5afd8a1) +resolutions-блок в rootpackage.json(verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + регенyarn.lock(single @smithy/signature-v4@5.3.11 deduped). Pushed victor/emspb.ru master218c3ae..5afd8a1. Endpoint fix218c3ae(s3.endpoint→minio.vds.kzntsv.site) в master с 2026-07-29, но в бегущем образе95a5c42(v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. - Build на VDS:
git archive 5afd8a1→~/build/emspb→docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN→registry.kzntsv.site/emspb:5afd8a1(+latest), digestsha256:72bb59fc355d4f020fe49e1c410e6be411779ee1002d91cd4db7c3250f2c3358, EXIT=0.yarn install --immutableв Docker прошёл (yarn.lock matches, no YN0028). - Staging :5082 parity-gate GREEN (throwaway контейнер из env живого стека 18, 8 секретов,
--network proxy):- robots.txt md5
940a0903…byte-identical (31 B); sitemap index→sitemap-pages-1.xml29 locs == live 29. - 29/29 sitemap-роутов 200/200, 0 status diffs, content present (34-38 KB real bodies). Дельта +2 B =
localhost:5082(14) vswww.emspb.ru(13) ×2 (canonical+og:url) — benign host-context, post-cutover byte-identical. - theme assets 7/7 md5 IDENTICAL (v3 aws-sdk v3 S3-direct read из vds MinIO 2025): snolla-framework
5cb1031e…7529B, snolla-bootstrap9a56500a…19624B, slicke604b364…1183B, toolbox382a2ccc…10215B, themee02370e3…5640B, images/2.jpg0898d8df…387884B, /images/3.jpg48562843…115618B (== runbook 2026-07-02 baseline). - redirects parity:
/contacts/→301→/contacts,/Contacts→301→lowercase,/Portfolio→301→lowercase,/Services→301 (target modulo host);/index.php+/services→404 parity.
- robots.txt md5
- Portainer PUT stack 18 LIVE: node in-memory
put-stack(НЕ PS — глобальный fetch, compose в JS-var без /tmp round-trip; GET/stacks/18для Env +/stacks/18/fileдля StackFileContent), env 8/8 preserved,prune:false pullImage:true mem_limit 512m. PUT 200. Контейнер5afd8a1healthy сразу (~1s). - Live smoke GREEN (via traefik):
/200 54615B,/contacts+/portfolio+/prices200,/services+/index.php404, robots md5940a0903…, sitemap 29 locs, theme assets md5 LIVE==staging (v3 S3-direct), redirects 301 (targethttps://www.emspb.ru/...— byte-identical post-cutover), apexemspb.ru200. Лог чист (tedious deprecation +emspb.ru (snolla) listening). TLS не тронут. - Rollback:
emspb:95a5c42(0.42.1 v2, жив в registry) — Portainer PUT назад / compose revert.b6e361a(0.28.4) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний. - Compose (admin source-of-truth) синхронно закоммичен
95a5c42→5afd8a1. 5/6 v3 сайтов GREEN. Остался: tandemmebel (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).