From 4c4d19382beececf08444f879b831d025d505567 Mon Sep 17 00:00:00 2001 From: vitya Date: Mon, 20 Jul 2026 13:17:57 +0300 Subject: [PATCH] =?UTF-8?q?feat(on-snolla):=20CUTOVER=20DONE=20=E2=80=94?= =?UTF-8?q?=20on.snolla.com=20live=20on=20VDS=20(stack=2022,=20LE=20cert)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DNS reg.ru on.snolla.com A -> 89.253.255.94 (operator flip, authoritative NS verified). Traefik rule swapped staging->Host(on.snolla.com), LE cert issued (CN=on.snolla.com, YR1, until 2026-10-18). Live-smoke GREEN, homepage byte-identical prod-HTML, sitemap 3/3 parity. RUVDS IIS untouched (rollback = revert DNS). - compose source-of-truth updated to LIVE rule. - runbook .wiki/concepts/on-snolla-vds-deploy-runbook.md (cutover as-built + gotchas: snolla robotsTxt crash on undefined app.locals.domain, no Liquid form-tag, /c dead-link parity, Portainer JWT re-auth before PUT). Co-Authored-By: Claude --- .../concepts/on-snolla-vds-deploy-runbook.md | 90 +++++++++++++++++++ host-stacks/vds-kzntsv/on-snolla.compose.yml | 14 +-- 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 .wiki/concepts/on-snolla-vds-deploy-runbook.md diff --git a/.wiki/concepts/on-snolla-vds-deploy-runbook.md b/.wiki/concepts/on-snolla-vds-deploy-runbook.md new file mode 100644 index 0000000..656cb0e --- /dev/null +++ b/.wiki/concepts/on-snolla-vds-deploy-runbook.md @@ -0,0 +1,90 @@ +--- +title: on.snolla.com snolla-app — VDS deploy runbook (cutover 2026-07-20) +type: concept +tags: [on-snolla, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, landing] +related: [[../entities/vds-kzntsv], [../entities/ruvds-iis-host], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [portainer-stack-management-vds], [snolla-local-admin-and-on-snolla-migration-design]] +updated: 2026-07-20 +--- + +# on.snolla.com → VDS deploy runbook + +Вынос посадочной `on.snolla.com` (snolla-app, `@snollajs/snolla` **0.42.1**, server-side Liquid) с +[[../entities/ruvds-iis-host]] (catch-all CMS, 80.64.31.36) в docker-контейнер на [[../entities/vds-kzntsv]] +(89.253.255.94), за traefik. Паттерн = [[tandemmebel-vds-deploy-runbook]]. Отличие: **лендинг** (1 Page `/` ++ 2 StaticPages), БЕЗ блога/каталога/e-commerce → reconstruction = layout.liquid byte-identical prod-HTML. +Culture `en`. Спека: [[snolla-local-admin-and-on-snolla-migration-design]] §Task B. + +## Артефакты +- **Код:** `victor/on.snolla.com` @ `473923e494db` (apps/web, snolla 0.42.1). Структура-калька tandemmebel.ru. + `deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`). +- **Образ:** `registry.kzntsv.site/on-snolla:473923e494db` (+`:latest`). Имя **`on-snolla`** (= стек/контейнер). + Собран НА VDS (обход traefik-499). +- **Стек Portainer:** `on-snolla` (**Id 22**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/on-snolla.compose.yml`. +- **siteId:** `B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7` (non-secret, в `production.json`). +- **siteUrl:** `https://on.snolla.com` (в `production.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 из tandemmebel стека 20 — общая snolla-инфра). НЕ в образе, НЕ в git. +- **mem_limit:** `512m`. + +## Site model (MoreThenCms DB, siteId B9ECDB50) +- 1 Page `/` (template `content_page`, `customDefaultLayout=layout`). **Content-поля пустые** → весь + bootstraptor-лендинг baked в `views/layout.liquid` (byte-identical prod). `{{ item.content }}` (пуст) wired. +- Form "Join us" (Path `/`, FormTemplate `join_us`: first_name/last_name/email/website/description/captcha) + baked в `layout.liquid`; POST `/` → snolla `forms` middleware → `form_ajax_response.liquid` (AJAX replace). + Legacy reCAPTCHA v1 в разметке мертва (Google shut down v1) — parity с продом, НЕ чиним. +- StaticPages: `/yandex_7a510ac1f8311fca.html` (verification, IncludeInSitemap=true), `/4a052808276c.html` (not in sitemap). +- `/c` → **404** (stale dead-link в .NET sitemap; snolla-app sitemap тоже его несёт → parity `/`+`/c`+`/yandex`). +- Theme assets `/themes/c406a987ebe14244b0eefe7b8f959a6f/{css,js,images}/*` — из MinIO bucket `themes` (snolla + `themeFiles` middleware; пути literal в `layout.liquid`). +- `/admin` остаётся на RUVDS .NET — НЕ часть этого snolla-app. + +## Cutover 2026-07-20 (первый вынос на VDS — DNS-gated) +1. **Build на VDS** (archive `473923e494db` → `~/build/on-snolla` → `docker build -f deploy/Dockerfile + --build-arg VERDACCIO_TOKEN -t .../on-snolla:473923e494db -t .../on-snolla:latest . && push`). Guard: + `config/default.json` ABSENT в архиве (.dockerignore), пин snolla = 0.42.1. +2. **Throwaway-staging :5077** из собранного env (8 секретов inline, т.к. нового live-контейнера ещё нет — + секреты общие с тиражом). `docker run -d --name on-snolla-staging --env-file .staging.env -p 127.0.0.1:5077:5000`. + Healthy. robots/`/`/sitemap 200. +3. **Completeness-gate С VDS** — sitemapindex разворот. **NEW==PROD locs: 3 = 3 IDENTICAL** (`/`, `/c`, `/yandex_…`). + Self-consistency: `/`→200, `/yandex`→200, `/c`→404 (dead-link, parity с прод-оракулом — prod /c тоже 404). + **Byte-parity:** homepage + /yandex **byte-identical** staging vs prod (RUVDS, via `--resolve :443:80.64.31.36`). + GREEN = 0 регрессий. +4. **Portainer стек 22** создан со **staging-Host** `on-snolla.vds.kzntsv.site` (wildcard → VDS), LE=default cert. + Healthy за traefik. (`create-stack.mjs` node-скрипт, Portainer JWT auth, env array 8/8, `mem_limit 512m`.) +5. **DNS flip** — operator: reg.ru `on.snolla.com` A `80.64.31.36` → `89.253.255.94`. +6. **Verify авторит. NS** — `nslookup on.snolla.com ns1/ns2.reg.ru` = `89.253.255.94` (BEFORE traefik rule swap — + memory `operator-dns-flip-verify-domain-before-cutover`: wrong domain burns LE rate-limit). Public resolvers propagated. +7. **PUT стека 22** — rule `Host(on-snolla.vds.kzntsv.site)` → `Host(on.snolla.com)`, env-preserving (8/8), + `prune:false, pullImage:false`. node `cutover-put.mjs` (Portainer JWT; **re-auth right before PUT** — + jwt из GET-фазы успел протухнуть к моменту PUT, первый PUT дал 401). PUT 200. +8. **Poll** — контейнер healthy, **LE-серт issued on first hit**: CN=on.snolla.com, issuer YR1, until 2026-10-18. +9. **Live-smoke С VDS + external** — `https://on.snolla.com/{robots.txt,/,/sitemap.xml,/yandex_…}` = 200, + `/c` = 404 parity. TLS verify `ssl_verify_result=0` (trusted LE chain). Homepage **byte-identical** prod-HTML. + +## Гочи (специфичные) +- **snolla `robotsTxt` middleware падает на undefined `app.locals.domain`** — on.snolla.com НЕ имеет row в + MoreThenCms `Domains` (served by primaryDomain без per-domain row). `robotsTxt.js` читает + `res.app.locals.domain.robotsTxt` без null-guard → GET /robots.txt (и Dockerfile healthcheck) крашится 500. + **Фикс в `apps/web/index.js`**: после `initApp`, если `!app.locals.domain`, synthesized `{robotsTxt: site.robotsTxt}` + (only robotsTxt.js reads locals.domain; theme resolution uses site.activeThemeId — safe). Без мутации shared DB. + **Альтернатива** (не применена): добавить Domains row (Name=on.snolla.com, SiteId=B9ECDB50, Public=1, ThemeId=null) + — но это мутация shared prod MoreThenCms; предпочтён app-code fallback. +- **Form "Join us" не имеет Liquid form-tag** — snolla не предоставляет form-rendering tag. Форма HTML baked в + `layout.liquid` (поля по FormTemplate `join_us`); POST обрабатывает `forms` middleware (мэтч по Path `/`). + `form_ajax_response.liquid` — AJAX-ответ (`$form.replaceWith(response)`). +- **`/c` в sitemap — dead-link** — snolla SitemapService эмбитит `/c` из sections-source, но страница 404. + Prod-оракул идентичен (404) → benign parity, не регрессия. +- **hosts override на воркстейшне** — Task A оставил `127.0.0.1 on.snolla.com` в hosts (локальный админ catch-all). + External verify прод-on.snolla.com с воркстейшна = только через `curl --resolve on.snolla.com:443:89.253.255.94` + (иначе попадёшь в локальный IIS). См. memory `workstation-lan-dns-serves-local-cms-copy`. +- **Portainer JWT short-lived** — GET stack + GET file + PUT в одном скрипте: jwt из GET-фазы может протухнуть + к PUT. Re-auth (`POST /api/auth`) прямо перед PUT. API-key `ptr_*` даёт 401 (см. [[portainer-stack-management-vds]]). +- **Образных rollback-тегов пока нет** — первый deploy, `473923e494db` = `:latest`. Rollback = revert DNS (RUVDS жив). + +## Rollback +- **DNS (предпочт, мгновенный):** revert reg.ru `on.snolla.com` A → `80.64.31.36` (RUVDS IIS жив, не тронут). +- **Образный (будущие bump'ы):** PUT стека 22 назад на предыдущий тег (rollback-теги в registry). + +## Связанное +- on.snolla.com admin (`/admin`) остаётся на RUVDS .NET — catch-all IIS `snolla` (см. [[snolla-local-admin-and-on-snolla-migration-design]] §Task A). +- Тираж snolla на VDS: labtools.ru(17), emspb(18), labtools.pro(19), tandemmebel(20), kupimknigi(21), **on-snolla(22)**. \ No newline at end of file diff --git a/host-stacks/vds-kzntsv/on-snolla.compose.yml b/host-stacks/vds-kzntsv/on-snolla.compose.yml index 0f6a8ee..6c0e650 100644 --- a/host-stacks/vds-kzntsv/on-snolla.compose.yml +++ b/host-stacks/vds-kzntsv/on-snolla.compose.yml @@ -13,9 +13,13 @@ # @snollajs/* из verdaccio.kzntsv.site). Тег = git-sha репо on.snolla.com (текущий 473923e494db). # Homepage render byte-identical прод-HTML (verified direct diff + completeness-gate 3/3 loc parity 2026-07-20). # -# Домен: STAGING-Host on-snolla.vds.kzntsv.site (wildcard -> VDS). Cutover: flip DNS reg.ru -> 89.253.255.94, -# затем PUT rule -> Host(`on.snolla.com`), LE-серт issued on first hit. Прод-оракул до cutover = RUVDS IIS -# (80.64.31.36), жив, не тронут. Rollback = revert DNS -> 80.64.31.36 ИЛИ PUT стека назад на тег. +# Домен: LIVE — on.snolla.com (cert LE HTTP-01, CN=on.snolla.com, issued 2026-07-20, until 2026-10-18). +# Cutover завершён 2026-07-20: DNS reg.ru on.snolla.com A 80.64.31.36 -> 89.253.255.94 (operator flip, +# авторит. NS ns1/ns2.reg.ru verified -> VDS), traefik rule staging->боевой (LE-порядок соблюдён), +# LE-серт issued on first hit. Live-smoke GREEN, homepage byte-identical прод-HTML, sitemap 3/3 parity. +# Предыстория: жил на RUVDS IIS (80.64.31.36) до 2026-07-20. RUVDS IIS не тронут (rollback = revert DNS). +# План отката = revert DNS reg.ru -> 80.64.31.36 (RUVDS IIS жив) ИЛИ PUT стека 22 назад на тег (пока +# единственный тег 473923e494db = :latest — первый deploy; образных rollback-тегов пока нет). # # Runtime egress: mssql.kzntsv.site:1433, minio.kzntsv.site:443 (оригиналы + variant-cache), smtp.yandex.ru:465. # @@ -47,8 +51,8 @@ services: labels: traefik.enable: "true" traefik.http.routers.on-snolla.entrypoints: websecure - # STAGING-RULE. Cutover: после flip DNS reg.ru -> 89.253.255.94 (verify авторит. NS) сменить на Host(`on.snolla.com`). - traefik.http.routers.on-snolla.rule: Host(`on-snolla.vds.kzntsv.site`) + # LIVE (cutover 2026-07-20). Rollback = Host(`on-snolla.vds.kzntsv.site`) + revert DNS -> 80.64.31.36. + traefik.http.routers.on-snolla.rule: Host(`on.snolla.com`) traefik.http.routers.on-snolla.tls.certresolver: letsEncrypt traefik.http.services.on-snolla.loadbalancer.server.port: "5000"