# [morethencms-s3-filestorage-provider] — S3-провайдер FileStorage для MoreThenCms (MinIO) **Status:** 🟢 LIVE на прод RUVDS (2026-07-03). Split-brain закрыт: боевой catch-all `C:\sites\snolla` теперь читает/пишет ассеты/темы/галереи из MinIO. Локальный IIS (windows-recovery-host) — отложен до ухода сайтов с RUVDS (решение user). ### Прод-cutover RUVDS (2026-07-03) - Backup: `web.config.bak-pre-s3-2026-07-03` + IIS-снапшот `pre-s3-cutover-2026-07-03`. Rollback = restore + recycle (Local вернётся, MinIO не трогается). - 3 DLL (S3+AWSSDK.*) → `C:\sites\snolla\bin`. web.config ``: 6 контентных классов (assets/galleries/images/scripts/stylesheets/watermarks) → S3, креды в конфиге (ACL SYSTEM+Admins); кэши (imageCache/uploadCache/contentCache/_imageCache-Azure) оставлены Local. Правка — точечный regex, UTF-8/BOM сохранён, XML валиден (10 записей). - Pre-flight: RUVDS→minio.kzntsv.site:443 = TCP+HTTPS 200. Egress ок. - Smoke GREEN: 7 тенантов 200/301 (0× 500); S3-read вживую — theme CSS `16ba5cb8…/css/lato.css` → 200/9239b/text/css, byte-parity с MinIO. Write-механика — self-test цикл (ранее). - imageCache-прун бакета `themes` (2.1 ГБ мусора) — фоном bfb8wy94n. **Валидация (standalone-проба, IIS/elevation не нужны):** Read/key/byte-parity ✅ + full self-test `put→exists→download(MD5==)→delete→exists=false` ✅ после upload-фикса. Upload-фикс: **`UseChunkEncoding=false`** в `PutObjectRequest` (MinIO отвергает AWSSDK 3.7 aws-chunked `STREAMING-AWS4-HMAC-SHA256-PAYLOAD`; `DisablePayloadSigning` НЕ помогает). 17/17 юнитов + интеграция. `_selftest/`-мусора нет. ### Метод валидации (важно — IIS/elevation НЕ нужны) Провайдер чистый (AWSSDK + базовый FileStorageClient, без SnollaHost/Autofac) → грузится standalone в PowerShell: загрузить 3 DLL из `deploy/` + зависимости из `C:\sites\stostayer.old\bin` (резолвер с guard от рекурсии), инстанцировать `AssetsStorage` через `$type::new($cfgDict)`, гонять против real MinIO. Проба: scratchpad `s3-provider-probe.ps1` + `s3-upload-diag.ps1`. stostayer.old НЕ трогается (только его DLL читаются в чужой процесс). Self-test пишет в throwaway `assets/_selftest/` (чистится). **Owner-split:** код — прогер (проект MoreThenCms); координация + deploy + приёмка — admin (я). ### Ключ-конвенция (ground-truthed 2026-07-03, финал) Провайдер = калька с **Local** (не Azure — Azure в бою не гонялся), ключи по snolla-фронту `packages/core/lib/services/storage.js` (`key = ownerId.toLowerCase()+'/'+storageFilename`). - **assets**: bucket `assets`, ключ `/` — ownerId **без дефисов** (админка отдаёт `ToString("N")`, объекты в MinIO без дефисов). НЕ вставлять дефисы. - **themes** (css/images/**js**/watermarks): единый bucket `themes`, ключ `/{css,images,js,watermarks}/`. scripts=`js` (не `/scripts`!). watermarks НЕ отдельный бакет — они в `App_Data\themes\\watermarks\` (Local `WatermarksStorage`), 11 файлов вкл. tandemmebel.ru. - **galleries**: bucket `galleries`, ключ `/` **плоско** (без `/images`). - Azure separator-баг (`prefix+fileUri` без `/`) прогер поймал → `Trim('/')+"/"+fileUri`. - Configuration net461→net462 (`OpeItcLoc03/MoreThenCms.Configuration 3e79ff2`) — build-only, на прод НЕ едет (шипим только 3 DLL). - Приёмка на локальном IIS: **key-parity + byte-parity** sweep (ключ совпал с существующими + MD5==). ## Зачем Админка MoreThenCms пишет ассеты на локальный диск IIS (`App_Data`, провайдер `FileStorage.Local`), а боевой фронт snolla читает из MinIO → split-brain (правка в админке не видна на сайте). Плюс отдельный симптом — 500 на delete из-за RX-only ACL (пофикшен 2026-07-03 выдачей Modify, но это лечит только delete, не устраняет раздвоение). Настоящее закрытие — посадить админку на тот же MinIO через S3-провайдер, которого в кодовой базе нет (есть только `Local` и `Azure`). ## Решения (приняты) - Новая сборка `MoreThenCms.FileStorage.S3` — калька с `MoreThenCms.FileStorage.Azure`, backend `AWSSDK.S3` против MinIO (`ForcePathStyle=true`). - Объём: **все** контентные классы (Assets/Galleries/Images/Scripts/Stylesheets/Watermarks). Кэши (image/upload/content) остаются Local. - Один MinIO на всех: `minio.kzntsv.site` (books-vds, 89.253.255.133), path-style. Bucket = имя класса, ключ = `/` **без ведущего слэша** (parity с уже мигрированными объектами). - Deploy target админки: локальный IIS на **windows-recovery-host** (репрпоуз PC; серверная роль была декоммишнута, сам PC жив). DNS-аудит 2026-07-03: инфра-хосты `*.kzntsv.site` резолвятся в реальные IP, перехвата `127.0.0.1` НЕТ — костыль в конфиг не нужен. ## Where I stopped ТЗ (полное, под новичка, с таблицей префиксов и выделенной граблей «ведущий слэш в ключе S3») лежит в `~/projects/MoreThenCms/.claude-inbox/2026-07-03T06-40-09Z-admin.md`. ## Next action Дождаться `MoreThenCms.FileStorage.S3.dll` от прогера → развернуть на локальный IIS (windows-recovery-host) → сквозняк: upload из админки → объект в MinIO с корректным ключом+Content-Type → фронт через imgproxy отдаёт 200 → byte-parity (ETag/MD5). Затем deploy на боевой + переключить `` секцию, секреты прокинуть из окружения (не хардкод). ## Пайплайн ТЗ ✅ → сборка ⏳ (прогер) → deploy-local ⏳ → сквозняк-приёмка ⏳ → deploy-remote ⏳ → close split-brain. ## Контекст - `.wiki/concepts/snolla-admin-appdata-acl-500-after-scp-migration.md` (500 + split-brain + fix) - `.wiki/concepts/galleries-storage-class-local-not-s3.md` (Local-класс, миграция в S3, префиксы) - Абстракция: `MoreThenCms/FileStorage/FileStorageClient.cs` + `Azure/AzureCloudStorage.cs` (образец) **Branch:** n/a (admin ops + external code)