Files
admin/.tasks/STATUS.md
vitya 202b7c32af meta(tasks): block [books-task-runner-registry-auth-cred] — cred placed+verified, new registryGc (a3734d5) not deployed
Кред books-ci (секция registry, полная — bind-mount затеняет config образа)
положен в task-runner config-volume на books-vds, 644 root сохранён
(бэкап .bak-pre-registry-2026-06-18, EACCES не повторён), container
restart→healthy, config.get(registry.*) резолвится. books-ci валиден:
/v2/_catalog→200, репы books-* видны, 401 нет.

Блокер: работающий образ (88f2bee, 2026-06-17) содержит старый registryGc
(Gitea packages API), тега master-a3734d5 в реестре нет → CI не собрал
v2-DELETE rewrite. In-app dryRun ждёт build+deploy. Inbox-ответ books отправлен.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:40:07 +03:00

115 KiB
Raw Blame History

Admin Task Board

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: INITFORMAT, прогон 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=trueDELETE _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.)

🟡 [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.comwww.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. Branch: n/a (admin ops)


[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 + .wiki/concepts/backup-inventory-2026-06.md. Branch: n/a (admin ops)


🟢 [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.

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


🟢 [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 § Closure + .wiki/concepts/es-destructive-delete-incident-2026-05-26.md.

Branch: n/a


[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)


🟢 [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 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 httpswebsecure (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


🟢 [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 § 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. 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.

🟢 [unify-backup-notifications] — closed 2026-05-25 — единый формат push + email для VDS/RUVDS/windows-host (3 scripts), VDS run.sh импортирован в repo. См. 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 5b173de8bffd68 (main): templates align + mariadb:10.6 + mongo:4.2 + api internal-only + depends_on removed. Pass: gitea/victor-books-ci-bookva-overlay.


🟢 [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 § 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


[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) 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


🟢 [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 § Closure note.

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 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. Branch: master

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


🔵 [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

🔵 [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, шаги 47» в victor/books.

  1. Maintenance window 12ч (объявить за 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 за 12 недели до этой таски). Branch: n/a

[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 БД:

SELECT id, login, email, role FROM users WHERE email IN (<list>) AND deleted_at IS NULL;
  1. Разобрать расхождения с учредителем (missing / role mismatch / inactive).
  2. Зафиксировать финальный список в OpeItcLoc03/admin/.wiki/concepts/books-bookva-whitelist.md (или в local-only encrypted file если NDA-чувствительно).
  3. Финальный список становится input'ом для Фазы 3, шаг 1: при настройке Bookva-БД в неё попадают только эти users.
  4. Закрыть с note: «whitelist (N users) подтверждён учредителем, готов к применению на cutover'е». Branch: n/a

🔵 [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 ». 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

🟢 [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 — не проверен.

исходный scope (decision-task)

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


🟡 [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. 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


Галерейные (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.jpg200 (235KB / 735KB) — оригинал жив, читается с локального диска;
    • 800x450, default, thumbnail, 100x100, 600x400404 всегда. → ресайз-пайплайн 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\\).
  • 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.

🟢 [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\assetss3://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.jpgHTTP 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\assetsmin: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).

🟢 [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+www80.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 ✓

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 нет) → resolutions3622662 зелёный.


🟢 [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 ✓


🟢 [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): hasMerchantReturnPolicyreturnPolicyCategory=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 ✓


🔵 [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_ESMpackages/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


🟢 [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:
    "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

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.

🔵 [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) добавить секцию:

"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: 🔵 blocked — кред положен+verified, но новый registryGc (a3734d5) НЕ задеплоен 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). Container restart → healthy, config.get('registry.*') резолвится. Кред books-ci валиден: /v2/_catalog→200, репы books-* видны, 401 нет. НО работающий образ (88f2bee, 2026-06-17 20:23) содержит СТАРЫЙ registryGc (Gitea packages API, config.gitea.token), а в реестре нет тега master-a3734d5 → CI не собрал новый код. In-app dryRun нового v2-DELETE прогнать нельзя. Next action: Дождаться build+deploy a3734d5 в books-task-runner:master (CI → registry → pull на books-vds). По появлении master-a3734d5 в реестре + подтянутом образе — прогнать registryGc {dryRun:true} через /scheduler, проверить repos books-* в отчёте без 401 → close. Inbox-ответ books отправлен (2026-06-18T08-38-57Z-admin.md). Blocker: books CI ещё не собрал/задеплоил a3734d5 (registryGc v2-DELETE rewrite). Owner блокера — books-сессия (notify OpeItcLoc03/books). Branch: n/a Notify: OpeItcLoc03/books