--- title: IIS Host Migration Session 2026-05-19 type: source tags: [migration, iis, windows, recovery, traefik] ingested: 2026-05-19 raw_path: ../../.tasks/iis-on-host-migration.md updated: 2026-05-21 --- # IIS Host Migration Session 2026-05-19 Перенос основного CMS-сайта (`MoreThenCms.Web`, обслуживает 11 клиентских доменов через Host header) с IIS внутри [[snolla-recovery-vm]] на нативный IIS [[windows-recovery-host]]. Цель — убрать VBox как слой нестабильности. ## Хронология **Старт сессии** (после [[nas-recovery-session-2026-05-18]]) — VM один раз отвалилась сетью, оживили через `VBoxManage guestcontrol` + `ipconfig /release /renew` (recipe в [[vbox-windows-stability-tuning]] подтверждён ещё раз). **Phase 1 — discovery (read-only):** - В VM через SSH (NAT-forward 127.0.0.1:8022, не bridged 192.168.1.15 — это устарело): `appcmd list site/apppool/app/vdir /xml`. - На хосте: `Get-WindowsOptionalFeature -Online IIS-*` через elevated PS — все нужные фичи (`IIS-WebServer`, `IIS-ASPNET45`, `IIS-NetFxExtensibility45`, `IIS-ISAPIFilter`, `IIS-ManagementConsole`, `IIS-IIS6ManagementCompatibility`, `IIS-Metabase`) уже установлены. `WebAdministration` PS-module грузится из admin-PS. - В источниках на хосте (`grep`): hardcoded `C:\inetpub\wwwroot\` в коде CMS нет — всё через `ConfigurationManager.AppSettings["sitePath"]`. **НО** в prod Web.config (из backup `vm-sites\`) ключ `sitePath` явно проставлен абсолютным путём — патч в Phase 2 необходим. **Phase 1 находки (важные расхождения с wiki):** - `Snolla.IdentityManager` в VM **на :8089, не :80** (вики говорила :80). Sub-apps stostayer.old `/calc`, `/price`, `/price/tireService` существуют с отдельными app pools. - `C:\stayer\Snolla.IdentityManager\Web.config` указывает на **`SRV-1135520-1\SQLEXPRESS` + Integrated Security** — мёртвый внешний сервер. Сайт точно не работает, deploy-артефакт без живого консьюмера. - `C:\stayer\MoreThenCms.Web\Web.config` (stostayer) использует **внешний production MSSQL `89.253.219.2,1433`** с user `stostayer` — это **другая инфраструктура**, не наш контейнер. **Phase 2 — миграция (admin PS):** - `Default Web Site` остановлен (`autoStart=false`). - `robocopy` из `C:\nas-recovery\vm-sites\` → `C:\sites\MoreThenCms.Web` (8.7 GB / 44.7k файлов / 1m13s) и → `C:\stayer\` (2.2 GB / 13.9k файлов / 21s). - Web.config patch только для `C:\sites\MoreThenCms.Web\Web.config`: `sitePath` `C:\inetpub\wwwroot\MoreThenCms.Web\` → `C:\sites\MoreThenCms.Web\` + `Data Source=10.0.2.2` → `Data Source=localhost`. UTF-8 with BOM через `[System.IO.File]::WriteAllText` (паттерн из [[cms-config-rewrite-pattern]]). - 3 AppPool (`.NET v4.0` Integrated, `ApplicationPoolIdentity`): `MoreThenCms.Web`, `stostayer`, `stostayer.old`. - 3 IIS-сайта: `MoreThenCms.Web *:80` (catch-all), `stostayer *:8090`, `stostayer.old *:8091`. - ACL `icacls /grant 'IIS AppPool\:(OI)(CI)M' /T` на каждый physical root. - Skip: `Snolla.IdentityManager` (по решению — публично не нужен), 3 sub-apps stostayer.old (по решению). - Smoke test через `http://localhost/` с Host header: все 11 хостов отвечают (большинство 302 redirect — CMS работает). `rimiz.ru` — 404 (CMS-side, не инфра). stostayer/oldstostayer на :8090/:8091 — timeout, **в текущей сессии не разбирались**. **Phase 3 — traefik switch (file-provider auto-reload):** - Backup `*.yml.bak-phase3-2026-05-19` для 11 файлов в `C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\`. - Patch: `host.docker.internal:18080` → `host.docker.internal:80` (11 yml: snolla, rimiz, labtools, labtoolspro, pilorama98, tandemmebel, emspb, kupimknigi, maljarka, sestech, isc-artmaterials). UTF-8 без BOM. - **Не трогали** `stostayer.yml` (`:18180`) и `oldstostayer.yml` (`:18181`) — stayer-сайты остаются на VM. - Public smoke через `https://localhost:4443/` с Host header: **10/11 → HTTP 200**. Только `rimiz.ru` → 404 (как и в локальном тесте). ## Результат Production-трафик 10 главных доменов теперь идёт **полностью без VM**: client → OpenWRT → traefik → host-IIS native → MSSQL container на хосте. VM продолжает обслуживать только stostayer/oldstostayer через старые NAT port forwards (`:18180/:18181`). ## Открытые вопросы / нюансы - **`rimiz.ru` отдаёт 404** на host-IIS (и на VM до миграции тоже отдавал?). CMS-routing не знает этот host. Проверить mapping в БД / CMS-сайт-таблице. - **stostayer + stostayer.old timeout** на host-IIS (`http://localhost:8090/`, `:8091`). Возможно cold-start ASP.NET, возможно реальный bug. Сейчас не разбирали — traefik для них всё ещё на VM. - **Snolla.IdentityManager не мигрирован** — если что-то внутри CMS дёргает его через `localhost:8089`, может тихо ломаться (пока симптомов не видно). - **VM можно глушить** только после: либо доделать stostayer на host, либо явное решение не мигрировать stayer на этот хост. - **Phase 5 не сделан**: URL Rewrite + ARR (фикс `:4443` в редиректах CMS), Application Initialization (warm-start), log rotation `C:\inetpub\logs\`. ## Технические артефакты - `.tasks/iis-on-host-migration.md` — детальный план фаз, обновлён со статусом Phase 1-3 = done. - Backup конфигов traefik: `*.yml.bak-phase3-2026-05-19`. - Скрипты Phase 2 и Phase 3 — в истории чата сессии (не сохранены отдельным файлом, идемпотентны на повторный запуск). ## Phase 4 — реорг `C:\sites\` + перенос stayer'ов (вечер 2026-05-19) Пользователь возмутился разбросом (`C:\sites\` только для snolla, `C:\stayer\` для stayer). Реорганизация ради единой иерархии: - `C:\sites\MoreThenCms.Web` → `C:\sites\snolla` (rename) - `C:\stayer\MoreThenCms.Web` → `C:\sites\stostayer` (move) - `C:\stayer\stostayer.old` → `C:\sites\stostayer.old` (move) - `C:\stayer\Snolla.IdentityManager` → `C:\sites\snolla-identity-manager` (move + kebab-rename) - Удалены: `C:\stayer\Mis.StoStayer.{Price,TireService,Calculator}.{Api,Web}` (sub-apps не нужны). - `C:\stayer\` полностью удалён. IIS sync: - Site `MoreThenCms.Web` → переименован в `snolla` (`Set-ItemProperty IIS:\Sites\... -Name name`) - AppPool `MoreThenCms.Web` — **rename невозможен in-place**, пересоздан как `snolla` с тем же набором properties (.NET v4.0 Integrated, `ApplicationPoolIdentity`), site rebound, старый pool удалён. - `physicalPath` всех 3 sites обновлён под `C:\sites\`. - Web.config sitePath patches: - `C:\sites\snolla\Web.config`: `C:\sites\MoreThenCms.Web\` → `C:\sites\snolla\` - `C:\sites\stostayer\Web.config`: `C:\stayer\MoreThenCms.Web\` → `C:\sites\stostayer\` - ACL re-grant + cleanup stale ACE для `IIS AppPool\MoreThenCms.Web`. ## Phase 5 — stostayer.old DB-conn патч (запутанная история conn-string'ов в stayer\) Локальный smoke `:8091` падал в timeout. Причина: `C:\sites\stostayer.old\web.config` имел `Data Source=10.0.2.2` (адрес VBox NAT gateway, на хосте не резолвится) с user=`snolla` — фактически идентичная нашему MSSQL контейнеру конфигурация, только адрес неправильный. Patch: `10.0.2.2` → `localhost`. После — `:8091` → 200. Это **наша ошибка** Phase 2 — пропустили patch этого файла, так как фокус был только на `MoreThenCms.Web\Web.config`. ## Phase 6 — stostayer DB-conn миграция на новый сервер (`www.stostayer.ru`) Локальный smoke `:8090` тоже timeout — но по другой причине. `C:\sites\stostayer\Web.config` указывал на `89.253.219.2,1433` (внешний production MSSQL), оказался **полностью недоступен**: TCP timeout, ping fail, traceroute затухает на 8-м hop у `139.45.230.171`. На VM (через тот же путь) тоже не работал — пользователь подтвердил. Пользователь предоставил новые creds: `www.stostayer.ru,1433` / `stayer_site` / `^I9D)LB)DK)8J#xBDG$t}W&_ioaT!M!LF`. Тест из не-elevated PS: TCP reachable, SQL login OK (SQL Server 2022). Patched Web.config. **Pitfall:** после patch IIS отдавал HTTP 500. Причина — символ `&` в пароле, который **в XML является зарезервированным**. ASP.NET Web.config XML парсер падал на парсинг conn-string. Fix: `&` → `&` (XML entity escape). См. [[webconfig-password-xml-escape]]. ## Phase 7 — отключение traefik routes для stayer'ов Пользователь решил: stayer'ы наружу светить не нужно вообще (даже после миграции на хост). `stostayer.yml` и `oldstostayer.yml` переименованы в `*.yml.disabled` — traefik file-provider не подхватывает, route gone. Site'ы на хосте `:8090/:8091` остаются для возможного внутреннего использования. Backup yml для этих изменений: `*.yml.bak-stayer-switch-2026-05-19` (содержит вариант с переключением на `:8090/:8091` — на случай если решим включить обратно). ## Phase 8 — VM savestate После того как 100% prod-трафика идёт через host-IIS, VM `snolla-recovery` заморожена через `VBoxManage controlvm "snolla-recovery" savestate`. VMState = `saved`. Конфигурация и диски сохранены — resume за 5-10 сек если что-то понадобится. Не удалена. ## Финальное состояние (2026-05-19 конец сессии) ``` C:\sites\ ├── snolla\ (was MoreThenCms.Web in C:\inetpub\wwwroot, was in vm-sites\wwwroot\MoreThenCms.Web) ├── stostayer\ (was C:\stayer\MoreThenCms.Web) ├── stostayer.old\ (was C:\stayer\stostayer.old) └── snolla-identity-manager\ (was C:\stayer\Snolla.IdentityManager — deploy artifact, без живого consumer'а) IIS sites/pools (имя=имя=пуло): snolla *:80 → C:\sites\snolla (pool snolla, .NET v4.0 Integrated, AppPoolIdentity) stostayer *:8090 → C:\sites\stostayer (pool stostayer) stostayer.old *:8091 → C:\sites\stostayer.old (pool stostayer.old) Connection strings: snolla: Data Source=localhost → MSSQL container на хосте (DB MoreThenCms) stostayer: Data Source=www.stostayer.ru,1433 (user stayer_site) → внешний SQL Server 2022 stostayer.old: Data Source=localhost → MSSQL container на хосте (DB stostayer, user snolla) Traefik active routes: 10 главных доменов (snolla.com и др.) + rimiz.ru → host:80. Stayer routes (stostayer.snolla.com, old.stostayer.ru) DISABLED — приватны. Mёртвые: disk.yml, dsm.yml (192.168.1.10). VM snolla-recovery: VMState=saved (frozen, ~92 GB на диске + ~3 GB savestate RAM dump). ``` ## Открытые вопросы (что унесли в следующую сессию) - **`rimiz.ru` отдаёт 404** на host-IIS. CMS-routing не знает этот host — нужно копнуть БД (таблица CMS-сайтов, mapping host header → site). - **Snolla.IdentityManager** на хосте: папка перенесена, но IIS-сайт не создавался (по решению). Если CMS-код где-то дёргает `localhost:8089` или подобное — будет тихий fail. Симптомов пока нет. - **Phase 5 (из старого плана)**: URL Rewrite + ARR для фикса `:4443` в редиректах, Application Initialization (warm-start), log rotation `C:\inetpub\logs\`. Не блокеры. - **VM-cleanup**: если через ~неделю стабильной работы host'а проблем не будет — `VBoxManage unregistervm "snolla-recovery" --delete` освободит ~92 GB. NAT port forwards в OpenWRT (host:18080/18180/18181/18189) можно удалить. ## Связано [[recovery-architecture-snapshot]] обновлён под полностью-host chain. [[snolla-recovery-vm]] — VMState=saved, все sites unused. [[windows-recovery-host]] — финальный layout `C:\sites\`. [[webconfig-password-xml-escape]] — новый concept про XML entity escape в conn-string'ах. [[cms-config-rewrite-pattern]] — UTF-8 BOM подтверждён ещё много раз. --- ## Phase 9 — RЕVERT (вечер 2026-05-19, после провала) **Что случилось:** через ~10 минут после моего commit'a "done" пользователь открыл `https://www.pilorama98.ru/` → `502 Bad Gateway`. Дальше остальные домены показали `TOO_MANY_REDIRECTS`. **Root cause** (расписан полностью в [[iis-migration-2026-05-19-postmortem]]): - Phase 3 я patched traefik backend `host.docker.internal:18080` → `host.docker.internal:80`. - Внутри traefik-контейнера `host.docker.internal:80` через Docker Desktop NAT резолвится **обратно в сам traefik** на его HTTP entrypoint :80 (potential Docker publish-port loopback gotcha). - traefik `https.yml` имеет http-catchall middleware `redirect-to-https` → traefik отвечает 301 на свой же запрос → loop. **Реактивная цепочка ошибок** (~1 час): - Restart-WebAppPool, nuke workers, docker restart traefik → only sделали хуже (503). - Patched `host.docker.internal:80 → 192.168.1.143:80` → тот же loop (LAN-IP через NAT тоже возвращается в traefik). - Patched на `:8088` + добавил IIS binding → нужен `Stop+Start Website` чтобы binding applied, изначально забыл → IIS не listen → 503. - Думал что 301 от CMS (Pitfall 5 X-Forwarded-Proto), копал CMS код — реальный источник был самим traefik (headers без `Server: Microsoft-IIS` подсказывали). **Revert (выполнен):** - `VBoxManage startvm "snolla-recovery"` — VM поднята из savestate (~10s + 90s warmup + recipe `ipconfig /release /renew` для застрявшего network adapter из [[vbox-windows-stability-tuning]]). - 11 главных yml восстановлены из `*.bak-phase3-2026-05-19` (`host.docker.internal:18080` → VM). - Stayer yml: `stostayer.yml.disabled / oldstostayer.yml.disabled` удалены, восстановлены из `*.bak-stayer-switch-2026-05-19` (`:18180/18181` → VM). - `docker restart traefik` (file-provider не подхватил reload автоматом). - Public smoke через VM-chain → 7/11 хостов отвечают (2 c 200, 5 с CMS-side редиректами на canonical — нормально для CMS-логики); проверено пользователем в браузере → работает. ## Финальное состояние (после revert) Production снова на VM-chain (как было в начале сессии 2026-05-19). На хосте осталось: - `C:\sites\{snolla, stostayer, stostayer.old, snolla-identity-manager}` — ~11 GB, **inert** (не на prod-пути) - IIS sites/pools `snolla, stostayer, stostayer.old` — не получают трафика (traefik backend назад на VM) - traefik backups: `*.bak-phase3-2026-05-19`, `*.bak-stayer-switch-2026-05-19`, `*.bak-hostip-2026-05-19` - VM `snolla-recovery` — running, IIS активен, обслуживает 11 главных доменов + 2 stayer публично через port forwards. **Артефакты для следующей попытки** (НЕ удалять): - Web.config'и в `C:\sites\` пропатчены правильно (sitePath, conn-strings, stostayer на новый DB) — можно переиспользовать. - IIS sites/pools уже настроены — переключение требует только traefik backend patch + tested correctly. - Recipe для правильной миграции — в [[iis-migration-2026-05-19-postmortem]]. --- ## Phase 10 — Attempt 2 (вечер 2026-05-19, после прочтения post-mortem) Повторная попытка миграции, выполненная **по recipe из [[iis-migration-2026-05-19-postmortem]]**. Все 5 главных правил соблюдены, миграция прошла без incidents. ### Что сделано 1. **Read-only sanity (без prod-touches):** VM running confirmed, port-scan на хосте показал `:18090` и `:8089` свободны, traefik dir contains 4 серии bak-stamp'ов сохранённых от attempt 1, IIS sites/pools (`snolla, stostayer, stostayer.old`) живы. 2. **Backend port: `:8089`** (recipe-A — НЕ `:80`, НЕ совпадает с traefik publish `:8000/:4443/:8080`, НЕ совпадает с VM NAT forwards `:18080/:18180/:18181`). Выбор user'а (был кандидат `:18090`, user предпочёл `:8089`). 3. **Atomic revert plan ДО старта** (recipe-E): новая bak-серия `.bak-pre-attempt2-2026-05-19` для всех 13 yml (11 cms + 2 stayer), paste-ready команда восстановления записана в `.tasks/STATUS.md`. 4. **IIS binding** `snolla *:8089` добавлен через elevated PS (`New-WebBinding` + `Stop-Website; Start-Website` — recipe-A note: без restart binding не активируется). 5. **Loop-detect через `docker exec`** (recipe-A + recipe-F): `docker exec traefik wget --spider -S --header="Host: emspb.ru" http://host.docker.internal:8089/` → first response `301 → http://www.emspb.ru/` от `Server: Microsoft-IIS/10.0`. `host.docker.internal` resolves to `192.168.65.254:8089` — это Docker Desktop host gateway, **не traefik publish-port**. NO NAT loop. См. [[docker-host-loopback-detect]] про общую технику. 6. **Smoke test с `-MaximumRedirection 0`** (recipe-B): `curl.exe -k -I --max-redirs 0 -H "Host: emspb.ru" https://localhost:4443/` → first response `301`, `Server: Microsoft-IIS/10.0`, `Location: http://www.emspb.ru/`. Это **expected CMS canonical redirect** (та же логика что была на VM). 7. **Canary atomic** — patched ОДИН yml (`emspb.yml`), file-provider auto-reload, public smoke clean → 📱 **phone-test с мобильного интернета** (recipe-C) → `https://emspb.ru/` открылось → ✅. 8. **Second canary** — `labtools.yml`, smoke + phone-test → ✅. 9. **Batch patch** оставшихся 9 cms yml (snolla, rimiz, labtoolspro, pilorama98, tandemmebel, kupimknigi, maljarka, sestech, isc-artmaterials) одним PS-блоком (recipe-G: batch не item-by-item) → smoke 20 hostnames → все `Server: Microsoft-IIS/10.0` ✅. 10. **Stayer routes — disabled** (re-confirmed Phase 7 decision): user подтвердил «stayer'ы локальные, через traefik наружу не светят». Rename `stostayer.yml → stostayer.yml.disabled`, `oldstostayer.yml → oldstostayer.yml.disabled`. `docker restart traefik` (file-provider не подхватил deletion auto-reload). Verify: `stostayer.snolla.com`/`old.stostayer.ru` → `404 text/plain` (traefik no-route). Host IIS sites `:8090`/`:8091` остаются live для прямого/локального доступа. 11. **VM running parallel** (recipe-D): VM **НЕ savestate'ить** минимум 24h+. Освободит RAM/disk только после подтверждённой стабильности host'а. ### Pitfall found and resolved - **WinHTTP proxy strips Server header.** Локальный `curl.exe http://localhost:8090/` returned response без `Server: Microsoft-IIS/10.0` и без `X-Powered-By: ASP.NET` (плюс с `Proxy-Connection: keep-alive`) — выглядело как «не IIS отвечает». Это привело к moment'у panic. Но `docker exec traefik wget ...` (real production path) **показал headers корректно**. Вывод: для loop-detect / IIS-confirmation тестов **использовать `docker exec` из traefik container**, не Windows `curl.exe` — последний ходит через Windows-уровневый proxy который headers вырезает. ### My mistake during this session - Я неверно интерпретировал «мигрировать stayer'ов» как «patch traefik backend → host:8090/8091» (т.е. пускать через traefik наружу). User имел в виду «host IIS уже на :8090/:8091, traefik routes должны быть **DISABLED**». Сделал prod-changing patch → user интервент-stop → revert + rename `.yml.disabled`. См. memory `feedback-migrate-semantics`. Lesson — переспрашивать semantics для internal/low-traffic сервисов перед prod-changing. ### Финальный chain (после attempt 2) ``` Клиент (browser) → DNS → 94.19.247.14 (public IP) → OpenWRT NAT 443 → 192.168.1.143:4443 → traefik:4443 (TLS termination) → match Host → одно из 11 cms-yml → backend → http://host.docker.internal:8089/ ↳ Docker Desktop host gateway 192.168.65.254:8089 → Windows host IIS site `snolla` (*:80 + *:8089 bindings) → C:\sites\snolla\, .NET Framework 4.8.1, ApplicationPoolIdentity → conn → MSSQL container на host:1433 ← HTTP response ``` Stayer chain: traefik routes disabled. `stostayer.snolla.com` / `old.stostayer.ru` → traefik 404. Host IIS sites `:8090`/`:8091` живут для локального доступа. VM `snolla-recovery`: **running parallel** (24h+ soak), backend в traefik больше не используется — VM NAT port forwards (`:18080/:18180/:18181`) холостые. ### Артефакты этой сессии - `.tasks/STATUS.md` — статус task'а 🔴 active. - traefik backups в `data/custom/`: `*.yml.bak-pre-attempt2-2026-05-19` для 13 yml, плюс `stostayer.yml.disabled`, `oldstostayer.yml.disabled`. - `feedback-migrate-semantics` memory — урок для будущих сессий. - `concepts/docker-host-loopback-detect.md` — новый concept для loop-detect technique. ## Что осталось open - **24h+ soak** до savestate VM (минимум до утра 2026-05-20, лучше 48h до 2026-05-21). - **VM cleanup**: после soak — `savestate` (освободит RAM), позже `unregistervm --delete` (освободит ~92 GB). - **Port forwards OpenWRT** (`host:18080/18180/18181/18189` → VM) — больше не нужны, удалить позже. - **rimiz.ru / ics-artmaterials.com 404** — CMS-side routing issues, не инфра. Открытым. - **X-Forwarded headers** (`:4443` в redirect URL) — известный bug [[traefik-on-windows-docker-desktop]] Pitfall 5, отдельная задача. ## Phase 11 — close-out 2026-05-21 (~36h soak) Soak window 2026-05-19 22:00 MSK → 2026-05-21 ~08:00 MSK ≈ 36 hours. Verify: - **Traefik:** `Up 38 hours` (`docker ps`) — zero restarts, zero unscheduled bouncing. - **w3wp.exe** (IIS worker для `snolla` site): PID 10432, CreationDate 2026-05-20 23:19:21 MSK, uptime 8.8h. Это **scheduled IIS app pool recycle** (default 1740 min ≈ 29h), не crash — нормальное поведение pool'а после ~29h работы. Память 522 MB. - **HTTPS smoke** 11 заявленных hosts через `docker exec traefik wget --spider --server-response https:///`: | Host | Code | Server | Verdict | |---|---|---|---| | `emspb.ru` | 301→www | Microsoft-IIS/10.0 | ✅ | | `snolla.com` | 301→on.snolla | Microsoft-IIS/10.0 | ✅ | | `on.snolla.com` | 200 | Microsoft-IIS/10.0 | ✅ | | `pilorama98.ru` | 301→www | Microsoft-IIS/10.0 | ✅ | | `labtools.ru` | 301→www | Microsoft-IIS/10.0 | ✅ | | `labtools.pro` | 301→www | Microsoft-IIS/10.0 | ✅ | | `tandemmebel.ru` | 301→www | Microsoft-IIS/10.0 | ✅ | | `kupimknigi.spb.ru` | 200 | Microsoft-IIS/10.0 | ✅ | | `maljarka.ru` | — | DNS bad address | ⚠️ off-infra (nslookup 8.8.8.8 → no A record) | | `sestech.ru` | — | TLS cert `CN=*.domainparking.ru` (expired 2026-05-13) | ⚠️ off-infra (domain parking provider) | | `ics-artmaterials.com` | 200 | `nginx-reuseport/1.21.1` | ⚠️ off-infra (A→87.236.16.28, мигрирован на сторонний WP-хостинг) | **Итог:** 8/8 наших sites зеленые. 3 hostname'а из исходного списка task'а оказались **уже не на нашей инфраструктуре** — DNS либо снят, либо переключен на сторонние хостинги. Это не regression миграции; эти domains надо просто убрать из traefik/IIS routing'а как dead. Decisions: - iis-host migration close-out: **DONE 2026-05-21**. - VM `snolla-recovery`: готова к savestate (soak passed, zero rollbacks). User-side savestate решает отдельно. - 3 off-infra domains: новая задача — `iis-traefik-dead-routes-cleanup` (low prio). ## Артефакты Phase 11 - `.tasks/STATUS.md` — task → 🟢 done. - (existing) traefik bak-серия `.bak-pre-attempt2-2026-05-19` — оставить ещё неделю (atomic revert на случай unforeseen regression).