design(snolla): local .NET admin restore + on.snolla.com VDS migration

Two linked tasks, spec in .wiki/concepts/ per project-discipline.

Task A — snolla-local-admin-restore: восстановить локальный catch-all IIS-сайт
`snolla` (снесён 2026-06-08) копированием C:\sites\snolla\ с RUVDS (прод-админ
с MinIO drop-in), conn → mssql.kzntsv.site MoreThenCms. Доступ
<alias>.snolla.com/admin через hosts-override, один AppPool. Адреса + siteIds
извлечены из MoreThenCms DB read-only.

Task B — on-snolla-vds-migration (blocked by A): on.snolla.com (siteId
B9ECDB50…, alias on, "Internal Site", culture en) с RUVDS IIS → VDS Node
snolla-app 0.42.1, реконструкция Liquid-шаблонов из боевого сайта+админки
(исходников нет). По рецепту tandemmebel-vds-deploy-runbook.

Board entries: .tasks/STATUS.md + per-task files.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-20 09:45:06 +03:00
parent a02c4b86a2
commit b1b0092cdc
4 changed files with 161 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
---
title: SNOLLA local .NET admin restore + on.snolla.com VDS migration — design
type: concept
tags: [snolla, morethencms, iis, admin, minio, vds, migration, on-snolla, design]
related: [[../entities/windows-recovery-host]], [[../entities/ruvds-iis-host]], [[../entities/vds-kzntsv]], [[tandemmebel-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]], [[portainer-stack-management-vds]], [[filestorage-s3-provider@MoreThenCms]]
updated: 2026-07-20
---
# SNOLLA local .NET admin restore + on.snolla.com → VDS migration
Две связанные задачи. **Task A** восстанавливает локальный .NET-админ (чтобы редактировать контент всех сайтов тиража + on.snolla.com). **Task B** мигрирует посадочную `on.snolla.com` с RUVDS на VDS как Node snolla-app, реконструируя отсутствующие исходники из боевого сайта + админки. Task A → питает Task B (без рабочего админа не увидеть контент/структуру on.snolla.com).
## Контекст
- **Тираж SNOLLA** (5 сайтов) уже на VDS как Node snolla-app `@snollajs/snolla` 0.42.1, читают контент из общей MSSQL `MoreThenCms` на `mssql.kzntsv.site:1433` + ассеты из MinIO `minio.kzntsv.site`. Стеки Portainer: labtools.ru(17), emspb.ru(18), labtools.pro(19), tandemmebel(20), kupimknigi(21). Публичный snolla-app **без админки** (`apps/web` не несёт admin-роутов).
- **На RUVDS IIS** (`80.64.31.36`, Windows Server 2025 Core) остались: catch-all IIS-сайт `snolla` (MoreThenCms .NET 4.8, `/admin` для всех `*.snolla.com`) + посадочная `on.snolla.com`. МинIO-поддержка в .NET-админ добавлена (drop-in `MoreThenCms.FileStorage.S3`, см. MoreThenCms `.wiki/concepts/filestorage-s3-provider.md`).
- **Локальный админ-хост** = этот рабочий PC (`windows-recovery-host`, DESKTOP-NSEF0UK). IIS-сайт `snolla` + `C:\sites\snolla\` были снесены 2026-06-08 (RUVDS забрал прод-админ). Живой локальный аналог остался: `stostayer`(:8090)/`stostayer.old`(:8091) — тот же движок MoreThenCms, `/admin`, conn → `mssql.kzntsv.site,1433`. Это рабочий шаблон конфига.
- **on.snolla.com** — `dbo.Sites`: `SiteId=B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7`, `Alias=on`, `Title="Internal Site"`, `Culture=en`, `Live=1, Production=1`. Лендинг самого SNOLLA. Отдельного репо/исходников нет.
## Task A — восстановить локальный .NET-админ (catch-all IIS `snolla`)
### Адресация (confirmed оператором)
Catch-all IIS-сайт `snolla`, binding `*:80` (локально — `127.0.0.1:80` или `*:80` без публикации наружу), **один AppPool `snolla`**. Доступ через `hosts`-override: `127.0.0.1 <alias>.snolla.com` на каждый таргет. Multi-tenant routing в CMS по Host-header → siteId из `dbo.Sites`.
**Адреса админок** = `<Alias>.snolla.com/admin`:
| Адрес | PrimaryDomain | siteId |
|---|---|---|
| `tandemmebel.snolla.com/admin` | tandemmebel.ru | 78080707-F6E0-4330-BA30-7922354C2CEF |
| `labtools.snolla.com/admin` | labtools.ru | D375C419-D787-4FCC-8934-A3B9DC951006 |
| `labtoolspro.snolla.com/admin` | labtools.pro | 663F9410-A6CC-4651-9A5C-62844A313957 |
| `emspb.snolla.com/admin` | emspb.ru | 96EBC481-D26A-47BE-B660-13D49E7D0A61 |
| `kupimknigi.snolla.com/admin` | kupimknigi.spb.ru | E924A354-0377-4E1E-80C6-2EB0194AA55F |
| `on.snolla.com/admin` | on.snolla.com | B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7 |
Catch-all обслужит `/admin` для любого сайта из DB при добавлении `127.0.0.1 <alias>.snolla.com` в hosts — тираж = 5 + on.snolla.com минимум, при желании все ~60.
### Источник `C:\sites\snolla\`
**Рекомендация — копировать с RUVDS** (`scp`/`rclone`): RUVDS держит `C:\sites\snolla\` (8.66 GB) = текущий прод-админ **с уже применённым MinIO drop-in**. Копируем → получаем админ с MinIO из коробки, без пересборки solution и без ручного патча DLL. Альтернативы (build из MoreThenCms source + применить S3 drop-in вручную; restore с kreknin-backup) дольше и требуют повторной MinIO-настройки.
### Шаги
1. **Verify source (read-only SSH RUVDS, креды `pass show ruvds-iis/full-env`):** `C:\sites\snolla\` есть; `Web.config` несёт `storageType="MoreThenCms.FileStorage.S3.*"` + endpoint `minio.kzntsv.site`; IIS site `snolla` жив. Если MinIO НЕ применён — fallback: build из source + drop-in из `MoreThenCms.FileStorage.S3/deploy/` (3 DLL + `fileStorageClients-s3.sample.config`).
2. **Копировать `C:\sites\snolla\`** RUVDS → локально `C:\sites\snolla\` (~8.66 GB; rsync недоступен на Core — `scp -r` или `rclone copy`, resume через rclone).
3. **Репойнт conn-string** в локальном `Web.config``Data Source=mssql.kzntsv.site,1433;Initial Catalog=MoreThenCms;User ID=<snolla-db-user>;Password=<...>;MultipleActiveResultSets=True;TrustServerCertificate=True` (паттерн из `stostayer.old/Web.config`). DB-креды — из `pass` (MoreThenCms DB user; тот же, что tandemmebel production.json `DB_USER`/`DB_PASSWORD`).
4. **MinIO-ключи** в `<fileStorageClients>`: endpoint `https://minio.kzntsv.site`, бакеты `assets`/`galleries`/`themes`, `accessKey`/`secretKey` — из `pass show minio-vds/full-env`. Если в скопированном Web.config ключи плейсхолдеры `__SET_ON_DEPLOY__` — подставить реальные. Кэши (`imageCache`/`uploadCache`/`contentCache`/`sessionFiles`) оставить `Local`.
5. **IIS-сайт `snolla`** воссоздать: AppPool `snolla` (.NET v4.0 Integrated, `ApplicationPoolIdentity`), binding `*:80` catch-all (как на проде). Локальный доступ гарантируется FW-блоком inbound 80 + `hosts`-override → 127.0.0.1 (внешние не достучатся). Multi-tenant по Host-header.
6. **ACL**: `icacls C:\sites\snolla /grant "IIS AppPool\snolla:(OI)(CI)(M)" /T` (паттерн `snolla-admin-appdata-acl-500`).
7. **hosts-override**: добавить `127.0.0.1 tandemmebel.snolla.com labtools.snolla.com labtoolspro.snolla.com emspb.snolla.com kupimknigi.snolla.com on.snolla.com` в `C:\Windows\System32\drivers\etc\hosts` (нужен elevation).
8. **URL Rewrite rule** (X-Forwarded-Proto→HTTPS server-vars) — **НЕ ставить** локально (нет traefik; ходим по http, иначе `:port` leak как в `cms-server-port-leak-fix`).
9. **Firewall**: НЕ открывать 80/443 inbound — локальный только.
10. **Smoke**: `curl -H "Host: tandemmebel.snolla.com" http://127.0.0.1/admin/account/login` → 200 логин-форма; аналогично для on.snolla.com. MinIO: залить тестовый ассет через админку → виден через `minio.kzntsv.site`/фронтом (приёмка #5 drop-in'а).
### Гочи (специфичные)
- **`appSettings/sitePath`** в catch-all `Web.config``C:\sites\snolla\` (multi-tenant; siteId резолвится из Host-header, не из appSettings — в отличие от per-site stostayer.old где `siteId` в appSettings).
- **machineKey** — оставить из скопированного Web.config (общий для всех tenant'ов админ-сессий).
- **`customErrors mode="Off"`** — обязательно (иначе fatal config error после ASP.NET reload, см. `cms-server-port-leak-fix` side-regression).
- **MSSQL-коннект с workstation** — уже работает (stostayer.old его использует); `TrustServerCertificate=True` обязательно (mssql.kzntsv.site cert self-signed/не-Microsoft).
## Task B — on.snolla.com → VDS (Node snolla-app, реконструкция)
on.snolla.com = MoreThenCms-сайт на RUVDS (посадочная). На VDS (Linux) .NET Framework не бежит → становится Node snolla-app по образцу `victor/tandemmebel.ru`. Контент (DB-строки + ассеты) уже на vds-kzntsv (общая `MoreThenCms` DB + MinIO). Нет репозитория шаблонов+конфига — собираю из боевого сайта + админки.
### Реконструкция
1. **siteId найден**: `B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7` (alias `on`, culture `en`).
2. **Новый репо** `victor/on-snolla` (имя уточнить у оператора) — структура как `tandemmebel.ru`: `apps/web/{server.js,index.js,config/,views/,package.json}`, `deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`). `apps/web/package.json` пин `@snollajs/snolla` 0.42.1 (parity с тиражом).
3. **production.json**: `siteId=B9ECDB50…`, `siteUrl=https://on.snolla.com`, `data.sequelize``MoreThenCms` DB `mssql.kzntsv.site:1433` (Catalog `MoreThenCms`), `s3``minio.kzntsv.site`, `imgproxy``imgproxy.kzntsv.site`, `media` sharp serve-bytes, `mailSettings`. Секреты в runtime-env стека (Portainer), НЕ в образе/гите — как tandemmebel (8 секретов).
4. **Liquid-шаблоны (views/)** — реверс-инжиниринг с боевого `https://on.snolla.com/`: для каждой страницы смотрим HTML, сопоставляем с DB-контент-структурой (секции/блоки через локальный админ из Task A), пишем `layout.liquid` + page/partials. Содержимое (текст/картинки) идёт из DB; шаблоны = layout + разметка. Итеративно: пиши шаблон → рендерь на staging → сверяй с боевым HTML (byte/structure parity).
5. **Build на VDS** (обход traefik-499), **throwaway-staging `:50XX`**, **completeness-gate** (sitemap parity vs прод-on.snolla.com — page-locs NEW==PROD, self-consistency), **stack** за traefik (`Host(on.snolla.com)`), **DNS-flip** reg.ru→89.253.255.94. По рецепту `tandemmebel-vds-deploy-runbook` (cutover) + `snolla-live-prod-inplace-image-bump` (gate). `mem_limit 512m`.
### Trade-off реверс-инжиниринга
Боевой HTML даёт вёрстку, но Liquid-примитивы snolla (sections/blocks/feeds) надо сопоставить с DB-структурой — итеративно. Риск: сложная посадочная с нестандартными блоками = больше итераций. Митигация: on.snolla.com Title="Internal Site" culture `en` — вероятно простой лендинг, объём шаблонов ограничен.
## Последовательность и зависимости
1. **Task A** — восстановить локальный админ (RUVDS→scp→IIS→MinIO→smoke). Блокирует Task B (нужен админ для контент-инспекции).
2. **Task B** — через локальный админ + боевой HTML собрать on.snolla.com snolla-app → build на VDS → cutover.
## Load-bearing допущения (verify в плане)
- RUVDS `C:\sites\snolla\` несёт MinIO drop-in (SSH read-only — первый шаг Task A). Если нет — fallback на build+drop-in.
- MinIO-ключи в RUVDS Web.config — реальные, не `__SET_ON_DEPLOY__` (иначе подтянуть из `pass`).
- MoreThenCms DB-креды (snolla-user) в `pass` — найти путь (кандидаты: `mssql-vds/...`, общий env). SA-доступ есть (`pass show mssql-vds/sa-password`) — но админ использует app-аккаунт, не SA; уточнить у оператора.
- on.snolla.com siteId/контент доступны в общей `MoreThenCms` DB (подтверждено: строка в `dbo.Sites` есть).
- `hosts`-override + IIS-воссоздание требуют elevation (admin-сессия на workstation).
## Rollback
- **Task A:** удалить IIS-сайт `snolla` + `C:\sites\snolla\` + откатать hosts-записи. Прод на RUVDS не тронут — rollback = просто не пользоваться локальным админом.
- **Task B:** образ предыдущего тега в registry (или RUVDS IIS жив для on.snolla.com пока DNS не флипнут — revert DNS→80.64.31.36).
## Open questions (для оператора)
- Имя нового репо для on.snolla.com (`victor/on-snolla`? `victor/on.snolla.com`?).
- MoreThenCms DB-креды: app-аккаунт + путь в `pass` (или использовать SA локально / завести отдельный read-write аккаунт).
- Включать ли в локальный админ ВСЕ сайты (~60) или только тираж(5)+on.snolla.com — catch-all даёт все бесплатно, решает hosts-список.