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>
115 lines
14 KiB
Markdown
115 lines
14 KiB
Markdown
---
|
||
title: emspb.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
|
||
type: concept
|
||
tags: [emspb, snolla, vds, deploy, docker, portainer, traefik, runbook, migration]
|
||
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[labtools-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]]
|
||
updated: 2026-07-05
|
||
---
|
||
|
||
# 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). digest `sha256: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`, activeTheme `DD8D6F7A-CEDC-4D5A-8BFB-E5F9BE9DC035` (theme store MinIO `themes/dd8d6f7acedc4d5a8bfbe5f9be9dc035/`). Non-secret — в `production.json`, НЕ env.
|
||
|
||
## Сборка образа (на VDS)
|
||
```bash
|
||
# чистый 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-кириллица-гочу)
|
||
```bash
|
||
# 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; `/services` 404/404 (nav-якорь, не страница); `/index.php` 404/404; `/robots.txt` 200 байт-в-байт (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
|
||
Порядок был:
|
||
1. **(оператор)** reg.ru: A-записи `emspb.ru` + `www.emspb.ru` → `89.253.255.94` (с RUVDS `80.64.31.36`). — сделал оператор (сигнал «поменял DNS у провайдера»). Проверено внешним резолвером (8.8.8.8): оба хоста → VDS; labtools остался на RUVDS (не путать домены!).
|
||
2. **(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.
|
||
3. **Live-smoke GREEN:** `www.emspb.ru`+`emspb.ru` → 200 `ssl_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**, digest `d6299cf…`). Собран на 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 validates `sslverify=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` (commit `5afd8a1`) + `resolutions`-блок в root `package.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 master `218c3ae..5afd8a1`. Endpoint fix `218c3ae` (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), digest `sha256: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.xml` **29 locs == live 29**.
|
||
- **29/29 sitemap-роутов 200/200, 0 status diffs**, content present (34-38 KB real bodies). Дельта **+2 B** = `localhost:5082` (14) vs `www.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-bootstrap `9a56500a…` 19624B, slick `e604b364…` 1183B, toolbox `382a2ccc…` 10215B, theme `e02370e3…` 5640B, images/2.jpg `0898d8df…` 387884B, **/images/3.jpg `48562843…` 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.
|
||
- **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. Контейнер `5afd8a1` healthy сразу (~1s).
|
||
- **Live smoke GREEN (via traefik):** `/` 200 54615B, `/contacts`+`/portfolio`+`/prices` 200, `/services`+`/index.php` 404, robots md5 `940a0903…`, sitemap 29 locs, theme assets md5 LIVE==staging (v3 S3-direct), redirects 301 (target `https://www.emspb.ru/...` — byte-identical post-cutover), apex `emspb.ru` 200. Лог чист (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 визуальном дефекте, проверить перед бампом).
|