Stood up a permanent self-renewing Let's Encrypt pipeline on the RUVDS IIS host, replacing the manual traefik acme.json -> PFX import and closing the 2026-07-22 cert-expiry deadline (new 25-SAN cert valid to 2026-09-03, SYSTEM scheduled task renews 55 days before expiry). Key obstacle: the MoreThenCms OWIN catch-all (owin:HandleAllRequests) swallowed /.well-known/acme-challenge/. Solved by carving the challenge path into a separate IIS application in a No-Managed-Code app pool, plus patching win-acme's Web_Config.xml template to remove the inherited Owin handler. Staging + prod validation green for all 25 hostnames; live TLS smoke confirms the new cert is served (incl degraded maljarka/rimiz). - scripts/iis-migration-to-ruvds/03-ruvds-winacme.ps1 (idempotent setup) - scripts/iis-migration-to-ruvds/winacme-Web_Config.xml (patched template) - .wiki/concepts/winacme-iis-owin-catchall-http01.md (recipe + gotchas) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
20 KiB
Markdown
137 lines
20 KiB
Markdown
# iis-migration-to-ruvds
|
||
|
||
## Goal
|
||
|
||
Migrate IIS hosting from [[../entities/windows-recovery-host]] (DESKTOP-NSEF0UK) to RUVDS Windows Server 2025 Core (`80.64.31.36`). Убрать SPOF домашней машины.
|
||
|
||
**Scope finalize 2026-05-24:** только `snolla` IIS site (catch-all для ~26 hostnames через CMS multi-tenant routing) — 8.66 GB. `stostayer` уже на external MSSQL (не наш scope). `stostayer.old` local-only (defer). `snolla-identity-manager` dead conn-string (defer). См. "Scope" ниже.
|
||
|
||
**Pre-requisites done:**
|
||
- MSSQL+MinIO уже на [[../entities/vds-kzntsv]] (см [[mssql-minio-migration-to-vds]])
|
||
- RUVDS purchased: Windows Server 2025 Core, 2GB RAM, 30GB HDD, 1IP, DC Королёв
|
||
- RDP ready (creds — `pass show ruvds-iis/full-env`, public IP `80.64.31.36`)
|
||
|
||
## Scope
|
||
|
||
**1 IIS site → 25 HTTPS hostnames bound via SNI:**
|
||
|
||
| Hostname | Note |
|
||
|---|---|
|
||
| kupimknigi.spb.ru | ⚡ pilot, DNS flipped 2026-05-24 |
|
||
| emspb.ru / www.emspb.ru | ✅ ready |
|
||
| pilorama98.ru / www.pilorama98.ru | ✅ ready |
|
||
| labtools.ru / www.labtools.ru | ✅ ready |
|
||
| labtools.pro / www.labtools.pro | ✅ ready |
|
||
| tandemmebel.ru / www.tandemmebel.ru | ✅ ready |
|
||
| snolla.com / on.snolla.com / ics-artmaterials.snolla.com / pilorama98.snolla.com | ✅ ready |
|
||
| labtools.snolla.com / emspb.snolla.com / tandemmebel.snolla.com | ✅ ready |
|
||
| sestech.snolla.com / labtoolspro.snolla.com / artmone.snolla.com | ✅ ready |
|
||
| rimiz.ru / www.rimiz.ru / rimiz.snolla.com | ⚠ CMS-side 502/404, не migration defect — degraded pre-cutover [[#degraded-tenants]] |
|
||
| maljarka.tandemmebel.ru | ⚠ CMS-side 502 (см. выше) |
|
||
| 1 catch-all `*:80:` HTTP binding | retained для legacy/diagnostics |
|
||
|
||
Все 25 HTTPS bindings связаны с LE certs (R13, выпущены 2026-04-23, valid до 2026-07-22).
|
||
|
||
## Key files
|
||
|
||
- `C:\sites\snolla\` — 8.66 GB / 44725 files (source on windows-recovery-host)
|
||
- `\\80.64.31.36\C$\sites\snolla\` — destination (transferred 2026-05-23 23:08-23:33 via scp)
|
||
- `C:\Users\vitya\iis-backup-pre-ruvds\scp-snolla.log` — scp transcript
|
||
- `C:\ProgramData\ssh\administrators_authorized_keys` (RUVDS) — pubkey deployed for transfer
|
||
- `~/.ssh/ruvds-iis-migration` / `.pub` (source) — temp key pair (clean up post-cutover)
|
||
- `pass show ruvds-iis/full-env` — RDP creds (canonical secret store)
|
||
- `scripts/iis-migration-to-ruvds/01-ruvds-bootstrap.ps1` — RUVDS bootstrap (idempotent)
|
||
- `[[../.wiki/concepts/windows-server-2025-core-bootstrap]]` — bootstrap recipe (SMB section deprecated, см. Decisions log 2026-05-23 23:00)
|
||
|
||
## Decisions log
|
||
|
||
- **2026-05-23 09:37 (admin attempt):** RDP creds сохранены в `.secrets/ruvds-iis.env` plaintext, в repo tree → нарушение etap-2 secrets-discipline. Ретроспективно вынесены в `pass show ruvds-iis/full-env`, `.secrets/` + `*.env` + `*-log.txt` + `*-size.txt` добавлены в `.gitignore`.
|
||
- **2026-05-23 18:08 (admin attempt):** robocopy через UNC `\\80.64.31.36\sites\snolla\` упал exit 16. Initial root cause: SMB-share не создан + FW 445 не открыт на fresh Win Server 2025 Core. Зафиксировано в `windows-server-2025-core-bootstrap.md`.
|
||
- **2026-05-23 22:30 (session-recovery transfer attempt):** После bootstrap'а (FW+share созданы) SMB всё равно не reachable — TCP/445 не выходит **с home-ISP**. **Real root cause: outbound 445 блокирует ISP (стандартная анти-worm политика residential провайдеров RU). FW scoping на RUVDS-стороне корректен.** SMB recommendation в bootstrap-концепте **deprecated** — SSH/scp = canonical transfer-метод.
|
||
- **2026-05-23 22:35 (transfer):** OpenSSH.Server на RUVDS уже был installed (admin'ом раньше), sshd running. Открыл FW 22 scoped к source IP, deployed ed25519 pubkey в `C:\ProgramData\ssh\administrators_authorized_keys` (с правильным ACL — SYSTEM + Administrators only). scp -r `C:\sites\snolla` → `C:/sites/` отработал за ~25 мин (8.66 GB / 5 MB/s home uplink), exit 0, count+size MATCH (44725 files / 9302398880 bytes).
|
||
- **2026-05-23 23:18 (IIS recreate):** Default Web Site удалён. AppPool `snolla` (.NET v4.0, Integrated, ApplicationPoolIdentity), Website `snolla` с physicalPath `C:\sites\snolla` + catch-all binding `*:80:`. ACL — `IIS APPPOOL\snolla` + `IIS_IUSRS` Read на 46150 file entries. Local smoke с loopback `localhost` + `Host: kupimknigi.spb.ru` → 200 OK + правильный title.
|
||
- **2026-05-23 23:25 (external smoke through home network):** все 8 hostnames вернули "SNOLLA | Cloud CMS" default (28406 bytes), **не** per-tenant content. Внутри RUVDS (loopback) — корректный per-tenant content. Difference: external requests **из home network** теряют Host header через HTTP-aware middlebox (DPI/transparent proxy в OpenWRT/ISP path). SSH tunnel localhost:8888→RUVDS:80 → correct content. Тест из VDS (другая сеть) → correct content. **Conclusion: home-side outbound HTTP mutation; real end-users из других сетей не пострадают.**
|
||
- **2026-05-23 23:30 (HTTPS bindings):** Экспортировал 14 LE certs из traefik `acme.json` (`C:\Users\vitya\projects\docker\diskstation\traefik\letsencrypt\acme.json`) → openssl pkcs12 -export → PFX → Import-PfxCertificate на RUVDS → New-WebBinding `*:443:<hostname>` с SslFlags=1 (SNI) → AddSslCertificate by thumbprint. 25 HTTPS bindings live. Cert chain valid (LE R13), HTTP/2 auto-negotiated.
|
||
- **2026-05-23 23:35 (live smoke from VDS):** 7 prod hostnames → 200 OK + correct content; canonical bare→www 301s (CMS-side); 4 hostnames (`maljarka.tandemmebel.ru`, `rimiz.ru`, `www.rimiz.ru`, `rimiz.snolla.com`) → 502/404 — CMS-internal tenant mismatch (на source IIS:8089 они возвращают 200 default = тоже degraded). Не блокирует cutover.
|
||
- **2026-05-24 ~00:00 (DNS partial-cutover):** user flipped A `kupimknigi.spb.ru` в reg.ru — сначала на `89.253.255.94` (VDS Linux, ошибка), потом на `80.64.31.36` (RUVDS). Authoritative `ns1.reg.ru` отдаёт правильное; public resolver-cache (8.8.8.8=6h, 1.1.1.1=24h, Yandex=2h) держат старое до TTL expiry. Real end-users мигрируют postepenno over cache TTL. **TTL=86400 — slишком много. Recommendation: снизить до 300s в reg.ru для всех hostnames в scope ДО полного DNS swap'а.**
|
||
- **2026-05-24 ~12:00 (second cutover):** `emspb.ru` + `www.emspb.ru` DNS flipped на 80.64.31.36 (1.1.1.1 + Yandex + reg.ru уже отдают новое, 8.8.8.8 кешировал старое). Smoke через --resolve и через partially-cached real DNS → 200 OK c correct content.
|
||
- **2026-05-24 (scope narrow):** user-decision — `tandemmebel.ru` + `www.tandemmebel.ru` ОСТАЮТСЯ на windows-IIS на неопределённый срок. Cert на RUVDS уже импортирован, HTTPS binding existing — но DNS не свапаем. Source IIS `snolla` site нельзя decommission'ить пока tandemmebel на нём же (catch-all binding). Это разделяет migration на 24 hostnames мигрируют, 2 (`tandemmebel.ru` + www) остаются. Future migration tandemmebel — отдельная задача когда user решит.
|
||
- **MinIO pipeline caveat (carry-over от `[iis-cutover-to-vds-services]`):** RUVDS IIS coupled к windows-host imgproxy через DNS `imgproxy.kzntsv.site`. SPOF home machine остаётся для image-rendering. Verified post-migration: `Test-NetConnection imgproxy.kzntsv.site -Port 443` с RUVDS = OK. Image pipeline working but не resilient.
|
||
- **2026-05-25 (close-time DNS probe):** между cutover'ом 2026-05-24 и closure user тихо swap'нул ещё 5 пар hostnames через reg.ru. Реальное DNS состояние на 2026-05-25 11:30 MSK через `Resolve-DnsName -Server 8.8.8.8`:
|
||
- **На RUVDS 80.64.31.36** (9): `kupimknigi.spb.ru`, `emspb.ru` + `www.emspb.ru`, `pilorama98.ru` + `www.pilorama98.ru`, `labtools.pro` + `www.labtools.pro`, `rimiz.ru` + `www.rimiz.ru`. TTL на swap'нутых: 3600s (auth ns1.reg.ru).
|
||
- **На windows source 94.19.247.14** (16): `labtools.ru` + `www.labtools.ru`, `snolla.com` + 12× *.snolla.com (incl `rimiz.snolla.com`), `maljarka.tandemmebel.ru`, `tandemmebel.ru` + `www.tandemmebel.ru` (scope exception). TTL 86400s — не lowered.
|
||
- **Live smoke** через real DNS: `kupimknigi.spb.ru`/`www.emspb.ru`/`www.pilorama98.ru`/`www.labtools.pro` → 200 OK + correct per-tenant title. Bare-domain 3 redirects работают (emspb/pilorama98/labtools.pro). `rimiz.ru` / `www.rimiz.ru` → 404 — pre-existing CMS-defect (тоже 404 на source), не migration regression.
|
||
- **2026-06-05 (tandemmebel cutover — post-closure follow-up):** user (владелец tandemmebel) сам перенастроил DNS в reg.ru. Проверено `Resolve-DnsName`: `tandemmebel.ru` + `www.tandemmebel.ru` + `maljarka.tandemmebel.ru` → `80.64.31.36` (RUVDS) на authoritative ns1.reg.ru + 8.8.8.8 + 1.1.1.1 + Yandex. apex+CNAME сработал (www/maljarka follow apex автоматом). Smoke через real DNS: `tandemmebel.ru`/`www` → **200 OK** correct content; `maljarka.tandemmebel.ru` → **502** (pre-existing CMS-дефект, на source ровно так же — не регрессия cutover'а, см. Open questions). **Снимает прежнюю scope-exception 2026-05-24** (tandemmebel больше НЕ остаётся на windows). **Source НЕ заглушен** — user-decision держать как warm rollback: сайт `snolla` — общий catch-all, 11 `*.snolla.com` (incl `tandemmebel.snolla.com`) ещё резолвятся на `94.19.247.14`, полный `Stop-Website` невозможен; индивидуальное снятие 3 HTTPS-биндингов отложено до full-site decommission. Текущий баланс: **14 hostnames на RUVDS / 11 на windows-source**.
|
||
- **2026-06-05 (LE auto-renewal pipeline построен — win-acme):** на RUVDS поднят постоянный self-renewing HTTP-01 pipeline (win-acme v2.2.9). Один **25-SAN cert** (store WebHosting, Issuer LE YR2, valid до **2026-09-03**) установлен во все 25 SNI-биндинга; **scheduled task `win-acme-renew-snolla`** (SYSTEM, daily, renew 55д до expiry). **Закрывает дедлайн cert-expiry 2026-07-22** и снимает зависимость RUVDS от домашнего traefik по сертификатам. Главный gotcha: OWIN-catch-all CMS (`owin:HandleAllRequests=true`) перехватывал `/.well-known/acme-challenge/` → решено выносом challenge-пути в **отдельное IIS-приложение в пуле «No Managed Code»** + патч шаблона `C:\win-acme\Web_Config.xml` (`<remove name="Owin"/>`). Staging + prod валидация всех 25 хостов зелёная; живые HTTPS-эндпоинты отдают новый cert (проверено TLS-смоком, incl maljarka/rimiz). Скрипт `scripts/iis-migration-to-ruvds/03-ruvds-winacme.ps1` + шаблон `winacme-Web_Config.xml`. Полный recipe: [[../.wiki/concepts/winacme-iis-owin-catchall-http01]].
|
||
- **2026-06-05 (FULL CUTOVER достигнут):** проверка всех 25 hostname против authoritative ns1.reg.ru — **все → `80.64.31.36` (RUVDS), на windows-source authoritative не осталось ничего**. Вся зона `snolla.com` (apex + 10 субдоменов, независимые A-записи) переехала. Хвост кэша: `on.snolla.com` + `tandemmebel.snolla.com` ещё `94.19.247.14` на Google 8.8.8.8 (TTL 86400, дотекает; 1.1.1.1/Yandex уже RUVDS). RUVDS smoke: `snolla.com` 200 (default лендинг), `tandemmebel.snolla.com` 200 (tenant content). **Разблокирует decommission** `snolla` site + LE-renewal через win-acme+HTTP-01 (теперь challenge не отскочит на windows). **Source НЕ заглушен** — user-decision: 7-day soak warm rollback + ждём drain кэша. Целевой decommission ~2026-06-12; cert-renewal deadline ~2026-07-15 (certs expire 2026-07-22).
|
||
- **2026-05-25 (close decision):** user-decision close — несмотря на 16 hostnames pending DNS swap + source IIS:8089 still live + LE renewal pipeline pending + cleanup pending, **task закрывается** на этом этапе. Reason: Phase 1 (RUVDS infra + scp + IIS recreate + cert import + 9 DNS swaps) выполнен и live; остальное — coordination/wait/decommission character, не agent-work load. Future actions перечислены в Closure note ниже, не trackable как `iis-migration-to-ruvds` continuation.
|
||
|
||
## Open questions
|
||
|
||
- [ ] **DNS TTL = 86400 на kupimknigi.spb.ru** — снизить до 300s в reg.ru перед swap'ом остальных 24 hostnames. Это сократит cache-tail с 24h до 5 мин.
|
||
- [ ] **Source IIS state backup НЕ снят** (skip'нули — appcmd требует elevated source-shell которого не было). Acceptable risk — source IIS всё ещё running как rollback. Если RUVDS proves stable за 1 неделю → можно decommission source без forensic snapshot'а.
|
||
- [ ] **CMS-side 502/404 на 4 hostnames** — `maljarka.tandemmebel.ru` / `rimiz.ru` / `www.rimiz.ru` / `rimiz.snolla.com`. На source тоже не работают (return default page). Pre-existing dead-routes от `[iis-traefik-dead-routes-cleanup]` 2026-05-21. Изоляция не блокер для migration; защититься от cutover-blame — отдельная investigation если нужно.
|
||
- [ ] **Backup strategy для RUVDS** — расширить `vds-backup-rsync-kreknin` cron как третий source? Решение отложено до после full cutover.
|
||
- [x] **LE renewal на RUVDS** — ✅ DONE 2026-06-05 (win-acme HTTP-01, см. Decisions log + [[../.wiki/concepts/winacme-iis-owin-catchall-http01]]). Выбран Option A (win-acme + HTTP-01 после full cutover). Новый 25-SAN cert до 2026-09-03, авто-renewal через SYSTEM scheduled task. Дедлайн 2026-07-22 снят. ~~Историческая формулировка ниже:~~ текущие certs valid до 2026-07-22 (~60 дней). До expiry нужен permanent renewal pipeline:
|
||
- Option A: win-acme (wacs.exe) standalone-на-RUVDS с DNS-01 (manual TXT на reg.ru — нет reg.ru-plugin) или HTTP-01 (но только после DNS swap'а — иначе LE challenge bounce'нется на home).
|
||
- Option B: cron-job который re-extract'ит из traefik acme.json + scp upload + Import-PfxCertificate на RUVDS. Pollute source's traefik lifecycle.
|
||
- Recommendation: Option A win-acme + HTTP-01 после full cutover (~95 days margin до cert expiry — 60 days minus DNS-stabilization window).
|
||
- [ ] **Image-pipeline SPOF (post-cutover):** windows-host imgproxy остаётся single-point. Option B (local nginx-relay) / Option D (decompile DLL) — defer.
|
||
|
||
## Completed steps
|
||
|
||
- [x] **2026-05-22:** vendor research (`windows-hosting-vendor-research` 🟢) — RUVDS selected.
|
||
- [x] **2026-05-23 09:37:** RUVDS purchased + RDP creds saved (плюс post-hoc cleanup → `pass`).
|
||
- [x] **2026-05-23 18:08:** failed-robocopy attempt → finding закреплён.
|
||
- [x] **2026-05-23 22:00:** RUVDS bootstrap (IIS + sub-features + .NET 4.8 verify + URL Rewrite + FW 80/443 + SMB share + 445 rule). Idempotent script `scripts/iis-migration-to-ruvds/01-ruvds-bootstrap.ps1`.
|
||
- [x] **2026-05-23 22:35:** OpenSSH.Server + FW 22 + key-auth setup.
|
||
- [x] **2026-05-23 22:47-23:13:** scp transfer (8.66 GB / 44725 files, exit 0, count+size MATCH).
|
||
- [x] **2026-05-23 23:18:** IIS site `snolla` recreated на RUVDS + ACL grant.
|
||
- [x] **2026-05-23 23:25:** HTTP catch-all smoke — local (loopback) green, external (home) garbled by middlebox, external (VDS) green.
|
||
- [x] **2026-05-23 23:30:** 14 LE certs extracted from traefik acme.json → 14 PFX → 25 HTTPS bindings + SNI live.
|
||
- [x] **2026-05-23 23:35:** 7 prod hostnames live-smoked through VDS → 200 OK / correct content; 4 hostnames degraded pre-migration (acceptable).
|
||
- [x] **2026-05-24 ~00:00:** DNS swap kupimknigi.spb.ru → 80.64.31.36 (initial misroute to VDS corrected).
|
||
|
||
## Closure note (2026-05-25)
|
||
|
||
Task **closed 🟢** per user decision. Phase 1 — RUVDS infra + cert pipeline + 9 hostnames live — done.
|
||
|
||
**Что НЕ сделано** (не теряем — записано здесь, не в STATUS.md):
|
||
|
||
1. **16 hostnames still DNS на windows source** (94.19.247.14). Из них:
|
||
- 14 plan to migrate later: `labtools.ru` + `www.labtools.ru`, `snolla.com` + 12× *.snolla.com (incl `rimiz.snolla.com`, `maljarka.tandemmebel.ru`).
|
||
- ~~2 scope-exception: `tandemmebel.ru` + `www.tandemmebel.ru` — stays на windows-IIS~~ → **SUPERSEDED 2026-06-05: tandemmebel мигрировал на RUVDS** (см. Decisions log). Остаётся `tandemmebel.snolla.com` (snolla-алиас) на windows.
|
||
- Action: user manually swap A-records в reg.ru → 80.64.31.36 при готовности. Recommend pre-step: lower TTL 86400s → 300s in advance to shrink cache-tail.
|
||
- Нет reg.ru API key в pass entries — agent НЕ может сделать swap autonomously.
|
||
|
||
2. **Source IIS НЕ decommission'ен.** `localhost:8089` snolla site + port 80 catch-all still live на windows-recovery-host. Не трогать пока 16 hostnames на 94.19.247.14. После full cutover + 7-day soak — `Stop-Website snolla` + archive `C:\sites\snolla` (8.66GB) → kreknin.
|
||
|
||
3. ~~**LE renewal pipeline НЕ построен.**~~ → **DONE 2026-06-05** (win-acme HTTP-01 после full cutover, как и рекомендовалось). Новый 25-SAN LE cert до 2026-09-03 + SYSTEM auto-renew task. Recipe: [[../.wiki/concepts/winacme-iis-owin-catchall-http01]].
|
||
|
||
4. **Temp SSH key cleanup не сделан.** Оставлены (могут понадобиться для будущих push'ей на RUVDS):
|
||
- `~/.ssh/ruvds-iis-migration` + `.pub` на source workstation
|
||
- FW rule `ssh-from-source` + `smb-from-source` на RUVDS
|
||
- `C:\ProgramData\ssh\administrators_authorized_keys` на RUVDS
|
||
- Action defer до post-decommission.
|
||
|
||
5. **Wiki concept updates не сделаны** (low-priority, value beyond this task):
|
||
- `.wiki/concepts/windows-server-2025-core-bootstrap.md` — SMB section deprecate, add HTTP-middlebox warning + HTTP/2 note.
|
||
- New `.wiki/concepts/traefik-acme-json-to-iis-cert-import.md` — recipe extract+pkcs12+Import-PfxCertificate+AddSslCertificate.
|
||
|
||
6. **CMS-defect rimiz / maljarka** — 4 hostnames 502/404 pre-existing, не migration regression. На source ровно так же. Investigation отдельная история, не блокирует closure.
|
||
|
||
**Image-pipeline SPOF carry-forward:** RUVDS IIS зависит от `imgproxy.kzntsv.site` (windows-host imgproxy). SPOF home machine для image-rendering. Не решено в этой migration — задокументировано как separate `image-pipeline-resilience` (см. brainstorm option B local nginx-relay / option D decompile DLL — defer).
|
||
|
||
**Rollback (если RUVDS пропадает):** revert A-records 9 swapped hostnames в reg.ru на 94.19.247.14. Source IIS:8089 + traefik routes live = instant fallback.
|
||
|
||
## Notes
|
||
|
||
- **Win Server 2025 Core** — GUI-less, RDP-only, drag-n-drop через `mstsc /admin` Local Drives redirect; scp/PSSession предпочтительнее RDP-copy.
|
||
- **2GB RAM** — w3wp ~330 MB при cold start, monitor under load. Возможно нужен `RecyclingPeriodicRestartMemory 200MB`.
|
||
- **HTTP middlebox в home network** mangles Host header for direct external HTTP — affected smoke testing, не end-users.
|
||
- **Migration transitional** — long-term snolla-on-node makes IIS obsolete. Не over-engineer.
|
||
|
||
<!-- created-by: user-decision / 2026-05-22 / trigger: RUVDS purchased -->
|
||
<!-- attempted-by: admin-session / 2026-05-23 09:37-18:08 / dropped after failed-robocopy -->
|
||
<!-- session-recovery: vitya / 2026-05-23 evening / SMB-ISP-block detected → SSH/scp pivot → 8.66 GB transferred → IIS recreated → 25 HTTPS SNI bindings → kupimknigi DNS flipped 2026-05-24 / soak window 24h+ -->
|