Files
admin/.wiki/concepts/galleries-storage-class-local-not-s3.md
vitya 2e5adcf11e 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>
2026-07-03 13:47:21 +03:00

6.4 KiB
Raw Blame History

title: MoreThenCms galleries — Local storage class, never in S3 (snolla imgproxy 404 root cause) status: resolved for pilorama98 (2026-06-12); other sites unmigrated tags: [cms, snolla, morethencms, minio, imgproxy, s3, galleries, migration] related: minio-imgproxy-on-vds, ../entities/books-vds, ../entities/ruvds-iis-host

Galleries storage class = Local (disk), not S3

Root cause

В MoreThenCms <fileStorageClients> (Web.config) каждый storageClient — это алиас конфига, не имя бакета. Алиас galleries маппится на тип MoreThenCms.FileStorage.Local.GalleriesStorage → файлы на локальном диске IIS:

C:\sites\snolla\App_Data\galleries\<siteId:N>\<storageFilename-guid>.<ext>

То же у assets, images, scripts, stylesheets, watermarks — все Local. Продукты (e-commerce) — другой механизм, реально в S3 (bucket pilorama98, prefix products/, шард по hex, без siteId).

Переписка snolla (Node) наивно строит источник как s3://${image.storageClient}/${siteId}/${storageFilename} (packages/snolla/middleware/galleries.js:47, lib/viewModels/index.js:289) — т.е. ждёт галереи в S3 в бакете с именем galleries. Но их там никогда не было → imgproxy «Source image is unreachable» (404). Это НЕ баг билдера URL и НЕ поломка ресайза — просто данные не мигрированы из Local-диска в S3.

Симптом-близнец на legacy-prod (pilorama98.ru/galleries/<id>/images/<size>/<seq>.jpg): original/* = 200 (читает диск), любой ресайз = 404 — отдельная мёртвая ветка legacy-ресайзера (System.Drawing/image-cache). Целевая архитектура = imgproxy, legacy-ресайз не чинится.

Fix recipe (путь A — выбран)

storageClient как имя бакета = рабочая конвенция snolla. Заливаем Local-диск в одноимённый bucket, ключи verbatim — код snolla не меняется:

# на RUVDS IIS (rclone v1.74.2 уже стоит). env-var remote, без записи cred в rclone.conf
$env:RCLONE_CONFIG_MIN_TYPE='s3'; $env:RCLONE_CONFIG_MIN_PROVIDER='Minio'
$env:RCLONE_CONFIG_MIN_ACCESS_KEY_ID='<minio-access>'; $env:RCLONE_CONFIG_MIN_SECRET_ACCESS_KEY='<minio-secret>'
$env:RCLONE_CONFIG_MIN_ENDPOINT='https://minio.kzntsv.site'   # books-vds S3 API за traefik
& rclone mkdir min:galleries
& rclone copy "C:\sites\snolla\App_Data\galleries\<siteId>" min:galleries/<siteId> --transfers 8
& rclone size min:galleries/<siteId>   # сверить count+size с источником
  • <siteId> папки на диске уже lowercase 32-hex == ownerId snolla (String(site.id).replace(-).toLowerCase()) → ключи совпадают 1:1, трансформаций не надо.
  • Креды MinIO: pass books-vds/full-env (контейнер minio, env MINIO_ACCESS_KEY/MINIO_SECRET_KEY). imgproxy key/salt — env контейнера imgproxy.

Smoke (важно: мимо LAN-DNS воркстейшна)

imgproxy.kzntsv.site с воркстейшна резолвится в 127.0.0.1 (LAN-перехват CMS-доменов) — смоук гнать с самого books-vds или --resolve …:89.253.255.133:

curl -sk --resolve imgproxy.kzntsv.site:443:127.0.0.1 'https://imgproxy.kzntsv.site/<sig>/fill/W/H/ce/0/<b64url(s3://galleries/<siteId>/<guid>.jpg)>.webp'
# подпись: base64url( HMAC_SHA256(KEY_bin, SALT_bin + path) ), KEY/SALT = hex из env imgproxy

Реальный объект → 200 image/webp; несуществующий guid → 404 (негативный контроль).

Status / scope

  • pilorama98 (siteId 37e67fc44b9e4c06a522a26a73bac9b0): мигрирован 2026-06-12 — 301 файл / 77 MiB, imgproxy 200 verified.
  • Остальные ~40 siteId-папок в App_Data\galleriesНЕ мигрированы (часть пустые). Bucket galleries shared; заполнен только префикс pilorama98. Другие сайты в snolla → 404 пока не зальёшь (регрессии нет).
  • Если поднимаются другие Local-классы (images/scripts/stylesheets/watermarks) в snolla через imgproxy — их ждёт та же дыра, тот же рецепт.

assets класс — мигрирован 2026-06-13

Тот же Local-класс, тот же путь A. Отличие ключа: prefix = OwnerId медиа-папки (из таблицы Folders), НЕ siteId; snolla строит s3://assets/<ownerId>/<storageFilename> (middleware/assets.js:98), storageFilename плоский. Залит весь C:\sites\snolla\App_Data\assets (145 owner-папок, 4750 файлов / 215.5 MiB) в новый bucket assets rclone'ом с RUVDS — scope шире pilorama98 (owner→сайт маппинг неочевиден; заливка всего гарантирует покрытие + закрывает класс для всех тенантов). Verify count+size == source. Smoke: brevno.jpg → 200, валидно-подписанный фейк → 404.

⚠️ На стороне 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].