deploy(cms-s3): S3 FileStorage provider LIVE on RUVDS — admin↔MinIO split-brain closed
Разбор 500 на /admin/assets/<owner>/delete → корень ACL (app pool RX-only на App_Data после scp-миграции) → полная развязка хранилища CMS: - S3 FileStorage провайдер (MoreThenCms.FileStorage.S3) построен (координация с интерн-сессией) и раскатан LIVE на прод RUVDS: 6 контентных классов web.config → S3/MinIO, кэши остались Local. Смок 7 тенантов 200/301, 0×500, S3-read byte-parity. Upload-гоча: UseChunkEncoding=false (MinIO ⊥ AWSSDK aws-chunked). - Весь локальный контент RUVDS → MinIO (galleries 5.3G, themes 2.7G, maxMind); imageCache-блоат (2.1G) выпилен из бакета themes. - stostayer.old определён как stale-копия (не мигрировать). Новый concept: snolla-admin-appdata-acl-500-after-scp-migration. Трекеры: morethencms-s3-filestorage-provider (LIVE), reconcile-local-assets-to-minio (done). Rollback: web.config.bak-pre-s3-2026-07-03 на хосте. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,4 +61,6 @@ curl -sk --resolve imgproxy.kzntsv.site:443:127.0.0.1 'https://imgproxy.kzntsv.s
|
||||
|
||||
⚠️ На стороне snolla `content-api/routes/assets.js` — пустой stub: объекты в S3 есть и отдаются через imgproxy, но inline-картинки контента не отрендерятся, пока роут не реализован (код-таска snolla).
|
||||
|
||||
⚠️ **Админка на S3 НЕ переключена** (класс `assets` в её `<fileStorageClients>` всё ещё Local) → правки ассета через админку RUVDS меняют только локальный `App_Data`, а боевой app читает MinIO — расходятся. Плюс отдельный баг прав на этой же локалке ронял админский delete/upload в 500. Оба — в [[snolla-admin-appdata-acl-500-after-scp-migration]] (там же рецепт ручного зеркалирования в оба хранилища).
|
||||
|
||||
См. задачи `.tasks/STATUS.md` → `[migrate-gallery-originals-to-s3]`, `[migrate-assets-originals-to-s3]`.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: MoreThenCms admin (RUVDS) — App_Data RX-only после scp-миграции → HTTP 500 на любой записи (delete/upload)
|
||||
status: fixed 2026-07-03 (Modify выдан на весь App_Data)
|
||||
tags: [cms, snolla, morethencms, iis, ruvds, acl, permissions, migration, assets, 500]
|
||||
related: [[galleries-storage-class-local-not-s3]], [[../entities/ruvds-iis-host]], [[../sources/iis-migration-to-ruvds-2026-05-23]]
|
||||
updated: 2026-07-03
|
||||
---
|
||||
|
||||
# App_Data RX-only → 500 на записи из админки
|
||||
|
||||
## Симптом
|
||||
|
||||
`POST /admin/assets/<ownerId>/delete?path=/&filename=<file>` → **HTTP 500**. В IIS-логе
|
||||
(`C:\inetpub\logs\LogFiles\W3SVC1\`, время в **UTC**; сервер MSK=UTC+3) — `sc-status 500
|
||||
sc-substatus 0 sc-win32-status 0` = чистое managed-исключение, не IIS-уровень. В Windows
|
||||
**Application Event Log пусто**, файловых логов CMS нет → ASP.NET глотает исключение default-handler'ом.
|
||||
GET того же URL → 404 (delete только `[HttpPost]`).
|
||||
|
||||
## Root cause — ACL, не код и не MinIO
|
||||
|
||||
App pool `snolla` = **ApplicationPoolIdentity** → процесс бежит как `IIS AppPool\snolla`.
|
||||
После scp-миграции 2026-05-23 bootstrap выдал этому аккаунту на дереве контента **только
|
||||
`(RX)` (Read&Execute)**, без Modify:
|
||||
|
||||
```
|
||||
C:\sites\snolla\App_Data IIS APPPOOL\snolla:(OI)(CI)(RX) ← только чтение
|
||||
...\labtools-price.pdf IIS APPPOOL\snolla:(I)(RX)
|
||||
```
|
||||
|
||||
Провайдер `assets` в `<fileStorageClients>` = `MoreThenCms.FileStorage.Local.AssetsStorage`
|
||||
(**локальный диск**, `App_Data\assets\<ownerId:N>\<storageFilename>`; `basePath=""`) — см.
|
||||
[[galleries-storage-class-local-not-s3]]. Код-путь удаления:
|
||||
`AssetsController.Delete` → `AssetsService.DeleteAsset` (`MoreThenCms/Assets/Services/AssetsService.cs:84`):
|
||||
`FileExists` (RX-чтение ок → true) → **`DeleteFile` = `File.Delete`** под identity с RX →
|
||||
`UnauthorizedAccessException` → необработанное → **500**. DB-delete (строкой ниже) даже не
|
||||
достигается. `RX` не содержит право DELETE, и на родителе нет `DC` (delete-child) → удалять нечем.
|
||||
`BUILTIN\Users:(AD)(WD)` не спасает — виртуальный аккаунт пула в `IIS_IUSRS`, не в `Users`; и AD/WD — создание, не удаление.
|
||||
|
||||
**Дифдиагностика (чем исключены NRE-версии):** в БД `mssql.kzntsv.site` подтверждены обе строки —
|
||||
`Folders` (root-папка owner'а, `Path='/'`, `Discriminator=AssetsFolder`) и `Files`
|
||||
(`StorageClient='assets'`, `StorageFilename=<file>`), поэтому `GetFolderByPath(...).Id` и
|
||||
`asset.Folder` не-null, выполнение доходит именно до `File.Delete`. Единственная падающая операция — физическое удаление.
|
||||
|
||||
**Класс проблемы шире delete:** RX-only убивает ВСЕ Local-записи из админки — upload ассетов,
|
||||
`contentCache`/`uploadCache`/`imageCache`, галереи, watermarks. Всё это классы Local в `App_Data`.
|
||||
|
||||
## Fix (применён 2026-07-03)
|
||||
|
||||
```powershell
|
||||
# на RUVDS IIS (80.64.31.36), от Administrator
|
||||
icacls "C:\sites\snolla\App_Data" /grant "IIS AppPool\snolla:(OI)(CI)(M)" /T /C
|
||||
```
|
||||
|
||||
`(M)`=Modify (включает DELETE+WRITE), `(OI)(CI)`=наследование на файлы+подпапки, `/T`=протянуть
|
||||
на существующие. Результат: **44293 файла, 0 ошибок**. Проверка: на целевом файле теперь
|
||||
`IIS APPPOOL\snolla:(I)(M)`. Рестарт пула не нужен — ACL применяется на лету. Живой delete-тест
|
||||
намеренно НЕ гонялся (удалил бы реальный ассет); доказательство = ACL на точном таргете + протрассированный код-путь.
|
||||
|
||||
## Split-brain: админка ≠ боевой сайт
|
||||
|
||||
Админка MoreThenCms живёт на **RUVDS IIS** и читает/пишет **локальный** `App_Data`. Боевой
|
||||
snolla-app (labtools.ru и др. на [[../entities/vds-kzntsv]]) отдаёт ассеты/галереи из **MinIO**
|
||||
(`minio.kzntsv.site` = books-vds, bucket `assets/<ownerId>/<file>`), см.
|
||||
[[galleries-storage-class-local-not-s3]]. Классы мигрированы в S3 (галереи 12.06, assets 13.06),
|
||||
но **админка на S3 не переключена** → любая правка ассета через админку меняет только локалку IIS
|
||||
и НЕ отражается на боевом сайте (и наоборот). Пока админка не repointed на S3 — правки зеркалить руками в оба места.
|
||||
|
||||
Пример (2026-07-03): замена `labtools-price.pdf` (ownerId `d375c419…`) — новый файл залит
|
||||
**и** в MinIO (`mc cp --attr Content-Type=application/pdf … snm/assets/d375c419…/labtools-price.pdf`,
|
||||
ETag→`6426ddd0…`) **и** на локальный диск IIS (scp, MD5 сверен). Оба слоя = байт-в-байт с исходником.
|
||||
|
||||
## TODO (настоящее закрытие split-brain)
|
||||
|
||||
Переключить админский `<fileStorageClients>`-класс `assets` (и `galleries` и пр.) на S3-провайдер,
|
||||
указывающий на `minio.kzntsv.site` — тогда админка и боевой app работают с одним хранилищем,
|
||||
ручное зеркалирование и ACL на App_Data перестают быть нужны. Это код/конфиг-таска на стороне MoreThenCms.
|
||||
@@ -35,6 +35,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
||||
- [iis-migration-2026-05-19-postmortem](concepts/iis-migration-2026-05-19-postmortem.md) — post-mortem миграции CMS на нативный IIS, 2026-05-19
|
||||
- [minio-imgproxy-on-vds](concepts/minio-imgproxy-on-vds.md) — MinIO+imgproxy stack на VDS, миграция с upgrade 2020→2025 + DNS swap pending
|
||||
- [morethencms-null-settingsdata-https-502](concepts/morethencms-null-settingsdata-https-502.md) — MoreThenCms тенант с `Sites.SettingsData=NULL` → 502 только на HTTPS (нет `httpSecure` → KeyNotFound); fix = UPDATE + recycle. maljarka 2026-06-08
|
||||
- [snolla-admin-appdata-acl-500-after-scp-migration](concepts/snolla-admin-appdata-acl-500-after-scp-migration.md) — админский delete/upload ассета → 500: app pool имел на `App_Data` только RX после scp-миграции → `File.Delete` UnauthorizedAccessException. Fix = icacls Modify. + split-brain: админка пишет Local, боевой app читает MinIO
|
||||
- [mssql-container-data-restore](concepts/mssql-container-data-restore.md) — MSSQL контейнер с восстановленными production data — паттерн
|
||||
- [mssql-on-vds](concepts/mssql-on-vds.md) — MSSQL Express 2022 Linux на VDS — миграция + login orphan fix + traefik TCP gotchas
|
||||
- [ocis-on-vds-deploy-recipe](concepts/ocis-on-vds-deploy-recipe.md) — oCIS на VDS — deploy recipe + non-obvious gotchas (UID 1001 vs 1000, basic auth, LibreGraph user-create)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
||||
|
||||
## [2026-07-03] ingest | NEW concepts/snolla-admin-appdata-acl-500-after-scp-migration — разведка «почему `/admin/assets/<owner>/delete` → 500» на RUVDS IIS. Корень: app pool `IIS AppPool\snolla` (ApplicationPoolIdentity) имел на `C:\sites\snolla\App_Data` только `(RX)` после scp-миграции 23.05 → `File.Delete` в `AssetsService.DeleteAsset` кидает UnauthorizedAccessException → необработанное 500 (event log пуст, IIS substatus 0/win32 0). NRE-версии исключены проверкой строк в БД (Folders root + Files). Fix: `icacls App_Data /grant "IIS AppPool\snolla:(OI)(CI)(M)" /T` (44293 файла, verified `(I)(M)` на таргете). Класс шире delete — все Local-записи из админки (upload/кэши/галереи). Также: заменён `labtools-price.pdf` (owner d375c419) в ОБА хранилища (MinIO `assets/…` ETag→6426ddd0 + локалка IIS, MD5 сверены). Split-brain админка(Local)↔боевой app(MinIO) — cross-ref [[concepts/galleries-storage-class-local-not-s3]].
|
||||
|
||||
## [2026-06-29] deploy | pilonuxt `cf2bba2` LIVE (стек 16) — ADR-0010: формы переехали с monolith `@snollajs/snolla` на `@snollajs/forms-api`, snolla **дропнут целиком**, core@0.1.1 (Buffer вместо btoa). Тот же build-пайплайн (форс-регенерация клиента + save|ssh load + push с VDS). **Новый обязательный шаг — tree-check `.output/server/node_modules` ПЕРЕД перекатом** (вся сага content-api↔snolla про dual-instance, dev маскирует unmet-deps): `docker run --rm --entrypoint sh <img> -c 'find /app/.output/server/node_modules ...'` → подтвердил 0× snolla, 0× btoa, ровно 1× data@0.9.1, core 0.1.1 + content-api 0.14 + forms-api 0.1.0. Smoke: SSR `/`+`/catalog`→200 (= btoa/snolla-дроп бандл не сломал), robots/yandex из БД (ADR-0009 не регресс), форма `POST /snolla-forms/forms?path=/checkout` пустой→422 JSON (valid НЕ слал — реальное письмо менеджеру), `/nope`→404. **Гоча smoke-харнеса:** forms-api отдаёт 422 только при `Accept: application/json` (иначе `prefersJson(req)` false → ветка `res.status(result.status||404).end()` даёт 404 — это не баг приложения, а отсутствие Accept в curl). Источник: наряд [deploy-pilonuxt-forms-api-drop-snolla]. См. [[concepts/portainer-stack-management-vds]] § Stack redeploy.
|
||||
|
||||
## [2026-06-29] deploy | pilonuxt `40bb383` LIVE (стек 16) — DB-backed staticPages + robots.txt (ADR-0009). Build из монорепы victor/pilorama98.ru: ключевой шаг — **форс-регенерация gitignored-клиента** `apps/web/src/generated/` (`rm -rf` + `yarn workspace nuxt-app schema-gen` против live MSSQL), иначе ensure-schema skip-if-present собрал бы старый клиент без `getStaticPage`/`getRobotsTxt` → server-500. Push с дома повторил traefik-499 на `.output`-слое даже при образе 425МБ (не multi-GB) → канонический fallback `docker save | ssh vds 'docker load'` + push с VDS (registry локален) сработал. Перекат через Portainer PUT стек 16 (`pullImage:true`, байты+UTF8 gotcha #9). Smoke acceptance 6/6 телами: robots полный из БД (Yandex Clean-param + Sitemap, не статика), yandex/google верификации 200, /nope→Nuxt-404, /+/catalog→200. Источник: наряд [deploy-pilonuxt-static-pages-robots]. См. [[concepts/portainer-stack-management-vds]] § Stack redeploy.
|
||||
|
||||
Reference in New Issue
Block a user