Files
admin/.wiki/concepts/on-snolla-vds-deploy-runbook.md
vitya 4c4d19382b 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>
2026-07-20 13:17:59 +03:00

90 lines
8.4 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: 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)**.