feat(galleries): migrate pilorama98 gallery originals Local-disk → S3

snolla galleries 404 (imgproxy "Source unreachable"): legacy storageClient
"galleries" = MoreThenCms Local storage class (App_Data\galleries\<siteId>),
never in S3 — products were migrated, galleries weren't. Path A (user pick):
new bucket `galleries`, 301 pilorama98 originals (77 MiB) rclone'd from RUVDS
IIS → s3://galleries/37e6…/<guid>.jpg verbatim. snolla code unchanged
(storageClient=bucket is the working convention). imgproxy smoke from
books-vds: real obj 200 image/webp, fake guid 404. Other sites unmigrated
(scope). Inbox sent to victor/snolla.

- close [migrate-gallery-originals-to-s3] (scope pilorama98)
- NEW .wiki concept galleries-storage-class-local-not-s3
- fix stale minio-imgproxy-on-vds (pipeline on books-vds since 2026-06-08)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 14:50:16 +03:00
parent 4c640dec65
commit f04426617c
4 changed files with 80 additions and 7 deletions

View File

@@ -1,4 +1,5 @@
# Admin Task Board
_Updated: 2026-06-12 (4) — 🟢 **[migrate-gallery-originals-to-s3] closed (scope pilorama98).** Галереи snolla не отдавались (imgproxy 404) т.к. в legacy `storageClient="galleries"` = тип **Local** (диск `App_Data\galleries\<siteId>`), НЕ S3 — оригиналы никогда туда не заливались (продукты — заливались, bucket `pilorama98`). Развилка A/B закрыта фактами MinIO → **A**: создан bucket `galleries`, залиты 301 файл (77 MiB) pilorama98 (siteId `37e67fc4…`) с RUVDS IIS через rclone → `s3://galleries/<siteId>/<guid>.jpg` verbatim (== ровно то, что строит `middleware/galleries.js:47`). Код snolla НЕ менялся. Verify: count+size == source. Smoke с books-vds: реальный объект imgproxy → 200 webp, fake → 404. Уточнение инфра: `imgproxy.kzntsv.site` с 08.06 на **books-vds** (стек 29/30), не на windows-host — `minio-imgproxy-on-vds.md` был stale. Inbox victor/snolla отправлен._
_Updated: 2026-06-12 (3) — 🟢 **bookva-es snapshot done** (не отложен). Portainer stack 37 пересоздан с `path.repo=/snapshots`+bind (PUT API, том цел, epz/products уцелели), repo `kreknin` зарегистрирован, snapshot-блок в `run.sh`. Поймана коллизия basename `snapshots` (оба ES-каталога одноимённы → сливались в один dest-репо, порча обоих) → источник bookva-es = родительский `/usr/docker/bookva-es`. Verified: `BOOKS-VDS backup OK 10m28s`, на kreknin раздельно `snapshots/`(slovo index-41) + `bookva-es/snapshots/`(index-4). Таска `bookva-es-snapshot-repo` 🟢 closed._
_Updated: 2026-06-12 (2) — 🟢 **bookva-* backup gap закрыт.** bookva tenant (db/mongo/es/minio, поднят 26.05) не бэкапился — books-скрипт написан 25.05 до bookva, покрытие не расширили (висело wiki-follow-up #2). Добавлены bookva-db (тот же root pw), bookva-mongo (no-auth), bookva-minio (raw volume) в `books-vds-backup-daily-kreknin/run.sh`; деплой == репо (`.bak-pre-bookva`). Verified green: `BOOKS-VDS backup OK 10m42s`, артефакты на kreknin (bookva-mariadb 257M, bookva-mongo 3.2M, bookva-minio 1.4G). bookva-es отложен → `bookva-es-snapshot-repo` (покрыт slovo-снапшотом, индексы идентичны). Правило в память: новый stateful → бэкап в том же коммите._
_Updated: 2026-06-12 — 🔴→🟢 **MSSQL backup incident + estate backup audit.** VDS daily backup падал с 12.06 (line 108, exit 1): mssql-блок (added 11.06) использовал `WITH ... INIT` → упирался в компрессованный media-header майских `.bak` (Express не пишет COMPRESSION) → молча падал первым cron-запуском. **5 боевых CMS-баз 3 недели без offsite-копии.** Fix: `INIT`→`FORMAT`, прогон verified (MoreThenCms 910M/StayerCalculator 528M/StayerPrice 39M/TireService 4.5M/stostayer 990M на kreknin). Репо-копия `run.sh` синхронизирована (mssql-блока не было). Concept: `.wiki/concepts/mssql-on-vds.md` gotcha#2. **Estate backup-аудит** → `.wiki/concepts/backup-inventory-2026-06.md`: ✅ openwrt UCI backup настроен (cron 03:30 → kreknin, restricted forced-command key); ⚪ заведена `kreknin-self-backup` (#1 SPOF, на потом); nl-vds x-ui.db — user: не нужен; windows-host stale script — user: забыть._
@@ -362,7 +363,9 @@ vehicles-loader (stostayer) деплоится в Docker у клиента (lite
---
## [migrate-gallery-originals-to-s3] — Галерейные (blog/gallery) картинки не отдаются ни на dev (imgproxy 404 "Source image is unreachable"), ни на prod (legacy-ресайз 404). Диагностика проведена в victor/snolla против живого pilorama98 + кода MoreThenCms. Нужен доступ к БОЕВОМУ серверу (локальный диск IIS + MinIO/S3), которого нет у snolla-агента — поэтому задача на admin.
## 🟢 [migrate-gallery-originals-to-s3] — closed 2026-06-12 (scope: pilorama98) — путь A: создан bucket `galleries`, залиты 301 файл (77 MiB) legacy-оригиналов pilorama98 с RUVDS IIS `App_Data\galleries\37e6…` → `s3://galleries/37e67fc44b9e4c06a522a26a73bac9b0/<guid>.jpg`. Smoke с books-vds: реальный объект через imgproxy → 200 image/webp 32102 B, fake guid → 404. Код snolla НЕ менялся (storageClient=имя бакета — рабочая конвенция). Inbox victor/snolla отправлен. Развилка A/B закрыта фактами MinIO: galleries в legacy = Local storage class (диск), никогда не в S3; A выбран user'ом.
Галерейные (blog/gallery) картинки не отдаются ни на dev (imgproxy 404 "Source image is unreachable"), ни на prod (legacy-ресайз 404). Диагностика проведена в victor/snolla против живого pilorama98 + кода MoreThenCms. Нужен доступ к БОЕВОМУ серверу (локальный диск IIS + MinIO/S3), которого нет у snolla-агента — поэтому задача на admin.
## Диагноз (доказан, не гипотеза)
@@ -412,11 +415,19 @@ vehicles-loader (stostayer) деплоится в Docker у клиента (lite
**weight:** needs-human
**notify:** victor/snolla
**Status:** ready
**Where I stopped:** (not started) — диагностика выполнена в victor/snolla, см. тело
**Next action:** 1) В MinIO/S3 проверить: существует ли бакет `galleries` и есть ли в нём объекты `<siteId>/<storageFilename>`; сверить с тем, как лежат рабочие продукты (`s3://pilorama98/products/...`). 2) Залить legacy-оригиналы `App_Data\galleries\<siteId>\*` в целевой бакет под ключи `<siteId>/<storageFilename>`. 3) Re-smoke imgproxy на одной картинке.
**Status:** done (closed 2026-06-12, scope pilorama98)
**Where I stopped:** мигрировано. MinIO (books-vds стек 30, за `imgproxy.kzntsv.site` с 08.06): bucket `galleries` создан, prefix `37e67fc44b9e4c06a522a26a73bac9b0/` залит (301×.jpg, 77.0 MiB, rclone RUVDS→minio.kzntsv.site, count+size == source). Smoke OK с books-vds (200/404).
**Next action:** — (хвосты в inbox victor/snolla: остальные siteId-папки галерей не мигрированы по скоупу; legacy prod-ресайз остаётся 404 by design).
**Branch:** master
**Notify:** victor/snolla
**Notify:** victor/snolla — ✅ inbox `.claude-inbox/2026-06-12T11-47-33Z-admin.md`
**Closure note (2026-06-12):**
- **Развилка A/B закрыта фактами:** в Web.config legacy `<fileStorageClients>` storageClient `galleries` = тип **Local** (`GalleriesStorage`, диск `App_Data\galleries\<siteId>`), НЕ S3 → галереи никогда не были в S3 (продукты — да, bucket `pilorama98/products`). Корень доказан. **A** (отдельный bucket `galleries`, key `<siteId>/<storageFilename>`) выбран user'ом → нулевой код-фикс snolla.
- **MinIO state:** bucket'ы до миграции — artmone/books/imgproxytest/manuals/modulair/modules/obsidian/ozon/pilorama98/strapi/test/upload; `galleries` отсутствовал; `pilorama98` = только `products/` (шард по hex, без siteId). Зафиксировано.
- **Залив:** RUVDS IIS `C:\sites\snolla\App_Data\galleries\37e67fc44b9e4c06a522a26a73bac9b0\` (301 файл, dir-name уже lowercase 32-hex == ownerId snolla) → `s3://galleries/37e6…/<guid>.jpg` verbatim. rclone v1.74.2 inline s3-remote (env-var config, без записи cred). Verify: 301 объект, 77.012 MiB == source.
- **Smoke (с books-vds, мимо LAN-DNS-перехвата воркстейшна):** подписанный imgproxy-URL реального объекта → 200 image/webp 32102 B; несуществующий guid → 404 (негативный контроль).
- **Wiki-drift зафиксирован:** `minio-imgproxy-on-vds.md` устарел (говорит pipeline на windows-host; фактически imgproxy на books-vds с 08.06). Добавлен concept `galleries-storage-class-local-not-s3`.
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/snolla / 2026-06-12T11:09:50.425Z -->
<!-- closed-by: vitya@.admin-exec / 2026-06-12 / path A: bucket galleries + 301 pilorama98 originals, imgproxy 200 verified, snolla code unchanged -->
---

