feat(on-snolla): CUTOVER DONE — on.snolla.com live on VDS (stack 22, LE cert)
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 <noreply@anthropic.com>
This commit is contained in:
90
.wiki/concepts/on-snolla-vds-deploy-runbook.md
Normal file
90
.wiki/concepts/on-snolla-vds-deploy-runbook.md
Normal file
@@ -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)**.
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user