Files
admin/.wiki/concepts/labtools.pro-vds-deploy-runbook.md
vitya 531d7f0fda deploy(labtools-pro): staging GREEN on vds-kzntsv — image 7bd9fae, stack 19, parity vs prod
Mirror of emspb deploy. Built registry.kzntsv.site/labtools-pro:7bd9fae on VDS
(guard: default.json absent), created Portainer stack labtools-pro (Id 19, env
verbatim from labtools stack 17). Container healthy, MSSQL+S3 connected.

Staging smoke vs www.labtools.pro (RUVDS) GREEN, run from VDS:
- nav + 3 catalog sections + products 200/200
- redirects (catalog#1->lshm-750, /Contacts lowercase, trailing) 301 parity
- sitemap: snolla index of 7 children, aggregate 25 URLs == prod set, host=www (R3)
- theme assets 7/7 md5-identical; Cache-Control absent (correct, CachingOptions=[])
- content pages real body, delta +155..222B (host length in canonical)

Cosmetic (not a defect, flagged to workshop): startup banner logs "labtools.ru
(snolla)" — scaffold constant; site identity is siteId 663F9410 in production.json.

Cutover (live DNS) is gated on operator. Runbook + compose committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:38:56 +03:00

79 lines
9.6 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: labtools.pro snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
type: concept
tags: [labtools-pro, labtools, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, catalog]
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[emspb-vds-deploy-runbook]], [[labtools-vds-deploy-runbook]]
updated: 2026-07-02
---
# labtools.pro → VDS deploy runbook
Вынос `labtools.pro` (snolla-приложение, `@snollajs/snolla` **0.28.7**, server-side Liquid, **каталожный сайт**)
с [[../entities/ruvds-iis-host]] (catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]]
(89.253.255.94), за traefik. Модель — snolla-app: читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy. **Зеркало [[emspb-vds-deploy-runbook]]** — тот же паттерн;
отличие: каталог `/products/*` + `Culture=en` (англоязычный близнец labtools.ru) + Cache-Control отсутствует (см. ниже).
## Артефакты
- **Код:** `victor/labtools.pro` @ `7bd9fae` (apps/web, ре-ревью PASS все 9 дименшнов, ноль хаков, snolla 0.28.7 final). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck `/robots.txt`).
- **Образ:** ⚠️ `registry.kzntsv.site/labtools-pro:7bd9fae` (+`:latest`) — имя **`labtools-pro`**, НЕ `labtools` (тот у labtools.ru!). Собран НА VDS (обход traefik-499). digest `sha256:23d0bc597c8930a1bf443c418a4af68d63b692903963c0e1f9f25baa4a1dd236`.
- **Стек Portainer:** `labtools-pro` (**Id 19**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/labtools-pro.compose.yml`.
- **siteId:** `663F9410-A6CC-4651-9A5C-62844A313957`, activeTheme `389AD745-E3EE-4F23-BE16-DF38440A0941` (theme store MinIO `themes/389ad745e3ee4f23be16df38440a0941/`). Non-secret — в `production.json`, НЕ env.
- **siteUrl:** `https://www.labtools.pro` — в `production.json` (R3: sitemap/robots/canonical отдают www).
## Сборка образа (на VDS)
```bash
git -C ~/projects/labtools.pro archive --format=tar 7bd9fae \
| ssh vitya@89.253.255.94 'rm -rf ~/build/labtools-pro && mkdir -p ~/build/labtools-pro && tar -x -C ~/build/labtools-pro'
ssh vitya@89.253.255.94 "cd ~/build/labtools-pro && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/labtools-pro:7bd9fae -t registry.kzntsv.site/labtools-pro:latest . \
&& docker push registry.kzntsv.site/labtools-pro:7bd9fae && docker push registry.kzntsv.site/labtools-pro:latest"
```
- VERDACCIO_TOKEN — `pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN`, build-time only (не в финальный образ; docker linter warn-only).
- `config/default.json` (dev-секреты) исключён `.dockerignore` — guard проверен на архиве: default.json ABSENT (только production.json + custom-environment-variables.json + default.example.json).
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
**Значения идентичны labtools/emspb** (все snolla-тенанты = одна MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив **переиспользован verbatim из labtools stack (Id 17)** через Portainer API. 8 env: `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`. Маппинг env→config: `apps/web/config/custom-environment-variables.json`.
PORT — не переопределять (образ 5000; EXPOSE 5000; healthcheck `/robots.txt`). traefik service-port = 5000.
## Создание стека (Portainer API, X-API-Key)
```bash
K=$(pass show vds-kzntsv/full-env | sed -n 's/^PORTAINER_API_KEY=//p'); BASE=https://portainer.vds.kzntsv.site
ENV=$(curl -ksS -H "X-API-Key: $K" $BASE/api/stacks/17 | jq '.Env') # verbatim из labtools stack 17
COMPOSE=$(cat ~/build/labtools-pro.compose.yml)
PAYLOAD=$(jq -n --arg name labtools-pro --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
curl -ksS -X POST "$BASE/api/stacks/create/standalone/string?endpointId=1" -H "X-API-Key: $K" -H 'Content-Type: application/json' --data "$PAYLOAD" # → Id 19
```
Предусловие pull: `registry.kzntsv.site` зарегистрирован в Portainer как Custom registry Id 1 (иначе `no basic auth`).
## Runtime egress
`mssql.kzntsv.site:1433` (БД-контент) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
## Staging smoke (2026-07-02, GREEN) — `labtools-pro.vds.kzntsv.site` vs бой `www.labtools.pro` (RUVDS IIS 80.64.31.36)
Гнать с самого VDS (воркстейшн ловит LAN-DNS-перехват на `*.labtools.pro`; с VDS бой резолвится публично → RUVDS).
- **Status-паритет:** nav (`/ /about /contacts`) + 3 каталог-секции (`/products/laboratory-presses`, `/products/press-forms`, `/products/milling-accessories`) + продукты (`plg-20`, `press-forms/ring`) — все 200/200.
- **Редиректы (301 parity, target идентичен modulo host):** каталог#1 `/products/laboratory-ball-mills``…/laboratory-ball-mills/lshm-750`; `/Contacts`→lowercase `/contacts`; `/contacts/`→trailing `/contacts`.
- **Sitemap — структура index (snolla) vs плоский (legacy), покрытие идентично:** бой отдаёт плоский `/sitemap.xml` с 25 URL; snolla отдаёт **sitemap-INDEX** из 7 дочерних (`sitemap-pages-1` 3, `sitemap-static-pages-1` 2, `sitemap-sections-1` 3, 4× `sitemap-catalog-<GUID>-products-1` = 1+2+2+12=17), все 200, **в сумме ровно 25 URL, набор == бой** (comm — ноль расхождений). `<loc>` host = `www.labtools.pro` (R3). **НЕ дефект** — index-структура штатна для snolla (как pilonuxt).
- **theme-ассеты из MinIO md5-IDENTICAL бою** (проверены css/js/png/svg под `/themes/389ad745e3ee4f23be16df38440a0941/`): 7/7 MD5-OK, http 200.
- ⚠️ **Cache-Control ОТСУТСТВУЕТ на theme-ассетах — КОРРЕКТНО** (labtools.pro `CachingOptions=[]` empty-config; движок 0.28.7 не ставит заголовок = бой). НЕ флагать как дефект (в отличие от emspb/labtools где был `max-age=86400`).
- **Контент-страницы рендерят реальное тело** (1222 KB), дельта vs бой +155…+222 B — стабильна, объясняется длиной staging-host в canonical/og (на 11 симв. длиннее www; на cutover уравняется в byte-parity).
- Контейнер healthy, MSSQL (tedious) + S3 (aws-sdk) подключены, `listening at http://localhost:5000`, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
### Косметика (не дефект, отдано workshop)
Стартовый баннер в логе — `labtools.ru (snolla) listening…` — строка-константа унаследована из labtools.ru-референса при скаффолде. Идентификация сайта = siteId в production.json (663F9410…), на рендер/контент не влияет.
## Cutover (live DNS) — GATED, действие ОПЕРАТОРА (ещё НЕ выполнен)
Бой live на [[../entities/ruvds-iis-host]] (`80.64.31.36`) **остаётся живым** до подтверждения. Порядок (как emspb):
1. **(оператор)** reg.ru: A-записи `labtools.pro` + `www.labtools.pro``89.253.255.94` (с RUVDS `80.64.31.36`). Понизить TTL заранее.
2. ⚠️ **Проверить внешним резолвером (8.8.8.8), что флипнут именно `labtools.pro`** (НЕ `labtools.ru` — он тоже ждёт cutover на RUVDS; не перепутать домены). Cм. memory [operator-dns-flip-verify-domain-before-cutover].
3. **(ops) ТОЛЬКО ПОСЛЕ flip:** в стеке `labtools-pro` (Id 19) заменить traefik-rule на `Host(\`labtools.pro\`) || Host(\`www.labtools.pro\`)` (Portainer PUT, env сохранить, pullImage=false). LE HTTP-01 выпустит cert на первый хит.
4. Проверить LE cert выпущен + live-smoke на боевом хосте; убрать staging-хост `labtools-pro.vds.kzntsv.site` из rule post-cutover.
5. Отрепортить workshop.
⚠️ **Порядок критичен:** Host-правило добавлять ТОЛЬКО ПОСЛЕ flip DNS (иначе traefik проактивно тянет LE-cert HTTP-01, challenge упадёт на RUVDS → сожжём rate-limit).
## Rollback
- **DNS:** вернуть A-записи `labtools.pro`/`www``80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
- **Стек:** Portainer → stack `labtools-pro` (Id 19) → remove (или откат тега образа). Образ в registry остаётся.
- RUVDS IIS labtools.pro **не выводить** до явного решения оператора (директива 2026-07-01). (Ср.: emspb.ru биндинги на RUVDS сняты 2026-07-02 после подтверждённого cutover — см. [[../entities/ruvds-iis-host]].)