--- title: MinIO + imgproxy stack on vds-kzntsv status: live as standby — CMS image pipeline остаётся на windows-host (closed-source DLL hardcoded URL) tags: [vds, minio, imgproxy, migration, ops] related: [[vds-kzntsv]], [[recovery-architecture-snapshot]], [[mssql-on-vds]] --- # MinIO + imgproxy on vds-kzntsv Запущен 2026-05-22 как часть `minio-imgproxy-vds-migration`. Заодно **upgrade MinIO** `RELEASE.2020-07-13` (5 лет, CVE-куча) → `RELEASE.2025-09-07`. ## Architecture ``` CMS templates (URL pattern: imgproxy.kzntsv.site//.../plain/s3:///) → traefik (windows-host) [пока, до DNS swap] → imgproxy-nginx (windows-host) [пока] → imgproxy (windows-host) [пока] → MinIO localhost:9000 (windows-host) [пока] После DNS swap imgproxy.kzntsv.site → 89.253.255.94 (VDS): → VDS traefik (HTTPS, LE cert) → imgproxy-nginx (VDS, nginx:alpine, 10 GB cache) → imgproxy (VDS, darthsim/imgproxy:latest) → minio (VDS, RELEASE.2025-09-07, internal docker DNS через proxy network) ``` Stack: `/opt/stacks/storage/minio-imgproxy/{docker-compose.yml,.env,data,nginx.conf,nginx-cache}`. Все 3 контейнера в `proxy` network — service-name DNS работает. **Traefik routes (live):** - `minio.vds.kzntsv.site` → minio:9000 (S3 API) - `minio-console.vds.kzntsv.site` → minio:9001 (web UI) - `imgproxy.vds.kzntsv.site` → imgproxy-nginx:80 (test endpoint pre-cutover) После DNS swap имя `imgproxy.kzntsv.site` (без `.vds.`) добавить в traefik label imgproxy-nginx сервиса. ## Migration recipe ```bash # 1. pass insert (local) — preserve original IMGPROXY_KEY/SALT, reuse MinIO creds pass insert -m minio-vds/full-env < MINIO_ROOT_PASSWORD= IMGPROXY_KEY= IMGPROXY_SALT= EOF # 2. VDS: create dir + scp nginx.conf + write .env from pass + write compose ssh vitya@vds 'sudo mkdir -p /opt/stacks/storage/minio-imgproxy/{data,nginx-cache} sudo chown -R vitya:vitya /opt/stacks/storage' scp /nginx.conf vitya@vds:/opt/stacks/storage/minio-imgproxy/ pass show minio-vds/full-env | ssh vitya@vds 'cat > /opt/stacks/storage/minio-imgproxy/.env chmod 600 .env' # 3. docker compose up -d (image pull + start all 3) # 4. Local mc setup + bucket prep mc alias set old http://localhost:9000 mc alias set new https://minio.vds.kzntsv.site --insecure for b in $(mc ls old | awk '{print $NF}' | tr -d /); do mc mb --ignore-existing new/$b --insecure # mc 2025 не auto-creates на mirror done # 5. Mirror (background, ~1 час на 3 GB при 600 KiB/s) mc mirror --preserve --quiet --overwrite old new --insecure # 6. Verify per-bucket (size + object count) for b in $(mc ls old | awk '{print $NF}' | tr -d /); do echo "[$b]"; mc du old/$b; mc du new/$b --insecure done ``` ## Gotchas 1. **MinIO upgrade 5-летний gap WORKS via mc mirror** — старый FS backend (2020) → новый erasure-coded single-drive (2025) совместим через S3 API copy. `mc mirror` reads через S3 API, не сырое filesystem. Бинарный bind-mount свопнуть НЕЛЬЗЯ — формат `xl.meta` разный, новый MinIO не поднимется на старых dirs. 2. **mc 2025 НЕ auto-creates target buckets** — old mc (<2023?) делало неявно. Симптом: `Failed to perform mirroring The specified bucket does not exist`. Fix: `mc mb --ignore-existing new/` для каждого bucket заранее. 3. **Non-ASCII filenames + slow uplink → partial mirror failure**: один файл `Albrecht Dürer ...tif` (30 MiB) в `imgproxytest` bucket не доехал с первого `mc mirror` exit=0 — silent skip без error. Retry того же `mc mirror` подтащил. Recommendation: всегда verify по object count + size после mirror, не doверять exit code. 4. **IMGPROXY_KEY + IMGPROXY_SALT preserve обязательно** — это HMAC signing для URLs. Если поменять, все pre-existing CMS image URLs (signed ссылки в шаблонах + кешированные в admin) ломаются. Reuse same values из source. 5. **MinIO root creds vs AWS-style**: source использует MINIO_ACCESS_KEY/MINIO_SECRET_KEY (deprecated env vars), новый MinIO — MINIO_ROOT_USER/MINIO_ROOT_PASSWORD. Reuse same values (no rotation в этой миграции) → CMS+imgproxy credentials не меняются. Rotation — отдельная hardening-таска. ## CMS image pipeline — DLL hardcoded constraint (2026-05-22 finding) Browser-side URL pattern: `https://www./imgproxy//fill/W/H/no/1//)>.`. Sample (working URL provided by user): ``` https://www.pilorama98.ru/imgproxy/3rGOrTLEtTSjsrwvbs2fe75A1jDHFGPbaSe9ea7vtPQ/fill/800/800/no/1/czM6Ly9waWxvcmFtYTk4L3Byb2R1Y3RzL2wvOWQ0MjgwODktMGFjYi00ZGM2LWE3YjAtMGQxYzIxN2E1ZTFjLmpwZw.webp ``` Base64 source decode: `s3://pilorama98/products/l/9d428089-0acb-4dc6-a7b0-0d1c217a5e1c.jpg`. HMAC signature `3rGOr...` computed from `IMGPROXY_KEY + SALT` (preserved в нашей миграции). **Server-side architecture:** ``` Browser → https://www./imgproxy//... → traefik (windows-host) → IIS snolla site :8089 → ImgproxyHandler (MoreThenCms.Modules.Imgproxy.dll, closed-source) → HTTP GET https://imgproxy.kzntsv.site//... (HARDCODED in DLL) → traefik (windows-host) imgproxy.yml route → imgproxy-nginx:80 → imgproxy → MinIO localhost:9000 ``` Decoded DLL strings (`cat MoreThenCms.Modules.Imgproxy.dll | tr -cd '[:print:]\n' | grep -oE ...`): ``` /imgproxy;https://imgproxy.kzntsv.site/-ImgproxyHandler_Invoke ``` `MoreThenCms.Modules.Imgproxy.dll` closed-source (исходника нет в `~/projects/MoreThenCms`; ни одного `*.cs` файла с `imgproxy` literal). Options для cutover: - A) Hosts file + DNS-01 cert trick — средняя сложность - B) Local nginx-relay на windows + self-signed cert + CallTrust поведение DLL unknown — средне-высокая - C) Keep as-is — windows-host imgproxy/nginx/MinIO живут indefinitely, VDS stack = standby - D) Decompile + recompile DLL — высокая, fragile (binding+strong-name) **User decision 2026-05-22: Option C.** Image pipeline остаётся на windows-host; VDS stack — standby/future use. Decommission windows-host MinIO/imgproxy/imgproxy-nginx НЕ выполняется. Если в будущем понадобится cutover — рассматривать Option A (DNS-01 challenge через REGRU API) как наиболее clean путь, при условии что `imgproxy.kzntsv.site` снова станет нашим (сейчас занят другим сервисом на другом сервере). ## Rollback ```bash # On VDS cd /opt/stacks/storage/minio-imgproxy && sudo docker compose down -v # Source windows-host MinIO + imgproxy + imgproxy-nginx должны быть still running (48ч uptime acceptance). # Если уже decommissioned — docker start minio imgproxy imgproxy-nginx. # Snapshot insurance: tar archive bind-mount source data preserved отдельно (acceptance step 3). ```