1436 lines
229 KiB
Markdown
1436 lines
229 KiB
Markdown
# Admin Task Board
|
||
_Updated: 2026-07-04 (8) — 🟢 **[tandemmebel-web-vds-deploy] gallery-дефект ЗАКРЫТ на staging — GREEN.** Workshop прислал коррекцию sha → ребилд на `b02ca18` (core 0.16.2, SlugFeedPage-fix). Собрал `tandemmebel:b02ca18` (digest f8672218), передеплой стека 20 (env 8/8, healthy). DoD-чек **12/12 GREEN**: gallery-грид staging == prod ТОЧНО на всех роутах (bedrooms 80/80, kids 138/138, office 26/26 и т.д.). Крошки наполнены (`Главная/Мебель/Галерея идей`, были пустые), title полный (`Фото Офисная мебель…`), байты 28508≈прод 28253. Я флагал сомнение (мой DB-дамп показал: `/gallery` — конвенционный саб-роут, а не feed; SlugFeedPage-fix мог промахнуться) — но жёсткий рендер-чек показал GREEN, фикс сработал. **Cutover ОТЛОЖЕН оператором (не забыть!) — триггер запуска = владелец сайта меняет DNS reg.ru→89.253.255.94**, тогда verify авторит.NS→боевой Host в стек 20→live-smoke (порядок в [[NEXT_SESSION.md]]). Прогеру после разноса от оператора отправлен рабочий green-хендофф на свип 188 URL. RUVDS=rollback._
|
||
_Updated: 2026-07-04 (7) — 🔵 **[tandemmebel-web-vds-deploy] дефект идентифицирован = gallery FeedPages-VM.** Workshop прислал ops-таск: ребилд стека 20 на `a173401` (FeedPages page-type портирован, чинит gallery 404→200). Гейт green (origin HEAD=a173401 ⊇ a173401+22bd8f2), собрал `tandemmebel:a173401` (snolla 0.35.0/core 0.16.0/data 0.13.0, digest 6962bd97), передеплоил стек 20 (Portainer API, env 8/8, healthy). Роутинг чинится: 12/12 furniture gallery-роутов 404→200. **НО рендер-чек RED — grid ПУСТОЙ на всех роутах** (staging `/galleries/*/images` = 0 vs prod 24/80/62) + **breadcrumb-подписи пустые** (`itemprop=name` без текста). Оба симптома = один корень: FeedPage-VM не прокидывает `item` в шаблон. Оператор дефект подтвердил независимо. Мой деплой чист; фикс на прогере (tandemmebel.ru). Пропинговал прогера с уликами (empty-grid + breadcrumb addendum) → ждёт новый sha, прогоню тот же DoD (staging_imgs==prod_imgs + непустые крошки). Cutover HELD. См. [[NEXT_SESSION.md]]._
|
||
_Updated: 2026-07-04 (6) — 🔵 **[tandemmebel-web-vds-deploy] BLOCKED на новом дефекте.** Дал оператору staging-URL (https://tandemmebel.vds.kzntsv.site) на визуальную проверку sharp-сборки → оператор нашёл НОВЫЙ дефект (иной, чем прошлые глиф/imgproxy), специфику не назвал, «чиним в новой сессии». Cutover HELD. Мои автоматические гейты по sharp-staging были green (media/watermark/variant-cache/parity) — значит дефект визуальный, не пойманный curl-смоком. Sharp-staging жив на стеке 20 для разбора в новой сессии. См. [[NEXT_SESSION.md]]._
|
||
_Updated: 2026-07-04 (5) — 🟢 **[tandemmebel-sharp-staging-rebuild] closed — SHARP staging GREEN.** Пивот tandemmebel на in-process sharp завершён. Образ `tandemmebel:68b93a9` (snolla 0.34.0/core 0.15.0, digest 12288b15) собран на VDS, стек 20 обновлён (env verbatim 8/8, imgproxy из tandem-пути убран). Parity-smoke GREEN: nav 8/8, sitemap staging⊇prod (prod-only=0, +12 categories benign), 2012-посты 31/31, redirects 301/301; sharp media = slug-URL `/galleries/<gid>/images/<variant>/<seq>` 200 webp serve-bytes, **0 /imgproxy-рефов** (осиротевший imgproxy-конфиг в пине эмпирически мёртв → heads-up воркшопу вычистить), variant-cache пишется (32 объекта content-addressed), watermark визуально ✓ featured+lightbox / gallery-small чистый. [tandemmebel-web-vds-deploy] теперь 🟡 sharp-staging-green, ждёт **cutover DNS — gated оператором** (reg.ru→89.253.255.94). RUVDS=rollback._
|
||
_Updated: 2026-07-04 (4) — 🟢 **[minio-variant-cache-bucket] closed.** sharp-prereq. Создан бакет `variant-cache` в shared MinIO (minio.kzntsv.site/books-vds) — S3-эквивалент легаси App_Data/imageCache, куда sharp кладёт resize+watermark варианты on-demand (без него 500 NoSuchBucket на всех sharp-картинках = agent-блокер 1 из tandemmebel sharp-verify). Грант: snolla S3-креды == MinIO root (сверено) → в mode-server-fs полный доступ by construction, IAM не нужен. Write-verify под деплойными кредами: put→stat→get(md5 match)→delete→gone ✓. Другие 17 бакетов не тронуты, lifecycle не ставил (кэш регенерируем). Готовит почву под sharp-переезд tandemmebel (движок victor/snolla media-sharp-imageprocessor-port в переработке)._
|
||
_Updated: 2026-07-04 (3) — 🟢 **[imgproxy-stack29-watermark-rollback] closed.** Оператор сменил watermark-подход: tandemmebel уходит с imgproxy-watermark на in-process sharp (victor/snolla media-sharp-serve-and-watermark) → глифу на SHARED стеке 29 не место. Снял `IMGPROXY_WATERMARK_DATA` со стека 29 (books-vds): вырезал строку → результат byte-identical pre-watermark бэкапу (cross-check true) → PUT+redeploy+purge. Verify (не задеть боевых): pilorama98 (главный консюмер) 3 рендера == baseline (43950/172042/160010B) не задет; stostayer НЕ imgproxy-консюмер (картинки /galleries//images//assets/) → не затрагивается; WATERMARK_DATA present:false; tandem featured→чистый (ожидаемо, staging→sharp). Глиф-бинарь 7127e92a оставлен для sharp. [tandemmebel-web-vds-deploy] теперь ждёт ПЕРЕСБОРА образа на sharp-пин перед cutover. Notify workshop._
|
||
_Updated: 2026-07-04 (2) — 🟡 **[tandemmebel-web-vds-deploy] STAGING GREEN, ждёт cutover.** Финал тиража tandemmebel. Образ `tandemmebel:0facb35` (snolla@0.32.2/core@0.13.8, digest 464d2f22, watermark-override в пине) собран на VDS (обход 499). Стек Portainer **Id 20** (staging-rule `tandemmebel.vds.kzntsv.site`, env verbatim из labtools стека 17, 8/8), контейнер healthy MSSQL+S3. imgproxy-nginx кэш (books-vds стек 29) purged → featured отдаёт вотермарк без cache-bust (27820B). Staging-smoke с VDS GREEN: status-parity 10/10, sitemap staging⊇prod (prod-only=0, +12 /projects/categories/*=benign#3, /articles 404 parity), trailing 301/301, blog-post 200 (+3.5KB=benign#2 imgproxy vs /galleries/ 26/26), 2012-посты 31/31, theme 21/22 MD5-OK. Находка (не блокер): prod Gotham-Pro.css=0B пустой, staging=4436B корректный → cutover чинит латентный прод-баг шрифта. **HOLD cutover** — жду отмашку оператора на DNS reg.ru→89.253.255.94; порядок: verify авторит.NS → ТОЛЬКО потом боевой Host-rule (иначе LE упадёт на RUVDS). RUVDS=rollback._
|
||
_Updated: 2026-07-04 — 🟢 **[imgproxy-watermark-glyph-books-vds] closed.** Ops-подхват по тиражу tandemmebel: воркшоп попросил выставить глиф-вотермарк на imgproxy стек 29 (books-vds). Первый base64 (inbox И закоммиченный таск) был БИТЫЙ на источнике — `tasks_create` порезал 10КБ-поле (sha `f8f0…`≠`e995…`, IEND нет, зацикленный хвост `FQtAlGLzMfBc`×N); поймал sha-сверкой ДО прода (не запушил битьё → imgproxy не упал). Воркшоп до-доставил `logo.png` бинарём в git (`host-stacks/books-vds/tandemmebel-watermark-logo.png`, commit `7127e92a`, sha256 `e995…971fc`, 8015B, IEND ok). PUT стека 29 (Portainer `portainer.kzntsv.site` ep1, X-API-Key, `IMGPROXY_WATERMARK_DATA` inline, `PullImage:false`, БЕЗ глобального `_OPACITY`). Smoke cache-busted: A tandem вотермарк ВИДЕН глазами (27294→27820B); B pilorama контроль byte-identical (160010B) — opacity не просочилась. Notify workshop → tandem-агент на ре-скрин→пин→deploy. Дальше жду `[tandemmebel-web-vds-deploy]`._
|
||
_Updated: 2026-07-03 — 🟡 **заведена [morethencms-s3-filestorage-provider].** Разведка «почему /admin/assets/.../delete → 500» на RUVDS IIS: корень = app pool `IIS AppPool\snolla` имел на `App_Data` только RX после scp-миграции → `File.Delete` UnauthorizedAccessException (не MinIO, провайдер `assets`=Local; NRE-версии исключены проверкой строк Folders/Files в БД). Fix применён: `icacls App_Data /grant "IIS AppPool\snolla:(OI)(CI)(M)" /T` (44293 файла, verified). Попутно заменён `labtools-price.pdf` в ОБА хранилища (MinIO ETag→6426ddd0 + локалка, MD5 сверены). Настоящее закрытие split-brain (админка=Local vs фронт=MinIO) → новая таска на S3-провайдер `MoreThenCms.FileStorage.S3` (name approved): ТЗ доставлено в инбокс MoreThenCms, объём=все контентные классы, 1 MinIO, deploy на локальный IIS windows-recovery-host→боевой. DNS-аудит: инфра-хосты не перехватываются (нет 127.0.0.1). Concepts: snolla-admin-appdata-acl-500 + galleries-storage-class-local-not-s3._
|
||
_Updated: 2026-06-29 (4) — 🟢 **[pilonuxt-deploy-sitemap-taxonomy] closed.** pilonuxt `6b9ae63` LIVE (стек 16) — bump-only sitemap taxonomy: content-api 0.15→0.16, core 0.2→0.4 (taxonomy enum, прокси не менялся). **Tree-check `.output` 5/5** (content-api 0.16.0, core 0.4.0, data 0.9.1 1×, snolla 0, btoa 0). Push словил **499 на .output** → ретрай прошёл (digest `226584ce…`). Acceptance 4/4: `/sitemap.xml` индекс 7 записей с `store-taxonomy-1`; `/sitemap-store-taxonomy-1.xml`→200 57 url абсолютные (35 categories+22 tags, vendors 0=пусто в CMS); v1 цел (products-1/pages-1 200); `taxonomy-999`→404. Регресс `/`/`/catalog` 200, stderr чист. Откат: стек 16→`42f9d64`. Ack→victor/pilorama98.ru._
|
||
_Updated: 2026-06-29 (3) — 🟢 **[pilonuxt-deploy-sitemap] closed.** pilonuxt `42f9d64` LIVE (стек 16) — sitemap.xml через content-api@0.15.0 (+core@0.2.0). Build из монорепы (форс schema-gen против прод-MSSQL). **Tree-check `.output` 5/5:** content-api 0.15.0, core 0.2.0, data 0.9.1 (1×), snolla 0 (7 grep-хитов = комменты-провенансы, 0 import), btoa 0. Push **напрямую** (499 не случился). Acceptance 3/3: `/sitemap.xml`→200 `<sitemapindex>` 6 источников; `/sitemap-pages-1.xml`→200 `<urlset>` 70 url все абсолютные; `/sitemap-store-products-999.xml`→404. Регресс цел (robots/`/`/`/catalog` 200), stderr чист. Откат: стек 16→`cf2bba2`. Ack→victor/pilorama98.ru._
|
||
_Updated: 2026-06-29 (2) — 🟢 **[deploy-pilonuxt-forms-api-drop-snolla] closed.** pilonuxt `cf2bba2` LIVE (стек 16) — ADR-0010: формы на `@snollajs/forms-api`, `@snollajs/snolla` дропнут целиком, core@0.1.1. Перед перекатом **tree-check `.output` 4/4** (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, robots/yandex из БД (ADR-0009 не регресс), форма `?path=/checkout` пустой→422 JSON / `/nope`→404 (valid НЕ слал). Гоча: 422 только с `Accept: application/json`. Notify victor/pilorama98.ru (монореп-inbox)._
|
||
_Updated: 2026-06-29 — 🟢 **[deploy-pilonuxt-static-pages-robots] closed.** pilonuxt `40bb383` (DB-backed staticPages + robots.txt, ADR-0009) LIVE на проде (Portainer стек 16). Build из монорепы с **форс-регенерацией клиента** (`rm src/generated`+`schema-gen 0.6` → новые методы `getStaticPage`/`getRobotsTxt`); push с дома 499 на `.output`-слое → fallback `docker save | ssh vds load`+push с VDS. Smoke acceptance 6/6 verified телами (robots полный из БД с Yandex Clean-param+Sitemap, yandex/google верификации, /nope→404, /+/catalog→200). Notify victor/pilorama98.ru отправлен._
|
||
_Updated: 2026-06-18 (2) — 🟢 **[books-task-runner-registry-auth-cred] closed.** Кред books-ci (секция `registry`, полная) в task-runner config-volume на books-vds (644 root, бэкап, EACCES не повторён). После deploy нового кода (`master-bf2a8c5`) прогнан `registryGc {dryRun:true}` через scheduler-dispatch: **success, 401 нет**, repos books-* видны (7), отчёт сгенерён. Acceptance auth/dryRun met. **Finding отдан books (НЕ блокер):** GC бежит, но `drop=0` всегда (keep=tags−1) — `getCreated`→null для всех манифестов (configDigest не извлекается, OCI image-index?), `planDeletions` защищает null-dated группы → keepLastN не применяется, реестр не чистится. Bug в books `lib/registryV2.js`, owner — books._
|
||
_Updated: 2026-06-18 — 🔵 **[books-task-runner-registry-auth-cred] blocked.** Кред books-ci (секция `registry`, baseUrl+username+password — полная, bind-mount затеняет config образа) положен в task-runner config-volume на books-vds; mode 644 root сохранён (бэкап `.bak-pre-registry-2026-06-18`, EACCES-инцидент 25.05→18.06 не повторён), container restart→healthy, `config.get('registry.*')` резолвится. books-ci валиден: `/v2/_catalog`→200, репы books-* видны, 401 нет. **Блокер:** работающий образ (88f2bee, 17.06) содержит СТАРЫЙ registryGc (Gitea API), тега `master-a3734d5` в реестре нет → CI не собрал v2-DELETE rewrite. In-app dryRun ждёт build+deploy. Inbox-ответ books отправлен._
|
||
_Updated: 2026-06-13 — 🟢 **[migrate-assets-originals-to-s3] closed.** assets-оригиналы (весь `App_Data\assets`: 4750 obj / 215.5 MiB, 145 owner-папок) залиты в новый MinIO bucket `assets` (books-vds) rclone'ом с RUVDS IIS, ключи `<ownerId>/<storageFilename>` verbatim. Verify count+size == source. Smoke (мимо LAN-DNS): brevno.jpg → 200 image/webp 64556 B (был 404), валидно-подписанный несуществующий объект → 404 (негатив-контроль). Код snolla не менялся (path A); scope намеренно шире pilorama98 — закрывает класс-404 для всех тенантов snolla, регрессии нет (shared bucket, как galleries). ⚠️ snolla-side остаётся `content-api/routes/assets.js` (пустой stub) — без него картинки не рендерятся на сайте. Inbox victor/snolla отправлен._
|
||
_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: забыть._
|
||
_Updated: 2026-06-08 — 🟢 ad-hoc fix `maljarka.tandemmebel.ru` (iis-migration follow-up): домен переехал на RUVDS корректно (DNS/TLS/IIS ок), но отдавал 502 **только на HTTPS**. Root cause не в миграции: у тенанта maljarka `dbo.Sites.SettingsData=NULL` (нет блока `httpSecure`) → MoreThenCms `SnollaMiddleware` бросает KeyNotFound на HTTPS-ветке → 502; по HTTP 200. Fix: `UPDATE dbo.Sites SET SettingsData='{httpSecure:enableHttps=true,…}' WHERE SiteId=a2476738… AND SettingsData IS NULL` + `Restart-WebAppPool snolla`. Verified maljarka:443→200 (server-local + external по 80.64.31.36), kupimknigi не задет. Audit: maljarka — единственный NULL-сайт с :443-биндингом из 25. rimiz degraded по др. причине. Concept: `.wiki/concepts/morethencms-null-settingsdata-https-502.md`._
|
||
_Updated: 2026-06-05 (вечер) — 🟡 `fix-nl-vds-reality-pq-dest`: по команде user применён server-side fix + ротация кредов. Reality dest intel→microsoft (x-ui.db + restart), сквозной тоннель через реальный :443 = HTTP 204; 32030 жив. Креды ротированы (root SSH / panel user+pass / panel secret-JWT — светились в плейнтексте; новые в `pass nl-vds-3xui/full-env`, старые отклоняются, БД-бэкап на сервере). Остался client-side: user меняет SNI в v2rayN (`pqmkayaxo2`→microsoft) + подтверждает → close._
|
||
_Updated: 2026-06-05 — заведена `fix-nl-vds-reality-pq-dest`. Новый NL VDS (213.176.64.253, 3x-UI/Xray 26.6.1): диагностирован отказ Reality-инбаунда 443 — **ML-DSA-65 (PQ) × dest `www.intel.com` (Akamai шлёт HRR)**; plain VLESS 32030 работает. Узел+root-cause в вики (`nl-vds-3xui`, `reality-pq-mldsa65-dest-incompatibility`); креды в `pass nl-vds-3xui/full-env`._
|
||
_Updated: 2026-05-31 — 🟢 `vehicles-loader-progress-deploy` **closed**: live-verify прогона Sun 31.05 06:21→07:53 MSK прошёл — progress-logging показывает движение по всем фазам (▶/батчи/✓/delete-sweep), НЕ тишина; exit 0; `importRun id=4 ok`, `reportJson errors:[]`. **Попутно найден+починен email-баг:** `STOSTAYER_MAIL_TO=site@stostayer.ru` (слалось само себе) → `vitya.kuznetsov@gmail.com` в host env (бэкап `.bak.20260531`); доставка на gmail подтверждена тест-письмом (`250 queued`). Follow-up'ы (не блокеры): units-фаза 1h31m на 100k rows = узкое место; generation `unmatched:51`; репо `config/default.json` дефолт `to` всё ещё site@ (прод перекрыт env)._
|
||
_Updated: 2026-05-30 — 🟡 `vehicles-loader-progress-deploy`: деплой 0.4.0 ВЫПОЛНЕН (build здесь → push `docker.stostayer.ru` → host pull + re-tag `:latest`=0.4.0, 0.3.0 retained). Acceptance #1 ✅. Verify-окно = natural (user-выбор, не форсим baseline → клиент без внепланового email). timer next trigger Sun 2026-05-31 06:21 MSK → live-verify journalctl в след. сессию. Open Q #1 (push кода) снят: `a74ef73` уже в origin/master._
|
||
_Updated: 2026-05-30 — заведена ⚪ `vehicles-loader-progress-deploy` (handoff из stostayer.new). Progress-logging 0.4.0 зашипан в коде (stostayer.new `a74ef73`, 24/24 теста); ops-follow-up — rebuild образа той же схемой (build здесь → push `docker.stostayer.ru` → host pull+re-tag) и снять `journalctl` с боевого прогона (закрывает не-верифицированный 5-й критерий «journalctl показывает движение»). Попутно добивает остаток email-SEND из `vehicles-loader-image-distribution`._
|
||
_Updated: 2026-05-29 — 🟢 `vehicles-loader-image-distribution` **closed**: канал поставки = собственный registry клиента `docker.stostayer.ru`, build у нас (verdaccio-депы запекаются → клиенту verdaccio не нужен), без Portainer (oneshot + systemd-timer). Доки финализированы (stostayer.new `e55cfba`). Открытие `pass stostayer/client` показало свою инфру клиента (Portainer+registry) → развилка A/B пересмотрена. Фактический prod-деплой — follow-up, ждёт grant'а._
|
||
_Updated: 2026-05-29 — **РЕЦИДИВ того же инцидента, диагноз сменён.** Вчерашняя гипотеза «оператор в cutover» **опровергнута**. Истинная причина: ES публиковал `0.0.0.0:9200` мимо traefik (free-ES без auth) → **ransom-бот** удалял индексы by-name (мимо Control #1), оставлял `read_me` с BTC-выкупом. accessLog (Control #2) пуст — бот шёл прямо в порт. **Control #3 (fix):** убрана публикация host-порта (Portainer PUT stack 33) → дыра закрыта; `epz/products/artmone` restore из `daily-2026-05-25`. Второй, отдельный баг: epz-поиск падал у ОБОИХ тенантов — `config.get("tenant")` (node-config) не задан ни в `default.json`, ни в env-маппинге, `TENANT` env был мёртвым грузом → добавил `tenant` в overlay `default.json` (slovo/bookva), резолвится, ошибки прекратились (products работал — другой код-путь). accessLog откатан (сторожил не ту дверь). Exposure-audit → ⚪ `harden-books-vds-exposed-ports`. См. `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md` § «Рецидив 2026-05-29»._
|
||
_Updated: 2026-05-28 (вечер) — 🟢 `restore-elasticsearch-indices-books-vds` **closed** в ту же сессию через snapshot restore (46 сек) из `daily-2026-05-25` (полные данные, ~2ч после оригинального reindex'а). RCA: 5 индексов (включая system `.tasks`) удалены через ES API `DELETE _all` за 1 сек на 2026-05-26 10:21 UTC, **1ч 11мин после создания bookva-es** — оператор в cutover-prep попал на canonical вместо internal-only bookva-es. Caller identity не восстановим (audit log = X-Pack платный, traefik accessLog был выключен, Portainer audit = enterprise). 2 preventive фикса applied + verified: (1) ES env `action.destructive_requires_name=true` — `DELETE _all` / wildcard теперь 400; (2) traefik JSON accessLog в `/letsencrypt/access.log` — будущие DELETE оставят forensic след. См. таску + `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md`._
|
||
_Updated: 2026-05-28 — **prod incident**: books-app slovo поиск товаров + EPZ сломаны, ES `elasticsearch.kzntsv.site` (books VDS stack 33) пуст (0 индексов из 3 ожидаемых; source `elasticold.kzntsv.site` rollback — все 928k docs на месте). Заведена ⚪ `restore-elasticsearch-indices-books-vds`. Окно поломки: 27.05 10:24 → 28.05 13:59, в этом окне шла работа bookva-cutover-prep (bookva-es stack 37 создавался) — возможный конфликт. См. таску для playbook'а._
|
||
_Updated: 2026-05-28 — **vds-kzntsv network-stack mismatch RESOLVED** в 13:52 MSK. Сначала ~2.5ч активного outage (05:45-08:30) + ~5.5ч на эфемерной статике до окончательного fix хостером. Revised RCA: наш netplan+networkd поверх provider's expected ifupdown stack ломал их auto-recovery когда DHCP-binding разорвался на их стороне. Fix: `systemctl mask netplan systemd-networkd` (на running system, без stop — IP и SSH сохранились), Rusonyx ребутнули + положили чистый `/etc/network/interfaces.d/ifcfg-eth0` с /18 netmask через свой `start/ipadd` procedure. Все 24 docker контейнера up. Disk after GC: 79% (132G→118G/158G). Anti-pattern закреплён: НЕ использовать netplan на Rusonyx VDS. Wiki updated с revised RCA + permanent-fix runbook + 2 quirks (#9 /18 layout, #10 ifupdown vs netplan)._
|
||
_Updated: 2026-05-27 — `modulair-rag-vds-redeploy` 🟢 **closed** в ту же сессию: 4-контейнерный стек развёрнут на VDS (Portainer stack 15), acceptance 6/6. Образы пересобраны на самом VDS (push 3.36GB через traefik с дома падал 499); env/entrypoint/minio-host скорректированы под VDS-реальность. 3 follow-up'а переданы в modulair-rag handoff._
|
||
_Updated: 2026-05-27 — заведена `modulair-rag-vds-redeploy` ⚪ (handoff из modulair-rag session). NAS-loss redeploy 4 контейнеров на VDS. Блокер MinIO снят — verified up на VDS 2026-05-27. postgres `proxy`-network reachability подтверждён (`postgres:5432` резолвится из pipeline/mcp). Спека: modulair-rag concept `nas-loss-vds-redeploy-context` + compose.yml as-is._
|
||
_Updated: 2026-05-27 (ночь, после re-open) — ops-mcp multi-tenant **activated** (stack 26 PUT + BOOKVA_MARIADB_PASSWORD env). Smoke verified: slovo/bookva DB queries возвращают tenant-specific data (АФО2 vs Ира warehouses), bookva agendaJobs count=2818. Один host-level books-ops-mcp видит обе tenant DBs._
|
||
|
||
_Updated: 2026-05-27 (ночь) — bookva-tenant deep-debug session. 6 багов одной природы (incomplete cutover-prep): (1) bookva-web без config volume → slovo DB leak; (2) bookva-{api,scheduler,task-runner} mount path /app→/usr/src/app; (3) NITRO/NUXT env не reference'или в compose; (4) bookva-scheduler без RECONCILER_ENABLED → dry-run handlers; (5) bookva-scheduler без healthcheck → CI timeout; (6) deploy.yml svc=job-scheduler vs secret=SCHEDULER mismatch. + composite agendaJobId display fix + books-web config mount (slovo also missed) + jobs.post.js agenda.db.collection→MongoClient. Ingested wiki concept tenant-overlay-config-volume-mount-path-pitfall (shared). ops-mcp multi-tenant код в image готов но stack 26 ещё не пере-PUT'нут (next session)._
|
||
|
||
_Updated: 2026-05-26 (поздний вечер) — `books-ops-mcp-host-promote` 🟢 **closed** в ту же сессию следом за заведением. Stack 26 (books-ops-mcp) in-place PUT в host-level compose без container churn. Stack 43 (bookva-ops-mcp) deleted. 2 Gitea secrets revoked. `victor/books ae3ab14` + `victor/bookva-overlay 50f5bbb` + `.admin` host-stacks/books-vds/ops-mcp.compose.yml. ops-mcp теперь management-plane (manual Portainer)._
|
||
|
||
_Updated: 2026-05-26 (поздний вечер) — заведена `books-ops-mcp-host-promote` ⚪ (deploy run#437/439 валился на bookva-ops-mcp restart-loop → hot-fix `bookva-overlay 437d024` idle-stub interim → user ideology «books VDS = host, slovo/bookva = клиентские стэки» → promote `books-docker-proxy-ro`+`books-ops-mcp` в host-level stack, drop bookva-ops-mcp). Deploy run#440 зелёный._
|
||
|
||
_Updated: 2026-05-26 (поздний вечер) — `bookva-tenant-cutover-prep` 🟢 **closed** — 6/7 в одну сессию: Steps 1+2+3+4+5+7. Step 6 delayed по spec (1-2 нед до cutover). bookva-ozon-mcp deferred (image не в registry). MinIO bucket copy deferred (cutover-time). 9 bookva Portainer stacks created (3 active: db/mongo/es internal-only; 6 stopped pre-cutover). bookva-overlay 4 commits: templates+mariadb:10.6+mongo:4.2+api internal-only+depends_on removed._
|
||
|
||
_Updated: 2026-05-26 (вечер) — `bookva-tenant-cutover-prep` 🟡 paused, 4/7 steps done в этой сессии: Steps 1+5+7 closed, Step 3 partial (6 empty volumes + 4 populated). Maintenance-window remaining: Step 2 stacks, Step 3 stateful (Mongo/MariaDB/MinIO), Step 4 login-gate. bookva-overlay templates align'нуты к prod shape + design v3 (commit `5b173de` pushed). books `75ea320` pushed (deploy/web.compose.yml embed-api refs). Pass entry `gitea/victor-books-ci-bookva-overlay` added._
|
||
|
||
_Updated: 2026-05-26 — заведена `bookva-tenant-cutover-prep` ⚪ (7-step prep на books VDS перед поднятием Bookva стека). Code-side done в victor/books master: embed-api с zod^4 fix, ES endpoint per-tenant, PWA redirect banner, deploy.yml per-tenant, overlay-репо finalize, ntfy per-tenant. Осталась только Portainer/Gitea-secrets/VDS работа._
|
||
|
||
_Updated: 2026-05-25 (`iis-migration-to-ruvds` 🟢 closed Phase 1 per user decision — 9/24 hostnames live на RUVDS, source IIS оставлен running. `migrate-elasticsearch-to-books-vds` 🟢 closed ранее сегодня. 16 IIS hostnames + LE renewal pipeline + decommission — descoped в Closure note, не отдельные tracker tasks.)_
|
||
|
||
## 🟢 [kupimknigi-deploy-snolla-0-42-0] — VDS-staging образ kupimknigi.spb.ru на snolla@0.42.0 (одностраничник, финал тиража). Per-task: `kupimknigi-deploy-snolla-0-42-0.md`.
|
||
|
||
**Status:** done — 2026-07-05. ✅ Собрал `registry.kzntsv.site/kupimknigi:9608ff6` (digest `ac7f846`, 583MB) на VDS из sha 9608ff6 (snolla 0.42.0/core 0.24.0/data 0.14.1), запушил. Создал **новый Portainer-стек Id 21 `kupimknigi`** (POST create/standalone, env verbatim 8/8 из стека 20 — тот же тенант) → контейнер **healthy** сразу, running==9608ff6. Staging-host kupimknigi.vds.kzntsv.site.
|
||
**Where I stopped:** GREEN. Staging-smoke с VDS пройден: `/`→200==prod; H1 «Скупка старых книг в Санкт-Петербурге…» идентичен prod; robots.txt 200 (healthcheck); тема-ассет `/themes/ef2c663c…/css/toolbox.css`→200 (MinIO); форма `action="/callback-order"` в HTML; callback-роуты паритет prod (`/callback-order/`→301 canonical, `/callback-order`→404 POST-only); байты 17717≈prod 17672. Форму не сабмитил (POST=реальное письмо клиенту) — роут зарегистрирован==prod, достаточно.
|
||
**Next action:** — (закрыто, ВКЛ. CUTOVER). ✅ **CUTOVER ВЫПОЛНЕН 2026-07-05:** оператор флипнул DNS reg.ru `kupimknigi.spb.ru` 80.64.31.36→89.253.255.94; подтвердил на ОБОИХ авторит.NS (ns1+ns2.reg.ru); вставил боевой `Host(kupimknigi.spb.ru)` в стек 21 (PUT 200), убрал staging-host. LE-серт выпущен (CN=kupimknigi.spb.ru, valid Jul5→Oct3 2026), боевой хост GREEN: /→200 H1 «Скупка старых книг…», robots/тема-css 200, callback canonical 301. **kupimknigi.spb.ru живёт на VDS.** RUVDS IIS 80.64.31.36 = rollback (revert DNS), НЕ тронут/декоммишн.
|
||
**Weight:** needs-claude · **Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: workshop / 2026-07-05 -->
|
||
|
||
---
|
||
|
||
## 🟡 [morethencms-s3-filestorage-provider] — S3-провайдер FileStorage для MoreThenCms (MinIO), закрывает split-brain админка↔фронт
|
||
**Status:** 🟡 in-progress — ТЗ (name `MoreThenCms.FileStorage.S3` одобрен) доставлено в инбокс MoreThenCms 2026-07-03; жду сборку от прогера. Owner-split: код — прогер; координация+deploy+приёмка — admin.
|
||
**Where I stopped:** Разведка 500 на `/admin/assets/.../delete` → корень RX-only ACL на `App_Data` (пофикшен `icacls …:(M) /T`, 44293 файла). `labtools-price.pdf` заменён в MinIO+локалке (MD5 сверены). ТЗ на провайдер: `~/projects/MoreThenCms/.claude-inbox/2026-07-03T06-40-09Z-admin.md`. Решения: все контентные классы, 1 MinIO (`minio.kzntsv.site`), ключ без ведущего слэша (parity), deploy → локальный IIS windows-recovery-host → боевой. DNS-аудит чист.
|
||
**Next action:** дождаться `MoreThenCms.FileStorage.S3.dll` → deploy на локальный IIS → сквозняк (upload→объект в MinIO→imgproxy 200, byte-parity) → deploy боевой + переключить `<fileStorageClients>`. См. [morethencms-s3-filestorage-provider.md](morethencms-s3-filestorage-provider.md).
|
||
**Branch:** n/a (admin ops + external code)
|
||
<!-- created-by: vitya@.admin-exec / 2026-07-03 / trigger: user-план «сайты→snolla, админка→локальный IIS» -->
|
||
|
||
---
|
||
|
||
## 🟡 [fix-nl-vds-reality-pq-dest] — Reality-инбаунд 443 на NL VDS (213.176.64.253): server-side done, ждёт client-side
|
||
**Status:** 🟡 server-side fix + cred-rotation применены и проверены 2026-06-05; остался один client-side шаг (user меняет SNI в v2rayN) + подтверждение → закрыть.
|
||
**Where I stopped:** ✅ dest `www.intel.com`→`www.microsoft.com` на инбаунде 443 (x-ui.db + restart), сквозной тоннель через реальный :443 (SNI microsoft) = HTTP 204; 32030 жив. ✅ Креды ротированы (root SSH pass / panel user+pass / panel secret-JWT — оригиналы светились в плейнтексте), всё в `pass nl-vds-3xui/full-env`, БД-бэкап на сервере. Root cause: ML-DSA-65 (PQ) × intel/Akamai (HRR), разбор в `.wiki/concepts/reality-pq-mldsa65-dest-incompatibility.md`.
|
||
**Next action:** user в v2rayN профиль `pqmkayaxo2`: SNI → `www.microsoft.com`, reconnect. Подтвердит, что Reality поднялся → 🟢 close. См. [fix-nl-vds-reality-pq-dest.md](fix-nl-vds-reality-pq-dest.md).
|
||
**Branch:** n/a (admin ops)
|
||
<!-- created-by: vitya@.admin-exec / 2026-06-05 / trigger: user-задача "не получается vless+Reality" -->
|
||
|
||
---
|
||
|
||
## ⚪ [kreknin-self-backup] — второй таргет для приёмника бэкапов (на потом)
|
||
**Status:** ⚪ backlog — заведена 2026-06-12 по итогам backup-gap аудита, user: «на потом».
|
||
**Where I stopped:** kreknin (`195.19.90.188`, `/volume1` 7 ТБ) — единственный приёмник всех 4 пайплайнов, сам не бэкапится → SPOF всей estate. Направление: второй облачный таргет (Backblaze B2 / Synology Hyper Backup, client-side encryption) для critical-subset (`*/latest` ~30 ГБ + `.hbk` vault).
|
||
**Next action:** при подъёме — выбрать B2 vs Glacier, настроить DSM Hyper Backup на subset. См. [kreknin-self-backup.md](kreknin-self-backup.md) + `.wiki/concepts/backup-inventory-2026-06.md`.
|
||
**Branch:** n/a (admin ops)
|
||
<!-- created-by: vitya@.admin-exec / 2026-06-12 / trigger: backup-gap аудит, MSSQL incident -->
|
||
|
||
---
|
||
|
||
## 🟢 [vehicles-loader-progress-deploy] — closed 2026-05-31 — 0.4.0 задеплоен + live-verify прошёл. Прогон Sun 31.05 06:21:48→07:53:14 MSK на changed-выгрузке: журнал показал движение по всем фазам (▶ branches/vehicles/units/delete-sweep, батч-прогресс `manufacturers 18/183…`, `units 3/26…`, `✓ done: N rows`, per-model delete-sweep), НЕ тишина. exit 0, `importRun id=4 ok`, `reportJson errors:[]`. **Email-баг найден+починен:** `STOSTAYER_MAIL_TO` слался сам себе (`site@stostayer.ru`) → исправлен на `vitya.kuznetsov@gmail.com` в host env (бэкап `.bak.20260531`), доставка на gmail подтверждена тест-письмом. Все 4 acceptance ✅. См. [vehicles-loader-progress-deploy.md](vehicles-loader-progress-deploy.md).
|
||
**Follow-ups (не блокеры, в stostayer.new):** (1) units-фаза 1h31m на 100529 rows — узкое место, батч-инсёрт/индексы; (2) generation `unmatched:51` — проверить не теряем ли данные; (3) `config/default.json` дефолт `to: site@stostayer.ru` выровнять (прод перекрыт env).
|
||
**Branch:** n/a
|
||
<!-- created-by: vitya@stostayer.new-session / 2026-05-30 / handoff: stostayer.new/.tasks/vehicles-loader-progress-logging.md -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-05-31 / live-verify journalctl прошёл + email-recipient баг починен+verified -->
|
||
|
||
---
|
||
|
||
## 🟢 [restore-elasticsearch-indices-books-vds] — closed 2026-05-28 — snapshot restore из `kreknin:daily-2026-05-25` за 46 сек (epz=820604, products=105922, artmone=2621, counts == source). Forensics: 5 индексов удалены через ES API `DELETE _all` 26.05 10:21 UTC, через 1ч 11мин после создания `bookva-es` — оператор в cutover-prep попал на canonical вместо internal-only bookva. Caller identity unrecoverable (audit log = X-Pack платный, traefik accessLog был выключен). 2 preventive фикса applied: ES env `action.destructive_requires_name=true` + traefik JSON accessLog в `/letsencrypt/access.log`. См. [restore-elasticsearch-indices-books-vds.md](restore-elasticsearch-indices-books-vds.md) § Closure + `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md`.
|
||
**Branch:** n/a
|
||
<!-- created-by: vitya@books-session / 2026-05-28 / trigger: prod 404 index_not_found_exception на /api/epz/search + /api/products/search -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-05-28 / snapshot restore + 2 preventive fixes verified -->
|
||
<!-- REOPENED+RECLOSED: vitya@.admin-exec / 2026-05-29 / рецидив — true RCA = exposed :9200 ransom-бот, Control #3 (порт закрыт) + re-restore + epz tenant-config fix -->
|
||
|
||
---
|
||
|
||
## ⚪ [harden-books-vds-exposed-ports] — закрыть публично торчащие host-порты на books VDS (89.253.255.133)
|
||
**Status:** ready
|
||
**Where I stopped:** exposure-audit сделан (ES :9200 уже закрыт в рамках инцидента); mongo/books-db/bookva-db/minio/bookva-minio + rsync:873 торчат на `0.0.0.0`, все credentialed (auth включён, не дыра-нараспашку как free-ES).
|
||
**Next action:** по каждому сервису определить — приложение коннектится через docker-сеть или host-порт; нужен ли внешний admin (тогда SSH-туннель / DOCKER-USER IP-allowlist вместо публичного порта). Начать с mongo (EOL 4.2 + reuse пароля).
|
||
**Branch:** n/a (admin ops)
|
||
<!-- created-by: vitya@.admin-exec / 2026-05-29 / trigger: exposure-audit во время ES ransom-incident — 5+ stateful-сервисов с публичными host-портами -->
|
||
|
||
---
|
||
|
||
## 🟢 [modulair-rag-vds-redeploy] — closed 2026-05-27 — 4-контейнерный стек развёрнут на VDS, acceptance 6/6
|
||
|
||
NAS помер → 4 контейнера пропали → fresh redeploy на VDS (89.253.255.94). Portainer stack id=15. **Acceptance 6/6 green:** 4 контейнера up без restart-loop; lightrag Uvicorn :9621; `https://modulair-mcp.kzntsv.site`→200; pipeline+tier1 0% CPU; pipeline-лог без postgres/minio ошибок.
|
||
|
||
Таска писалась по stale-предпосылкам — по ходу исправлено (см. [modulair-rag-vds-redeploy.md](modulair-rag-vds-redeploy.md) Decisions): registry-образы отсутствовали (registry переустановлен 2026-05-20) → **rebuild на VDS** (push 3.36GB tier1-слоя с дома падал 499 через traefik → собрал на самом VDS, push локальный); `MINIO_ENDPOINT`=`minio.vds.kzntsv.site` (не `minio.kzntsv.site`=books VDS); traefik entrypoint `https`→`websecure` (NAS-имя в compose-лейбле); DB+bucket+scoped-svcacct созданы; ROUTERAI key в pass; DNS поправил user.
|
||
|
||
**Follow-ups (не блокеры acceptance):** modulair-rag/compose.yml entrypoint-фикс в репо; portainer-stack.md под VDS; lightrag embedding binding=ollama/model=None — проверить ROUTERAI_* маппинг. Все переданы в handoff (consumer repo / parallel session).
|
||
**Branch:** master
|
||
<!-- closed-by: vitya@.admin-exec / 2026-05-27 / Portainer stack 15 deployed, acceptance 6/6 verified -->
|
||
<!-- created-by: vitya@modulair-rag-session / 2026-05-27 / handoff: user gave deploy to .admin (has VDS access) -->
|
||
<!-- created-by: vitya@modulair-rag-session / 2026-05-27 / handoff: user gave deploy to .admin (has VDS access) -->
|
||
|
||
---
|
||
|
||
## 🟢 [migrate-elasticsearch-to-books-vds] — closed 2026-05-25 — ES indices с Windows (elasticold.kzntsv.site, 7.10.1) → books VDS (elasticsearch.kzntsv.site, 7.10.0). 3 indices, 928616 docs. Reindex-from-remote через `reindex.remote.whitelist` env, добавленный в Portainer stack 33. Pre-migration snapshot в kreknin repo как rollback. Consumer configs sed'd, 3 containers restarted, smoke green. Source НЕ выключен.
|
||
|
||
См. [migrate-elasticsearch-to-books-vds.md](migrate-elasticsearch-to-books-vds.md) § Closure note.
|
||
|
||
## 🟢 [books-vds-stacks-to-portainer] — closed 2026-05-25 — 6 SSH-compose стеков на books VDS (89.253.255.133) мигрированы в Portainer-managed (endpoint 1, https://portainer.kzntsv.site). Skip traefik + portainer (management plane). Adapter `scripts/books-vds-portainer-migration/migrate.sh`. Wiki: [portainer-stack-management-books-vds.md](../.wiki/concepts/portainer-stack-management-books-vds.md). Backup pipeline bind paths preserved (verified by inspection, full run pending next 06:00 MSK).
|
||
|
||
## 🟢 [books-vds-backup-daily-kreknin] — closed 2026-05-25 — daily backup books VDS (89.253.255.133, host4g.ru, CentOS 7) → kreknin в 06:00 MSK. DB dumps (mariadb/mongo×2) + ES snapshot via REST → rsync 4.87GB → ntfy/email (phone+inbox ✓). ES path.repo bootstrap + snapshot repo `kreknin` registered. См. [books-vds-backup-daily-kreknin.md](books-vds-backup-daily-kreknin.md).
|
||
|
||
## 🟢 [unify-backup-notifications] — closed 2026-05-25 — единый формат push + email для VDS/RUVDS/windows-host (3 scripts), VDS run.sh импортирован в repo. См. [unify-backup-notifications.md](unify-backup-notifications.md) § Closed. windows-host self-deploy остаётся на user'е (elevated PS).
|
||
|
||
---
|
||
|
||
## 🟢 [bookva-tenant-cutover-prep] — closed 2026-05-26 — 7/7 (Step 6 descoped) + extension (bookva-minio + external port-bind)
|
||
|
||
Все ops-шаги VDS-side выполнены: Gitea secrets ✓ (7 шт), **10/11** Portainer bookva stacks ✓ (ozon-mcp deferred — image отсутствует в registry; bookva-minio добавлен post-closure), volumes ✓ (db+mongo cp -a, es+ntfy empty, 4 config volumes populated, bookva-minio-data cp с books bucket), login-gate SQL ✓ (bookva-db users id≥3 → UUID random pwd), DNS ✓ (pre-existed), books-web embed-api switch ✓, **external access** ✓ (bookva-db:33306 + bookva-minio:9001).
|
||
|
||
**Контекст dev-source:** victor/books `.wiki/concepts/tenant-split.md` rev v3. Code-side: `5e28fd1` embed-api+zod^4, `4a9cafc` ES endpoint, `c1e58cf` PWA redirect, `88df172` deploy.yml per-tenant. Overlay-repos: **bookva-overlay/main `8bffd68`** (templates aligned + mariadb:10.6 + mongo:4.2 + api internal-only + depends_on removed).
|
||
|
||
**Done в сессии 2026-05-26:**
|
||
- ✅ Step 1: 7 Gitea secrets (BOOKVA_OVERLAY_TOKEN, HEALTHCHECK_BOOKVA_{API,WEB}_URL, PORTAINER_STACK_ID_BOOKVA_{API=45,WEB=46,SCHEDULER=47,OPS_MCP=43}). books `2f7b539` (secret name fix).
|
||
- ✅ Step 2: 9/10 Portainer stacks. Stack IDs: db=34, mongo=36, es=37, ops-mcp=43, ntfy=44, api=45, web=46, scheduler=47, task-runner=48. ozon-mcp deferred (image not in registry). User-facing stacks (api/web/scheduler/task-runner/ops-mcp/ntfy) **stopped** post-create (Status=2) — bookseller.kzntsv.site returns 000 как desired pre-cutover state. db/mongo/es оставлены running (внутренние, useful для testing).
|
||
- ✅ Step 3: 6 volumes created. **bookva-db-data (4.7G)** = cp -a `/usr/docker/books-db/data` (downtime 59s). **bookva-mongo-data (517M)** = cp -a `/opt/books/job-scheduler/mongo/db` (downtime 5s). 4 config volumes populated из corrected overlay templates (api, scheduler, task-runner, web-branding). 2 empty by design (ntfy-data, es-data — Phase 2 snapshot/restore). MinIO deferred (no maintenance impact — `mc cp books bookva` at cutover, не rename).
|
||
- ✅ Step 4: bookva-db login-gate `UPDATE users SET password=UUID() WHERE id_user NOT IN (1,2)` — 3 users scrambled (id=3,4,5), id=1 (Bookva founder) + id=2 (Slovo founder) untouched. books-db verified unaffected.
|
||
- ✅ Step 5: DNS `bookseller.kzntsv.site` → 89.253.255.133 (был pre-existing, closed by inspection).
|
||
- ✅ Step 7: Portainer books-web stack 24 PUT atomic (compose+env). Env: `NUXT_PUBLIC_BOOKS_API_URL=/api`, `NUXT_AUTH_TOKEN`, `NUXT_JWT_SECRET_KEY`. Embed-api на bookva.kzntsv.site/api/* живёт (smoke green, auth identical to books-api). books `75ea320` (compose template synced). Soak 24-48ч до books-api shutdown micro-task.
|
||
- 🔵 Step 6: delayed по spec (за 1-2 нед до cutover — `NUXT_PUBLIC_BOOKVA_REDIRECT_URL=https://bookseller.kzntsv.site` env на books-web для PWA redirect banner user.id=1).
|
||
|
||
**Deferred (отдельной micro-task):**
|
||
- `bookva-ozon-mcp` stack — image `registry.kzntsv.site/books-ozon-mcp:master` не существует в registry. Нужен build в victor/books CI (ozon-mcp service в build.yml?). Скорее всего service ещё не настроен — отдельная task'а.
|
||
- Mongo agenda cleanup partial — 7 jobs с idSeller=2 удалены, осталось 2817 jobs (большинство — system-wide без idSeller). При cutover проверить если нужен дальнейший cleanup.
|
||
|
||
**Branch:** master | **Closure pushes:** books `2f7b539`+`75ea320` (master); bookva-overlay `5b173de`→`8bffd68` (main): templates align + mariadb:10.6 + mongo:4.2 + api internal-only + depends_on removed. Pass: `gitea/victor-books-ci-bookva-overlay`.
|
||
<!-- created-by: vitya / 2026-05-26 / Phase 2 cutover prep после tenant-split code-side done -->
|
||
<!-- progress-2026-05-26: 6/7 closed одну сессию: Steps 1+2+3+4+5+7. Step 6 delayed by design. bookva-ozon-mcp deferred (image build needed). MinIO bucket copy deferred (cutover-time) -->
|
||
|
||
---
|
||
|
||
## 🟢 [books-ops-mcp-host-promote] — closed 2026-05-26 — host-promote ops-mcp+docker-proxy в `.admin/host-stacks/books-vds/`, drop bookva-ops-mcp
|
||
|
||
Stack 26 (books-ops-mcp) in-place PUT без container churn (env MARIADB_PASSWORD preserved). Stack 43 (bookva-ops-mcp) deleted. Gitea secrets `PORTAINER_STACK_ID_BOOKVA_OPS_MCP` + `PORTAINER_STACK_ID_OPS_MCP` revoked. ops-mcp убран из tenant=slovo|bookva pipeline (deploy.yml). Management-plane: image updates через Portainer UI manual.
|
||
|
||
**Design locks (см. [books-ops-mcp-host-promote.md](books-ops-mcp-host-promote.md) § Closure note):** Q1 location=`.admin/host-stacks/`, Q2 deploy=manual Portainer, Q3 MariaDB scope=slovo's only (Option A), Q4 config volume path unchanged, Q5 audit прочих host-level кандидатов punt.
|
||
|
||
**Commits:** `victor/books ae3ab14` + `victor/bookva-overlay 50f5bbb` + `.admin` (this commit).
|
||
**Branch:** master
|
||
<!-- created-by: vitya / 2026-05-26 / trigger: deploy run#437/439 fail + user ideology clarification -->
|
||
<!-- closed-by: vitya / 2026-05-26 / Portainer in-place PUT stack 26 + delete stack 43 + 2 secrets revoked + 3 commits -->
|
||
|
||
---
|
||
|
||
## ⚪ [stateful-split-volume-copy] — поднять `bookva-db` + `slovo-db` на VDS как копии `books-db` через `cp -a` volume
|
||
|
||
Ops-таска для Фазы 1 дизайна `tenant-split` из victor/books. Scope сужен 2026-05-25 (user: «просто поднимем 2 БД»): только MariaDB volume copy + up 2 контейнеров. Mongo / MinIO / DELETE / app-стеки — отдельными ops-тасками потом.
|
||
|
||
**Контекст dev-source:** дизайн в victor/books `.wiki/concepts/tenant-split.md` § «Фаза 1». Compose-файлы готовы в `bookva-overlay` / `slovo-overlay` (commit d0eb210 в books).
|
||
|
||
**Acceptance:** `docker ps` показывает живые `bookva-db` + `slovo-db`, оба отвечают `SELECT 1`. Текущий `books-db` стек снова в строю после maintenance window.
|
||
|
||
**Status:** ready
|
||
**Where I stopped:** (not started — lean playbook готов в [stateful-split-volume-copy.md](stateful-split-volume-copy.md))
|
||
**Next action:** под maintenance window 5-10 мин на VDS — execute 5 команд из task-файла. Backup → stop books-db → cp -a в 2 volume'а → up 2 новых контейнера через Portainer → start books-db.
|
||
**Blocker:** —
|
||
**Branch:** master
|
||
<!-- created-by: vitya / 2026-05-25 / moved from victor/books .tasks/ (scope is ops/VDS, not books-code) -->
|
||
|
||
---
|
||
|
||
<!--
|
||
Status legend:
|
||
🔴 Active — only one at a time
|
||
🟡 Paused — in progress, resumable
|
||
⚪ Ready — defined, not started
|
||
🟢 Done — moved to ARCHIVE.md once committed
|
||
🔵 Blocked — waiting on external input
|
||
-->
|
||
## 🟢 [iis-migration-to-ruvds] — closed 2026-05-25 per user decision — Phase 1 done: RUVDS infra setup + 8.66GB scp + IIS recreate + 25 HTTPS SNI bindings (LE R13 expire 2026-07-22) + 9 hostnames (kupimknigi.spb.ru, emspb±www, pilorama98±www, labtools.pro±www, rimiz±www) live на 80.64.31.36 with correct per-tenant content. 16 hostnames остаются на windows source per user pace (incl. tandemmebel scope-exception). Source IIS:8089 + traefik routes ALIVE для rollback. Decommission + LE renewal + cleanup descoped в Closure note. См. [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md) § Closure note.
|
||
|
||
<!-- HISTORICAL details preserved below — task closed, content kept до prune'а board'а -->
|
||
|
||
**Status:** closed 2026-05-25
|
||
**Where I stopped:** 2026-05-24 — `kupimknigi.spb.ru` + `emspb.ru` (+ `www.emspb.ru`) DNS A flipped на `80.64.31.36`, authoritative `ns1.reg.ru` правильный, public resolver caches expire'ятся (8.8.8.8=~6h, 1.1.1.1=~24h max). RUVDS state: snolla site (8.66 GB / 44725 files) transferred + IIS recreated + 25 HTTPS SNI bindings c LE certs (R13, valid до 2026-07-22), 7 prod hostnames live-smoke через VDS (третья сеть) → 200 OK / correct content. Source IIS:8089 + traefik routes ALIVE — rollback ready. **2026-05-25 close-time DNS probe:** ещё 5 пар hostnames swap'нуты user'ом silently — итого 9 на RUVDS, 16 на source.
|
||
**Findings зафиксированы в** [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md) Decisions log: (1) outbound 445 блокирует home ISP, не RUVDS-FW → SSH/scp = canonical transfer-метод; (2) home network HTTP-middlebox mangles Host header for direct external HTTP — real end-users не пострадают, тестировать через VDS; (3) traefik acme.json → IIS PFX recipe работает (extract + openssl pkcs12 -export + Import-PfxCertificate + AddSslCertificate by thumbprint); (4) IIS 10 HTTP/2 default; (5) `maljarka.tandemmebel.ru` + 3 rimiz hostnames → 502/404 — pre-existing CMS-tenant config gap, не migration defect.
|
||
|
||
**Scope exception (2026-05-24 user decision):** `tandemmebel.ru` + `www.tandemmebel.ru` ОСТАЮТСЯ на windows-IIS на неопределённый срок (отдельное решение user'а — site не готов к cutover ровно сейчас). DNS НЕ свапать. Cert на RUVDS уже импортирован, binding existing — может оставаться idle, traffic не пойдёт.
|
||
|
||
**Next action:**
|
||
1. **Снизить DNS TTL** в reg.ru на оставшиеся **22 hostnames** до 300s — `pilorama98.ru`, `labtools.{ru,pro}`, `snolla.com` + 11 snolla subdomains + `rimiz.{ru,snolla.com}` + `maljarka.tandemmebel.ru` (но не `tandemmebel.ru` — см. Scope exception). Сократит cache-tail с 24h до 5 мин.
|
||
2. **24h soak kupimknigi + emspb** — verify через cache-clean resolver (потом curl без `--resolve`).
|
||
3. **Bulk DNS swap** оставшихся 22 hostnames на 80.64.31.36 (single sitting). **tandemmebel.ru / www.tandemmebel.ru — пропустить.**
|
||
4. **1-week prod soak** с RUVDS как live source для migrated hostnames.
|
||
5. **Decommission source IIS:8089** только для migrated hostnames (snolla catch-all site нельзя decommission'ить пока tandemmebel.ru на нём же). Решение defer до tandemmebel migration.
|
||
6. **LE renewal pipeline** — win-acme + HTTP-01 на RUVDS после full cutover (LE certs expire 2026-07-22, soak window до ~07-15).
|
||
7. **Cleanup migration-temp:** `Remove-NetFirewallRule 'smb-from-source','ssh-from-source'` на RUVDS, удалить `~/.ssh/ruvds-iis-migration*` на source, очистить `C:\ProgramData\ssh\administrators_authorized_keys` на RUVDS.
|
||
|
||
Полный план + Completed + Open questions + Remaining steps — в [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md).
|
||
**Branch:** master
|
||
<!-- created-by: user-decision / 2026-05-22 / trigger: RUVDS purchased -->
|
||
<!-- partial-cutover-by: vitya / 2026-05-24 / kupimknigi + emspb DNS flipped, 22 hostnames pending, tandemmebel.ru excluded -->
|
||
<!-- scope-narrowed: vitya / 2026-05-24 / tandemmebel.ru stays on windows-IIS indefinitely -->
|
||
**Branch:** master
|
||
|
||
---
|
||
|
||
## ⚪ [infra-inventory] — two-tier инвентаризация: public-карта в global wiki + admin-detailed runbook локально
|
||
|
||
**Status:** ready
|
||
**Where I stopped:** (not started — создана 2026-05-22 из инцидента `gitea.` vs `git.` + перепутанный source/dest IP в traefik access-логах; scope расширена в тот же день — single-page → two-tier per user «у все общее представление, у админа полное»)
|
||
**Next action:** см. полный план в `infra-inventory.md`. Шаги: (1) probing-фаза одна для обоих — `nslookup` известных subdomain'ов + `vds-ops`/`synology-ops` `ops_docker_ps`/`inspect` + `knowledge_search` по существующим bootstrap-concept'ам + `rg "kzntsv.site"` по `~/projects/`. (2) Write A — light public в `projects-wiki/concepts/infrastructure-inventory.md` (машины, canonical hostnames, anti-aliases) → `knowledge_ingest`. (3) Write B — heavy admin в `.admin/.wiki/concepts/infrastructure-stack.md` (per-stack: версии, internal IPs, volumes, depends-on, backup paths, DR pointers) → commit + push. (4) Cross-refs A↔B + pointer в `.admin/.wiki/CLAUDE.md` Domain conventions.
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: gitea-hostname-confusion-incident -->
|
||
<!-- scope-expanded: 2026-05-22 — single-page → two-tier (public + admin-detailed) -->
|
||
|
||
---
|
||
|
||
## 🔵 [books-vds-bookva-bootstrap] — Ops-таска для Фазы 4 дизайна `tenant-split` из victor/books (см. `mcp__projects-meta__knowledge_get slug=concepts/tenant-split`). Поднять новый VDS для Bookva — billing на юрлицо Bookva (учредители развелись, каждое юрлицо платит инфру напрямую провайдеру).
|
||
|
||
**Контекст dev-source:** дизайн в victor/books `.wiki/concepts/tenant-split.md`; brainstorm trace `~/projects/.workshop/.archive/2026-05-24-books-tenant-split.md`.
|
||
|
||
**Direction:** Slovo остаётся на текущем VDS, Bookva переезжает на новый.
|
||
|
||
**Acceptance:**
|
||
- Новый VDS у провайдера (Rusonyx или другой по предпочтению Bookva), billing-account оформлен на юрлицо Bookva.
|
||
- Docker + traefik + certbot развёрнуты.
|
||
- DNS `*.bookva.<tld>` готов резолвиться на новый IP (фактический switch — в `books-dns-cutover-bookva`).
|
||
- SSH-ключи: только инженерные (core-разраб + админ Bookva, если есть). Аналитики Bookva — НЕ имеют SSH к новому VDS.
|
||
|
||
**Status:** blocked
|
||
**Where I stopped:** (not started)
|
||
**Next action:** **Pre-step:** прочитать `concepts/tenant-split.md` § «Фаза 4» в victor/books (через `mcp__projects-meta__knowledge_get slug=concepts/tenant-split` или git clone victor/books → `.wiki/concepts/tenant-split.md`).
|
||
|
||
1. Согласовать с учредителем Bookva: provider, configuration (CPU/RAM/disk), регистрация billing'а на юрлицо Bookva.
|
||
2. Provision VDS, базовая настройка (firewall, fail2ban, SSH ключи).
|
||
3. Установить Docker + docker-compose.
|
||
4. Развернуть traefik + certbot из overlay-репо `victor/books-bookva/deploy/traefik.compose.yml`.
|
||
5. Подготовить DNS-зону `bookva.<tld>` (создать A-records, TTL=300 для возможности быстрого switch'а).
|
||
6. Smoke: `curl https://placeholder.bookva.<tld>` → 200 от nginx-placeholder или из traefik.
|
||
7. Документировать в `victor/books-bookva/README.md`: hostname, IP, как ssh, runbook smoke-теста.
|
||
8. Закрыть с note: «VDS поднят, IP=<...>, готов к stack deploy».
|
||
**Blocker:** `OpeItcLoc03/admin books-stateful-split-execution` (Фаза 1 — physical split на shared VDS, bookva-* контейнеры живут) + `victor/books per-tenant-backup-and-observability` (Фаза 2 — per-tenant backups настроены до физического переезда)
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-24T12:02:19.507Z -->
|
||
|
||
---
|
||
|
||
## 🔵 [books-dns-cutover-bookva] — Ops-таска для Фазы 4, шаг 5 дизайна `tenant-split` из victor/books. DNS switch `*.bookva.<tld>` → новый VDS IP, с parallel-run старого bookva-стека на shared VDS 7 дней без публичного hostname.
|
||
|
||
**Контекст dev-source:** дизайн в victor/books `.wiki/concepts/tenant-split.md` § «Фаза 4».
|
||
|
||
**Acceptance:**
|
||
- DNS `*.bookva.<tld>` (или конкретный hostname Bookva — определяется учредителем) указывает на новый VDS IP.
|
||
- Старый bookva-стек на shared VDS остаётся запущенным, **без публичного hostname** (traefik labels убраны или disabled), доступен только по docker-сети для возможного отката.
|
||
- Smoke с двух разных сетей (mobile + офисный wifi) — `https://<bookva-hostname>` отвечает с нового VDS.
|
||
- Per-VDS backup'ы на новом VDS подтверждены — есть snapshot спустя 24ч после DNS-switch'а.
|
||
- Через 7 дней parallel-run без откатов — decommission старого bookva-стека (см. Фаза 4, шаг 7 в spec).
|
||
|
||
**Status:** blocked
|
||
**Where I stopped:** (not started)
|
||
**Next action:** **Pre-step:** прочитать `concepts/tenant-split.md` § «Фаза 4, шаги 4–7» в victor/books.
|
||
|
||
1. Maintenance window 1–2ч (объявить за 7 дней).
|
||
2. Финальная синхронизация: `mysqldump mariadb-bookva` на текущем VDS → `mysql` на новом VDS (если schema/data разошлись с момента Фазы 2).
|
||
3. Smoke на новом VDS до DNS-switch'а: hosts-файл override → проверить login + base flow.
|
||
4. DNS-switch: меняем A-record(s) для bookva-hostname на new IP. TTL=300 уже стоял (Phase 4 step 1).
|
||
5. Через 1ч (TTL expiry + propagation): smoke с двух сетей.
|
||
6. На старом VDS — отключить traefik labels для bookva-стека (контейнеры продолжают работать, но не доступны снаружи).
|
||
7. **Parallel-run 7 дней.** Каждый день — smoke. Per-VDS backup на новом VDS — проверить хотя бы 1 успешный snapshot.
|
||
8. Если за 7 дней проблем нет → закрыть с note «cutover stable». Если есть — DNS rollback на старый IP, follow-up debug task в victor/books.
|
||
9. **Decommission** старого bookva-стека на shared VDS (отдельный шаг, не в этой таске — финальный backup в архив + `docker compose down` + volume drop).
|
||
**Blocker:** `books-vds-bookva-bootstrap` + victor/books `pwa-redirect-handoff` (SW bump выкачен в shared API за 1–2 недели до этой таски).
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-24T12:02:41.503Z -->
|
||
|
||
---
|
||
|
||
## ⚪ [books-bookva-user-whitelist-gathering] — **Процессная** ops-таска для Фазы 3, шаг 1 дизайна `tenant-split` из victor/books. Получить от учредителя Bookva письменный список пользователей, которые допущены к Bookva-инсталляции. Без этого списка cutover не возможен — иначе утечка доступа: либо лишние юзеры получат доступ к Bookva (через копирование «всех users»), либо легитимные юзеры заблокированы (через пустой whitelist).
|
||
|
||
**Direction:** Bookva — новая инсталляция, начинается с whitelist'а (закрытый список). Slovo — остаётся на текущем стеке, получает копию всех текущих users (статус-кво).
|
||
|
||
**Контекст dev-source:** дизайн в victor/books `.wiki/concepts/tenant-split.md` § «Двухуровневая изоляция пользователей → App-level» и § «Фаза 3, шаг 1».
|
||
|
||
**Не блокируется ничем** — можно начинать gathering сразу, лаг до cutover'а может быть значительным.
|
||
|
||
**Acceptance:**
|
||
- Письменный список от учредителя Bookva (email или подписанный документ): ФИО + role (если знают).
|
||
- Список зафиксирован в `OpeItcLoc03/admin/.wiki/concepts/books-bookva-whitelist.md` или в отдельной таблице.
|
||
- Для каждой строки списка сверка с текущей `users`-таблицей shared БД: user существует / роль соответствует / email актуален.
|
||
- Любые расхождения (запрошенный user не существует в shared / роль другая) — обработаны: создать новый user в Bookva-БД post-cutover ИЛИ скорректировать список с учредителем.
|
||
|
||
**Status:** ready
|
||
**Where I stopped:** (not started)
|
||
**Next action:** 1. Отправить учредителю Bookva формальный запрос (email/мессенджер): «Для tenant-split нужен список пользователей, которые останутся в Bookva после разделения. Формат: ФИО + роль + email/login. Список будет применён на cutover'е как whitelist».
|
||
2. Дождаться ответа (это может занять дни/недели — нормально).
|
||
3. Получив список — сверить с `users`-таблицей shared БД:
|
||
```sql
|
||
SELECT id, login, email, role FROM users WHERE email IN (<list>) AND deleted_at IS NULL;
|
||
```
|
||
4. Разобрать расхождения с учредителем (missing / role mismatch / inactive).
|
||
5. Зафиксировать финальный список в `OpeItcLoc03/admin/.wiki/concepts/books-bookva-whitelist.md` (или в local-only encrypted file если NDA-чувствительно).
|
||
6. Финальный список становится input'ом для Фазы 3, шаг 1: при настройке Bookva-БД в неё попадают **только** эти users.
|
||
7. Закрыть с note: «whitelist (N users) подтверждён учредителем, готов к применению на cutover'е».
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-24T12:03:18.548Z -->
|
||
|
||
## 🔵 [books-stateful-split-execution] — Ops-таска для **Фазы 1** дизайна `tenant-split` из victor/books. Выполнить playbook на shared VDS в maintenance window — docker volume copy для MariaDB + Mongo + MinIO bucket rename + DELETE cleanup. Identical bit-for-bit копии через `cp -a` alpine helper. Никаких mysqldump/mongodump/mc cp.
|
||
|
||
**Playbook (dev-source, читать целиком до начала):** `victor/books/.tasks/stateful-split-volume-copy.md`.
|
||
|
||
**Контекст дизайна:** `mcp__projects-meta__knowledge_get slug=concepts/tenant-split` § «Фаза 1 — Physical stateful split + overlay-repos». Brainstorm trace: `~/projects/.workshop/.archive/2026-05-24-books-tenant-split.md`.
|
||
|
||
**Container naming:** drop `books-` prefix → `bookva-<svc>` / `slovo-<svc>`.
|
||
|
||
**TDD-режим:** `[skip-tdd: one-off migration on maintenance window]` — verify через post-cleanup audit.
|
||
|
||
**Acceptance:**
|
||
- `bookva-db` + `slovo-db` контейнеры живые, audit `SELECT DISTINCT idSeller` = `{1}` / `{2}` соответственно.
|
||
- `bookva-mongo` + `slovo-mongo` контейнеры живые, agenda-история preserved, jobs чужого tenant'а cleanup'нуты.
|
||
- MinIO (один shared) имеет bucket'ы `bookva` + `slovo` вместо `books`; объекты чужого tenant'а удалены.
|
||
- Shared catalog tables (`books`, `editions`, `epz`, `epz_pages`, `warehouses`, `products`) — полные копии в обеих БД.
|
||
- Smoke с двух сетей: login через `bookva.<tld>` → Bookva-данные; текущий URL → Slovo.
|
||
- Через 7-14 дней parallel-run без откатов → cleanup старых `books-*` контейнеров и volumes (отдельный заход).
|
||
|
||
**Status:** blocked
|
||
**Where I stopped:** (not started)
|
||
**Next action:** **Pre-step:** прочитать `victor/books/.tasks/stateful-split-volume-copy.md` целиком. Убедиться что 7 open questions resolved (закрыто dev-prep таской `victor/books stateful-split-volume-copy`): FK CASCADE, units/receipts strategy, MinIO key structure, users whitelist, task_runs handling, chrome/traefik shared.
|
||
|
||
1. Подтвердить overlay-репо `victor/bookva-overlay` + `victor/slovo-overlay` готовы — compose-файлы для `bookva-*` / `slovo-*` стеков на месте.
|
||
2. Подтвердить per-tenant backups настроены (`victor/books per-tenant-backup-and-observability`) — иначе нет точки rollback.
|
||
3. Объявить maintenance window 1-2ч (без записи).
|
||
4. Execute Steps 0-11 из playbook на shared VDS.
|
||
5. Post-execution audit: `SELECT DISTINCT idSeller FROM <each>`, `db.agendaJobs.distinct('data.idSeller')`, `mc ls` on MinIO.
|
||
6. Smoke с двух сетей.
|
||
7. Через 7-14 дней parallel-run без откатов → cleanup старых `books-*` контейнеров+volumes (отдельный заход).
|
||
8. Closing note: «Physical split done, bookva-* + slovo-* контейнеры живут на shared VDS, audit clean, parallel-run start <date>».
|
||
**Blocker:** `victor/books stateful-split-volume-copy` (dev-prep — 7 open questions resolved + dev-DB test passed) + `victor/books per-tenant-backup-and-observability` (Фаза 2 — per-tenant backups настроены)
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-25T04:46:48.271Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [vehicles-loader-image-distribution] — closed 2026-05-29 — канал поставки решён: build здесь → push в **собственный registry клиента** `docker.stostayer.ru` → host pull + re-tag `:latest`. **Без Portainer** (oneshot не stack + Portainer cron не умеет; systemd-timer + `docker run --rm` сохраняет `OnFailure=`). Build здесь = депы (вкл. приватный verdaccio) запекаются в образ, клиенту verdaccio/yarn/node не нужны. Финализированы `deploy/README.md` § Image distribution + дизайн-док (вопрос #4 закрыт). **Commit:** stostayer.new `e55cfba`. **Prod-деплой ВЫПОЛНЕН 2026-05-29** (stostayer.new `ddda63f`): образ `docker.stostayer.ru/vehicles-loader:0.3.0` собран здесь+запушен, env `/etc/stostayer/vehicles-loader.env` (600 root), systemd-timer enabled (next 2026-05-30 06:20 MSK). **Гочи по ходу (обе в `stostayer.new/.wiki/concepts/client-infra-access.md`):** (1) MariaDB — под `--network host` наследуется хостовый `/etc/hosts` (`www→127.0.0.1 И ::1`), Node резолвит `::1` первым, mariadb слушает только IPv4 → timeout. Фикс: `STOSTAYER_DB_HOST=127.0.0.1`. (2) Контейнерный DNS сломан (Docker переписывает resolv.conf на публичные, исходящий :53 зафайрволен; `--add-host` под host-net игнорится) → email-отчёт на `mail.stostayer.ru` не резолвился. Фикс: `-v /etc/stostayer/container-hosts:/etc/hosts:ro` (свой hosts с пином mail-IP).
|
||
|
||
**Первый боевой прогон 2026-05-30 06:20:** sync УСПЕШЕН (`importRun id=3 ok`, ~84мин, units.json изменился относительно baseline), но упал на email (DNS, см. гочу #2) → systemd `failed` + ложный OnFailure. **Данные залились корректно.** Email-фикс применён и проверен `nodemailer.verify()` → `SMTP_VERIFY_OK` (DNS+TCP+STARTTLS+AUTH). Юнит redeployed, failed сброшен, next run Sun 06:21. Доки в stostayer.new `8e5997c`. **Заведена ⚪ `vehicles-loader-progress-logging`** (stostayer.new) — лог тонкий, ~2ч sync молчал. **Остаточное:** живой email-SEND в составе sync проверится на следующем changed-прогоне (verify доказал транспорт); OnFailure-юнит шлёт через хостовый `mail` — не проверен.
|
||
|
||
<details><summary>исходный scope (decision-task)</summary>
|
||
|
||
vehicles-loader (stostayer) деплоится в Docker у клиента (lite docker-deploy, дизайн залочен 29.05, см. stostayer.new `.wiki/concepts/vehicles-loader-docker-deploy.md`). Нужно решение по каналу поставки образа клиенту — единственный открытый вопрос #4, блокирующий финализацию Dockerfile CMD + deploy/README.
|
||
|
||
Развилка:
|
||
- **A (рекомендация brainstorm'а):** мы билдим образ → push в docker registry VDS → клиент `docker pull` по pinned-тегу. Rollback = перетыкнуть тег. Клиенту не нужен monorepo + yarn berry build-toolchain. Требует: у клиента docker login creds + сетевой доступ к нашему registry.
|
||
- **B (fallback):** клиент билдит из git у себя. Требует VCS-доступ + node22 + yarn berry **+ доступ к verdaccio** (`verdaccio.kzntsv.site`) с валидным токеном на хосте клиента. Причина: `.yarn/cache` в `.gitignore` (не коммитится), build идёт offline против cache → свежий `git clone` имеет пустой cache → `yarn workspaces focus` падает, пока клиент не прогонит `yarn install` против приватной регистры. Тяжелее, медленнее rollback, +зависимость от приватной регистры.
|
||
|
||
Образ: multi-stage node:22-alpine, `yarn workspaces focus @stostayer/vehicles-loader --production` (плагин `@yarnpkg/plugin-workspace-tools` закоммичен в stostayer.new `.yarn/plugins/`).
|
||
|
||
> **UPDATE 2026-05-29 (из stostayer.new session):** lite docker-deploy CORE зашипан (10/10 tasks на master). **Dockerfile CMD уже финализирован** (`ENTRYPOINT ["node","index.js"]` + `CMD ["sync"]`, source через env `STOSTAYER_SOURCE_ZIP` дефолт `/var/from_1c/export_from_1c.zip`) — эта часть блокера СНЯТА. Остаётся ТОЛЬКО: (1) решение по каналу поставки образа; (2) distribution-секция `deploy/README.md` (помечена PENDING на эту таску). **A build-proven:** образ собран локально (`docker build` ок) и запускается (`docker run --rm vehicles-loader:dev --help` ок). С учётом verdaccio-зависимости B (выше) — **A ещё предпочтительнее**: при A клиенту verdaccio не нужен вообще (cache наполняется нашим authed `yarn install` при сборке).
|
||
|
||
**Status:** closed 2026-05-29
|
||
**Resolution:** канал = собственный registry клиента `docker.stostayer.ru` (не наш VDS-registry, не build-from-git). Развилка пересмотрена при открытии `pass stostayer/client` — у клиента своя инфра (Portainer + registry). Build остаётся у нас (verdaccio-депы запекаются). Без Portainer для запуска (oneshot). Доки финализированы в stostayer.new `e55cfba`.
|
||
**Branch:** n/a
|
||
</details>
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/stostayer.new / 2026-05-29T10:17:29.967Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-05-29 / decision: client-registry + build-here + no-Portainer; deploy/README + design-doc finalized (stostayer.new e55cfba) -->
|
||
|
||
---
|
||
|
||
## 🟡 [agents-task-runner-vds-deploy] — Deploy agents-task-runner (the poller) on WORKER machine(s) — per-machine federation (agent-orchestration-without-user Q9). Each worker runs its own runner, claims from the SHARED Gitea board, runs the agent LOCALLY, commits/pushes. VDS hosts the Gitea board ONLY (already up) — it is NOT a worker, nothing deploys on VDS. [Slug legacy-named '-vds-deploy'; scope corrected 2026-06-08, VDS-as-worker DROPPED.] Runtime code deploy-ready (poller registered in tasks.json, DRY_RUN + requirements-filter + confirm-gate built; compose+Dockerfile). Credentialed per-machine setup (ops).
|
||
|
||
**Status:** paused
|
||
**Where I stopped:** 2026-06-08: DRY_RUN провалидирован; `.admin` claim-guard (`policy.toml` default_weight=needs-human) запушен+верифицирован (все `.admin`-таски → needs-human skip). Always-on bring-up начат (docker mongo+reconciler) затем **снесён по запрету user'а «не запускай поллеров без разрешения»** — host task-runner НЕ стартовал, ни одного claim/spawn. **GATE: запуск поллеров ждёт явного гранта user'а.** Попутно поймана+закрыта stale-claimable `cms-maljarka-https-mode-bug-fix` на MoreThenCms board (баг починен сегодня, таска висела открытой — без паузы always-on заспавнил бы агента на уже-решённое). Конфиг (.env/secrets.env/policy guard) готов. См. [agents-task-runner-vds-deploy.md](agents-task-runner-vds-deploy.md).
|
||
**Next action:** (0) BUILD PREREQ (from consult-execution-policy-governance review-outcome, OpeItcLoc03/common): repo tracks TS source only — `dist/` is gitignored. After pulling OpeItcLoc03/common on each worker, run `npm run build` (tsc) in BOTH lib/projects-meta-mcp AND lib/agents-task-runner, then (re)start the runner; otherwise the execution-policy governance lever (consult_policy + needs-human claim-gate, runner v0.16.x) is INERT — the old compiled JS keeps running. (1) Pick first worker machine (workstation / factory Linux). (2) Install + run agents-task-runner there, pointed at the shared Gitea board. (3) Secrets PER MACHINE: ANTHROPIC_API_KEY (headless) or `claude login` + Gitea token + projects-meta config; 600 root, not in git. (4) Capability filter: claims only what that machine can do (requirements/runtime_allowed); cheap model (sonnet/haiku) for impl polling. (5) HARD: .admin excluded from autonomous claim until governance L2/L3 lands (see blocker). (6) DRY_RUN on prod board first → then one live cycle on a real non-.admin task → spawn→commit→push, verified. (7) anti-self-review: distinct machine-id per worker.
|
||
**Branch:** n/a
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-06-08T10:10:16.420Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [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.
|
||
|
||
## Диагноз (доказан, не гипотеза)
|
||
|
||
Корень: **галерейные оригиналы лежат ТОЛЬКО на локальном диске legacy-IIS** (`App_Data\galleries\<siteId:N>\<storageFilename-guid>.<ext>`). В S3/MinIO их нет. Продукты — мигрированы (`s3://pilorama98/products/...` отдаёт 200), галереи — нет.
|
||
|
||
Три независимых следствия одного корня:
|
||
|
||
1. **Prod не отдаёт картинки блога.** Legacy-роут `/galleries/<galleryId:N>/images/<size>/<seq>.jpg` — ресайзер на лету. Распиновка живого pilorama98 (2 галереи, 98078b74… и c1b53362…):
|
||
- `original/001.jpg` → **200** (235KB / 735KB) — оригинал жив, читается с локального диска;
|
||
- `800x450`, `default`, `thumbnail`, `100x100`, `600x400` → **404** всегда.
|
||
→ ресайз-пайплайн legacy-хоста мёртв глобально (System.Drawing / image-cache storage). Это отдельная legacy-поломка; целевая архитектура — отдавать через imgproxy, не чинить legacy-ресайз.
|
||
|
||
2. **Превью в админке видно.** Админка рендерит ОРИГИНАЛ (passthrough по imageId → `GetGalleryImageById`, без ресайза), читает локальный диск. Тот же путь, что `original/001.jpg`=200. Сломанную ресайз-ветку не трогает.
|
||
|
||
3. **Dev imgproxy 404.** snolla строит source `s3://${storageClient}/${siteId}/${storageFilename}` (packages/snolla/middleware/galleries.js:47 и lib/viewModels/index.js:289). Ключ-форма КОРРЕКТНА (реальный StorageFilename guid + siteId, ровно legacy-раскладка). Но imgproxy читает S3, а объекта там нет → "Source unreachable". Код билдера менять НЕ надо.
|
||
|
||
## Развилка, которую закрыть на боевом (нужен доступ к MinIO)
|
||
|
||
Имя бакета. Продукты: бакет `pilorama98`, prefix `products/`. snolla для галерей берёт `image.storageClient` («galleries») как ИМЯ БАКЕТА → `s3://galleries/...`. Проверить в MinIO:
|
||
- (A) целевой бакет реально `galleries` (отдельный) → достаточно залить файлы, код snolla без изменений;
|
||
- (B) всё в бакете `pilorama98`, галереи должны быть под prefix `galleries/` → тогда + маленький код-фикс на стороне snolla (storageClient как prefix, а не bucket) — в этом случае ЗАВЕСТИ follow-up таску обратно на victor/snolla через notify, НЕ чинить snolla из admin.
|
||
|
||
## Acceptance criteria
|
||
|
||
1. Определено фактическое состояние MinIO: какие бакеты есть, как лежат рабочие продукты, отсутствуют ли галерейные объекты — зафиксировано в close-note.
|
||
2. Развилка A/B закрыта явно (какой бакет/prefix — целевой).
|
||
3. Если A: legacy-оригиналы залиты в S3 под ключи, которые snolla уже ждёт (`<siteId>/<storageFilename>`); проверена доступность через imgproxy (хотя бы одна картинка → 200).
|
||
4. Если B: создана follow-up таска на victor/snolla с точной целевой формой ключа (bucket + prefix), миграция файлов под этот prefix выполнена.
|
||
5. В inbox victor/snolla при close: итоговая форма source (bucket/prefix/key), какие галереи/сколько файлов мигрировано, и нужен ли код-фикс на snolla.
|
||
|
||
## Контекст-источники (для воспроизведения)
|
||
- Живой prod: `https://www.pilorama98.ru/galleries/98078b74e062443ba0708476e031b6b0/images/original/001.jpg` (200) vs `/800x450/001.jpg` (404).
|
||
- Legacy-код: MoreThenCms/Galleries/GalleryImage.cs (Path/StorageFilename), GalleriesAppFunc.cs (resolve по galleryId+seq, 404 при null/mime-mismatch), Services/GalleryImagesService.cs:135 (StorageClient="galleries", filename=Guid.NewGuid():N), FileStorage.Local/GalleriesStorage.cs (BasePath App_Data\\galleries\\<siteId>\\).
|
||
- snolla-консьюмер: packages/snolla/middleware/galleries.js, lib/viewModels/index.js:289.
|
||
- siteId pilorama98: 37e67fc4… (бакет-сегмент в source).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
|
||
- invoke `systematic-debugging` — закрыть развилку A/B по фактам MinIO до любых действий
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- invoke `using-vds-ops` — если MinIO/imgproxy крутятся на VDS (docker-диагностика)
|
||
- invoke `using-projects-meta` — кросс-проектный отчёт/ follow-up в victor/snolla
|
||
|
||
**TDD:** нет — ops/migration задача (нет тестируемого кодового ядра; если всплывёт код-фикс на snolla — он уйдёт отдельной impl-таской на victor/snolla с TDD).
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human
|
||
**notify:** victor/snolla
|
||
|
||
**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 — ✅ 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 -->
|
||
|
||
---
|
||
|
||
## 🟢 [migrate-assets-originals-to-s3] — closed 2026-06-13 — assets-оригиналы залиты в S3, imgproxy 200 verified (scope расширен: весь assets-дерево, не только pilorama98). Создан bucket `assets` (отсутствовал) на MinIO books-vds; rclone RUVDS IIS `App_Data\assets` → `s3://assets/<ownerId>/<storageFilename>` verbatim. **Verify:** dest == source точь-в-точь — 4750 объектов / 225 935 853 B (215.5 MiB), 145 owner-папок. **Smoke (через books-vds, мимо LAN-DNS):** brevno.jpg (предподписанный URL из задачи) → 200 image/webp 64556 B (был 404); валидно-подписанный несуществующий объект → 404 (негативный контроль). Код snolla НЕ менялся (path A). Inbox victor/snolla отправлен. ⚠️ Остаётся snolla-side: `content-api/routes/assets.js` пустой stub — картинки не поедут на сайт пока роут не реализован (код-таска snolla, не блокер миграции).
|
||
|
||
### (исходное описание ниже сохранено до prune)
|
||
Залить Local-storage assets-оригиналы сайта pilorama98 в S3 — латентная дыра того же класса, что галереи (закрыта миграцией `migrate-gallery-originals-to-s3`). snolla отдаёт inline-картинки контента (`<img src="/assets/<ownerId>/<size>/<file>">`) через imgproxy с источником `s3://assets/<ownerId>/<storageFilename>`, но объектов в S3 нет — imgproxy 404 "Source image is unreachable". БД-записи есть, файлы только на legacy-IIS диске. Тип `StorageClient="assets"` — это `MoreThenCms.FileStorage.Local.*`, в S3 никогда не мигрировали.
|
||
|
||
## Acceptance criteria
|
||
1. Legacy assets-оригиналы pilorama98 залиты в S3 bucket `assets` ключами `<ownerId>/<storageFilename>` (path A, как с галереями: storageClient = имя бакета).
|
||
2. Контрольный прозвон imgproxy подтверждает 200 image/webp на реальном объекте + 404 на несуществующем (негативный контроль).
|
||
3. Verify: dest object count == source, размер == source.
|
||
4. Зафиксирована точная форма ключа и охват (какие ownerId/папки залиты), отчёт в inbox victor/snolla.
|
||
|
||
## Подтверждённое репро (от victor/snolla, 2026-06-13)
|
||
- URL у консьюмера: `http://localhost:3742/assets/83150d773cf0455ea58ddcccb2578713/800x450/brevno.jpg` → 404.
|
||
- БД `MoreThenCms` (mssql.kzntsv.site), таблицы `Folders`/`Files`:
|
||
- `Folders`: OwnerId `83150d77-3cf0-455e-a58d-dcccb2578713` → корневая папка `/` (FolderId `03CE5D8D-95B9-4D81-B222-80AEC76225CD`). **Внимание: ownerId это владелец медиа-папки, НЕ siteId** (siteId pilorama98 = `37e67fc4…`).
|
||
- `Files`: `brevno.jpg` → StorageClient `assets`, StorageFilename `brevno.jpg`, ContentType image/jpeg, Size 580667 B.
|
||
- Прямой прозвон imgproxy подписанным URL источника `s3://assets/83150d773cf0455ea58ddcccb2578713/brevno.jpg` → **HTTP 404 "Source image is unreachable"** (подпись валидна, объекта в S3 нет).
|
||
- Подписанный путь для верификации после заливки:
|
||
`https://imgproxy.kzntsv.site/n8AdQMelCr2lIvWzeQ3Mw6Fqxft3Q24Rd3vCg-ctHaA/fill/800/450/ce/1/czM6Ly9hc3NldHMvODMxNTBkNzczY2YwNDU1ZWE1OGRkY2NjYjI1Nzg3MTMvYnJldm5vLmpwZw.webp` → ждём 200 после миграции.
|
||
|
||
## Подсказки по форме ключа (важно — отличие от галерей)
|
||
- Галереи: prefix = `String(site.id)` (siteId сайта). Ассеты: prefix = **OwnerId папки из таблицы Folders**, НЕ siteId. Для brevno.jpg это `83150d773cf0455ea58ddcccb2578713`. На диске ассеты, вероятно, разложены по этим owner-id, а не по siteId.
|
||
- Ключ верстается snolla как `s3://${asset.storageClient}/${ownerId}/${asset.storageFilename}` (`middleware/assets.js:98`), storageFilename берётся verbatim (с заменой `\`→`/`).
|
||
- Залить ВСЕ assets-папки/owner'ы pilorama98 (не только owner brevno.jpg) — иначе остальные inline-картинки контента дадут ту же дыру.
|
||
|
||
## Хвосты (контекст, не блокеры)
|
||
- Тот же Local-класс у `images / scripts / stylesheets / watermarks` — если snolla отдаёт их через imgproxy, их ждёт та же дыра (отдельный аудит на стороне victor/snolla, таска `audit-local-storage-clients-imgproxy-s3-gap`).
|
||
- На стороне snolla отдельно нужна реализация `content-api/routes/assets.js` (сейчас пустой stub) — это код-таска victor/snolla, не блокер миграции, но картинки не поедут пока не сделаны ОБА (миграция + роут).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- invoke `using-projects-meta` — cross-project: отчёт в inbox victor/snolla при close/park
|
||
- invoke `systematic-debugging` — прозвон по фактам (DB → диск → S3 → imgproxy) до выводов
|
||
|
||
**TDD:** нет — инфра-миграция/ops (если всплывёт код — отдельной impl-таской с TDD).
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human
|
||
**notify:** victor/snolla
|
||
|
||
**Status:** done (closed 2026-06-13)
|
||
**Where I stopped:** мигрировано всё дерево assets. rclone size min:assets == источник (4750 / 225935853). Smoke 200/404 OK.
|
||
**Next action:** — (хвост на стороне snolla: реализовать `content-api/routes/assets.js`; остальные Local-классы images/scripts/stylesheets/watermarks — отдельный аудит snolla, если поднимутся через imgproxy).
|
||
**Branch:** master
|
||
**Notify:** victor/snolla — ✅ inbox `.claude-inbox/2026-06-13T20-14-16Z-admin.md`
|
||
|
||
**Closure note (2026-06-13):**
|
||
- **Диск (RUVDS IIS `C:\sites\snolla\App_Data\assets`):** Local storage class, 145 owner-папок (32-hex), плоские файлы. brevno owner `83150d77…` = 6 файлов, `brevno.jpg`=580667 B == DB `Files.Size`. Ключ-форма `<ownerId>/<storageFilename>` подтверждена на диске.
|
||
- **MinIO (books-vds, minio.kzntsv.site):** bucket `assets` отсутствовал → создан. Залив rclone v1.74.2 (env-var s3-remote, без записи cred), `App_Data\assets` → `min:assets` verbatim. Verify `rclone size` == source: 4750 объектов / 225935853 B.
|
||
- **Smoke (мимо LAN-DNS воркстейшна, `--resolve …:89.253.255.133`):** предподписанный imgproxy-URL brevno.jpg → 200 image/webp 64556 B; валидно-подписанный (IMGPROXY_KEY/SALT с books-vds) несуществующий объект → 404 text/plain. 200 не ложный — imgproxy реально ходит в S3.
|
||
- **Scope расширен намеренно:** задача про pilorama98, залито всё (215 MiB — мизер). owner→сайт маппинг неочевиден (риск пропустить owner pilorama98), заливка всего гарантирует покрытие + закрывает класс-404 для всех тенантов. Регрессии нет (shared bucket, как galleries).
|
||
- **Wiki:** concept `galleries-storage-class-local-not-s3` обновлён (assets done).
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/snolla / 2026-06-13T19:31:07.686Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-13 / path A: bucket assets + весь App_Data\assets (4750 obj/215.5 MiB), imgproxy 200/404 verified, snolla code unchanged -->
|
||
|
||
---
|
||
|
||
## 🟢 [deploy-pilonuxt-vds] — closed 2026-06-15 — CUTOVER LIVE на www.pilorama98.ru (tag 125a3e2, Portainer stack 16). **Цель:** Собрать prod-образ нового Nuxt-фронта ПИЛОРАМА98 (`victor/pilonuxt`, master) и задеплоить на VDS как Portainer-стек за Traefik. Готово в репо: `Dockerfile` + `.dockerignore` в корне. Полный овнершип сборки и деплоя — на тебе (build → push в VDS-registry → Portainer stack), как books/stostayer.
|
||
|
||
**Сборка:**
|
||
- `docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -t <registry>/pilonuxt:<tag> .`
|
||
- `VERDACCIO_TOKEN` — ЖИВОЙ JWT (len ~300, 2 точки), User-env этой же машины (где `pass`). НЕ legacy-opaque (len ~196). Детали: pilonuxt `.wiki/concepts/verdaccio-token-lifecycle.md`.
|
||
- Билд ходит в сеть: `verdaccio.kzntsv.site` (пакеты) + `mssql.kzntsv.site:1433` (postinstall→schema-gen генерит `src/generated/` из CMS-БД по `config/default.json`). Если собираешь там, где CMS-БД недоступна — предгенери `yarn schema-gen` и положи `src/generated/` в контекст (schema-gen в билде тогда пропустится).
|
||
- Многостадийный, нативных модулей НЕТ (better-sqlite3 выпилен) → build-toolchain не нужен.
|
||
|
||
**Runtime egress из контейнера:**
|
||
- `mssql.kzntsv.site:1433` — живые запросы CMS (cacheTimeout=0), обязателен.
|
||
- `smtp.yandex.ru:465` — письма форм (contacts/checkout).
|
||
- Собственный публичный origin — SSR self-call на `/snolla` через `useRequestURL().origin` (`app/plugins/snolla.ts`). Контейнер должен резолвить+достигать свой публичный домен через Traefik. До cutover домена origin-петля на боевом не замкнётся.
|
||
|
||
**Env:**
|
||
- build: `VERDACCIO_TOKEN` (secret, НЕ ARG/ENV — иначе осядет в слоях).
|
||
- runtime (опц.): `NITRO_PORT`(деф 3000), `NITRO_HOST`(деф 0.0.0.0), `NUXT_PUBLIC_GTM_ID`(деф боевой `GTM-MNNXFJ6`; пустой → выкл), `NUXT_PUBLIC_SNOLLA_BASE_URL`(деф `/snolla`).
|
||
- Секреты (MSSQL/SMTP/imgproxy key+salt/siteId) ЗАПЕЧЕНЫ в `config/default.json` внутри образа (решение vitya — образ только в приватном registry). Env-override не настроен.
|
||
|
||
**Контекст инфры (с твоей доски):**
|
||
- imgproxy на books-vds (стек 29/30); у фронта `/imgproxy/**` = routeRule-редирект на `imgproxy.kzntsv.site`.
|
||
- ⚠️ Известный gap: snolla-side `content-api/routes/assets.js` — пустой stub (ты флагал в `migrate-assets-originals-to-s3`); без него часть картинок может не рендериться. Проверь на smoke.
|
||
- Прод `www.pilorama98.ru` сейчас = легаси MoreThenCms. Cutover домена — решение vitya: деплой на временный поддомен (напр. `pilonuxt.vds.kzntsv.site`) для smoke ИЛИ сразу боевой — согласуй.
|
||
|
||
**Acceptance:**
|
||
1. Образ собран и в registry.
|
||
2. Portainer-стек за Traefik, healthy.
|
||
3. Smoke: главная 200 + SSR-разметка (не 500); товары/категории из CMS; картинки рендерятся; форма contacts шлёт письмо.
|
||
4. Доклад в inbox `victor/pilonuxt`: URL, имя стека/тег образа, итог smoke.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-tasks` — статус задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- invoke `using-vds-ops` — диагностика контейнеров VDS
|
||
- invoke `using-projects-meta` — cross-project (notify pilonuxt, чтение pilonuxt-вики)
|
||
- invoke `using-wiki` после деплоя — заингесть итог в `.admin/.wiki/concepts/`
|
||
|
||
**TDD:** нет — ops/deploy, не код.
|
||
**Разрешения:** интерны: нет | автопуш инфра-конфигов: да
|
||
**weight:** needs-human (deploy-инфра)
|
||
**notify:** victor/pilonuxt
|
||
|
||
**Status:** done (closed 2026-06-15)
|
||
**Closure / coverage check:** vitya флипнул DNS pilorama98.ru+www → 89.253.255.94; я перенастроил traefik-роутер на боевые хосты (`Host(www.pilorama98.ru)||Host(pilorama98.ru)||Host(pilonuxt.vds.kzntsv.site)`, cert LE на apex+www), снял smoke GTM-override (прод-аналитика GTM-MNNXFJ6 ВКЛ), выкатил `125a3e2` (фикс «Продукция»→/catalog). Acceptance: (1) образ в registry ✓ 125a3e2; (2) Portainer stack 16 за traefik healthy ✓; (3) smoke ✓ — финальный краул с VDS по боевому www.pilorama98.ru: все страницы page 200/viewModel 200 (главная/каталог/категории/товар/услуги/контакты/оплата/доставка), картинки imgproxy 200 webp, x-powered-by Nuxt (не IIS), cert valid, футер /products битый линк убран (0 вхождений, «Продукция»→/catalog); форма contacts — закрыта (vitya «формы заебись» + pilonuxt end-to-end verified в dev, smtp egress жив); (4) доклад pilonuxt ✓. Урок применён: краулил весь меню+viewModel, не 2 роута ([[smoke-crawl-real-pages-not-two-routes]]).
|
||
**Follow-ups (не блокеры, отдельно):**
|
||
- **Старый IIS RUVDS 80.64.31.36 — РЕШЕНИЕ vitya 2026-06-15: держим для отката, НЕ выводим.** ⚠️ Этот IIS = snolla catch-all на 25 хостнеймов (emspb/kupimknigi/labtools/…), целиком гасить нельзя — при будущем cleanup снимается только биндинг pilorama98, не весь сайт. **Rollback pilorama98:** вернуть DNS reg.ru `pilorama98.ru`+`www` → `80.64.31.36` (старый сайт + его LE-cert живы), TTL низкий. Decommission-биндинга — отдельно после soak.
|
||
- DNS-propagation хвост (часть юзеров ещё ловит старый IIS пока кэши истекают).
|
||
- Опц.: apex→www 301 canonical (легаси так делал; сейчас apex отдаёт фронт напрямую) — SEO-nicety.
|
||
- bare `/products`→404, но unlinked (на него ничто не ссылается).
|
||
**Branch:** n/a | **Notify:** victor/pilonuxt ✓
|
||
|
||
<!-- HISTORICAL (pre-cutover) ниже -->
|
||
**Status (pre-cutover):** blocked
|
||
**Where I stopped (2026-06-14 21:05):** pilonuxt пофиксил content-api (`@snollajs/data@0.9.1` — models-load баг + `tedious` в `nitro.externals.traceInclude`), тег `961a838` (все прошлые битые). Перекатил stack 16. **Полный re-smoke (curl весь меню 21 стр + viewModel по каждому + браузер Playwright + скрин):** viewModel 200 везде (был 500), все страницы меню 200 с реальным контентом (категории/товары/услуги/инфо), товар-стр 200, картинки imgproxy 200 webp, браузер-консоль 0 ошибок, верстка целая. **ОСТАЛСЯ РЕАЛЬНЫЙ БАГ: пункт меню «Продукция» → `/products` → 404** (битый линк, до cutover нельзя). Мелочи: `/blog`+`/services/building/projects/` viewModel 404 (страницы 200), Я.Метрика external не грузится (не баг app). Форма contacts → email НЕ гонял (живой email, жду решение). Скрин `pilonuxt-home-smoke.jpeg`. Inbox pilonuxt отправлен (21:05).
|
||
**Next action:** pilonuxt чинит `/products` 404 → новый тег → pull+stack16 update → добить smoke (/products + форма). Согласовать с vitya: тест формы contacts (live [SMOKE TEST] vs закрыть по verified). Cutover www.pilorama98.ru — после закрытия /products + формы.
|
||
**Blocker:** pilonuxt: пункт меню «Продукция» → /products → 404 (роут/CMS-страница отсутствует либо нужен редирект на /catalog).
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilonuxt
|
||
|
||
**(history)** Фальш-green #1 (20:25): объявил green по `/`+`/catalog`, vitya поймал `/snolla/pages/viewModel?path=/services`→500; content-api лежал полностью (`sequelize['snolla']` undefined — `@snollajs/data` models не загружались + `tedious` не в .output). Урок: [[smoke-crawl-real-pages-not-two-routes]]. Путь: ТЗ→pull→домен(temp). Мои 2 Dockerfile-бага (npmAlwaysAuth + COPY scripts/src-generated до install) → pilonuxt пофиксил +свой (build-time VERDACCIO_TOKEN default `${VAR:-}`) → образ `36ec970` битый: дубль Vue в prod-бандле давал SSR 500 `reading 'ce'` на всех роутах; диагностировал «инфра зелёная, баг в app»; pilonuxt нашёл (Vite дедупит Vue в dev, Nitro prod нет) → `resolutions` → `3622662` зелёный.
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilonuxt / 2026-06-14T18:45:22.955Z -->
|
||
<!-- activated: vitya@.admin-exec / 2026-06-14 / ТЗ получено, домен=temp-subdomain для smoke -->
|
||
<!-- re-smoke green: vitya@.admin-exec / 2026-06-14 20:05 / stack 16 = pilonuxt:3622662, infra+app green; pending: contacts-email test + cutover -->
|
||
|
||
---
|
||
|
||
## 🟢 [redeploy-pilonuxt-gsc-offers] — closed 2026-06-16 — pilonuxt `37412d0` в проде, offers в микроразметке Product live. Build на workstation (verdaccio build-secret) → push registry.kzntsv.site/pilonuxt:37412d0 (398MB) → перекат Portainer stack 16 (PUT API). Acceptance verified (curl --resolve к VDS): на `/shop/products/doska-obreznaya-25-100-6000` есть `itemprop="offers"` itemtype Offer с price=277.5/priceCurrency=RUB/availability=InStock/url-канон; X-Powered-By Nuxt; регрессии нет (меню /catalog,/services,/contacts,/payment = 200, viewModel жив). ⚠️ По ходу: в Portainer не был зарегистрирован НИ ОДИН registry → первый pull упал `no basic auth credentials`; зарегистрировал `registry.kzntsv.site` (Custom, Id 1, registry-креды из pass) → перекат прошёл, приватный registry теперь авторизован в Portainer постоянно. Inbox pilonuxt отправлен (GSC → «Проверить устранение»). Передеплой pilonuxt с SEO-фиксом микроразметки Product (commit 37412d0). Google Search Console отсеивал страницы товаров из выдачи: критическая ошибка «задайте offers / review / aggregateRating» — у Product не было блока offers, а aggregateRating рендерился только при наличии рейтинга. Фикс добавил schema.org/Offer (price/priceCurrency/availability/url) в app/pages/shop/products/[slug].vue, проверено в SSR. Только код фронта — env/инфра/секреты не менялись, build/runtime egress те же, что в прошлый деплой (verdaccio + mssql при сборке; mssql + smtp + self-origin в рантайме). Acceptance: новый образ в проде, на странице товара в HTML присутствует itemprop="offers" с ценой и наличием.
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
|
||
**TDD:** нет — ops/deploy, кода не пишем; верификация = SSR-вывод страницы товара.
|
||
**Разрешения:** интерны: нет | автопуш: n/a (деплой в твоём контуре)
|
||
**weight:** needs-human
|
||
**notify:** victor/pilonuxt
|
||
|
||
**Status:** 🟢 done (closed 2026-06-16 vitya@.admin-exec)
|
||
**Closure / coverage check:** build workstation @ 37412d0 (verdaccio JWT build-secret) → push registry.kzntsv.site/pilonuxt:37412d0 (398MB) → Portainer stack 16 PUT (PullImage:true) → контейнер running 37412d0. Smoke curl --resolve www.pilorama98.ru:443:89.253.255.94: offers-блок present (price/priceCurrency/availability/url), X-Powered-By Nuxt, меню+viewModel 200 (без content-api-500). Урок [[smoke-crawl-real-pages-not-two-routes]] применён — краулил реальные nav-href, не гадал слаги (/delivery 404 но не в меню → не баг).
|
||
**Follow-up (не блокер):** registry.kzntsv.site зарегистрирован в Portainer (Custom Id 1) — закрывает «no basic auth» на будущих перекатах приватных образов; до этого pull держался на host docker login, который слетел.
|
||
**Next action:** —
|
||
**Branch:** master
|
||
**Notify:** victor/pilonuxt ✓
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-16 / build+push+stack16 PUT, offers verified via VDS-resolve smoke, Portainer registry registered -->
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilonuxt / 2026-06-16T13:49:40.667Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [redeploy-pilonuxt-gsc-structured-data] — closed 2026-06-17 — `19a4a84` в проде, structured-data (description/brand/hasMerchantReturnPolicy) live. Передеплой www.pilorama98.ru с фиксом микроразметки Product под minor-замечания Google Search Console (structured data «Данные о товарах продавца»).
|
||
|
||
Коммит уже на origin/master: `19a4a84` (fix(seo): обогатил микроразметку Product offers). Изменён только `app/pages/shop/products/[slug].vue` — добавлены itemprop description (always-on), brand, hasMerchantReturnPolicy внутри offers. Тот же пайплайн, что вчерашний `redeploy-pilonuxt-gsc-offers` (37412d0).
|
||
|
||
## Acceptance
|
||
- Образ собран из master @ `19a4a84`, тег `registry.kzntsv.site/pilonuxt:19a4a84`, запушен в приватный registry.
|
||
- Portainer-стек 16 перекатан на новый образ, контейнер healthy за Traefik.
|
||
- На проде на странице товара (например `/shop/products/doska-obreznaya-25-100-6000`) в SSR-HTML присутствуют: `itemprop="description"` (непустой), `itemprop="brand"` (Пилорама 98), внутри `itemprop="offers"` — `itemprop="hasMerchantReturnPolicy"` с `returnPolicyCategory=MerchantReturnNotPermitted`. Прежние offers-поля (price/priceCurrency/availability/url) на месте, регрессий нет.
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `using-tasks` — управление статусом этой задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- по деплою свериться с концептом вики pilonuxt `concepts/docker-deploy.md` (build-secret VERDACCIO_TOKEN, runtime-egress, приватный registry registry.kzntsv.site зарегистрирован в Portainer Custom Id 1)
|
||
|
||
**TDD:** нет — ops-задача (build + redeploy), кода не пишется.
|
||
**Разрешения:** интерны: нет | автопуш: n/a (билд из уже запушенного master, кода не коммитим)
|
||
**weight:** needs-human (deploy-инфра: registry + Portainer-стек за Traefik)
|
||
**notify:** victor/pilonuxt (ответ о закрытии/затыке — в inbox pilonuxt, там слушает монитор)
|
||
|
||
**Status:** 🟢 done (closed 2026-06-17 vitya@.admin-exec)
|
||
**Closure / coverage check:** build на workstation @ 19a4a84 (verdaccio JWT build-secret, EXIT=0, src/generated предгенерён → mssql на билде не дёргался) → push registry.kzntsv.site/pilonuxt:19a4a84 (digest sha256:9642…fa5) → Portainer stack 16 PUT(pullImage:true) → контейнер running 19a4a84 (vds-ops ps). Smoke curl --resolve www.pilorama98.ru:443:89.253.255.94 на /shop/products/doska-obreznaya-25-100-6000: **(1)** `itemprop="description"` непустой («Характеристики Сорт 1 Материал Сосна…») ✓; **(2)** `itemprop="brand"` content="Пилорама 98" ✓; **(3)** внутри `itemprop="offers"` (schema.org/Offer): `hasMerchantReturnPolicy`→`returnPolicyCategory=https://schema.org/MerchantReturnNotPermitted` ✓; **(4)** прежние offers-поля целы: price=277.5/priceCurrency=RUB/availability=InStock/url=канон ✓; X-Powered-By Nuxt. Регрессий нет: / /catalog /services /contacts /payment = 200 (/delivery 404 unlinked, как и вчера). Урок [[smoke-crawl-real-pages-not-two-routes]] применён.
|
||
**Gotcha (новый, заингещён):** Portainer stack update через PS 5.1 Invoke-RestMethod коррапит кириллические комментарии compose — ответ /file декодится как ISO-8859-1 (mojibake) → обратный PUT даёт `yaml: could not find expected ':'`. Фикс: GET байтами (-OutFile) + read -Encoding UTF8, PUT тело UTF-8-байтами. Записано в concepts/portainer-stack-management-vds.md.
|
||
**Next action:** —
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilonuxt ✓
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-17 / build+push+stack16 PUT(pullImage), structured-data verified via VDS-resolve smoke, PS-UTF8 gotcha ingested -->
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilonuxt / 2026-06-17T07:00:53.563Z -->
|
||
|
||
---
|
||
|
||
## 🔵 [stostayer-web-complaint-form-deploy] — Раскатать на прод (www.stostayer.ru) фикс формы жалоб. Коммит **120bc07** уже в `origin/master` репо `victor/stostayer.new`.
|
||
|
||
## Контекст
|
||
|
||
Прод-форма `/pozhalovatsya#complaint_form` не отправляла данные. Причина: в `packages/web/pages/pozhalovatsya.vue` `onSubmit` вызывал голый `axios(...)` вместо `this.$axios(...)` → `ReferenceError`, съеденный пустым `catch{}`. Чистый клиентский фикс (1 файл). Прод = старый **`packages/web`** (Nuxt 2 SSR), web4 ещё НЕ переключён.
|
||
|
||
## ⚠️ Деплой-нюанс
|
||
|
||
Это `.vue`-страница в Nuxt 2 **SSR-сборке** — простого `git pull` НЕДОСТАТОЧНО. Прод крутит собранный output через pm2 (`packages/web`: `start = node server/index.js`, `build = nuxt build`, pm2 → `ecosystem.config.js`). Нужен: pull → `yarn build` (или `nuxt build` с `NODE_OPTIONS=--openssl-legacy-provider`) в `packages/web` → рестарт pm2-процесса. Точную прод-процедуру ты знаешь лучше (раскатывал vehicles-loader) — действуй по факту, это лишь напоминание что нужна пересборка, не только pull.
|
||
|
||
## Acceptance
|
||
|
||
- На проде HEAD `packages/web` = коммит 120bc07 (или master ≥ него).
|
||
- Пересборка выполнена, процесс рестартнут без ошибок в логах.
|
||
- Smoke вручную: открыть https://www.stostayer.ru/pozhalovatsya#complaint_form, заполнить (Имя, Телефон, СТО, Описание), Отправить → улетает `POST /ostavit-otzyv`, появляется success-модалка «Подтверждение». Письмо/заявка доходит как у `/ostavit-otzyv`.
|
||
- Откат: если форма ломается — вернуть прежний билд/коммит (RTO минимальный).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
|
||
- invoke `using-tasks` — управление статусом этой задачи
|
||
- invoke `project-discipline` — дисциплина деплоя/коммитов
|
||
|
||
**TDD:** нет — деплой existing-коммита, admin кода не пишет; верификация = ручной smoke.
|
||
**Разрешения:** интерны: нет | автопуш: нет (admin коммитов не делает; если понадобится тег/бамп — спросить у victor/stostayer.new через inbox).
|
||
**weight:** needs-human
|
||
**notify:** victor/stostayer.new
|
||
|
||
**Status:** blocked
|
||
**Where I stopped:** Cherry-pick `120bc07` на `d02f740` (pre-ESM, рецепт stostayer.new) → `7d88a02`. **Собралось** offline (nuxt build прошёл) → push 0.3.19 в `docker.stostayer.ru` (с 1 попытки на новом VPN; прежние retry-штормы засветили egress → хостинг забанил наш VPN-IP, vitya сменил VPN) → передеплой Portainer-стека `stostayer-web` (Id 16, EndpointId 3) на 0.3.19 через Portainer API по IP контейнера `172.18.0.2:9000` (мимо Angie BA). **Но контейнер краш-лупит в рантайме:** `ERR_REQUIRE_ESM` — `packages/web/server/index.js:6` `require('@snollajs/snolla')`, а он ESM (lockfile d02f740 тянет ESM-версию). d02f740 собирается, но НЕ runtime-чистая. **Откатил стек на 0.3.18** (тот же PUT, тег назад) — контейнер Up, штатный Nuxt2, сайт восстановлен. vitya: остаёмся на 0.3.18, апдейт отставлен.
|
||
**Next action:** ПАРКНУТО. Деплой невозможен до web4-cutover. Вердикт (stostayer.new 14-55, подтверждён): легаси `packages/web` require-ит **5** ESM-ставших пакетов (`@snollajs/snolla` 0.7.4, `@snollajs/content-api` 0.8.0, `@stostayer/api`, `@stostayer/data` ×2 места) → чейз CJS-базы бесполезен (надо откатиться к ~0.3.18 и потерять всё). `0.3.18` = последний собираемый артефакт, заморожен. Фикс формы (`120bc07`) едет вместе с переездом формы в web4. Ранбук: `.wiki/concepts/stostayer-web-deploy-runbook.md`. Дохлый `0.3.19` в `docker.stostayer.ru` — **оставлен по решению vitya (2026-06-17)**, не деплоить.
|
||
**Blocker:** web4-cutover (планирование на стороне vitya, отдельным решением) — НЕ на stostayer.new и НЕ канал доставки (он рабочий). Прод остаётся на 0.3.18, сайт восстановлен.
|
||
**Branch:** n/a
|
||
**Notify:** victor/stostayer.new
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/stostayer.new / 2026-06-17T12:56:53.300Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [books-scheduler-registry-auth-cred] — ## Проблема (простыми словами)
|
||
|
||
Планировщик books (`books-job-scheduler` на books-vds) умеет запускать «docker-таски» — для каждой такой задачи он скачивает (pull) готовый образ-инструмент из реестра `registry.kzntsv.site` и запускает его контейнером. Один из таких инструментов — `books-tool-create-picking-list-pdf` (рендерит PDF листа подбора FBS).
|
||
|
||
С 23 мая каждый такой запуск падает на стадии скачивания образа с ошибкой:
|
||
`pull/create failed: (HTTP 500) ... no basic auth credentials`.
|
||
|
||
Причина (разобрана совместно с admin): `registry.kzntsv.site` — это standalone `registry:2` за htpasswd Basic-авторизацией (так с самого bootstrap 20.05, анонимного доступа не было никогда). Раньше образ просто лежал в локальном кэше демона и pull не требовался; ночная чистка `vdsDockerCleanup` 23 мая вычистила кэш — и следующий pull впервые упёрся в всегда-бывшую auth-стену. Код планировщика звал pull **без креда** (анонимно) → реестр отвечал 401 → dockerode оборачивал в 500. Итог: ~80 фейлов, PDF листов подбора не генерятся с 23 мая.
|
||
|
||
Approach ратифицирован человеком (вариант A): код планировщика будет слать креды в pull (это делаю я, books, отдельно, под TDD), а реестру нужен отдельный htpasswd-юзер `books-ci` (его ты уже завёл, кред в `pass vds-kzntsv/registry-books-ci`, проверен live: authed HEAD manifests/master → 200).
|
||
|
||
## Что нужно сделать (твоя зона — admin)
|
||
|
||
Положить кред `books-ci` в config-volume планировщика на **books-vds (89.253.255.133)**:
|
||
|
||
- **Файл (bind-mount):** `/opt/books/job-scheduler/config/default.json`
|
||
- В этом JSON уже есть объект верхнего уровня `"docker"` (там `host`, `maxConcurrent`, `pullBackoffMs` и т.д.). Добавить в него ключ `registryAuth`:
|
||
```json
|
||
"docker": {
|
||
"...": "... существующие поля не трогать ...",
|
||
"registryAuth": {
|
||
"username": "books-ci",
|
||
"password": "<пароль из pass vds-kzntsv/registry-books-ci>",
|
||
"serveraddress": "registry.kzntsv.site"
|
||
}
|
||
}
|
||
```
|
||
- `serveraddress` — ровно `registry.kzntsv.site` (без `https://`, без `/v2`).
|
||
- После правки **перезапустить контейнер `books-job-scheduler`** (через Portainer/compose), чтобы node перечитал config — bind-mount подхватывается только на старте процесса.
|
||
|
||
## Acceptance
|
||
|
||
- Ключ `docker.registryAuth` присутствует в `/opt/books/job-scheduler/config/default.json`, пароль соответствует `books-ci` в реестре.
|
||
- Контейнер `books-job-scheduler` перезапущен и `healthy`.
|
||
- (Финальная проверка — за books: после деплоя code-части убедиться что `createPickingListPdf` перестал падать на pull. Образ и тег `master` на месте, допушивать ничего не надо — pull заработает сразу как кред доедет + выйдет code-часть.)
|
||
|
||
## Замечания
|
||
|
||
- Кред **не хардкодить** в git — только в config-volume на хосте (как mongo/s3/mariadb-креды рядом).
|
||
- registryGc (вторая docker-related задача, падает «gitea config missing») — **в эту таску НЕ входит**, разбирается отдельно на стороне books (мис-таргетирован на Gitea-packages, переписывается на registry v2 DELETE позже).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
|
||
- invoke `using-tasks` — для управления статусом этой задачи
|
||
- invoke `using-projects-meta` — cross-project координация / нотификация
|
||
- invoke `project-discipline` — дисциплина изменений на проде (config-volume, рестарт)
|
||
- invoke `using-vds-ops` или `books-ops-mcp` — read-only проверка `books-job-scheduler` (health/env) после рестарта
|
||
|
||
**TDD:** нет — ops-задача (размещение креда + рестарт контейнера), кода нет.
|
||
**Разрешения:** интерны: нет (секрет) | автопуш: н/п (не git-код books)
|
||
**weight:** needs-human
|
||
**notify:** victor/books
|
||
|
||
**Status:** done
|
||
**Where I stopped:** cred placed in config-volume; activation = books code deploy (no premature restart)
|
||
**Next action:** (none — kept until merged)
|
||
**Branch:** n/a
|
||
**Notify:** victor/books
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/books / 2026-06-18T06:57:04.379Z -->
|
||
|
||
|
||
|
||
## Done (admin infra)
|
||
- `docker.registryAuth = {username:"books-ci", password:<pass vds-kzntsv/registry-books-ci>, serveraddress:"registry.kzntsv.site"}` written into `/opt/books/job-scheduler/config/default.json` (bind-mount → scheduler container, verified pwlen=48, valid JSON). Backup: `default.json.bak-pre-registryauth-2026-06-18`.
|
||
- **No restart performed** — current running code doesn't read `registryAuth` yet (inert/backward-compatible). Activation happens on books' code deploy (rebuild = restart, bind-mounted config persists). If books had already deployed the new code, a single `books-job-scheduler` restart activates it.
|
||
- Cred itself verified against registry earlier: `books-ci` Basic → 200 on `books-tool-create-picking-list-pdf:master`, wrong-pass → 401.
|
||
- Image + `master` tag present in registry → pull works as soon as code+cred are both live; no re-push needed.
|
||
<!-- closed-by: OpeItcLoc03@DESKTOP-NSEF0UK / 2026-06-18T07:00:51.841Z / note: cred placed in config-volume; activation = books code deploy (no premature restart) -->
|
||
|
||
---
|
||
|
||
## 🟢 [books-task-runner-registry-auth-cred] — Положить кред books-ci в **task-runner** config-volume, чтобы заработал переписанный `registryGc` (чистка Docker registry через Registry v2 DELETE).
|
||
|
||
Контекст: registryGc раньше бил в Gitea packages API и никогда не отрабатывал (образы books-* живут в standalone registry:2 на registry.kzntsv.site за htpasswd). Переписан на Registry v2 REST с Basic-auth кредом books-ci (commit books@a3734d5). Кред books-ci уже есть в **job-scheduler** config-volume (для docker pull) — нужен тот же и в **task-runner** (registryGc бежит там, http-runner, отдельный volume).
|
||
|
||
## Что сделать
|
||
В task-runner config-volume (`/opt/books/task-runner/config/...` — local.json/production.json поверх default.json) добавить секцию:
|
||
```json
|
||
"registry": {
|
||
"baseUrl": "https://registry.kzntsv.site",
|
||
"username": "books-ci",
|
||
"password": "<из pass vds-kzntsv/registry-books-ci>"
|
||
}
|
||
```
|
||
(baseUrl+username уже в git default.json; нужен только password — секрет, в git его нет).
|
||
|
||
⚠️ Файл config-volume: соблюсти права как в memory `feedback_container_user_node_uid_1000` — контейнер бежит под uid 1000, НЕ ставить `600 root:root` (был прод-даун 2026-06-18 на createPickingListPdf из-за EACCES). Нужен read для uid 1000 (644).
|
||
|
||
## Acceptance
|
||
- `registry.password` присутствует в task-runner config-volume.
|
||
- Ручной trigger registryGc через /scheduler с `dryRun=true` отрабатывает без 401: в отчёте видны repos books-* и «будет удалено N». (Если 405 на DELETE — значит на registry не включён `REGISTRY_STORAGE_DELETE_ENABLED=true`; отдельный вопрос, отметить.)
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `using-tasks` — для управления статусом задачи
|
||
- invoke `using-projects-meta` — cross-project tasks/wiki
|
||
- invoke `books-ops-mcp` — проверить контейнер/конфиг task-runner read-only до и после
|
||
|
||
**TDD:** нет — ops/secret, кода нет.
|
||
**Разрешения:** интерны: нет (секрет) | автопуш: n/a (ручная ops на VPS).
|
||
**weight:** needs-human — секрет + доступ к VPS config-volume, критичная инфра.
|
||
**notify:** OpeItcLoc03/books
|
||
|
||
**Status:** 🟢 done — кред положен, acceptance (auth/dryRun) verified
|
||
**Where I stopped:** Закрыта. Секция `registry` (baseUrl+username+password books-ci, полная — bind-mount затеняет config образа) в `/opt/books/task-runner/config/default.json` (books-vds, 644 root сохранён, бэкап `.bak-pre-registry-2026-06-18`, только ключ registry added, EACCES-инцидент 18.06 не повторён). После deploy нового кода (образ `master-bf2a8c5`, код a3734d5 внутри) прогнан `registryGc {dryRun:true,debug:true}` через scheduler-dispatch-путь (`POST /tasks/registryGc` + `x-agenda-job-id`, idентично http.js handler'у). Результат: **success, 401 нет нигде** (hit `/v2/_catalog`=14 repos, `/tags/list`, resolveManifest), repos books-* видны (7 processed), отчёт `task-reports/j/...md`.
|
||
**Acceptance coverage:** (1) `registry.password` в config-volume ✓ (len 48, `config.get` резолвится). (2) dryRun без 401 + repos books-* + «будет удалено N» ✓ (N=0 — см. finding ниже; критерий по форме выполнен). 405/`REGISTRY_STORAGE_DELETE_ENABLED` — dryRun DELETE не слал, не проверено этим прогоном (по вики registry-kzntsv-auth-model уже =true).
|
||
**Finding (отдано books, НЕ блокер кред-таски):** GC аутентифицируется и бежит, но **ничего не удаляет** — по каждой репе `keep=tags−1, drop=0` независимо от keepLastN=3. Root cause в books-коде `lib/registryV2.js`: `getCreated` возвращает null для всех манифестов (вероятно `configDigest` не извлекается — OCI image-index/manifest-list от buildx, или blob 404), а `planDeletions` ставит `protected=true` любой группе с `created==null` → drop пуст всегда. keepLastN не применяется. Owner — books-сессия.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/books
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/books / 2026-06-18T08:28:09.066Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-18 / cred placed (644 mode preserved) + new code deployed (master-bf2a8c5) + dryRun verified no-401, repos books-* visible; flagged planDeletions/getCreated no-op bug to books -->
|
||
|
||
---
|
||
|
||
## 🟢 [archive-pilorama-source-repos] — closed 2026-06-18 — 3 исходных репо заморожены. `archived:true` (Gitea API, http 200) на `victor/pilonuxt`/`OpeItcLoc03/pilorama98.ru-legacy`/`OpeItcLoc03/snolla-products-loader`; в каждом README-баннер + repo-description → `victor/pilorama98.ru/apps/{web,legacy,loader}`; локальные клоны удалены (verified clean + no unpushed ДО rm); projects-meta `archived_skipped` 1→4 (поллер скипает); shared-wiki concept `pilorama98-monorepo-consolidation` создан. **Sequencing-замечание:** архив выполнен 17:43 — раньше, чем пришло предупреждение пира (17:46) о порядке deploy→archive; без последствий (deploy нацелен на монореп, не на pilonuxt; archived-репо остаётся читаемым). См. отчёт в inbox pilorama98.ru.
|
||
|
||
Поглощены → подпапки монорепы:
|
||
- `victor/pilonuxt` → `apps/web` (Nuxt-сайт)
|
||
- `OpeItcLoc03/pilorama98.ru-legacy` → `apps/legacy` (старый сайт)
|
||
- `OpeItcLoc03/snolla-products-loader` → `apps/loader`
|
||
|
||
## Acceptance criteria
|
||
1. Все 3 Gitea-репо в archived (read-only); проверить `archived:true` через API/UI.
|
||
2. В README/описание каждого архивного репо — указатель «переехало в victor/pilorama98.ru → apps/<...>».
|
||
3. Локальные папки `~/projects/{pilonuxt,pilorama98.ru-legacy,snolla-products-loader}` удалены ПОСЛЕ подтверждения архива. Предохранитель перед rm: `git -C <dir> status` чист И `git -C <dir> log @{u}..` пуст (нет unpushed). Контент уже в монорепе и запушен.
|
||
4. projects-meta: поллер не должен surface'ить stale-доски этих репо (archived → skip; свериться что `meta_status.archived_skipped` вырос). Активные задачи уже перенесены в монореп.
|
||
5. Shared meta-wiki: если есть страницы про эти 3 проекта — обновить (переехали в монореп); если нет — короткая запись в concepts о консолидации pilorama-монорепо.
|
||
6. Отчёт в inbox pilorama98.ru.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-projects-meta` — meta-wiki/tasks мутации (preview→confirm) + freshness-gate
|
||
- invoke `using-tasks` — статус этой задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
|
||
**TDD:** нет — ops/админ-задача (verification = archived:true + папки удалены + meta обновлён).
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human
|
||
**notify:** pilorama98.ru
|
||
|
||
**Status:** done
|
||
**Where I stopped:** closed — 5/5 acceptance met (archived:true ×3 / README+desc указатели / папки удалены / поллер скипает 1→4 / shared-wiki concept создан). Отчёт пиру отправлен (с замечанием по их sequencing-багу — две связанные таски делегированы в обратном порядке).
|
||
**Next action:** —
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-18T17:38:42.828Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-18 / archived ×3 + указатели + папки rm + поллер skip 1→4 + shared-wiki concept; archive опередил deploy-sequencing без последствий -->
|
||
|
||
---
|
||
|
||
## 🟢 [deploy-pilorama-web-from-monorepo] — Раскатать на прод текущий `apps/web` из монорепы `victor/pilorama98.ru` — содержит SEO on-page фиксы (тех-аудит 2026-06-18): `buildProductSeo` (172 товарных title+meta, commit `03c250b`) + `seoOverrides` (16 category/service title + 8 meta, commit `b73c7c8`). На проде их пока НЕТ.
|
||
|
||
**Ключевое — перенацелить источник сборки.** Пайплайн исторически собирал из `victor/pilonuxt` (см. web-вики `apps/web/.wiki/concepts/docker-deploy.md`). После консолидации 2026-06-18 код в `victor/pilorama98.ru/apps/web/` (Dockerfile приехал с subtree, там же). Сборку надо перенаправить: clone монорепы, build-контекст = `apps/web/`.
|
||
|
||
## Acceptance criteria
|
||
1. Build из `victor/pilorama98.ru`, контекст `apps/web/` (двухстадийный Nitro node-server; VERDACCIO_TOKEN как docker build-secret — живой JWT len~300, npmAlwaysAuth:true; порядок COPY scripts/ + src/generated/ до install — всё в docker-deploy + verdaccio-token-lifecycle).
|
||
2. Деплой образа на VDS (Portainer стек за Traefik) — обычный re-deploy.
|
||
3. **Smoke на проде (verify, не «собралось=работает»):** товарная `…/shop/products/<slug>` → `<title>` ≤60 вида «{товар} — купить в СПб» + meta description уникальная с «доставка по СПб и ЛО»; категория `…/catalog/planken` → короткий title «Планкен прямой и скошенный — купить в СПб»; `…/shipping` и блог-пост → непустая meta.
|
||
4. Зафиксировать в web-вики (docker-deploy.md / log), что build-источник теперь монореп `apps/web`.
|
||
|
||
## Sequencing (важно)
|
||
Перенацелить пайплайн на монореп **ДО** архива `victor/pilonuxt` (таска `archive-pilorama-source-repos`). Иначе после архива деплой со старого репо не подхватит SEO-фиксы — их в pilonuxt нет. Архив pilonuxt — только после зелёного деплоя с монорепы.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-tasks` — статус задачи
|
||
- invoke `project-discipline` — коммиты/пуши
|
||
- invoke `verify` — РЕАЛЬНО открыть прод-URL и глазами увидеть title/meta, не доверять «собралось»
|
||
- invoke `using-projects-meta` — отчёт обратно
|
||
|
||
**TDD:** нет — deploy/ops-задача (verification = smoke на проде).
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human
|
||
**notify:** pilorama98.ru
|
||
|
||
**Status:** done
|
||
**Where I stopped:** prod зелёный на 6c6d52d — контейнер-smoke все 200 (ecommerce-листинг 200×3, SEO title/meta рендерятся), stderr пуст. Закрыто pilorama98 по подтверждению админа.
|
||
**Blocker:** Рантайм-регресс в snolla-пакетах из монореп-лока. `apps/web` тянет `@snollajs/content-api@^0.11.0`, который nested-депом подтягивает **`@snollajs/snolla@0.11.0`** (рядом с прямым `@snollajs/snolla@0.16.0`). content-api зовёт `SitesService.getSiteById` из 0.11.0 → `Cannot read properties of undefined (reading 'findByPk')` (Sites Sequelize-модель не инициализирована) → весь SSR 500. **Откатил прод на `19a4a84` (verified 200).** Фикс версий snolla — зона web/snolla-владельца (пир + snolla upstream), не ops. Артефакты сборки (новый `apps/web/Dockerfile` + root `.dockerignore`) лежат в рабочем дереве монорепы, готовы к коммиту после фикса депов.
|
||
**Update 2026-06-18 (2):** Пир снял snolla-блокер (commit `eaee56f`: content-api@0.12 peerDep + snolla@^0.17 + vue→3.5.38). Пересобрал из `eaee56f`, задеплоил → **snolla-500 ушёл, home/категории 200 с верным SEO** (`/catalog/planken` → title «Планкен прямой и скошенный — купить в СПб» ✓). **НО новый блокер:** `sharp` нативный libvips не бандлится в Nitro `.output` (`libvips-cpp.so.8.18.3: cannot open shared object`) → товарный листинг `/snolla/stores/.../products` 500 → каталог не грузит товары. Платформо-зависимо (локальный prod-бандл пира на его ОС грузит sharp). **Откатил прод на `19a4a84` снова (home 200 + products 200, каталог жив).** sharp приехал с бампом депов — старый 19a4a84 нативных модулей не имел.
|
||
**Blocker (актуальный):** `sharp`/libvips не попадает в standalone Nitro output. Шов: ПОЧЕМУ sharp в товарном пути = поведение snolla/content-api; КАК забандлить = `apps/web` `nuxt.config` (nitro traceInclude/externals) ИЛИ runner-стейдж Dockerfile (скопировать `@img/sharp-*` нативы в `.output`). Образ `eaee56f` в реестре (не рабочий из-за sharp).
|
||
**Update 2026-06-18 (3):** sharp-фикс СДЕЛАН (admin, Dockerfile-side): runner-стейдж копирует `/app/node_modules/@img` (вкл. `sharp-libvips-linux-x64` с `libvips-cpp.so.8.18.3`) в `.output/server/node_modules/@img`. Verified в контейнере: `import sharp → toBuffer()` = SHARP_OK. Пересобрал+задеплоил → sharp из лога ушёл. **НО за sharp вскрылся СЛЕДУЮЩИЙ блокер:** `getSiteById findByPk undefined` опять, теперь в snolla **0.17** (`@snollajs/snolla/lib/services/sites.js:21`) на **ecommerce-роуте** (`content-api/routes/ecommerce.js:304`) → `/snolla/stores/<id>/products` 500. При этом pages-путь работает: `/` и `/catalog/planken` = 200 c SEO, viewModel ок. Т.е. Sites-модель доступна на pages-роуте, но не на ecommerce — snolla-0.17-внутренности. Был замаскирован sharp'ом (тот падал первым на том же роуте). **Откатил прод на `19a4a84` (все 200, каталог жив).**
|
||
**Blocker (актуальный):** snolla 0.17 — `getSiteById` падает на ecommerce-роуте (Sites-модель undefined именно там; pages-роут ок). Зона пира/snolla. Локальный «товар 200» пира — видимо product-detail (viewModel), а не store-products-листинг; нужен прогон именно `/snolla/stores/<id>/products` в контейнере. sharp (моя часть) закрыт; Dockerfile-фикс ждёт в дереве монорепы (`M apps/web/Dockerfile`, не закоммичен).
|
||
**Update 2026-06-19 (4) — 🟢 GREEN на проде:** пир добил 4-й слой (commit `41cf89f`: `@snollajs/data` 0.9.0→0.9.1 — forEach-async race + Windows-path-surgery, ломавшая `import()` на Linux = ровно Linux-only дефект; snolla 0.17.1 единой runtime). Пересобрал из `6c6d52d` (HEAD, вкл. деп-фикс + мой sharp `@img` COPY), запушил с VDS, задеплоил стек 16. **Контейнер-smoke ВСЁ 200:** товарная `/shop/products/brusok-…-40-50-6000` → title «Брусок обрезной естественной влажности 40×50×6000 мм» (52≤60) + meta «…**доставка по СПб и ЛО** — от производителя» ✓; `/catalog/planken` → «Планкен прямой и скошенный — купить в СПб» ✓; `/snolla/stores/<id>/products` 200×3 стабильно; home/shipping/blog 200 + meta. Контейнер `6c6d52d` 3мин без рестартов, **stderr пуст**. Acceptance 1-3 выполнены, прод отдаёт SEO.
|
||
**Остаётся (🟡):** criterion 4 — закоммитить в МОНОРЕП (`victor/pilorama98.ru`) sharp-фикс `apps/web/Dockerfile` (некоммичен) + обновить `apps/web/.wiki/concepts/docker-deploy.md` («нативных модулей нет» → sharp via snolla 0.17, runner несёт @img). Это **cross-repo push** → ждёт явного push-ok от vitya (дисциплина Rule 4). Прод от этого не зависит — он уже зелёный.
|
||
**Next action:** (none — kept until merged)
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-18T17:44:33.214Z -->
|
||
<!-- blocked-by: vitya@.admin-exec / 2026-06-18 / build+deploy mechanics solved; prod 500 from @snollajs/snolla@0.11.0 getSiteById; rolled back to 19a4a84 (200) -->
|
||
<!-- reblocked: vitya@.admin-exec / 2026-06-18 / snolla fixed (eaee56f), но sharp/libvips не бандлится в .output → products 500; rolled back to 19a4a84 again -->
|
||
<!-- reblocked-2: vitya@.admin-exec / 2026-06-18 / sharp FIXED (admin Dockerfile @img copy, SHARP_OK); behind it getSiteById on snolla 0.17 ecommerce route → products 500; rolled back to 19a4a84 -->
|
||
<!-- closed-by: OpeItcLoc03@DESKTOP-NSEF0UK / 2026-06-18T21:15:28.486Z / note: prod зелёный на 6c6d52d — контейнер-smoke все 200 (ecommerce-листинг 200×3, SEO title/meta рендерятся), stderr пуст. Закрыто pilorama98 по подтверждению админа. -->
|
||
|
||
---
|
||
|
||
## 🟢 [deploy-gsc-product-name-fix] — closed 2026-06-19 — pilonuxt `c4e34d4` на проде, GSC Product `name` FIXED. Build здесь (425MB) → `docker save | ssh vds load` → push С VDS → Portainer stack 16 `pullImage:true` (тег 6c6d52d→c4e34d4). **Acceptance MET:** in-container smoke на VDS + прод-smoke (curl --resolve мимо LAN-DNS) — 3 товарных (`planken-{prjamoj,skoshennyj,zavaltsovannyj}`) → 200, внутри `schema.org/Product` ведущий `<meta itemprop="name">` = название товара (было только Brand `Пилорама 98`). 10 nav-роутов 200, без регрессии. Inbox victor/pilorama98.ru отправлен. — Собрать и переразвернуть прод-образ pilonuxt из master (commit `c4e34d4`) — критический GSC-фикс: микроразметка Product `name` (товары выпадали из выдачи Google «Описания товара → отсутствует поле name»).
|
||
|
||
## Что деплоим
|
||
- Источник: `victor/pilorama98.ru` master @ `c4e34d4` (уже запушен в git.kzntsv.site).
|
||
- Фикс затрагивает только `apps/web/app/pages/shop/products/[slug].vue` (SSR-микроданные Product).
|
||
|
||
## Шаги (по concept .wiki/concepts/docker-deploy.md)
|
||
1. Build из корня монорепы: контекст = корень, `-f apps/web/Dockerfile`, `yarn workspace nuxt-app build`. VERDACCIO_TOKEN — живой JWT через build-secret. `src/generated/` gitignored — собирать из populated dir (или `yarn schema-gen` заранее), иначе COPY упадёт. Порядок COPY: scripts/ до install. sharp @img: runner копирует полный `@img` из builder.
|
||
2. ⚠️ **ПУЛЛ ИЗ РЕЕСТРА.** Push образа в `registry.kzntsv.site/pilonuxt:c4e34d4`, затем стек 16 в Portainer **должен подтянуть свежий образ из registry** (re-pull, не крутить кэш старого тега). Без явного pull задеплоится старый код.
|
||
3. Перекат Portainer-стека 16 за Traefik.
|
||
|
||
## Acceptance
|
||
- Rich Results Test / view-source на товарной странице (`/shop/products/*`) в контейнере/на проде показывает непустой `<meta itemprop="name">` внутри `schema.org/Product`.
|
||
- Verify ТОЛЬКО в контейнере/на проде (локальный node .output на Windows маскирует баг фоллбэком в корневой node_modules).
|
||
- После деплоя отписать в inbox `victor/pilorama98.ru` — владелец нажмёт «Проверить устранение» в GSC.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `project-discipline` — дисциплина коммитов/деплоя
|
||
- свериться с `.wiki/concepts/docker-deploy.md` (в repo pilorama98.ru, apps/web) — runbook деплоя: грабли sharp @img, порядок COPY, verdaccio-token
|
||
|
||
**TDD:** н/п — ops-деплой; код-фикс уже с тестами в исходном проекте (46 зелёных).
|
||
**Разрешения:** интерны: н/п | автопуш: н/п (ops, в наш repo уже запушено).
|
||
**weight:** needs-human (deploy-инфра: docker/registry/Portainer/Traefik).
|
||
**notify:** victor/pilorama98.ru
|
||
|
||
**Status:** done
|
||
**Where I stopped:** closed — деплой завершён и верифицирован на проде (digest sha256:577d8b9). Compose SOT (`host-stacks/vds-kzntsv/pilonuxt.compose.yml`) выровнен на c4e34d4.
|
||
**Next action:** — (владелец нажмёт «Проверить устранение» в GSC)
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-19T06:43:45.922Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [deploy-pilonuxt-static-pages-robots] — closed 2026-06-29 — `40bb383` LIVE на проде (стек 16). DB-backed staticPages + robots.txt отдаются Nitro. Build-пайплайн: master@40bb383 → yarn install → **форс** `rm src/generated`+`schema-gen 0.6` (новый клиент несёт `getStaticPage`/`getRobotsTxt`, грабля обойдена) → docker build 425МБ → push с дома упал на `.output`-слое (EOF/499) → fallback `docker save | ssh vds load` + push с VDS (локально) ✓ → Portainer PUT стек 16 (`pullImage:true`). **Smoke acceptance 6/6** (мимо LAN-DNS, `--resolve` 89.253.255.94): `/robots.txt`→200 text/plain полный из БД (Yandex Clean-param + Sitemap, не allow-all, не статика); `/yandex_*.html`→200 `Verification:`; `/google*.html`→200; `/nope.html`→404 (Nuxt fall-through, не 500); `/`+`/catalog`→200. Notify victor/pilorama98.ru отправлен.
|
||
|
||
<details><summary>исходный наряд</summary>
|
||
|
||
## (orig) Деплой pilonuxt master (commit `40bb383`, включает feature `b32808d`) на прод VDS. Новая фича: DB-backed staticPages + robots.txt через content-api (ADR-0009). Build образа из Gitea master → push в `registry.kzntsv.site/pilonuxt:<sha>` → перекат Portainer-стека 16 за Traefik. Детали: apps/web/.wiki/concepts/docker-deploy.md.
|
||
|
||
## ⚠️ Критично перед билдом — форс-регенерация клиента
|
||
deps подняты: `@snollajs/snolla ^0.18`, `content-api ^0.13`, `content-api-client ^0.10`, `@snolla/site-schema-gen ^0.6`. Сгенерённый клиент `apps/web/src/generated/` получил НОВЫЕ методы `getStaticPage` / `getRobotsTxt`. `src/generated/` **gitignored** → его нет в Gitea, в build-контексте надо сгенерить заново именно schema-gen@0.6.0.
|
||
- **НЕ полагаться на ensure-schema skip-if-present:** если в рабочей папке остался `src/generated/` со старого деплоя (без новых методов) — postinstall его пропустит, соберётся СТАРЫЙ клиент, и server-код (`server/middleware/snolla-static-pages.ts`, `server/routes/robots.txt.get.ts`) упадёт на отсутствующих методах.
|
||
- Порядок: обновить master до `40bb383` → `yarn install` (тянет schema-gen 0.6 из verdaccio) → **`rm -rf apps/web/src/generated` → `yarn workspace nuxt-app schema-gen`** (нужен egress в MSSQL на build-prep) → docker build (context = корень монорепы, `-f apps/web/Dockerfile`).
|
||
|
||
## Acceptance (smoke на проде после переката)
|
||
- `GET /robots.txt` → 200 `text/plain` из БД (полный robots с Yandex-секцией + Sitemap), НЕ старый allow-all плейсхолдер.
|
||
- `GET /yandex_a4e129ae82fc9db5.html` → 200 `text/html` (Yandex-верификация).
|
||
- `GET /googlec155573e2673fe09.html` → 200.
|
||
- `GET /nope-not-a-page.html` → 404 (Nuxt fall-through, не 500).
|
||
- `/` + `/catalog` → 200 (без регресса).
|
||
- Убедиться, что `/robots.txt` не перехватывается traefik/статикой поверх Nitro (раньше был `public/robots.txt` — удалён в этом релизе).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `using-vds-ops` — smoke контейнера/логов + HTTP-проверка прод-URL после переката
|
||
- invoke `using-projects-meta` — cross-project lifecycle/notify
|
||
|
||
**TDD:** нет — ops/deploy, кода не пишем.
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human (deploy-инфра: docker/traefik/Portainer)
|
||
**notify:** victor/pilorama98.ru
|
||
|
||
**Status:** done
|
||
**Where I stopped:** деплой завершён — контейнер `pilonuxt` на `registry.kzntsv.site/pilonuxt:40bb383`, running; acceptance 6/6 verified.
|
||
**Next action:** — (closed)
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-29T08:52:31.094Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-29 / 40bb383 LIVE стек 16, smoke acceptance 6/6 (robots+staticPages из БД verified телами), notify sent -->
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## 🟢 [deploy-pilonuxt-forms-api-drop-snolla] — closed 2026-06-29 — `cf2bba2` LIVE (стек 16). ADR-0010: forms на `@snollajs/forms-api`, `@snollajs/snolla` дропнут целиком, core@0.1.1. Пайплайн: master@cf2bba2 → yarn install → форс `rm src/generated`+schema-gen → docker build 424МБ → save|ssh load + push с VDS (push с дома 499 как всегда) → Portainer PUT стек 16. **Tree-check `.output/server/node_modules` 4/4** (грабля #2): 0× snolla, 0× btoa, ровно 1× data@0.9.1, core 0.1.1 + content-api 0.14.0 + forms-api 0.1.0. **Smoke acceptance зелёный:** `/`+`/catalog`→200 (SSR жив, btoa/snolla-дроп бандл не сломал); `/robots.txt`+`/yandex_*.html`→200 (ADR-0009 не регресс); форма `POST /snolla-forms/forms?path=/checkout` пустой → **422 JSON** (`is_valid:false`, name/phone/email errors), `?path=/nope`→404 (valid НЕ слал — реального письма нет); stderr чист (нет btoa/snolla/MODULE_NOT_FOUND). Гоча smoke: 422 только с `Accept: application/json` (иначе `prefersJson` false → ветка `status||404` отдаёт 404 — не баг). Notify victor/pilorama98.ru (монореп-inbox) отправлен.
|
||
|
||
<details><summary>исходный наряд</summary>
|
||
|
||
## (orig) Деплой pilonuxt master (commit `cf2bba2`) на прод VDS. Релиз: forms на @snollajs/forms-api + ПОЛНЫЙ дроп @snollajs/snolla из pilonuxt (ADR-0010). content-api 0.14 (без snolla-peer) + новый @snollajs/core@0.1.1. Build из Gitea master → push registry → перекат Portainer-стека 16. Детали: apps/web/.wiki/concepts/{adr-0010-forms-api-consumer-drop-snolla,docker-deploy}.md.
|
||
|
||
## ⚠️ Критично #1 — форс-регенерация клиента (как в прошлый деплой)
|
||
`apps/web/src/generated/` gitignored. Порядок: master→`cf2bba2` → `yarn install` → **`rm -rf apps/web/src/generated` → `yarn workspace nuxt-app schema-gen`** (egress в MSSQL) → docker build. Иначе соберётся stale-клиент.
|
||
|
||
## ⚠️ Критично #2 — проверка дерева на prod `.output` ПЕРЕД перекатом
|
||
Это главный риск релиза (вся сага content-api↔snolla про dual-instance; dev маскирует unmet-deps). После `yarn install`/в собранном `.output/server/node_modules` проверить:
|
||
- **0× `@snollajs/snolla`** (полностью удалён; `yarn why @snollajs/snolla` пусто). Если транзитив притянул назад — СТОП, пинг victor/pilorama98.ru.
|
||
- **0× `btoa`** (был баг core@0.1.0, фикс в 0.1.1 — Buffer вместо btoa).
|
||
- **ровно 1× `@snollajs/data@0.9.1`** (НЕ две копии — иначе getSiteById падает, см. сагу).
|
||
- `@snollajs/core@0.1.1`, `@snollajs/content-api@0.14`, `@snollajs/forms-api@0.1.0` присутствуют.
|
||
Если что-то не так на `.output` — НЕ катить, написать victor/pilorama98.ru (snolla на standby, разберём).
|
||
|
||
## Acceptance (smoke на проде после переката)
|
||
- `GET /` + `/catalog` → 200 (SSR жив — это и есть проверка, что btoa/snolla-дроп не сломал бандл).
|
||
- `GET /robots.txt` → 200 из БД, `/yandex_a4e129ae82fc9db5.html` → 200 (ADR-0009 не регрессировал).
|
||
- **Форма (НЕ слать valid на боевую!):** `POST /snolla-forms/forms?path=/checkout` пустой → 422 JSON `{is_valid:false,errors:[...]}`; `?path=/nope` → 404. Этого достаточно — valid-сабмит НЕ слать (уйдёт реальное письмо менеджеру + запись). Контракт valid уже проверен на dev (#2427 на тест-форме).
|
||
- Контейнер running, stderr без btoa/snolla/MODULE_NOT_FOUND.
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `project-discipline` — дисциплина коммитов/пушей
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `using-vds-ops` — smoke контейнера/логов + HTTP прод-URL после переката
|
||
- invoke `using-projects-meta` — cross-project lifecycle/notify
|
||
|
||
**TDD:** нет — ops/deploy, кода не пишем.
|
||
**Разрешения:** интерны: нет | автопуш: да
|
||
**weight:** needs-human (deploy-инфра: docker/traefik/Portainer)
|
||
**notify:** victor/pilorama98.ru
|
||
|
||
**Status:** done
|
||
**Where I stopped:** деплой завершён — `pilonuxt` на `registry.kzntsv.site/pilonuxt:cf2bba2` running; tree-check 4/4 + smoke acceptance зелёный.
|
||
**Next action:** — (closed)
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-29T19:50:46.958Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-29 / cf2bba2 LIVE стек 16, tree-check 4/4 + smoke acceptance (форма 422/404, SSR 200, robots/yandex из БД), notify sent (монореп-inbox) -->
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## 🟢 [pilonuxt-deploy-sitemap] — [weight: needs-human — deploy-infra] Деплой pilonuxt с sitemap.xml через content-api. Собрать образ из Gitea master victor/pilorama98.ru @ 42f9d64 (feat: sitemap via content-api proxy, content-api@0.15.0), запушить в registry.kzntsv.site/pilonuxt:<sha>, перекатить Portainer-стек 16 за Traefik. Грабли билда (как в прошлых pilonuxt-деплоях): форс `rm apps/web/src/generated && yarn workspace nuxt-app schema-gen` перед билдом; tree-check `.output` — 0× snolla, 0× btoa, ровно 1× @snollajs/data@0.9.1, и content-api теперь 0.15.0 (+ @snollajs/core@0.2.0). DB-миграция НЕ нужна — sitemap-колонки на проде уже есть (snolla live-проверил на прод-MSSQL). Локально verified против прод-MSSQL.
|
||
|
||
**Status:** 🟢 closed 2026-06-29 — `pilonuxt:42f9d64` LIVE на проде (Portainer стек 16).
|
||
**Where I stopped:** Собран из Gitea master `victor/pilorama98.ru @ 42f9d64` (контекст=корень монорепы, `-f apps/web/Dockerfile`, verdaccio build-secret). Форс-регенерация `apps/web/src/generated` против прод-MSSQL (schema-gen ok). **Tree-check `.output/server/node_modules` 5/5:** content-api **0.15.0**, core **0.2.0**, data **0.9.1** (ровно 1×), forms-api 0.1.0; snolla-пакетов 0 (7 grep-хитов = только комменты-провенансы, 0 живых import), btoa 0 пакетов/0 импортов. Push в registry **напрямую** (499 не случился, digest `sha256:d937c6b3…`). Стек 16 перекатан PUT API (`cf2bba2`→`42f9d64`, pullImage:true), контейнер running, Nitro Listening, stderr чист (ECONNRESET нет). DB-миграция не требовалась.
|
||
**Acceptance на проде (curl --resolve мимо LAN-DNS, 89.253.255.94) — 3/3 + регресс:** (1) `/sitemap.xml` → 200 application/xml, `<sitemapindex>`, 6 источников ✓; (2) `/sitemap-pages-1.xml` → 200 application/xml, `<urlset>`, 70 url, все 70 абсолютные `https://pilorama98.ru/` ✓; (3) `/sitemap-store-products-999.xml` → 404 ✓. Регресс цел: `/robots.txt` 200 (Sitemap-директива на месте), `/`+`/catalog` 200. Откат при нужде: стек 16 → `pilonuxt:cf2bba2`. Ack отправлен в `victor/pilorama98.ru` inbox.
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru ✅ ack sent
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-29T20:46:40.071Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-29 / deploy 42f9d64 LIVE + smoke 3/3 -->
|
||
|
||
---
|
||
|
||
## 🟢 [pilonuxt-deploy-sitemap-taxonomy] — [weight: needs-human — deploy-infra] Деплой pilonuxt: sitemap taxonomy (категории/теги/vendors). Bump-only поверх уже-LIVE sitemap v1 — `@snollajs/content-api` 0.15.0→0.16.0 (несёт `@snollajs/core@0.4.0`, taxonomy enumeration), код прокси не менялся (generic-шим). Собрать из Gitea master victor/pilorama98.ru @ 6b9ae63, push в registry.kzntsv.site/pilonuxt:<sha>, перекат Portainer-стека 16. Грабли билда как обычно: `rm apps/web/src/generated && yarn workspace nuxt-app schema-gen`; tree-check `.output` — 0× snolla, 0× btoa, ровно 1× @snollajs/data@0.9.1, content-api теперь 0.16.0 + core 0.4.0. DB-миграция НЕ нужна. Локально verified против прод-MSSQL.
|
||
|
||
**Status:** 🟢 closed 2026-06-29 — `pilonuxt:6b9ae63` LIVE на проде (Portainer стек 16).
|
||
**Where I stopped:** Bump-only поверх sitemap v1. Собран из Gitea master `6b9ae63` (форс schema-gen против прод-MSSQL). **Tree-check `.output` 5/5:** content-api **0.16.0**, core **0.4.0**, data **0.9.1** (1×), snolla 0 пакетов/0 import, btoa 0. Push: словил **499 на .output-слое** (документированная грабля) → ретрай прошёл, digest `sha256:226584ce…`. Стек 16 перекатан PUT (`42f9d64`→`6b9ae63`, pullImage:true), running, Nitro Listening, stderr чист.
|
||
**Acceptance на проде (curl --resolve мимо LAN-DNS, 89.253.255.94) — 4/4 + регресс:** (1) `/sitemap.xml` → 200, индекс 7 записей, есть `sitemap-store-taxonomy-1.xml` ✓; (2) `/sitemap-store-taxonomy-1.xml` → 200 application/xml, `<urlset>`, 57 url все абсолютные (35 `/shop/categories/` + 22 `/shop/tags/`; vendors 0 — пусто в CMS, как в NB) ✓; (3) v1 цел: `/sitemap-store-products-1.xml` 200, `/sitemap-pages-1.xml` 200 ✓; (4) `/sitemap-store-taxonomy-999.xml` → 404 ✓. Регресс: `/`+`/catalog` 200. Откат при нужде: стек 16 → `pilonuxt:42f9d64`. Ack отправлен в `victor/pilorama98.ru` inbox.
|
||
**Branch:** n/a
|
||
**Notify:** victor/pilorama98.ru ✅ ack sent
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/pilorama98.ru / 2026-06-29T21:06:37.502Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-06-29 / deploy 6b9ae63 LIVE + smoke 4/4 -->
|
||
|
||
---
|
||
|
||
## 🟢 [labtools-web-vds-deploy] — Деплой `labtools.ru` snolla-приложения (`victor/labtools.ru/apps/web`) в docker-контейнере на Rusonyx VDS за traefik. Приложение parity-подтверждено (ре-ревью №2 PASS: HTML+визуал byte-parity с боем `www.labtools.ru`, ассеты из MinIO, ноль локальных хаков, `@snollajs/snolla@0.28.2`).
|
||
|
||
## Кто/где собирает образ (решение workshop)
|
||
**Образ собирает АДМИН на VDS** из `Dockerfile`, который пишет имплементер. build нужен docker + доступ к verdaccio (`verdaccio.kzntsv.site`) + `VERDACCIO_TOKEN` — эта инфра у админа/на VDS; имплементер владеет корректностью Dockerfile, не реестром. Кросс-машинный трансфер образа не нужен.
|
||
|
||
## Разделение обязанностей (code-ownership vs ops-ownership)
|
||
**Имплементер (`victor/labtools.ru`, отдельная сессия — код-артефакты в репо `deploy/`):**
|
||
- `deploy/Dockerfile` (multi-stage: node-build с `--build-arg VERDACCIO_TOKEN` → `yarn install` @snollajs/* из verdaccio → slim runtime, `node server.js`), `.dockerignore`, `PORT`-env, healthcheck.
|
||
- **Config/секреты — через env/mounted, НЕ запекать в образ** (config `default.json` gitignored). Отдать админу **список требуемых runtime-env**: DB (`mssql.kzntsv.site` MoreThenCms creds), S3/MinIO (`minio.kzntsv.site` keys), `siteId=D375C419-D787-4FCC-8934-A3B9DC951006`, imgproxy key/salt, verdaccio (только build-time).
|
||
- Отчёт workshop: Dockerfile готов + build-args + полный env-лист.
|
||
|
||
**Админ (эта таска — ops-acceptance на VDS):**
|
||
- Собрать prod-образ на VDS из Dockerfile имплементера (VERDACCIO_TOKEN из secret-store).
|
||
- Docker stack/compose + **traefik labels** (Host-rule, TLS/letsencrypt, service-port).
|
||
- **Runtime-секреты на VDS** как env (DB/S3/siteId/imgproxy) — не в образе, не в git.
|
||
- **DNS/cutover:** сперва деплой на **staging-host** (напр. `labtools.vds.kzntsv.site`) за traefik → smoke parity на задеплоенном URL vs бой. **Cutover живого `labtools.ru` DNS = ОТДЕЛЬНЫЙ gated шаг, подтвердить с оператором** (прод-переключение, план отката) — бой сейчас живой на RUVDS IIS.
|
||
- Внешний smoke: главная styled, каталог+порядок, редиректы, canonical, ассеты из MinIO — parity с боем.
|
||
- **Вики-рунбук** (invoke `using-wiki`): stack, traefik, env-контракт, образ/тег, cutover + rollback.
|
||
|
||
Оба — **репорт в inbox workshop** (from labtools / from admin) по ходу.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-vds-ops` (диагностика VDS-контейнеров) · invoke `using-wiki` (рунбук после деплоя) · invoke `project-discipline`
|
||
|
||
**TDD:** n/a (ops-деплой; приёмка = внешний smoke parity vs бой).
|
||
**Разрешения:** интерны: нет | автопуш: админ по своей дисциплине.
|
||
**weight:** needs-human
|
||
**notify:** OpeItcLoc03/workshop
|
||
Dev-source: `victor/labtools.ru/apps/web` (parity-док/буфер — в `.workshop/.brainstorm/snolla-sites-migration.md`). Blast: cutover затронет живой прод labtools.ru — staging-first, cutover gated.
|
||
|
||
**Status:** 🟢 CLOSED 2026-07-02 — cutover LIVE. `labtools.ru`+`www` на VDS (89.253.255.94), стек 17, образ `labtools:43e28ba`. Рунбук § Cutover обновлён.
|
||
**Closure:** DNS зона на **Yandex DNS** (не reg.ru). Флип шёл неравномерно — dns1/dns2 расходились; cutover-гейт = ОБА авторитетных Yandex-NS согласованно → VDS по apex+www (монитор дождался). Стек 17 traefik-rule → `Host(labtools.ru)||Host(www.labtools.ru)` (Portainer PUT, env 8/8, pullImage=false); LE-cert выпущен (~t+20s). Live-smoke GREEN: оба хоста 200 (nav+каталог-секции+продукты), редиректы (`/about/` trailing, `/Contacts` lowercase, `/index.php`→`/`) parity, sitemap index (host=labtools.ru non-www — siteUrl тенанта без www), theme-ассеты byte-identical (RUVDS 301 apex→www → прямой md5 артефакт, с -L MD5-OK). staging-host убран (→404). Внешне: apex→VDS 200; www ещё split-brain на кэше RUVDS (200, идентичный контент, тухнет по TTL 6ч). RUVDS = rollback, не тронут. Репорт workshop отправлен. **Весь тираж закрыт: labtools.ru + labtools.pro LIVE на VDS.**
|
||
**Next action:** — (закрыто). Опционально позже по решению оператора: снять RUVDS-биндинги labtools.ru/www (как для emspb) — сейчас оставлены как rollback.
|
||
**Branch:** master
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-01T13:02:23.634Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [emspb-web-vds-deploy] — closed 2026-07-02 — staging GREEN + cutover LIVE. Образ `registry.kzntsv.site/emspb:b6e361a` (digest `8f5ba02…`) собран на VDS, Portainer stack `emspb` (Id 18). Staging smoke vs бой 23/23 status-parity, theme-CSS+image из MinIO md5-identical, Cache-Control 86400. **Cutover выполнен в ту же сессию** (оператор флипнул DNS emspb.ru/www→VDS): traefik-rule `Host(emspb.ru)||Host(www.emspb.ru)` (staging-хост убран post-cutover→404), единый LE-cert, live-smoke `www.emspb.ru`+`emspb.ru`→200 `ssl_verify=0`, canonical нормализован, live==старый RUVDS байт-в-байт. RUVDS IIS `80.64.31.36` цел = rollback. Рунбук [emspb-vds-deploy-runbook](../.wiki/concepts/emspb-vds-deploy-runbook.md). Notify→workshop.
|
||
|
||
<!-- HISTORICAL task spec preserved below -->
|
||
## ⚪ [emspb-web-vds-deploy] — Деплой `victor/emspb.ru` `apps/web` (snolla-приложение, БД-контент+SSR Liquid) на Rusonyx VDS в docker за traefik, **staging-first**. Зеркало `[labtools-web-vds-deploy]` — тот же паттерн, адаптировать хосты/siteId. Код готов (ре-ревью PASS, ноль хаков, 29/29 parity). `deploy/` артефакты уже в репо.
|
||
|
||
## Источник
|
||
`victor/emspb.ru` — `deploy/Dockerfile` (multi-stage, build-arg `VERDACCIO_TOKEN` build-time, node:22-slim non-root, healthcheck) + `deploy/README.md` (env-лист, все секреты через env, ничего не бейкается) + корневой `.dockerignore`. Референс-рунбук: admin-вики `labtools-vds-deploy-runbook`.
|
||
|
||
## Split (как labtools — dev даёт Dockerfile, ops собирает и катит)
|
||
1. **Образ собирает АДМИН на VDS** из emspb `deploy/Dockerfile` (docker+verdaccio+token на VDS), из **чистого git-archive** (config/default.json отсутствует ✅ — секретов в образе нет). Тег `registry.kzntsv.site/emspb:<sha>`.
|
||
2. **Portainer stack** на STAGING `emspb.vds.kzntsv.site` за traefik (Host-rule + LE cert + порт).
|
||
3. **Runtime-env-секреты в stack-env (НЕ в образ):** DB `mssql.kzntsv.site`/`MoreThenCms`, S3/MinIO, `siteId=96EBC481-D26A-47BE-B660-13D49E7D0A61`, imgproxy HMAC (кросс-проверить как labtools).
|
||
4. **Smoke parity vs бой `www.emspb.ru`:** страницы 200 (main/contacts/portfolio/prices + service-выборка), canonical 301 (trailing/lowercase), theme-ассеты из MinIO byte-identical, Cache-Control как бой.
|
||
|
||
## НЕ делать
|
||
- **Cutover live `emspb.ru` DNS — НЕ трогать.** Отдельный gated-шаг позже по слову оператора (бой на текущем хостинге живой; rollback = DNS назад). Эта таска = только staging + smoke.
|
||
- Секреты в образ/git — нет.
|
||
|
||
## Ops-acceptance
|
||
Внешний smoke parity vs бой `www.emspb.ru` на задеплоенном staging-URL `emspb.vds.kzntsv.site` = GREEN. Рунбук в admin-вике `emspb-vds-deploy-runbook` (зеркало labtools).
|
||
|
||
## Обязательные скилы — вызвать до начала работы
|
||
- invoke `using-tasks` — статус задачи
|
||
- invoke `project-discipline` — дисциплина
|
||
- invoke `using-vds-ops` — диагностика контейнеров на VDS при необходимости
|
||
- invoke `using-wiki` — записать рунбук `emspb-vds-deploy-runbook` в admin-вику
|
||
|
||
**TDD:** n/a (ops-деплой).
|
||
**Разрешения:** ops-задача админ-сессии.
|
||
**weight:** needs-human
|
||
**notify:** OpeItcLoc03/workshop
|
||
|
||
**Status:** 🟢 closed 2026-07-02 — staging + cutover оба GREEN, emspb.ru LIVE на VDS.
|
||
**Where I stopped:** (done) Образ b6e361a собран+запушен на VDS, stack Id 18, staging smoke 23/23, cutover выполнен (traefik-rule на боевые хосты, LE-cert, live-smoke 200/valid, паритет vs RUVDS байт-в-байт). RUVDS rollback цел.
|
||
**Next action (было):** Собрать образ на VDS из victor/emspb.ru deploy/Dockerfile (чистый git-archive, секретов нет) → registry.kzntsv.site/emspb:<sha> → Portainer stack STAGING emspb.vds.kzntsv.site за traefik+LE → runtime-env-секреты → smoke parity vs бой www.emspb.ru. Рунбук в admin-вику. Cutover live DNS — НЕ трогать (отдельный gated-шаг).
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-01T21:19:42.728Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [labtools-pro-web-vds-deploy] — Вынести `victor/labtools.pro` (snolla-app, `@snollajs/snolla@0.28.7`, server-side Liquid, БД-контент + MinIO-ассеты) с RUVDS IIS в docker-контейнер на VDS kzntsv (89.253.255.94) за traefik. **Зеркало `[emspb-web-vds-deploy]`** — тот же паттерн; рунбуки `emspb-vds-deploy-runbook` / `labtools-vds-deploy-runbook` (.admin вика) читать как шаблон. Код-DONE: ре-ревью PASS (все 9 дименшнов, ноль хаков), независимый ревьюер на живой БД+MinIO.
|
||
|
||
**Weight:** needs-human
|
||
**Notify:** OpeItcLoc03/workshop
|
||
|
||
## Артефакты (labtools.pro-специфика — отличия от emspb выделены)
|
||
- **Код:** `victor/labtools.pro` @ **`7bd9fae`** (apps/web, PASS, snolla **0.28.7** final). `deploy/Dockerfile` (multi-stage node:22-slim non-root, healthcheck `/robots.txt`) + `deploy/README.md` (env-лист).
|
||
- **Образ:** ⚠️ **`registry.kzntsv.site/labtools-pro:7bd9fae`** (+`:latest`) — имя **`labtools-pro`**, НЕ `labtools` (не коллизить с образом labtools.ru!). Собрать НА VDS из чистого `git archive 7bd9fae` (обход traefik-499; guard: `config/default.json` ABSENT в архиве, `.dockerignore`). build-arg `VERDACCIO_TOKEN` (build-time only, не в финальный образ).
|
||
- **siteId** `663F9410-A6CC-4651-9A5C-62844A313957`, activeTheme `389AD745-E3EE-4F23-BE16-DF38440A0941` (theme-store MinIO `themes/389ad745e3ee4f23be16df38440a0941/`). Non-secret → в `production.json`, НЕ env.
|
||
- **siteUrl** `https://www.labtools.pro` — уже в `production.json` (R3-фикс: sitemap/robots отдают www). Non-secret.
|
||
|
||
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
|
||
**Значения идентичны labtools/emspb** (все snolla-тенанты = одна MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). **Переиспользовать env-массив verbatim из labtools stack (Id 17)** через Portainer API (как для emspb stack 18). Маппинг env→config: `apps/web/config/custom-environment-variables.json`. 8 env: DB_USER/DB_PASSWORD, IMGPROXY_KEY/IMGPROXY_SALT, S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY, SMTP_USER/SMTP_PASSWORD. PORT не переопределять (образ 5000; traefik service-port 5000).
|
||
|
||
## Стек + staging
|
||
Portainer stack **`labtools-pro`** (новый), endpoint 1, source-compose `admin/host-stacks/vds-kzntsv/labtools-pro.compose.yml`. Staging-хост **`labtools-pro.vds.kzntsv.site`** за traefik+LE. Egress: mssql.kzntsv.site:1433, minio.kzntsv.site:443, imgproxy.kzntsv.site:443, smtp.yandex.ru:465.
|
||
|
||
## Staging smoke — parity vs бой `www.labtools.pro` (RUVDS, через `--resolve`)
|
||
Acceptance = внешний smoke на задеплоенном URL:
|
||
- **Каталог!** (в отличие от контент-only emspb): nav (`/ /about /contacts`) + 3 каталог-секции (`/products/laboratory-presses`, `/products/press-forms`, `/products/milling-accessories`) + продукты (`/products/laboratory-presses/plg-20`, press-form) — статус 200/200 vs бой.
|
||
- **redirect#1:** `/products/laboratory-ball-mills` → 301 → `…/lshm-750`. seoCanonical: `/Contacts`→301 lowercase, `/contacts/`→301 trailing.
|
||
- **theme-ассеты из MinIO md5-identical** бою (theme.css, logo.png, шрифты).
|
||
- ⚠️ **Cache-Control: НЕТ заголовка на theme css/js/images** (labtools.pro `CachingOptions=[]` → empty-config, движок 0.28.7 не ставит заголовок = бой). **НЕ флагай отсутствие Cache-Control как дефект** — для этого тенанта это КОРРЕКТНО (в отличие от emspb/labtools, где был max-age=86400).
|
||
- sitemap `/sitemap.xml` = 25 URL, `<loc>` host = **www** (R3).
|
||
|
||
## Cutover (live DNS) — ОТДЕЛЬНЫЙ gated-шаг по слову оператора
|
||
- Бой live на RUVDS IIS (rollback-цель; админ подтверждает IP — вероятно тот же `80.64.31.36`, что emspb, но проверить). **НЕ выводить RUVDS до подтверждённого cutover.**
|
||
- ⚠️ **НЕ путать с labtools.ru** — он ТОЖЕ ждёт DNS-cutover на RUVDS; флипается отдельно. Проверять внешним резолвером, что флипнут именно labtools.pro.
|
||
- Порядок (как emspb): оператор флипает DNS `labtools.pro`+`www.labtools.pro`→`89.253.255.94` → ТОЛЬКО ПОСЛЕ этого ops добавляет `Host(labtools.pro)||Host(www.labtools.pro)` в traefik-rule (иначе LE HTTP-01 упадёт на RUVDS → сожжём rate-limit) → live-smoke.
|
||
- **НЕ флипать DNS самим.**
|
||
|
||
## Скилы / отчёт
|
||
invoke `using-vds-ops`/`using-tasks`/`project-discipline`. Написать рунбук `labtools.pro-vds-deploy-runbook` в .admin вике (зеркало emspb). Репорт → OpeItcLoc03/workshop. Ops-acceptance = staging smoke-parity green; cutover — отдельным словом оператора.
|
||
|
||
**Status:** 🟢 CLOSED 2026-07-02 — cutover LIVE. `labtools.pro`+`www` на VDS (89.253.255.94), стек 19, образ `labtools-pro:7bd9fae`. Runbook: `.wiki/concepts/labtools.pro-vds-deploy-runbook.md`.
|
||
**Closure:** DNS флип подтверждён авторитетным `ns1.reg.ru` (Resolve-DnsName) + 8.8.8.8 → 89.253.255.94 (именно `.pro`; ранние проверки ловили старый TTL-кэш). Стек 19 traefik-rule → `Host(labtools.pro)||Host(www.labtools.pro)` (Portainer PUT, env 8/8 сохранён, pullImage=false); LE-cert выпущен мгновенно (ssl_verify=0). Live-smoke GREEN: оба хоста 200 (nav+каталог+продукты), редиректы parity, sitemap index host=www, контент реальный (lang=en). staging-хост убран (→404). Внешняя проверка с RUVDS-хоста: labtools.pro/www→89.253.255.94 HTTP 200 cert-trusted. RUVDS labtools.pro = rollback, жив, не тронут. Репорт workshop отправлен.
|
||
**Where I stopped:** Образ `registry.kzntsv.site/labtools-pro:7bd9fae` (+latest, digest `sha256:23d0bc59…`) собран+запушен НА VDS (guard default.json absent OK). Portainer stack `labtools-pro` **Id 19** создан (env verbatim из stack 17, 8 секретов), контейнер healthy, MSSQL+S3 подключены, лог чист. Staging `labtools-pro.vds.kzntsv.site` LE-cert выпущен. **Smoke-parity vs бой `www.labtools.pro` GREEN** (гнал с VDS): статусы nav+3 каталог-секции+продукты 200/200; редиректы (каталог#1→lshm-750, /Contacts→lowercase, /contacts/→trailing) 301 parity; sitemap = index из 7 дочерних, в сумме 25 URL набор==бой, host=www (R3); theme-ассеты 7/7 md5-identical; Cache-Control absent (корректно, `CachingOptions=[]`); контент-страницы реальное тело (delta +155…+222B = host-длина). Косметика (не дефект, отдана workshop): стартовый баннер лога = `labtools.ru (snolla)` (константа из скаффолда). Compose: `host-stacks/vds-kzntsv/labtools-pro.compose.yml`. Репорт workshop отправлен.
|
||
**Next action:** — (закрыто). Follow-up (не блокер, у workshop): косметика баннера `labtools.ru` в логе (константа app-name из скаффолда). Опционально позже: снять RUVDS-биндинги labtools.pro (как сделано для emspb) — по решению оператора, сейчас оставлены как rollback.
|
||
**Branch:** master
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-02T08:27:39.437Z -->
|
||
|
||
---
|
||
|
||
## 🟢 [imgproxy-watermark-glyph-books-vds] — closed 2026-07-04 — глиф LIVE на imgproxy стек 29 (books-vds). Первый base64 (в inbox И в таске) был битый на источнике (sha mismatch, IEND нет, зацикленный хвост — `tasks_create` порезал поле); поймал sha-сверкой ДО прода. Воркшоп до-доставил `logo.png` бинарём в git (`host-stacks/books-vds/tandemmebel-watermark-logo.png`, commit `7127e92a`, sha256 `e995…971fc`). PUT стека 29 (Portainer `portainer.kzntsv.site` ep1, X-API-Key, `IMGPROXY_WATERMARK_DATA` inline env, `PullImage:false`, БЕЗ глобального `_OPACITY`). Smoke cache-busted: A tandem 200/27294→27820B вотермарк ВИДЕН глазами; B pilorama контроль byte-identical 160010B (opacity не просочилась). Откат: PUT бэкапа стека без WATERMARK_DATA. Notify workshop отправлен → tandem-агент на ре-скрин→пин→deploy.
|
||
|
||
<!-- HISTORICAL turnkey preserved below (base64 в блоке БИТЫЙ — источник истины = git-бинарь 7127e92a) -->
|
||
|
||
## ⚪ [imgproxy-watermark-glyph-books-vds] — Ops-handoff от workshop (dev-source: victor/tandemmebel.ru pin 7e08bee; snolla core@0.13.8 commit 2ba8b54). Разблокирует финальный визуал-гейт tandemmebel post-featured watermark parity → потом deploy.
|
||
|
||
ЗАДАЧА: на **books-vds (89.253.255.133)** imgproxy **стек 29** выставить env `IMGPROXY_WATERMARK_DATA` = base64 глифа logo.png + рестарт imgproxy (Portainer стек 29 → env → redeploy, либо .env стека + docker compose up -d).
|
||
|
||
**Глиф:** 348×42 RGBA брендовый вордмарк «ТАНДЕМ МЕБЕЛЬ», sha256 `e995624735d709dbf5d0b5b5f1a46653e595a20c93270192d4b38e98207971fc`.
|
||
|
||
`IMGPROXY_WATERMARK_DATA` value (base64, одной строкой):
|
||
```
|
||
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MfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBcFQtAlGLzMfBc
|
||
```
|
||
|
||
**Проверка:** `echo '<base64>' | base64 -d | sha256sum` → должно дать `e995624735d709dbf5d0b5b5f1a46653e595a20c93270192d4b38e98207971fc`.
|
||
|
||
⚠️ **КРИТИЧНО: НЕ выставлять** глобальный `IMGPROXY_WATERMARK_OPACITY` — иначе зальёт вотермарком ВСЕ тенанты (emspb/labtools/labtools.pro/pilorama — они ДОЛЖНЫ остаться чистыми). Глиф применяется ТОЛЬКО по per-URL `wm`-опции, которую snolla шлёт лишь для tandem post-featured. Ключ именно `IMGPROXY_WATERMARK_DATA` (base64), НЕ `_BASE64`.
|
||
|
||
Рестарт imgproxy = короткий блип отдачи картинок всем сайтам (кэш-замаскирован nginx) — ратифицировано оператором.
|
||
|
||
**SMOKE после рестарта** (должен вернуть 200 image/webp С вотермарком по центру):
|
||
```
|
||
curl -sI 'https://imgproxy.kzntsv.site/nlXN7RMrer11Fe9XPQaXmq4H0ufzPpo2fOzBWtaL0po/rs:fill:1170:557:1/g:ce/wm:0.5:ce:0:0:0/czM6Ly9nYWxsZXJpZXMvNzgwODA3MDdmNmUwNDMzMGJhMzA3OTIyMzU0YzJjZWYvYWI0YmM0NDgwZjJkNDc4ZTgzYTQwOTc5NTI0NDA0NWEuSlBH.webp'
|
||
```
|
||
(до глифа тот же URL = 200 но чистый; после = с вотермарком.)
|
||
|
||
По готовности notify workshop → tandem-агент гоняет Playwright ре-скрин featured → пин → deploy. Отдельная deploy-таска `[tandemmebel-web-vds-deploy]` последует (в её production.json override `imgproxy.watermark={size:"post-featured",option:"wm:0.5:ce:0:0:0"}`).
|
||
|
||
**Status:** 🟢 closed 2026-07-04
|
||
**Where I stopped:** глиф LIVE на imgproxy стек 29, smoke green (A вотермарк виден / B контроль чист), notify workshop отправлен.
|
||
**Next action:** — (закрыто). Дальше по тиражу tandemmebel: ждать `[tandemmebel-web-vds-deploy]` от воркшопа, взять ops-часть.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T04:30:04.466Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-07-04 / sha-gate поймал битый base64, git-бинарь 7127e92a раскатан, smoke green, opacity не просочилась -->
|
||
|
||
---
|
||
|
||
## 🟡 [tandemmebel-web-vds-deploy] — STAGING GREEN, ждёт cutover (DNS gated оператором). **⚠️ ОБРАЗ К ФЛИПУ ОБНОВЛЁН → `tandemmebel:ed96b18` (snolla 0.42.0/core 0.24.0/data 0.14.1, digest `ca4da79`), СУПЕРСИДИТ b02ca18/0facb35** — раскатан на стек 20 задачей [tandemmebel-deploy-snolla-0-42-0] (2026-07-04, DoD 172/172 sitemap + 12/12 gallery-grid GREEN). Cutover = тот же DNS-gated шаг: владелец флипает reg.ru `tandemmebel.ru`+`www`→89.253.255.94 → verify авторит.NS (`Resolve-DnsName -Server ns1.reg.ru`) → ТОЛЬКО ПОТОМ боевой `Host()` в стек 20 (порядок критичен: LE HTTP-01) → live-smoke. RUVDS 80.64.31.36 = rollback (revert DNS), не тронут. Sharp media in-process (imgproxy из tandem-пути убран — прошлые imgproxy-заметки ниже historical). Историческая спека (0facb35/imgproxy-watermark) ниже — HISTORICAL, не актуальна. Smoke с VDS: status-parity 10/10, sitemap staging⊇prod (prod-only=0, +12 /projects/categories/* benign#3), trailing 301/301, blog-post 200 (+3.5KB=benign#2 imgproxy vs /galleries/), 2012-посты 31/31, theme 21/22 MD5-OK. Находка (не блокер): prod Gotham-Pro.css=0B (пустой), staging=4436B корректный → cutover чинит латентный прод-баг шрифта. Следующее: отмашка оператора на DNS → verify авторит.NS → боевой Host-rule в стек 20.
|
||
|
||
<!-- HISTORICAL spec preserved below -->
|
||
|
||
## ⚪ [tandemmebel-web-vds-deploy] — Вынос tandemmebel.ru (snolla-app, `@snollajs/snolla` **0.32.2** / `@snollajs/core` **0.13.8**, server-side Liquid, контент+блог-портфолио+category, БЕЗ e-commerce каталога) с RUVDS IIS на VDS kzntsv (89.253.255.94) за traefik. **Зеркало рунбука `labtools.pro-vds-deploy-runbook`** (модель snolla-тенанта: БД MoreThenCms `mssql.kzntsv.site` + ассеты MinIO через imgproxy). Dev + ДВА полных независимых ре-ревью-раунда + focus watermark-раунд ЗАВЕРШЕНЫ, независимый **PASS**. Это ops-handoff.
|
||
|
||
### Артефакты
|
||
- **Код:** `victor/tandemmebel.ru` @ `0facb35afde2a966add75e0d603dcf8f16a868af` (master, apps/web). Раунд-2 re-review PASS (byte-parity 9/10, секреты, тесты 22/22, ноль хаков) + focus watermark-раунд PASS (featured-вотермарк=бой, мелкие чисты, cross-tenant чист). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck).
|
||
- **Образ:** `registry.kzntsv.site/tandemmebel:0facb35` (+`:latest`), собрать **НА VDS** (обход traefik-499). Движок = **snolla@0.32.2 / core@0.13.8**. ⚠️ `content-api` НЕ runtime-dep web-апп (CMS/admin API, консюмер pilorama) — в образ не входит.
|
||
- **Стек Portainer:** новый `tandemmebel` (endpoint 1), compose по labtools-pro-шаблону (сменить service/host-имена). Source-of-truth: `admin/host-stacks/vds-kzntsv/tandemmebel.compose.yml`.
|
||
- **siteId** `78080707-F6E0-4330-BA30-7922354C2CEF`, activeTheme `EFCD9555-3400-411E-A27D-68D3D8B3DDB2` (theme store MinIO `themes/efcd95553400411ea27d68d3d8b3ddb2/`), Culture **ru-RU**. **siteUrl** `https://www.tandemmebel.ru`.
|
||
- **production.json В ПИНЕ содержит** `imgproxy.watermark` override `{size:"post-featured",option:"wm:0.5:ce:0:0:0"}` — уже в образе, отдельно ставить НЕ надо.
|
||
|
||
### Runtime env (8 секретов — Portainer stack-env, НЕ в образ/git)
|
||
Значения идентичны labtools/emspb/labtools.pro (общий тенант). **Переиспользовать env-массив verbatim из labtools stack (Id 17)** через Portainer API. 8: `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`. PORT не переопределять (5000; healthcheck; traefik service-port 5000). VERDACCIO_TOKEN build-time only. `config/default.json` исключён `.dockerignore` — проверить ABSENT в архиве.
|
||
|
||
### ⚠️ ОБЯЗАТЕЛЬНЫЙ cutover-шаг — PURGE imgproxy-nginx кэша (books-vds)
|
||
Focus-ревьюер (независимо) нашёл: nginx перед imgproxy (books-vds, `Cache-Control: max-age=2592000` = 30 дней) закэшировал **ДО-глиф ЧИСТЫЕ** рендеры featured-URL для постов, запрошенных при dev-тестинге (мин. `53-podemnaya-krovat-s-uglovym-shkafom` + smoke-посты). Прод featured-URL детерминированы (без cache-bust) → эти посты отдадут **stale-clean featured (без вотермарка)** в проде до TTL/purge. **ПЕРЕД/НА cutover:** purge nginx-кэш imgproxy на books-vds (стек 29 — cache-dir nginx, напр. `docker exec <imgproxy-nginx> sh -c 'rm -rf /var/cache/nginx/* && nginx -s reload'`, либо полный purge/redeploy nginx-стека). **Verify после:** свежий featured-URL поста 53 (`.../rs:fill:1170:557:1/g:ce/wm:0.5:ce:0:0:0/...`) → 200 **с вотермарком**.
|
||
|
||
### Staging smoke (`tandemmebel.vds.kzntsv.site` vs бой www.tandemmebel.ru; гнать С VDS)
|
||
- Status-parity: контент (`/ /prices /vacancies /worksorder /contacts`), category (`/furniture/kitchens`, `/furniture/kitchens/classic`), blog-listing `/projects`, blog-post, `/404` (брендированный, не пустой) — 200/parity.
|
||
- **Редиректы:** 65 DB-редиректов, `redirects.test.js` 17/17 vs бой — spot-проверить 301 (target parity modulo host).
|
||
- **Sitemap:** snolla index-структура (валидна), все post-URL → 200, 8 постов 2012 = верные даты (core@0.13.8 tz-fix). `/articles` bare-root в sitemap → 404 = **PARITY** с боем (не дефект).
|
||
- **Featured с вотермарком** (после cache-purge!), мелкие размеры (thumbs/related/category/hero/og) — чистые.
|
||
- theme-ассеты MinIO md5 vs бой под `themes/efcd95553400411ea27d68d3d8b3ddb2/`.
|
||
- Контейнер healthy, MSSQL+S3 подключены.
|
||
|
||
### Documented benign divergences (НЕ флагать при smoke — 2 раунда ре-ревью подтвердили benign)
|
||
1. `/projects` ~10 diff = 2015-ordering isotope (within-date, визуально незаметно). 2. gallery imgproxy vs `/galleries/` direct (URL длиннее, та же картинка 800×800, тот же s3-ключ). 3. sitemap 12× `/projects/categories/*` (200, valid over-inclusion). 4. sitemap index-vs-flat (оба валидны). 5. `/articles` bare-root 404 = parity.
|
||
|
||
### Cutover (live DNS) — GATED ОПЕРАТОРОМ
|
||
1. (оператор) reg.ru: A `tandemmebel.ru` + `www.tandemmebel.ru` → `89.253.255.94` (с RUVDS).
|
||
2. (ops) verify флипа авторитетным NS (`ns1.reg.ru` через Resolve-DnsName) ПЕРЕД traefik.
|
||
3. (ops) ТОЛЬКО ПОСЛЕ flip: stack `tandemmebel` traefik-rule → `Host(tandemmebel.ru) || Host(www.tandemmebel.ru)`. Порядок критичен (иначе LE HTTP-01 упадёт на RUVDS → rate-limit).
|
||
4. Live-smoke оба хоста + внешняя проверка + **featured-вотермарк на живом**. staging-host убрать из rule.
|
||
|
||
### Rollback
|
||
DNS → RUVDS IIS (живой, не выводить до явного решения оператора). Стек remove / откат тега.
|
||
|
||
**Status:** 🔵 BLOCKED 2026-07-04 — оператор при визуальной проверке sharp-staging (стек 20, образ 68b93a9, https://tandemmebel.vds.kzntsv.site) нашёл НОВЫЙ дефект (иной, чем прошлые). Специфику не назвал — «чиним в новой сессии». Cutover HELD до фикса. Sharp-staging сам по себе green по моим гейтам; дефект — визуальный, оператор опишет в новой сессии.
|
||
**Where I stopped:** build ✅ (tandemmebel:0facb35 на VDS) + стек Portainer Id 20 (staging-rule, оставлен UP) + staging-smoke GREEN (все гейты, benign #2/3/5 учтены). Оператор поставил cutover на HOLD → решение: watermark через sharp (victor/snolla media-sharp-serve-and-watermark), НЕ imgproxy. **imgproxy глиф со стека 29 СНЯТ** (таск [imgproxy-stack29-watermark-rollback] 🟢, боевые тенанты verified чисты); глиф-бинарь 7127e92a оставлен для sharp-composite. Текущий образ 0facb35 использует imgproxy.watermark — при возврате к деплою нужен ПЕРЕСБОР на sharp-пин. Compose source-of-truth в host-stacks/vds-kzntsv/tandemmebel.compose.yml.
|
||
**Next action:** ЖДУ отмашку ОПЕРАТОРА на DNS-флип reg.ru (tandemmebel.ru+www → 89.253.255.94). Потом: verify авторит.NS (Resolve-DnsName) ПЕРЕД traefik → PUT стека 20 rule → Host(`tandemmebel.ru`)||Host(`www.tandemmebel.ru`), убрать staging-host → live-smoke оба хоста + featured-вотермарк на живом. RUVDS=rollback, не трогать.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T05:18:05.411Z -->
|
||
<!-- staging-green: vitya@.admin-exec / 2026-07-04 / build+стек20+cache-purge+smoke green; cutover held on operator -->
|
||
|
||
---
|
||
|
||
## 🟢 [imgproxy-stack29-watermark-rollback] — closed 2026-07-04 — `IMGPROXY_WATERMARK_DATA` снят со стека 29 (books-vds), стек byte-identical pre-watermark бэкапу (cross-check true), redeploy+purge. Verify: pilorama98 (главный консюмер, 122 ref) 3 рендера == baseline (43950/172042/160010B) — не задет; stostayer НЕ imgproxy-консюмер (картинки через /galleries//images//assets/) → не затрагивается; WATERMARK_DATA present:false; tandem featured→чистый (27820→27294B, ожидаемая побочка, staging→sharp). Глиф-бинарь 7127e92a НЕ тронут (для sharp-composite). Notify workshop отправлен.
|
||
|
||
<!-- HISTORICAL spec below -->
|
||
|
||
## ⚪ [imgproxy-stack29-watermark-rollback] — СРОЧНО (оператор просил поскорее). Откатить watermark-инъекцию на SHARED imgproxy стек 29 (books-vds). Причина: tandemmebel уходит с imgproxy-watermark на in-process sharp (задача victor/snolla media-sharp-serve-and-watermark), глифу на общем инстансе больше не место. Shared инстанс держит БОЕВЫХ pilorama98/sotstayer — аккуратно.
|
||
|
||
## Шаги
|
||
|
||
1. **Verify (не по памяти):** подтвердить, что env `IMGPROXY_WATERMARK_DATA` реально сейчас присутствует на стеке 29. Если уже нет — стоп, доложить.
|
||
2. Восстановить env-бэкап БЕЗ `IMGPROXY_WATERMARK_DATA` (точечный PUT, как планировалось при заведении глифа) → redeploy стека 29 → purge imgproxy-кэша.
|
||
3. **Verify после:** pilorama98.ru + sotstayer рендерят картинки как раньше (главный критерий — не задеть боевых тенантов). Приложить проверку (curl/скрин ключевого изображения каждого).
|
||
|
||
## НЕ трогать
|
||
|
||
- Глиф-бинарь `host-stacks/books-vds/tandemmebel-watermark-logo.png` (commit 7127e92a) — ОСТАВИТЬ, переиспользуется для sharp-composite.
|
||
- Никаких других env стека 29, никаких других стеков.
|
||
|
||
## Ожидаемая побочка
|
||
|
||
tandemmebel-staging (стек 20) потеряет featured-вотермарк — ЭТО НОРМАЛЬНО (staging paused, переезжает на sharp). Не чинить.
|
||
|
||
## Обязательные скилы
|
||
|
||
- invoke `using-vds-ops` / ops-доступ к стеку — verify состояния контейнеров
|
||
- invoke `using-tasks` — статус
|
||
|
||
**weight:** needs-human (redeploy боевого shared docker-стека)
|
||
**notify:** OpeItcLoc03/workshop
|
||
**Dev-source:** victor/snolla media-sharp-serve-and-watermark (переход на sharp)
|
||
|
||
**Status:** 🟢 closed 2026-07-04
|
||
**Where I stopped:** WATERMARK_DATA снят со стека 29, redeploy+purge, pilorama/stostayer verified не задеты, глиф-бинарь оставлен. Notify workshop отправлен.
|
||
**Next action:** — (закрыто).
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T06:15:36.456Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-07-04 / rollback done, byte-identical to pre-watermark backup, boевые тенанты verified clean -->
|
||
|
||
---
|
||
|
||
## 🟢 [minio-variant-cache-bucket] — closed 2026-07-04 — бакет `variant-cache` создан в shared MinIO (minio.kzntsv.site/books-vds), пустой, другие 17 бакетов не тронуты. Грант: snolla S3-креды (S3_ACCESS_KEY_ID/SECRET из деплой-env) == MinIO root (сверено оба поля) → в mode-server-fs root имеет полный доступ by construction, IAM-грант не нужен (FS-режим его не держит). Write-verify ПОД деплойными кредами (явный alias): put→stat→get(md5 match 78fab481)→delete→gone ✓. Lifecycle не ставил (опц., кэш регенерируем). sharp-prereq (500 NoSuchBucket) снят. Notify workshop.
|
||
|
||
<!-- HISTORICAL spec below -->
|
||
|
||
## ⚪ [minio-variant-cache-bucket] — Создать MinIO-бакет `variant-cache` — prereq для sharp-медиа доставки. Без него sharp-движок при resize пишет готовый вариант в бакет `variant-cache`, а его нет → HTTP 500 `NoSuchBucket` на ВСЕХ sharp-картинках (agent-блокер 1, подтверждён live на tandemmebel-verify).
|
||
|
||
Это S3-эквивалент легаси App_Data/imageCache: движок ресайзит+вотермарчит on-demand и кладёт результат в этот бакет, потом отдаёт из него. Кэш ленивый/регенерируемый (можно чистить свободно — восстановится из оригиналов по заходу).
|
||
|
||
## Шаги
|
||
|
||
1. Создать ПУСТОЙ бакет `variant-cache` в том же shared MinIO, где лежат snolla-бакеты оригиналов/тем (тот же endpoint).
|
||
2. Дать сервис-кредам snolla content-api (те же, что читают оригиналы) **read+write** (putObject/getObject/headObject) на `variant-cache`. Verify, что деплойные S3-креды реально могут писать туда.
|
||
3. Confirm: бакет существует + writable тест-объектом (put→get→delete) → доложить.
|
||
|
||
## Границы / заметки
|
||
|
||
- Только СОЗДАТЬ новый бакет + грант snolla-сервису. Другие бакеты НЕ трогать.
|
||
- Имя бакета `variant-cache` — по текущему коду 0.14.0 + deploy-config. Снолла сейчас переделывает движок (задача victor/snolla media-sharp-imageprocessor-port); если в переработке имя cache-бакета изменится — она пингнёт, подкорректируешь. Пока — `variant-cache`.
|
||
- Опционально: lifecycle/retention на бакет (кэш регенерируем) — на твоё усмотрение, не обязательно.
|
||
|
||
## Обязательные скилы
|
||
|
||
- invoke `using-vds-ops` / ops-доступ к MinIO — создание/verify
|
||
- invoke `using-tasks` — статус
|
||
|
||
**weight:** needs-human (провижн на shared prod storage)
|
||
**notify:** OpeItcLoc03/workshop
|
||
**Dev-source:** victor/snolla media-sharp-imageprocessor-port (agent-блокер 1 из tandemmebel sharp-verify)
|
||
|
||
**Status:** 🟢 closed 2026-07-04
|
||
**Where I stopped:** бакет variant-cache создан + write-verify под деплойными кредами (put→get md5→delete) зелёный; snolla-креды==root (FS-режим). Notify workshop отправлен.
|
||
**Next action:** — (закрыто). Имя может смениться при media-sharp-imageprocessor-port → пересоздам по пингу.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T07:50:45.270Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-07-04 / bucket created, deploy-creds write-verify green, snolla==root FS-mode -->
|
||
|
||
---
|
||
|
||
## 🟢 [tandemmebel-sharp-staging-rebuild] — closed 2026-07-04 — sharp staging GREEN. Образ `tandemmebel:68b93a9` (snolla 0.34.0/core 0.15.0 in-process sharp, digest 12288b15) собран на VDS; стек 20 обновлён (env verbatim 8/8). Smoke: nav-parity 8/8, sitemap staging⊇prod (prod-only=0, +12 categories benign#3), 2012-посты 31/31, redirects 301/301; sharp media = slug-URL 200 webp serve-bytes, **0 /imgproxy-рефов**, variant-cache пишется (32 объекта, NoSuchBucket снят); watermark визуально ✓ на featured+lightbox, gallery-small чистый; лог без ошибок. Находка (не блокер): осиротевший imgproxy-блок в production.json пина эмпирически мёртв (0 /imgproxy-рефов) — вычистить в след. пине. Cutover — НЕ в scope, gated оператором. Notify workshop.
|
||
|
||
<!-- HISTORICAL spec below -->
|
||
|
||
## ⚪ [tandemmebel-sharp-staging-rebuild] — РЕЗЮМ ДЕПЛОЯ tandemmebel (был 🟡 paused под пивот на sharp — sharp готов, снимаем паузу). Оператор: «деплоим, ты всё проверишь». Пересобрать staging на новом sharp-движке, parity-smoke. Cutover DNS — НЕ в scope, отдельный gated шаг оператора позже.
|
||
|
||
## Что нового vs прошлый staging (imgproxy-путь)
|
||
|
||
- **reviewed-sha = `68b93a95d6cd6170ecdd01a37585ece3d6221df5`** (victor/tandemmebel master, запушен) — с него собирай.
|
||
- Движок: snolla **0.34.0** / core **0.15.0** (in-process sharp, data-driven watermark из данных темы; imgproxy из пути tandem убран).
|
||
- `production.json` в репо несёт media-конфиг (`resize.engine=sharp`, `delivery=serve-bytes`, `cache.mode=local-variant`, `storage.provider=s3`) — это НЕ секрет, приезжает с образом. Рукописного `media.watermark` больше нет.
|
||
|
||
## Шаги
|
||
|
||
1. Собрать образ на VDS из ЧИСТОГО git-archive sha `68b93a95` (guard: секретов в образе нет; production.json = конфиг, не секреты). Push в registry.
|
||
2. Обновить/пересобрать staging-стек tandemmebel (прошлый — Portainer стек 20, `tandemmebel.vds.kzntsv.site`) на новый образ. Runtime-секреты (DB mssql / MinIO S3-креды / siteId) — как раньше, verbatim, в stack-env, НЕ в образ.
|
||
3. **Parity-smoke vs бой `www.tandemmebel.ru`:**
|
||
- Картинки: slug-URL `/galleries/<gid>/images/<slug>/<seq>` → 200 image/webp; **0 `/imgproxy`-редиректов наружу** (сырой /imgproxy под sharp → 404 — норма).
|
||
- Featured + lightbox (gallery-large 1200×794, post-featured 1170×557) несут **watermark** (из sharp, центр ~0.6); мелкие (gallery-small, slider) — чистые.
|
||
- variant-cache пишется в бакет `variant-cache` (без NoSuchBucket).
|
||
- Роуты/редиректы parity как прошлый staging (10/10 было зелёное).
|
||
4. Доложить staging-green + находки в inbox workshop. Всё, что «увидишь хренью» — репорти, не проглатывай.
|
||
|
||
## Заметки / guards
|
||
|
||
- **variant-cache — общий бакет:** staging пишет туда же, куда потом прод. Кэш content-addressed (source+size+watermark) → байты идентичны, staging просто пред-прогреет. Не пуга́йся объектов в бакете.
|
||
- **GothamPro heads-up (из прошлого хендоффа):** прод-tandemmebel.ru отдаёт `Gotham-Pro.css` 0 байт, staging корректный ~4436B → шрифт заголовков на staging «правее» боя. Это НЕ регресс — известная дельта, всплывёт при cutover, не флагай как поломку.
|
||
- Cutover DNS (reg.ru) — НЕ трогай. Отдельный gated шаг оператора по твоему staging-green.
|
||
|
||
## Обязательные скилы
|
||
|
||
- invoke `using-vds-ops` — состояние контейнеров/стека
|
||
- invoke `using-tasks` — статус
|
||
|
||
**weight:** needs-human (сборка образа + деплой стека на VDS)
|
||
**notify:** OpeItcLoc03/workshop
|
||
**Dev-source:** victor/tandemmebel sha 68b93a95 (sharp-движок snolla 0.34.0/core 0.15.0)
|
||
|
||
**Status:** 🟢 closed 2026-07-04 (sharp staging green)
|
||
**Where I stopped:** образ 68b93a9 собран + стек 20 обновлён + parity-smoke GREEN (media/watermark/variant-cache/sitemap все ок). Notify workshop отправлен. Осиротевший imgproxy-конфиг в пине — heads-up воркшопу (dead).
|
||
**Next action:** — (закрыто). Cutover — в [tandemmebel-web-vds-deploy], ждёт DNS-отмашки оператора.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T08:42:44.850Z -->
|
||
<!-- closed-by: vitya@.admin-exec / 2026-07-04 / sharp staging green, все гейты + watermark visual verified -->
|
||
|
||
---
|
||
|
||
## 🟢 [tandemmebel-deploy-snolla-0-42-0] — Раскатить прод tandemmebel на `@snollajs/snolla@0.42.0` (core 0.24.0 / data 0.14.1) — engine-chain-обновление: перепил viewModel-навигации на единый ленивый cross-entity граф (EntityDrop/IndexDrop + декларативный GRAPH-реестр, includeX-запекание выпилено) + blog-вертикаль (blogAuthor / blog.archives / date-archive страницы) + path-фиксы. Всё TDD, опубликовано в registry, byte-verified двумя живыми консюмерами: artmone 17/17 byte-identical, tandemmebel 199/199 роутов Ф3-регресс 0 diff. Dev-source: `victor/snolla`, сессия 2026-07-04, трек `viewmodel-graph-full-platform`. Оператор деплой одобрил (workshop ратифицировал 2026-07-04).
|
||
|
||
## Топология — РЕШЕНА оператором: пересборка staging ДО cutover (вариант A)
|
||
Оператор: «пусть всё переебашит с новыми версиями пакетов до cutover». Значит:
|
||
- **Пересобрать VDS-staging образ на 0.42.0-чейне** (новые версии всех пакетов), НЕ деплоить in-place на текущий боевой old-infra прод.
|
||
- 0.42.0-образ **суперсидит** ранее собранный 0.16.2-staging (`b02ca18`) — это новый пин staging, старый снимается.
|
||
- Прод-флип остаётся **тем же DNS-gated cutover** из paused `[tandemmebel-web-vds-deploy]` (reg.ru→89.253.255.94, хозяин сайта). Этот таск готовит НОВЫЙ образ + перепрогоняет гейты; сам флип — по-прежнему по внешнему DNS + отмашке оператора. Развести с паркованным таском (объединить/пометить, чтобы не было двух конкурирующих образов).
|
||
- **Проверь tree-check образа:** 0.42.0 содержит sharp-пивот (snolla 0.34.0 нёс sharp, 0.42.0 > 0.34.0 → по версии да) — не потеряй media-sharp-переезд при пересборке.
|
||
|
||
## Acceptance (обязательные гейты)
|
||
1. **Pre-deploy SSR-чек (напоминание сноллы):** SSR `/projects` + blog-listing → **200, не 500**. Причина: `getBlogByPath` тянет новые SELECT-колонки; в боевой БД tandemmebel колонки уже есть (проверено сырым SQL в снолла-сессии), но подтверди на целевой БД.
|
||
2. **Completeness-DoD перепрогнать НА новом 0.42.0-образе** (не наследовать GREEN с 0.16.2): боевой sitemap ~188 URL prod↔staging → 0 реальных prod-200/staging-не-200; gallery grid-паритет 15 роутов staging==prod. Ядро навигации полностью перепахано — свежий прогон обязателен.
|
||
3. **Archive-страницы — НЕ блокер:** live-verify archive-**страниц** оператор отложил. Для `projects` archive-роуты off (`archivesPath=""`). Не гейтить на них.
|
||
4. **Rollback** (RUVDS / предыдущий пин) наготове.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-tasks` — управление статусом задачи
|
||
- invoke `using-vds-ops` — диагностика контейнеров на VDS
|
||
- invoke `project-discipline` — дисциплина коммитов/деплоя
|
||
- invoke `using-wiki` при закрытии — заингесть concept, если топология/пин менялись
|
||
|
||
## Отчётность
|
||
- **notify: OpeItcLoc03/workshop** (комиссионер-оркестратор).
|
||
- По закрытию/парку — heads-up ТАКЖЕ в `victor/snolla` inbox: dev-source просил уведомление по деплою.
|
||
|
||
**TDD:** нет — ops-деплой, кода не пишем.
|
||
**Разрешения:** интерны: нет | автопуш: н/д (ops).
|
||
**weight:** needs-human (боевой деплой живого сайта + engine-ядро)
|
||
|
||
**Status:** done
|
||
**Where I stopped:** ✅ GOTOVO. Собрал `tandemmebel:ed96b18` (digest `ca4da79`) на VDS из sha ed96b18 (snolla 0.42.0/core 0.24.0/data 0.14.1, verdaccio publish подтверждён), запушил в registry, передеплоил стек 20 (Portainer PUT, env 8/8 сохранён, PullImage) → контейнер **healthy**, running image==ed96b18. Гейты GREEN на новом образе: **acceptance#1** SSR /→200, /projects→200 (title «Наши проекты мебели», не 500); **acceptance#2** completeness-DoD с VDS — sitemap **172/172 паритет, 0 real-fail** (prod200/staging-not200) + gallery-grid **12/12 точный паритет** (bedrooms 80/80…kids 138/138), крошки Главная/Мебель/Галерея наполнены==prod, title==prod, sharp отдаёт webp-байты (200 image/webp). archive-роуты N/A (projects archivesPath=""). Rollback наготове (RUVDS не тронут, b02ca18 в registry).
|
||
**Next action:** — (закрыто). Cutover остаётся отдельным DNS-gated шагом в [tandemmebel-web-vds-deploy] — образ к флипу теперь ed96b18. Follow-up для dev/workshop: Dockerfile печёт VERDACCIO_TOKEN в ARG/ENV-историю слоёв (2 warn SecretsUsedInArgOrEnv) — вынести в build-secret.
|
||
**Branch:** n/a
|
||
**Notify:** OpeItcLoc03/workshop
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-07-04T19:59:45.158Z -->
|
||
|
||
---
|
||
|
||
## ⚪ [labtools-ru-deploy-snolla-0-42-1] — Обновить ЖИВОЙ прод labtools.ru на VDS (Portainer стек 17) с `@snollajs/snolla@0.28.2` → **0.42.1** in-place. Резолвимый sha бампа: **`566d41c`** (origin/master victor/labtools.ru; пин 0.42.1 + yarn.lock → core 0.24.1 / data 0.14.1 / liquid 0.10.2). Суперсидит пин 0.28.2 из `[labtools-web-vds-deploy]`.
|
||
|
||
⚠️ **labtools.ru УЖЕ cutover'нут на VDS** (2026-07-02, боевой `Host(labtools.ru)` на стеке 17). Это **обновление ЖИВОГО прода in-place** (pull новый тег → смена тега на живом стеке 17 → rollback = предыдущий тег), **НЕ staging-first, НЕ DNS-флип** (домен уже на VDS).
|
||
|
||
Контекст: 0.42.x = перепил viewModel-ядра (ленивый cross-entity граф, includeX-запекание выпилено). Прямой bump на 0.42.0 вскрыл engine-регрессию порядка изделий (`{% order by list_priority %}` no-op на Drop-VM) — сноллой закрыто в **0.42.1** (liquid 0.10.2, orderBy резолвит field через Drop-акцессор). Поэтому цель = 0.42.1, НЕ 0.42.0.
|
||
|
||
## Key files
|
||
- `~/projects/labtools.ru/apps/web/package.json:12` — пин `@snollajs/snolla` = `0.42.1`
|
||
- `~/projects/labtools.ru/deploy/Dockerfile` — multi-stage node:22-slim (без правок; пин течёт через yarn.lock, `yarn install --immutable`)
|
||
- Portainer **стек 17** labtools.ru — source-of-truth compose (боевой `Host(labtools.ru)`)
|
||
|
||
## Acceptance (гейты на НОВОМ образе ДО подмены — не наследовать)
|
||
1. Build на VDS из sha `566d41c` → образ в `registry.kzntsv.site` (BUILD_EXIT=0).
|
||
2. **Completeness-gate** на новом образе: 38 боевых-200 роутов (DB source-of-truth MoreThenCms siteId D375C419: 7 content + 6 секций + 24 продукта + static-page + sitemap/robots) → **0 боевых-200 дают 404**. Прогонять С VDS (воркстейшн ловит LAN-DNS-перехват).
|
||
3. **Catalog-parity:** порядок изделий в листингах by list_priority (presses = plg-20, plg-12, plg-25, pgr-10 — ключевой чек фикса 0.42.1); непустые сетки всех 6 секций.
|
||
4. Подмена тега на живом стеке 17 → контейнер healthy → live-smoke `labtools.ru` == прод, rollback (предыдущий тег) наготове.
|
||
5. Секреты НЕ в образе (`config/default.json` .dockerignore'd; VERDACCIO_TOKEN build-only) — уже провалидировано dev'ом локально (docker build+run: image healthy, свип 0 gaps, order корректен в образе, config/default.json ABSENT, VERDACCIO_TOKEN UNSET).
|
||
|
||
## Build-рецепт (как tandemmebel)
|
||
`git -C ~/projects/labtools.ru archive 566d41c | ssh vitya@<vds> tar -x` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<token> -t registry.kzntsv.site/labtools:566d41c . && push`. Portainer PUT стека 17 через curl -X + node (НЕ PS Invoke-RestMethod), env сохранить.
|
||
|
||
## Обязательные скилы — вызвать до начала
|
||
- invoke `using-tasks` — статус этой задачи
|
||
- invoke `project-discipline` — коммит/деплой-дисциплина
|
||
- invoke `using-vds-ops` — диагностика контейнеров VDS (стек 17, healthy)
|
||
|
||
**TDD:** нет — ops/deploy, кода нет.
|
||
**Разрешения:** автопуш деплой-артефактов на VDS — да (боевой apply гейтит оператор). Секреты НЕ в git/образ.
|
||
**weight:** needs-human — live-prod in-place: админ подтверждает боевую подмену тега с оператором ДО apply, НЕ автономно.
|
||
**notify:** victor/labtools.ru (репорт мне; deploy-петля минует workshop).
|
||
|
||
**Status:** ready
|
||
**Where I stopped:** (not started)
|
||
**Next action:** Собрать VDS-образ labtools.ru на sha `566d41c` (snolla 0.42.1) → registry.kzntsv.site. НЕ подменять живой стек 17 до re-прогона completeness+catalog-parity на НОВОМ образе и подтверждения оператором.
|
||
**Branch:** master
|
||
**Notify:** victor/labtools.ru
|
||
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: victor/labtools.ru / 2026-07-05T08:31:44.377Z -->
|
||
|
||
---
|