From 19422352ba2c378dddd6819f77212f03e989fa44 Mon Sep 17 00:00:00 2001 From: vitya Date: Tue, 19 May 2026 11:07:38 +0300 Subject: [PATCH] docs(.wiki): ingest NAS recovery session 2026-05-18/19 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 15-hour cross-hypervisor recovery from WD40EFAX SMR RAID5 cascade failure. 5 entities + 7 concepts + 1 source documenting: - Root cause: WD40EFAX SMR cascade in 3-disk RAID 5 - Hyper Backup .hbk structure + SFTP-jail / ACL workarounds - OVA from Synology VMM (KVM) → VirtualBox: SCSI→SATA, Hyper-V driver disable, paravirt=kvm, GA install, NAT switch - MSSQL restore via named volume + chown, sqlcmd -x, mssql-conf - Web.config UTF-16 vs UTF-8 BOM IIS 500.19 trap - Traefik on Windows DD: configFile, named volume for acme.json, file-provider as docker.sock workaround - Snapshot of current recovery architecture + SPOF list - Placeholder for future resilient-architecture work Plus .tasks/nas-recovery.md and STATUS.md updates closing the task. Co-Authored-By: Claude Opus 4.7 (1M context) --- cms-config-rewrite-pattern.md | 125 +++++++++++++++++ future-resilient-architecture-goals.md | 100 +++++++++++++ hyper-backup-structure-and-recovery.md | 101 ++++++++++++++ mssql-container-data-restore.md | 151 ++++++++++++++++++++ recovery-architecture-snapshot.md | 127 +++++++++++++++++ traefik-on-windows-docker-desktop.md | 185 +++++++++++++++++++++++++ vbox-windows-stability-tuning.md | 140 +++++++++++++++++++ wd40efax-smr-cascade.md | 76 ++++++++++ 8 files changed, 1005 insertions(+) create mode 100644 cms-config-rewrite-pattern.md create mode 100644 future-resilient-architecture-goals.md create mode 100644 hyper-backup-structure-and-recovery.md create mode 100644 mssql-container-data-restore.md create mode 100644 recovery-architecture-snapshot.md create mode 100644 traefik-on-windows-docker-desktop.md create mode 100644 vbox-windows-stability-tuning.md create mode 100644 wd40efax-smr-cascade.md diff --git a/cms-config-rewrite-pattern.md b/cms-config-rewrite-pattern.md new file mode 100644 index 0000000..7740130 --- /dev/null +++ b/cms-config-rewrite-pattern.md @@ -0,0 +1,125 @@ +--- +title: Web.config rewrite — кодировка и connection-string patterns +type: concept +tags: [iis, webconfig, encoding, powershell, traceback] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# Web.config rewrite на Windows: encoding pitfall + +## Сценарий + +После cross-host миграции CMS в VM, нужно переписать connection strings (`192.168.1.10` → `10.0.2.2` или другой host). Делается через SSH/PowerShell `Get-Content`+`-replace`+`Set-Content`. + +## Pitfall: Set-Content без -Encoding + +```powershell +# WRONG — на PowerShell 5.1 пишет UTF-16 LE +(Get-Content $f -Raw) -replace 'old', 'new' | Set-Content $f -NoNewline +``` + +**Симптом** после этого: +- IIS возвращает `HTTP 500.19 - Internal Server Error`, "Файл конфигурации создан в неправильном формате XML", error code `0x8007000d`. +- В детале: первые строки файла показывают артефакты `????????` (UTF-16 в окне UTF-8 ожидаемой проверки). + +**Корень:** PowerShell 5.1 (Windows встроенный) `Set-Content` без `-Encoding` использует **default = UTF-16 LE** (Unicode). IIS ожидает UTF-8 (BOM или без BOM). + +## Правильный способ + +```powershell +$utf8WithBom = New-Object System.Text.UTF8Encoding($true) +$content = Get-Content $file -Raw +$new = $content -replace 'old', 'new' +[System.IO.File]::WriteAllText($file, $new, $utf8WithBom) +``` + +Или, если хочется без BOM: +```powershell +$utf8NoBom = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText($file, $new, $utf8NoBom) +``` + +ASP.NET / IIS работают и с BOM, и без — но для дефолтного XML конфига Microsoft предпочитает с BOM. + +### PowerShell 7+ workaround + +В Core: `Set-Content -Encoding utf8` пишет UTF-8 БЕЗ BOM (отличается от PS 5!). `-Encoding utf8BOM` — с BOM. + +## Connection string patterns в CMS + +Нашли в `MoreThenCms.WebUI/Web.config` source (development): + +```xml + +``` + +В production (внутри OVA, после восстановления): + +```xml + +``` + +То есть в prod CMS использует **SQL login `snolla`**, не Integrated Security. Сам логин остался в `master.mdf` после восстановления — никаких дополнительных шагов не понадобилось. + +## Mass-edit нескольких сайтов + +Под Windows VM было 4 Web.config с одинаковым паттерном: + +- `C:\inetpub\wwwroot\MoreThenCms.Web\Web.config` +- `C:\inetpub\wwwroot\Snolla.IdentityManager\Web.config` +- `C:\stayer\stostayer.old\web.config` +- (плюс `C:\stayer\MoreThenCms.Web\` — main stostayer без 192.168.1.10 reference) + +Скрипт через SSH в VM: + +```powershell +$files = @( + 'C:\inetpub\wwwroot\MoreThenCms.Web\Web.config', + 'C:\inetpub\wwwroot\Snolla.IdentityManager\Web.config', + 'C:\stayer\stostayer.old\web.config' +) +$utf8WithBom = New-Object System.Text.UTF8Encoding($true) +foreach ($f in $files) { + $content = Get-Content $f -Raw + $new = $content -replace [regex]::Escape('192.168.1.10'), '10.0.2.2' + [System.IO.File]::WriteAllText($f, $new, $utf8WithBom) +} +# затем +iisreset +``` + +## Storage providers (Azure / MinIO) + +В `appSettings` нашли: + +```xml + + + + + + +``` + +Используется **Azure Storage SDK с custom endpoint**. В production endpoint указывает на **MinIO** (S3-compat но **не** Azure-compat — пользователь упомянул "не так всё" и обещал пояснить). Точная схема подключения — TBD, надо изучать `MoreThenCms.FileStorage.Azure.AzureCloudStorage` класс. + +## `[regex]::Escape` для безопасной замены IP + +`192.168.1.10` без escape — точки в regex matchят любой char. Лучше escape: + +```powershell +$pattern = [regex]::Escape('192.168.1.10') +$new = $content -replace $pattern, '10.0.2.2' +``` + +Связано: [[snolla-recovery-vm]], [[traefik-on-windows-docker-desktop]]. diff --git a/future-resilient-architecture-goals.md b/future-resilient-architecture-goals.md new file mode 100644 index 0000000..0598204 --- /dev/null +++ b/future-resilient-architecture-goals.md @@ -0,0 +1,100 @@ +--- +title: Future Resilient Architecture — цели на потом +type: concept +tags: [planning, architecture, resilience, roadmap, todo] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# Future Resilient Architecture + +Placeholder для глобальной задачи "выстроить отказоустойчивую архитектуру" — обсуждается **после** того как recovery полностью устаканится. + +Эта страница — **черновик целей**, не план. По ходу обсуждения распилится на специфичные концепты + ADR (architectural decision records). + +## Что сейчас не так + +См. [[recovery-architecture-snapshot]] → раздел "Single Points of Failure". Кратко: + +- ⚠️ Windows-PC = SPOF +- ⚠️ Один public IP +- ⚠️ Один MSSQL primary без replica +- ⚠️ Один MinIO single-node +- ⚠️ Backup только Hyper Backup на одну синку (Kreknin), сейчас она cold +- ⚠️ Backup нового рабочего состояния (CMS data на recovery-host) **отсутствует** +- ⚠️ Нет off-site backup (cloud) + +## Цели верхнего уровня + +1. **RTO** (Recovery Time Objective) — сколько максимум **downtime** клиенты должны видеть при отказе. + - Текущий по факту: ~15 часов (что и было). + - Целевой: **< 1 час** для одиночного отказа, **< 4 часов** для каскадного. +2. **RPO** (Recovery Point Objective) — сколько максимум **данных потерять** при отказе. + - Текущий: до 24 часов (если успеет бэкап после изменения) — больше если бэкап не успел. + - Целевой: **< 1 час**, в идеале continuous (replication). +3. **Стоимость** — bounded by разумным % от выручки клиентов. +4. **Operational simplicity** — никаких heroic ops по 15 часов в случае проблемы. + +## Технические направления (наброски) + +### Backup-стратегия 3-2-1 + +Стандарт: **3** копии данных, **2** разных media, **1** off-site. + +- **Копия 1:** primary storage (live) на NAS / Windows-PC +- **Копия 2:** local backup target ([[kreknin-synology]] остаётся, плюс отдельный) +- **Копия 3:** **cloud off-site** — Backblaze B2 / Yandex Object Storage / AWS S3 Deep Archive / Hetzner Storage Box +- Все backups encrypted client-side +- Retention: 7 daily / 4 weekly / 12 monthly + +### Compute resilience + +Варианты: + +A. **Multi-host on-prem:** 2-3 физических машины с виртуализацией (Proxmox VE), HA-кластер, VMотом migration. Дорого, но автономно. + +B. **Cloud-first:** перенос CMS-стека в managed Kubernetes (Yandex Cloud / VK Cloud / hetzner) — managed MSSQL / managed S3 / managed Postgres. Простота операций, но vendor lock-in. + +C. **Гибрид:** primary on-prem (Synology с CMR), warm standby в cloud (готовый к failover за 5 мин). Compromise. + +### Database resilience + +MSSQL options: +- **Always On Availability Groups** (требует Enterprise license — недёшево) +- **Log Shipping** (бесплатно, но manual failover) +- **MSSQL → PostgreSQL миграция?** (если есть полная свобода — выгоды Open Source + распространённые managed предложения). + +Для recovery-сценария: **continuous backup** через VDI + native MSSQL TDE backup → S3. + +### Network resilience + +- **Multi-WAN на OpenWRT:** primary провайдер + 4G/LTE USB-modem как failover. OpenWRT mwan3 package. +- **Cloudflare Proxy / Tunnel:** перенаправление публичного трафика через Cloudflare edge. Помогает с DDoS и переключением IP без DNS-changes. +- **Static IP пересмотр:** разные провайдеры → multi-homed setup. + +### Monitoring + auto-recovery + +- **Healthchecks** на каждый layer (TCP, HTTP, deep DB query) — Prometheus + Alertmanager или Uptime Kuma. +- **Watchdog скрипты** — auto `ipconfig /release/renew` в VM, auto `docker compose restart` контейнера если healthcheck падает > N min. +- **Telegram-уведомления** на инциденты (для пользователя в реал-тайм когда что-то фейлится). + +### Документация и runbook + +- Runbook на каждый процедурный сценарий (failover, restore, network swap). +- Регулярные **failover drills** раз в квартал — буквально нажать "восстановиться" и засечь время. +- Wiki эта — стартовая точка. + +## Что НЕ цели + +- Перестройка с нуля «потому что сейчас не идеально». Текущая архитектура работает, замена должна быть инкрементальной. +- 99.99% uptime — нереалистично для one-person infrastructure, и не нужно для CMS-сайтов клиентов. + +## Следующие шаги (когда дойдут руки) + +1. Заменить WD40EFAX на CMR-диски, пересобрать пул [[dead-synology-diskstation]]. +2. Включить ежедневный Hyper Backup из нового пула на [[kreknin-synology]]. +3. Добавить cloud-backup target (Yandex Object Storage наиболее очевидный для RU-юрисдикции). +4. Настроить monitoring/alerts. +5. Документировать runbooks. + +Связано со всеми остальными страницами этого wiki. diff --git a/hyper-backup-structure-and-recovery.md b/hyper-backup-structure-and-recovery.md new file mode 100644 index 0000000..890403b --- /dev/null +++ b/hyper-backup-structure-and-recovery.md @@ -0,0 +1,101 @@ +--- +title: Hyper Backup — структура репо и стратегия восстановления +type: concept +tags: [backup, synology, hyperbackup, recovery, lessons] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# Hyper Backup — структура и recovery + +## Структура `.hbk` репозитория + +Каталог `.hbk` на target-NAS содержит: + +``` +.hbk/ +├── Config/ — метаданные задачи (план бэкапа) +│ ├── @Share// — список файлов в каждой бэкапленной шаре +│ ├── target_info.db. — SQLite, инфа о target +│ ├── version_info.db. — версии бэкапов +│ ├── file_chunk*.index — индексы для дедупликации +│ ├── virtual_file.index — карта файлов +│ └── _Syno_TaskConfig — **plain text JSON конфиг задачи** (имя, source-host, encryption flag, schedule, backup_folders[], backup_apps[], backup_volumes[]) +├── Control/ — управление (lock, @writer) +├── Guard/ — служебное +├── Pool/ — дедуплицированные chunks данных +├── synobkpinfo.db — SQLite с backup_info_tb, task_id_tb +├── _Syno_TaskConfig — копия конфига в корне +└── SynologyHyperBackup.bkpi — маркер +``` + +**`_Syno_TaskConfig`** — самый ценный файл для оценки бэкапа без полной распаковки. JSON в нём содержит: +- `name` — имя задачи (в нашем кейсе `rsync Server 1`) +- `host_name` — имя source-NAS +- `enable_data_encrypt` (true/false) — шифрование клиентским паролем +- `backup_folders[]` — список путей, которые бэкапились +- `backup_apps[]` — DSM-пакеты (если бэкапили их данные) +- `backup_volumes[]` — VM-снимки (Synology VMM) + +## Hyper Backup Vault vs Hyper Backup + +- **Hyper Backup** (`/var/packages/HyperBackup/`) — клиент. Бэкапит ИЗ этого DSM. +- **Hyper Backup Vault** (`/var/packages/HyperBackupVault/`) — сервер. Принимает бэкап от других DSM. + +На target-NAS (где лежит репо) обычно установлен **Vault**. Сам он не имеет UI для restore — только receive. Restore инициируется из **Hyper Backup** (тот же пакет, но в другом режиме). + +## Стратегии восстановления + +Когда мёртвый NAS — source, репо на живом target: + +### A. **Restore через DSM UI на target-NAS** (применили мы) + +1. Открыть DSM web UI на target. +2. Hyper Backup → **Restore** → **Data Restore Wizard**. +3. Список задач **пустой** (этот NAS — приёмник, не source). Снизу-слева: **"Восстановить из существующих репозиториев"**. +4. Server type: **"В локальную папку и на USB"**. +5. Указать путь к `.hbk` (`/volume1/NetBackup/diskstation_1.hbk`). +6. Если шифрования нет (`enable_data_encrypt=false`) — сразу к выбору данных. +7. **Системную конфигурацию НЕ восстанавливать** (иначе наложатся пользователи/сеть/шары source-NAS на target). +8. Selective restore — пик нужные подпапки. +9. **Restore destination:** "Restore to another location" → новая папка (`/volume1/NetBackup/restore-tmp/` или просто root тома → создаст шары с именами как на source). +10. Версия: топовая (свежая дата). + +**Гочча:** при restore "to original location" DSM создаст **новые SMB-шары** на target c именами как на source (`backup/`, `docker/`, `work/`). Это **меняет state target-NAS**. Если важно — выбирать "another location". + +### B. **Hyper Backup Explorer (HBE)** — офлайн на Windows/Mac/Linux + +GUI-приложение от Synology, читает `.hbk` напрямую (требует password если шифрован). Подходит когда target-NAS недоступен и есть только файлы репо. + +**Минусы:** GUI, тысячи кликов для многих файлов, нет batch-restore. + +### C. **Restore + tar | ssh pull** (бекап-копия на чужую машину) + +Если уже restored to a target-NAS: + +``` +ssh user@target "tar cf - -C /volume1/backup ." | tar xf - -C /local/path +``` + +При chrooted SFTP (типичный DSM) — нельзя через scp/sftp пройти за пределы home. Решение: **`scp -O`** (legacy SCP protocol через чистый SSH-канал). Или `ssh ... 'cat file' > local-file` для одиночных файлов. + +## ACL/permissions особенности (DSM 7) + +- DSM 7 использует **Synology ACL** поверх POSIX. Видно `+` после permissions: `d---------+`. +- POSIX-биты могут показывать `0` для всех, но реально ACL может open file для специфичных users/groups. +- Файлы в Hyper Backup-репо могут иметь permission `-rw-------` (owner-only) — тогда другому юзеру `scp -O` не достанет, требуется `chmod` от root. + +## SFTP-jailed subsystem + +DSM SFTP-subsystem **chroot'ит** пользователя в home (`/var/services/homes//`). Пути за пределами не видны через SFTP-клиент. + +**Обход:** `scp -O` (флаг "use legacy SCP protocol") — обходит SFTP-subsystem, использует raw SSH-channel + shell-команды target-side. Тогда видно всё, что shell-пользователь видит. + +## Стратегии для будущего + +- **Hyper Backup ежедневно**, не "по триггеру" (как было). Окно потерь = 1 день. +- **Retention** 30+ дней — даёт возможность откатиться при позднем обнаружении проблем. +- **Test restore раз в квартал** — простейшая дисциплина: пик одну папку, восстанови в temp, проверь что файлы корректны. Иначе бэкап может тихо умереть без вашего ведома. +- **Многослойный backup:** не только Synology→Synology. Дополнительно cloud (Backblaze B2, AWS S3 Glacier, Yandex Object Storage) — на случай если оба NAS физически рядом и сгорят вместе. + +Связано: [[dead-synology-diskstation]], [[kreknin-synology]]. diff --git a/mssql-container-data-restore.md b/mssql-container-data-restore.md new file mode 100644 index 0000000..166e1fe --- /dev/null +++ b/mssql-container-data-restore.md @@ -0,0 +1,151 @@ +--- +title: MSSQL контейнер с восстановленными production data — паттерн +type: concept +tags: [mssql, docker, recovery, named-volume, chown] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# MSSQL container с production data + +## Проблема + +Восстановить MSSQL базу в Docker-контейнере на Windows-хосте из: +- Готовых `.mdf/.ldf` файлов production (взяты из tar `/var/opt/mssql/` source-контейнера) +- + 5 user-databases + системные (master/model/msdb/tempdb) + +## Что НЕ работает: bind-mount production data + +```yaml +volumes: + - ./production-data/mssql:/var/opt/mssql +``` + +**Не работает** на Windows Docker Desktop. MSSQL-контейнер требует: +- `master.mdf` owned by `mssql` user (UID 10001) или root +- `chmod` на файлах внутри `/var/opt/mssql/log/` (логи, .xel, .trc) + +На Windows DD bind-mount через WSL2-слой не позволяет: +- Файлы видятся как owned by root or other UID — MSSQL отказывается: `Your master database file is owned by root.` +- `chmod` внутри контейнера фейлится: `Operation not permitted` + +Симптом: MSSQL стартует, в логах ругается на chmod, потом стартует ещё раз и зависает. + +## Что работает: named volume + chown через temp container + +Идея: создать named volume, скопировать данные внутрь с правильным chown, потом примонтировать к MSSQL. + +```yaml +# docker-compose.yml — финальная версия +services: + mssql: + image: mcr.microsoft.com/mssql/server:2019-latest + container_name: mssql + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: ${SA_PASSWORD} + MSSQL_PID: Developer + ports: + - "1433:1433" + volumes: + - mssql_data:/var/opt/mssql + networks: + - proxy + restart: unless-stopped + +volumes: + mssql_data: + +networks: + proxy: + external: true +``` + +Заполнение volume (один раз перед `docker compose up -d`): + +```powershell +docker volume create mssql_mssql_data +docker run --rm \ + -v mssql_mssql_data:/dest \ + -v /mssql:/src:ro \ + alpine cp -a /src/. /dest/ + +docker run --rm -v mssql_mssql_data:/dest \ + alpine chown -R 10001:0 /dest + +docker compose up -d +``` + +После этого MSSQL стартует с production data, делает crash recovery в master.mdf, поднимается за 30-60 секунд (если CU контейнера == CU source) или 5-30 минут (если CU source старше — script upgrade mode). + +## sqlcmd "$( var-substitution" pitfall + +Альтернативный путь — restore из `.sql` script (export через "Generate Scripts" в SSMS). Файл 547 MB с CREATE+INSERT'ами всей БД. + +**Не запустить через `sqlcmd -i file.sql`** без флага `-x`. Потому что: + +- В данных встречаются jQuery JS-сниппеты типа `INSERT ... VALUES (N'$(function(){ ... })')` +- sqlcmd по умолчанию интерпретирует `$(varname)` как переменную для подстановки. +- На `$(function(){` парсер ломается → "Syntax error near command '('" в середине файла. + +Фикс: `sqlcmd -x` (disable variable substitution). + +``` +docker exec mssql /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U sa -P '' -C -x \ + -i /var/opt/mssql/backup/MoreThenCms.sql +``` + +## SA-аккаунт disabled в production + +Production SQL Server конфигурации часто **отключают `sa`** (security best practice). Пароль может быть прав, но account disabled → `Login failed for user 'sa'. Reason: The account is disabled.` + +Фикс: **`mssql-conf set-sa-password`** в offline-mode (server stopped) под root: + +``` +# Stop running container +docker compose stop + +# Run offline mssql-conf in temp container с тем же volume +docker run --rm --user 0:0 \ + -e ACCEPT_EULA=Y \ + -e MSSQL_SA_PASSWORD='' \ + -v mssql_mssql_data:/var/opt/mssql \ + mcr.microsoft.com/mssql/server:2019-latest \ + /opt/mssql/bin/mssql-conf set-sa-password + +# Запуск нормального container +docker compose start +``` + +`mssql-conf set-sa-password` сбрасывает пароль И **enables sa**. Если env-pw совпадает с тем что ожидает приложение — приложение продолжает работать. + +## Production passwords reuse + +Замеченный паттерн: у пользователя один пароль `fXkH4@8O%3pc` используется как: +- SA password (MSSQL) +- Connection string password для user `snolla` (CMS DB user) +- Возможно где-то ещё + +Лучше: rotate after recovery, разделить. + +## CU mismatch warning + +Если master.mdf был из старой CU (например 2019 CU14), а контейнер `2019-latest` (например CU32-GDR), MSSQL после старта войдёт в **script upgrade mode** на 10-30 минут: + +``` +Server is in script upgrade mode. Only administrator can connect at this time. +``` + +Видно в логах сотни строк `spid9s ... Deleting AlwaysOnAgReplicas...` — это нормальный msdb-upgrade. Подождать. Один раз. После завершения — никаких задержек на следующих стартах. + +## Healthcheck path для 2019-latest image + +В современных image SQL Server инструменты лежат в `/opt/mssql-tools18/bin/sqlcmd` (с TLS-флагом `-C`), а не `/opt/mssql-tools/bin/sqlcmd`: + +```yaml +healthcheck: + test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' -b -o /dev/null || exit 1"] +``` + +Связано: [[snolla-recovery-vm]] (Web.config указывает на `10.0.2.2:1433` = host из VM-NAT), [[windows-recovery-host]]. diff --git a/recovery-architecture-snapshot.md b/recovery-architecture-snapshot.md new file mode 100644 index 0000000..49307de --- /dev/null +++ b/recovery-architecture-snapshot.md @@ -0,0 +1,127 @@ +--- +title: Recovery architecture — текущая инфраструктура (2026-05-19) +type: concept +tags: [architecture, current-state, snapshot, recovery] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# Recovery Architecture Snapshot + +Снимок production-инфраструктуры на 2026-05-19, после завершения recovery. Это **рабочее, но временное** состояние — single-point-of-failure на бытовом железе. Замечания по улучшению — [[future-resilient-architecture-goals]]. + +## Цепочка запроса от клиента до CMS + +``` +Клиент (browser) + → DNS resolve (REGRU): *.kzntsv.site, snolla.com, rimiz.ru, labtools.pro/ru, и т.д. + → 94.19.247.14 (public IP, статический у провайдера) + → router OpenWRT (192.168.1.1) [[openwrt-router]] + → NAT 443 → 192.168.1.143:4443 + → NAT 80 → 192.168.1.143:8000 + → Windows-PC (192.168.1.143) [[windows-recovery-host]] + → traefik 2.6.6 на 4443/8000 + → TLS termination, certResolver=letsEncrypt из acme.json + → match по Host header (file-provider rules в data/custom/*.yml) + → backend = http://host.docker.internal:18080/ + → host:18080 = VBox NAT port forward → VM:80 + → snolla-recovery VM [[snolla-recovery-vm]] + → IIS на 80 + → IIS site matched by Host header (catch-all binding) + → MoreThenCms.Web → C:\inetpub\wwwroot\MoreThenCms.Web\ + → ASP.NET CMS code (.NET Framework 4.8) + → Connection strings: + → MSSQL: Data Source=10.0.2.2:1433 (VBox NAT gateway = host) + → MinIO/storage: TBD точная схема + → Elasticsearch: не используется CMS (там books-стек) + → MSSQL container на host:1433 + → 5 production DB (MoreThenCms, Stayer*, stostayer, TireService) + ← HTTP response back through chain +``` + +## Запущенные docker контейнеры на хосте + +| Container | Image | Port (host) | Volume | +|---|---|---|---| +| **traefik** | `traefik:v2.6.6` | 4443, 8000, 8080 | named: `traefik_traefik_letsencrypt`; bind: `data/traefik.yml`, `data/custom/` | +| **mssql** | `mcr.microsoft.com/mssql/server:2019-latest` | 1433 | named: `mssql_mssql_data` (filled from production tar) | +| **minio** | `minio/minio:RELEASE.2020-07-13T18-09-56Z` | 9000 | bind: `./data` | +| **imgproxy** | `darthsim/imgproxy:latest` | 8787 | (нет state) | +| **imgproxy-nginx** | `nginx:alpine` | 8788 | bind: `./nginx/cache`, `./nginx/nginx.conf` | +| **elasticsearch** | `elasticsearch:7.10.1` | 9200 | bind: `./data` | + +Все на docker network `proxy` (external) — это позволяет traefik резолвить `minio`, `elasticsearch`, `imgproxy-nginx` напрямую по docker DNS. + +## Traefik routes + +13 client домен-маршрутов в `data/custom/`: + +| File | Hosts | Backend | +|---|---|---| +| snolla.yml | snolla.com + 10 subdomains | host.docker.internal:18080 | +| rimiz.yml | rimiz.ru, www.rimiz.ru | host.docker.internal:18080 | +| labtools.yml | labtools.ru, www.labtools.ru | same | +| labtoolspro.yml | labtools.pro, www.labtools.pro | same | +| pilorama98.yml | pilorama98.ru, www.pilorama98.ru | same | +| tandemmebel.yml | tandemmebel.ru, www.tandemmebel.ru | same | +| emspb.yml | emspb.ru, www.emspb.ru | same | +| kupimknigi.yml | kupimknigi.spb.ru | same | +| maljarka.yml | maljarka.tandemmebel.ru | same | +| sestech.yml | sestech.ru, www.sestech.ru | same | +| isc-artmaterials.yml | ics-artmaterials.com, www.ics-artmaterials.com | same | +| oldstostayer.yml | (старый stostayer) | host.docker.internal:18181 (VM port 8081) | +| stostayer.yml | stostayer.ru или похожий | host.docker.internal:18180 (VM port 8080) | + +Плюс file-provider маршруты для инфраструктурных хостов: + +| File | Host | Backend | +|---|---|---| +| elasticsearch.yml | elasticold.kzntsv.site | http://elasticsearch:9200 + basicAuth `books:...` | +| minio.yml | minio.kzntsv.site | http://minio:9000 | +| imgproxy.yml | imgproxy.kzntsv.site | http://imgproxy-nginx:80 | + +И мёртвые (не отключены, но смотрят в никуда): +- `disk.yml` → 192.168.1.10:5005 (Synology disk на мёртвой синке) +- `dsm.yml` → 192.168.1.10:5000 (DSM мёртвой синки) + +## DNS + +Все домены остались указывать на **94.19.247.14** (public IP, статический). REGRU как registrar/DNS. Никаких DNS-изменений во время recovery не требовалось — only router NAT перенастроен. + +## Backup инфраструктура + +Текущая (на момент 2026-05-19): + +- [[kreknin-synology]] держит Hyper Backup репо мёртвой синки (~430 GB). Новых бэкапов с recovered Windows-PC **НЕТ** — рабочее состояние на Windows-PC не бэкапится никуда. +- Локально на Windows-PC: `C:\nas-recovery\vm-sites\` (~11 GB, дамп IIS sites из VM) — резерв если VM полностью умрёт. + +**Дыра:** если Windows-PC сгорит — ВСЁ ляжет. Никакой репликации, никакого off-site backup для нового рабочего состояния. + +## SSH ключи и доступ + +- [[windows-recovery-host]] → [[kreknin-synology]]: `id_ed25519_kreknin` (vitya@195.19.90.188) +- [[windows-recovery-host]] → [[openwrt-router]]: `id_ed25519_openwrt` (root@192.168.1.1) +- [[windows-recovery-host]] → [[snolla-recovery-vm]]: `id_ed25519_snolla_vm` (vitya@127.0.0.1:8022) + +После recovery — отозвать публичные ключи Claude из этих 3 машин (`~/.ssh/authorized_keys` или эквивалент). См. соответствующие entity-страницы. + +## Известные открытые баги + +1. **X-Forwarded headers** не передаются от traefik в IIS → CMS делает редирект с `:4443` в URL. См. [[traefik-on-windows-docker-desktop]] Pitfall 5. +2. **MinIO/Azure storage** в CMS — точная схема подключения TBD. Пользователь упомянул "не так всё". +3. **acme.json HTTP-01 renewal** будет фейлить для доменов с DNS не на нашем IP — нужен переезд на DNS-01 через REGRU до истечения сертификатов (~3 месяца). +4. **VM один раз "повисла" в сети** через 1-2 часа uptime — лечилось `ipconfig /release/renew` через VBoxManage guestcontrol. Нужен auto-watchdog. +5. **C:\inetpub\logs\** в VM растёт (6+ GB к recovery) — нужна ротация. +6. **docker.sock provider в traefik** не работает — но не блокирует (всё через file-provider). См. [[traefik-on-windows-docker-desktop]] Pitfall 3. + +## Single Points of Failure + +- Один Windows-PC (если сгорит — всё ляжет) +- Один публичный IP / провайдер +- Один WiFi-канал +- Один OpenWRT-роутер +- Один VBox VM (single instance, не replicate) +- Один MSSQL контейнер (single primary, нет replica) +- Один MinIO (single drive, не distributed) + +Каждый SPOF — кандидат на улучшение в [[future-resilient-architecture-goals]]. diff --git a/traefik-on-windows-docker-desktop.md b/traefik-on-windows-docker-desktop.md new file mode 100644 index 0000000..ccd21fb --- /dev/null +++ b/traefik-on-windows-docker-desktop.md @@ -0,0 +1,185 @@ +--- +title: Traefik на Windows Docker Desktop — нюансы +type: concept +tags: [traefik, docker, windows, letsencrypt, reverse-proxy] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# Traefik на Windows Docker Desktop + +Запуск traefik 2.6.6 на Windows Docker Desktop (WSL2 backend) с импортированным production-конфигом и сертификатами — пара ловушек. + +## Pitfall 1: traefik.yml не загружается автоматом + +Symptom: traefik стартует, но routers из `data/custom/*.yml` ругаются на "non-existent resolver: letsEncrypt", сертификаты не отдаются. + +Reason: traefik 2.x при отсутствии `--configFile=` ищет в дефолтных путях (`/etc/traefik/`, `./traefik.yml`), но Linux-контейнер с CWD=`/` и bind-mount `./data/traefik.yml:/traefik.yml` — почему-то не подхватывает. + +**Fix:** явно указать в compose `command:`: + +```yaml +command: + - "--configFile=/traefik.yml" + - "--log.level=DEBUG" +``` + +Подтверждение в логах: `Configuration loaded from file: /traefik.yml`. + +## Pitfall 2: acme.json permissions через bind-mount + +Symptom: traefik ругается `permissions 777 for /letsencrypt/acme.json are too open, please use 600` и **отключает** letsEncrypt resolver (даже если у него есть валидные certs). + +Reason: bind-mount Windows-файла в Linux-контейнере **всегда** показывает permissions `0777`. Изменить через `chmod` нельзя — bind-mount не транслирует POSIX-perms на Windows-side. + +**Fix:** named volume вместо bind для `/letsencrypt`: + +```yaml +volumes: + - "traefik_letsencrypt:/letsencrypt" # вместо ./letsencrypt:/letsencrypt + - "./data/traefik.yml:/traefik.yml:ro" + - "./data/custom/:/custom/:ro" + +# и снизу: +volumes: + traefik_letsencrypt: +``` + +Population volume единоразово: + +```powershell +docker volume create traefik_traefik_letsencrypt +docker run --rm \ + -v traefik_traefik_letsencrypt:/dest \ + -v /traefik/letsencrypt:/src:ro \ + alpine sh -c "cp /src/acme.json /dest/acme.json && cp /src/acme.old.json /dest/acme.old.json && chmod 600 /dest/*" +``` + +## Pitfall 3: docker.sock provider не работает + +Symptom: +``` +Failed to retrieve information of the docker client and server host: Error response from daemon: +providerName=docker +``` + +С `-v /var/run/docker.sock:/var/run/docker.sock:ro` сокет монтируется (видно `srw-rw---- 1 root root` внутри контейнера), но соединение с daemon обрывается. + +Reason: Docker Desktop на Windows транслирует docker.sock через WSL2 layer. Иногда permission-моэль клиента (libdocker) не принимает то, что предоставляет Desktop's proxy. + +**Workaround:** **полностью отключить docker-provider, использовать только file-provider** для всех routes. + +Маршруты, которые в производстве были как traefik labels на контейнерах (minio, elasticsearch, imgproxy с `traefik.http.routers...labels`), переписываются в `data/custom/.yml` руками: + +```yaml +# data/custom/minio.yml +http: + routers: + minio: + entryPoints: [https] + rule: Host(`minio.kzntsv.site`) + tls: + certResolver: letsEncrypt + service: minio + services: + minio: + loadBalancer: + servers: + - url: http://minio:9000 # docker DNS name (работает потому что все на одной network=proxy) +``` + +Подобно для elasticsearch (с basicAuth middleware), imgproxy (→ imgproxy-nginx:80), etc. + +## Pitfall 4: file-provider не подхватывает изменения через bind-mount + +`traefik.yml` имеет `providers.file.watch: true`, но на Windows bind-mount inotify не работает через WSL2-слой. Изменения в `data/custom/*.yml` не подхватываются автоматически. + +**Workaround:** `docker compose restart traefik` после изменений в custom/. Несколько секунд downtime, ничего страшного. + +## Production порты vs нашa конфигурация + +Production traefik compose биндил на host: + +```yaml +ports: + - "8000:80" # router forwards public 80 → host 8000 → container 80 + - "4443:443" + - "8080:8080" +``` + +То есть **роутер делает port-translation** 80→8000, 443→4443. Не стандартные порты, но работает. + +На Windows-хосте оставили те же порты (8000/4443) — не конфликтуют с локальным IIS на 80 (который не используется, но не выключен). И не требуют admin для bind. + +## host.docker.internal + +Из traefik-контейнера достучаться до VM (которая в VBox NAT, не в docker network) — через **`host.docker.internal`**: + +```yaml +servers: + - url: http://host.docker.internal:18080/ # 18080 = VBox NAT-forwarded port → VM:80 +``` + +Docker Desktop резолвит `host.docker.internal` в IP хоста (обычно 172.x.x.1 из docker bridge perspective). Дальше Windows-host обрабатывает 18080 → VBox NAT → VM:80 → IIS → CMS. + +## Pitfall 5: X-Forwarded headers НЕ настроены + +Traefik отдаёт backend (IIS) запрос с заголовком `Host: snolla.com`, но **не** добавляет `X-Forwarded-Proto: https` / `X-Forwarded-Host`. Backend (IIS) считает, что получил HTTP-запрос, делает 301 redirect на канонический URL `http://www.:4443/`. + +Симптом для клиента: видит URL с портом 4443 в адресной строке после редиректа. + +**Fix (TODO, не делали в emergency):** + +```yaml +# data/custom/forwarded-headers-middleware.yml +http: + middlewares: + secure-headers: + headers: + customRequestHeaders: + X-Forwarded-Proto: https + customResponseHeaders: + X-Forwarded-Proto: "" +``` + +И применить middleware к каждому router (или к глобальному entrypoint-level через `--entrypoints.https.forwardedheaders.insecure=true` в command). + +Plus на стороне IIS: установить ARR module + URL Rewrite, доверять `X-Forwarded-*` от 127.0.0.1 (host). + +## DNS-01 challenge через REGRU + +`traefik.yml` имеет HTTP-01 challenge: + +```yaml +certificatesResolvers: + letsEncrypt: + acme: + email: vitya.kuznetsov@gmail.com + storage: /letsencrypt/acme.json + httpChallenge: + entryPoint: http +``` + +В compose env уже: +``` +REGRU_USERNAME=OpeItcLoc03 +REGRU_PASSWORD=ytyYqC%u%QAJ +``` + +Эти креды для **DNS-01** через REGRU API. Просто закомментировать `httpChallenge` и активировать `dnsChallenge` в traefik.yml: + +```yaml +certificatesResolvers: + letsEncrypt: + acme: + email: ... + storage: /letsencrypt/acme.json + dnsChallenge: + provider: regru +``` + +DNS-01 более надёжный (не требует public:80 reachable для validation) и работает даже если HTTP-01 challenge не пройдёт (например, домен временно на другой хостинге). + +**Текущие 40 LE-сертификатов из acme.json валидны ~3 месяца** (LE default). Когда подойдут к истечению — пора переключать на DNS-01. + +Связано: [[snolla-recovery-vm]], [[windows-recovery-host]], [[recovery-architecture-snapshot]]. diff --git a/vbox-windows-stability-tuning.md b/vbox-windows-stability-tuning.md new file mode 100644 index 0000000..0993dc1 --- /dev/null +++ b/vbox-windows-stability-tuning.md @@ -0,0 +1,140 @@ +--- +title: VirtualBox + Windows-гость — нюансы стабильности при cross-hypervisor миграции +type: concept +tags: [virtualbox, windows, kvm, migration, troubleshooting] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# VirtualBox + Windows-гость: cross-hypervisor migration + +Импорт OVA с Synology VMM (KVM-based) в VirtualBox прошёл через 3 круга проблем. Записано чтобы в следующий раз не танцевать. Касается [[snolla-recovery-vm]]. + +## Симптом исходный + +OVA импортируется через `VBoxManage import`, VM стартует, Windows валится в WinRE ("Восстановление при загрузке не удалось восстановить компьютер"). Startup Repair не помогает. + +## Цепочка фиксов (применять по порядку) + +### 1. Storage controller: SCSI LsiLogic → SATA AHCI + +OVA с KVM-источника обычно имеет SCSI LsiLogic. Windows-гость не имеет встроенного boot-driver для VBox's LsiLogic emulation. SATA AHCI — generic, поддерживается любым Windows из коробки. + +``` +VBoxManage controlvm "" poweroff +VBoxManage storageattach "" --storagectl "SCSI" --port 0 --device 0 --medium none +VBoxManage storagectl "" --name "SCSI" --remove +VBoxManage storagectl "" --name "SATA" --add sata --controller IntelAhci --portcount 4 +VBoxManage storageattach "" --storagectl "SATA" --port 0 --device 0 --type hdd --medium "" +``` + +После этого Windows бутится дальше WinRE → доходит до login screen. + +### 2. Hyper-V Virtualization Infrastructure Driver — disable в Safe Mode + +После login Windows зависает в **чёрный экран после Welcome**. Виновник — Microsoft Hyper-V virtualization infrastructure driver, который остался от Synology VMM/KVM. Под VBox он не находит свой target hypervisor → виснет. + +Доступ: жмёшь power → Shift+Restart → Recovery → Troubleshoot → Advanced Options → Startup Settings → Restart → **4 (Safe Mode)** или **5 (Safe Mode with Networking)**. + +В Safe Mode: +- Device Manager → Системные устройства → **Драйвер инфраструктуры виртуализации Microsoft Hyper-V** → правый клик → **Отключить устройство** (Disable, не удалять — потом можно вернуть). +- Параллельно почистить "Другие устройства" с жёлтыми треугольниками (audio-controller, base system device) — uninstall, без удаления драйверов. + +Reboot нормально → Windows загружается до desktop. + +### 3. Paravirt provider: default → kvm + +После пары часов uptime VM начинает виснуть рандомно. Корень — несоответствие paravirt-интерфейса между source-гипервизором (Synology VMM = KVM) и VBox default (`default`/`auto`, который пытается подружиться с гостем но не угадывает). + +``` +VBoxManage modifyvm "" --paravirtprovider kvm +``` + +Windows-гость, который изначально загружал KVM paravirt drivers (virtio?), теперь под VBox видит знакомый интерфейс → стабильнее. + +### 4. OS type: Other_64 → Windows10_64 + +VBox по умолчанию ставит OS type = `Other/Unknown (64-bit)` при импорте OVA с неузнанной маркировкой. Это означает дефолтные acceleration settings, которые могут не подходить Windows. + +``` +VBoxManage modifyvm "" --ostype Windows10_64 +``` + +Включает VBox-внутренние оптимизации для Windows (HPET off, large pages on, и др.). + +### 5. HPET off, vCPU 2 (а не 4), RAM 4 GB + +- HPET (High Precision Event Timer) — для Windows-гостя на VBox чаще создаёт jitter чем помогает. Off: + ``` + VBoxManage modifyvm "" --hpet off + ``` +- vCPU: 4 на 2-ядерном/4-ядерном хосте может создавать contention. Снизить до 2: + ``` + VBoxManage modifyvm "" --cpus 2 + ``` +- RAM: 4 GB достаточно для IIS + CMS + Windows на легкой нагрузке. + +### 6. Установить **VirtualBox Guest Additions** + +Без GA Windows использует generic Microsoft драйверы для VBox-эмулированного железа. С GA — нативные оптимизированные VBox-драйверы для сети/видео/storage/устройств. + +**Установить через VRDE-консоль** (mstsc к `localhost:13389`), а не через сетевой RDP — потому что сеть может умереть до установки GA. + +Шаги: +1. На хосте: `VBoxManage storagectl "" --name "IDE" --add ide --controller PIIX4` (новый контроллер для DVD) +2. `VBoxManage storageattach "" --storagectl "IDE" --port 0 --device 0 --type dvddrive --medium "C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso"` +3. В VM: Win+E → D: drive → запустить `VBoxWindowsAdditions.exe` → Next/Install/доверять Oracle publisher → Restart. + +После: `GuestAdditionsRunLevel=3` (полностью активны). VM существенно стабильнее. + +## Network nuances + +### Bridged WiFi нестабильно + +Если хост-машина подключена по WiFi и VBox NIC = bridged через WiFi adapter — частые проблемы с promiscuous mode. Симптомы: +- VM получает IP по DHCP +- Работает 10-60 минут +- Network "повисает": TCP-handshake проходит, но трафик не идёт +- ARP-table показывает MAC, но State=`Stale` + +Решение: **NAT с port forwarding** вместо bridged. + +``` +VBoxManage modifyvm "" --nic1 nat +VBoxManage controlvm "" natpf1 "rdp,tcp,127.0.0.1,23389,,3389" +VBoxManage controlvm "" natpf1 "ssh,tcp,127.0.0.1,8022,,22" +VBoxManage controlvm "" natpf1 "http,tcp,127.0.0.1,18080,,80" +# ...и так далее на нужные порты +``` + +VM получает 10.0.2.15 (default NAT subnet). Host достижим из VM по 10.0.2.2 (NAT gateway). + +### Network recovery inside Windows VM + +Если внутри VM network "повис" (бывает даже с GA + NAT): + +``` +VBoxManage guestcontrol "" run --exe "C:\Windows\System32\cmd.exe" \ + --username vitya --password '' --wait-stdout --wait-stderr \ + -- cmd.exe /c "ipconfig /release && ipconfig /renew" +``` + +Через GA это работает не требуя SSH/RDP связи. + +## VRDE backup-доступ + +Всегда включён в нашей конфигурации как fallback: + +``` +VBoxManage controlvm "" vrde on +VBoxManage controlvm "" vrdeport 13389 +``` + +`mstsc → localhost:13389` показывает VM-консоль независимо от состояния сети в VM. Полезно для recovery когда RDP в VM умер. + +## Что не сработало + +- Hyper-V на хосте — рассматривался как cleaner альтернатива для Windows-гостя, но требует доустановки Hyper-V Manager и перезагрузки хоста. Отложено как Plan B, не понадобилось. +- VMware Workstation Pro 17 — бесплатен с 2024, но та же migration-головная боль на Windows-госте с другого гипервизора. + +Связано: [[snolla-recovery-vm]], [[windows-recovery-host]]. diff --git a/wd40efax-smr-cascade.md b/wd40efax-smr-cascade.md new file mode 100644 index 0000000..7423884 --- /dev/null +++ b/wd40efax-smr-cascade.md @@ -0,0 +1,76 @@ +--- +title: WD40EFAX SMR Cascade — root cause NAS failure 2026-05-18 +type: concept +tags: [hardware, raid, smr, failure-mode, wd, lessons] +sources: [../sources/nas-recovery-session-2026-05-18.md] +updated: 2026-05-19 +--- + +# WD40EFAX SMR Cascade + +Сценарий смерти RAID 5 на WD Red WD40EFAX (SMR-диски). Случился 2026-05-18 на [[dead-synology-diskstation]]. Известная community-проблема, не уникальная. + +## Что такое SMR + +**SMR** (Shingled Magnetic Recording) — способ записи на пластины, где дорожки наезжают друг на друга как черепица. Плюс: больше плотность, дешевле гигабайт. Минус: запись на одну дорожку **физически портит соседние** → нужна re-write whole "zone". Запись стала зональной (вместо random-access). + +**CMR** (Conventional Magnetic Recording) — стандарт, дорожки независимые. + +### Где SMR ломается + +| Сценарий | CMR | SMR | +|---|---|---| +| Sequential write | ✅ | ✅ | +| Чтение | ✅ | ✅ | +| Random writes (БД, активная FS) | ✅ | ❌ просаживается | +| **RAID rebuild** | ✅ | ❌❌ **смертельно** | + +Во время RAID-rebuild диск получает **долгую sustained-нагрузку**: parity-чтение + запись на новый диск. SMR-firmware пытается жонглировать зонами; внутренний CMR-кэш (есть в начале диска) забивается. Performance падает в 5-10 раз → **command timeouts** → RAID-контроллер выкидывает диск из массива. + +## Скандал WD + +WD в 2018-2020 **тихо** перевёл часть линейки "WD Red" (заточена под NAS) на SMR, не указав в маркировке. Community catch'нуло (Reddit, Servethehome, forum.synology). Class-action в США 2020, WD settled. После — WD переименовал CMR-варианты в "WD Red **Plus**" / "WD Red **Pro**"; "WD Red" без Plus остался SMR. + +**WD40EFAX-68JH4N1, WD40EFAX-68JN4N0** — главные жертвы. Если в RAID — лотерея. + +## Конкретный путь к смерти (наш кейс) + +1. **3-диск RAID 5** на WD40EFAX. Storage pool в DSM на `cachedev_0`, 7 TB usable. +2. **Начало мая 2026** — один из 3 дисков вылетел (вероятно тайм-аут под нагрузкой, не физический отказ). +3. **Пул degraded.** RAID 5 на 3 дисках теперь толерирует 0 дополнительных отказов. +4. **2 недели пользователь не заменил failed диск.** Пул работал в degraded; оставшиеся 2 SMR-диска делают parity-чтение для любого запроса. +5. **Под этой нагрузкой второй WD40EFAX накопил тайм-ауты** (известный паттерн для SMR в degraded-RAID). +6. **2026-05-18 ~17:00 MSK** — второй вылет. Пул past redundancy, "Сбой сборки" в DSM Storage Manager. +7. **Виден только Disk 3 + Disk 8** (третий, который вылетел первым, физически не определяется системой даже). + +## Ключевая ошибка + +**2 недели в degraded не лечатся.** В нормальном RAID 5 на CMR — можно прожить недели без последствий (всё работает). На SMR — каждый день в degraded **повышает шанс второго отказа** из-за SMR-induced таймаутов. + +Правило: при SMR в RAID 5 — **24-48 часов** на замену failed диска. Дольше — лотерея. Поэтому SMR в RAID **запрещён de facto** для серьёзных продакшнов. + +## Что НЕ делать после второго отказа + +- ❌ Repair / Online Assembly в DSM — пул past redundancy, mdadm не соберёт. +- ❌ Менять disks в degraded-пуле — может ускорить deterioration оставшихся. +- ❌ Запускать `mdadm --assemble` руками с force — без знания внутренней структуры → разрушение partial-data. + +## Что МОЖНО (но дорого) + +- **Pro data recovery** (Storelab/R.LAB/Ace Lab клиенты): $500-3000 typical. Контора берёт диски (все 3, включая failed), делает offline-reconstruct parity, восстанавливает file-tree. Подходит для возврата 9-дневного окна между последним бэкапом (2026-05-09) и инцидентом (2026-05-18). +- **Условие:** диски физически живы (головки не упали). У нас 2 видимых "Исправно" + 1 не определяемый — стандартный кейс для recovery service. + +## Lessons для будущего + +Для следующего NAS-пула: + +1. **CMR-only.** Никаких WD40EFAX/EFRX/EFAZ/EFGX (если они SMR). Кандидаты: + - WD Red **Plus** (CMR, маркировка "Plus" — важно) + - WD Red **Pro** (CMR, enterprise-grade) + - Seagate IronWolf 4TB+ (CMR — модели <4TB могут быть SMR, проверять по datasheet) + - HGST/WD Ultrastar (enterprise CMR) +2. **RAID 6 / SHR-2** при 4+ дисках — толерирует 2 отказа. Один отказ + один SMR-cascade не убивает массив. +3. **Дисциплина replace failed disk в 24-48 часов** — в degraded долго не сидеть. +4. **Hot spare** если есть place в шасси. +5. **Hyper Backup ежедневно** (а не "по триггеру") + retention 30+ дней. +6. **Тест восстановления раз в квартал** — мы впервые узнали что наш backup рабочий **только когда случился инцидент**. Это нехорошо.