View File

@@ -0,0 +1,58 @@
---
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 не меняется:
```powershell
# на 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`:
```bash
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-классы (`assets`/`images`/…) в snolla через imgproxy — их ждёт та же дыра, тот же рецепт.
См. задачу `.tasks/STATUS.md``[migrate-gallery-originals-to-s3]`.

View File

@@ -1,12 +1,14 @@
---
title: MinIO + imgproxy stack on vds-kzntsv
status: live as standby — CMS image pipeline остаётся на windows-host (closed-source DLL hardcoded URL)
status: STANDBY/unused for CMS — живой CMS image pipeline переехал на books-vds 2026-06-08 (см. ниже)
tags: [vds, minio, imgproxy, migration, ops]
related: [[vds-kzntsv]], [[recovery-architecture-snapshot]], [[mssql-on-vds]]
related: [[vds-kzntsv]], [[recovery-architecture-snapshot]], [[mssql-on-vds]], [[../entities/books-vds]], [[galleries-storage-class-local-not-s3]]
---
# MinIO + imgproxy on vds-kzntsv
> ⚠ **Stale для живого pipeline (2026-06-08).** `imgproxy.kzntsv.site` (hardcoded в `MoreThenCms.Modules.Imgproxy.dll`) теперь смотрит на **[[../entities/books-vds]]** (стек 29 imgproxy + стек 30 minio, bucket'ы `pilorama98`, `galleries`, …), а windows-recovery-host декоммишнен. Этот VDS-kzntsv стек — standby/неиспользуемый для CMS. Option C ниже («остаётся на windows-host») историчен. Миграция галерей и рецепт smoke — [[galleries-storage-class-local-not-s3]].
Запущен 2026-05-22 как часть `minio-imgproxy-vds-migration`. Заодно **upgrade MinIO** `RELEASE.2020-07-13` (5 лет, CVE-куча) → `RELEASE.2025-09-07`.
## Architecture

View File

@@ -69,3 +69,5 @@ Append-only log of wiki operations (ingests, promotions, lints, migrations).
## [2026-06-06] decision | nl-vds-3xui: +3 classic Shadowsocks inbounds (ports 32031/32/33) for Outline-app, per-key revocable; protocol e2e-tested OK, RF reachability unverified (SS DPI-blocked in RF); creds in pass; discovered undocumented VLESS:13027.
## [2026-06-10] ingest | concepts/mcp-init-resilience
## [2026-06-12] migrate | galleries pilorama98 → S3. NEW concepts/galleries-storage-class-local-not-s3.md (root: storageClient `galleries` = Local disk class, never in S3; snolla ждёт s3://galleries/<siteId>/<file> → 404). Путь A: bucket `galleries` создан, 301 файл (77 MiB) с RUVDS IIS залиты verbatim, imgproxy 200 verified с books-vds. UPDATE concepts/minio-imgproxy-on-vds.md (stale-баннер: pipeline на books-vds с 08.06, не windows-host). Task migrate-gallery-originals-to-s3 🟢.