Compare commits

..

230 Commits

Author SHA1 Message Date
8d7ddf2889 deploy(mappa): стек 27 → v0.64.16 (task:3664) — /task/list без parent отдаёт все таски 2026-09-21 09:32:27 +03:00
bb2b76b8b0 scripts(schedjs): фаза all — web-логин + trust + verify одной командой (task:3130) 2026-09-20 23:55:04 +03:00
28c70c7093 scripts(schedjs): разовая настройка OIDC-публикации (env npm-release + npm trust; task:3130) 2026-09-20 23:51:26 +03:00
560711af5c deploy(mappa): стек 27 -> v0.64.15 (release:25) — focus/sessionTargets + раскол соединений, task:3149 2026-09-20 21:36:26 +03:00
6dfe9813b2 chore(secrets): вычистить VDS-креды из .wiki-копий
Живое значение Portainer admin (плюс traefik/ntfy/registry-креды) лежало в
рабочем дереве .wiki открытым текстом — класс утечки inbox:4179/wiki:187.
Значения → плейсхолдеры со ссылкой на pass; канон страниц и так в mappa
(файловый канал .wiki закрыт). Ротация — task:3082.
2026-09-20 13:11:54 +03:00
47fba6d993 deploy(mappa): стек 27 -> v0.64.14 (release:24) — FP детектора секретов, task:3077 2026-09-19 22:52:53 +03:00
c8cc17047d fix(secrets): вычистить MinIO-креды из .wiki file-каналов (task:3076) 2026-09-19 22:05:36 +03:00
201fc6aa58 deploy(mappa): стек 27 -> v0.64.13 (release:23) — redirect-предикат линта без ложных срабатываний 2026-09-19 20:27:37 +03:00
ad731e014f deploy(mappa): стек 27 -> v0.64.12 (release:22, task:3068) + mem_limit 512m->1g по замеру 2026-09-19 19:37:54 +03:00
ca265bfe2f fix(agents): вернуть opt-in session-sync в AGENTS.md — live-ingest .admin был выключен
Строка `session sync: write to mappa` выпала из файла в e3fb3111 (18.09, task:3017,
переработка канон-блока); канон wiki:2608 её сохранил, повторный прогон блока
(7939ce36) не вернул. Без строки pi-расширение (mappa.ts isOptedIn) не поднимает
live-ingest → сессии .admin не пишутся в mappa (проверено: 01a0b97e и текущая 404,
контроль session:1913 200; у books/sched/tg-digest строка есть — их сессии в mappa есть).

Восстановлено verbatim из канона.
2026-09-19 16:36:44 +03:00
d1cb4d932a fix(admin): sync-sched-publish — .mappa вне паблик-сета + портативный leak-скан (gotcha 15/16, task:3059) 2026-09-19 14:59:13 +03:00
9d1ca60ddd deploy(mappa): стек 27 -> v0.64.10 (release:20, вершина 32d3c74)
canary зелёный (та же БД: dangling keys +reason/refs_total), pg_dump до PUT (126745887 B, sha256 86c4365e…, TOC 281), env 19/19, verify /health+meta 0.64.10/sha 32d3c74, sched 6/6, 0 mappa-404 в окне.
2026-09-19 14:31:09 +03:00
7939ce3611 docs(canon): ре-прогон канон-блока на mappa-setup@0.2.8 (task:3030) + refresh .mappa/share 2026-09-19 09:45:08 +03:00
e3fb31112b docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 22:03:15 +03:00
d8b5344a72 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:50:23 +03:00
2f695c2808 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:29:28 +03:00
77f23a0614 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:17:06 +03:00
d1b2e3efe7 ops(mappa): stack 27 -> mappa:v0.64.9 (source-of-truth compose, release:19) 2026-09-18 21:13:03 +03:00
d8bfde9e5d ops(mappa): stack 27 -> mappa:v0.64.8 (source-of-truth compose, release:18) 2026-09-18 19:08:10 +03:00
72c9b74bc8 ops(mappa): stack 27 -> mappa:v0.64.7 (source-of-truth compose, release:17) 2026-09-18 16:46:51 +03:00
0502726440 chore(host-stacks): mappa tag v0.64.5 -> v0.64.6 (task:2999) 2026-09-18 02:01:07 +03:00
a0fdb46958 docs: канон-блок AGENTS.md — Г2 v4 (адресация about/thread) + сняты junk mail-строки (task:2993) 2026-09-17 23:41:26 +03:00
.admin
10b02d7a9a chore(mappa): compose тег v0.64.5 (/mcp-телеметрия, timedOut, счётчик migrations; task:2951) 2026-09-17 13:47:30 +03:00
.admin
2ed8a0a8df chore(mappa): compose тег v0.64.4 (атомарность записи, 5 единиц, task:2947) 2026-09-16 15:24:36 +03:00
.admin
3ce97bc2fe chore(mappa): compose тег v0.64.3 (release-tooling: короткий sha, skipped с причиной, release.update, task:2912) 2026-09-15 22:43:57 +03:00
.admin
078ddcbc74 chore(mappa): compose тег v0.64.2 (CAS threads.setStatus, task:2914) 2026-09-15 20:07:51 +03:00
.admin
cf97ab897f chore(mappa): compose тег v0.64.1 (S4 hosted-политика + фиксы ревью дрейфа + thread в entities/list, task:2909) 2026-09-15 18:36:52 +03:00
.admin
cc4e22e126 chore(mappa): compose тег v0.64.0 (модель адресации писем S1/S1b/S2/S3/IA-P1/S5 + sched catch_stuck_threads, task:2902) 2026-09-15 16:32:25 +03:00
540e6b8b17 chore(mappa): compose тег v0.63.1 (батч фолоуаппов C6, task:2887) 2026-09-14 22:59:12 +03:00
85b8e665e0 chore(mappa): compose тег v0.63.0 (C6 hosted на контракт-ядре, task:2879) 2026-09-14 20:07:02 +03:00
38b99da440 chore(pilonuxt): compose тег 5a93b20 (таксономия по page_path — 33 × 200; task:2678) 2026-09-10 20:18:48 +03:00
36f3c88d02 chore(pilonuxt): compose тег 5952cb9 (галереи + канонизация таксономии; task:2676/2677) 2026-09-10 20:10:03 +03:00
2173e888df chore(pilonuxt): compose тег fc4533d (h3-резолвер, issue:59 + таксономия; task:2666) 2026-09-10 19:38:39 +03:00
afbabb0bbd chore(pilonuxt): compose тег fb77b9c (sitemap taxonomy /catalog; task:2666) + NODE_CONFIG из варианта A 2026-09-10 18:52:49 +03:00
048478fc9f chore(pilonuxt): выровнять комментарий строки image с живым стеком (drift-check) 2026-09-10 18:26:57 +03:00
497d6ad036 chore(pilonuxt): NODE_CONFIG s3-креды для content-api — голый /assets 500→200 (task:2665, issue:58) 2026-09-10 18:26:19 +03:00
df98ec9038 chore(pilonuxt): compose тег 5edf4a1 (CMS redirections — 60 правил живы; task:2662/2653) 2026-09-10 18:16:42 +03:00
3605cbc594 chore(pilonuxt): compose тег b336af7 (published=false → 404 + 301, issue:50/task:2631) 2026-09-10 14:04:33 +03:00
bb12fb4159 chore(pilonuxt): compose тег 856fc99 (soft-404 fix, issue:49/task:2627) 2026-09-10 11:15:23 +03:00
dab61342f0 chore(mappa): compose тег v0.62.0 (ingest dedup, task:2574/2587) 2026-09-07 01:15:28 +03:00
e7ef8c6529 chore(mappa): compose тег v0.61.9 (search tag-channel, task:2573/2586) 2026-09-06 23:53:02 +03:00
9774a2b5b2 chore(mappa): compose тег v0.61.8 (search FTS-safe, task:2572/2585) 2026-09-06 22:55:42 +03:00
83aca8e1d8 chore(mappa): compose тег v0.61.7 (handoff reactive, task:2571/2584) 2026-09-06 21:30:33 +03:00
29a5f06259 chore(mappa): compose тег v0.61.6 (search recall, task:2570/2577) 2026-09-06 18:59:15 +03:00
48bbf106d1 chore(deploy): mappa v0.61.5 (per-host creds 1a2e8b3, issue:30/task:2569) 2026-09-06 17:55:02 +03:00
a3dcc9589e chore(deploy): mappa v0.61.4 (host-dimension 872ce69, issue:30/task:2558) 2026-09-06 17:19:38 +03:00
f7e132f630 chore(marker): .mappa/config.yaml — git_host (issue:30, task:2558) 2026-09-06 16:39:22 +03:00
22023d71cc chore(deploy): mappa v0.61.3 (fix integrity 253d632) 2026-09-06 16:12:27 +03:00
59b16a9466 chore(mappa): compose тег v0.61.2 (деплой task:2557, PATCH fix 2545 + attach_commits) 2026-09-06 12:52:00 +03:00
abeb1deca7 chore(mappa): compose тег v0.61.1 (деплой task:2547, PATCH closure-скан 2542 + snapshot 2543) 2026-09-06 11:50:03 +03:00
e60bd3acb3 deploy(mappa): v0.60.0 тег в compose (зоны boss/ops 2519/2520 — 3546557, task:2525) 2026-09-05 19:33:16 +03:00
9e1ce522da deploy(mappa): v0.59.0 тег в compose (token-model кластер 2430-2436 — cf77e89, task:2524) 2026-09-05 17:04:37 +03:00
7388d2cb06 deploy(mappa): v0.58.2 тег в compose (0519583 — methodology поле реестра, task:2437) 2026-09-05 12:19:20 +03:00
e08938f656 deploy(mappa): v0.58.1 тег в compose (task:2392 — issue:39 session live-ingest 200+advisory) 2026-09-05 08:40:49 +03:00
d518774311 chore(deploy): mappa v0.58.0 — тег (code-access 2058/2192-2195 + closure 2149, task:2319; минор 0.58) 2026-09-05 01:23:07 +03:00
8328e2d9a7 chore(deploy): mappa v0.57.1 — тег (closure-гейт 383b918, task:2147/2154; патч 0.57) 2026-09-04 20:28:24 +03:00
004bc0bab6 chore(deploy): mappa v0.57.0 — тег (eval entity-type 5503108, task:2150; минор 0.57, не патч) 2026-09-04 20:07:26 +03:00
8463740fef chore(deploy): mappa v0.56.0 — тег (intent-тип 1da518c, task:2126/2132; минор 0.56, не патч) 2026-09-04 18:59:28 +03:00
cd0937b1a7 chore(deploy): mappa v0.55.7 — тег (архив metric-сирот: «сирота в живом графе», 7c5072c, task:2124/2125) 2026-09-04 17:51:55 +03:00
e1d1bd01c3 chore(deploy): mappa v0.55.6 — тег + TELEMETRY S3-декларации (attachments+телеметрия на MinIO) 2026-09-04 16:57:07 +03:00
ce11e2710e chore(deploy): mappa v0.55.4 — тег + backend=s3 (issue:35 фикс, live-verify 201) 2026-09-04 15:57:30 +03:00
aec6114456 chore(deploy): compose — S3-env декларации (task:2123; backend=local до фикса issue:35 s3-store.ts) 2026-09-04 14:24:37 +03:00
f8db4a4337 chore(deploy): mappa v0.55.3 — тег (issue:33 executor POST /tag, фикс 093e44c) 2026-09-03 03:28:21 +03:00
8b52046bb0 chore(deploy): mappa v0.55.2 — тег (issue:34 shared-PATCH 404, фикс 6c68439) 2026-09-03 03:22:23 +03:00
c0b43d6035 chore(deploy): mappa v0.55.1 — тег + MAPPA_CLIENT_LATEST/MIN в environment (env 10/10 в контейнер) 2026-09-03 01:32:45 +03:00
f80631ffc5 chore(deploy): mappa v0.55.0 — compose тег (client-update роут версий + 426, task:2075) 2026-09-03 01:09:08 +03:00
fd50916851 chore(bootstrap-v2): canon-блок + толстый кэш .mappa/share в .admin (methodology/kzntsv) 2026-09-03 00:30:47 +03:00
6d29949d55 chore(mappa): compose тег → v0.54.0 (code-access) + ro-маунт gitea repos (/data/gitea/repositories) 2026-09-02 23:16:07 +03:00
46f8efdca5 chore(mappa): compose тег → v0.53.0 (session-claim 170+172, task:2052) — догон отставания v0.48-v0.52 2026-09-02 20:05:28 +03:00
6f7f7f09be chore(mappa): compose тег v0.47.0 → v0.47.1 (деплой stale-порог per-runtime) 2026-09-01 20:59:50 +03:00
bb9618d493 chore(mappa): compose тег v0.46.1 → v0.47.0 (деплой модели доступа) 2026-09-01 20:39:17 +03:00
d9d0b58251 chore(deploy): mappa compose → v0.46.1 (fix issue:21 private-create operator_id, issue:20 /mappa 422 hint) 2026-09-01 18:23:34 +03:00
446ae387c1 chore(deploy): mappa compose → v0.46.0 (RLS private 1841-1844, session_autoclose 1847, mark_read=false 1840, RLS-fix 1851) 2026-09-01 17:25:16 +03:00
2cbd0c930f chore(deploy): mappa compose → v0.45.0 (operator-скоуп, task:1831) 2026-09-01 03:52:07 +03:00
970556a979 deploy: mappa v0.44.2 (search metric/anomaly types, task:1824) — image bump 2026-09-01 02:24:53 +03:00
ec3d2ea52d deploy: mappa v0.44.1 (issue:18 cross-project wiki slug) — image bump 2026-09-01 00:25:26 +03:00
b07069c315 deploy: mappa v0.44.0 (agent without project 1802-1804) — image bump 2026-08-31 23:58:19 +03:00
bb9ff93024 deploy: mappa v0.43.2 (operator dedupe guard, task:1795) — image bump 2026-08-31 22:04:40 +03:00
3312de53c1 chore(deploy): mappa compose v0.43.0 -> v0.43.1 (wiki_create shared-scope, issue:17) 2026-08-31 21:07:37 +03:00
05fb1f22c8 chore(deploy): mappa compose v0.42.0 -> v0.43.0 (inbox-agent-scope 1750/1752/1755/1757) 2026-08-31 20:26:06 +03:00
67eebab07d chore(marker): agent: .admin (субъектная доставка, brainstorm:164) 2026-08-31 19:47:41 +03:00
38ebf02387 chore(mappa): env MAPPA_GIT_INDEX_REPO_TIMEOUT_MS=120000 (task:1754, gotcha 4b: PUT env 8/8) 2026-08-31 18:43:39 +03:00
5983625370 chore(mappa): compose v0.42.0 (деплой task:1741) 2026-08-31 09:28:57 +03:00
cbffb4a0ca feat(scripts): sync-sched-publish.mjs — полный синк sched→GitHub с pre-push CI-гейтом
Алгоритм (ранбук wiki:3332, один коммит на волну):
collect → copy → bleach → scrub (скраб kzntsv в т.ч. \u002F) →
lock (yarn.lock против npmjs, gotcha 8) → gate (build 9 пакетов топо-порядком
+ tsc + vitest прямыми вызовами node_modules, обход Windows yarn-bin-болезни)
→ один чистый коммит (--amend при фиксе) → push.

Фикс бага: pairs были массивом строк, деструктуризация [old,nw] давала
односимвольные замены (g→i, r→e — ломало dist); теперь пары [old,nw].
Идемпотентен: повторный прогон = 0 изменений.
2026-08-31 08:55:38 +03:00
dcfb420f73 chore(mappa): compose v0.41.0 + git_index_sync правило (task:1701) 2026-08-31 07:18:39 +03:00
09a613d4bb chore(mappa): compose env sched (MAPPA_SCHED_RULES + MAPPA_TELEMETRY_ANOMALY_RULES, task:1651) 2026-08-30 23:10:42 +03:00
2299e2fed5 chore(mappa): compose v0.40.1 + volume /app/data (персистентный стор MCP-сессий, task:1681) 2026-08-30 20:17:34 +03:00
9ce4940cf2 chore: mappa compose v0.40.0 (revive-фикс 1666/issue:8+10, task:1671) 2026-08-30 18:00:44 +03:00
4179fb1ba6 chore: mappa compose v0.39.0 (GET /entities/list + revoke tenant_tokens) 2026-08-30 14:58:15 +03:00
e2bf57f50e chore(mappa): image v0.38.0 (telemetry cluster 1616/1614/1617, task:1632) 2026-08-30 10:11:39 +03:00
3322cd5a6c chore(.admin): убрать из трека daemon3.log, t2.db (+gitignore) 2026-08-30 03:35:22 +03:00
b1cf3a5785 chore(.admin): чистка — мусор удалён; sched-pipelines: daemon 0.12.1 (cancel-on-poll-timeout) + tasks.json; .mappa-маркер 2026-08-30 03:35:14 +03:00
1b98232093 chore(mappa): export-бэкап (task:1430) — env MAPPA_EXPORT_* + bind-mount /root/vds-mappa-backup:/backup 2026-08-30 03:31:16 +03:00
fa11c4430a chore(mappa): compose v0.36.0 → v0.37.0 (core-skill delete, task:1619) 2026-08-30 03:11:51 +03:00
8226177153 chore(mappa): compose v0.35.0 → v0.36.0 (owners-эндпоинт, task:1608) 2026-08-30 03:00:49 +03:00
5edf328ed0 chore(mappa): deploy v0.35.0 — сущность comment (1571/1572) 2026-08-30 00:40:26 +03:00
d12003b043 chore(mappa): deploy v0.34.1 — hotfix issue:7 (createLive 23505-retry) 2026-08-29 23:15:57 +03:00
91d25a8888 deploy(mappa): v0.34.0 — mail-mode (PATCH /session/:id mail_mode, GET поле, revive сохранение meta) — canary-процедура 2026-08-29 23:06:59 +03:00
3e5b29bc1a deploy(mappa): v0.33.0 — canary-процедура: canary протестирован (health/welcome/MCP) → переключение боя, прод healthy 2026-08-29 19:29:36 +03:00
692739ab75 deploy(mappa): ROLLBACK v0.33.0 → v0.32.0 (Portainer pull не смог авторизоваться в registry — Azure-type auth, стек восстановлен на v0.32.0) 2026-08-29 19:21:19 +03:00
f23f819d4c deploy(mappa): v0.33.0 — монорепо-батч (welcome 1529, .mappa-маркер 1538, setup 1502, onboarding-confirm 1505, origin 1508, fix 1525) 2026-08-29 19:11:34 +03:00
6723afb148 docs(runbook): sync sched.tasks.json source-of-truth — таски воркеров @apilki (мок-контур verdaccio+Gitea, плейсхолдеры секретов; task:1537) 2026-08-29 18:15:15 +03:00
21a0219287 docs(runbook): sched-workers-vds-mock-runbook (wiki:3339) + стек 29 compose — воркеры @apilki на VDS (мок-прогон, task:1537) 2026-08-29 18:14:34 +03:00
4a41be7943 fix(books-backup): exit 3 → on_error в if-блоках ES-snapshot (иначе trap ERR молчит, уведомления не шлются) — инцидент 2026-08-29 2026-08-29 13:07:40 +03:00
a38a4ba137 docs(skill): admin-runbooks v1.1.0 — ранбуки только в mappa wiki (индекс wiki:3316), файловый канал закрыт — task:1517 2026-08-29 11:40:43 +03:00
6547558833 docs(runbooks): ранбуки-файлы → стубы с указателями [[wiki:N]] (канал файлов закрыт) — task:1507
18 runbook-файлов .wiki/concepts/ заменены на стубы-указатели на mappa wiki-сущности.
5 без дубля заингестены в mappa: wiki:3329-3333 (gitea-project-create, pilorama98-vds-deploy,
sched-pipelines-local-stack, sched-publish, sched-vds-deploy). Индекс wiki:3316 переведён на wiki-ссылки.
2026-08-29 11:13:47 +03:00
b6e0e776e8 docs(agents): AGENTS.md → указатель на wiki:2608 (канон в mappa, opt-in session-sync) — task:1506 2026-08-29 11:06:19 +03:00
f2078c1c8a chore(deploy): mappa v0.32.0 — стек 27 (compose тег) + инвентарь ранбука (task:1492)
PUT stack 27: image v0.32.0, env 3/3, pullImage. Verify: health 0.32.0 healthy; регрессия /session /inbox /board/digest /admin/status /graph/stats — 200. Батч только hooks/+package.json (серверный код не тронут).
2026-08-29 03:24:05 +03:00
e694518ec4 chore(deploy): mappa v0.31.0 — стек 27 (compose тег) + инвентарь ранбука (task:1484)
PUT stack 27: image v0.31.0, env 3/3, pullImage. Verify: health 0.31.0, mode work|chat (POST/PATCH/409), promote attach+task (in_session), work->400.
2026-08-29 00:24:04 +03:00
9da36ddb27 docs(runbook): tg-digest VDS deploy runbook + stack artifacts (task:1311)
Ранбук первого деплоя паттерна sched+worker (модель для yt-digest): стек tg-digest (воркер internal, mem_limit 256m), runtime-регистрация задачи POST /tasks, env-контракт, smoke/verify/rollback, gotchas (сессия Telethon, TZ UTC, timeoutMs). Артефакты: compose source-of-truth, Dockerfile + http_worker.py (черновики для коммита в victor/tg-digest). task:1311 разблокирован (1305 done) -> ready.
2026-08-28 23:33:47 +03:00
73f2a74644 docs(runbook): skills-catalog — вычистка остаточных interim-формулировок (core-путь программный) 2026-08-28 22:36:47 +03:00
f6ccb5ecf2 docs(runbook): mappa 0.30.0 deployed (task:1469) — v0.30.0/GIT_SHA 2528e57; core-PATCH программный (1466 live, source=admin) 2026-08-28 22:36:17 +03:00
6317c9b1b5 chore(deploy): mappa 0.30.0 — image tag v0.29.0 → v0.30.0 (source-of-truth, deploy task:1469) 2026-08-28 22:35:19 +03:00
b9f248949e docs(skill): admin-runbooks WRITE — ранбук обязательно как wiki-сущность в mappa, не только файл (инцидент skills-catalog 2026-08-28) 2026-08-28 18:19:15 +03:00
bc27f1e7af docs(runbook): skills-catalog deploy — финализация (команды deploy-catalog.mjs из README, task:1461 done); core-immutable interim до mappa 1466/1467 2026-08-28 18:16:25 +03:00
6143c1c3e1 docs(runbook): skills-catalog — решения оператора inbox:2620 (standing rule confirmed, адресант по каталогам, core=доделать через 1461) 2026-08-28 18:01:35 +03:00
0e33f103f0 docs(runbook): skills-catalog-deploy-runbook draft (task:1462, DRAFT до mappa 1461) + index row 2026-08-28 17:53:52 +03:00
5801ca90cf chore(deploy): mappa v0.29.0 (4e013bd) — issue/entity-version/attachment-E2 + gotcha upload-boundary 2026-08-28 15:57:34 +03:00
4def67cd0d docs(runbook): mappa v0.28.0 (f1ba4e9) + gotcha ре-индекса tsvec 2026-08-28 13:14:35 +03:00
e3f17ece50 chore(deploy): mappa v0.28.0 (f1ba4e9) — батч 1337-1416 2026-08-28 13:14:21 +03:00
d1964e6710 docs(runbook): minio/ops-mcp cgroup-потолки + ES off-heap нюанс 2026-08-28 08:53:20 +03:00
5844d8dddf docs(runbook): gotcha 2 — книги-стеки через portainer endpoint 1 (books VDS), синхронность файл/Portainer 2026-08-28 08:47:53 +03:00
048baf588b docs(runbook): bookva-es урезан до 256m (Portainer стек 37, доступ есть), gotcha 5 исправлена 2026-08-28 08:47:34 +03:00
3a0e930e59 docs(runbook): books-vds-memory-optimize — OOM фикс (mongo wiredTiger 0.25, ES heap, CI buildx снос), Manticore-задел 2026-08-28 08:37:33 +03:00
386fc7f750 docs(runbook): gitea-project-create runbook — создание private Gitea-репо (POST /user/repos → transfer, verify, rollback, gotchas) 2026-08-28 02:46:59 +03:00
eb0fa6cbcc sched: alerts программно через SCHED_ALERTS_WEBHOOK_URL (обход fileAlerts). Пока не срабатывает onRunFinal в published 0.12.1 — диагноз: нужен books-форк 2026-08-28 01:22:16 +03:00
ee28de7552 sched: alerts (ntfy) wiring в tasks.json; убран failing-smoke (debug). Отмечено: daemon-path алерта не срабатывает (нужен след. отладка) 2026-08-28 01:11:37 +03:00
90d76a797f sched: UI morda (/) открыт — статичная форма входа без данных; data plane (/api/* кроме health, /mcp) за Bearer. Фикс UI-torчit. 2026-08-28 00:52:52 +03:00
308a53530e sched: SECURITY — закрыть /mcp и / (morda) без токена; auth-гвард на входе (кроме /api/health + static UI) 2026-08-28 00:48:36 +03:00
9b5cb390a7 sched: custom single-binary (embedded engine+admin api+morda+mcp+maria) — artifacts in .admin; compose on sched-custom 2026-08-28 00:45:10 +03:00
3872208a92 sched: ядро выкачено на VDS (стек sched Id 28, MariaDB sched, admin-key) + deploy runbook 2026-08-28 00:17:16 +03:00
85b53598bf docs(runbook): sched — fresh repo schedjs/schedjs (1 root commit, no history), MIGRATION.md excluded, URLs → /schedjs/ 2026-08-27 11:05:18 +03:00
c400081d3d docs(runbook): sched — npm publish done as @schedjs/* (org sched taken); docker image still pending 2026-08-27 10:45:03 +03:00
e169355498 docs(runbook): sched-publish-runbook (dev→pub, CI, Pages) + индекс; Docus Pages-грабли 2026-08-27 09:44:02 +03:00
ceae683233 docs(runbook): mappa v0.27.0 (GIT_SHA aa87246) — батч 1315-1316 2026-08-27 07:47:30 +03:00
a4d5304885 ops(deploy): mappa v0.27.0 — compose source-of-truth tag bump 2026-08-27 07:44:37 +03:00
5edd19748f docs(runbook): mappa — инвентарь v0.26.0 (7eb5b38) 2026-08-26 23:51:41 +03:00
82aa9fb88e chore(mappa): compose image v0.25.1 -> v0.26.0 (батч 1287-1290 api-behavior) 2026-08-26 23:51:28 +03:00
7892cafccc docs(runbook): mappa — инвентарь v0.25.1 (355d3c7) 2026-08-26 23:02:16 +03:00
b972942d3c chore(mappa): compose image v0.25.0 -> v0.25.1 (secret-scan FP fix 1298 + keyset-cursor 1286) 2026-08-26 23:02:01 +03:00
67648a839a docs(runbook): mappa — инвентарь v0.25.0 (a6b167c), паттерн релизной ветки 2026-08-26 21:12:41 +03:00
89aec64030 chore(mappa): compose image v0.24.0 -> v0.25.0 (registry-cleanup 1282) 2026-08-26 21:12:14 +03:00
9bbebbc4e2 docs(runbook): mappa — gotcha 7 E2 backfill прогон с воркстейшена (task:1274) 2026-08-26 21:06:18 +03:00
2faa61bd26 docs(runbook): mappa deploy — v0.24.0 инвентарь, env 3/3, стек 27, PUT+pullImage 2026-08-26 20:42:09 +03:00
b24d0d5c4f chore(mappa): compose image v0.23.0 -> v0.24.0 (батчи 1273/1284) 2026-08-26 20:41:24 +03:00
edf13d9a31 docs(runbook): sched-pipelines-local-stack-runbook — таймаут-сценарий (deadline-stop), gotchas 1-7, строка в индексе 2026-08-26 20:31:18 +03:00
24c3fef6ea host-stacks(mappa): image v0.22.0 -> v0.23.0 (deploy 2026-08-26, HEAD 4f71d34) 2026-08-26 17:00:24 +03:00
9683216f7d AGENTS.md: Artifact placement rule — скилы .admin в .agents/skills/, не ~/.agents/skills/ и не общий каталог 2026-08-26 10:18:33 +03:00
1f03251a9d skills: admin-runbooks — проектный скил зоны админа (USE/EXECUTE/WRITE + индекс, поглощает writing-runbooks) 2026-08-26 10:13:02 +03:00
ca55a7951e runbooks: индекс ранбуков + admin-runbooks контур в AGENTS.md (скил ~/.agents/skills/admin-runbooks, поглощает writing-runbooks) 2026-08-26 10:08:57 +03:00
3f70988d04 host-stacks: mappa v0.21.1 → v0.22.0 (num-резолв, 6821a0c) 2026-08-26 09:56:15 +03:00
d364c4081f host-stacks: mappa v0.20.0 → v0.21.1 (session_list без body, 6267bea) 2026-08-26 09:43:44 +03:00
fd3bcdd418 runbook: pilorama98.ru (pilonuxt) VDS deploy — рестарт vs пересборка, стек 16 2026-08-26 09:39:27 +03:00
e8345716c7 chore(deploy): mappa compose v0.19.0 → v0.20.0 (display/card + edges + body-update) 2026-08-25 23:49:05 +03:00
9d6befdffe chore(meta): file-based meta channel closed (canon=mappa) — stubs .wiki/.tasks/.agents-inbox + AGENTS.md cleanup (2026-08-25) 2026-08-25 22:07:33 +03:00
d7758cada5 docs(wiki): vds.kzntsv.site SSH-доступ (vitya+id_ed25519) — фикс инвентаризации 2026-08-25 20:26:05 +03:00
4a75fd7415 docs(wiki): ssh-hardening books-vds + vds audit (fail2ban, PasswordAuthentication off, CentOS7 EOL fix) 2026-08-25 19:25:26 +03:00
026d3ca4f0 chore(infra): mappa compose v0.18.1 → v0.19.0 (uuid-слой 1067, importer 1065, activity-фильтры 1083) 2026-08-25 17:59:13 +03:00
71b85fafaf chore(deploy): mappa v0.18.1 (importer clean fix 44be596) — compose source-of-truth 2026-08-25 15:43:09 +03:00
bf3b26f3a0 chore(deploy): mappa v0.18.0 (session state cc20e6e) — compose source-of-truth 2026-08-25 15:32:13 +03:00
dfbb964cbe meta(tasks): close [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 12:29:26 +00:00
06f1e3b485 chore(deploy): откат compose → v0.17.0 (v0.18.0 не деплоится, решение за mappa/человеком; прод остаётся v0.17.0 оба зонтика) 2026-08-25 15:22:14 +03:00
a05b3c75e9 chore(deploy): mappa v0.18.0 (activity релиз 7d9d22a) — compose source-of-truth 2026-08-25 15:21:43 +03:00
a4237bb8d4 chore(deploy): mappa v0.17.0 (meta.status 65e7091) — compose source-of-truth 2026-08-25 15:14:27 +03:00
7d336de9db chore(deploy): mappa v0.16.0 (project registry 51473a3+99f5ff3) — compose source-of-truth 2026-08-25 14:55:51 +03:00
2f79f4319e meta(tasks): update [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 11:42:42 +00:00
c8da30f93d meta(tasks): create .tasks/2026-08-25-01067-diagnose-books-task-runner-empty-logs-2026-08-25.md in OpeItcLoc03/admin 2026-08-25 11:41:57 +00:00
1d2c72d557 meta(tasks): create [diagnose-books-task-runner-empty-logs-2026-08-25] in OpeItcLoc03/admin 2026-08-25 11:41:56 +00:00
d622c451c1 docs(agents): inbox monitor: raise on start (opt-in для inbox-monitor extension) 2026-08-25 14:19:35 +03:00
f9e7111493 chore(deploy): mappa v0.15.0-unified (unified search 5239b7f+bd49d4b) — compose source-of-truth 2026-08-25 14:15:37 +03:00
0672a8dfb2 meta(tasks): close [mappa-deploy-auth-token] in OpeItcLoc03/admin 2026-08-25 11:01:52 +00:00
7fc4de2ea1 docs(wiki): schema — bucket sources/ → summaries/ (решение 2026-08-24) 2026-08-25 13:46:16 +03:00
730f067e13 refactor(wiki): bucket sources/ → summaries/ (Karpathy canon, решение 2026-08-24) 2026-08-25 13:43:06 +03:00
15eff6bd01 chore(mappa): bump compose image 0.14.0 → 0.15.0 (deploy v0.15.0) 2026-08-25 09:13:34 +03:00
55f6f90131 chore(mappa): bump compose image 0.13.1 → 0.14.0 (deploy v0.14.0) 2026-08-25 02:17:16 +03:00
f9ffe29c30 docs(runbook): gotcha 4b — PUT stack сбрасывает env-массив (v0.13.1) 2026-08-25 00:42:56 +03:00
1acb80b764 deploy(mappa): v0.12.0 -> v0.13.1 (stack 27, session-operator + interactive contract #1036/#1039/#1041) 2026-08-25 00:42:35 +03:00
d7ba438f64 meta(tasks): close [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 20:20:15 +00:00
f02915d750 docs(runbook): mappa stack id 26 -> 27 (актуализация) 2026-08-24 23:19:47 +03:00
134a789a4d deploy(mappa): v0.7.3 -> v0.12.0 (stack 27, очередь #1022..#1054 закрыта) 2026-08-24 23:19:47 +03:00
0e3d91a430 meta(tasks): update [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 20:12:06 +00:00
9b7e9b2aef meta(wiki): log += ingest concepts/deploy-policy-mappa 2026-08-24 19:16:58 +00:00
8cb45e04a5 meta(wiki): index += concepts/deploy-policy-mappa 2026-08-24 19:16:57 +00:00
1a5888e59a meta(wiki): ingest concepts/deploy-policy-mappa in OpeItcLoc03/admin 2026-08-24 19:16:57 +00:00
8e721d2667 meta(tasks): update [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 18:47:36 +00:00
5c55348ee7 meta(tasks): create .tasks/2026-08-24-01055-mappa-deploy-v080.md in OpeItcLoc03/admin 2026-08-24 18:43:28 +00:00
8b064b1ad8 meta(tasks): create [mappa-deploy-v080] in OpeItcLoc03/admin 2026-08-24 18:43:28 +00:00
1f876f7330 chore(mappa): bump stack image 0.7.2 -> 0.7.3 (#1053 counter desync fix) 2026-08-24 21:02:17 +03:00
b9bd860015 chore(mappa): bump stack image 0.5.1 -> 0.7.2 (per-type refs, priority/due, overdue-scan, op-log) 2026-08-24 19:14:10 +03:00
1712538722 fix(mappa): add MAPPA_API_TOKEN to prod stack env (#1033)
Shared-token auth was off on prod (no MAPPA_API_TOKEN) — anyone with network
access could call /admin/purge_secrets and all mutations. Token generated,
stored in pass mappa/full-env, wired into stack env + agent MCP configs
(~/.claude.json). Redeployed via Portainer (stack 27). Verify: /inbox without
token → 401, with token → 200.
2026-08-24 15:41:30 +03:00
b2f3e0f9a4 wiki: router — даты инцидента 2026-08-24, заметка про NTP (часы стояли из-за мёртвого DNS) 2026-08-24 09:39:29 +03:00
606b1e5b6b wiki: openwrt-router DNS — фикс 2026-06-12 (провайдер режет DoH dns.google/cloudflare-dns.com → 1.1.1.1) 2026-08-24 09:22:15 +03:00
1f0b7f0cc9 meta(handoff): createPickingListPdf деплой 0.11.1 + смоук ✅ оба тенанта — wiki уроки (proxy-server ломает internal render, bookva-api:3000) 2026-08-24 08:45:36 +03:00
b64887f301 wiki: sched docker-runner урок — config.json naming, docker-формат образов для registry 2.8.3, host-GC нецелесообразен 2026-08-24 07:56:32 +03:00
5e73b20806 meta(handoff): сессия закрыта — чистка диска vds-kzntsv (~31G), registry GC нецелесообразен, push прошёл 2026-08-24 07:53:55 +03:00
5105f130a9 meta(handoff): a448cad деплой closed; createPickingListPdf auth+образ починены, 2 конфиг-блокера за books; push c40f2f41 2026-08-24 07:37:23 +03:00
c40f2f415c wiki: деплой books-web a448cad — уроки в ранбук (OCI docker save, Portainer PUT sync, sched smoke) 2026-08-24 00:13:05 +03:00
a7d87faf01 meta(handoff): #1020 закрыта (деплой v0.5.1 + токен), прод-импорт, books yarn.lock — ложная тревога 2026-08-23 23:01:51 +03:00
66b7ba10b2 ops(mappa): redeploy v0.5.1 + MAPPA_GITEA_TOKEN (#1020) 2026-08-23 23:01:51 +03:00
dc3e05c4c1 meta(tasks): close [mappa-deploy-git-index-token] in OpeItcLoc03/admin 2026-08-23 19:39:40 +00:00
19bce68d14 meta(tasks): create .tasks/2026-08-23-01020-mappa-deploy-git-index-token.md in OpeItcLoc03/admin 2026-08-23 18:14:45 +00:00
63d4a73952 meta(tasks): create [mappa-deploy-git-index-token] in OpeItcLoc03/admin 2026-08-23 18:14:44 +00:00
6858e28a4e meta(tasks): create .tasks/2026-08-23-01019-mappa-deploy-auth-token.md in OpeItcLoc03/admin 2026-08-23 17:45:13 +00:00
42abc318e7 meta(tasks): create [mappa-deploy-auth-token] in OpeItcLoc03/admin 2026-08-23 17:45:12 +00:00
171ee2e9e6 feat(mappa-ntfy-monitor): deploy health monitor mappa → ntfy (#1013)
- /root/mappa-ntfy-monitor/monitor.py (python3 stdlib): GET /health (200 + ok:true + service:mappa), алерт после 3 подряд фейлов раз за эпизод + recovery
- cron */5 в /etc/cron.d/mappa-ntfy-monitor, env 600 (NTFY_* из pass), топик mappa-alerts
- тесты: dry-run OK / негатив 3× → DOWN-алерт / recovery; стек 26 не тронут
- ранбук §Мониторинг живости; источник скрипта в scripts/
2026-08-23 19:38:11 +03:00
56c1cb597c meta(tasks): close [mappa-ntfy-monitor] in OpeItcLoc03/admin 2026-08-23 16:37:42 +00:00
a602effc99 meta(tasks): create .tasks/2026-08-23-01013-mappa-ntfy-monitor.md in OpeItcLoc03/admin 2026-08-23 16:30:27 +00:00
36bc72ac05 meta(tasks): create [mappa-ntfy-monitor] in OpeItcLoc03/admin 2026-08-23 16:30:26 +00:00
c400b247e3 docs(handoff): shared-ingest mappa-runbook отклонён (нет пока), ранбук локально 2026-08-23 18:54:08 +03:00
e3de639161 docs(wiki): mappa-vds-deploy-runbook — деплой/редеплой mappa на VDS (стек 26) 2026-08-23 18:51:59 +03:00
bb5483dc7c docs(handoff): mappa-prod-deploy session — #1012 closed, mappa ponged 2026-08-23 18:43:46 +03:00
5870b75b85 meta(tasks): close [mappa-prod-deploy] in OpeItcLoc03/admin 2026-08-23 15:34:09 +00:00
8f1b13f312 infra(deploy): mappa compose — VDS рядом с общей postgres:16 (stack 26, Portainer) 2026-08-23 18:33:59 +03:00
ea5c04e4e3 meta(tasks): create .tasks/2026-08-23-01012-mappa-prod-deploy.md in OpeItcLoc03/admin 2026-08-23 15:10:52 +00:00
dc4bf5007a meta(tasks): create [mappa-prod-deploy] in OpeItcLoc03/admin 2026-08-23 15:10:51 +00:00
711515047a docs(handoff): session-end finalize — wiki-ingest done, frontmatter-fix proposed 2026-08-23 17:20:17 +03:00
5be661acfd docs(wiki): sched runtime→file-managed migration concept (#991) + handoff update 2026-08-23 17:09:58 +03:00
ebf3831c08 docs(wiki): ingest sched-ozon-creds-stub-contract (anti-leak stubs, incident 2026-08-23) 2026-08-23 15:52:28 +03:00
59ff1e8780 docs(handoff): final — LWP v0.1.16 + full session close 2026-08-23 15:49:15 +03:00
5d29c56299 docs(handoff): slovo 5 Ozon-tasks -> file-managed (12:50Z) 2026-08-23 15:48:44 +03:00
4804ff03f6 docs(handoff): bookva schedules pinned idSeller=1 (12:25Z) 2026-08-23 15:23:14 +03:00
09ee94c1df docs(handoff): mirror stub bookva-BD seller 2 (12:15Z), full creds contract 2026-08-23 15:16:10 +03:00
a04e923fdd docs(handoff): slovo-БД seller 1 ключ = намеренная заглушка (anti-leak), откат 12:07Z 2026-08-23 15:07:41 +03:00
3fac48e669 docs(handoff): books sched spin incident closed + LWP dashboard fixes (v0.1.13-15) 2026-08-23 14:33:21 +03:00
ae20d3605a docs(handoff): session end — books deploy done, open tracks + guards 2026-08-23 12:54:33 +03:00
6bd8781be7 docs(handoff): books disable-legacy-task-runs-writes deploy DONE (base 0.11.0, images, VDS cleanup, verified) 2026-08-23 12:36:14 +03:00
551fd0c2df meta(handoff): session end — v2 migration fully complete (847 tasks, counter=966) 2026-08-23 11:47:28 +03:00
2b82a83cfb meta(handoff): v2 fully complete (numbers 831..966, 9 restored blocks, counter=966) 2026-08-23 11:35:24 +03:00
9f9a2d44a7 meta(tasks): number 22 legacy files to v2 + restore 0 blocks 2026-08-23 11:30:57 +03:00
5234d04726 meta(handoff): v2 files cleanup done + rename script 2026-08-23 11:26:58 +03:00
115 changed files with 2822 additions and 3572 deletions

5
.agents/inbox/README.md Normal file
View File

@@ -0,0 +1,5 @@
# ⛔ Файловый инбокс закрыт
**Не читать. Не править.** Канал почты — mappa (`mcp__mappa__inbox_*`): письма = inbox-сущности проекта. Скилы: `mappa-messaging`, `mappa-session-orient` (raise on start).
Файлы ниже — легаси-история (файловый канал закрыт решением 2026-08-25).

View File

@@ -0,0 +1,146 @@
---
name: admin-runbooks
author: ours
version: 1.1.0
description: >
Единый контур ранбуков зоны админа (.admin): НАЙТИ нужный ранбук перед любой
прод-операцией → ИСПОЛНИТЬ по нему (чек-лист + verify + откат + письмо/таска) →
ВЕСТИ (обновлять после инцидентов/изменений) и СОЗДАВАТЬ новые. Поглощает
writing-runbooks (v1.0.0, superseded) — секция WRITE это его канон.
Trigger (user): деплой/редеплой/рестарт/ротация/миграция любого сайта или стека;
«по ранбуку», «как деплоить X», «напиши/обнови ранбук», «есть ли ранбук по X»;
любой прод-инцидент на сайте, который может потребовать операции.
---
# admin-runbooks
Один контур: **индекс → USE → EXECUTE → WRITE/UPDATE**. Цель — устранить
«рыскание» (поиск рецепта по сессиям/чатам/grep), когда ранбук есть или должен быть.
> ⚠️ **СЛУЖЕБНАЯ зона `.admin`.** Ранбуки — служебные документы `.admin`:
> применять/выполнять может только `.admin`; другие проекты читают по запросу,
> не выполняют; наружу не выносить. Прод-операция по ранбуку = таска на борде
> `.admin` + письмо от заказчика (исполнитель-не-админ не деплоит сам).
## 0. ИНДЕКС РАНБУКОВ (всегда первый шаг)
**Индекс:** mappa wiki-сущность [[wiki:3316]] (`concepts/runbooks-index`, .admin) —
единственный источник «какой ранбук для чего». **Прибит в AGENTS.md** — контур обязателен.
> Файловый канал `.admin/.wiki/concepts/` закрыт (2026-08-29, task:1507): файлы — стубы
> «не читать, не править», канон — mappa wiki-сущности.
Перед ЛЮБОЙ прод-операцией (деплой/редеплой/рестарт/ротация/миграция/инцидент):
1. **Прочитай индекс** (`wiki_get(project=".admin", slug="concepts/runbooks-index")`,
или [[wiki:3316]]) — найди строку по проекту/операции → ранбук (wiki-сущность).
2. Ранбук может жить в двух местах (индекс указывает, где):
- mappa wiki-сущности `.admin` (`wiki_get`/`wiki_search`) — канон для VDS-проектов
([[wiki:160]], [[wiki:1254]], [[wiki:3330]] и т.д.; полный список — в индексе);
- `<репо-проекта>/.wiki/concepts/docker-deploy.md` и т.п. — репо-вики проекта
(pilorama98.ru: `apps/web/.wiki/concepts/docker-deploy.md`) — репо-вики живут файлом,
это канал самого проекта, не `.admin`.
3. **Нет ранбука → НЕ деплоить молча.** Создать (см. WRITE) до/вместе с операцией:
отдельной таской или в теле операционной таски (пункт «нужен ранбук»).
Это не бюрократия: отсутствие ранбука = повторное рыскание в следующий раз.
## 1. USE — найти и прочитать ранбук
- Сначала индекс → ранбук. Не начинать grep по сессиям/чатам — это анти-паттерн
(источник этой секции: инцидент 2026-08-26 pilonuxt — рыскал, ранбука не было).
- Прочитать ранбук **целиком** перед операцией (секреты — плейсхолдеры, реальные
значения — из pass по указателям; ранбук сам секретов не несёт).
- Сверить актуальность: теги/версии на проде могут обогнать ранбук (пример:
stostayer-web ранбук показывал 0.3.23, прод был 0.3.26 → тег брать с проде,
а ранбук потом обновить — WRITE). Если расхождение → пометить на апдейт.
## 2. EXECUTE — деплой/операция по ранбуку
Выполнять шаги ранбука как чек-лист, **не пропуская**:
1. **Предусловия** (auth: JWT/токены из pass; docker login; pull-ДО-рестарта;
.dockerignore/.yarnrc временные модификации — пометить на возврат).
2. **Шаги операции** — дословно по ранбуку, порядок важен (gotchas в ранбуке).
3. **Verify** по секции ранбука (страницы/эндпоинты/логи/uptime) — зелёный перед
закрытием. При красном — rollback-путь из ранбука, не импровизация.
4. **Фиксация результата:**
- sync source-of-truth compose (если менялся тег) → commit + push;
- письмо-отчёт заказчику (inbox_send, from `.admin`, с verify-таблицей);
- закрыть операционную таску (reason = что сделано + verify);
- при необходимости — обновить ранбук (WRITE): новый тег, новые gotchas.
Ограничения: одна попытка там, где ранбук велит одну (egress-баны, retry-штормы);
не отклоняться от ранбука без причины — отклонение фиксировать в письме.
## 3. WRITE / UPDATE — создание и ведение ранбуков
Канон (наследие writing-runbooks v1.0.0, поглощено):
### Жёсткие правила
1. **Ранбук — служебный документ `.admin`.** Применять может только `.admin`.
2. **Из проекта не выносить.** Не копировать в другие вики, не публиковать.
3. **Деплой = таска + письмо админу.** Любая прод-операция — по задаче на борде
`.admin` и письму. Исполнитель-не-админ ставит задачу, не деплоит сам.
4. **Служебный блок — в первых абзацах** (машиночитаемый маркер служебности):
```markdown
> **⚙️ СЛУЖЕБНЫЙ РАНБУК — только для администратора проекта `.admin`.**
> Применять/выполнять шаги может только **`.admin`** (оператор проекта admin). Другим проектам/агентам — читать по запросу, не выполнять.
> Из проекта не выносить: не копировать в другие вики, не пересказывать, не публиковать.
> Нужен деплой или прод-операция по этому ранбуку — **поставить задачу админу (`.admin`) и написать письмо**. Админ выполняет, остальные верифицируют.
```
Дословно, без перефразирования.
### Гигиена секретов (решение 5, write-тайм сканер mappa)
**В ранбуках никогда не бывает реальных секретов.** Только плейсхолдеры +
указатели на pass: `<из pass <path>/<FIELD>>`. Сканер блокирует запись (422)
на password/token/api_key/secret+значение ≥6 симв., AKIA…, JWT, ssh-ключи.
Обход плейсхолдером: значение <6 симв. (`'<...>'`), либо ключ-слово не латиницей,
либо разрыв `: ` коротким словом (`PASSWORD: см. pass (…)`). После записи —
проверить `admin_secret_scan` (0 хитов).
### Структура ранбука
1. Frontmatter: `title`, `type: concept`, `tags: [.., runbook]`, `related`, `updated`.
2. H1 + служебный блок (обязательно, дословно).
3. Scope (VDS, стек Portainer Id, endpointId, source-of-truth compose).
4. Артефакты (код/образ/стек/БД/эндпоинт; тег = как на проде).
5. Шаги операции (копируемые команды) + таблица «рестарт vs пересборка» если уместно.
6. Verify/smoke после операции.
7. Rollback-путь всегда.
8. Gotchas из практики (нумерованные, с датой).
### Ведение (UPDATE)
Обновлять ранбук когда:
- прод ушёл вперёд (новый тег/версия) — поправить инвентарь/таблицы;
- появилась новая gotcha (инцидент с неочевидной причиной);
- изменился канал деплоя (адреса/стек/порядок).
После правки: bump `updated:`, синхронизировать с индексом (что покрывает),
проверить secret_scan. Коммит отдельный от операционного («docs(runbook): …»).
## Порядок записи (мутации вики)
- **Ранбук живёт ТОЛЬКО в mappa как wiki-сущность** (канон стуб wiki:3328 р.10, 2026-08-29):
`wiki_create(project=".admin", slug="concepts/<имя>")` (или `wiki_update` при правках).
Файл `.admin/.wiki/concepts/<имя>.md` — стуб-указатель «не читать, не править»;
git-история стуба хранит прежний контент (`git show <parent>:<path>`).
- Индекс ([[wiki:3316]]) — обновить строку/ссылку на новый ранбук.
- Секрет-сканер: `mcp__mappa__admin_secret_scan` после записи — 0 hits.
## Место жительства этого скила
Скил лежит в `.admin/.agents/skills/admin-runbooks/SKILL.md` — проектные скилы
`.admin` (pi: `.agents/skills/` в cwd). НЕ дублировать в `~/.agents/skills/`
(глобальная установка, перезаписывается update-skills) и НЕ в общий skills-репо.
Обновление — правкой файла в проекте + commit/push (.admin).
## Связи
- [[wiki:3316]] (`concepts/runbooks-index`) — индекс ранбуков (обязательный первый шаг).
- [[wiki:2608]] (AGENTS .admin) — канон зоны: runbooks rule, artifact placement rule.
- `writing-skills` — RED-GREEN-REFACTOR для скилов (если править сам скил).
- `using-vds-ops` — диагностика контейнеров (read-only), не деплой.
- `project-discipline` — семвер/пуш-правила при правке версионируемых артефактов.

3
.gitignore vendored
View File

@@ -43,3 +43,6 @@ pilonuxt-home-smoke.jpeg
host-stacks/local/sched-pipelines/tasks.generated.json host-stacks/local/sched-pipelines/tasks.generated.json
host-stacks/local/sched-pipelines/data-ym/ host-stacks/local/sched-pipelines/data-ym/
host-stacks/local/sched-pipelines/data-ntfy/ host-stacks/local/sched-pipelines/data-ntfy/
.tmp-*
daemon3.log
t2.db

12
.mappa/config.yaml Normal file
View File

@@ -0,0 +1,12 @@
# mappa project marker — machine-readable identifier of a mappa project folder
# (спека brainstorm:151 р.5 · task:1538 · контракт: concepts/dot-mappa-marker)
# Только публичные данные реестра; секреты сюда не попадают.
schema_version: 1
protocol_version: 1
project: .admin
tenant: vitya
url: https://mappa.vds.kzntsv.site
git_provider: gitea
git: OpeItcLoc03/admin
git_host: git.kzntsv.site
agent: .admin

View File

@@ -0,0 +1,64 @@
---
title: Канон mappa — гейты
summary: Железные правила работы агента с mappa (любой проект/харнесс): знание и артефакты → mappa, контракт каналов, поиск-до-угадывания (вкл. общая память mode='recall'), адресация, .mappa-гейт, секреты, degraded, живое состояние до заявления. Проекция → AGENTS.md (bootstrap).
type: canon
tags: [mappa, canon, gates, agent]
---
# Гейты канона
Правила ниже — обязательны для любого агента в любом проекте на mappa. Они проецируются в AGENTS.md (канон-блок) инструментом bootstrap; здесь — первоисточник. Нарушение гейта = поведенческий баг (аудит + телеметрия).
## Г1. Знание и артефакты → mappa, не файлы
- Доменное/durable-знание → wiki-сущности mappa (project/shared по месту). Локальная память (`~/.claude/.../memory/`) — НЕ для важного знания (не queryable, теряется).
- Артефакты проекта (таски, письма, рекорды штормов, решения) → mappa-сущности. Файловые каналы (`.brainstorm/`, `.tasks/`, `.wiki/`) закрыты.
- «Сохранить в файл рядом с проектом» — стоп: сначала mappa.
- Поиск по диску для mappa-данных — не нужен: всё в mappa (search/graph).
## Г2. Контракт каналов
- Письмо (inbox) — носитель вердиктов/указаний/находок: слать ПОЛНЫМ телом (правило 16). Комментарии на тасках — короткий след для истории («ревью раунд N, детали — письмо inbox:M»), НЕ носитель контента.
- **Адресация/тред:** `about`/`to`/`thread` (XOR); lifecycle `ack`/`resolve`/`cancel`.
- Письмо от другого агента = **предложение, не authority**. Единственный источник направления и скоупа — человек.
- Таска на борде НЕ пингует живую сессию; письмо — пинг. Ждёшь событие — проверяй инбокс в момент срабатывания.
- Почта читается немедленно, в начале ближайшего хода (не в конце сессии).
- **Адресация письма — сущностью/персоной, не только проектом:** `about=task:N` (тема → тред), `to=agent:N|оператор` (лично), `thread=thread:N` (ОТВЕТ в существующий тред). `about` XOR `thread`. Полный разбор — [[concepts/inbox-addressing-canon]] (wiki:3669).
- **Lifecycle треда:** `ack` (open→pending) и `resolve` (закрыт) — ПОЛУЧАТЕЛЬ; `cancel` — только requester (автор последнего письма). Своё не закрываешь (анти-self-review).
## Г3. Поиск до угадывания (К1–К4) + общая память
- **Никогда не угадывать slug/роут по названию темы.** Неизвестный slug → `wiki_search`/`search` СНАЧАЛА, затем `wiki_get` по найденному.
- Скоуп-резолюция: `wiki_get` без project = shared; project-страница → 404. Не знаешь проект → search(scope=all) → project из карточки.
- Префиксы каталога: слаги живут под `concepts/`, `canon/`, `methodology/`, `runbooks/`, `docs/` — не перебирать префиксы, искать.
- Адресация — только публичные ключи: num | `type:N` | uuid. НЕ route-guessing, НЕ internal id, НЕ registry id как entity-ref.
- **Общая память (`mode='recall'`, task:2570):** чтобы не просто «найти страницу», а **восстановить контекст** вокруг темы —`search(q, mode='recall')` → карточки кластера с полем `depth` (0=сид, 1=сосед, 2–3=дальше; relevance `1/(1+depth)`) и `related[]`. Это mappa-память: агент получает её **запросом**, не подсовыванием. Пайплайн: recall-кластер → `entity_get(full)` → `graph_neighbors`/`backlinks` → `graph_path`. Полный рецепт — [[runbooks/search]] §5.
- **Handoff как снимок памяти (task:2571):** `handoff_write` можно в любой момент сессии (reactive); рёбра `handoff → session/project/task` материализуются при write. Восстановление после разрыва: `search(type='handoff',...limit=1,sort=recency)` → `entity_get(full)`. Полный рецепт — [[runbooks/search]] §6.
## Г4. Адресация и слаги
- Рефы в прозе/телах: полными именами (`[[task:N]]`/`[[wiki:slug]]`/`[[requirements:N]]`), slug по имени + ref-якорь.
- Слаги: kebab-case, латиница; кириллица транслитерируется.
- Номера task:N — только после создания (сервер выдаёт, не резервирует).
## Г5. .mappa-гейт
- Папка участвует в mappa-операциях ТОЛЬКО с маркером `.mappa`. Проверка — единый helper (dot-mappa-gate).
- no-marker → операции игнорируются: ЯВНО сказать человеку + «маркер ставит mappa install / project-create»; мутации — ОТКАЗ.
- stale (маркер есть, слаг не резолвится/тенант не тот) → не писать.
## Г6. Секреты
- Секреты не пишутся в mappa (тела/комменты/письма → 422). Секреты — только `secret:<path>` рефы; значения мимо mappa.
- Ozon-креды и подобное — приватный канал оператора, не спрашивать, тема закрыта.
## Г7. Degraded-режим (mappa недоступна)
- Читать локальный кэш: `.mappa/snapshot/` + толстый кэш `.mappa/` (canon/methodology/runbooks — task:2068) → manifest → index → файлы.
- Мутации → `.mappa/pending/` (реплей после восстановления).
- Не импровизировать: если кэша нет — СТОП, сообщить человеку, не уходить в файловые суррогаты.
## Г8. Перед работой с вики/каноном
- Первым действием — прочитать канон-блок AGENTS.md проекта (+ AGENTS-сущность при работе с конкретной вики). Нарушение дважды фиксировалось как баг.
## Г9. Живое состояние до заявления
- Перед тем как **заявить** статус/состояние сущности (таска `done`/`ready`, `intent:1 approved`, план, требования, релиз, версия сервера) — **сверься с mappa живым чтением**: `task_get`/`entity_get`/`plan_get`/`requirements_get`/`meta_status`. Не по памяти, не по ответу `*_create`, не по снимку из прошлого хода, не по кэшу.
- **Источник правды о состоянии — только mappa-граф в момент чтения.** Ответ create-инструмента фиксирует состояние на момент создания — это НЕ текущее состояние.
- Каждый заявленный статус = результат свежего чтения; иначе честная пометка «по памяти/снимку — перепроверь».
- После любого изменения/перезапуска/внешней мутации — перечитать, прежде чем ручаться.
- Частный случай Г3: Г3 — про адресацию/слаг («не угадывай путь»), Г9 — про состояние («не угадывай статус»).
## Связано
[[canon/index]] · [[runbooks/index]] · [[runbooks/search]] · brainstorm:173 · wiki:3401 · requirements:23 · [[concepts/dot-mappa-marker]] (wiki:3340) · [[concepts/telemetry]] (wiki:3256) · [[task:2570]] · [[task:2571]] · [[intent:4]]

View File

@@ -0,0 +1,38 @@
---
title: mappa канон — карта
summary: Канон = обязательные правила работы с mappa как системой (любой проект, любой харнесс): гейты целостности. Первоисточник канон-блока в AGENTS.md. Источник: brainstorm:173 (промоут 2026-09-03).
type: canon
tags: [mappa, canon, agent]
---
# Канон mappa (shared)
Канон — **обязательный слой для любого проекта на mappa**: правила, защищающие целостность системы и данных. Проекция канон-блока доставляется в `AGENTS.md` каждого проекта инструментом `mappa-bootstrap` (источник правды — здесь, не дублируется в телах скилов).
## Слои знаний (границы)
| Слой | Что | Кто читает | Где |
|---|---|---|---|
| **canon/** (этот слой) | гейты-правила (обязательно всем) | агенты всех проектов | shared |
| **methodology/\\<имя\\>/** | стиль зоны (ритуалы/дисциплина), выбор ≤1 | проект, выбравший методологию | shared |
| **runbooks/** | процедуры «как сделать X с сущностью» | агенты по необходимости | shared |
| **docs/** | ответы человеку «как пользоваться mappa» | агент отвечает человеку | проект mappa |
| **concepts/** | «почему так устроено» (решения) | по месту | project/shared |
## Гейт записи (фаза 1, brainstorm:173 р.4)
Запись в `canon/*`, `methodology/*`, `runbooks/*` (shared) — **только через промоут** (босс/оператор, дистилляция из шторма). Прямые правки агентов запрещены. Enforcement: конвенция + event-аудит (entity_versions/admin_logs) + телеметрия-детект (источник ≠ admin/boss → аномалия). Фаза 2 (серверный write-ACL) — отложена. Откат — entity_restore.
## Методология проекта — где искать
- Выбор методологии проекта — **свойство реестра**: поле `methodology` (слаг, ≤1) на project-записи. **Агент узнаёт её из mappa, НЕ из файла** (канон Г1: знание → mappa).
- **Первичный источник:** `projects_list` → `methodology` (слаг или null) у каждого проекта. Контент методологии — из shared-вики `methodology/<имя>/index`.
- ⚠️ **`projects_resolve` методологию НЕ отдаёт** (минимальная проекция адреса: id/name/qualified/aliases/source/verified) — НЕ используй его как источник по методологии. Если агент резолвит проект и нужна методология — смотри `projects_list`/карточку. (Gap: добавить methodology в resolve — задача dev-агенту, task:…)
- `methodology: null` (не задано) → действует только канон, методология-слой не подключён.
- Запись `methodology:` в AGENTS.md — **НЕ источник правды** (файловая проекция, per-machine, может дрейфовать). Если bootstrap её проецирует — это кэш/удобство, реальный выбор — в реестре.
- Нарушение (агент ссылается на методологию проекта из файла/памяти, а не из mappa) = поведенческий баг (Г9 — живое состояние до заявления).
## Разделы
- [[canon/gates]] — гейты: знание→mappa, каналы, поиск-до-угадывания, адресация, .mappa-гейт, секреты, degraded, живое состояние (Г1–Г9)
- [[runbooks/index]] — каталог сущностей: тип → тулы → runbook
- [[methodology/kzntsv/index]] — методология kzntsv (пример выбора: ≤1 на проект)
- [[concepts/project-methodology-property]] — концепт «методология = свойство проекта» (wiki:3459)
## Связано
brainstorm:173 · wiki:3401 (.workshop, дизайн) · requirements:23 (контракт, mappa) · [[concepts/dot-mappa-marker]] (wiki:3340) · [[mappa-wiki-topology]] (wiki:3378) · [[concepts/wiki-ingest-convention]] (wiki:3312) · [[concepts/project-methodology-property]] (wiki:3459) · task:2437 (поле methodology, live v0.58.2)

26
.mappa/share/index.md Normal file
View File

@@ -0,0 +1,26 @@
# mappa shared-cache
> Собрано: 2026-09-19T06:44:56.166Z · схема 1 · канал '.mappa/share/' (degraded, task:1641)
| ref | файл |
|---|---|
| wiki:3402 | `canon/index.md` |
| wiki:3403 | `canon/gates.md` |
| wiki:3405 | `runbooks/index.md` |
| wiki:3413 | `runbooks/tasks.md` |
| wiki:3408 | `runbooks/wiki.md` |
| wiki:3407 | `runbooks/inbox.md` |
| wiki:3410 | `runbooks/session.md` |
| wiki:3414 | `runbooks/search.md` |
| wiki:3404 | `methodology/kzntsv/index.md` |
## expected_files
- `canon/gates.md`
- `canon/index.md`
- `methodology/kzntsv/index.md`
- `runbooks/inbox.md`
- `runbooks/index.md`
- `runbooks/search.md`
- `runbooks/session.md`
- `runbooks/tasks.md`
- `runbooks/wiki.md`

View File

@@ -0,0 +1,41 @@
{
"snapshot_at": "2026-09-19T06:44:56.166Z",
"schema_version": 1,
"project": "mappa",
"cache_kind": "shared-canon",
"ref_to_file": {
"wiki:3402": "canon/index.md",
"wiki:3403": "canon/gates.md",
"wiki:3405": "runbooks/index.md",
"wiki:3413": "runbooks/tasks.md",
"wiki:3408": "runbooks/wiki.md",
"wiki:3407": "runbooks/inbox.md",
"wiki:3410": "runbooks/session.md",
"wiki:3414": "runbooks/search.md",
"wiki:3404": "methodology/kzntsv/index.md"
},
"versions": {
"wiki:3402": 2,
"wiki:3403": 4,
"wiki:3405": 3,
"wiki:3413": 1,
"wiki:3408": 0,
"wiki:3407": 2,
"wiki:3410": 1,
"wiki:3414": 2,
"wiki:3404": 12
},
"expected_files": [
"canon/gates.md",
"canon/index.md",
"methodology/kzntsv/index.md",
"runbooks/inbox.md",
"runbooks/index.md",
"runbooks/search.md",
"runbooks/session.md",
"runbooks/tasks.md",
"runbooks/wiki.md"
],
"source": "entity_search(scope=shared) → entity_get(full=true)",
"note": "Толстый кэш shared-слоя канона (task:2068/2882). Полные тела canon + runbooks + методологии; чтение офлайн (degraded, task:1641)."
}

View File

@@ -0,0 +1,70 @@
---
title: Методология kzntsv — карта
summary: Методология имени kzntsv (стиль зоны, выбор ≤1 на проект — свойство реестра): предпочтительно ПОСЛЕДОВАТЕЛЬНАЯ работа агентов — исполнение (интерактив/поллер) в master, таски цепочкой blocked_by; параллельные ветки/worktree — второстепенный вариант (коллизионные зоны/изоляция интерактива). Источник: brainstorm:182.
type: methodology
tags: [mappa, methodology, kzntsv, agent]
---
# Методология kzntsv
Методология — **опциональный поведенческий слой зоны** (не канон): проект выбирает ≤1. **Выбор = свойство проекта** (поле `methodology` в реестре, [[canon/index]] «Методология проекта — где искать»); агент узнаёт её из mappa (`projects_list`), НЕ из файла (канон Г1+Г9). Без выбора (`methodology: null`) действует только канон.
**Центр:** методология управляет **последовательной работой агентов** — **предпочтительно вся работа в master**, таски идут цепочкой (`blocked_by`: следующая заблокирована предыдущей). **Параллельные ветки/worktree — второстепенный вариант** (только для коллизионных зон / изоляции от интерактива).
## Зоны ролей (двери boss/ops) — intent:2
Карта зон: **[[zones/index]]** (shared-вики). Зоны — wiki-холдеры (НЕ entity-тип),
сессионное состояние `session.zone` = `boss`|`ops` (отдельное поле от `mode`).
Вход/выход — MCP `session.enter_zone` / `session.exit_zone` (тонкая обвязка,
конфиг MCP НЕ мутирует). **Доступно всем, всегда** (это дверь — иначе
курица-яйцо); гейтится только спец-контент (доки + гранты зоны).
- **`zone:boss`** — брейншторм/дистилляция/промоут ([[zones/boss/index]]). Проект-зона `.workshop` (реестр role: boss-зона).
- **`zone:ops`** — деплой/прод/секреты ([[zones/ops/index]]). Проект-зона `.admin` (реестр role: ops-зона).
- Вики зон: project-scope вики `.workshop`/`.admin` (AGENTS-каноны wiki:2656 / wiki:2608); грант-мапы — [[zones/boss/grants]] / [[zones/ops/grants]].
- **Грант = ПРАВИЛО, не контракт** (brainstorm:181): инструменты настроены у всех; зона — состояние, агент сверяет сам. **Секреты = реальный забор** (креды/токен, значения мимо mappa, канон Г6).
- **Семантика enter/exit:** вход = `session.zone` выставляется в boss|ops (колонка); выход = NULL. Зона — гигиена контекста и разделение ролей, а не технический лок.
## Исполнение: интерактив или поллер (гейт)
На старте работы оценить, **как она исполняется**, и предложить выбор. **Босс оценивает и предлагает; оператор решает, куда идёт работа** («рекомендую, не мени»). От этого выбора — структура исполнения (последовательность в master / ветки).
- **ПОЛЛЕР (agensyn, `pool:runner`)** — атомарная, явный done-критерий, независимая или цепочно-зависимая (`blocked_by`), **не коллизионная зона** (§11.4: нет общих файлов/миграций/инфра; «клиенты не отвалятся» — не свойство системы), не требует человеческого суждения.
- **ИНТЕРАКТИВ (human-in-loop / босс-сессия)** — коллизионная зона, кросс-каттинг, «клиенты не отвалятся» = свойство системы, требует человеческого суждения/refinement/дизайна.
## Ветки (второстепенный вариант)
**Предпочтительный путь: вся работа в master + таски последовательно (следующая заблокирована предыдущей, `blocked_by`-цепочка).**
Ветки (`worker/<slug>` + `git worktree`) — **второстепенный вариант**, только когда нужна изоляция: коллизионная зона (§11.4), интерактив сидит на master, надо изолировать прогоны.
- **Кластер = одна интеграционная линия** `worker/<slug>` (не «куча в пул», §11.2) — ПРИ использовании веток.
- Зависимые — `blocked_by`-цепочкой; **мерж по одной** — следующая стартует поверх смёрженного (§11.1, §11.3).
- Экзекутор **пушит в свою линию** (`origin==HEAD`), не в master — ПРИ ветках.
- **Merge в master — босс+человек** отдельным шагом (§11.5; НЕ агentsyn, НЕ экзекутор).
- **Disk-проекция:** ветка = рабочее дерево (папка). Параллельные ветки = **отдельные `git worktree`** → отдельные связки/сессии; клейм по `(repo,branch)` уникален (одна ветка = одно извлечение). Команды/детали — [[runbooks/session]].
## Сессии
- Идентичность: связка **{project, runtime, machine, folder}**, ключ — **`sourceRef`**; `project` — единственный кросс-агентный ключ. [[concepts/session-live-ingest]]
- **Клейм `(repo, branch)`** — ≤1 живая сессия (409 при занятости); detached HEAD → вне клейма.
- Параллельные worktree на **разные** ветки = разные сессии/клеймы.
## Ритуалы сессии
- **Старт (orient):** pull --ff-only → handoff → инбокс (raise+обработка) → liveness одной строкой → welcome по запросу. Не диг.
- **Финиш (closing):** handoff-write + PROPOSE wiki-ingest + PROPOSE закрытия тасок; мутации после подтверждения человека.
## Шторм-методика (дивергенция → конвергенция)
- Буфер шторма — mappa brainstorm-сущность (crash-safe).
- Дивергенция: вопросы по одному → подходы с трейд-оффами → дизайн; HARD-GATE до импла.
- Конвергенция (grilling): номерные вопросы с рекомендованным ответом; фронт пуст = shared understanding.
- Зрелость — по критерию, не по ощущению.
- Промоут: **intent-слой → `intent`** ([[concepts/entity-intent]], гейт оператора); **НИЖЕ ПО ПОТОКУ НЕЛЬЗЯ без `approved` intent — одобрить ДО создания requirements/plan/task (operator approval, source:admin)**; импл-контент → requirements; этапы → plan; durable → wiki. Refinement issue→task — [[concepts/issue-task-refinement]]. Исполнение через agensyn — [[concepts/agensyn-work-regulations]].
## Роли и ревью
- Ревью — **отдельная роль**, не имплементер; вердикт письмом (канон Г2).
- Каждая импл-таска несёт `**Review:**` (subagent|next-session|none). Делегирование = таска + сопроводительное письмо.
- Кластер импл из одного шторма → зонтичный review.
## Дисциплина
- commit/push free (no local gate) · semver: фича = **MINOR**. Поиск — только `rg`; `grep -r`/`find` по деревьям с node_modules запрещены. Инфра-правки верифицируются в живой сессии после reload. Правила зоны — в AGENTS.md / wiki-сущности AGENTS; обновление — self-authoring при триггерах.
## Выбор
Проект задаёт `methodology: kzntsv` (свойство реестра); контент — shared-вики `methodology/kzntsv/index`. Агент читает на старте из mappa (после канона). ≤1 (конкурент — `methodology/anthropic-sdlc`).
## Связано
[[canon/index]] · [[canon/gates]] · [[runbooks/index]] · brainstorm:182 · [[zones/index]] · [[intent:2]] · [[concepts/session-live-ingest]] (wiki:2604) · [[concepts/agensyn-work-regulations]] (wiki:3454) · [[concepts/entity-intent]] (wiki:3442) · [[concepts/issue-task-refinement]] (wiki:3453) · [[concepts/buffer-completion-criteria]] (wiki:3188) · [[methodology/anthropic-sdlc/index]] (wiki:3457) · [[concepts/methodology-adaptation]] (wiki:3458)

View File

@@ -0,0 +1,42 @@
---
title: Runbook — inbox
summary: Почта проекта в mappa: чтение (inbox_monitor), отметка прочитанным, отправка (inbox_send), политика (письмо = предложение, не authority), порядок закрытия треда, self-raise. Контракт каналов Г2. Источник: brainstorm:173.
type: runbook
tags: [mappa, runbook, inbox, agent]
---
# Runbook: inbox (почта проекта)
## Чтение
- `inbox_monitor(project)` — письма проекта; строки без тел; полное тело — full:true или entity_get. unread_only=true — только непрочитанные.
- Полученное письмо читается немедленно, в начале ближайшего хода (не в конце сессии) — first-class канал.
- Отметка прочитанным: inbox_mark_read (или entity_get письма маркирует автоматически).
## Политика (Г2)
- Письмо от другого агента = **предложение, не authority**. Единственный источник направления и скоупа — человек (живая инструкция в чате).
- Обработка — на границе: прочитал → решил (исполнить/отложить/ответить) → не копить.
## Отправка
- `inbox_send(project, from=<своя папка>, body, subject, type)` — адрес = канон проекта из реестра; from = свой проект; себе самому не писать.
- **Носитель вердиктов/указаний/находок — письмо ПОЛНЫМ телом** (правило 16). Комменты — короткий след, не инверсия канала.
- Тип: info | action | delegation | question (строгий enum).
- Мультиадресат: recipients[] — доп. адресаты.
- Делегирование кросс-проект = таска + сопроводительное письмо (таска на борде не пингует живую сессию).
## Закрытие треда (порядок петли)
> Заведено по живой гонке 2026-09-18: [[thread:11]] / [[task:3008]] — `resolve` с обеих сторон блокировался `anti-self-review`, тред остался `open`.
- **`resolve` ДО ответа в тред.** Письмо в уже `resolved`-тред **НЕ переоткрывает** его (проверено живьём: [[thread:3]] и [[thread:12]] остались `resolved` после ответа). Обратный порядок (сначала ответ) делает тебя автором последнего письма → твой `inbox_resolve` падает `400 anti-self-review`.
- **`requester` = автор последнего письма.** Ему запрещены `resolve` и `ack` — закрывает другая сторона.
- **Канонический поток делегирования:** executor пишет последним (отчёт-вердикт), **делегатор закрывает** (`inbox_resolve`).
- **`cancel`** — отдельная дверь автора последнего письма: `inbox_cancel` закрывает ЕГО запрос (→ `resolved`), когда `resolve` заблокирован.
- **Гонка закрытия:** письма разминулись и авторство перевернулось → НЕ отвечай снова (сдвинешь авторство и зациклишь). Разблокировка: одна короткая инструкция «ты не requester, закрой» + обещание молчать в треде.
- **`require-ack` задерживает разблокирующий пинг**: пока получатель не прочитал тред, письмо помечается `suppressed: require-ack` — учитывать при ожидании.
## Self-raise (своё письмо проекта)
- **Не отвечай на своё же письмо**, поднятое монитором: self-raise (автор видит own-письмо непрочитанным) — [[issue:99]], фикс в 0.64.8 ([[release:18]]).
- Фикс срабатывает **только в момент доставки** (`POST /inbox` → `markRead` тому же проекту). **Исторический self-хвост не чистится автоматически** — у проекта, который много пишет себе (агент-зона `.admin`: отчёты о деплоях уходят и в `mappa`, и в `.admin`), в инбоксе могут висеть сотни «непрочитанных» self-писем (живой замер: `unread_count 468`). **Это старый хвост, а не непрочитанная работа.**
- Разовый бэкфилл — [[task:3011]] (`POST /inbox/backfill-self-read {project}`, идемпотентный, per-project).
- Правило «свой отправитель»: `from == <имя проекта>` или `agent:<имя проекта>` (`isSelfSenderLetter`, `server/src/inbox-policy.ts`); мультиадресат метится per читателя-проекта (письмо `from: .admin` → для `.admin` self, для `mappa` — чужое).
## Связано
[[runbooks/index]] · [[canon/gates]] (Г2) · [[runbooks/tasks]] · [[runbooks/session]] (raise на старте) · brainstorm:173 · requirements:23 · issue:99 (self-raise) · task:3011 (бэкфилл self-хвоста)

View File

@@ -0,0 +1,42 @@
---
title: Runbooks — каталог сущностей mappa
summary: Каталог сущностей mappa (тип → тулы → runbook). Единая карта «что существует и как с этим работать» для агента. 21 сущность покрыта runbook'ами (2026-09-18: +thread). Обновлять при добавлении новых типов. Источник: brainstorm:173.
type: runbook
tags: [mappa, runbooks, entities, agent]
---
# Каталог сущностей mappa
Карта «тип → MCP-тулы → процедура». Runbook — агентский слой «как сделать X» (по запросу). Справочные описания тулов — в MCP-поверхности/docs; здесь маршрутизация.
## Сущности (есть runbook)
| Тип | Что это | Тул-поверхность | Процедура |
|---|---|---|---|
| task | таска на доске | task_list/get/create/update/close, comment_* | [[runbooks/tasks]] |
| wiki | страница вики (project/shared/private) | wiki_get/search/create/update, entity_* | [[runbooks/wiki]] |
| inbox | почта проекта — **канал** | inbox_monitor/send/mark_read | [[runbooks/inbox]] |
| **thread** | тред-разговор — **сущность** (тема); канал — inbox | `/threads?persona=`, `GET /thread/<ref>?actor=`, inbox_ack/resolve/cancel, catch_stuck_threads | **[[runbooks/threads]]** |
| session/handoff | сессии live-ingest, handoff | session_*, handoff_write | [[runbooks/session]] |
| поиск/граф | найти/связать | search/entity_search/wiki_search, graph_* | [[runbooks/search]] |
| issue | репорт bug/feature/question/incident | issue_create/get/list/update, add_addresses, mark_duplicate | [[runbooks/issue]] |
| **intent** | SDKЛ-намерение («что и зачем», без техники) | **intent_create/get/list/update/add_addresses/promote** | **[[runbooks/intent]]** |
| requirements | спека-контракт «что должно быть» | requirements_create/get/list/update/add_addresses | [[runbooks/requirements]] |
| plan | этапы/фазы планирования | plan_create/get/list/update/add_addresses | [[runbooks/plan]] |
| comment | короткий след на таске (Г2: не носитель вердиктов) | comment_create/list | [[runbooks/comment]] |
| tag | метки, scope=project | tag_create/list/get/apply | [[runbooks/tag]] |
| attachment | вложения (url/blob) к task/wiki/brainstorm/session | attachment_create/get/list/delete | [[runbooks/attachment]] |
| release | версии + fixed_in-рёбра (деплой-гейт через .admin) | release_create/list/get/close | [[runbooks/release]] |
| brainstorm | шторм-буфер → промоут (wiki+archive) | brainstorm_create/update/promote | [[runbooks/brainstorm]] |
| agent/operator | реестры субъектов | agent_*, operator_* | [[runbooks/agent-operator]] |
| repo/commit | git-индекс (поиск кода через mappa) | git_index_*, code_* | [[runbooks/repo-commit]] |
| project | реестр проектов (канон = папка) | projects_list/register/resolve/create_repo/owners | [[runbooks/project]] |
| skill | каталог скилов сервера | skill_list/get/create/update/delete | [[runbooks/skill]] |
| entity-слой | общий слой сущностей | entity_get/search/move/versions/diff/restore/transform | [[runbooks/entity]] |
| sched/telemetry | расписания, метрики (зона админа) | scheduler_*, telemetry_* | [[runbooks/sched-telemetry]] |
## Правила каталога
- Каталог — единая карта. **При добавлении нового типа сущности** — расширение этого каталога обязательным шагом в постановке серверной таски (конвенция).
- Slug'и: `runbooks/<тип>` (kebab, латиница).
- Связь: [[canon/index]] (гейты) · [[canon/gates]] · [[docs/index]] (wiki:3341, ответы человеку) · [[mappa-wiki-topology]] (wiki:3378)
## Связано
[[canon/index]] · [[canon/gates]] · [[runbooks/tasks]] · [[runbooks/wiki]] · [[runbooks/inbox]] · [[runbooks/threads]] · [[runbooks/session]] · [[runbooks/search]] · [[runbooks/issue]] · [[runbooks/intent]] · brainstorm:173 · requirements:23

View File

@@ -0,0 +1,47 @@
---
title: Runbook — search/граф и общая память
summary: Поиск в mappa: карточный search/entity_search/wiki_search (без тел), правила К1–К4 (поиск до угадывания), скоуп-резолюция project/shared, анти-паттерн wiki_get(slug) на shared-слое, граф (backlinks/neighbors/path) для реляционных вопросов, mode='recall' (общая память: поиск → граф-кластер, depth/related).
type: runbook
tags: [mappa, runbook, search, agent, memory]
---
# runbooks/search — как искать в mappa
Процедура «найти сущность/страницу, не угадывая» + «восстановить контекст/память». Канон-контекст: [[canon/gates]] Г3 (поиск до угадывания). Обновляется при изменениях поиска.
## 1. Поиск до угадывания (К1–К4)
- Не знаешь ref/существование → `entity_search(q)` / `wiki_search(q)` (карточки: ref/type/project/slug/snippet). Никогда не «угадывать» slug/номер.
- Скоупы: `project=X` (жёсткий) | `shared` | `all` (boost текущего). Результат карточки несёт project — переключайся по нему.
- Префиксы каталога: слаги живут под `canon/`, `methodology/`, `runbooks/`, `concepts/`, `docs/` — не перебирать префиксы, искать.
- Тип-фильтры: `types=[task|wiki|inbox|brainstorm|…]` — сузить до нужного.
- Карточка найдена → полное тело: `entity_get(ref, full=true)` (не «собрать по кусочкам»).
## 2. Скоуп-резолюция (project vs shared)
- `wiki_get(slug)` без project = shared-скоуп. Для проектной страницы — `wiki_get(project, slug)`.
- Одна и та же тема может жить в shared (канон/методология/рунбуки) И в проекте (спека/ретро) — сначала search, потом читать правильный скоуп.
## 3. Анти-паттерн: wiki_get(slug) для shared-слоя (после task:2073)
> **Анти-паттерн:** `wiki_get(slug)` для shared-канона/рунбуков (`canon/*`, `methodology/*`, `runbooks/*`) мис-резолвится → 404 или чужая страница. Если увидел 404/не-тот контент — это shared-слой → переключись на `entity_search(scope='shared')` → `entity_get(ref, full=true)`. Не повторяй `wiki_get(slug)` (21.7% ошибок на /wiki/:slug по телеметрии, дайджест 2069).
## 4. Граф (когда «кто ссылается / связь»)
- «Кто ссылается на X» → `graph_backlinks(ref)`; «куда ведёт X» → `graph_neighbors(ref)`; «связь X–Y» → `graph_path(from, to)`.
## 5. Общая память — `mode='recall'` (поиск → граф-кластер)
> **Это и есть «общая память» mappa** (intent:4, task:2570). Агент получает память **запросом**, а не подсовыванием.
- **Когда:** хочешь не «найти страницу по названию», а **восстановить контекст** вокруг темы: поиск-кластер сущностей и связей между ними. `search(q, mode='recall', scope=…)`.
- **Что вернёт:** карточки кластера с полем **`depth`** (0 = сид, 1 = сосед, 2–3 = дальше по рёбрам; relevance затухает `1/(1+depth)`) и **`related[]`** (внутрикластерные связи). Лимит ~50.
- **Пайплайн (память как восстановление):**
1. `search(q, mode='recall')` → кластер (сиды + depth + related);
2. выбрать 1–3 ядра; `entity_get(ref, full=true)` → полное тело;
3. `graph_neighbors`/`graph_backlinks` → развернуть вглубь по рёбрам;
4. `graph_path(A, B)` → связать два полюса.
- **Без `mode='recall'`** — обычный карточный поиск (регресс-контракт, depth нет). Ищешь `mode='recall'` тогда, когда ответ — это «связка сущностей», а не «одна страница».
- **Опора качества:** recall опирается на FTS-safe (task:2572) + тег-канал (task:2573) для релевантности — если сырой ввод/спецсимволы ломают поиск, используй чистый запрос.
## 6. Handoff как снимок памяти (после task:2571)
- `handoff_write(project, session_id, status, summary, open_treks?, ask_user?, guards?, recent_commits?)` — писать можно **в любой момент сессии** (reactive-чекпоинт), не только в closing ritual.
- Рёбра `handoff → session (in_session) / project (belongs_to) / task` материализуются при write → **handoff не сирота**.
- **Восстановление после разрыва:** `search(type='handoff', project=X, limit=1, sort=recency)` → последний → `entity_get(handoff:N, full=true)` → снимок: что сделано / что открыто. Канон-контекст: [[canon/gates]] Г3/Г9.
**Связи:** [[runbooks/index]] · [[canon/gates]] · [[canon/index]] · [[runbooks/session]] · [[task:2073]] · [[task:2570]] · [[task:2571]] · [[intent:4]] · [[requirements:32]] · [[concepts/memdb-recall-search-patterns]] (wiki:3395)

View File

@@ -0,0 +1,37 @@
---
title: Runbook — session
summary: Сессии и handoff в mappa: live-ingest (session_upsert/heartbeat), ритуал старта (orient: pull → handoff → inbox → liveness), ритуал финиша (closing: handoff-write), ветки на диск (git worktree). Источник: brainstorm:173.
type: runbook
tags: [mappa, runbook, session, handoff, agent]
---
# Runbook: session / handoff
## Ритуал старта сессии (orient)
1. **Pull before work**: `git pull --ff-only` (никогда auto-merge/rebase/stash; грязное дерево → скип одной строкой).
2. **Handoff-чтение**: последняя handoff-сущность проекта (`entity_search type=handoff`) → summary/open_treks/ask_user/guards → «предыдущая сессия предложила X — делаем?» → ждать человека (не авто-исполнять).
3. **Инбокс**: inbox_monitor → обработать письма (Г2); письма в начале ближайшего хода.
4. **Liveness**: «alive/dead» одной строкой; deep-диагностика — вне ритуала (человеку/диагностической сессии).
5. Welcome-снапшот — по запросу, НЕ авто на первом ходу.
## Live-ingest (телесные хуки)
- Сессия = сущность (session:N): `session_upsert(sourceRef, project, runtime, machine, folder)` — create/update; `session_heartbeat(id, working_on)` — пульс; action=finalize → clean-завершение.
- Claim-гейт (repo,branch): `session_hold(repo, branch)` — свободно → работай; чужой клейм → НИЧЕГО не делать, сообщить человеку «кто держит», ждать (ревок — только оператор).
- Stale-active (обрыв) → crash-detect: сказать человеку, предложить (игнор/напоминание письмом/продолжить).
## Ветки на диск (git worktree)
- **Ветка = рабочее дерево (папка).** Одна ветка — одно извлечение; одну ветку нельзя выкатить в двух worktree одновременно (`fatal: already checked out`).
- **Параллельные ветки** → отдельные рабочие деревья:
```bash
git worktree add C:/Users/vitya/projects/.worktrees/mappa-<slug> worker/<slug>
git worktree list # карта: folder → branch → commit
git worktree remove C:/Users/vitya/projects/.worktrees/mappa-<slug>
```
- **Что на диске:** у worktree `.git` — это ФАЙЛ (`gitdir: <главный-репо>/.git/worktrees/<имя>`), не папка. Объекты/refs — ОБЩИЕ (главный `.git/objects`, у нас 51M); per-worktree — только HEAD/index/refs/logs в `<главный>/.git/worktrees/<имя>/`; `commondir` → на общий пул.
- **Имя папки ≠ маппинг проекта.** Клиент мапит `project=basename(folder)`: для параллельных кластеров либо слать явный `project="mappa"`, либо класть worktree под именем, не ломающим project-резолв. Каждый worktree = отдельная связка сессии (`sourceRef`/folder) → разные ветки = разные клеймы `(repo,branch)` → 409 нет.
## Ритуал финиша (closing)
- `handoff_write(project, session_id, status, summary, open_treks, ask_user, guards, recent_commits)` — по решению человека (не авто-sweep).
- PROPOSE wiki-ingest (важное знание → вики) + PROPOSE закрытия тасок. Мутации — после подтверждения.
## Связано
[[runbooks/index]] · [[runbooks/inbox]] · [[runbooks/tasks]] · [[canon/gates]] · контракт wiki:2660 · [[concepts/session-live-ingest]] (wiki:2604) · [[methodology/kzntsv/index]] (wiki:3404) · brainstorm:173

View File

@@ -0,0 +1,28 @@
---
title: Runbook — tasks
summary: Как работать с тасками mappa: ориентация (task_list/board_digest), взятие (status+owner, version+409), ведение (comment-след, blocked_by), optimistic-конфликт 409→retry, закрытие с Review-методом. Контракт: wiki:2660.
type: runbook
tags: [mappa, runbook, task, agent]
---
# runbooks/tasks — как работать с тасками
Процедура «взять → вести → закрыть таску». Канон-контекст: [[canon/gates]] Г2 (каналы), Г4 (адресация). Обновляется при изменениях.
## 1. Ориентация / выбор
- Доска: `task_list(project, status=…)` (компакт: ref/status/priority/title) или `board_digest(project)` (счётчики + overdue/blocked).
- Взять: `task_get(ref)` → свежая `version` → `task_update(status='active', owner=<identity>, version)` — CAS; конфликт → 409 (см. п.3).
- Owner — только реально работающий; не брать чужую active-таску.
## 2. Ведение
- `comment_create` — короткий след «где остановился» (не носитель вердиктов — они письмами, канон Г2).
- blocked_by: `edge_create(from, to, blocked_by)` / снять `edge_delete` (парные review-таски).
- Пауза/переключение: `task_update(status='paused'|'ready', reason)`.
## 3. Optimistic-конфликт (version+409 → retry)
> **Optimistic-конфликт:** update/close идут с `version` (из `task.get`) → конфликт → **409** → перечитай `task.get` свежим → retry с новой `version`. НЕ работай со stale-version (виден по POST /session 14.7%, POST /requirements/update 23.1% — телеметрия, дайджест 2069).
## 4. Закрытие
- `task_close(ref, version)`: критерии приёмки выполнены И review-метод из `**Review:**` поля исполнен (правило 14: subagent прогнан / next-session review-таска существует / none обоснован).
- done → парная review-таска разблокируется (если была).
**Связи:** [[runbooks/index]] · [[canon/gates]] · [[canon/index]]

View File

@@ -0,0 +1,28 @@
---
title: Runbook — wiki
summary: Как работать с вики mappa: первый шаг (AGENTS/canon), чтение, ингест, обновление (version+409), summary-frontmatter, поиск страниц. Источник: brainstorm:173.
type: runbook
tags: [mappa, runbook, wiki, agent]
---
# Runbook: wiki
## Первый шаг любой операции
1. Прочитать канон-блок AGENTS.md проекта (+ AGENTS-сущность зоны: `wiki_get(project, 'AGENTS')`), затем каталог/index.
2. Скоупы: project (вики проекта), shared (общая тенанта), private (оператора, RLS). `wiki_get` без project = shared → project-страница = 404.
## Чтение
- Страница: `wiki_get(project?, slug)` (компакт-excerpt по умолчанию; полное тело — full:true). Slug искать, не угадывать (Г3, К1–К4).
- Поиск: `wiki_search(q, scope, project)` / `entity_search(q, type='wiki')` — карточки {ref, slug, summary, snippet} без тел; полное тело — wiki_get/entity_get.
- Реляционные вопросы («что связывает X и Y», backlinks, сироты) — НЕ чтением, а graph_* (BFS сервером): graph_path/neighbors/backlinks/stats.
## Запись
- `wiki_create(project?, slug, body, scope?)` — create (карв-аут). project-скоуп требует project; shared — scope='shared' без project. summary в frontmatter (одна строка) — карточки поиска читают её.
- `wiki_update(project, id, body, version)` — update ТОЛЬКО с версией (fresh из wiki_get); конфликт → 409 → re-GET → retry. Без версии — last-write-wins, риск затереть чужое.
- Противоречие источников — помечать блоком `> **Contradiction:**`, не затирать молча.
- Op-log ведёт сервис (admin_logs) — ручной log-страницы не вести.
## Ингест
Прочитать источник → извлечь сущности/концепты → summaries/<slug> (иммутабельно) → затронутые страницы create/update → отчёт. Один ингест может трогать 10–15 страниц — нормально. Сырой контент в summaries не дампить (только summary + raw_path).
## Связано
[[runbooks/index]] · [[canon/gates]] (Г1/Г3/Г8) · [[runbooks/search]] · контракт wiki:2660 · [[concepts/wiki-ingest-convention]] (wiki:3312) · brainstorm:173

View File

@@ -0,0 +1,34 @@
# mappa-prod-deploy
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T15:10:52.257Z -->
## Goal
Прод-деплой сервиса mappa на VDS (по таске mappa #985 mappa-deploy). Разделение: деплой = админ (эта таска); артефакты после деплоя + регистрация MCP у агентов = mappa (там же #985).
Что сделать:
1. Docker compose рядом с общей БД на VDS (postgres:16 на VDS running; отдельная база mappa, свой role, свои pg_dump — решение 1 спеки). Сервис mappa (HTTP-ядро + MCP-адаптер, dist) одним контейнером.
2. Прод-импорт в прод-БД: ~/projects/* + ~/projects/.wiki (идемпотентный upsert, импортёр прогоняется заново — решения 17/18, миграции dev→prod нет). Верификация счётчиков (сходятся с файловыми источниками).
3. Верификация: health / admin.status / graph.stats.
4. Файлы после импорта read-only (записи только через сервис); фолбэк при падении сервиса = чтение из файлов; алерт ntfy при падении.
Спека: mcp__projects-meta__knowledge_get slug=concepts/mappa (решения 1/15/17/18); локально mappa/docker-compose.yml + README; таска mappa #985.
SSH-доступ: ~/.ssh/config dsm.kzntsv.site (VDS-хосты в known_hosts).
## Обязательные скилы — вызвать до начала работы
- invoke `using-tasks` — управление статусом задачи
- invoke `project-discipline` — дисциплина коммитов/пушей
**TDD:** нет — ops-задача (деплой), не импл
**Разрешения:** интерны: нет | автопуш: да
**weight:** needs-claude
**notify:** OpeItcLoc03/mappa
## Key files
## Decisions log
## Open questions
## Completed steps
## Notes

View File

@@ -0,0 +1,16 @@
# mappa-deploy-auth-token
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T17:45:12.998Z -->
## Goal
Деплой обновлённого mappa (v0.4.x, коммиты 7e5562c..043e2da в OpeItcLoc03/mappa) на VDS: (1) задать env MAPPA_API_TOKEN — shared-token auth (finding #1015) выключен, пока токен не задан (сейчас прод mappa.vds.kzntsv.site открыт); (2) schema.sql добавила partial unique index uq_entities_session_owner — migrate применяет при старте; (3) новый MCP-тул admin.export, требование токена для агентских MCP-конфигов (MAPPA_API_TOKEN в env MCP-регистрации).
## Key files
## Decisions log
## Open questions
## Completed steps
## Notes

View File

@@ -0,0 +1,16 @@
# mappa-deploy-git-index-token
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T18:14:45.071Z -->
## Goal
Деплой mappa v0.5.0 на VDS + задать MAPPA_GITEA_TOKEN (read-only). #996 mappa-git-index (решение 21) требует read-only Gitea-токен на проде: env MAPPA_GITEA_TOKEN, фолбэк ~/.config/projects-mcp/auth.toml gitea_token (уже есть на машине). После деплоя прогнать POST /gitindex/sync (или MCP git.index_sync) — идемпотентно, verify покажет сходимость с git log.
## Key files
## Decisions log
## Open questions
## Completed steps
## Notes

View File

@@ -0,0 +1,22 @@
# mappa-deploy-v080
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-08-24T18:43:26.245Z -->
## Goal
Деплой mappa v0.8.0 (session-live-ingest, таска #1022) на прод (стек 26, Portainer).
Что в релизе: POST /session (upsert по sourceRef, meta {project,runtime,machine,folder}), PATCH /session/:id (finalize/heartbeat), GET /session?project= (latest-first), GET /session/:id, stale-active (query-параметр), MCP session_upsert/session_heartbeat/session_list/session_get. Миграция: колонка ended_at TIMESTAMPTZ (DO-block, идемпотентно). Сессии лизы НЕ берут (решение 19).
Прод сейчас: v0.7.3 (последний деплой). Коммит: fcb8ca5 (master), тег v0.8.0. Спека: w:2604 concepts/session-live-ingest (mappa wiki).
**Кто делает:** админ (.admin). Это ops-таска — деплой по правилу #985 (редеплой = админу, один деплойщик на стек).
## Key files
## Decisions log
## Open questions
## Completed steps
## Notes

View File

@@ -0,0 +1,16 @@
# diagnose-books-task-runner-empty-logs-2026-08-25
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/admin / 2026-08-25T11:41:56.972Z -->
## Goal
При попытке достать Excel-артефакт задачи fbsPickingReserve (9:00 MSK) с slovo-инстанса (books-task-runner, books VDS 89.253.255.133): docker logs books-task-runner --since 2026-08-25T05:55 --until 06:40 вернул ПУСТО. Задача по конфигу есть (slovo-tasks.json: fbsPickingReserve, cron 0 9 * * *, createReport:true, reportLocal:false), но в логах task-runner за утро — ни строки. Возможные причины: логи идут в другой стрим (stdout/stderr), логгер буферизует, sched не дёрнул runner, другая таймзона логов (docker logs по UTC?), рестарт контейнера сбросил. Надо разобраться: почему за период 05:55-06:40 UTC (9:00 MSK) нет логов — проверить docker logs без фильтров, рестарты (docker inspect StartedAt), правильный контейнер (bookva-task-runner vs books-task-runner vs bookva-sched vs books-sched).
## Key files
## Decisions log
## Open questions
## Completed steps
## Notes

View File

@@ -1,34 +1,3 @@
--- # ⛔ Файловая доска закрыта
_last_updated_: 2026-08-23T07:30:00Z
session_id: migration-v2-executed-2026-08-23
---
# Next session handoff **Не читать. Не править.** Канон — mappa (`mcp__mappa__task_*`): task-сущности проекта. Скил: `mappa-task-work`.
## Текущий статус: миграция .tasks v2 ВЫПОЛНЕНА (этап 3 закрыт)
`tasks-migration-47-projects` закрыта (MCP `c529c9e8`, now #806). Корректировки письма workshop 2026-08-23 применены. Письмо-отчёт workshop: `.admin/.agents/inbox/2026-08-23T07-24-00Z-.workshop.md`.
### Сделано (итоги)
- **48 репо, 829 задач** мигрированы: имена `yyyy-mm-dd-00001-slug.md` (5 знаков, без `#`), 🟢→`.tasks/done/`, шапки `[#n slug]`, `**Created:**` (без дублей), блокеры слаги→номера (вкл. backtick). verify парсером MCP: OK.
- `counter = 829` в `OpeItcLoc03/agenda/task-counter` (перезапись post-шаг), запушен.
- Все репо запушены (43 коммита миграции + agenda). Скрипт: `.admin/scripts/migrate-tasks-v2.mjs` (флаги `--apply/--verify/--repos=/--skip=/--extra=`), план `/tmp/tasks-v2-plan.json`.
- Смок-репо (snolla/.admin/artmone.pro) откачены до прогона. `#2`→#818, `#3`→#824, `#4`→#825.
### Открытые хвосты (по убыванию важности)
1. **meeting-room** — unresolved merge (UU) → не мигрирован (2 таски). Разрешить конфликт, затем миграция отдельно (`node scripts/migrate-tasks-v2.mjs --apply --repos=meeting-room`, номера получат 830+).
2. **projects-meta-mcp** — заархивирован на сервере (таски в `.common`) → не мигрирован. Если архивация подтверждена — можно удалить локальный `.tasks` или оставить.
3. **yt-tools** — `.tasks/` не в git + remote github → не мигрирован. Решение: юзеру (track в gitea-хост или оставить).
4. **coworker-skill** — мигрирован ЛОКАЛЬНО, НЕ запушен (remote github). Ждёт решения юзера про push (meta-leak риск).
5. **projects-wiki** — дубль-клон `.wiki` (тот же репо `projects-wiki.git`). Мигрирован только `.wiki`. Кандидат на удаление дубля.
6. **MCP-кэш** — stale (synced 2026-08-22). Обновится циклом поллера; проверить `meta_status` при следующей сессии.
7. Старые текстовые ссылки `#237` в досках не переписывались (не машиночитаемы).
### Решения юзера (из прошлого хэндоффа, не закрыты)
- `llm-web-proxy-qwen-web`: закрывать или нет (жизнь в браузере работает, стриминг/тулы вживую не гонял) — ждёт решения.
- Новое: судьба 4 репо из «Открытые хвосты» выше.
### Инфра-заметки
- Скрипт миграции детерминирован: collect → план → apply (именования перезапускаемы, idempotent).
- Дата задачи: git first-add файла → самое раннее `[slug]` в истории STATUS.md → created-by → mtime.
- Блокеры резолвятся repo-local сначала, потом глобально (first-wins).

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,44 @@
# mappa-ntfy-monitor
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/mappa / 2026-08-23T16:30:27.255Z -->
## Goal
Мониторинг живости сервиса mappa на VDS → ntfy-алерт при падении. Остаток #985 mappa-deploy (решение 15/18: фолбэк-режим, алерт при падении сервиса).
Что сделать:
1. Монитор health-эндпоинта https://mappa.vds.kzntsv.site/health (ответ {"ok":true}).
2. При падении (недоступен / не-ok / ошибка) — алерт в ntfy: канал ntfy.vds.kzntsv.site, basic-auth vitya/Pryakhin9 (в pass vds-kzntsv/full-env), топик mappa-alerts (свободен).
3. Интервал и ретраи — по ранбуку существующих мониторов VDS (пример: snolla-smtp-monitor cron 08:00 MSK). Разумно: проверка раз в ~5-10 мин, алерт после N подряд фейлов (не шуметь на единичном сбое).
Спека: решения 6/15/18 concepts/mappa (SPOF принят, деградация = явные ошибки + meta.health + алерт).
Ограничение: не трогать сам стек mappa (Portainer 26) — только мониторинг. Редеплой/рестарт — отдельной задачей (правило: инфра на VDS через админа).
## Обязательные скилы — вызвать до начала работы
- invoke `using-tasks` — управление статусом задачи
- invoke `project-discipline` — дисциплина коммитов/пушей
**TDD:** нет — ops-задача (мониторинг), не импл
**Разрешения:** интерны: нет | автопуш: да
**weight:** needs-claude
**notify:** OpeItcLoc03/mappa
## Key files
## Decisions log
- 2026-08-23: Монитор на VDS (как snolla-smtp-monitor: python3 stdlib, /root/, cron /etc/cron.d/, env 600) — проверка с самого VDS через публичный URL (реальный путь пользователя через traefik).
- 2026-08-23: Алерт после 3 подряд фейлов (~15 мин при */5), раз за эпизод (state-файл, dedupe) — не шуметь на единичном сбое; recovery-сообщение при возврате.
- 2026-08-23: Строгая проверка health: HTTP 200 + ok==true + service=="mappa" (guard от чужого ответа на URL).
## Open questions
## Completed steps
- [x] Монитор задеплоен: /root/mappa-ntfy-monitor/monitor.py (sha совпадает с источником .admin/scripts/mappa-ntfy-monitor/monitor.py)
- [x] Cron */5 в /etc/cron.d/mappa-ntfy-monitor (root, лог /var/log/mappa-ntfy-monitor.log)
- [x] Env /root/.mappa-ntfy-monitor.env (600, из pass vds-kzntsv/full-env), топик mappa-alerts
- [x] Dry-run success: OK health, exit 0
- [x] Негатив-тест: 3 фейла → DOWN-алерт (priority high, ntfy HTTP 200, проверено в топике); 1-2 фейла — без алерта
- [x] Recovery: RECOVERED-сообщение в топике, state сброшен
- [x] Стек 26 не тронут (только мониторинг)
## Notes
- Деплой: 2026-08-23. Топик mappa-alerts подтверждён в ntfy (vitya admin-role → rw без правки ACL).

View File

@@ -1,24 +1,3 @@
# .wiki/ — Karpathy LLM Wiki # ⛔ Файловый канал закрыт
Curated knowledge base. Structure: **Не читать. Не править.** Канон — mappa (`mcp__mappa__*`): wiki-сущности проекта, конвенции — AGENTS-сущность. Скил: `mappa-knowledge`.
- `entities/<slug>.md` — physical/logical assets (hosts, NAS, VDS, router, recovery-VM)
- `concepts/<slug>.md` — patterns, root-cause analyses, recipes, postmortems
- `packages/<slug>.md` — third-party / first-party software packages relevant to this stack
- `sources/<slug>.md` — chronologies, session traces, primary records
- `raw/` — immutable external clippings (transcripts, screenshots, vendor docs)
- `index.md` — catalog of all pages
- `log.md` — op-log of ingest / promote / lint operations
- `overview.md` — high-level project orientation
## Domain conventions
### Mandatory: read design context before implementation
Before picking up any task in `.tasks/`, load the full design context. It does **not** live in this repo as a standalone source — only pointers do. Sources, in order:
1. **Local design (canonical):** `.wiki/concepts/admin-infra-project.md` — ingested via promote 2026-05-21. Identity, scope, content inventory, migration recipe (subtree-split + read-tree merge for populated prefixes), initial agenda.
2. **Brainstorm process trace (rationale):** `~/projects/.workshop/.archive/2026-05-21-admin-infra-project.md`. Why each decision was made (recommend vs menu trade-offs), what was rejected (filter-repo vs subtree-split, "admin-only" skills, splitting roadmap from ops), anti-patterns flagged during brainstorm.
3. **Local `overview.md`** — thin summary, quick orientation only — never the source of truth.
Do **not** invent migration recipes, file lists, taxonomies, or scope decisions from task `where_stopped` lines alone — those are pointers, not specifications. The concept doc and archive contain the rationale.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3304]] (concepts/books-vds-memory-optimize-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,67 @@
---
title: books-vds Gitea Actions выпилены — сборка только локально (решение vitya 2026-08-23)
status: live
tags: [books-vds, ci, gitea-actions, build, decision]
---
# Gitea Actions на books-vds — выпилены полностью (директива vitya 2026-08-23)
**Решение юзера:** никакого Gitea Actions / CI-раннера. Сборка образов — **только локально на рабочей машине**, деплой — прямыми `PUT /api/stacks/<id>` в Portainer.
## Что выпилено
- Workflow-файлы удалены и запушены: `books/.gitea/workflows/{build,deploy,build-tools,build-sched-daemon}.yml`, `vds-ops-mcp/.gitea/workflows/build.yml` (там был РЕАЛЬНЫЙ автобилд `on: push` — «автосборка», которую юзер увидел).
- books-vds (`89.253.255.133`): раннер вычищен под ноль:
- `systemctl stop/disable gitea-runner.service` + `rm /etc/systemd/system/gitea-runner.service`
- `rm -rf /opt/gitea-runner` (бинарник, config.yaml, .runner)
- `docker rm -f GITEA-ACTIONS-TASK-*` (job-контейнеры)
- `userdel -r gitea-runner`
- vds-kzntsv: следов не было (проверено).
## Процедура локальной сборки + публикации (проверена 2026-08-23)
1. `git archive <sha> | tar -x -C /c/tmp/build` (чистый контекст, без node_modules/.git)
2. `VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f packages/<svc>/Dockerfile -t <svc>:<sha> .`
3. `docker save` → tar. **Docker 29 на этой машине сразу отдаёт OCI-layout** (`oci-layout` + `index.json` + `blobs/sha256/*`) — конвертация не нужна, `--format oci` отсутствует и не требуется. `index.json` → OCI **index** (multi-arch: amd64 child + attestation `platform: unknown/unknown`). Дочерний **amd64-манифест** лежит в `blobs/sha256/<index-digest>` (прочитать index-блоб, взять `manifests[]` с `platform.architecture == 'amd64'`).
4. Заливка блобов curl'ом: HEAD-check → если нет — `POST /v2/<repo>/blobs/uploads/` → `PUT <Location>&digest=sha256:<d>` с телом (монолитный, Content-Length). Большой слой: `cat blob | curl -X PATCH -T -` (chunked) + финальный `PUT &digest` (Location брать из PATCH-ответа). Перед большим чтением файла — `sync; echo 3 > /proc/sys/vm/drop_caches` (books-vds 3.9GB RAM → OOM на 2GB файле).
5. Манифест: если `index.json` → OCI index (multi-arch, у Docker Desktop так) — **класть дочерний amd64-манифест** (`application/vnd.oci.image.manifest.v1+json`), не индекс (реестр: MANIFEST_INVALID).
6. Креды реестра: `docker login registry.kzntsv.site -u books-ci --password-stdin` (актуальный пароль books-ci — в `/opt/books/sched/docker-config.json` на books-vds; в `/root/.docker/config.json` был УСТАРЕВШИЙ → 401 на push).
## Gotcha: большие пуши через traefik умирают
`docker buildx --push` 1.5-2GB слоёв через `registry.kzntsv.site` (traefik buffering middleware `maxRequestBodyBytes=4GB`) → `Client Closed Request`: traefik буферизует тело в RAM vds-kzntsv (1GB free) + чтение с buildkit-кэша медленное + OOM. curl монолитными PUT/PATCH работает (54MB/s). Связано: [[registry-traefik-buffering-gotcha]].
## Деплой: Portainer PUT gotchas (2026-08-23, деплой master-a448cad)
**`PUT /api/stacks/<id>?endpointId=1` — синхронный и тянет образ ВНУТРИ запроса** (676МБ с registry через интернет = минуты). Клиентский таймаут (90с urllib) рвёт ожидание, но операция НЕ отменяется — Portainer доделывает server-side (файл стека обновляется, pull идёт, контейнер пересоздаётся). Правильный флоу: `PUT` с timeout 900+, при обрыве — НЕ пере-PUT (задвоит деплой), а проверить контейнеры:
```
GET /api/endpoints/1/docker/containers/json?all=1 # фильтр по Names: books-web / bookva-web → Image + Status
GET /api/endpoints/1/docker/images/json # образ уже на VDS? (показывает in-flight pull)
```
Смоук/верификация: `DELETE /api/fbs/pickingLists/:id` → `{taskId}`, статус `GET /api/fbs/tasks/<uuid>`, run в sched daemon: `docker exec books-sched node -e 'fetch("http://localhost:3031/api/runs?task=<name>&limit=3",{headers:{"Authorization":"Bearer <SCHED_ADMIN_KEY>"}})...'` (**curl в контейнере нет**, node fetch есть; ключ — из env стека). Nuxt-прокси `/admin/scheduler/**` для внешних проверок бесполезен: гейтится `requireAdmin` (JWT isAdmin), `?token=` API-ключ → 403.
## Sched docker-runner: pull auth + mediaType (2026-08-23, разбор `createPickingListPdf`)
**docker CLI читает креды ТОЛЬКО из `${DOCKER_CONFIG}/config.json`.** Файл `docker-config.json` (как велели старые доки) → игнорируется → pull без auth → 401 «no basic auth credentials». Фикс: `mv /opt/books/sched/docker-config.json /opt/books/sched/config.json` (bookva volume уже был правильный). Рестарт daemon не нужен — файл читается при каждом pull.
**Вторая засада — mediaType образа.** Теги, запушенные buildx (и моим curl-флоу из OCI-save) = **OCI image-index**. registry 2.8.3 отдаёт их ТОЛЬКО на Accept с `application/vnd.oci.image.index.v1+json`; docker daemon (26.1.4) получает «manifest unknown», если child-манифест отсутствует (а он отсутствует после прежних host-GC — см. [[registry-oci-image-index-gc]]). Диагностика: `curl` манифеста тега с docker-only Accept → 404, с OCI-index → 200; child по digest → 404. **Фикс: перепуш образа как docker schema2-манифест** (mediaType `application/vnd.docker.distribution.manifest.v2+json`, config `application/vnd.docker.container.image.v1+json`, layers `application/vnd.docker.image.rootfs.diff.tar.gzip`) — блобы те же (content-addressed), только обёртка; pull снова работает. Сборка: `docker save` → OCI → взять amd64 child → трансформ mediaTypes → PUT в тег с docker Content-Type. Важно: docker config-манифест пишется `container.image.v1+json`, НЕ `distribution.image.config.v2+json` (последний docker не знает → «(unknown)» при pull).
**Третий слой — конфиг daemon.** `createPickingListPdf` рендерит print-роут: env берётся из tasks.json на VDS; плейсхолдер `API_TOKEN: "<SET_IN_PROD_VOLUME_MOUNT>"` = production-конфиг daemon'а не задеплоен в монтированную диру (в репо tasks.json — `${config:api.authToken}`). Конфиг-дир `/opt/books/sched/` должен содержать production-overrides (api.authToken, puppeteer.browserWSEndpoint, s3).
## 2026-08-24 — deploy sched-daemon 0.11.1 + createPickingListPdf ЗАРАБОТАЛ (admin)
Деплой демона на 0.11.1 (docker-runner `config.network`) + фикс tasks.json обоих тенантов → смоук оба тенанта ✅ (slovo 429, bookva 219: PDF в S3 + `pdf_remote_path` в БД).
**Процедура bump books-sched-daemon (без CI, локальная сборка):**
1. `deploy/sched/Dockerfile` FROM → `registry.kzntsv.site/sched-daemon:0.11.1`;
2. `VERDACCIO_TOKEN=<jwt> docker build --secret id=verdaccio_token,env=VERDACCIO_TOKEN -f deploy/sched/Dockerfile -t registry.kzntsv.site/books-sched-daemon:master-0.11.1 /c/tmp/schedctx` (пустой контекст — Dockerfile без COPY; НЕ `.` — иначе поедет node_modules);
3. пуш schema2-флоу (`docker save` → OCI → amd64 child → трансформ mediaTypes → curl блобы + PUT манифеста) в теги `master` + `master-0.11.1` (скрипт-образец `/c/tmp/pushimg/push.py`);
4. Portainer PUT обоих sched-стеков (Id 50 books-sched, Id 51 bookva-sched на portainer.kzntsv.site) — DEPLOY_AT bump + pullImage:true;
5. verify: `docker exec books-sched node -e 'require("/app/node_modules/@sched/daemon/package.json").version'` → 0.11.1; admin-api `/api/tasks` → `config.network: "proxy"`.
**Задачи docker-тасков спавнятся на сети `proxy`** (`config.network: "proxy"` в tasks.json; docker-runner 0.11.1 `docker run --network proxy`) → тул видит `books-web:3000`/`chrome:3000`/`books-api:3021`. tasks.json live-reload — директорный бинд `/opt/books/sched/` (slovo) / volume `bookva-sched-config` (bookva), поллинг ~60с, рестарт не нужен.
**Готча 1: chrome WS-эндпоинт БЕЗ `--proxy-server`.** Эндпоинт `ws://chrome:3000?token=…&stealth&--proxy-server=http://proxy-chain:8000` ЛОМАЕТ рендер внутренней страницы: proxy-chain гонит весь трафик (включая внутренний `books-web:3000`) во внешний пул → navigation timeout → browserless job timeout → SIGKILL → puppeteer «Protocol error (Page.navigate): Target closed». Внутренний рендер print-роута должен идти напрямую: `ws://chrome:3000?token=0ffbbc13-…` (без stealth, без proxy-флага). `--proxy-server` наследовался от мёртвого внешнего chrome.kzntsv.site.
**Готча 2: bookva-api слушает 3000, НЕ 3021.** (slovo books-api — 3021, bookva-api — 3000; проверка `docker exec bookva-sched node -e 'fetch("http://bookva-api:3000")'` → 401, на 3021 → ECONNREFUSED). В tasks.json bookva `API_URL` был `http://bookva-api:3021` → PATCH `/fbs/pickingLists/:id/pdf` упал бы. Исправлено на `:3000` (live volume + репо-шаблон).
**Готча 3: `pdf_remote_path` не виден в `GET /api/fbs/pickingLists/:id`** — api-сериализация листа его не отдаёт; проверять в БД напрямую: `docker exec books-db mysql -ubooks -p… books -e "SELECT id_picking_list, pdf_remote_path FROM fbs_picking_lists WHERE id_picking_list=…"` (таблица `fbs_picking_lists`, колонка `pdf_remote_path`).

View File

@@ -0,0 +1,22 @@
---
title: config-custom-env-mapping-in-mounted-dirs — env бесполезен без mapping-файла
status: live
tags: [config, npm-config, env, books, gotcha]
---
# npm `config` + примонтированный конфиг-дир: env читается только через custom-environment-variables.json
## Проблема (2026-08-23, деплой books)
Код читает `config.get('sched')` (npm-пакет `config`), env-mapping — `config/custom-environment-variables.json` (`"sched": {"adminUrl": "SCHED_ADMIN_URL", ...}`). В контейнерах books (api/web/task-runner) конфиг-дир — примонтированный (bind `/opt/books/*/config` или named volume), и в нём НЕ было `custom-environment-variables.json` (только `default.json`) → **`config.get('sched')` THROW: "Configuration property sched is not defined"**, даже с env-переменными в стеке.
Причина: маунт **затеняет** файлы из образа; env-mapping работает только если mapping-файл лежит в РАБОЧЕМ конфиг-дире (mounted), не в образе.
## Фикс
Положить `custom-environment-variables.json` (с sched-mapping) в каждый примонтированный конфиг-дир:
- slovo: `/opt/books/api/config/`, `/opt/books/task-runner/config/` (host binds)
- volumes: `books-web-config`, `bookva-api-config`, `bookva-web-config`, `bookva-task-runner-config` — через `docker run --rm -v <vol>:/c -v /tmp:/tmp:ro alpine cp /tmp/file /c/`
Нюансы:
- mapping **не создаёт** свойство, если его нет в файлах И env-переменная отсутствует: `config.get('sched')` бросит. Нужны оба: mapping-файл + env в стеке.
- `config` кэшируется на старте процесса — после правки файлов контейнер нужно пересоздать (redeploy).
- Проверка: `docker exec <ctr> sh -c 'cd /usr/src/app/packages/<svc> && node -e "console.log(JSON.stringify(require(\"config\").get(\"sched\")))"'` (cwd ВАЖЕН — конфиг резолвится от него; `cd /usr/src/app/packages/*` ломается на multi-dir).

View File

@@ -0,0 +1,17 @@
---
title: deploy-policy-mappa
type: concept
ingested_at: '2026-08-24T19:16:54.665Z'
ingested_by: OpeItcLoc03@DESKTOP-NSEF0UK
source_project: OpeItcLoc03/workshop
---
# Деплой-политика mappa
**Правило (решение оператора 2026-08-24, уточняет #985).**
1. **Деплой — когда ВСЯ очередь готова + стабильность.** Не по одному скоупу: закрытие одной таски (напр. #1022 → v0.8.0) НЕ триггер деплоя. Один общий деплой, когда закрыты все таски очереди (все накопленные версии: v0.8.0–0.10.0 и т.п.) и сервис стабилен.
2. **Деплой — только через .admin** (правило #985, директивы i:2067/2068): прод (стек Portainer, образы в registry, compose-источник правды) трогает только админ. Роль mappa — письмо-запрос + верификация после деплоя. Сама ничего не собирает, в registry не пушит, Portainer не читает.
3. **Нарушение повторно = жёсткий откат.** Первое нарушение (2026-08-24, i:2069) — откат + предупреждение; повторное — жёсткая реакция оператора.
4. Пока очередь не готова — версии остаются в репо, образы не собираются.
**Контекст:** правило из инцидента 2026-08-24 (mappa самовольно собрала/запушила образ v0.7.3 и читала Portainer) + уточнение оператора: #1022 закрыта (v0.8.0) → деплой-запрос придержан (#1055 blocked в .admin) до завершения всей очереди (#1054 → #1030 → #1031 → #1032 → #1033).

View File

@@ -1,114 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: emspb.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
type: concept
tags: [emspb, snolla, vds, deploy, docker, portainer, traefik, runbook, migration]
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[labtools-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]]
updated: 2026-07-05
---
# emspb.ru → VDS deploy runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:160]] (concepts/emspb-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Вынос `emspb.ru` (snolla-приложение, `@snollajs/snolla` 0.28.4, server-side Liquid) с [[../entities/ruvds-iis-host]]
(catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]] (89.253.255.94), за traefik.
Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy. **Зеркало [[labtools-vds-deploy-runbook]]** — тот же паттерн.
## Артефакты
- **Код:** `victor/emspb.ru` @ `b6e361a` (apps/web, ре-ревью PASS, 29/29 parity, snolla 0.28.4 final). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck `/robots.txt`).
- **Образ:** `registry.kzntsv.site/emspb:b6e361a` (+`:latest`). Собран НА VDS (обход traefik-499). digest `sha256:8f5ba02651b71f340fa9bc3b079fd2853b01b1461e6c479fb2cf788691d31a74`.
- **Стек Portainer:** `emspb` (Id 18, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/emspb.compose.yml`.
- **siteId:** `96EBC481-D26A-47BE-B660-13D49E7D0A61`, activeTheme `DD8D6F7A-CEDC-4D5A-8BFB-E5F9BE9DC035` (theme store MinIO `themes/dd8d6f7acedc4d5a8bfbe5f9be9dc035/`). Non-secret — в `production.json`, НЕ env.
## Сборка образа (на VDS)
```bash
# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
git -C ~/projects/emspb.ru archive --format=tar b6e361a \
| ssh vitya@89.253.255.94 'rm -rf ~/build/emspb && mkdir -p ~/build/emspb && tar -x -C ~/build/emspb'
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), передаётся через env (не в argv), push из VDS
ssh vitya@89.253.255.94 "cd ~/build/emspb && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/emspb:b6e361a -t registry.kzntsv.site/emspb:latest . \
&& docker push registry.kzntsv.site/emspb:b6e361a && docker push registry.kzntsv.site/emspb:latest"
```
- VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only ×2 — ожидаемо).
- `config/default.json` (dev-секреты) исключён `.dockerignore` — в образе только `production.json` + `custom-environment-variables.json` (guard проверен на архиве: default.json ABSENT).
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
Маппинг env→config: `apps/web/config/custom-environment-variables.json`. Non-secret (host/siteId/endpoints) — в `production.json`.
**Значения идентичны labtools** (все snolla-тенанты читают одну MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив переиспользован verbatim из labtools stack (Id 17) через Portainer API.
| ENV | Значение | Источник |
|---|---|---|
| `DB_USER` | `snolla` | web.config боя MoreThenCmsEntities |
| `DB_PASSWORD` | ⟨секрет⟩ | web.config боя MoreThenCmsEntities (User Id=snolla) |
| `IMGPROXY_KEY` | ⟨128hex⟩ | `pass minio-vds/full-env` (== сервер imgproxy books-vds — HMAC совпадает) |
| `IMGPROXY_SALT` | ⟨128hex⟩ | `pass minio-vds/full-env` |
| `S3_ACCESS_KEY_ID` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_USER) |
| `S3_SECRET_ACCESS_KEY` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_PASSWORD) |
| `SMTP_USER` | `noreply@snolla.com` | `pass snolla-smtp/full-env` |
| `SMTP_PASSWORD` | ⟨секрет⟩ | `pass snolla-smtp/full-env` (SMTP_PASS) |
PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck `/robots.txt` на $PORT). traefik service-port = 5000.
## Создание стека (Portainer API, на VDS — bash+curl+jq, обходит PS-кириллица-гочу)
```bash
# JWT (pass vds-kzntsv/full-env PORTAINER_PASS); env переиспользован из labtools stack 17
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth -d '{"username":"vitya","password":"<pw>"}' | jq -r .jwt)
ENV=$(curl -ksS -H "Authorization: Bearer $JWT" https://portainer.vds.kzntsv.site/api/stacks/17 | jq '.Env')
COMPOSE=$(cat ~/build/emspb.compose.yml)
PAYLOAD=$(jq -n --arg name emspb --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
curl -ksS -X POST "https://portainer.vds.kzntsv.site/api/stacks/create/standalone/string?endpointId=1" \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" --data "$PAYLOAD" # → Id=18
```
Предусловие pull: `registry.kzntsv.site` зарегистрирован в Portainer как Custom registry Id 1 (иначе `no basic auth`).
## Runtime egress (наружу из proxy-сети)
`mssql.kzntsv.site:1433` (БД-контент, обязателен) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
## Staging smoke (2026-07-02, GREEN) — `emspb.vds.kzntsv.site` vs бой `www.emspb.ru` (RUVDS IIS 80.64.31.36, через `--resolve`)
- **Status-паритет 23/23:** 5 nav (`/ /contacts /portfolio /prices`) + 17 service-детальных (`/ustanovka-*`, `/almaznoe-burenie-*` …) все 200/200; `/services` 404/404 (nav-якорь, не страница); `/index.php` 404/404; `/robots.txt` 200 байт-в-байт (31 B).
- **Дельта размера контент-страниц стабильна +8 B**, полностью объяснена: +9 host-строка в canonical/og itemprop (`emspb.vds.kzntsv.site` на 9 символов длиннее `www.emspb.ru`), −1 — новый snolla почистил битый двойной слэш боя `/images//3.jpg`→`/images/3.jpg` (оба варианта → 200, картинка не сломана). `/` — байт-в-байт (canonical из DB-домена, не request-host).
- **theme CSS ×3 (framework/bootstrap/slick) из MinIO — md5 IDENTICAL** с боем; `Cache-Control: max-age=86400, public, must-revalidate`, `text/css`.
- **theme image `/images/3.jpg` из MinIO — md5 IDENTICAL** (115618 B, image/jpeg).
- Редиректы: `/contacts/`→301→`/contacts` (trailing-slash), `/Contacts`→301→lowercase, `/Portfolio`→301→lowercase — как бой.
- Контейнер healthy (t+10s), MSSQL (tedious) + S3 (aws-sdk) подключены, `listening at http://localhost:5000`, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
Порядок был:
1. **(оператор)** reg.ru: A-записи `emspb.ru` + `www.emspb.ru` → `89.253.255.94` (с RUVDS `80.64.31.36`). — сделал оператор (сигнал «поменял DNS у провайдера»). Проверено внешним резолвером (8.8.8.8): оба хоста → VDS; labtools остался на RUVDS (не путать домены!).
2. **(ops)** В стеке `emspb` (Id 18) traefik-rule расширен: `Host(\`emspb.ru\`) || Host(\`www.emspb.ru\`)` — Portainer PUT (env сохранён, pullImage=false). LE HTTP-01 выпустил cert на первом хите после flip (~t+20-70s). **staging-хост `emspb.vds.kzntsv.site` убран из rule post-cutover** (проверено: → 404). Cert перевыпустится на 2 боевых SAN при renewal.
3. **Live-smoke GREEN:** `www.emspb.ru`+`emspb.ru` → 200 `ssl_verify=0` (cert доверенный); все страницы 200; canonical нормализован на `www.emspb.ru`; live VDS `/contacts` == старый RUVDS **байт-в-байт** (host уравнялся).
⚠️ **Порядок критичен:** Host-правило добавлено ТОЛЬКО ПОСЛЕ flip DNS (иначе LE HTTP-01 challenge упал бы на RUVDS → сожгли бы rate-limit). Перед PUT — обязательно проверить внешним резолвером, что DNS реально на VDS.
### Guard (как labtools)
- **НЕ добавлять `Host(emspb.ru)` в traefik-rule ДО флипа DNS** — traefik на reload проактивно тянет LE-cert (HTTP-01), challenge упадёт на RUVDS → сожжём LE rate-limit.
- **НЕ выводить RUVDS IIS emspb из эксплуатации** — это rollback-путь.
- **НЕ флипать DNS самим** — это делает хозяин домена/оператор.
## Rollback
- **DNS:** вернуть A-записи `emspb.ru`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
- **Стек:** Portainer → stack `emspb` (Id 18) → remove (или откат тега образа). Образ в registry остаётся.
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
Тираж snolla 0.42.1. emspb.ru обновлён на живом стеке 18 **in-place** (домен уже на VDS, DNS не трогали).
Рецепт — [[snolla-live-prod-inplace-image-bump]]. Оператор дал отмашку на боевой apply.
- **Образ:** `registry.kzntsv.site/emspb:95a5c42` (snolla 0.28.4→**0.42.1**, digest `d6299cf…`). Собран на VDS.
- **Acceptance С VDS (throwaway-staging из env стека 18):** **29 sitemap-роутов все 200** (0 non-2xx/3xx), content-not-lost 29/29 (0 потерь). Sitemap НЕ реструктурировался (29==29, в отличие от каталожного labtools.ru).
- **TLS:** серт — SAN покрывает `emspb.ru`/`www.emspb.ru`/`emspb.vds.kzntsv.site`, CN косметически = staging-хост (curl validates `sslverify=0`). Это тот самый серт с cutover 07-02 (см. выше: «Cert перевыпустится на 2 боевых SAN при renewal») — swap его не тронул. При renewal можно пере-выпустить с CN=emspb.ru, опционально.
- **Swap:** Portainer PUT стека 18 (env 8/8 сохранён) → healthy → live-smoke GREEN, sitemap 29 вживую.
- **Rollback:** тег `emspb:b6e361a` (0.28.4, в registry) / стек 18 PUT назад / DNS→RUVDS.
- Compose обновлён на `95a5c42`. Таска `[emspb-deploy-snolla-0-42-1]` 🟢. Notify=victor/emspb.ru.
## 0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
5-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro — 4 GREEN). In-place bump стека 18 (DNS на VDS с 2026-07-02, не трогали). Рецепт — [[minio-split-cutover-runbook]] §B3-v3.
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` (commit `5afd8a1`) + `resolutions`-блок в root `package.json` (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + реген `yarn.lock` (single @smithy/signature-v4@5.3.11 deduped). Pushed victor/emspb.ru master `218c3ae..5afd8a1`. Endpoint fix `218c3ae` (s3.endpoint→minio.vds.kzntsv.site) в master с 2026-07-29, но в бегущем образе `95a5c42` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
- **Build на VDS:** `git archive 5afd8a1` → `~/build/emspb` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/emspb:5afd8a1`(+latest), digest `sha256:72bb59fc355d4f020fe49e1c410e6be411779ee1002d91cd4db7c3250f2c3358`, EXIT=0. `yarn install --immutable` в Docker прошёл (yarn.lock matches, no YN0028).
- **Staging :5082 parity-gate GREEN** (throwaway контейнер из env живого стека 18, 8 секретов, `--network proxy`):
- robots.txt md5 `940a0903…` byte-identical (31 B); sitemap index→`sitemap-pages-1.xml` **29 locs == live 29**.
- **29/29 sitemap-роутов 200/200, 0 status diffs**, content present (34-38 KB real bodies). Дельта **+2 B** = `localhost:5082` (14) vs `www.emspb.ru` (13) ×2 (canonical+og:url) — benign host-context, post-cutover byte-identical.
- **theme assets 7/7 md5 IDENTICAL** (v3 aws-sdk v3 S3-direct read из vds MinIO 2025): snolla-framework `5cb1031e…` 7529B, snolla-bootstrap `9a56500a…` 19624B, slick `e604b364…` 1183B, toolbox `382a2ccc…` 10215B, theme `e02370e3…` 5640B, images/2.jpg `0898d8df…` 387884B, **/images/3.jpg `48562843…` 115618B** (== runbook 2026-07-02 baseline).
- redirects parity: `/contacts/`→301→`/contacts`, `/Contacts`→301→lowercase, `/Portfolio`→301→lowercase, `/Services`→301 (target modulo host); `/index.php`+`/services`→404 parity.
- **Portainer PUT stack 18 LIVE:** node in-memory `put-stack` (НЕ PS — глобальный fetch, compose в JS-var без /tmp round-trip; GET `/stacks/18` для Env + `/stacks/18/file` для StackFileContent), env 8/8 preserved, `prune:false pullImage:true mem_limit 512m`. PUT 200. Контейнер `5afd8a1` healthy сразу (~1s).
- **Live smoke GREEN (via traefik):** `/` 200 54615B, `/contacts`+`/portfolio`+`/prices` 200, `/services`+`/index.php` 404, robots md5 `940a0903…`, sitemap 29 locs, theme assets md5 LIVE==staging (v3 S3-direct), redirects 301 (target `https://www.emspb.ru/...` — byte-identical post-cutover), apex `emspb.ru` 200. Лог чист (tedious deprecation + `emspb.ru (snolla) listening`). TLS не тронут.
- **Rollback:** `emspb:95a5c42` (0.42.1 v2, жив в registry) — Portainer PUT назад / compose revert. `b6e361a` (0.28.4) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний.
- Compose (admin source-of-truth) синхронно закоммичен `95a5c42`→`5afd8a1`. **5/6 v3 сайтов GREEN.** Остался: tandemmebel (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3329]] (concepts/gitea-project-create-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -1,90 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: labtools.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
type: concept
tags: [labtools, snolla, vds, deploy, docker, portainer, traefik, runbook, migration]
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[snolla-live-prod-inplace-image-bump]]
updated: 2026-07-30
---
# labtools.ru → VDS deploy runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:168]] (concepts/labtools-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Вынос `labtools.ru` (snolla-приложение, `@snollajs/snolla`, server-side Liquid) с [[../entities/ruvds-iis-host]]
(catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]] (89.253.255.94), за traefik.
Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy.
## Артефакты
- **Код:** `victor/labtools.ru` @ `43e28ba` (apps/web, parity ре-ревью №2 PASS). `deploy/Dockerfile` (multi-stage node:22-slim).
- **Образ:** `registry.kzntsv.site/labtools:43e28ba` (+`:latest`). Собран НА VDS (обход traefik-499), 114 MB content.
- **Стек Portainer:** `labtools` (Id 17, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/labtools.compose.yml`.
## Сборка образа (на VDS)
```bash
# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
git -C ~/projects/labtools.ru archive --format=tar 43e28ba \
| ssh vitya@89.253.255.94 'rm -rf ~/build/labtools && mkdir -p ~/build/labtools && tar -x -C ~/build/labtools'
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), push из VDS
ssh vitya@89.253.255.94 'cd ~/build/labtools && docker build -f deploy/Dockerfile \
--build-arg VERDACCIO_TOKEN=<token> -t registry.kzntsv.site/labtools:43e28ba -t registry.kzntsv.site/labtools:latest . \
&& docker push registry.kzntsv.site/labtools:43e28ba && docker push registry.kzntsv.site/labtools:latest'
```
- VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only).
- `config/default.json` (dev-секреты) исключён `.dockerignore` — в образе только `production.json` + `custom-environment-variables.json`.
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
Маппинг env→config: `apps/web/config/custom-environment-variables.json`. Non-secret (host/siteId/endpoints) — в `production.json`.
| ENV | Значение | Источник |
|---|---|---|
| `DB_USER` | `snolla` | web.config боя MoreThenCmsEntities |
| `DB_PASSWORD` | ⟨секрет⟩ | web.config боя MoreThenCmsEntities (User Id=snolla) |
| `IMGPROXY_KEY` | ⟨128hex⟩ | `pass minio-vds/full-env` (== сервер imgproxy books-vds — HMAC должен совпадать) |
| `IMGPROXY_SALT` | ⟨128hex⟩ | `pass minio-vds/full-env` |
| `S3_ACCESS_KEY_ID` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_USER) |
| `S3_SECRET_ACCESS_KEY` | ⟨секрет⟩ | `pass minio-vds/full-env` (MINIO_ROOT_PASSWORD) |
| `SMTP_USER` | `noreply@snolla.com` | `pass snolla-smtp/full-env` |
| `SMTP_PASSWORD` | ⟨секрет⟩ | `pass snolla-smtp/full-env` (SMTP_PASS; default.json имел dev-значение — НЕ брать оттуда) |
PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck `/robots.txt` на $PORT). traefik service-port = 5000.
## Runtime egress (наружу из proxy-сети)
`mssql.kzntsv.site:1433` (БД-контент, обязателен) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
## Staging smoke (2026-07-01, GREEN) — `labtools.vds.kzntsv.site` vs бой `www.labtools.ru`
- Все страницы меню (home/about/clients/contacts/cookie/privacy/6×products) — 200/200, дельта размера стабильна (длина хоста в canonical/og).
- Редиректы: `.php`→301→`/`, `/about/`→301 (trailing-slash), `/Products/Press-Forms`→301→lowercase — 1:1 с боем.
- `theme.css` из MinIO — **байт-в-байт** (12248), `Cache-Control: max-age=86400, public, must-revalidate`.
- `/assets/<ownerId>/*.webp` из MinIO — **байт-в-байт** с боем (44444, 24354); content-type `application/octet-stream` — как на бою.
- Контейнер healthy (MSSQL+S3 подключены).
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
⚠️ **DNS зоны `labtools.ru` — на Yandex DNS (`dns1/dns2.yandex.net`), НЕ reg.ru** (в отличие от `labtools.pro`/reg.ru). Порядок был:
1. **(оператор)** Yandex DNS: A-записи `labtools.ru` + `www.labtools.ru` → `89.253.255.94` (с RUVDS `80.64.31.36`).
2. **(ops) verify перед traefik — критично:** флип шёл неравномерно, **dns1 и dns2 расходились** (dns2 обновился первым, dns1 отставал). Гейт cutover = **ОБА** авторитетных NS согласованно отдают VDS по apex+www (иначе LE мог попасть на dns1→RUVDS→сжечь rate-limit). Публичные резолверы кэшировали до 6ч (TTL 21600) — для LE не критично (резолвит по авторитетному). Cм. memory [operator-dns-flip-verify-domain-before-cutover].
3. **(ops) после подтверждения обоих NS:** стек `labtools` (Id 17) traefik-rule → `Host(\`labtools.ru\`) || Host(\`www.labtools.ru\`)` (Portainer PUT, env 8/8 сохранён, pullImage=false). LE-cert выпущен (~t+20s; первые хиты ловили self-signed в окне issuance, потом ssl_verify=0).
4. **Live-smoke GREEN:** оба хоста 200 (nav + каталог-секции + продукты); редиректы (`/about/`→trailing, `/Contacts`→lowercase, `/index.php`→`/`) parity; sitemap = index (host=`labtools.ru`, non-www — siteUrl этого тенанта без www, в отличие от .pro/R3); theme-ассеты из MinIO byte-identical бою (RUVDS отдаёт 301 apex→www, поэтому прямой md5 «DIFF» — артефакт; с `-L` MD5-OK). staging-хост `labtools.vds.kzntsv.site` убран (→404). **Внешняя проверка с RUVDS-хоста:** `labtools.ru`→VDS 200; `www` ещё на кэше RUVDS (200, идентичный контент) — split-brain окно, тухнет по TTL.
5. `labtools.pro` — **закрыт отдельно** ([[labtools.pro-vds-deploy-runbook]], LIVE 2026-07-02).
## Rollback
- **DNS:** вернуть A-записи `labtools.ru`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
- **Стек:** Portainer → stack `labtools` → remove (или откат тега образа). Образ в registry остаётся.
- RUVDS IIS labtools **не выводить из эксплуатации** до явного решения оператора (директива 2026-07-01).
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
Тираж snolla 0.42.1. labtools.ru обновлён на живом стеке 17 **in-place** (домен уже на VDS, DNS не трогали).
Рецепт — [[snolla-live-prod-inplace-image-bump]] (общий для labtools.ru/emspb/labtools.pro). Оператор дал отмашку на боевой apply.
- **Образ:** `registry.kzntsv.site/labtools:566d41c` (snolla 0.28.2→**0.42.1**, digest `ea0649a…`). Собран на VDS.
- **Acceptance С VDS (throwaway-staging из env стека 17):** новый sitemap = **38 page-locs** (7 pages+1 static+6 sections+24 продукта) — 37×200 + 1×301(`/products/laboratory-ball-mills`→`/lshm-750`, идентично проду). content-not-lost 11/11 (0 потерь). **Order-фикс 0.42.1** (liquid 0.10.2): presses = `plg-20,plg-12,plg-25,pgr-10` == прод.
- **Находка:** старый 0.28.2 sitemap был дефицитным (sections=1, catalog=2 продукта) — 0.42.1 отдаёт полную корректную структуру. Ещё: sitemap листит 301-секцию как `<loc>` (прод идентичен, engine-intended; heads-up прогу→снолле).
- **Swap:** Portainer PUT стека 17 (env 8/8 сохранён, pullImage=true) → healthy → live-smoke GREEN, TLS CN=labtools.ru не тронут (без LE-churn).
- **Rollback:** тег `labtools:43e28ba` (0.28.2, в registry) / стек 17 PUT назад / DNS→RUVDS.
- Compose обновлён на `566d41c`. Таска `[labtools-ru-deploy-snolla-0-42-1]` 🟢. Notify=victor/labtools.ru.
## 0.43.2 v3 bump (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
Тираж v3 (MinIO split Track B3-v3, 3-й сайт после on-snolla + kupimknigi). Домен на VDS, DNS не трогали.
Рецепт — [[snolla-live-prod-inplace-image-bump]] + [[minio-split-cutover-runbook]] §B3-v3.
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` + `resolutions`-блок в root `package.json` (commit `0dc0b4e`, pushed victor/labtools.ru master). yarn install: single `@smithy/signature-v4@5.3.11` (deduped). Endpoint fix `4f3314f` (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе `566d41c` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
- **Build на VDS:** `git archive 0dc0b4e` → `~/build/labtools` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/labtools:0dc0b4e`(+latest), EXIT=0.
- **Throwaway staging :5080 parity-gate GREEN** (из env живого стека 17, 8 секретов): `/` byte-identical с real Host (18594B), theme asset `lato.css` 200 9239B byte-identical (v3 aws-sdk v3 S3-direct read из vds MinIO 2025), robots 200/200, sitemap = index 38 page-locs == live (identical set, 0 diff), self-consistency 0 bad (все 200/3xx), catalog section `/products/accessories` byte-identical (order-parity ✅).
- **Portainer PUT stack 17 LIVE:** node in-memory `put-stack` (НЕ PS), env 8/8 preserved, `mem_limit 512m` уже в compose, `prune:false pullImage:true`. PUT 200. Контейнер `0dc0b4e` healthy сразу. Source-of-truth compose синхронно закоммичен (admin).
- **Live smoke GREEN:** `/` 200, www 200, robots/sitemap 200, menu pages (about/clients/contacts/cookie/privacy) 200, `/products/press-forms` + `/products/accessories` + `/products/laboratory-ball-mills` 200, theme `lato.css` 200 9239B (v3 S3-direct). TLS CN=labtools.ru не тронут (LE до Sep 30). `/products` (bare) + `/products/presses` → 404 = ожидаемо (не в sitemap, не real pages; real section = `/products/press-forms`). **Rollback = `566d41c`** (v2 0.42.1, жив в registry).

View File

@@ -1,107 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: labtools.pro snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback)
type: concept
tags: [labtools-pro, labtools, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, catalog]
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[emspb-vds-deploy-runbook]], [[labtools-vds-deploy-runbook]], [[snolla-live-prod-inplace-image-bump]]
updated: 2026-07-30
---
# labtools.pro → VDS deploy runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:169]] (concepts/labtools.pro-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Вынос `labtools.pro` (snolla-приложение, `@snollajs/snolla` **0.28.7**, server-side Liquid, **каталожный сайт**)
с [[../entities/ruvds-iis-host]] (catch-all CMS) в отдельный docker-контейнер на [[../entities/vds-kzntsv]]
(89.253.255.94), за traefik. Модель — snolla-app: читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
+ ассеты из MinIO (`minio.kzntsv.site`) через imgproxy. **Зеркало [[emspb-vds-deploy-runbook]]** — тот же паттерн;
отличие: каталог `/products/*` + `Culture=en` (англоязычный близнец labtools.ru) + Cache-Control отсутствует (см. ниже).
## Артефакты
- **Код:** `victor/labtools.pro` @ `7bd9fae` (apps/web, ре-ревью PASS все 9 дименшнов, ноль хаков, snolla 0.28.7 final). `deploy/Dockerfile` (multi-stage node:22-slim, non-root, healthcheck `/robots.txt`).
- **Образ:** ⚠️ `registry.kzntsv.site/labtools-pro:7bd9fae` (+`:latest`) — имя **`labtools-pro`**, НЕ `labtools` (тот у labtools.ru!). Собран НА VDS (обход traefik-499). digest `sha256:23d0bc597c8930a1bf443c418a4af68d63b692903963c0e1f9f25baa4a1dd236`.
- **Стек Portainer:** `labtools-pro` (**Id 19**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/labtools-pro.compose.yml`.
- **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/canonical отдают www).
## Сборка образа (на VDS)
```bash
git -C ~/projects/labtools.pro archive --format=tar 7bd9fae \
| ssh vitya@89.253.255.94 'rm -rf ~/build/labtools-pro && mkdir -p ~/build/labtools-pro && tar -x -C ~/build/labtools-pro'
ssh vitya@89.253.255.94 "cd ~/build/labtools-pro && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
--build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/labtools-pro:7bd9fae -t registry.kzntsv.site/labtools-pro:latest . \
&& docker push registry.kzntsv.site/labtools-pro:7bd9fae && docker push registry.kzntsv.site/labtools-pro:latest"
```
- VERDACCIO_TOKEN — `pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN`, build-time only (не в финальный образ; docker linter warn-only).
- `config/default.json` (dev-секреты) исключён `.dockerignore` — guard проверен на архиве: default.json ABSENT (только production.json + custom-environment-variables.json + default.example.json).
## Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)
**Значения идентичны labtools/emspb** (все snolla-тенанты = одна MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив **переиспользован verbatim из labtools stack (Id 17)** через Portainer API. 8 env: `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`. Маппинг env→config: `apps/web/config/custom-environment-variables.json`.
PORT — не переопределять (образ 5000; EXPOSE 5000; healthcheck `/robots.txt`). traefik service-port = 5000.
## Создание стека (Portainer API, X-API-Key)
```bash
K=$(pass show vds-kzntsv/full-env | sed -n 's/^PORTAINER_API_KEY=//p'); BASE=https://portainer.vds.kzntsv.site
ENV=$(curl -ksS -H "X-API-Key: $K" $BASE/api/stacks/17 | jq '.Env') # verbatim из labtools stack 17
COMPOSE=$(cat ~/build/labtools-pro.compose.yml)
PAYLOAD=$(jq -n --arg name labtools-pro --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
curl -ksS -X POST "$BASE/api/stacks/create/standalone/string?endpointId=1" -H "X-API-Key: $K" -H 'Content-Type: application/json' --data "$PAYLOAD" # → Id 19
```
Предусловие pull: `registry.kzntsv.site` зарегистрирован в Portainer как Custom registry Id 1 (иначе `no basic auth`).
## Runtime egress
`mssql.kzntsv.site:1433` (БД-контент) · `minio.kzntsv.site:443` (ассеты) · `imgproxy.kzntsv.site:443` · `smtp.yandex.ru:465` (формы).
## Staging smoke (2026-07-02, GREEN) — `labtools-pro.vds.kzntsv.site` vs бой `www.labtools.pro` (RUVDS IIS 80.64.31.36)
Гнать с самого VDS (воркстейшн ловит LAN-DNS-перехват на `*.labtools.pro`; с VDS бой резолвится публично → RUVDS).
- **Status-паритет:** nav (`/ /about /contacts`) + 3 каталог-секции (`/products/laboratory-presses`, `/products/press-forms`, `/products/milling-accessories`) + продукты (`plg-20`, `press-forms/ring`) — все 200/200.
- **Редиректы (301 parity, target идентичен modulo host):** каталог#1 `/products/laboratory-ball-mills`→`…/laboratory-ball-mills/lshm-750`; `/Contacts`→lowercase `/contacts`; `/contacts/`→trailing `/contacts`.
- **Sitemap — структура index (snolla) vs плоский (legacy), покрытие идентично:** бой отдаёт плоский `/sitemap.xml` с 25 URL; snolla отдаёт **sitemap-INDEX** из 7 дочерних (`sitemap-pages-1` 3, `sitemap-static-pages-1` 2, `sitemap-sections-1` 3, 4× `sitemap-catalog-<GUID>-products-1` = 1+2+2+12=17), все 200, **в сумме ровно 25 URL, набор == бой** (comm — ноль расхождений). `<loc>` host = `www.labtools.pro` (R3). **НЕ дефект** — index-структура штатна для snolla (как у pilorama98.ru).
- **theme-ассеты из MinIO md5-IDENTICAL бою** (проверены css/js/png/svg под `/themes/389ad745e3ee4f23be16df38440a0941/`): 7/7 MD5-OK, http 200.
- ⚠️ **Cache-Control ОТСУТСТВУЕТ на theme-ассетах — КОРРЕКТНО** (labtools.pro `CachingOptions=[]` empty-config; движок 0.28.7 не ставит заголовок = бой). НЕ флагать как дефект (в отличие от emspb/labtools где был `max-age=86400`).
- **Контент-страницы рендерят реальное тело** (12–22 KB), дельта vs бой +155…+222 B — стабильна, объясняется длиной staging-host в canonical/og (на 11 симв. длиннее www; на cutover уравняется в byte-parity).
- Контейнер healthy, MSSQL (tedious) + S3 (aws-sdk) подключены, `listening at http://localhost:5000`, лог чист (только deprecation-warnings tedious/aws-sdk-v2).
### Косметика (не дефект, отдано workshop)
Стартовый баннер в логе — `labtools.ru (snolla) listening…` — строка-константа унаследована из labtools.ru-референса при скаффолде. Идентификация сайта = siteId в production.json (663F9410…), на рендер/контент не влияет.
## Cutover (live DNS) — ✅ ВЫПОЛНЕН 2026-07-02
Порядок был (как emspb):
1. **(оператор)** reg.ru: A-записи `labtools.pro` + `www.labtools.pro` → `89.253.255.94` (с RUVDS `80.64.31.36`).
2. **(ops) verify флипа перед traefik:** предыдущие проверки ловили старый TTL-кэш (публичные резолверы отдавали 80.64.31.36); **авторитетный `ns1.reg.ru` через `Resolve-DnsName` подтвердил** `labtools.pro`+`www` → `89.253.255.94` (TTL 3600), 8.8.8.8 догнал. Именно `.pro` (НЕ `.ru`). Урок: при "поменял DNS" проверять авторитетный NS напрямую, а не только кэширующие резолверы. Cм. memory [operator-dns-flip-verify-domain-before-cutover].
3. **(ops) ТОЛЬКО ПОСЛЕ flip:** стек `labtools-pro` (Id 19) traefik-rule → `Host(\`labtools.pro\`) || Host(\`www.labtools.pro\`)` (Portainer PUT, env сохранён 8/8, pullImage=false). LE HTTP-01 выпустил cert **мгновенно** (ssl_verify=0 с первого хита).
4. **Live-smoke GREEN:** оба хоста 200 (nav+каталог+продукты); редиректы (ball-mills→lshm-750, /Contacts→lowercase, /contacts/→trailing) parity; sitemap index 7 детей host=www; контент реальный (12728 B, `lang="en"`). staging-хост `labtools-pro.vds.kzntsv.site` убран из rule (→ 404). **Внешняя проверка с RUVDS-хоста (Королёв, public DNS):** `labtools.pro`+`www` → 89.253.255.94 HTTP 200, cert доверенный.
5. RUVDS labtools.pro — rollback-путь, жив (отвечает 301, TLS ок), НЕ тронут.
⚠️ **Порядок критичен:** Host-правило добавлять ТОЛЬКО ПОСЛЕ flip DNS (иначе traefik проактивно тянет LE-cert HTTP-01, challenge упадёт на RUVDS → сожжём rate-limit).
## Rollback
- **DNS:** вернуть A-записи `labtools.pro`/`www` → `80.64.31.36` (RUVDS IIS живой, нетронут). Откат = смена DNS.
- **Стек:** Portainer → stack `labtools-pro` (Id 19) → remove (или откат тега образа). Образ в registry остаётся.
- RUVDS IIS labtools.pro **не выводить** до явного решения оператора (директива 2026-07-01). (Ср.: emspb.ru биндинги на RUVDS сняты 2026-07-02 после подтверждённого cutover — см. [[../entities/ruvds-iis-host]].)
## 0.42.1 in-place bump — ✅ ВЫПОЛНЕН 2026-07-05
Тираж snolla 0.42.1. labtools.pro обновлён на живом стеке 19 **in-place** (домен уже на VDS, DNS не трогали).
Рецепт — [[snolla-live-prod-inplace-image-bump]]. Оператор дал отмашку на боевой apply.
- **Образ:** `registry.kzntsv.site/labtools-pro:0610432` (snolla 0.28.7→**0.42.1**, digest `3d543fc…`). Собран на VDS.
- **Acceptance С VDS (throwaway-staging из env стека 19):** **25 sitemap page-locs все 200** (+robots+sitemap.xml = 27 прогерских), content-not-lost 25/25 (0 потерь).
- **Order-парити ВСЕ 3 секции MATCH == прод** (фикс liquid 0.10.2): presses=`plg-20,plg-12`, milling=`milling-jars,grinding-media`, press-forms=**12** изделий (round-xrf…round-collapsible). NB: первичный ручной счёт дал 13 (задвоил тайл), прог-сверка = 12, парити не задет.
- **Swap:** Portainer PUT стека 19 (env 8/8 сохранён) → healthy → live-smoke GREEN (nav+presses+press-forms 200, order вживую), TLS CN=labtools.pro не тронут.
- **Rollback:** тег `labtools-pro:7bd9fae` (0.28.7, в registry) / стек 19 PUT назад / DNS→RUVDS.
- Compose обновлён на `0610432`. Таска `[labtools-pro-deploy-snolla-0-42-1]` 🟢. Notify=victor/labtools.pro.
## 0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
4-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru — все GREEN). MinIO split Track B3-v3.
Паттерн отработан x3; labtools.pro = English-twin (`Culture=en`) labtools.ru, отдельный репо `victor/labtools.pro`.
Оператор дал GO на боевой apply после staging parity-gate.
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` (commit `12f8992`) + `resolutions`-блок в root `package.json` (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + реген `yarn.lock` (commit `a106622`, single @smithy/signature-v4@5.3.11 deduped). Pushed victor/labtools.pro master `6c7ea88..a106622`.
- **Endpoint fix `6c7ea88`** (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе `0610432` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
- **Build на VDS:** `git archive a106622` → `~/build/labtools-pro` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/labtools-pro:a106622` (+latest), digest `sha256:2152642c4b17…`, 476MB, EXIT=0.
- **Staging :5081 parity-gate GREEN** (throwaway контейнер из env живого стека 19, 8 секретов):
- `/` nav catalog = **4-байт diff = `og:url`+`og:image` request-host** (`localhost:5081` 13 chars vs `www.labtools.pro` 15 chars × 2 = 4B). **Benign request-context** — post-cutover (оба за traefik Host=www.labtools.pro) byte-identical. (Ср. kupimknigi 3-байт form-url diff.)
- Redirects (`/contacts/`→`/contacts`, `/products/laboratory-ball-mills`→`/lshm-750`) **IDENT** (301).
- `/robots.txt` `/sitemap.xml` **IDENT** (7 sitemap детей == live, locs identical, host=www).
- **theme `lato.css` 200 9994B md5-identical** — v3 aws-sdk v3 S3-direct read из vds MinIO 2025 WORKS (ключевая v3-верификация).
- **Order-parity** `/products/press-forms` = 12 изделий identical order (multi-position…vacuum-ring).
- Self-consistency: всё 200/3xx, 0 × 4xx/5xx.
- **Portainer PUT stack 19 LIVE:** node in-memory `put-stack` (НЕ PS Invoke-RestMethod — корраптит кириллицу compose; скрипт `scripts/tmp/put-stack-labtools-pro.mjs`), env 8/8 preserved, `prune:false pullImage:true mem_limit 512m`. PUT 200. Контейнер `a106622` healthy сразу.
- **Live smoke GREEN (via traefik):** `/` 200 12736B, `/about`+`/contacts` 200, `/robots.txt`+`/sitemap.xml` 200 (7 детей все 200), 3 каталог-секции 200, продукты (`press-forms/ring` 17255B, `laboratory-presses/plg-20` 22077B) 200, theme `lato.css` 200 9994B (v3 S3-direct), redirects parity, `labtools.pro` apex 200. Лог чист (tedious deprecation + cosmetic `labtools.ru listening` banner — known benign). TLS CN=labtools.pro не тронут (LE notBefore Jul 2 / notAfter Sep 30 2026).
- **Rollback:** `labtools-pro:0610432` (0.42.1, жив в registry) — Portainer PUT назад / compose revert.
- Compose (admin source-of-truth) синхронно закоммичен `0610432`→`a106622`. 4/6 v3 сайтов GREEN. Остались: **emspb** (стек 18), **tandemmebel** (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).

View File

@@ -1,62 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: maljarka.tandemmebel.ru snolla-app — VDS deploy runbook (restore 2026-07-31)
type: concept
tags: [maljarka, tandemmebel, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, landing, ruvds-decomm]
related: [[../entities/vds-kzntsv], [on-snolla-vds-deploy-runbook], [tandemmebel-vds-deploy-runbook], [morethencms-null-settingsdata-https-502], [snolla-local-admin-and-on-snolla-migration-design]]
updated: 2026-07-31
---
# maljarka.tandemmebel.ru → VDS deploy runbook (restore) **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:170]] (concepts/maljarka-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Восстановление лендинга `maljarka.tandemmebel.ru` («Малярка от Тандеммебель», snolla-app `@snollajs/snolla` **0.43.2** / aws-sdk v3, server-side Liquid) — **7-й сайт тиража snolla на VDS**. Паттерн = [[on-snolla-vds-deploy-runbook]] (прецедент: тоже лендинг с RUVDS catch-all origin, 1 Page `/` + empty content + StaticPages, реконструкция layout.liquid).
## Почему восстанавливали (root cause)
maljarka была **.NET MoreThenCms tenant** (тема «Reversal», siteId `A2476738-E26C-49D5-95AD-958F42DCCDD0`), служила на **RUVDS IIS catch-all** (`80.64.31.36`). В v3 тираж (6 Node snolla-app сайтов, 2026-07-30) **не входила** — осталась на RUVDS. **RUVDS декоммишнен 2026-07-21** ([[../entities/ruvds-iis-host]] §DECOMM, погашен у провайдера) → maljarka потеряла единственный публичный хост. DNS перебросили на VDS (CNAME → `tandemmebel.ru` → `89.253.255.94`), но `Host()`-правила и snolla-app с siteId `A2476738` на VDS не было → traefik 404. Аудит 21.07 «maljarka VDS=200» = **false-positive** с локального IIS воркстейшна (local .NET-admin catch-all поднят 20.07, LAN-DNS отдавал его; memory `workstation-lan-dns-serves-local-cms-copy`). Контент в shared `MoreThenCms` DB (`mssql.kzntsv.site`) остался цел — восстанавливаемо.
## Артефакты
- **Код:** `victor/maljarka.tandemmebel.ru` @ `7f03d67` (apps/web, snolla 0.43.2). Клон `victor/on.snolla.com` (lean-база: 4 view-файла, root `package.json` v3 resolutions). `deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`).
- **Образ:** `registry.kzntsv.site/maljarka:7f03d67` (+`:latest`), digest `sha256:df4e3642…`. Имя **`maljarka`** (= стек/контейнер). Собран НА VDS (обход traefik-499).
- **Стек Portainer:** `maljarka` (**Id 23**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/maljarka.compose.yml`.
- **siteId:** `A2476738-E26C-49D5-95AD-958F42DCCDD0` (non-secret, в `production.json` + `default.json`).
- **siteUrl:** `https://maljarka.tandemmebel.ru`.
- **Runtime env (8 секретов):** `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD` — Portainer stack env (переиспользованы verbatim из tandemmebel стека 20 — общая snolla-инфра). НЕ в образе, НЕ в git.
- **mem_limit:** `512m`.
## Site model (MoreThenCms DB, siteId A2476738)
- 5 Pages (intro/services/portfolio/about/contacts), но **Published=true только `/`** (content пуст — лендинг baked в теме) → sitemap = 1 loc `/`.
- 4 StaticPages (all published, sitemap=false): `/index.html` (18401B), `/portfolio-single.html` (1336B), `/portfolio-single-gallery.html` (1567B), `/portfolio-single-video.html` (822B) — отдаются snolla из DB verbatim.
- 2 Domains rows (→ robotsTxt-гоча НЕ applies, `app.locals.domain` set; index.js null-guard остался как defensive).
- SettingsData `httpSecure` блок на месте (fix 2026-06-08, [[morethencms-null-settingsdata-https-502]]).
- Тема «Reversal» `4881FC7F` → MinIO bucket `themes` prefix `4881fc7f52034f8eb371fe5637b1cce1/` (78 файлов, УЖЕ была в MinIO — заливать не понадобилось).
## Реконструкция шаблонов
- `views/layout.liquid` = **byte-identical** рендеру локального .NET-admin (`curl --noproxy '*' --resolve maljarka.tandemmebel.ru:80:127.0.0.1`, 43730B). `{{ item.content }}` вставлен **inline** после последней `</section>` (content пуст → empty output → byte-parity сохранён).
- `error_404.liquid` / `error_500.liquid` — Reversal-тема + maljarka-бренд (вместо on.snolla SNOLLA-бренда).
- Источник реконструкции = локальный catch-all IIS `snolla` ([[snolla-local-admin-and-on-snolla-migration-design]] §Task A) — тот же MoreThenCms DB + та же тема.
## Deploy 2026-07-31 (DNS уже на VDS — без DNS-flip-шага)
1. **Репо:** `victor/maljarka.tandemmebel.ru` создан на gitea (API, admin-token). Клон on.snolla.com → swap `production.json` (siteId A2476738, siteUrl, appName) + package names → `yarn install` (regen lockfile под переименованный workspace) → push. SHA `ebebe4e` → `7f03d67` (lockfile).
2. **Build на VDS:** `git archive 7f03d67` → scp → `~/build/maljarka` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<vitya JWT>` → `registry.kzntsv.site/maljarka:7f03d67`(+latest). Guard: `config/default.json` ABSENT в архиве (.dockerignore), snolla 0.43.2, `yarn install --immutable` GREEN (lockfile перегенерён под maljarka workspace). Push OK.
3. **Throwaway-staging :5081** из env live tandemmebel-контейнера (8 секретов, `--network proxy`). Healthy. **Completeness-gate С VDS GREEN:** `/` **byte-identical** oracle (sha256 `64011be6…`, 43730B), robots 200, sitemap-pages-1 1 loc `/` (+ пустой blog-index — benign parity, как on-snolla `/c`), 4 staticpages 200 (sizes == DB clen), theme asset `bootstrap.min.css` 200 103320B (MinIO Reversal подтверждена), unpublished /about/services/contacts/intro → 404 (parity). 0 регрессий.
4. **Portainer стек 23** создан node-скриптом `create-maljarka-stack.mjs` (JWT auth, **НЕ PS** — кириллица compose; memory `ps51-iso8851-corrupts-portainer-stackfile`), env 8/8, `mem_limit 512m`, `Host(\`maljarka.tandemmebel.ru\`)`. DNS уже `89.253.255.94` → **LE-серт issued on first hit**: CN=maljarka.tandemmebel.ru, issuer YR1, until 2026-10-29.
5. **Live smoke GREEN:** `/` 200 43730B byte-identical, `<title>Малярка от Тандеммебель</title>`, 4 staticpages 200 (sizes == DB), theme asset 200, robots/sitemap 200, TLS LE. Log чист.
## Гочи
- **`variant-cache` bucket на vds MinIO 2025 (КРИТИЧНО, shared v3-регрессия)** — v3 minio-split перевёл сайты на `minio.vds.kzntsv.site`, но `variant-cache` bucket не создали там (был на старом `minio.kzntsv.site`/books-vds 2020). snolla sharp-pipeline (`variantCache.js` `DEFAULT_NAMESPACE='variant-cache'`, `storage.js: bucket=storageClient`) → **NoSuchBucket → 500 на ALL sharp gallery-images** (не только maljarka — на всех 6 v3 сайтах с 2026-07-30). Заметно на maljarka-портфолио (35 фото). v3-gate не поймал т.к. проверял theme CSS (`themes` bucket, themeFiles — без sharp), не gallery-image URLs. **Fix 2026-07-31:** `mc mb vds/variant-cache` (пустой, кэш регенерируем — миграция старых entries НЕ нужна). После: portfolio 34/34 → 200 image/webp. См. [[minio-split-cutover-runbook]] Track A + memory `variant-cache-bucket-missing-on-vds-2025`.
- **yarn.lock workspace-rename** — переименование root package (`on.snolla.com`→`maljarka.tandemmebel.ru`) и `apps/web` name (`@on-snolla/web`→`@maljarka/web`) требует regen yarn.lock, иначе `yarn install --immutable` падает (lockfile ссылается старые workspace-дескрипторы). Fix: `yarn install` локально под vitya JWT → commit обновлённого lockfile.
- **workstation-curl отравлен локальным HTTP-прокси** — `--resolve` игнорируется прокси (CONNECT), обе пробы уходят на VDS. External verify prod-maljarka с воркстейшна = только через VDS-ssh curl (или `curl --resolve :443:89.253.255.94 --noproxy`, но прокси всё равно может перехватить). Локальный IIS-рендер для реконструкции — через `--noproxy '*' --resolve :80:127.0.0.1`.
- **hosts override на воркстейшне** — Task A оставил hosts-override для .snolla.com (НЕ maljarka), но local IIS `*:80` catch-all + LAN-DNS могут перехватить. Оракул-рендер снимался явно через 127.0.0.1.
- **robotsTxt-гоча НЕ applies** — у maljarka 2 Domains rows (`app.locals.domain` set). index.js null-guard (из on.snolla) остался как defensive, не вредит.
- **RUVDS DECOMM** — rollback только образ-тегом в registry, НЕ DNS (RUVDS погашен 2026-07-21).
## Rollback
- **Образный:** PUT стека 23 назад на предыдущий тег в registry. С первого deploy ведём rollback-теги (пока только `7f03d67` = `:latest`).
- DNS-rollback НЕ доступен (RUVDS DECOMM).
- Если layout сломан: revert Host-правила (вернёт 404) ИЛИ поправить `layout.liquid` + rebuild.
## Связанное
- Тираж snolla на VDS теперь **7/7**: labtools.ru(17) / emspb(18) / labtools.pro(19) / tandemmebel(20) / kupimknigi(21) / on-snolla(22) / **maljarka(23)**.
- Прецедент-паттерн: [[on-snolla-vds-deploy-runbook]] (лендинг, реконструкция из .NET-admin рендера).
- 502-на-HTTPS fix (RUVDS-эпоха, 2026-06-08): [[morethencms-null-settingsdata-https-502]].
- Локальный .NET-admin (источник реконструкции): [[snolla-local-admin-and-on-snolla-migration-design]] §Task A.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:1254]] (concepts/mappa-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -20,7 +20,7 @@ related: [[vds-kzntsv]], [[recovery-architecture-snapshot]], [[mssql-on-vds]], [
| Endpoint (внешний, TLS) | `https://minio.kzntsv.site` (traefik → `minio:9000`, LE cert, port 443) | | Endpoint (внешний, TLS) | `https://minio.kzntsv.site` (traefik → `minio:9000`, LE cert, port 443) |
| Endpoint (сырой host-порт) | `http://89.253.255.133:9000` (тот же бэкенд, ssl:false) | | Endpoint (сырой host-порт) | `http://89.253.255.133:9000` (тот же бэкенд, ssl:false) |
| Endpoint (inter-container на books-vds) | `http://minio:9000` (сеть `proxy`) — так ходит imgproxy | | Endpoint (inter-container на books-vds) | `http://minio:9000` (сеть `proxy`) — так ходит imgproxy |
| accessKeyId | `AKIAJ2YJP72W6ZHCRE6Q` (= `MINIO_ACCESS_KEY`, root, read+write all) | | accessKeyId | `secret:pass/minio-vds/full-env` (= `MINIO_ACCESS_KEY`, root, read+write all) |
| secretAccessKey | в `pass minio-vds/full-env` (`MINIO_ROOT_PASSWORD`) | | secretAccessKey | в `pass minio-vds/full-env` (`MINIO_ROOT_PASSWORD`) |
| region / pathStyle / ssl | `local` / `true` / **`true` для https-хостнейма** (false для сырого `:9000`) | | region / pathStyle / ssl | `local` / `true` / **`true` для https-хостнейма** (false для сырого `:9000`) |

View File

@@ -1,280 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: MinIO split — cutover runbook (Track A books-vds 2020→2025 + Track B CMS-media → vds)
type: concept
tags: [minio, vds, books-vds, cutover, runbook, migration, snolla, s3, imgproxy, obsidian]
related: [[../entities/vds-kzntsv], [../entities/books-vds], [on-snolla-vds-deploy-runbook], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [minio-imgproxy-on-vds], [portainer-stack-management-vds]]
updated: 2026-07-30
---
# MinIO split — cutover runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:173]] (concepts/minio-split-cutover-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
> **Режим:** подготовка. **На железе — НИЧЕГО без команды user.** Все команды готовы к выполнению по «го».
> Owner: admin. Создано 2026-07-29. Перенесён из `scripts/minio-split-cutover/` 2026-07-30 (дисциплина: runbook'и → `.wiki/concepts/`).
## Goal
Разнести MinIO-контент на 2 сервера:
- **books-vds shared `minio`** (`minio.kzntsv.site`) → только slovo-бакеты books-приложений. Апгрейд 2020→2025.
- **books-vds `bookva-minio`** → bookva-бакеты (уже там с 2026-05). Апгрейд 2020→2025. Консоль наружу НЕ выставляем.
- **vds `minio`** (`minio.vds.kzntsv.site`, уже 2025-09-07) → CMS-медиа snolla-сайтов + artmone + obsidian + modules + modulair + maxmind + strapi.
- Креды **не трогаем** (один root-ключ на все инстансы; **доступ → `pass show minio-vds/full-env`**, поля `MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`). Креды НЕ в git.
## Два независимых трека
- **Track A — апгрейд books-vds minio (shared + bookva).** Не требует правок apps (transparent: hostname/creds/контент сохраняются). Можно делать отдельно, до/после Track B.
- **Track B — сплит CMS-медиа на vds.** Координированный cutover: миррор → DNS-flip imgproxy → snolla deploy. Требует гейтов.
## Progress (2026-07-30)
| Шаг | Статус | Примечание |
|---|---|---|
| B1 миррор CMS-медиа books-vds → vds minio | ✅ DONE | galleries/pilorama98/themes/assets смиррорены (свежим миррором поверх stale 2026-05) |
| B2 DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 | ✅ LIVE | imgproxy.kzntsv.site/<pilorama98-image> → 200 webp (vds imgproxy ← vds minio); CMS-сайты берут картинки через vds |
| **B3 on-snolla v2-пилот (S3-direct-path)** | ✅ LIVE 2026-07-30 | in-place bump стека 22 `473923e494db→07db7e3` (snolla 0.42.1 v2, config endpoint → minio.vds.kzntsv.site, без бампа package.json). Theme-assets `/themes/c406…/…` → 200 97324 B == live (aws-sdk v2 → vds minio 2025 — compat подтверждена, главный риск закрыт). Sitemap 3/3 parity, `/c` 404 parity. Rollback-тег `473923e494db` в registry. **Промежуточный** шаг — v2 совместим, но v3 — конечная цель (user 2026-07-30). |
| **B3v3 on-snolla v3-пилот** | ✅ LIVE 2026-07-30 | `@snollajs/snolla 0.43.2` published (verdaccio latest). Live MinIO verify ЗЕЛЁНЫЙ (snolla, оба конфига). **v3 cutover 2026-07-30T11:28Z**: bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (root package.json), commit `eb8c143` pushed, build на VDS → `registry.kzntsv.site/on-snolla:eb8c143`, Portainer PUT stack 22 env-preserving (8/8), prune:false pullImage:true, mem_limit 512m. Throwaway staging :5078 parity-gate GREEN (theme asset 200 97324 B byte-identical, sitemapindex+3 sub-sitemaps byte-identical, /c 404 parity). Live smoke GREEN: / 200, robots/sitemap/sub-sitemaps 200, /yandex 200, theme asset 200 97324 B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025). TLS не тронут (LE до Oct 18). Rollback = образ-тег `07db7e3` (v2, жив). |
| **B3v3 kupimknigi.spb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.0→0.43.2` + resolutions (commit `5364353`, apps/web + root). Build на VDS (archive → `~/build/kupimknigi`) → `registry.kzntsv.site/kupimknigi:5364353`(+latest). Bundles a83e2a9 endpoint fix (minio.vds.kzntsv.site) — был в репо, но в бегущем образе 9608ff6 (v2) его не было. Throwaway staging :5079 parity-gate GREEN: `/` byte-identical с real Host, theme asset `toolbox.css` 200 35799 B byte-identical (v3 aws-sdk v3 S3-direct read из vds MinIO 2025), robots 200, `/callback-order/`→301. Portainer PUT stack 21 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200, robots/sitemap 200, H1 «Скупка книг на дому в СПб…», theme asset 200 35799 B. TLS LE до Oct 3 (не тронут). **Rollback = `9608ff6`** (v2, жив в registry). |
| **B3v3 labtools.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `0dc0b4e`, apps/web + root). Endpoint fix `4f3314f` в master, но в бегущем образе `566d41c` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/labtools`) → `registry.kzntsv.site/labtools:0dc0b4e`(+latest). Throwaway staging :5080 parity-gate GREEN: `/` byte-identical (18594B), theme `lato.css` 200 9239B byte-identical (v3 S3-direct), sitemap 38 locs == live (0 diff, 0 bad), section order-parity byte-identical. Portainer PUT stack 17 env-preserving (8/8, node in-memory), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: menu pages 200, theme 200 9239B, TLS CN не тронут (LE до Sep 30). **Rollback = `566d41c`** (v2, жив). См. [[labtools-vds-deploy-runbook]] §0.43.2. |
| **B3v3 labtools.pro** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commits `12f8992` bump + `a106622` yarn.lock реген — обязателен, иначе `yarn install --immutable` падает YN0028). Endpoint fix `6c7ea88` в master, но в бегущем образе `0610432` (v2) его не было. Build на VDS (archive → `~/build/labtools-pro`) → `registry.kzntsv.site/labtools-pro:a106622`(+latest), digest `2152642c4b17`. Throwaway staging :5081 parity-gate GREEN: `/`+nav+catalog 4-байт diff = `og:url`/`og:image` request-host (benign), redirects IDENT, robots/sitemap IDENT (7 детей == live), theme `lato.css` 200 9994B md5-identical (v3 S3-direct), order-parity press-forms 12 изделий. Portainer PUT stack 19 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav+каталог+продукты 200, theme 200 9994B, TLS CN=labtools.pro не тронут (LE до Sep 30). **Rollback = `0610432`** (v2, жив). См. [[labtools.pro-vds-deploy-runbook]] §0.43.2. |
| **B3v3 emspb.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `5afd8a1`, apps/web + root). Endpoint fix `218c3ae` в master, но в бегущем образе `95a5c42` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/emspb`) → `registry.kzntsv.site/emspb:5afd8a1`(+latest), digest `72bb59fc…`. Throwaway staging :5082 parity-gate GREEN: robots md5 identical (31B), sitemap 29 locs == live, 29/29 routes 200 (+2B host-context benign), theme assets 7/7 md5 IDENTICAL (v3 S3-direct: snolla-framework/bootstrap/toolbox/theme/slick + images/2.jpg + /images/3.jpg 115618B). Portainer PUT stack 18 env-preserving (8/8, node in-memory put-stack), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: nav 200, /services+/index.php 404, theme assets md5 LIVE==staging, redirects 301 byte-identical post-cutover, apex 200. TLS не тронут. **Rollback = `95a5c42`** (v2, жив). См. [[emspb-vds-deploy-runbook]] §0.43.2. |
| **B3v3 tandemmebel.ru** | ✅ LIVE 2026-07-30 | bump `@snollajs/snolla 0.42.1→0.43.2` + resolutions (commit `9d51c09`, apps/web + root) + реген `yarn.lock` (single `@smithy/signature-v4@5.3.11`). Endpoint fix `f4ddce6` в master, но в бегущем образе `8df10ee` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio. Build на VDS (archive → `~/build/tandemmebel`) → `registry.kzntsv.site/tandemmebel:9d51c09`(+latest), digest `d14982c24dc7…`, 476MB (v2 8df10ee 583MB — v3 leaner). Throwaway staging :5020 parity-gate GREEN: robots md5 `a01e4c60…` identical (87B), sitemap 5 sub-sitemaps → **184 locs == live 184 IDENTICAL** (0 prod-only/new-only), self-consistency 183×200 + 1×404 (`/articles` parity benign), `/` +557B host-context benign, theme `Gotham-Pro.css` 200 4436B (v3 S3-direct). Portainer PUT stack 20 env-preserving (8/8, node in-memory put-stack — кириллица compose), `prune:false pullImage:true mem_limit 512m`. Live smoke GREEN: `/` 200 45742B, `/projects` 200, sitemap 184 locs, robots md5 identical, theme 200 4436B. TLS CN=tandemmebel.ru не тронут (LE до Oct 10). **Rollback = `8df10ee`** (v2, жив). См. [[tandemmebel-vds-deploy-runbook]] §0.43.2. **6/6 v3 сайтов GREEN — тираж v3 ЗАВЕРШЁН.** |
| Track A (books-vds minio 2020→2025) | ✅ DONE 2026-08-03 | **Оба инстанса переведены** на `RELEASE.2025-09-07T16-13-09Z` (тот же digest что vds). Shared `minio` (стек 30): зеркало всех 17 бакетов → `/usr/docker/minio/data-new`, Portainer swap, traefik/imgproxy работают. `bookva-minio` (стек 49): зеркало `books` (38692 obj) → том `bookva-minio-data-new` (docker volume rename НЕдоступен — старый daemon API; живёт на -new, бэкап-скрипт обновлён). Старые данные (`/usr/docker/minio/data`, `bookva-minio-data`) сохранены 48ч. Gotchas: (1) mc multipart-баг `ContentLength=16777216` на крупных файлах → `--disable-multipart`; (2) транзиентные `Connection closed by foreign host` при дефиците памяти (3.8G) при ПАРАЛЛЕЛЬНЫХ зеркалах → последовательно + retry-цикл; (3) compose файлы CRLF → `\r` в секрете ломает Authorization → `tr -d '\r'`; (4) Portainer stack file — через API `/api/stacks/{id}/file`. |
| **variant-cache bucket на vds 2025** | ✅ DONE 2026-07-31 | v3 cutover перевёл сайты на vds MinIO 2025, но `variant-cache` bucket не создали там (был на books-vds 2020). NoSuchBucket → 500 на ALL sharp gallery-images всех 6 v3 сайтов с 2026-07-30 (gate проверял theme CSS, не gallery URLs). Fix: `mc mb vds/variant-cache` (пустой, регенерируем). maljarka portfolio 34/34 → 200. См. memory `variant-cache-bucket-missing-on-vds-2025` + [[maljarka-vds-deploy-runbook]] §Гочи. |
| B4 .NET catch-all админ s3 endpoint | ✅ DONE 2026-07-30 | `C:\sites\snolla\Web.config` `<fileStorageClients>` 6/6 endpoint `minio.kzntsv.site`→`minio.vds.kzntsv.site` (galleries/assets/themes×4). region `us-east-1`, forcePathStyle (bin: AWSSDK.Core/S3 v3 модульный, bindingRedirect AWSSDK 1.3.19 — legacy-мусор). Pre-flip verify: `MINIO_ROOT_USER`==Web.config accessKey `AKIAJ2YJP72W6ZHCRE6Q` (reuse, no rotation) → mc alias Web.config-кредами → `minio.vds.kzntsv.site` → galleries/assets/themes читаются (B1 mirror жив). IIS auto-recycle по Web.config; login 200. **Превью:** оказался pre-existing баг S3-провайдера (НЕ от flip): `GalleriesStorage`/`AssetsStorage` ctor падал `KeyNotFoundException` на `config["ownerId"]` — контроллер-пути gallery preview/upload/delete и media-assets передавали только `siteContext`. Self-test провайдера gap — покрывал `GalleryImagesService` + unit-тесты `S3Ids`/`S3ObjectKey`, но preview-контроллер не тестировался. Фикс в провайдере (drop-in 1 DLL, MoreThenCms.FileStorage.S3.dll — `ToolsController` в `MoreThenCms.Web.dll` не собирается на боксе): `S3CloudStorage.ReadOwnerId` null-safe (explicit ownerId винит; иначе `siteContext.AssetsOwnerId`==SiteId; пусто → null без throw). Тесты 21/21 (4 новых). Live-verified: превью грузятся против vds MinIO. Коммит MoreThenCms `09cb072d`. Backup: `Web.config.b4.bak` + `MoreThenCms.FileStorage.S3.dll.b4.bak`. |
| B5 Obsidian Remotely Save endpoint | ✅ DONE 2026-07-30 | endpoint flipped `minio.kzntsv.site`→`minio.vds.kzntsv.site` в vault `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64 обфускация), bucket `obsidian`, s3Region `us-east-1`, forcePathStyle. Verified деобфускацией config-блока. См. memory `obsidian-remotely-save-endpoint-after-mirror` (менять ТОЛЬКО после B1 миррора — что и было). |
**Решение user (2026-07-30):** сплит без бампа snolla-пакетов — сайты остаются на 0.42.1 + aws-sdk v2, только config endpoint. snolla-евый v3-трек (0.43.1/0.7.8/0.17.2) — HOLD, отдельная история (stostayer). Пилот on-snolla подтвердил: **v2 vs MinIO 2025 работает** (без @smithy pin) — SignatureDoesNotMatch нет.
**Разворот 2026-07-30T11:00Z:** v3 — конечная цель (user). v3 опубликована (см. §B3-v3). Промежуточный v2-пилот доказал совместимость; v3-пилот on-snolla — следующий шаг после live MinIO verify.
---
## §B3-v3 — v3-бамп (resolutions = optional determinism, НЕ mandatory)
`@snollajs/snolla 0.43.2` опубликована 2026-07-30 на verdaccio (latest): `@snollajs/liquid 0.11.0` + `@snollajs/data 0.15.0` + `@snollajs/core 0.26.4` (peerDep `@snollajs/data` widen `<0.15.0`→`<0.16.0` — баг-фикс: кап не расширен в `21c73aa`) + `@snollajs/snolla 0.43.2` (@smithy pin + `@aws-sdk/client-s3 3.1005.0` exact baked в deps). Коммит snolla `a8b198d`. Тесты core 259/259, snolla 120/120.
### Live MinIO verify 2026-07-30 — ЗЕЛЁНЫЙ (оба конфига)
snolla гоняла clean-consumer install `@snollajs/snolla@0.43.2` против живого `minio.vds.kzntsv.site`:
- **Run A** (с resolutions, `@smithy/signature-v4` 5.3.11 на signing-path): LIST/READ-PARITY/PUT/GET/MULTIPART — все OK, EXIT 0.
- **Run B** (БЕЗ resolutions, `@smithy/signature-v4` **5.6.12** на signing-path): те же тесты — **все OK, EXIT 0**.
**Оба зелёных.** 5.6.12 НЕ даёт SignatureDoesNotMatch против vds MinIO 2025. Regression-гипотеза (v3 SigV4 vs MinIO) **не воспроизвелась end-to-end** — была halted-bisect гипотезой, зацементированной как «CONFIRMED» без live-теста (косяк коммуникации, исправлен 2026-07-30). См. admin memory `aws-sdk-v3-smithy-sigv4-minio-regression`.
### Resolutions = optional determinism, НЕ SigV4-необходимость
Dual-instance механика реальна (5.3.11 exact из snolla/core + 5.6.12 из `@aws-sdk/core` рядом, без resolutions), но 5.6.12 на signing-path **работает** против vds MinIO 2025. Поэтому:
- `resolutions`-блок в package.json сайта — **опциональный** determinism-hardening (зафиксировать @smithy от свежего re-resolve, version-stability), НЕ обязательный для SigV4.
- Бамп только версии `@snollajs/snolla` без resolutions — **работает** (verify доказал).
- Рекомендую всё же ставить resolutions (детерминизм, безобидно, 5.3.11 тоже зелёный) — но framing «упадёт без resolutions» **ложен**, убрать.
### Copy-paste resolutions-блок (optional, recommended для determinism)
```json
"resolutions": {
"@aws-sdk/client-s3": "3.1005.0",
"@aws-sdk/lib-storage": "3.1005.0",
"@smithy/signature-v4": "5.3.11",
"@smithy/node-http-handler": "4.4.14",
"@smithy/fetch-http-handler": "5.3.13",
"@smithy/protocol-http": "5.3.11",
"@smithy/middleware-endpoint": "4.4.23",
"@smithy/middleware-retry": "4.4.40"
}
```
### v3 bump procedure (per site, после live MinIO verify ⚪)
1. `package.json`: `@snollajs/snolla` → `0.43.2` + вставить resolutions-блок выше (optional но recommended).
2. `yarn install` (clean resolve — убедиться `npm ls @smithy/signature-v4` == single instance).
3. Build на VDS из site-репо (`deploy/Dockerfile`, build-arg VERDACCIO_TOKEN) → `registry.kzntsv.site/<site>-web:<sha>`.
4. Throwaway staging `:50XX` из env живого контейнера → parity-gate (theme asset 200 97324 B == live, sitemap 3/3, `/c` 404 parity).
5. Portainer PUT env-preserving (8 секретов), `prune:false, pullImage:true`, mem_limit 512m.
6. Live smoke (crawl menu-страниц + viewModel, не только `/`).
7. Rollback = образ-тег (текущий v2-тег).
### Breaking changes v3 (минимизированы back-compat шимом)
- `s3ForcePathStyle` → `forcePathStyle` — рантайм **НЕ breaking** (back-compat шим в storage.js v3 читает оба ключа). Config-правка recommended (forward-clean), не обязательна.
- `sslEnabled` (config) + `S3_SSL_ENABLED` (env) — удалены (unused), игнорируются если остались.
- `aws-sdk` / `s3fs` удалены из snolla deps (s3fs был unused). Consumer-код, импортивший их из snolla-deps напрямую (неофициальная поверхность) — сломается; по коду 6 сайтов такого нет.
- `populate-theme-store.mjs` мигрирован v2→v3 (script, не рантайм) — admin-side, рантайм не трогает.
---
## S3-клиент совместимость (audit — сделано)
| consumer | S3 client | версия | риск vs MinIO 2025 |
|---|---|---|---|
| books (api/web/...) | @aws-sdk/client-s3 (v3) | ^3.450.0 | ✅ низкий |
| modules-db | minio (minio-js) | ^8.0.7 | ✅ низкий |
| snolla (7 сайтов + framework) | **aws-sdk v2** | ^2.1257.0 | ⚠️ EOL, тест обязателен |
| modulair-rag | client inside lightrag:latest image | — | проверить (bucket пуст) |
| obsidian Remotely Save | bundled | 0.5.25 | ✅ низкий |
| .NET admin (AWSSDK) | AWSSDK.S3 | ? | проверить (region `local`) |
**snolla ДО кутовера** запускает локальный node-скрипт (aws-sdk v2) → `https://minio.vds.kzntsv.site`, bucket `galleries` (stale-миррор там есть): listObjectsV2 + getObject. 200 = ок. Падение = бамп v2→v3 в фреймворке до кутовера.
---
# Track A — апгрейд books-vds minio 2020→2025
> Все шаги — на books-vds (`root@89.253.255.133`, ключ `~/.ssh/id_ed25519_books_ops`).
> Transparent: НЕ меняем hostname/creds/имя-контейнера для apps. Только swap бэкенда под тем же именем.
> Креды для `mc alias` — из `pass show minio-vds/full-env` (`MINIO_ROOT_USER`/`MINIO_ROOT_PASSWORD`).
## A1. Shared `minio` 2020→2025
Принцип: новый 2025-контейнер `minio-new` (новый data-dir), `mc mirror` slovo-бакетов, swap traefik route, retire old. Контейнер-имя `minio` + internal DNS `http://minio:9000` сохранить для imgproxy/зависимостей (или imgproxy на books-vds выводится из эксплуатации после Track B DNS-flip — тогда можно переименовать).
**Бакеты, что остаются на shared minio (slovo):** `books` (37442 obj, 1.2 GiB), `ozon` (0B), `manuals` (0B), `test`/`upload`/`imgproxytest`. + почистить bookva-дубли (см. A3).
```bash
# Креды из pass (НЕ хардкодить, НЕ коммитить):
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# 0. Стоп-условие: books apps живы на старом minio (baseline). Снимок логов.
ssh root@89.253.255.133 'docker logs --tail 50 books-api; docker exec minio sh -c "echo ok"'
# 1. Новый 2025 контейнер, новый data-dir, тот же network=proxy, креды из env
ssh root@89.253.255.133 'mkdir -p /usr/docker/minio-new/data'
# compose: image minio/minio:RELEASE.2025-09-07..., command server /data --console-address :9001,
# env MINIO_ROOT_USER/PASSWORD (== $MINIO_ROOT_USER/$MINIO_ROOT_PASSWORD), volume /usr/docker/minio-new/data:/data,
# network proxy, traefik labels minio-console.kzntsv.site→:9001 (новая консоль) — написать compose-файл
# (template ниже; НЕ поднимать до готовности миррора)
# 2. mc mirror slovo-бакетов old(2020) → new(2025)
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set old http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set new http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
for b in books ozon manuals test upload imgproxytest; do mc mb --ignore-existing new/$b; mc mirror --overwrite old/$b new/$b; done
'
# 3. Verify per-bucket object count old==new
# for b in ...; mc ls --recursive old/$b | wc -l ; mc ls --recursive new/$b | wc -l
# 4. Swap: traefik route minio.kzntsv.site → new:9000 (rule в dynamic config / compose labels new-контейнера)
# + minio-console.kzntsv.site → new:9001 (новая веб-консоль 2025; старый /minio/login на :9000 умрёт)
# 5. Smoke: books-api/web/task-runner пишут/читают S3. Picking-list report GET.
# 6. Retire old 2020 minio (docker stop minio; НЕ удалять data 48ч — rollback).
```
**Rollback:** traefik route → old minio:9000; old container ещё running 48ч.
## A2. `bookva-minio` 2020→2025
Контейнер `bookva-minio`, volume `bookva-minio-data` (37074 obj в бакете `books`). НЕ пуст (мигрирован ~2026-05). Console наружу НЕ выставляем.
```bash
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# 1. Новый 2025 контейнер bookva-minio-new (новый volume bookva-minio-data-new), тот же creds
# 2. mc mirror bookva-minio(2020):9001 → bookva-minio-new(2025):900X, бакет books (37074 obj)
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set bvold http://127.0.0.1:9001 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set bvnew http://127.0.0.1:900X "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc mb --ignore-existing bvnew/books; mc mirror --overwrite bvold/books bvnew/books
'
# 3. Verify count: mc ls --recursive bvold/books | wc -l == mc ls --recursive bvnew/books | wc -l (37074)
# 4. Swap: container name bookva-minio (чтобы bookva-api http://bookva-minio:9000 не менялся) → новый
# (down old, up new с именем bookva-minio). Или compose-имя сохранить.
# 5. Smoke: bookva-api report/picking-list GET.
# 6. Retire old (48ч rollback).
```
## A3. Почистить bookva-дубли из shared `books` (после A1, перед mirror-verify)
В shared `books` остались 95+4 bookva-файла от до-миграции (дубли того, что уже на bookva-minio). Bookva-unique ключи вычислить из bookva-db и удалить из shared `books`.
```bash
# Выгрузить bookva-unique s3-пути (на books-vds):
docker exec -i bookva-db mariadb -ubooks -pFNud4Um47y books -N -e \
"SELECT DISTINCT report_remote_path FROM fbs_picking_lists WHERE report_remote_path!=''" > /tmp/bv-paths.txt
# (плюс pdf_remote_path, и из других таблиц — receipts/reports/task-reports — полный набор S3-путей bookva)
# Пересечь с shared-minio books-бакетом; bookva-unique (не в slovo-db) → mc rm из shared books
```
**Гейт:** до удаления убедиться, что КАЖДЫЙ bookva-путь жив на bookva-minio (после A2). Иначе не удалять.
---
# Track B — сплит CMS-медиа на vds
## B0. vds backup (precondition)
`scripts/vds-backup-rsync-kreknin/run.sh` rsync sources — добавить `/opt/stacks/storage/minio-imgproxy/data` (и compose/nginx.conf для воспроизводимости):
```diff
/opt/stacks/owncloud \
+ /opt/stacks/storage/minio-imgproxy/data \
/etc/ssh \
```
+ обновить EMAIL_BODY components. На vds: `sudo systemctl restart cron` не нужен (cron читает файл). **Verify:** дождаться реального прогона (05:00) ИЛИ manual `sudo bash /opt/stacks/backup/scripts/run.sh` → ntfy push на `vds-backup` topic (phone-confirm, был открыт).
## B1. Миррор CMS-медиа books-vds → vds minio (свежий)
vds minio уже 2025-09-07. Stale-копия от 2026-05 — перезаписать свежим миррором.
```bash
source <(pass show minio-vds/full-env | grep -E 'MINIO_ROOT_(USER|PASSWORD)')
# На books-vds (источник) → vds minio (назначение), через mc. Creds одинаковые.
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set src http://127.0.0.1:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc alias set dst https://minio.vds.kzntsv.site "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --insecure >/dev/null
for b in galleries artmone themes assets pilorama98 maxmind obsidian strapi; do
mc mb --ignore-existing dst/$b
mc mirror --overwrite --preserve src/$b dst/$b --insecure
done
'
# variant-cache НЕ миррорим (кэш imgproxy, перестроится на vds).
# modulair / modules — ВНЕ СПЛИТА (user: забыть). Остаются на books-vds shared minio, потребители не трогаются.
```
**Verify (обязательно):** recursive-count src==dst по каждому бакету (mc ls --recursive | wc -l). Совпадение = ок.
- Ожидаемые объёмы: galleries 9984, artmone 16273, themes 10864, assets 4750, pilorama98 225, maxmind 2, obsidian 179, strapi 0. ~8.9 GiB.
## B2. DNS-flip `imgproxy.kzntsv.site` → 89.253.255.94 (reg.ru)
- В reg.ru `kzntsv.site` A-запись `imgproxy` → `89.253.255.94` (сейчас → books-vds 89.253.255.133).
- vds imgproxy-nginx traefik label: добавить `Host(imgproxy.kzntsv.site)` (сейчас serves `imgproxy.vds.kzntsv.site`).
- TTL дождаться (был 86400 — лучше заранее снизить).
- **Verify:** `curl -sI https://imgproxy.kzntsv.site/` → vds traefik; sample CMS image URL (signed) → 200.
## B3. snolla deploy (6 сайтов) — по "го", после B1+B2
Образы собирает admin на VDS (snolla MCP read-only, нет build-канала; либо user даёт канал). Registry = `registry.kzntsv.site` (НЕ stostayer).
```bash
# На vds, для каждого из 6 репо (on-snolla, kupimknigi, tandemmebel, labtools, labtools.pro, emspb):
cd /tmp && git clone git@git.kzntsv.site:victor/<site>.git && cd <site>
git pull # правка s3.endpoint уже в master (snolla запушил: 07db7e3, a83e2a9, 218c3ae, 4f3314f, 6c7ea88, f4ddce6)
docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<from pass vds-kzntsv/verdaccio-ci> \
-t registry.kzntsv.site/<site>-web:<sha> .
docker push registry.kzntsv.site/<site>-web:<sha>
```
Затем parity-gate (throwaway staging `:50XX`) + Portainer PUT env-preserving (по [[snolla-live-prod-inplace-image-bump]]). Smoke каждого сайта (crawl menu pages + viewModel, не только /).
## B4. .NET catch-all админ (воркстейшн) — s3 endpoint
`C:\sites\snolla\Web.config` → `<fileStorageClients>` serviceURL: `https://minio.kzntsv.site` → `https://minio.vds.kzntsv.site` (galleries/assets/themes). imgproxy НЕ трогать (хардкод `imgproxy.kzntsv.site` → DNS-flip решает). IIS auto-recycle по Web.config. Smoke: `/admin/account/login` → Asset Manager превью.
## B5. Obsidian Remotely Save — endpoint (ПОСЛЕ B1 миррора obsidian)
Гейт: бакет `obsidian` смиррорен на vds (B1). Менять ТОЛЬКО тогда.
- Obsidian → Settings → Remotely Save → S3 endpoint: `https://minio.kzntsv.site/` → `https://minio.vds.kzntsv.site/` (бакет `obsidian`, креды те же). ИЛИ ре-обфусцировать `C:\Users\vitya\obsidian\main\.obsidian\plugins\remotely-save\data.json` (rev+base64) при ЗАКРЫТОМ Obsidian.
- Verify: sync run → no errors.
---
# P3 — cleanup / verify (после cutover)
- Retire books-vds 2020 minio containers (после 48ч rollback-окна): `docker stop minio` (old), `docker stop bookva-minio` (old). НЕ удалять data до подтверждения бэкапа.
- Retire books-vds imgproxy stack 29 (после DNS-flip — изображения идут через vds imgproxy). Проверить, что никто не ходит на books-vds imgproxy.
- Удалить с shared `books` bookva-дубли (A3).
- vds-backup: подтвердить что `/opt/stacks/storage/minio-imgproxy/data` попадает в nightly snapshot (B0 verify).
- Smoke: все 6 snolla-сайтов (crawl), books slovo reports, bookva reports, obsidian sync, modules-db.
---
# Открытые/verify (до кутовера)
1. **snolla compat-тест aws-sdk v2 vs vds minio** — snolla делает сейчас (без железа).
2. **.NET admin AWSSDK версия + region** — `local` работает с MinIO 2025? (stostayer нужен был `us-west-1`, но там minio настроен `us-west-1`; books-vds minio = `local`). Verify.
3. **modulair-rag lightrag:latest S3-клиент** — compat с MinIO 2025 (bucket пуст, низкий приоритет).
4. **artmone** — деплой неясен (контейнера на vds нет). Бакет миррорим (B1), но потребителя для endpoint-смены нет. Отдельный вопрос.
5. **bookva-minio расход 368 obj** (shared 37442 vs bookva-minio 37074) — проверить, не stranded ли bookva-файлы в shared (не попали в миррор 2026-05). До A3.

View File

@@ -1,101 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: on.snolla.com snolla-app — VDS deploy runbook (cutover 2026-07-20)
type: concept
tags: [on-snolla, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, landing]
related: [[../entities/vds-kzntsv], [../entities/ruvds-iis-host], [tandemmebel-vds-deploy-runbook], [snolla-live-prod-inplace-image-bump], [portainer-stack-management-vds], [snolla-local-admin-and-on-snolla-migration-design]]
updated: 2026-07-20
---
# on.snolla.com → VDS deploy runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:182]] (concepts/on-snolla-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Вынос посадочной `on.snolla.com` (snolla-app, `@snollajs/snolla` **0.42.1**, server-side Liquid) с
[[../entities/ruvds-iis-host]] (catch-all CMS, 80.64.31.36) в docker-контейнер на [[../entities/vds-kzntsv]]
(89.253.255.94), за traefik. Паттерн = [[tandemmebel-vds-deploy-runbook]]. Отличие: **лендинг** (1 Page `/`
+ 2 StaticPages), БЕЗ блога/каталога/e-commerce → reconstruction = layout.liquid byte-identical prod-HTML.
Culture `en`. Спека: [[snolla-local-admin-and-on-snolla-migration-design]] §Task B.
## Артефакты
- **Код:** `victor/on.snolla.com` @ `473923e494db` (apps/web, snolla 0.42.1). Структура-калька tandemmebel.ru.
`deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root, healthcheck `/robots.txt`).
- **Образ:** `registry.kzntsv.site/on-snolla:473923e494db` (+`:latest`). Имя **`on-snolla`** (= стек/контейнер).
Собран НА VDS (обход traefik-499).
- **Стек Portainer:** `on-snolla` (**Id 22**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/on-snolla.compose.yml`.
- **siteId:** `B9ECDB50-2B93-4CE5-B238-A9918B3F9CF7` (non-secret, в `production.json`).
- **siteUrl:** `https://on.snolla.com` (в `production.json`).
- **Runtime env (8 секретов):** `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD`
— Portainer stack env (переиспользованы verbatim из tandemmebel стека 20 — общая snolla-инфра). НЕ в образе, НЕ в git.
- **mem_limit:** `512m`.
## Site model (MoreThenCms DB, siteId B9ECDB50)
- 1 Page `/` (template `content_page`, `customDefaultLayout=layout`). **Content-поля пустые** → весь
bootstraptor-лендинг baked в `views/layout.liquid` (byte-identical prod). `{{ item.content }}` (пуст) wired.
- Form "Join us" (Path `/`, FormTemplate `join_us`: first_name/last_name/email/website/description/captcha)
baked в `layout.liquid`; POST `/` → snolla `forms` middleware → `form_ajax_response.liquid` (AJAX replace).
Legacy reCAPTCHA v1 в разметке мертва (Google shut down v1) — parity с продом, НЕ чиним.
- StaticPages: `/yandex_7a510ac1f8311fca.html` (verification, IncludeInSitemap=true), `/4a052808276c.html` (not in sitemap).
- `/c` → **404** (stale dead-link в .NET sitemap; snolla-app sitemap тоже его несёт → parity `/`+`/c`+`/yandex`).
- Theme assets `/themes/c406a987ebe14244b0eefe7b8f959a6f/{css,js,images}/*` — из MinIO bucket `themes` (snolla
`themeFiles` middleware; пути literal в `layout.liquid`).
- `/admin` — **публичного `on.snolla.com/admin` не существует** (никогда не было: RUVDS catch-all IIS не
маршрутизировал /admin на публичном on.snolla.com — cert на 80.64.31.36:443 SNI on.snolla.com = чужой
`CN=kupimknigi.spb.ru`, /admin → 404). Админка — только **локальный catch-all IIS `snolla`** (Task A,
воркстейшн). Site alias переименован `on`→`internal` (2026-07-20), доступ = `http://internal.snolla.com/admin`
через hosts override → 127.0.0.1 → 302→login. **Web.config `primaryAlias`** тоже обновлён `on`→`internal`
(default-site resolution; иначе catch-all NRE-500 на ВСЕХ запросах — primaryAlias ссылается на алиас,
переименование сайта без обновления primaryAlias ломает весь catch-all). НЕ часть snolla-app.
## Cutover 2026-07-20 (первый вынос на VDS — DNS-gated)
1. **Build на VDS** (archive `473923e494db` → `~/build/on-snolla` → `docker build -f deploy/Dockerfile
--build-arg VERDACCIO_TOKEN -t .../on-snolla:473923e494db -t .../on-snolla:latest . && push`). Guard:
`config/default.json` ABSENT в архиве (.dockerignore), пин snolla = 0.42.1.
2. **Throwaway-staging :5077** из собранного env (8 секретов inline, т.к. нового live-контейнера ещё нет —
секреты общие с тиражом). `docker run -d --name on-snolla-staging --env-file .staging.env -p 127.0.0.1:5077:5000`.
Healthy. robots/`/`/sitemap 200.
3. **Completeness-gate С VDS** — sitemapindex разворот. **NEW==PROD locs: 3 = 3 IDENTICAL** (`/`, `/c`, `/yandex_…`).
Self-consistency: `/`→200, `/yandex`→200, `/c`→404 (dead-link, parity с прод-оракулом — prod /c тоже 404).
**Byte-parity:** homepage + /yandex **byte-identical** staging vs prod (RUVDS, via `--resolve :443:80.64.31.36`).
GREEN = 0 регрессий.
4. **Portainer стек 22** создан со **staging-Host** `on-snolla.vds.kzntsv.site` (wildcard → VDS), LE=default cert.
Healthy за traefik. (`create-stack.mjs` node-скрипт, Portainer JWT auth, env array 8/8, `mem_limit 512m`.)
5. **DNS flip** — operator: reg.ru `on.snolla.com` A `80.64.31.36` → `89.253.255.94`.
6. **Verify авторит. NS** — `nslookup on.snolla.com ns1/ns2.reg.ru` = `89.253.255.94` (BEFORE traefik rule swap —
memory `operator-dns-flip-verify-domain-before-cutover`: wrong domain burns LE rate-limit). Public resolvers propagated.
7. **PUT стека 22** — rule `Host(on-snolla.vds.kzntsv.site)` → `Host(on.snolla.com)`, env-preserving (8/8),
`prune:false, pullImage:false`. node `cutover-put.mjs` (Portainer JWT; **re-auth right before PUT** —
jwt из GET-фазы успел протухнуть к моменту PUT, первый PUT дал 401). PUT 200.
8. **Poll** — контейнер healthy, **LE-серт issued on first hit**: CN=on.snolla.com, issuer YR1, until 2026-10-18.
9. **Live-smoke С VDS + external** — `https://on.snolla.com/{robots.txt,/,/sitemap.xml,/yandex_…}` = 200,
`/c` = 404 parity. TLS verify `ssl_verify_result=0` (trusted LE chain). Homepage **byte-identical** prod-HTML.
## In-place bumps (после cutover)
- **07db7e3 — MinIO split config-endpoint (v2, 2026-07-30).** In-place bump стека 22 `473923e494db→07db7e3` без правки `package.json`: только `config/production.json` `s3.endpoint` `minio.kzntsv.site→minio.vds.kzntsv.site`. snolla остался 0.42.1 (aws-sdk v2). Пилот S3-direct-path — theme asset 200 97324 B == live, v2 vs vds MinIO 2025 работает без @smithy pin. Rollback-тег `473923e494db`. См. [[minio-split-cutover-runbook]] §B3 (Track B).
- **eb8c143 — v3 bump (aws-sdk v2→v3, 2026-07-30T11:28Z).** End-goal (user). `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` + `resolutions` (@aws-sdk/client-s3 3.1005.0 exact + @smithy chain 5.3.11) в root `package.json` (optional determinism, single-instance — live verify доказал что 5.6.12 тоже работает, pin для version-stability). Commit `eb8c143` pushed. Build на VDS (archive → `~/build/on-snolla` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN`) → `registry.kzntsv.site/on-snolla:eb8c143` (+`:latest`). Throwaway staging :5078 из env live-контейнера (8 секретов) → parity-gate GREEN (theme asset 200 97324 B byte-identical, sitemapindex+3 sub-sitemaps byte-identical, `/c` 404 parity). Portainer PUT stack 22 env-preserving (8/8, node in-memory `put-stack` — НЕ PS Invoke-RestMethod), `prune:false, pullImage:true`, `mem_limit 512m` preserved. Live smoke GREEN: `/` 200, robots/sitemap/sub-sitemaps 200, `/yandex` 200, theme asset 200 97324 B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025). TLS не тронут (LE до Oct 18). Back-compat шим `s3ForcePathStyle`→`forcePathStyle` (v2-key ещё читается) — config-правки не требовались. **Rollback = `07db7e3`** (v2, жив в registry). См. [[minio-split-cutover-runbook]] §B3-v3 + [[snolla-live-prod-inplace-image-bump]].
## Гочи (специфичные)
- **snolla `robotsTxt` middleware падает на undefined `app.locals.domain`** — on.snolla.com НЕ имеет row в
MoreThenCms `Domains` (served by primaryDomain без per-domain row). `robotsTxt.js` читает
`res.app.locals.domain.robotsTxt` без null-guard → GET /robots.txt (и Dockerfile healthcheck) крашится 500.
**Фикс в `apps/web/index.js`**: после `initApp`, если `!app.locals.domain`, synthesized `{robotsTxt: site.robotsTxt}`
(only robotsTxt.js reads locals.domain; theme resolution uses site.activeThemeId — safe). Без мутации shared DB.
**Альтернатива** (не применена): добавить Domains row (Name=on.snolla.com, SiteId=B9ECDB50, Public=1, ThemeId=null)
— но это мутация shared prod MoreThenCms; предпочтён app-code fallback.
- **Form "Join us" не имеет Liquid form-tag** — snolla не предоставляет form-rendering tag. Форма HTML baked в
`layout.liquid` (поля по FormTemplate `join_us`); POST обрабатывает `forms` middleware (мэтч по Path `/`).
`form_ajax_response.liquid` — AJAX-ответ (`$form.replaceWith(response)`).
- **`/c` в sitemap — dead-link** — snolla SitemapService эмбитит `/c` из sections-source, но страница 404.
Prod-оракул идентичен (404) → benign parity, не регрессия.
- **hosts override на воркстейшне** — Task A оставил `127.0.0.1 on.snolla.com` в hosts (локальный админ catch-all).
External verify прод-on.snolla.com с воркстейшна = только через `curl --resolve on.snolla.com:443:89.253.255.94`
(иначе попадёшь в локальный IIS). См. memory `workstation-lan-dns-serves-local-cms-copy`.
- **Portainer JWT short-lived** — GET stack + GET file + PUT в одном скрипте: jwt из GET-фазы может протухнуть
к PUT. Re-auth (`POST /api/auth`) прямо перед PUT. API-key `ptr_*` даёт 401 (см. [[portainer-stack-management-vds]]).
- **Образных rollback-тегов пока нет** — первый deploy, `473923e494db` = `:latest`. ~~Rollback = revert DNS (RUVDS жив).~~ RUVDS DECOMM 2026-07-21 — rollback только образ-тегом.
## Rollback
- ~~**DNS (предпочт, мгновенный):** revert reg.ru `on.snolla.com` A → `80.64.31.36` (RUVDS IIS жив, не тронут).~~ **НЕАКТУАЛЬНО с 2026-07-21** — [[../entities/ruvds-iis-host]] DECOMM (погашен у провайдера). RUVDS более не rollback-target.
- **Образный (рабочий):** PUT стека 22 назад на предыдущий тег (rollback-теги в registry). `473923e494db` = `:latest` — первый deploy, предыдущего тега нет; с этого дня ведём rollback-теги на каждый bump.
## Связанное
- on.snolla.com admin (`/admin`) остаётся на RUVDS .NET — catch-all IIS `snolla` (см. [[snolla-local-admin-and-on-snolla-migration-design]] §Task A).
- Тираж snolla на VDS: labtools.ru(17), emspb(18), labtools.pro(19), tandemmebel(20), kupimknigi(21), **on-snolla(22)**.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3330]] (concepts/pilorama98-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -36,19 +36,19 @@ docker run -d --name portainer --network proxy \
# API admin/init available 5 minutes after start # API admin/init available 5 minutes after start
curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/admin/init \ curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/admin/init \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}' -d '{"Username":"vitya","Password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}'
# Login → JWT # Login → JWT
JWT=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/auth \ JWT=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/auth \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{"Username":"vitya","Password":"Pryakhin9-VDS-2026"}' \ -d '{"Username":"vitya","Password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' \
| jq -r .jwt) | jq -r .jwt)
# Generate API key # Generate API key
APIKEY=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/1/tokens \ APIKEY=$(curl -sk -X POST https://portainer.vds.kzntsv.site/api/users/1/tokens \
-H "Authorization: Bearer $JWT" \ -H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{"description":"automation","password":"Pryakhin9-VDS-2026"}' \ -d '{"description":"automation","password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' \
| jq -r .rawAPIKey) | jq -r .rawAPIKey)
``` ```
@@ -62,7 +62,7 @@ curl -sk -X POST https://portainer.vds.kzntsv.site/api/endpoints \
## Trade-off ## Trade-off
- ✗ Имя пользователя пароль user'а — приходится менять на «12+ chars» (`Pryakhin9-VDS-2026` вместо `Pryakhin9`). - ✗ Имя пользователя пароль user'а — приходится менять на «12+ chars» (`<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>` вместо `Pryakhin9`).
- ✔ Single source of truth — admin live в DB только через API, всегда последовательное состояние. - ✔ Single source of truth — admin live в DB только через API, всегда последовательное состояние.
- ✔ API token доступен сразу для дальнейшей автоматизации stacks через REST. - ✔ API token доступен сразу для дальнейшей автоматизации stacks через REST.

View File

@@ -19,7 +19,7 @@ Portainer API token из `vds-kzntsv/full-env` `PORTAINER_API_KEY` ранее д
```bash ```bash
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth \ JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"username":"vitya","password":"Pryakhin9-VDS-2026"}' | jq -r .jwt) -d '{"username":"vitya","password":"<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>"}' | jq -r .jwt)
``` ```
Pass-store: `pass show vds-kzntsv/full-env` (full env file со всеми creds). Pass-store: `pass show vds-kzntsv/full-env` (full env file со всеми creds).

View File

@@ -75,3 +75,18 @@ Sub-manifest (и его слои) физически удалены, а **тег
- [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md) — auth/htpasswd, юзер books-ci, GC через v2 DELETE. - [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md) — auth/htpasswd, юзер books-ci, GC через v2 DELETE.
- [`bindmount-config-edit-preserve-mode`](bindmount-config-edit-preserve-mode.md) — как клали кред books-ci в task-runner config (shadow + mode гочи). - [`bindmount-config-edit-preserve-mode`](bindmount-config-edit-preserve-mode.md) — как клали кред books-ci в task-runner config (shadow + mode гочи).
- Хост: [`books-vds`](../entities/books-vds.md) (task-runner), реестр на [`vds-kzntsv`](../entities/vds-kzntsv.md). - Хост: [`books-vds`](../entities/books-vds.md) (task-runner), реестр на [`vds-kzntsv`](../entities/vds-kzntsv.md).
## Обновление 2026-08-23: 2.8.3 vs OCI-index — docker pull «manifest unknown» + фикс docker-форматом
**Симптом:** `docker pull registry.kzntsv.site/books-tool-create-picking-list-pdf:master` → `manifest unknown` (и «no basic auth credentials» до починки config.json — см. [[books-vds-no-gitea-actions]]).
**Диагностика (curl к /v2/<repo>/manifests/<tag>):**
- Accept docker-only (`manifest.v2+json`, `manifest.list.v2+json`) → **404**
- Accept c `application/vnd.oci.image.index.v1+json` → 200 (Content-Type: OCI index)
- child-манифест из index по digest → **404** (удалён прежним host-GC — dangling, как описано выше)
→ registry 2.8.3 отдаёт OCI-index тег только на точный OCI-Accept; docker daemon 26.1.4 получает «manifest unknown». Запушенные buildx/curl-флоу теги = OCI index с **отсутствующим child** = сломанный pull.
**Фикс (проверен): перепуш образа как docker schema2-манифест** — те же блобы (content-addressed), обёртка docker: mediaType `application/vnd.docker.distribution.manifest.v2+json`, config `application/vnd.docker.container.image.v1+json` (НЕ `distribution.image.config.v2+json` — docker его не знает, «(unknown)» на pull), layers `application/vnd.docker.image.rootfs.diff.tar.gzip`. Пути: `docker save` → OCI tar → amd64 child → трансформ mediaTypes → `PUT /v2/<repo>/manifests/<tag>` с docker Content-Type. Pull снова работает.
**Вывод для оператора:** на registry:2.8.3 избегать пуш OCI-формата (buildx по умолчанию), host-GC рвать children нельзя; 2026-08-23 dry-run GC: 67 «eligible», реально существующих файлов ~2.5МБ (остальное — уже удалённые ссылки) → host-GC на живом реестре нецелесообразен, риск > выгоды. Аккуратная чистка buildcache/старых тегов — index-aware инструментом books (`registryGc` в task-runner, v2 DELETE).

View File

@@ -0,0 +1,24 @@
---
title: registry-traefik-buffering — большие пуши умирают (Client Closed Request)
status: live
tags: [registry, traefik, buffering, push, gotcha]
---
# Большие (>1.5GB) пуши в registry.kzntsv.site через traefik — умирают
## Симптом
`docker buildx build --push` (или `docker push`) слоя ~2GB через `registry.kzntsv.site` → `ERROR: unknown: Client Closed Request` (499), обычно после ~5 мин / ~600-800MB. В логах реестра: клиент открывает `POST /v2/<repo>/blobs/uploads/` каждые ~70с, но `PATCH` до реестра НЕ доходит.
## Причина
Traefik-роут реестра имеет buffering middleware (`traefik.http.middlewares.registry-buffering.buffering.maxRequestBodyBytes=4000000000`): traefik буферизует ВЕСЬ body в RAM vds-kzntsv (7.7GB, свободно ~1GB) перед forwarding. Плюс медленное чтение 2GB слоя из buildkit-кэша на books-vds (3.9GB RAM) → OOM-killer режет процесс → «Client Closed Request». curl с /dev/zero (2GB, chunked, 38с) — проходит (нет дискового рида/кэша).
## Обход (без изменения traefik)
- Монолитные uploads curl'ом: `POST /v2/<repo>/blobs/uploads/` → `PUT <Location-from-POST>&digest=sha256:<d>` с телом (Content-Length). Мелкие блобы — так, 201.
- Большой слой: `sync; echo 3 > /proc/sys/vm/drop_caches` → `cat blob | curl -X PATCH -T -` (chunked, стримом) → финальный `PUT <Location-ИЗ-PATCH-ответа>&digest=sha256:<d>` (Location меняется после PATCH!). 202+201.
- Либо собрать образ ТАМ, где живёт реестр (vds-kzntsv), и пушить напрямую в контейнер реестра (мимо traefik).
## Если понадобится починить навсегда
Убрать/поднять buffering middleware на роуте registry (Portainer stack 5 на vds-kzntsv, `/opt/stacks/registry/docker-compose.yml`) — traefik будет стримить body. Требует отмашки (инфра-изменение на проде).
## Связано
[[books-vds-no-gitea-actions]] (локальная сборка + заливка), [[registry-kzntsv-auth-model]] (креды).

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3316]] (concepts/runbooks-index). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,68 @@
---
title: sched-ozon-creds-stub-contract
type: concept
status: live
tags: [books, sched, ozon, creds, tenant-split, anti-leak]
related: [[books-vds]], [[portainer-stack-management-books-vds]]
---
# Sched Ozon creds stub contract (slovo/bookva)
Контракт кредов Ozon между двумя тенантами books на books VDS. **Каждый стек ходит
в Ozon только своими кредами**: Slovo — client_id 94191, Bookva — client_id 50542.
Мисматч-пары (client_id ↔ чужой ключ) в обеих БД — **намеренные заглушки**, НЕ баги
и НЕ артефакты копирования. Зафиксировано владельцем 2026-08-23 после того, как
.admin «починил» заглушку и вызвал инцидент.
## Почему заглушки
Tenant-split (volume-copy) оставил ОБОИХ sellers в обеих БД. Если бы в каждой БД
лежали рабочие ключи обоих кабинетов — любой стек мог бы ходить в Ozon под чужим
кабинетом (утечка/перекос данных). Заглушка делает чужой кабинет **недостижимым**:
Ozon отвечает `Invalid Api-Key (code 5)` на мисматч-пару.
## Матрица (актуально 2026-08-23)
| БД | id_seller | client_id | api_key | статус |
|---|---|---|---|---|
| slovo (books-db) | 1 | 50542 | `968302d1-…` (slovo-ключ) | **заглушка** (мисматч) |
| slovo (books-db) | 2 | 94191 | `968302d1-…` (slovo-ключ) | рабочий |
| bookva (bookva-db) | 1 | 50542 | `ddc24146-…` (bookva-ключ) | рабочий |
| bookva (bookva-db) | 2 | 94191 | `ddc24146-…` (bookva-ключ) | **заглушка** (мисматч) |
Хэши SHA2 (проверка целостности): slovo-ключ = `d6227b…`, bookva-ключ = `f700f3…`.
Оба ключа — штатные креды, живут в git (`.slovo.cmd`/`.bookva.cmd` обёртки
`packages/tools`), не выдуманы.
Проверка Ozon API (2026-08-23): 94191 + slovo-ключ → 200 OK; 50542 + slovo-ключ →
Invalid; 50542 + bookva-ключ → 200 OK.
## Как это безопасно
- Код-фикс `2a38fb5` (break в outer catch `ozonFbsPostingsSyncronization.js`) —
задеплоен на оба task-runner 2026-08-23. all-sellers диспатч (data:null),
упёршийся в заглушку → `Invalid Api-Key` → **break → фейл-фаст, БЕЗ спина**
(до фикса — бесконечный while(true) ~75 req/сек, инцидент 2026-08-23).
- Расписания с корректным `data.idSeller` (slovo=2, bookva=1) заглушек вообще не
касаются — ходят только своим продавцом.
## Verify (если кто-то засомневается)
```sql
-- slovo: seller 1 должен быть 50542 + 968302d1 (заглушка)
SELECT id_seller, client_id, LEFT(api_key,8) FROM sellers ORDER BY id_seller;
-- bookva: seller 2 должен быть 94191 + ddc24146 (заглушка)
```
## НЕ делать (guards)
- **НЕ «чинить» мисматч-пары** — это контракт. «Починил» → slovo-стек начинает
ходить в Ozon как Bookva → утечка.
- Не трогать bookva-БД seller 1 (рабочий) и slovo-БД seller 2 (рабочий).
- Изменение ключей — только через владельца кабинета (Ozon) + письмо books.
## Инцидент-ссылка
2026-08-23: спин `ozonFbsPostingsSyncronization` (невалидный ключ + no-break код) →
полный разбор в переписке `books/.agents/inbox/` (отчёты .admin 10:22→12:50Z) и
тасках books #974/#975/#977/#990/#991. Фоллоу-ап: документация runtime-контракта
sched-тасок (file-managed перевод slovo сделан 12:50Z, коммит 887bbae).

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3331]] (concepts/sched-pipelines-local-stack-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3332]] (concepts/sched-publish-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,78 @@
---
title: sched-runtime-to-file-managed-migration
type: concept
status: live
tags: [books, sched, file-managed, durable-config, incident, live-sync]
related: "[[sched-ozon-creds-stub-contract]], [[snolla-smtp-mail-delivery-2026-08]], [[portainer-stack-management-books-vds]]"
updated: 2026-08-23
---
# Sched runtime → file-managed миграция (durable config)
## TL;DR
После инцидента 2026-08-23 (спин `ozonFbsPostingsSyncronization`, ~75 req/сек) оба тенанта books
(slovo + bookva) переведены на **file-managed** расписания sched: декларатив в
`deploy/sched-tasks.json` (git-tracked), демон подхватывает файл live-sync'ом. Runtime-путь
(таски в сторе, `fileManaged:false`) хрупкий — расписания живут в памяти демона и слетают
при pause/перезапуске, таски остаются сиротами без `data`.
**Ключевой факт миграции: live-sync НЕ конвертирует runtime-таски** (sync создаёт только
новые из файла, существующие runtime НЕ перезаписывает). Перевод = явный `DELETE` runtime-
тасок и расписаний через admin API, после чего sync доставляет file-managed версии.
## Почему runtime хрупкий (контекст инцидента)
- Расписания runtime-тасок держатся **в памяти демона** → при `pause`/`restart` слетают.
- Таски остаются сиротами: `fileManaged:false`, `schedule:null`, `data:undefined`.
- `deploy/sched-tasks.json` был **gitignored** (прод-файл на хосте) — конфиг не версионировался,
восстановление = ручные миграции из `agendaJobs`.
- Runtime-таска могла нести **чужой URL runner'а** (cross-tenant leak: `books-task-runner` в
bookva-конфиге) — файл чинит (bookva → `bookva-task-runner`).
## Миграционный рецепт (по тенанту)
1. **Файл** — из git-репо в прод-расположение:
- slovo: `/opt/books/sched/tasks.json` (bind-директория `/opt/books/sched:/app/config:ro`).
- bookva: volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json` (→ `/app/config`).
- Заливка **in-place**: `cp /tmp/new /path/tasks.json` (не mv — сохранить inode/perms).
Бэкап: `cp tasks.json tasks.json.bak-<tag>`.
- Перед заливкой: diff против live — допускаются только целевые изменения (books собирает
файл байт-идентичным live + новые таски).
2. **Live-sync** — демон подхватывает файл ≤60с, рестарт НЕ нужен. Верифицировать через
`GET /api/tasks` (новые таски появляются как `fileManaged:true`).
3. **DELETE runtime** (sync их не убирает — только API, runs-история сохраняется):
- `DELETE /api/tasks/:name` ×N — имена URL-encode (`%20`).
- `DELETE /api/schedules/:id` ×N — **не каскадятся**, удалять отдельно. Все → 204.
4. **Verify**:
- `fileManaged:true` у всех целевых, `disabled:false`, `paused:false`.
- Дублей 0: `[].taskName | group_by(.) | map(select(length>1))` пуст.
- `data` на расписании (не на таске): slovo `{idSeller:2}` / bookva `{idSeller:1}`,
отчёт — `{recipients:[...], idSeller}`.
- Ручной раунд: `POST /api/tasks/:name/run` → `succeeded {sellersProcessed:N}`.
## Gotchas
| Гоча | Деталь |
|---|---|
| id-паттерн | file-managed schedule `id` = taskName; runtime = UUID |
| ghost-таски | `fileManaged:true` сироты (выпилены из файла) висят `disabled:true` — удалять `DELETE /api/tasks/:name` |
| task-runner config | `/opt/books/task-runner/config/default.json` кэшируется при первом send (lazy init `sendReportsByEmail.js`) → после правки **рестарт обязателен** |
| SMTP | `from` == auth-user обязателен (Yandex: 550 not owned / 525 disabled). Креды `e-16513832@yandex.ru` (app-pass, `pass show snolla-smtp/full-env`). См. [[snolla-smtp-mail-delivery-2026-08]] |
| имена тасок | «ozon fbs postings syncronization» — пробелы, в DELETE URL-encode |
| шаблон vs прод | конвертер из шаблона может давать лишние trigger-only таски — в деплой-файл не включать без причины |
## Текущее состояние (2026-08-23, #991 closed)
- slovo: 25 тасок, file-managed, отчёт `{idSeller:2, recipients:[3]}` (recipients восстановлены,
commit `854e381`).
- bookva: 23 таски, file-managed, `data {idSeller:1}` (commit `6d4f163` bookva-overlay).
- Ghost `system-cleanup-task-runs` удалён на обоих тенантах.
- Доступ: sched admin API — books-sched `172.20.0.20:3031`, bookva-sched `172.20.0.12:3031`,
Bearer `SCHED_ADMIN_KEY` (env контейнера), через SSH books-vds. Роуты — `packages/admin-api/src/admin-api.ts` (репо sched).
## Related
- [[sched-ozon-creds-stub-contract]] — почему мисматч-пары кредов намеренные (тот же инцидент).
- [[snolla-smtp-mail-delivery-2026-08]] — SMTP-уроки (525/554/550), перенос на e-16513832@yandex.ru.
- [[portainer-stack-management-books-vds]] — управление стеками books VDS.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3333]] (concepts/sched-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,6 @@
# sched-workers-vds — СТУБ
> ⚙️ **Не читать, не править.** Канон — mappa wiki-сущность `concepts/sched-workers-vds-mock-runbook` (wiki:3339, проект .admin).
> Файловый канал ранбуков закрыт (2026-08-29, task:1507). Git-история хранит прежний контент.
См. `wiki_get(project=".admin", slug="concepts/sched-workers-vds-mock-runbook")`.

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3315]] (concepts/skills-catalog-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -0,0 +1,27 @@
---
title: SSH-хардненинг books-vds + аудит vds (2026-08-25)
status: live
tags: [security, ssh, fail2ban, centos7, books-vds, vds-kzntsv, decision]
---
# SSH-хардненинг books-vds + аудит vds (2026-08-25)
Решение vitya: «ты за безопасность отвечаешь — и на vds, и на books-vds». Триггер — аудит task:1165.
## Инцидент-триггер
Рестарт `books-job-scheduler-mongo` 24.08 22:02Z (инцидент sched, storage-mongo 0.5.1 ветка) — внешнее действие вне агентов (.admin/books/sched сессий нет). Аудит доступа показал: успешных чужих SSH-входов НЕТ (все Accepted = наш ключ), но **постоянный брутфорс**: 8773 Failed за 25.08, 5810 с `210.61.148.174` (HiNet, Тайвань), `PasswordAuthentication=yes`, fail2ban не установлен. Рестарт mongo не через SSH (Accepted в окне пусто) и не через sched-таску — вероятнее Portainer UI (запросы не логируются).
## Что сделано — books-vds (89.253.255.133, CentOS 7!)
1. **`PasswordAuthentication no`** в /etc/ssh/sshd_config (бэкап `sshd_config.bak-*`, `sshd -t` перед reload). Доступ — только по ключу `id_ed25519_books_ops` (authorized_keys = 1 запись).
2. **fail2ban** (sshd jail: bantime 1h, findtime 10m, maxretry 5, ignoreip 94.19.247.14). **Уже забанил 210.61.148.174**.
3. **CentOS 7 EOL-фикс реп**: mirrorlist.centos.org мёртв → vault.centos.org/7.9.2009 (base/extras/updates/centosplus), EPEL 7 → archives.fedoraproject.org (epel-release-7-14), `ip_resolve=4` в /etc/yum.conf (битый IPv6 провайдера ломал yum — dl.fedoraproject.org отдавал «Cannot open»).
## vds.kzntsv.site (89.253.255.94)
Парольный SSH **уже отключён** (сервер принимает только publickey — проверено). НО наш SSH-ключ на vds не установлен (ни один из ~/.ssh/* не подошёл) — доступ только через Portainer API (portainer.vds.kzntsv.site). fail2ban на vds не проверен (нет SSH). Хвост: решить, нужен ли SSH-ключ к vds.
## Portainer books (portainer.kzntsv.site)
Admin-пароль в pass НЕ хранится (только BOOKS_PORTAINER_API_KEY). Рекомендация: сменить admin-пароль + записать в pass.

View File

@@ -22,7 +22,7 @@ updated: 2026-07-22
| Параметр | Значение | | Параметр | Значение |
|---|---| |---|---|
| accessKeyId | `stayer_minio` (bucket-user) | | accessKeyId | `stayer_minio` (bucket-user) |
| secretAccessKey | `cXY>AVoWBv#_V-SZn6Vu6Tzx&!M[%UOod` | | secretAccessKey | `secret:stostayer/minio` |
| endpoint | `https://minio-api.stostayer.ru` | | endpoint | `https://minio-api.stostayer.ru` |
| region | `us-west-1` (см. ниже — НЕ `local`) | | region | `us-west-1` (см. ниже — НЕ `local`) |
| pathStyle / ssl | `true` / `true` | | pathStyle / ssl | `true` / `true` |
@@ -31,7 +31,7 @@ imgproxy: `https://imgproxy.stostayer.ru`, basePath `/imgproxy2`, key+salt —
## Три бага в исходном Web.config (почему "смотрит на локальное") ## Три бага в исходном Web.config (почему "смотрит на локальное")
1. **Чужие креды.** `accessKey=AKIAJ2YJP72W6ZHCRE6Q` + `secretKey=7o0Q4NjE5GL…` — это **books-vds/snolla MinIO root-ключ** (см. [[minio-imgproxy-on-vds]]). На stostayer MinIO не зарегистрирован → `The Access Key Id you provided does not exist in our records`. Видимо, конфиг копипастом из snolla catch-all admin ([[snolla-local-admin-and-on-snolla-migration-design]], тот юзает books-vds `minio.kzntsv.site`), без замены ключей на stostayer-свои. 1. **Чужие креды.** `accessKey=secret:pass/minio-vds/full-env` + `secretKey=<root-secret>5GL…` — это **books-vds/snolla MinIO root-ключ** (см. [[minio-imgproxy-on-vds]]). На stostayer MinIO не зарегистрирован → `The Access Key Id you provided does not exist in our records`. Видимо, конфиг копипастом из snolla catch-all admin ([[snolla-local-admin-and-on-snolla-migration-design]], тот юзает books-vds `minio.kzntsv.site`), без замены ключей на stostayer-свои.
2. **Не API-порт.** `serviceURL=https://minio.stostayer.ru` → MinIO отвечает `S3 API Requests must be made to API port`. Прод юзает `https://minio-api.stostayer.ru`. Оба хоста резолвятся в один IP `2a03:6f01:1:2::c66b`, но `minio.stostayer.ru` — не S3 API-эндпоинт. 2. **Не API-порт.** `serviceURL=https://minio.stostayer.ru` → MinIO отвечает `S3 API Requests must be made to API port`. Прод юзает `https://minio-api.stostayer.ru`. Оба хоста резолвятся в один IP `2a03:6f01:1:2::c66b`, но `minio.stostayer.ru` — не S3 API-эндпоинт.
3. **Регион.** `authenticationRegion="local"` → SigV4-подпись регионом не совпала → `AmazonS3Exception: The authorization header is malformed; the region is wrong; expecting 'us-west-1'` (HTTP 400) на `GalleriesStorage.EnsureKeyPrefixExists` → `S3DirectoryInfo.ExistsWithBucketCheck` (провайдер галерей падал на ините, в трассе `ToolsController.ImagePreview` → `CreatePreview`). stostayer MinIO настроен на регион `us-west-1`. Прод `default.json` пишет `region:"local"`, но то Node aws-sdk (galleries через imgproxy мимо app-S3; для .NET AWSSDK регион обязан быть `us-west-1`). 3. **Регион.** `authenticationRegion="local"` → SigV4-подпись регионом не совпала → `AmazonS3Exception: The authorization header is malformed; the region is wrong; expecting 'us-west-1'` (HTTP 400) на `GalleriesStorage.EnsureKeyPrefixExists` → `S3DirectoryInfo.ExistsWithBucketCheck` (провайдер галерей падал на ините, в трассе `ToolsController.ImagePreview` → `CreatePreview`). stostayer MinIO настроен на регион `us-west-1`. Прод `default.json` пишет `region:"local"`, но то Node aws-sdk (galleries через imgproxy мимо app-S3; для .NET AWSSDK регион обязан быть `us-west-1`).
@@ -40,8 +40,8 @@ imgproxy: `https://imgproxy.stostayer.ru`, basePath `/imgproxy2`, key+salt —
9 S3-блоков в `fileStorageClients` (galleries/assets/images/scripts/stylesheets/imageCache/watermarks + inert-настройки contentCache/uploadCache) — 3 глобальные замены в `Web.config`: 9 S3-блоков в `fileStorageClients` (galleries/assets/images/scripts/stylesheets/imageCache/watermarks + inert-настройки contentCache/uploadCache) — 3 глобальные замены в `Web.config`:
``` ```
accessKey: AKIAJ2YJP72W6ZHCRE6Q → stayer_minio accessKey: secret:pass/minio-vds/full-env → stayer_minio
secretKey: 7o0Q4NjE5GLkdC48r0oZFEnqddjPNLqtCk+ZEh+S → cXY&gt;AVoWBv#_V-SZn6Vu6Tzx&amp;!M[%UOod (XML-escape: > → &gt;, & → &amp;) secretKey: secret:pass/minio-vds/full-env → cXY&gt;AVoWBv#_V-SZn6Vu6Tzx&amp;!M[%UOod (XML-escape: > → &gt;, & → &amp;)
serviceURL: https://minio.stostayer.ru → https://minio-api.stostayer.ru serviceURL: https://minio.stostayer.ru → https://minio-api.stostayer.ru
authenticationRegion: local → us-west-1 authenticationRegion: local → us-west-1
``` ```
@@ -68,4 +68,4 @@ authenticationRegion: local → us-west-1
- [[galleries-storage-class-local-not-s3]] — snolla-тираж: galleries/assets Local→S3 миграция, snolla catch-all admin на books-vds MinIO. - [[galleries-storage-class-local-not-s3]] — snolla-тираж: galleries/assets Local→S3 миграция, snolla catch-all admin на books-vds MinIO.
- [[snolla-admin-appdata-acl-500-after-scp-migration]] — split-brain админка(Local)↔боевой app(MinIO) на RUVDS; та же проблема «админка не переключена на S3». - [[snolla-admin-appdata-acl-500-after-scp-migration]] — split-brain админка(Local)↔боевой app(MinIO) на RUVDS; та же проблема «админка не переключена на S3».
- [[minio-imgproxy-on-vds]] — books-vds MinIO (root-ключ `AKIAJ2YJP72W6ZHCRE6Q`), который ошибочно попал в stostayer Web.config. - [[minio-imgproxy-on-vds]] — books-vds MinIO (root-ключ `secret:pass/minio-vds/full-env`), который ошибочно попал в stostayer Web.config.

View File

@@ -1,134 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: stostayer-web — деплой легаси web на прод клиента (runbook)
status: live
tags: [stostayer, docker, portainer, deployment, nuxt2, esm, ops, rollback]
related: [[portainer-stack-management-vds]]
updated: 2026-06-17
---
# stostayer-web — деплой легаси web на прод клиента **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:207]] (concepts/stostayer-web-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Прод `https://www.stostayer.ru` = легаси `packages/web` (Nuxt 2, CJS, SSR) из монорепо `victor/stostayer.new`. web4 (Nuxt 4) ещё НЕ переключён. Хостится контейнером, управляется Portainer-стеком у клиента.
> **Машина клиента, настраивает их админ — мы только деплоим свой web-образ, конфиги хоста не трогаем.** Деталь доступов/инфры — в `stostayer.new/.wiki/concepts/client-infra-access.md`. Все креды — `pass stostayer/client`.
## Параметры
| Что | Значение |
|---|---|
| Хост | `91.222.236.225` (SSH `:20435`, юзер `victor` + sudo-с-паролем; парольный SSH) |
| Reverse-proxy | **Angie** (форк nginx) на `:443`, навешивает **Basic-auth** на сервисные сабдомены |
| Registry | `docker.stostayer.ru` (BA те же, что Angie BA) — образы `stostayer-web:<tag>` |
| Стек | Portainer **`stostayer-web` Id 16, EndpointId 3**, compose в Portainer-volume `/data/compose/16/docker-compose.yml` |
| Прод-тег (2026-08-13) | `0.3.23`. На хосте лежат старые теги под откат (`0.3.22` и ранее) |
| Образ | `packages/web/Dockerfile`: `node:16` → `yarn install` → `yarn build` (nuxt build) → `CMD yarn start`. pm2/`ecosystem.config.js` в проде НЕ используется |
## Канал деплоя (build здесь → registry → Portainer)
### 1. Build образа (локально, offline)
Тег образа независим от `packages/web/package.json` version — это чисто image-тег, инкремент от прода (`0.3.18` → `0.3.19`). Кода/версий не бампать.
```bash
# в чекауте нужного коммита, .yarn/cache populated
docker build -f packages/web/Dockerfile -t docker.stostayer.ru/stostayer-web:<tag> . # контекст = корень репо
```
Гочи build:
- **`.dockerignore` обязателен** (его нет в репо) — иначе `node_modules`/`.git` улетают в build-контекст. Создать временный: `node_modules **/node_modules .git apps docs build *.zip` и т.п. (на явные `COPY` не влияет).
- **Локальная `npmAuthToken: "${VERDACCIO_TOKEN}"` в `.yarnrc.yml`** (рабочая модификация, не в коммите) **ломает build**: yarn внутри образа падает `Usage Error: Environment variable not found (VERDACCIO_TOKEN)`. Убрать эту строку из build-копии (потом вернуть).
- **Offline против `.yarn/cache`:** добавить `enableNetwork: false` в `.yarnrc.yml` — depы пекутся из cache, verdaccio-токен в образе не нужен. (Cache содержит third-party зипы; их версии ESM-миграция не меняла, так что cache от master-tip обычно покрывает и более старые коммиты.)
- **✅ Offline-кэш ПОЧИНЕН (2026-08-13, коммит `5c583f8`):** в `.yarnrc.yml` добавлен `supportedArchitectures: {cpu:[x64], os:[linux,win32,darwin], libc:[glibc,musl]}` — раньше без `libc` yarn пропускал fetch `@rollup/rollup-linux-x64-gnu` (условие libc=glibc). `.yarn/cache` gitignored → один раз на build-машине прогнать сетевой `yarn install` (с `VERDACCIO_TOKEN`) — yarn дольёт `@esbuild/linux-x64`/`@rollup/rollup-linux-x64-gnu` в локальный кэш, дальше offline (`enableNetwork:false`) работает. Сделано 2026-08-13 на этой машине (зипы в кэше).
### 2. Push в registry
```bash
echo '<ba_pass>' | docker login docker.stostayer.ru -u Victor --password-stdin
docker push docker.stostayer.ru/stostayer-web:<tag>
```
> ⚠️ **ОДИН push, без retry-циклов.** Инцидент 2026-06-17: retry-шторм (6 попыток, образ 3.29GB) на `docker.stostayer.ru` засветил egress нашего **VPN** → хостинг-провайдер забанил VPN-IP → ВЕСЬ HTTPS дом→хост (`:443`: сайт + registry + portainer) стал TLS-fail (`schannel: failed to receive handshake`), выглядело как «сайт лёг» (а TCP 443/SSH 20435 — открыты). Лечится сменой VPN. **На client-инфру: одна попытка, сбой → стоп и к человеку, не долбить.** (см. [[verify-on-real-client-not-own-curl-tests]])
>
> ⚠️ **Инцидент 2026-07-21: единичный large-push ТОЖЕ триггерит бан** (не только retry-storm). Первый push `0.3.20` (3.33GB) с VPN → тот же VPN-IP бан → `:443` к хосту TLS-fail с операторского IP (сайт для остальных посетителей работал — Angie access-log это подтвердил; SSH:20435 оставался открыт). **Бан НЕ IP-специфичный для всех — для остальных сайт жив, режется только egress-IP источника push'а.** Митигация: (a) **resume-push после смены egress** — `docker push` resumable, уже залитые слои «Layer already exists» (без re-upload), добивается только остаток + manifest → egress мал → повторный бан не триггерится; (b) **build-on-host + push в localhost-registry** (registry-контейнер на хосте, `127.0.0.1` — вообще без внешнего egress) — самый чистый путь для крупных образов. **Не пытаться долбить упавший push повторно с того же egress.**
### 3. Передеплой стека — с ХОСТА, мимо Angie BA
Portainer published **только в docker-сеть** (на host-localhost его НЕТ — там MinIO на `:9000`). Прямой Portainer-API из дома ломает Angie BA (Bearer затирает Basic → 401; auth-cookie не ставится). Решение — бить API по **IP контейнера portainer с самого хоста**:
```bash
# SSH (PuTTY plink; host-key уже известен)
plink -ssh -batch -hostkey SHA256:6Zk14J/UakVqBYm/fMPFrL3gosdBdyZOKZnbA0GVnD0 \
-P 20435 -pw '<ssh_pass>' victor@91.222.236.225 "bash -s" <<'EOF'
base=http://172.18.0.2:9000 # docker inspect portainer → IP в bridge; ПОРТ :9000 обязателен (голый IP → curl на :80 → HTTP=000)
JWT=$(curl -s $base/api/auth -H 'Content-Type: application/json' \
-d '{"username":"victor","password":"<app_pass>"}' | jq -r .jwt)
FILE=$(curl -s -H "Authorization: Bearer $JWT" $base/api/stacks/16/file | jq -r .StackFileContent)
NEW=$(printf '%s' "$FILE" | sed 's#stostayer-web:0.3.18#stostayer-web:<NEWTAG>#')
PAYLOAD=$(jq -n --arg c "$NEW" '{stackFileContent:$c, env:[], prune:false, pullImage:true}')
curl -s -w "\nHTTP=%{http_code}\n" -X PUT -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" -d "$PAYLOAD" "$base/api/stacks/16?endpointId=3"
EOF
```
- `docker` на хосте — через `sudo` (victor не в группе docker): `echo '<ssh_pass>' | sudo -S <cmd>`. Sudo-пароль = SSH-пароль victor.
- `jq` на хосте есть. Скрипт через `bash -s`/stdin — чисто с кавычками; UTF-8 round-trip compose не портит (в отличие от PowerShell Invoke-RestMethod, [[portainer-stack-management-vds]] gotcha #9).
- `pullImage:true` → Portainer сам тянет новый тег из `docker.stostayer.ru` (хост-docker уже авторизован в registry).
### 4. Verify (read-only, без нагрузки на публичный сайт)
```bash
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}' | grep stostayer-web # образ = новый тег, Up
docker logs --tail 20 stostayer-web # чистый старт Nuxt, без ошибок/краш-лупа
```
**Verify контента (напр. цен) — с хоста, прямым curl в nuxt-контейнер** (Angie даёт 403 на no-UA запрос с самого хоста; обход — `localhost:3000`, host-network):
```bash
# на хосте (sudo docker + host-network контейнер на :3000)
curl -s -H "Host: www.stostayer.ru" -A "Mozilla/5.0" http://localhost:3000/<page> -o /tmp/v.html
grep -c "<expected-text>" /tmp/v.html
```
**Гоча verify-URL:** bare `/remont-kondicionerov` 301-редиректит на `/remont/remont-kondicionerov` (DB-driven `oldPageRedirect`/unit-slug mapping) — это CMS-контент-страница БЕЗ nuxt-компонента. Компоненты `pages/<dir>/*.vue` реально live на своих nuxt-маршрутах (напр. `/remont-kondicionerov/{zapravka,diagnostika}-kondicionera`) — верифицируй по ним, не по bare-URL.
**Прод-дрейф mount-конфига (2026-08-16, инцидент content-api):** хост монтирует
`/var/docker/stostayer-web/config` → `/usr/src/app/packages/web/config` — это **источник истины
для прод-конфига**. node-config читает из cwd (`packages/web/config`), т.е. репо-правки
`packages/*/config/default.json` (напр. c43d168: `s3.region local → us-west-1`) на прод **не
влияют** — образ подхватывает mount. После деплоя, меняющего конфиг: синхронизировать
хост-конфиг (или осознанно задокументировать mount как truth). Нюанс node18: `localhost` →
`::1` (IPv6-first) — в образе `NODE_OPTIONS=--dns-result-order=ipv4first` (MariaDB/MSSQL на
хосте IPv4-only).
**Гоча локального smoke-контейнера:** `packages/web/config/default.json` содержит **прод-БД** (`www.stostayer.ru:3306` MariaDB + `:1433` MSSQL), и `www.stostayer.ru` резолвится в публичный IP хоста → smoke-контейнер без env-overrides **подключается к prod-БД клиента по интернету** (read-only SSR, без writes, но всё равно нежелательно). Для чистого локального smoke — переопределяй `data.sequelize.*.host` env'ом на localhost/заглушку, либо глуши контейнер сразу после `Server Listening`.
### Откат
Тот же PUT с прежним тегом (`<NEWTAG>` → `0.3.18`), `pullImage:true`. 0.3.18 на хосте есть. RTO ~30-60с (пересоздание контейнера).
## ✅ БЛОКЕР РАЗРЕШЁН 2026-07-21 (retrofit + snolla 0.7.6)
> **Legacy web снова собирается+запускается с master.** Решение: ретрофит `packages/web` (`require()`→dynamic `import()` в 5 местах, 2 serverMiddleware → CJS) + фикс `@snollajs/snolla@0.7.6` (bare-`get`-баг, см. ниже). Образ `0.3.20` собран, запушен, передеплоен на прод stostayer.ru, verify зелёный. Ретрофит лежит в working-tree stostayer.new (незакоммичен на 2026-07-21) — **stostayer.new должен закоммитить** его в master, иначе следующий legacy-web деплой упрётся в ту же стену. Секция ниже оставлена как история.
## ⛔ БЛОКЕР (история, 2026-06-17): легаси web НЕ пересобирается с текущего master (ESM-стена)
> На 2026-06-17 фикс формы жалоб (`120bc07`, задача `stostayer-web-complaint-form-deploy`) **выкатить не удалось** — два ESM-барьера:
1. **Build-time:** с master-tip `yarn build` (nuxt) падает `SyntaxError: await is only valid in async functions...` — `@stostayer/data` после таски `esm-data-dual` грузит модели через **top-level await**; цепочка `nuxt.config.js → @stostayer/api → @stostayer/data`, Nuxt2 читает конфиг через jiti → top-level await недопустим. Корень — коммит `c805e7e` (ESM-миграция data/api).
2. **Runtime:** cherry-pick `120bc07` на `d02f740` (родитель c805e7e, «последний CJS-собираемый master») **собирается**, но контейнер **краш-лупит**: `ERR_REQUIRE_ESM` — `packages/web/server/index.js:6` `require('@snollajs/snolla')`, а версия `@snollajs/snolla` из lockfile d02f740 уже **ESM**. Т.е. d02f740 не runtime-чистая.
Прод `0.3.18` жив только потому, что собран на ещё более старом стейте (CJS `@snollajs/snolla`, CJS data/api).
**Вердикт (stostayer.new подтвердил 2026-06-17):** легаси-web `require()`-ит **5** ESM-ставших пакетов — `@snollajs/snolla` (0.7.4, `server/index.js:6`), `@stostayer/api` (`nuxt.config.js:3`), `@snollajs/content-api` (0.8.0, `nuxt.config.js:4`), `@stostayer/data` (`serverMiddleware/oldPagesRedirections.js:14` + `redirections.js:14`). Чтобы найти base где ВСЕ пять ещё CJS — надо к ~0.3.18 и потерять всё с тех пор. **Чейз базы бесполезен. `packages/web` (node16/CJS/Nuxt2) не пересобираем ни с какого свежего дерева — весь dep-граф pure-ESM. `0.3.18` заморожен (последний собираемый артефакт).**
**Путь:** фикс формы (`120bc07`) едет вместе с **переездом формы в web4** (Opt 3). Ретрофит `require`→dynamic `import()` в 5 местах технически возможен на node16, но это часы на стек, который удаляется web4 — не рекомендуется. До web4-cutover — **остаёмся на 0.3.18**, admin-сессия код не правит ([[verify-on-real-client-not-own-curl-tests]]).
> Образ `0.3.19` (краш-лупный) лежит в `docker.stostayer.ru` — **НЕ деплоить**. Оставлен в registry по решению vitya (2026-06-17), не удаляем.
## ✅ Деплой 0.3.23 (2026-08-13)
Запрос их прогера (письмо `.agents/inbox/2026-08-13T08-43-41Z-stostayer-new.md`): calculator-баннер про цены при покупке запчастей в компании Стайер (exactPrices>0) + фикс краша (snolla `loadCity`/`loadSite` в asyncData) + стек-фикс легаси (явные vue/vuex/vue-router/mariadb deps, `nmHoistingLimits: workspaces`). Коммиты `e5f7bd6`+`7e51cbf`+`d49b4bb` (master-tip). Собран (см. гоча offline выше), запушен **1 попыткой** (слои mostly already-exists → egress мал, бана нет), стек 16 передеплоен 0.3.22→0.3.23 (`pullImage:true`, env re-supply — у стека env пуст). Verify зелёный: Focus II → 200 + баннер, регресс `/`+ремонтная 200, Focus IV (exactPrices=0) без баннера + алерт «уточняйте у мастеров». Прод до этого был на **0.3.22** (0.3.21/0.3.22 собраны 24.07; деплой 0.3.22 шёл через pscp+`sudo docker load` без push в :443).
## Связи
- [[portainer-stack-management-vds]] — родственный паттерн (наш VDS), оттуда PowerShell-кодировочные гочи и redeploy-рецепт.
- Доступы/инфра клиента (детально): `stostayer.new/.wiki/concepts/client-infra-access.md`.
- Задача: `.tasks/stostayer-web-complaint-form-deploy.md` (🔵 blocked на victor/stostayer.new).

View File

@@ -1,161 +1,3 @@
--- # ⛔ Файловый канал закрыт
title: tandemmebel.ru snolla-app — VDS deploy runbook (cutover + 0.42.1 bumps + 0.43.2 v3 cutover)
type: concept
tags: [tandemmebel, snolla, vds, deploy, docker, portainer, traefik, runbook, migration, blog-portfolio, v3, aws-sdk]
related: [[../entities/vds-kzntsv]], [[../entities/ruvds-iis-host]], [[portainer-stack-management-vds]], [[minio-imgproxy-on-vds]], [[snolla-live-prod-inplace-image-bump]], [[labtools-vds-deploy-runbook]], [[labtools.pro-vds-deploy-runbook]], [[emspb-vds-deploy-runbook]], [[minio-split-cutover-runbook]]
updated: 2026-07-30
---
# tandemmebel.ru → VDS deploy runbook **Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:208]] (concepts/tandemmebel-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.
Вынос `tandemmebel.ru` (snolla-приложение, `@snollajs/snolla` **0.42.1**, server-side Liquid, **блог-портфолио БЕЗ e-commerce каталога**)
с [[../entities/ruvds-iis-host]] (catch-all CMS, 80.64.31.36) в отдельный docker-контейнер на [[../entities/vds-kzntsv]]
(89.253.255.94), за traefik. Модель — snolla-app: читает БД-контент MoreThenCms (`mssql.kzntsv.site`)
+ ассеты из MinIO (`minio.kzntsv.site`). In-process sharp (resize+data-driven watermark), delivery=serve-bytes,
variant-cache бакет в MinIO. imgproxy ИЗ ПУТИ tandem УБРАН (сырой `/imgproxy` → 404 норма).
Паттерн = [[emspb-vds-deploy-runbook]] / [[labtools.pro-vds-deploy-runbook]]. Отличие: **блог-портфолио** (не каталожный →
order-парити секций НЕ применимо — см. 0.42.1 bump ниже). Culture `ru-RU`.
## Артефакты
- **Код:** `victor/tandemmebel.ru` @ `0cd9351` (apps/web, snolla **0.42.1** / core 0.24.1 / liquid 0.10.2 / data 0.14.1).
`deploy/Dockerfile` (multi-stage node:22-slim, `yarn install --immutable`, non-root uid `node`, healthcheck `/robots.txt`).
- **Образ:** `registry.kzntsv.site/tandemmebel:0cd9351` (+`:latest`). Имя **`tandemmebel`** (= имя стека/контейнера).
Собран НА VDS (обход traefik-499). digest `sha256:f29c187fe6114d6ab3926132f4b3eb09df9b606d448cd8fd873780687cb46b8f`.
- **Стек Portainer:** `tandemmebel` (**Id 20**, endpoint 1). Source-of-truth compose: `admin/host-stacks/vds-kzntsv/tandemmebel.compose.yml`.
- **siteId:** `78080707-F6E0-4330-BA30-7922354C2CEF` (non-secret, в `production.json` — НЕ env).
- **siteUrl:** `https://www.tandemmebel.ru` (в `production.json`).
- **Runtime env (8 секретов):** `DB_USER DB_PASSWORD IMGPROXY_KEY IMGPROXY_SALT S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY SMTP_USER SMTP_PASSWORD` — Portainer stack env (переиспользованы verbatim из labtools стека 17). НЕ в образе, НЕ в git.
- **mem_limit:** `512m` (guardrail; см. [[portainer-stack-management-vds]] § Convention).
## Cutover 2026-07-12 (первый вынос на VDS — DNS-gated)
Последовательность (LE-критичность соблюдена) — см. `NEXT_SESSION.md`:
1. **Verify авторит. NS** — `nslookup tandemmebel.ru ns1/ns2.reg.ru` = оба `89.253.255.94` (apex + www), не только резолвер.
2. **GET стек 20** — образ `tandemmebel:ed96b18` (0.42.0, staging cutover-подготовлен), env 8/8, rule staging `Host(tandemmebel.vds.kzntsv.site)`.
3. **PUT стек 20** — `Host(tandemmebel.ru) || Host(www.tandemmebel.ru)`, env-preserving, `prune:false, pullImage:false`. Python urllib UTF-8 (НЕ PS Invoke-RestMethod — gotcha кириллицы, см. [[portainer-stack-management-vds]]).
4. **Poll** — контейнер healthy, LE-серт issued on first hit: CN=tandemmebel.ru, SAN оба, issuer YR2, until 2026-10-10.
5. **Live-smoke С VDS** — GREEN.
6. Compose source-of-truth + борд обновлены.
**Латентный прод-баг починен cutover'ом:** `Gotham-Pro.css` был 0B на RUVDS → теперь 200/4436B.
## In-place bump 2026-07-12: 0.42.0 → 0.42.1 (order-tag Drop-field fix)
По рецепту [[snolla-live-prod-inplace-image-bump]] (поверх cutover'а того же дня).
### Консюмер-бамп (решён оператором, без dev-source)
Тот же блокер-паттерн 2026-07-04 (заявленная snolla-версия ≠ консюмер-пин) — на этот раз решён самим оператором:
- `apps/web/package.json:12` — `0.42.0` → `0.42.1`.
- `yarn install` в корне монорепы → обновил корневой `yarn.lock` (snolla 0.42.1 / core 0.24.1 / liquid 0.10.2 / data 0.14.1). Dockerfile `yarn install --immutable` — lock обязан совпадать с пином.
- commit `0cd9351` + push origin (git.kzntsv.site/victor/tandemmebel), подтверждён `ls-remote`.
- Тег образа = sha монорепы `0cd9351`.
### Build → staging → gate → swap
1. **Build на VDS** (archive `0cd9351` → `~/build/tandemmebel` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN -t .../tandemmebel:0cd9351 -t .../tandemmebel:latest . && push`). Guard: `config/default.json` ABSENT в архиве (.dockerignore), пин = 0.42.1.
2. **Throwaway-staging :5020** из env живого контейнера (`docker inspect tandemmebel` → `.staging.env`, фильтр `^(HOSTNAME|PATH|NODE_VERSION|YARN_VERSION|HOME)=`). `docker run -d --name tandemmebel-staging --env-file .staging.env -p 127.0.0.1:5020:5000 .../tandemmebel:0cd9351` → healthy. robots.txt+`/`+sitemap 200.
3. **Completeness-gate С VDS** — sitemapindex разворот (`expand()`), два чека:
- **NEW==PROD locs: 184 = 184 IDENTICAL** (вкл. 0.42.x sitemap-реструктуризацию — parity сохранился).
- **self-consistency + content-not-lost** (locs идентичны → один проход): 183×2xx + 1×404.
- Единственный 404 `/articles` — **идентичен прод-оракулу** (404 и на live `https://www.tandemmebel.ru/articles`) → benign (пустая секция-без-индекса, не регрессия). Tandemmebel НЕ каталожный → order-парити **не применимо** (0.42.1 order-fix инертен на этой теме — как archive-роуты на 0.40→0.42).
- **GREEN = 0 регрессий в 404/5xx.**
4. **Боевой swap** — operator-gated, node `put-stack.js` (env-preserving: 8/8 name+value, `prune:false, pullImage:true`):
`META=st20.json node put-stack.js 20 1 ed96b18 0cd9351` → PUT 200. Контейнер 0cd9351+healthy за ~8s.
5. **Live-smoke** — `https://www.tandemmebel.ru/{robots.txt,/,/projects,/sitemap.xml}` = 200. TLS-серт CN=tandemmebel.ru **не дёрнут** (in-place swap серт не трогает). `docker rm -f tandemmebel-staging`.
### Rollback
- Тег `ed96b18` (0.42.0) в registry (+ `b02ca18`, 0.16.2-staging) → PUT стека 20 назад.
- ИЛИ revert DNS → 80.64.31.36 (RUVDS IIS жив, не тронут — rollback всех мигрированных).
## In-place bump 2026-07-13: 0.42.1 → 0.42.1 (sha 0cd9351 → 8df10ee, template-only)
Ops-handoff от tandemmebel-сессии (inbox-запрос): убрать FB/Twitter/Google+ share-кнопки
(экстремистистская символика РФ), оставить ВК+Одноклассники. Коммит `8df10ee` в `victor/tandemmebel.ru`
master, меняет только `apps/web/views/social_buttons.liquid` (1 file, 12 deletions), snolla pin 0.42.1
НЕ менялся → in-place bump на той же 0.42.1 (не консюмер-бамп).
### Предсборочная верификация (поймала расхождение)
Записка от tandemmebel-сессии утверждала: «сейчас живой на стеке 20 — `ed96b18`/0.42.0». Фактически
на проде крутился **`0cd9351`/0.42.1** (in-place bump 0.42.0→0.42.1 был 2026-07-12). Без проверки это
не повлияло бы (см. ниже), но вслепую строить нельзя. Проверки С VDS + gitea API (токен `gitea/admin-token`):
- `docker inspect tandemmebel` → `registry.kzntsv.site/tandemmebel:0cd9351`, healthy, started 2026-07-12.
- gitea `compare/0cd9351...8df10ee` → `total_commits:1`: фикс ровно один коммит поверх 0.42.1 (правильная база).
- `git show 8df10ee:apps/web/package.json` → `@snollajs/snolla: 0.42.1` (pin не менялся); yarn.lock snolla 0.42.1 /
core 0.24.1 / liquid 0.10.2 / data 0.14.1 — идентично live 0cd9351.
- `git show 8df10ee --stat` → только `apps/web/views/social_buttons.liquid` (modified).
- 8df10ee full sha `8df10eedcba9979ac91dce2025b2ff827d60e808`, master HEAD = 8df10ee.
Если бы 8df10ee был на базе 0.42.0 (172 locs) → completeness-gate поймал бы регрессию 184→172. Он на 0.42.1 →
parity 184=184.
### Build → staging → gate → swap
1. **Build на VDS** из чистого архива `8df10ee` (gitea API `/archive/<fullsha>.tar.gz`, token- auth `oauth2:`-
НЕ работает, `Authorization: token`-header на API endpoints работает; git clone по HTTPS фейлится даже
с oauth2:token — gitea admin-token = API-only, не git-transport). Extract `--strip-components=1` в
`~/build/tandemmebel-8df10ee`. `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN=<books-ci JWT>
-t registry.kzntsv.site/tandemmebel:8df10ee -t .../tandemmebel:latest .` → digest
`sha256:30a7e5ab82f2bf371042f1b5fa0cd7ac5ceda71379d1da59e148dee8166bd560`, layer-cache hit (package.json+
yarn.lock идентичны 0cd9351), push OK.
2. **Throwaway-staging :5020** из env живого контейнера (`docker inspect tandemmebel` → `.staging.env`,
фильтр `^(HOSTNAME|PATH|NODE_VERSION|YARN_VERSION|HOME|PORT|NODE_ENV)=`). `docker run -d --name
tandemmebel-staging --env-file .staging.env -p 127.0.0.1:5020:5000 .../tandemmebel:8df10ee` → healthy 8s.
3. **Completeness-gate С VDS** — sitemapindex разворот. **Важно:** staging sitemapindex содержит АБСОЛЮТНЫЕ
prod-URLs (siteUrl=prod в production.json) → sub-sitemaps надо фетчить со staging ПО ПУТЯМ
(`http://127.0.0.1:5020/sitemap-pages-1.xml`), а не следовать абсолютным URL (иначе сравнишь PROD==PROD).
- NEW==PROD locs: **184 = 184 IDENTICAL** (0 prod-only, 0 new-only).
- self-consistency: 183×200 + 1×404 (`/articles` benign parity staging==prod).
- share-block: staging vk+ok / fb-tw-gp=0 vs prod-before (0cd915 live ещё имел) vk+ok+fb+tw+gp —
фикс убирает ровно лишнее, остальное не трогает.
- GREEN = 0 регрессий.
4. **Боевой swap** — Portainer JWT auth (`POST /api/auth` vitya/Pryakhin9-VDS-2026; API-key `ptr_*` даёт 401,
workaround = JWT). GET `/api/stacks/20` (env 8/8) + `/api/stacks/20/file` (compose). Python urllib UTF-8
(НЕ PS Invoke-RestMethod — gotcha кириллицы #9). Замена `tandemmebel:0cd9351`→`8df10ee` в StackFileContent
(ровно 1 замена, traefik rule line не тронута). PUT `/api/stacks/20?endpointId=1` `{stackFileContent,
env: <8 preserved>, prune:false, pullImage:true}` → HTTP 200. Контейнер 8df10ee+healthy ~8s.
5. **Live-smoke С VDS** — robots/`/`/sitemap 200, /articles 404 parity, sitemap 184 locs, 4 share-block
страницы (project-post ×2 `/projects/2011/...`, `/furniture/bedrooms`, `/furniture/kitchens/classic`) все
200 → **vk+ok на месте, fb/tw/gp=0**. TLS-серт CN=tandemmebel.ru (LE YR2, until 2026-10-10) **не дёрнут**.
`docker rm -f tandemmebel-staging`.
### Rollback (8df10ee)
- **Образный (предпочт):** PUT стека 20 назад на `0cd9351` (0.42.1, {% order %} fix) — template-only фикс,
откатится чисто. Тег в registry.
- `ed96b18` (0.42.0) / `b02ca18` — в registry (ed96b18 стёрт с VDS при disk-cleanup 2026-07-13, `pullImage:true`
дотянет из registry).
- DNS: revert reg.ru → 80.64.31.36 (RUVDS IIS жив, не тронут).
### Hygiene-заметка
Portainer stack 20 file несёт устаревшие STAGING-комменты (строки 16-17 «Домен: STAGING», 53
«STAGING-RULE. Cutover: после flip DNS сменить на Host(...)») — но сама `traefik...rule` строка (55) уже LIVE
(`Host(tandemmebel.ru) || Host(www.tandemmebel.ru)`, совпадает с label'ом живого контейнера). Косметика,
функционально нейтральна. При деплое 8df10ee обновлён только image-line коммент; шапочные STAGING-комменты
не трогались (минимальное изменение). Source-of-truth compose `host-stacks/vds-kzntsv/tandemmebel.compose.yml`
актуализирован полностью (8df10ee + LIVE-комменты + история bump'ов). При следующем full-sync можно выровнять
Portainer file под git-source.
## Гочи (специфичные)
- **Блокер-паттерн консюмер-пина** — snolla-репо релизит версию, но консюмер-пин в tandemmebel-репо отстаёт. На 0.42.0 (2026-07-04) ждали dev-source; на 0.42.1 (2026-07-12) оператор сделал сам. Всегда byte-verify пин+yarn.lock+`ls-remote` перед build.
- **`yarn install --immutable`** в Dockerfile — корневой `yarn.lock` обязан быть снапшотом целевого пина, иначе build падает на checksum-mismatch. Бамп = package.json + `yarn install` (обновляет lock) + commit обоих.
- **0.42.x sitemap-реструктуризация** — parity сверять по **page-locs** (развёрнутый sitemapindex), НЕ по именам под-sitemap'ов. 184 тут (было 172 на 0.42.0 — выросло, но NEW==PROD).
- **`/articles` 404 benign** — loc из sitemap отдаёт 404, если прод-оракул идентичен → не дефект (см. рецепт § Completeness-gate).
- Staging-порт `5020` — проверять `docker ps | grep 127.0.0.1:50` перед bind (stale throwaway от прошлого тиража).
- Smoke гнать **С VDS** (воркстейшн ловит LAN-DNS-перехват прод-доменов — см. memory `workstation-lan-dns-serves-local-cms-copy`).
- build-secret (VERDACCIO_TOKEN в ARG/ENV build-стадии) — известный follow-up, НЕ блокер (runtime-стадия отдельная, токена в финальном образе нет).
## Тираж snolla 0.42.1 — статус
Все 5 snolla-сайтов на VDS:
- labtools.ru (17), emspb.ru (18), labtools.pro (19) — 0.42.1 in-place bump 2026-07-05.
- kupimknigi (21), **tandemmebel (20)** — 0.42.0 cutover, tandemmebel in-place bump до 0.42.1 2026-07-12. (kupimknigi остался на 0.42.0 по решению оператора — см. `NEXT_SESSION.md`.)
## 0.43.2 v3 cutover (aws-sdk v2→v3) — ✅ ВЫПОЛНЕН 2026-07-30
6-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro, emspb — 5 GREEN). In-place bump стека 20 (DNS на VDS с 2026-07-12, не трогали). Рецепт — [[minio-split-cutover-runbook]] §B3-v3. **6/6 v3 сайтов GREEN = тираж завершён.**
- **Bump:** `@snollajs/snolla 0.42.1→0.43.2` в `apps/web/package.json` (commit `9d51c09`) + `resolutions`-блок в root `package.json` (verbatim labtools.ru: `@aws-sdk/client-s3 3.1005.0` + `@smithy` chain 5.3.11 — optional determinism, live verify доказал 5.6.12 работает vs vds MinIO 2025) + реген `yarn.lock` (single `@smithy/signature-v4@5.3.11` deduped, no dual-instance). Pushed victor/tandemmebel.ru master `f4ddce6..9d51c09`. Endpoint fix `f4ddce6` (s3.endpoint→minio.vds.kzntsv.site) был в master с 2026-07-29, но в бегущем образе `8df10ee` (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
- **Build на VDS:** `git archive 9d51c09` → `~/build/tandemmebel` → `docker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKEN` → `registry.kzntsv.site/tandemmebel:9d51c09`(+latest), digest `sha256:d14982c24dc7c61a334c8973e6f1c48147c7d3cbcc772b28af6c629431947b16`, 476MB (v2 8df10ee был 583MB — v3 leaner), EXIT=0. `yarn install --immutable` в Docker прошёл (yarn.lock matches).
- **Staging :5020 parity-gate GREEN** (throwaway контейнер из env живого стека 20, 8 секретов, `--network proxy`):
- robots.txt md5 `a01e4c60…` byte-identical (87 B).
- sitemap index→5 sub-sitemaps → **184 locs == live 184 IDENTICAL** (0 prod-only / 0 new-only — regression нет).
- **self-consistency 183×200 + 1×404** (`/articles` — prod=404 parity benign, не регрессия). 0×301/5xx.
- `/` 200 staging 45738B vs prod 45181B (+557B = og:url/og:image request-host context — benign, byte-identical post-cutover).
- theme `Gotham-Pro.css` 200 4436B (v3 aws-sdk v3 S3-direct read из vds MinIO 2025 — латентный прод-баг 0B→4436B починен ещё cutover'ом 2026-07-12, сохранён).
- **Portainer PUT stack 20 LIVE:** node in-memory `put-stack` (НЕ PS — кириллица compose; Node 22 global fetch, GET `/stacks/20` для Env + `/stacks/20/file` для StackFileContent), env 8/8 preserved, `prune:false pullImage:true mem_limit 512m`. PUT 200. Контейнер `9d51c09` healthy ~9s.
- **Live smoke GREEN (via traefik):** `/` 200 45742B, `/projects` 200 90758B, `/sitemap.xml` 200 (184 locs == parity), robots md5 `a01e4c60…`, `/articles` 404 parity, theme `Gotham-Pro.css` 200 4436B (v3 S3-direct). TLS CN=tandemmebel.ru, expire Oct 10 2026 (не тронут — in-place swap). Лог чист (tedious deprecation + `tandemmebel.ru (snolla) listening`).
- **Rollback:** `tandemmebel:8df10ee` (0.42.1 v2, template-only share-buttons, жив в registry) — Portainer PUT назад / compose revert. `0cd9351` (0.42.1) + `ed96b18` (0.42.0) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний.
- Compose (admin source-of-truth) синхронно закоммичен `8df10ee`→`9d51c09`. **6/6 v3 сайтов GREEN — тираж v3 завершён.**

View File

@@ -0,0 +1,3 @@
# ⛔ Файловый канал закрыт
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:3320]] (concepts/tg-digest-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.

View File

@@ -3,7 +3,7 @@ title: OpenWRT Router (192.168.1.1)
type: entity type: entity
tags: [hardware, networking, openwrt, nat, dhcp] tags: [hardware, networking, openwrt, nat, dhcp]
sources: [../sources/nas-recovery-session-2026-05-18.md] sources: [../sources/nas-recovery-session-2026-05-18.md]
updated: 2026-05-19 updated: 2026-08-24
--- ---
# OpenWRT Router # OpenWRT Router
@@ -55,6 +55,30 @@ updated: 2026-05-19
Дополнительные input rules для wan (стандартные OpenWRT): Allow-DHCP-Renew, Allow-Ping, Allow-IGMP, Allow-DHCPv6, Allow-MLD, Allow-ICMPv6-*, Allow-IPSec-ESP. Дополнительные input rules для wan (стандартные OpenWRT): Allow-DHCP-Renew, Allow-Ping, Allow-IGMP, Allow-DHCPv6, Allow-MLD, Allow-ICMPv6-*, Allow-IPSec-ESP.
## DNS (https-dns-proxy / DoH)
- LAN DNS обслуживает dnsmasq (`192.168.1.1:53`), который форвардит на локальные `https-dns-proxy`: `127.0.0.1#5053` (resolver 1) и `#5054` (resolver 2).
- Резолверы после фикса 2026-08-24:
- `https://1.1.1.1/dns-query` (Cloudflare)
- `https://one.one.one.one/dns-query` (Cloudflare)
### ⚠️ Инцидент 2026-08-24: провайдер режет DoH
Весь DNS локальной сети умер: dnsmasq логировал `Maximum number of concurrent DNS queries reached (max: 150)`, у https-dns-proxy куча соединений в FIN_WAIT1 к 8.8.4.4:443 и 104.16.249.249:443, клиенты получали таймауты.
**Побочный эффект:** пока DNS был мёртв, роутер не мог резолвить NTP-пул (`0.openwrt.pool.ntp.org`) — часы стояли на ~2.5 месяца назад (Jun 12 при реальном Aug 24). После фикса DNS NTP доехал и время синхронизировалось. Проверить: `date` на роутере. Если DNS снова ляжет — ждать рассинхрона часов (логи/крона).
Проверено с роутера: **рабочие** — `https://1.1.1.1/dns-query` (HTTP 200, отвечает), UDP 8.8.8.8:53, UDP 1.1.1.1:53, ICMP. **Нерабочие** — `https://dns.google/dns-query`, `https://cloudflare-dns.com/dns-query` (таймаут TCP 443), `https://dns.quad9.net/dns-query`. Обычный TCP 443 наружу работает (1.1.1.1:443 → 301), т.е. блокировка точечная по DoH-резолверам.
**Фикс:**
```
uci set https-dns-proxy.@https-dns-proxy[0].resolver_url='https://1.1.1.1/dns-query'
uci set https-dns-proxy.@https-dns-proxy[1].resolver_url='https://one.one.one.one/dns-query'
uci commit https-dns-proxy
/etc/init.d/https-dns-proxy restart
/etc/init.d/dnsmasq restart # при необходимости; dnsmasq_config_update='*' обычно сам
```
## Config backup (с 2026-06-12) ## Config backup (с 2026-06-12)
Daily UCI/config backup → [[kreknin-synology]]: Daily UCI/config backup → [[kreknin-synology]]:

View File

@@ -68,7 +68,7 @@ Production CMS (MoreThenCms) **остаётся на** [`windows-recovery-host`]
| Hostname | Service | Auth | Назначение | | Hostname | Service | Auth | Назначение |
|---|---|---|---| |---|---|---|---|
| `portainer.vds.kzntsv.site` | Portainer | vitya / `Pryakhin9-VDS-2026` (18 chars, см. [`portainer-2.21-admin-password-regression`](../concepts/portainer-2.21-admin-password-regression.md)) | Container management | | `portainer.vds.kzntsv.site` | Portainer | vitya / `<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>` (18 chars, см. [`portainer-2.21-admin-password-regression`](../concepts/portainer-2.21-admin-password-regression.md)) | Container management |
| `traefik.vds.kzntsv.site` | Traefik dashboard | basicAuth vitya / Pryakhin9 | Traefik runtime view | | `traefik.vds.kzntsv.site` | Traefik dashboard | basicAuth vitya / Pryakhin9 | Traefik runtime view |
| `git.kzntsv.site` | Gitea | kreknin users restored | Git hosting | | `git.kzntsv.site` | Gitea | kreknin users restored | Git hosting |
| `verdaccio.kzntsv.site` | Verdaccio | kreknin htpasswd (vitya) | Private npm | | `verdaccio.kzntsv.site` | Verdaccio | kreknin htpasswd (vitya) | Private npm |
@@ -144,3 +144,13 @@ DB TLS: self-signed certs (CN matches hostname), клиент с `verify-none` /
- ntfy push — TODO ([`vds-ntfy-push`](../../.tasks/vds-ntfy-push.md)). - ntfy push — TODO ([`vds-ntfy-push`](../../.tasks/vds-ntfy-push.md)).
- `iputils-ping` не установлен на host — `apt install iputils-ping` при следующем maintenance, иначе пользоваться `curl` для проверки сети. - `iputils-ping` не установлен на host — `apt install iputils-ping` при следующем maintenance, иначе пользоваться `curl` для проверки сети.
- Hermes — defer, ждёт уточнения user. - Hermes — defer, ждёт уточнения user.
## SSH-доступ (2026-08-25, критично!)
**`ssh -i ~/.ssh/id_ed25519 vitya@89.253.255.94`** — штатный вход (НЕ root!). sudo NOPASSWD для vitya.
- root login + password auth disabled пост-bootstrap (00-hardening.conf: PermitRootLogin no / PasswordAuthentication no)
- Источник: session:462. **Всегда пробовать vitya+id_ed25519 ПЕРВЫМ**, root — только через sudo vitya.
- root-пароль: `pass vds-kzntsv/root-pass-2026-08-25` (сменён Rusonyx 25.08, НЕ VDS_ROOT_PASS_INITIAL — неактуален)
- Ключ id_ed25519 дополнительно в /root/.ssh/authorized_keys (установлен 25.08 через docker)
- Portainer: `portainer.vds.kzntsv.site` + PORTAINER_API_KEY (pass vds-kzntsv/full-env) — полный docker-доступ, fallback без SSH
- Диагностика/восстановление через docker (Portainer): privileged-контейнеры с bind-mount/PidMode host (проверено 25.08)

View File

@@ -1,111 +1,3 @@
# Wiki Index # ⛔ Файловый канал закрыт
Catalog of all wiki pages. One line per page, organized by type. Updated on every ingest / new page. **Не читать. Не править.** Канон — mappa (`mcp__mappa__*`): wiki-сущности проекта, конвенции — AGENTS-сущность. Скил: `mappa-knowledge`.
## Overview
- [overview.md](overview.md) — project overview
## Entities
- [books-vds](entities/books-vds.md) — Books VDS (89.253.255.133 — host4g.ru, client server hosting books app + shared mongo/minio/elasticsearch)
- [dead-synology-diskstation](entities/dead-synology-diskstation.md) — мёртвая Synology DiskStation (source NAS)
- [kreknin-synology](entities/kreknin-synology.md) — Kreknin Synology (backup target + DDNS)
- [nl-vds-3xui](entities/nl-vds-3xui.md) — NL VDS 3x-UI VLESS/Reality node (213.176.64.253) — Reality 443 сломан (PQ×intel), plain VLESS 32030 работает
- [de-vds-3xui](entities/de-vds-3xui.md) — DE VDS 3x-UI VLESS node (130.17.17.158, Fornex Germany) — plain VLESS 32030 security=none; 3x-ui 3.5.0 gotchas (CSRF на POST, client-tables, binary-only setting)
- [openwrt-router](entities/openwrt-router.md) — OpenWRT Router (192.168.1.1)
- [ruvds-iis-host](entities/ruvds-iis-host.md) — RUVDS IIS Host (Win Server 2025 Core, 80.64.31.36) — CMS catch-all destination
- [snolla-recovery-vm](entities/snolla-recovery-vm.md) — Snolla Recovery VM (VirtualBox, savestate'нута 2026-05-21 после 36h soak)
- [vds-kzntsv](entities/vds-kzntsv.md) — VDS kzntsv (Rusonyx 160 NVMe cloud server)
- [vdsina-outline-3xui](entities/vdsina-outline-3xui.md) — VDSina Amsterdam VPN (46.151.25.64) — Outline (основной, 11 keys, :443) + 3x-UI/Xray VLESS; основной боевой VPN семьи
- [windows-recovery-host](entities/windows-recovery-host.md) — Windows Recovery Host (рабочий PC пользователя)
## Concepts
- [omniroute-combo-deepseek-web-tuning](concepts/omniroute-combo-deepseek-web-tuning.md) — настройка web-комбо под агента: system_message убивает скилы pi, tool_filter_regex режет тулы, search-режим ломает tool-calling, 503 chat_admission_busy, эмуляция `<tool>` работает, план кастомного провайдера, qwen-web baxia-WAF стена
- [forgetmeai-free-web-chat-proxies](concepts/forgetmeai-free-web-chat-proxies.md) — семейство бесплатных web-chat прокси ForgetMeAI (DeepSeek/Qwen/GLM/Kimi): паттерн «обход капчи через реальную браузерную сессию», PoW-солвер DeepSeek, браузерный путь через baxia-WAF (fetch в page.evaluate), CloakBrowser, классификаторы капчи; карта «что берём в lwp»
- [sched-admin-client-openapi](concepts/sched-admin-client-openapi.md) — F&F раунды 2026-08-18: Python-клиент одной командой из спеки (openapi-generator 7.24.0); r1 0.1.2 — 24/24+smoke 19/19, находки F-1..F-5 (8 операций на raw-обходе); r2 0.2.0/0.3.5 — все 5 закрыты, raw_ops удалён, 11/11 typed-only, вердикт «claim подтверждён полностью»
- [openapi-generator-3-1-spec-gotchas](concepts/openapi-generator-3-1-spec-gotchas.md) — генерация клиентов из 3.1-спеки (openapi-generator 7.24.0, python): wrapped-ответы {} → ValidationError (raw-обход историчен: r2 спека фикснута), const:true → строковый enum, oneOf+discriminator → actual_instance=None (r2: плоская схема), java из PATH а не JAVA_HOME, InlineObjectN-имена, info.version не бьётся
- [nvm-junction-ismain-gate-gotcha](concepts/nvm-junction-ismain-gate-gotcha.md) — ESM isMain-гейт молча не выполняется под junction'нутым npm-рутом (nvm/mise/volta); realpath-фикс; диагностика за 30 сек
- [nvm-windows-node-switch](concepts/nvm-windows-node-switch.md) — смена версии Node на nvm-windows: junction, per-version глобалы, PATH-кэш, npm 11 allow-scripts; Node 22→24 evidence
- [sched-fnf-r8-verification-stand](concepts/sched-fnf-r8-verification-stand.md) — стенд верификации F&F r8 (schedule-as-entity): путь, версии core 0.40.2/ui 0.2.0, карта репро-скриптов (миграции/движок/политики/API 52/UI 13), подтверждённый контракт (ceiling, retryCount, snapshot, pause AND, tz-hoist, PATCH-merge), находки №5 (U1 форма-tz, U2 вёрстка), как закрыть раунд
- [sched-fnf-testing-method](concepts/sched-fnf-testing-method.md) — метод F&F-тестирования (урок r10): тест по СКОМПИЛИРОВАННОЙ доке (dev-сервер localhost:5108), не по сырцам; порядок хендоффа 1–6 (код → доки source парсящиеся → generate/dist → push ноль ahead → verdaccio → пинг), битый пример = возврат, schedjs.com = заглушка (деплой не требовать)
- [windows-docker-test-harness-gotchas](concepts/windows-docker-test-harness-gotchas.md) — грабли Windows-верификации против docker: rmSync до DatabaseSync (EBUSY), vitest 10s vs ~11s docker-DDL, монотонность фейк-часов, изоляция DB на сценарий, висельники node-процессов
- [snolla-smtp-mail-delivery-2026-08](concepts/snolla-smtp-mail-delivery-2026-08.md) — SMTP-инцидент 12–14.08: 525 (Яндекс отключил протоколы) → 554 (холодный ящик, мульти-получатели) → 550 (from not owned); фиксы + монитор на VDS + рецепт деплоя
- [keenetic-rci-api](concepts/keenetic-rci-api.md) — KeeneticOS RCI API: auth, чтение конфига, запись через CLI-parser, DNS-override против сломанного hairpin
- [admin-api-embed-pilonuxt](concepts/admin-api-embed-pilonuxt.md) — admin-api embedded in pilonuxt /admin/api, stack 25 retired (2026-08-05)
- [pilonuxt-restart-vs-redeploy-2026-08-16](concepts/pilonuxt-restart-vs-redeploy-2026-08-16.md) — стек 16: рестарт (in-memory viewModel кэш content-api) vs пересборка (код); инцидент 500 на новинках (Content object vs array → core 0.26.7/admin-api 0.11.0); откат-цепочка образов 2026-08-16
- [namespace-consolidation-snolla](concepts/namespace-consolidation-snolla.md) — @snollajs → @snolla, 8 sites bumped, 17 packages (2026-08-04/05)
- [admin-infra-project](concepts/admin-infra-project.md) — design + migration plan для OpeItcLoc03/admin (canonical)
- [backup-inventory-2026-06](concepts/backup-inventory-2026-06.md) — карта всех машин × что реально бэкапится (с доказательством); дыры по приоритету (kreknin SPOF, nl-vds x-ui.db, bookva-*, openwrt) — триггер: MSSQL 3-нед gap
- [bindmount-config-edit-preserve-mode](concepts/bindmount-config-edit-preserve-mode.md) — правка bind-mounted config'а через mktemp+mv роняет режим 644→600 → non-root контейнер (uid 1000) EACCES crash-loop; chmod --reference=backup. + гоча: bind-mount затеняет config образа целиком → класть полную секцию, не дельту. Инциденты books-job-scheduler + task-runner 2026-06-18
- [vds-kzntsv-ssh-access](concepts/vds-kzntsv-ssh-access.md) — SSH access audit на vds-kzntsv (infra VDS), retained keys + add/revoke processes (was misnamed `books-ssh-access` до 2026-05-25 — content всегда был про vds-kzntsv)
- [compose-bcrypt-escape-trap](concepts/compose-bcrypt-escape-trap.md) — Docker Compose ест `$` в bcrypt hashes
- [db-tls-self-signed-via-traefik-raw-tcp](concepts/db-tls-self-signed-via-traefik-raw-tcp.md) — DB TLS через traefik raw TCP с self-signed certs
- [docker-host-loopback-detect](concepts/docker-host-loopback-detect.md) — Docker host-loopback detection — как доказать что host.docker.internal не петля
- [emspb-vds-deploy-runbook](concepts/emspb-vds-deploy-runbook.md) — emspb.ru snolla-app на VDS (стек 18): сборка/env/cutover 07-02 + **0.42.1 in-place bump 07-05** (образ `emspb:95a5c42`)
- [es-destructive-delete-incident-2026-05-26](concepts/es-destructive-delete-incident-2026-05-26.md) — ES indices wiped on canonical — **true root cause (2026-05-29): ransom-бот через открытый `0.0.0.0:9200` мимо traefik, free-ES без auth**; гипотеза operator-error опровергнута. Fix = убрать публикацию host-порта (Control #3) + restore. Exposure-audit: mongo/mariadb/minio тоже exposed (credentialed)
- [future-resilient-architecture-goals](concepts/future-resilient-architecture-goals.md) — fault-tolerance roadmap placeholder (расширяется через `[resilience-roadmap-design]`)
- [hyper-backup-structure-and-recovery](concepts/hyper-backup-structure-and-recovery.md) — Hyper Backup — структура репо и стратегия восстановления
- [iis-migration-2026-05-19-postmortem](concepts/iis-migration-2026-05-19-postmortem.md) — post-mortem миграции CMS на нативный IIS, 2026-05-19
- [labtools-vds-deploy-runbook](concepts/labtools-vds-deploy-runbook.md) — labtools.ru snolla-app на VDS (стек 17): сборка/env/cutover 07-02 (Yandex DNS!) + **0.42.1 in-place bump 07-05** (образ `labtools:566d41c`, order-фикс + sitemap починен)
- [labtools.pro-vds-deploy-runbook](concepts/labtools.pro-vds-deploy-runbook.md) — labtools.pro snolla-каталог на VDS (стек 19): сборка/env/cutover 07-02 + **0.42.1 in-place bump 07-05** (образ `labtools-pro:0610432`, order-парити 3 секции)
- [tandemmebel-vds-deploy-runbook](concepts/tandemmebel-vds-deploy-runbook.md) — tandemmebel.ru snolla-блог-портфолио на VDS (стек 20): cutover 07-12 (DNS reg.ru, LE) + **0.42.1 in-place bump 07-12** (образ `tandemmebel:0cd9351`, 184/184 parity, /articles 404 benign)
- [maljarka-vds-deploy-runbook](concepts/maljarka-vds-deploy-runbook.md) — maljarka.tandemmebel.ru snolla-лендинг на VDS (стек 23): restore 2026-07-31 (потерян при decomm RUVDS 2026-07-21, не был в v3 тираже). Клон on.snolla.com, siteId A2476738, layout.liquid byte-identical .NET-admin рендеру (43730B). Тираж 7/7
- [minio-imgproxy-on-vds](concepts/minio-imgproxy-on-vds.md) — MinIO+imgproxy stack на VDS, миграция с upgrade 2020→2025 + DNS swap pending
- [minio-split-cutover-runbook](concepts/minio-split-cutover-runbook.md) — разнос MinIO-контента: Track A books-vds 2020→2025 (shared+bookva) + Track B CMS-медиа → vds 2025. Progress: B1 миррор✅ B2 DNS-flip✅ B3 on-snolla v2-пилот✅ LIVE, B3v3 (snolla 0.43.2 опубликована, live verify ЗЕЛЁНЫЙ, resolutions optional) ⚡READY, B4/B5/Track A TODO. v3 — конечная цель.
- [morethencms-null-settingsdata-https-502](concepts/morethencms-null-settingsdata-https-502.md) — MoreThenCms тенант с `Sites.SettingsData=NULL` → 502 только на HTTPS (нет `httpSecure` → KeyNotFound); fix = UPDATE + recycle. maljarka 2026-06-08
- [snolla-admin-appdata-acl-500-after-scp-migration](concepts/snolla-admin-appdata-acl-500-after-scp-migration.md) — админский delete/upload ассета → 500: app pool имел на `App_Data` только RX после scp-миграции → `File.Delete` UnauthorizedAccessException. Fix = icacls Modify. + split-brain: админка пишет Local, боевой app читает MinIO
- [snolla-local-admin-and-on-snolla-migration-design](concepts/snolla-local-admin-and-on-snolla-migration-design.md) — design: Task A восстановить локальный .NET-админ (catch-all IIS `snolla`, hosts-override, MinIO drop-in) + Task B on.snolla.com → VDS как Node snolla-app
- [snolla-local-admin-https-binding](concepts/snolla-local-admin-https-binding.md) — реализация 2026-07-29: HTTPS на локальном IIS-админе `snolla` :443 (self-signed wildcard `*.snolla.com` в Trusted Root, без traefik) + pilorama98.snolla.com в hosts; рабочий entry-point `/admin/account/login` (голый `/admin` → canonical на public-домен, предсуществующее поведение CMS)
- [mssql-container-data-restore](concepts/mssql-container-data-restore.md) — MSSQL контейнер с восстановленными production data — паттерн
- [mssql-on-vds](concepts/mssql-on-vds.md) — MSSQL Express 2022 Linux на VDS — миграция + login orphan fix + traefik TCP gotchas
- [ocis-on-vds-deploy-recipe](concepts/ocis-on-vds-deploy-recipe.md) — oCIS на VDS — deploy recipe + non-obvious gotchas (UID 1001 vs 1000, basic auth, LibreGraph user-create)
- [portainer-2.21-admin-password-regression](concepts/portainer-2.21-admin-password-regression.md) — Portainer 2.21 `--admin-password` regression + min 12-char policy
- [portainer-stack-management-books-vds](concepts/portainer-stack-management-books-vds.md) — Portainer-managed stacks на books VDS — pattern application + migration log 2026-05-25
- [portainer-stack-management-vds](concepts/portainer-stack-management-vds.md) — Portainer-managed stacks на VDS — canonical pattern + migration script + stack-redeploy recipe (новый тег) + gotchas (вкл. PS 5.1 ISO-8859-1 коррапт кириллицы при API round-trip)
- [snolla-live-prod-inplace-image-bump](concepts/snolla-live-prod-inplace-image-bump.md) — переиспользуемый рецепт обновления образа на ЖИВОМ snolla-стеке VDS in-place (build→throwaway-staging-acceptance С VDS→env-preserving Portainer PUT→live-smoke); вкл. `put-stack.js`; отработан на тираже 0.42.1 (4 сайта: labtools/emspb/labtools.pro 2026-07-05 + tandemmebel 2026-07-12)
- [stostayer-web-deploy-runbook](concepts/stostayer-web-deploy-runbook.md) — деплой легаси web (`www.stostayer.ru`) на прод клиента: build→registry→Portainer-стек 16 через container-IP API с хоста; **деплой 0.3.23 (2026-08-13): calculator-баннер + стек-фикс**; гочи — один push без retry (VPN-IP банится хостером), offline-build не всегда проходит (linux-бинари esbuild/rollup не в cache → временный VERDACCIO_TOKEN ARG/ENV), Portainer API base=http://IP:9000 (порт обязателен)
- [stostayer-admin-minio-config](concepts/stostayer-admin-minio-config.md) — локальная .NET-админка stostayer (:8090) на stostayer MinIO: креды НЕ в pass (в stostayer.new config), endpoint `minio-api.stostayer.ru` (minio.stostayer.ru — не API-порт), region `us-west-1` (не local); fix 2026-07-22 — 3 замены в Web.config, админка=прод единое хранилище
- [stostayer-markdown-render-404-incident-2026-07](concepts/stostayer-markdown-render-404-incident-2026-07.md) — постмортем 2026-07-24: 404/500 на body_repair_service = двойной корень (snolla `markdown();` без присвоения + exact-pin-staleness — content-api `workspace:*` запёк `@snollajs/snolla: 0.7.4`, фиксовая 0.7.7 легла top-level, content-api юзал nested баговую). Фикс snolla 0.7.7/core 0.26.1 + content-api 0.8.2 `^0.7.7`; деплой 0.3.22 через pscp+load (без :443 push). Pre-build чек yarn.lock на single-snolla ловит класс. Cleanup `workspace-caret-interpackage-deps` (vitya scope)
- [proxy-debugging-test-the-real-client](concepts/proxy-debugging-test-the-real-client.md) — анти-паттерн: свои curl/standalone-тесты «работают», а боевой клиент пользователя нет; источник истины — реальный клиент
- [reality-pq-mldsa65-dest-incompatibility](concepts/reality-pq-mldsa65-dest-incompatibility.md) — REALITY+ML-DSA-65 (PQ) не работает с не-PQ dest (Akamai/intel шлёт HRR); fix = PQ-совместимый dest или выключить PQ; **caveat: снятие PQ ≠ рабочий Reality у GUI-клиентов**
- [recovery-architecture-snapshot](concepts/recovery-architecture-snapshot.md) — текущая recovery architecture (2026-05-19/21, attempt 2)
- [registry-gc-mount-and-modify-flag](concepts/registry-gc-mount-and-modify-flag.md) — Docker Registry GC mount layout + `-m` flag
- [registry-kzntsv-auth-model](concepts/registry-kzntsv-auth-model.md) — registry.kzntsv.site = standalone registry:2 + htpasswd Basic (бинарный доступ, НЕ Gitea-packages, нет per-repo ACL/robot-токенов); как заводить htpasswd-юзеров (hot-reload), GC через v2 DELETE; юзеры vitya + books-ci
- [registry-oci-image-index-gc](concepts/registry-oci-image-index-gc.md) — books-* образы в registry = OCI image-index (buildx multi-manifest): top-level `.config`=null, дата `.created` живёт в платформенном sub-manifest; наивный GC по top-level дате → null у всех → no-op (keepLastN не применяется). DELETE по index-digest, дата из sub. + **dangling-индексы**: 16/19 тегов books-web — тег жив, sub-manifest `MANIFEST_UNKNOWN` (вычищен прежним host-GC); date-GC защищает их как null-dated → не чистит, хотя они и есть мусор. Зафиксировано на books registryGc dryRun 2026-06-18
- [rusonyx-vps-onboarding-quirks](concepts/rusonyx-vps-onboarding-quirks.md) — Rusonyx VPS onboarding quirks (Astra Облако / myvm.rusonyx.ru)
- [traefik-acme-json-to-iis-cert-import](concepts/traefik-acme-json-to-iis-cert-import.md) — Экспорт LE certs из traefik acme.json в IIS (PFX + SNI bindings)
- [traefik-file-watch-wsl2-broken](concepts/traefik-file-watch-wsl2-broken.md) — Traefik file-watch broken под Docker Desktop Windows (WSL2 9p mount)
- [traefik-on-windows-docker-desktop](concepts/traefik-on-windows-docker-desktop.md) — Traefik на Windows Docker Desktop — нюансы
- [traefik-tcp-passthrough-vs-starttls](concepts/traefik-tcp-passthrough-vs-starttls.md) — Traefik TCP passthrough vs STARTTLS-protocols
- [vbox-windows-stability-tuning](concepts/vbox-windows-stability-tuning.md) — VirtualBox + Windows-гость — нюансы стабильности cross-hypervisor миграции
- [vds-kzntsv-dhcp-outage-2026-05-28](concepts/vds-kzntsv-dhcp-outage-2026-05-28.md) — DHCP outage 2026-05-28 на vds-kzntsv — диагностика + recovery runbook (link UP но без IPv4 → статика + тикет хостеру)
- [verdaccio-prune-semantics](concepts/verdaccio-prune-semantics.md) — Verdaccio prune semantics — proxied vs locally-published
- [verdaccio-restore-packument-desync](concepts/verdaccio-restore-packument-desync.md) — disaster-restore: тарболл на диске + древний packument → EEXISTS 409; fix = снять коллизирующий .tgz, не rm -rf каталог
- [verdaccio-token-lifecycle](concepts/verdaccio-token-lifecycle.md) — restart trap (ephemeral secret) + max_users:-1 pnpm 409 + JWT fix + yarn clients
- [yarn-npm-minimal-age-gate](concepts/yarn-npm-minimal-age-gate.md) — Yarn ≥4.16 client-side age-gate (YN0016 «quarantined») на версиях <24ч; глобальный, не per-scope; fix npmMinimalAgeGate:0
- [winacme-iis-owin-catchall-http01](concepts/winacme-iis-owin-catchall-http01.md) — win-acme HTTP-01 авто-renewal LE на IIS под OWIN-catch-all CMS — challenge в отдельном No-Managed-Code приложении + патч шаблона; заменяет ручной PFX-импорт
- [wd40efax-smr-cascade](concepts/wd40efax-smr-cascade.md) — WD40EFAX SMR Cascade — root cause NAS failure 2026-05-18
- [windows-server-2025-core-bootstrap](concepts/windows-server-2025-core-bootstrap.md) — Win Server 2025 Core (RUVDS) — bootstrap для IIS-хоста: default-blockers + transfer-методов матрица
- [mcp-init-resilience](concepts/mcp-init-resilience.md) — mcp-init-resilience
## Packages
<!-- (none yet) -->
## Sources
- [omniroute-session-2026-08-22](sources/omniroute-session-2026-08-22.md) — хронология сессии настройки omniroute web-комбо (deepseek-web): корни вылетов, фиксы, тесты эмуляции, план кастомного провайдера
- [iis-host-migration-2026-05-19](sources/iis-host-migration-2026-05-19.md) — IIS Host Migration Session 2026-05-19 chronology
- [iis-migration-to-ruvds-2026-05-23](sources/iis-migration-to-ruvds-2026-05-23.md) — IIS migration to RUVDS — session 2026-05-23/24 (SSH/scp pivot, 25 SNI bindings, partial DNS cutover)
- [nas-recovery-session-2026-05-18](sources/nas-recovery-session-2026-05-18.md) — NAS Recovery Session 2026-05-18/19 chronology
- [books-vds-backup-daily-kreknin-2026-05-25](sources/books-vds-backup-daily-kreknin-2026-05-25.md) — books VDS daily backup → kreknin pipeline standup 2026-05-25 (ES file-snapshot repo + DB dumps + curl SMTP)
- [ruvds-backup-daily-kreknin-2026-05-24](sources/ruvds-backup-daily-kreknin-2026-05-24.md) — RUVDS daily backup → kreknin pipeline standup 2026-05-24 (rclone+SFTP, SYSTEM task)
- [nl-vds-3xui-setup-2026-06-05](sources/nl-vds-3xui-setup-2026-06-05.md) — NL VDS 3x-UI setup+troubleshooting session; итог: у реальных клиентов из РФ работает только plain VLESS 32030 (Reality/MTProto/SOCKS не поднялись)
- [de-vds-3xui-setup-2026-07-14](sources/de-vds-3xui-setup-2026-07-14.md) — DE VDS (Fornex Germany) setup session; 3x-ui 3.5.0 gotchas: CSRF на panel POST, прямой DB-insert не рендерит (client-tables), wrapper x-ui setting не применяет; plain VLESS 32030 confirmed на real RF-клиенте
- [vdsina-outline-3xui-inventory-2026-06-24](sources/vdsina-outline-3xui-inventory-2026-06-24.md) — VDSina Amsterdam (46.151.25.64) inventory: Outline 11 keys + 3x-UI VLESS, секреты в pass vdsina-outline
- [vds-kzntsv-bootstrap-2026-05-20](sources/vds-kzntsv-bootstrap-2026-05-20.md) — VDS bootstrap session 2026-05-20 chronology
- [vds-kzntsv-incident-2026-05-28](sources/vds-kzntsv-incident-2026-05-28.md) — DHCP outage incident session 2026-05-28 — chronology + ticket text

View File

@@ -1,145 +1,3 @@
## [2026-08-19] ingest | NEW concepts/sched-fnf-testing-method.md — метод F&F-тестирования (урок r10): тест по скомпилированной доке (dev-сервер localhost:5108), порядок хендоффа 1–6, битый пример = возврат, schedjs.com заглушка. index.md updated (+1). # ⛔ Файловый канал закрыт
## [2026-08-16] ingest | concepts/pilonuxt-restart-vs-redeploy-2026-08-16.md — день деплоев стека 16 (7 операций): рестарт vs пересборка разведены как отдельные операции. Рестарт сбрасывает in-memory viewModel кэш content-api (цена за м³ подтянулась после рестарта без пересборки); пересборка — только для кода. Анти-симптом: 500 на категориях с логом `@snolla/core viewModels/index.js:419 _.find(x=>x.name)` = код/данные (Content object vs array, admin-api create), рестарт не лечит — фикс snolla core 0.26.7 + admin-api 0.11.0 array-canon (1128ddc → 64c2539). Откат-цепочка образов 64c2539→…→8b7e8c4. +index.md. Монорепа-сторона заингестена прогером (lazy-data-not-found-404, admin-api-content-wire array-canon, cms-edits-cache-restart, v-html-on-component-ssr, admin-api-content-edit).
## [2026-08-14] decision | FormSubmissions.Content ₽→«?» — координация закрыта. ALTER на клиентской stostayer сделан (.admin); snolla опубликовала `@snolla/data@0.15.1` (STRING→TEXT=NVARCHAR(MAX), тесты 7/7); скан живых БД VDS: MoreThenCms+Staging уже nvarchar (ALTER не нужен), VDS-stostayer varchar — не прод, не трогаем (роль — к оператору); legacy stostayer.web на @snollajs 0.7.x — фикс не едет, ALTER покрывает (backport опция `065bb44`); формы есть у всех сайтов тиража (pilorama98 2516/labtools 916 — вопреки гипотезе snolla). Бонус: email-контракт задокументирован snolla (forms-email-contract.md), warn при пустом `to` — ждёт решения vitya. Таска snolla [formsubmissions-content-nvarchar] закрыта.
## [2026-08-14] implement | MSSQL stayer_site: ALTER FormSubmissions.Content varchar(max) cp1251 → NVARCHAR(MAX) (запрос их прогера: ₽→«?» в письмах с калькулятора). Verify до/после (nvarchar max). Live-тест не гонял, тестовые заявки #68485/#68486 не удалял (ждут подтверждения). Соединение: tedious из packages/web node_modules, MSSQL 91.222.236.225:1433 открыт снаружи.
## [2026-08-14] lint | inbox-rename cascade audit (idea 13): rg по живых инструкциях .admin — единственный хит log.md (op-log история, ок); stostayer.new — только маркеры «устаревший путь»; ack-письма в .agents/inbox подтверждены (.read); pi-расширение inbox-monitor (деплой+source of truth) на .agents/inbox, .claude-inbox не упоминается. Подтверждение оператору отправлено в .workshop/.agents/inbox.
## [2026-08-13] ingest | UPDATE concepts/stostayer-web-deploy-runbook.md — offline-гоча ПОЧИНЕНА их коммитом `5c583f8` (supportedArchitectures libc glibc/musl): сетевой yarn install на build-машине долил linux-зипы в кэш, будущие offline-сборки чисты. Runbook переписан с рецептом. Ack-обмен с stostayer.new (их инбокс `.agents/inbox/`).
## [2026-08-13] ingest | UPDATE concepts/stostayer-web-deploy-runbook.md — деплой stostayer-web:0.3.23 на прод www.stostayer.ru (запрос их прогера, письмо inbox). Нюансы зафиксированы: (1) offline-build НЕ проходит — в `.yarn/cache` нет linux-бинарей `@esbuild/linux-x64`/`@rollup/rollup-linux-x64-gnu` → обход через временный `ARG/ENV VERDACCIO_TOKEN` + `--build-arg` (после — revert); (2) Portainer API с хоста — `base=http://<IP>:9000` (порт обязателен); (3) прод-тег в таблице актуализирован 0.3.18→0.3.23; (4) блок деплоя 0.3.23. Task [stostayer-web-deploy-0-3-23] 🟢.
## [2026-08-12] implement | UPDATE entities/kreknin-synology.md — автосинк LE-серта: acme-nginx-sync.sh (cron 15 мин) ротирует серт git.kreknin.site из traefik acme.json в nginx DSM автоматически.
## [2026-08-12] implement | UPDATE entities/kreknin-synology.md — LE-серт git.kreknin.site (из traefik acme.json) подложен в nginx DSM reverse proxy. Из LAN: валидный LE + Gitea, без DSM-пароля (root SSH).
## [2026-08-12] implement | UPDATE entities/kreknin-synology.md — reverse proxy git.kreknin.site:443 → traefik:4443 на DSM nginx (server.ReverseProxy.conf w3conf + reload). Из LAN git.kreknin.site → Gitea (DNS-override + proxy), снаружи LE-серт. Осталось: LE-серт в DSM для git.kreknin.site (из LAN, без предупреждения).
## [2026-08-12] implement | NEW concepts/keenetic-rci-api.md — разобрана автоматизация KeeneticOS 5.1.3 (RCI: GET /rci/show/*, POST /rci/ массив parse-команд). Через API добавлены DNS-записи git.kreknin.site/kreknin.site → 192.168.1.43 (решение сломанного hairpin). Проверено с LAN: всё 200. Правила порт-форвардинга в 5.x лежат в ip.static (to-host=MAC).
## [2026-08-12] implement | UPDATE entities/kreknin-synology.md — Gitea 1.27.1 + postgres 16 на kreknin (для Алексея), traefik починен (restart policy), LE-серт git.kreknin.site, проброс 80/443→8000/4443 на Keenetic. KeeneticOS Viva API закрыт — правила в GUI. NAT loopback сломан → нужен DNS-override git.kreknin.site→192.168.1.43. Пароли: pass kreknin/keenetic + pass kreknin/gitea.
# Wiki op-log
## [2026-08-14] ingest | NEW concepts/snolla-smtp-mail-delivery-2026-08.md — postmortem SMTP-инцидента 12–14.08: 525 SMTP disabled (Яндекс 360, доменка) → переезд на e-16513832@yandex.ru (app-password); 554 SPAM на 2+ получателях от холодного ящика → @snolla/mailer@0.9.0 per-recipient (forms-api 0.2.0, web 0.44.0); 550 from not owned → mailSettings.from во всех production.json + шаблон pilorama98checkoutconfirm. Монитор SMTP на VDS (cron 08:00, ntfy) + parity-чек Portainer env. Рецепт деплоя тиража (порядок, гочи: ad-hoc контейнер vs Portainer, regex-парсинг image, yarn up -R data).
Append-only log of wiki operations (ingests, promotions, lints, migrations). **Не читать. Не править.** Канон — mappa (`mcp__mappa__*`): wiki-сущности проекта, конвенции — AGENTS-сущность. Скил: `mappa-knowledge`.
## [2026-08-05] ingest | concepts/namespace-consolidation-snolla + concepts/admin-api-embed-pilonuxt — крупнейшие изменения сессии: неймспейс-миграция @snollajs→@snolla (17 пакетов, 8 сайтов бампнуты), admin-api embed в pilonuxt /admin/api, retire стека 25. +index.md строки.
## [2026-07-30] migrate | RELOCATE runbook `scripts/minio-split-cutover/runbook.md` → `concepts/minio-split-cutover-runbook.md` (дисциплина: runbook'и → `.wiki/concepts/`, не `scripts/`; `scripts/` для executable). +UPDATE content: v3-секция (snolla 0.43.2 опубликована, live MinIO verify ЗЕЛЁНЫЙ оба конфига, resolutions=optional determinism НЕ mandatory — regression-гипотеза v3 SigV4 vs MinIO НЕ воспроизвелась end-to-end, «CONFIRMED» framing откатан), креды нормализованы → `pass show minio-vds/full-env` (были plaintext в bash-примерах — wiki-конвенция «креды в pass, не в git»). +index.md строка. Старый `scripts/minio-split-cutover/` удалён (в нём кроме runbook'а ничего не было). Cross-ref [[on-snolla-vds-deploy-runbook]], [[minio-imgproxy-on-vds]], [[snolla-live-prod-inplace-image-bump]], [[../entities/vds-kzntsv]], [[../entities/books-vds]]. Related memory: admin `aws-sdk-v3-smithy-sigv4-minio-regression`, `aws-sdk-v2-minio-2025-works-no-smithy-pin`.
## [2026-07-29] implement | NEW concepts/snolla-local-admin-https-binding.md + UPDATE index.md (concepts +2 строки) — локальный .NET-админ snolla (catch-all IIS `snolla`) переведён на HTTPS. Повод: `https://pilorama98.snolla.com/admin` → 404 → ERR_CONNECTION_CLOSED. Корень 404: `pilorama98.snolla.com` не был в hosts-override → реальный DNS → VDS `89.253.255.94` → traefik без роута (системный прокси `127.0.0.1:10808` маскировал «server 127.0.0.1» в curl). pilorama98 — реальный сайт в `dbo.Sites` (Alias=pilorama98, PrimaryDomain=pilorama98.ru, Live), вне тиража-5 из design-дока. Корень ERR_CONNECTION_CLOSED: браузер HSTS-апгрейдил http→https, а локальный IIS без 443. Решение: **IIS-native self-signed wildcard `*.snolla.com` на :443 + Trusted Root**, НЕ traefik (локальный acme.json пуст → LE на snolla.com нет; traefik-as-TLS-terminator = NAT-loopback/docker.sock/URL-Rewrite/SERVER_PORT-leak готчи без выгоды). Скрипт elevated (UAC): `New-SelfSignedCertificate -Subject '*.snolla.com' -DnsName '*.snolla.com','snolla.com' -TextExtension EKU serverAuth` (гоча: `-DnsName` конфликтует с ручным SAN `2.5.29.17` → SAN через `-DnsName`) → копия в `Cert:\LocalMachine\Root` → `New-WebBinding snolla https 443 *` + `AddSslCertificate` + restart. Cert thumb `6E2212B4C13BD28E2FF680322F11741193E8C619`, SAN `*.snolla.com,snolla.com`, 10 лет — покрывает все alias'ы. hosts: добавлен `pilorama98.snolla.com` (elevated, построчно — `Get-Content -Raw` мог опустошить файл). Результат: `https://<alias>.snolla.com/admin/account/login` → 200 форма SNOLLA (pilorama98/kupimknigi/internal/tandemmebel верифицированы). **Голый `/admin` → 302 `/login` → 301 `https://www.<PrimaryDomain>/`** — предсуществующее поведение CMS (оператор подтвердил: «так и было» на RUVDS), не поломка миграции; рабочий entry-point `/admin/account/login`. `on.snolla.com` → 404: hosts стоит `internal.snolla.com` (реальный alias сайта `on` = `internal`), не `on` (design-док писал `on`). Trade-off: self-signed не LE — допустимо (админка локальная, FW закрыт). +index.md (2 строки: design-док ранее не был индексирован + новый page). Cross-ref [[snolla-local-admin-and-on-snolla-migration-design]], [[traefik-on-windows-docker-desktop]], [[stostayer-admin-minio-config]].
## [2026-07-22] fix | NEW concepts/stostayer-admin-minio-config.md — локальная .NET-админка stostayer (`C:\sites\stostayer\Web.config`, IIS :8090) переведена на рабочий stostayer MinIO. Исходно S3-провайдеры (`MoreThenCms.FileStorage.S3` в `bin/`) уже стояли, но с 3 багами: (1) чужие креды `AKIAJ2YJP72W6ZHCRE6Q` = books-vds/snolla root-ключ (копипаст из snolla catch-all admin, не заменён на stostayer-свои → "Access Key Id does not exist"); (2) `serviceURL=https://minio.stostayer.ru` — НЕ S3 API-порт ("S3 API Requests must be made to API port"), прод юзает `minio-api.stostayer.ru`; (3) `authenticationRegion="local"` → SigV4 регион-мismatch → `AmazonS3Exception: the region is wrong; expecting 'us-west-1'` (400) на `GalleriesStorage.EnsureKeyPrefixExists` (`ToolsController.ImagePreview` → `CreatePreview`). Креды stostayer MinIO **не в pass** — лежат в `~/projects/stostayer.new/packages/web/config/default.json` → `s3`: accessKey `stayer_minio`, endpoint `https://minio-api.stostayer.ru`, region `us-west-1`. Fix: 3 глобальные замены × 9 S3-блоков в Web.config (accessKey/secretKey[XML-escaped `>`/`&`]/serviceURL) + region `local`→`us-west-1`; бэкап `Web.config.bak-2026-07-22`; IIS recycle, лог чист. mc `alias set` + ListBuckets OK (10 бакетов: assets/galleries/themes/…); `assets/9cf0a8e52cf144619fd290606a146d35/` несёт реальные файлы. Acceptance оператором: картинки грузятся. Админка=прод единое хранилище, ручное зеркалирование в `App_Data` упразднено. +index.md concepts-строка. Cross-ref [[galleries-storage-class-local-not-s3]], [[snolla-admin-appdata-acl-500-after-scp-migration]], [[minio-imgproxy-on-vds]].
## [2026-07-14] ingest | sources/de-vds-3xui-setup-2026-07-14.md (new) + entities/de-vds-3xui.md (sources: привязан) — полноценный ingest DE-сессии: sources-хроника с борьбой против 3x-ui 3.5.0 (CSRF-403 на login → `CSRFMiddleware`/`X-CSRF-Token`/`GET {BP}csrf-token`; прямой DB-insert в `inbounds` не рендерит → client-tables `clients`/`client_inbounds`/`client_traffics` заполняются только `AddInbound` → panel API `POST {BP}panel/api/inbounds/add` с stringified settings/streamSettings/sniffing; wrapper `x-ui setting` не применяет креды → бинарник). Эталон JSON снят с рабочего nl-vds 32030. +index.md sources-строка. Real RF-client confirmed.
## [2026-07-14] decision | entities/de-vds-3xui.md — real-client из РФ подтверждён (user подключён через 32030, сессия идёт через узел). Plain VLESS = proven-working на обоих NL+DE. Задача [de-vds-3xui-setup] закрыта. Перекрестил `Проверено` блок ✅.
## [2026-07-14] ingest | entities/de-vds-3xui.md (new) — поднят VPN-VDS в Германии (Fornex `130.17.17.158`, Ubuntu 24.04, 1/2G/20G), аналог [[nl-vds-3xui]]. Повторён **только proven-working plain VLESS 32030** `security=none` (на NL у боевых клиентов из РФ заработал только он); Reality/MTProto/SOCKS5 намеренно НЕ подняты. Креды → `pass de-vds-3xui/full-env`. 3x-ui тут **3.5.0** (на NL была 3.2.7) — вскрыл новые гоча, зафиксированы в entity: (1) **CSRF на всех panel POST** (`CSRFMiddleware` → 403 пустое тело+CSP-nonce без заголовка `X-CSRF-Token`; токен — `GET {BP}csrf-token`; login = `POST {BP}login` JSON); (2) **прямой INSERT в `inbounds` НЕ рендерит инбаунд в 3.5.0** — клиентская модель разнесена по `clients`/`client_inbounds`/`client_traffics`, заполняется только сервисом `AddInbound` → инбаунд надо создавать через panel API `POST {BP}panel/api/inbounds/add` (body=model.Inbound camelCase, settings/streamSettings/sniffing stringified); (3) **wrapper `x-ui setting -username/-password` НЕ применяет** — только бинарник `/usr/local/x-ui/x-ui setting` (после `systemctl stop x-ui`). Server-side e2e подтверждён (xray слушает 32030, exit=сервер); реальный РФ-клиент ждёт проверки user. +index.md.
## [2026-07-12] deploy | tandemmebel.ru in-place bump 0.42.0→0.42.1 LIVE (стек 20) — поверх cutover'а того же дня (стек уже боевой, образ `ed96b18`/0.42.0). Консюмер-бамп сделан оператором сам (тот же блокер-паттерн 0.42.0 2026-07-04 — теперь решён без запроса dev-source): пин `apps/web/package.json:12` 0.42.0→0.42.1 + `yarn install` (lock snolla 0.42.1/core 0.24.1/liquid 0.10.2/data 0.14.1) + commit `0cd9351` + push origin (ls-remote confirmed). Verdaccio: 0.42.1 published (latest). Build на VDS → `registry.kzntsv.site/tandemmebel:0cd9351` (digest f29c187f). Throwaway-staging :5020 из env живого стека, healthy. Completeness-gate С VDS: **184/184 parity** (NEW==PROD, вкл. 0.42.x sitemap-реструктуризацию), единственный 404 `/articles` идентичен прод-оракулу → benign. Operator-gated PUT стека 20 (node put-stack.js, env 8/8 preserve, prune:false pullImage:true) → контейнер 0cd9351+healthy ~8s. Live-smoke GREEN (robots/home/projects/sitemap 200, TLS-серт CN не дёрнут). 0.42.1 = order-tag Drop-field fix, инертен на блог-портфолио (без e-commerce каталога). NEW concepts/tandemmebel-vds-deploy-runbook.md + UPDATE concepts/snolla-live-prod-inplace-image-bump.md (тираж 0.42.1 = 4 in-place bump-сайта) + host-stacks/vds-kzntsv/tandemmebel.compose.yml (тег+комментарий 0.42.1) + index.md. Rollback = тег ed96b18 в registry (+ b02ca18). **Тираж snolla 0.42.1 закрыт полностью.**
## [2026-07-12] deploy | tandemmebel.ru cutover LIVE — последний из тиража snolla 0.42.x. DNS reg.ru `tandemmebel.ru`+`www`→89.253.255.94 (verify ns1/ns2.reg.ru оба), traefik Host-rule staging→боевой на стеке 20 (env 8/8 preserve, prune:false, pullImage:false — образ `ed96b18`/0.42.0 не менялся, без пересборки по решению оператора). LE-серт issued on first hit: CN=tandemmebel.ru, SAN оба, until 2026-10-10. Live-smoke С VDS GREEN: key pages 200, sitemap 184 page-locs, sharp media webp 200, robots из БД. Gotham-Pro.css 0B→4436B (латентный прод-баг починен). Rollback = revert DNS→80.64.31.36 ИЛИ PUT staging-host. UPDATE entities/ruvds-iis-host (tandemmebel→🗑️ cutover VDS, биндинги оставлены как rollback) + host-stacks/vds-kzntsv/tandemmebel.compose.yml (rule боевой, заголовок live). **Тираж snolla 0.42.x полностью живой (5/5 стеков на VDS).**
## [2026-07-03] ingest | NEW concepts/snolla-admin-appdata-acl-500-after-scp-migration — разведка «почему `/admin/assets/<owner>/delete` → 500» на RUVDS IIS. Корень: app pool `IIS AppPool\snolla` (ApplicationPoolIdentity) имел на `C:\sites\snolla\App_Data` только `(RX)` после scp-миграции 23.05 → `File.Delete` в `AssetsService.DeleteAsset` кидает UnauthorizedAccessException → необработанное 500 (event log пуст, IIS substatus 0/win32 0). NRE-версии исключены проверкой строк в БД (Folders root + Files). Fix: `icacls App_Data /grant "IIS AppPool\snolla:(OI)(CI)(M)" /T` (44293 файла, verified `(I)(M)` на таргете). Класс шире delete — все Local-записи из админки (upload/кэши/галереи). Также: заменён `labtools-price.pdf` (owner d375c419) в ОБА хранилища (MinIO `assets/…` ETag→6426ddd0 + локалка IIS, MD5 сверены). Split-brain админка(Local)↔боевой app(MinIO) — cross-ref [[concepts/galleries-storage-class-local-not-s3]].
## [2026-06-29] deploy | pilorama98.ru (контейнер `pilonuxt`) `cf2bba2` LIVE (стек 16) — ADR-0010: формы переехали с monolith `@snollajs/snolla` на `@snollajs/forms-api`, snolla **дропнут целиком**, core@0.1.1 (Buffer вместо btoa). Тот же build-пайплайн (форс-регенерация клиента + save|ssh load + push с VDS). **Новый обязательный шаг — tree-check `.output/server/node_modules` ПЕРЕД перекатом** (вся сага content-api↔snolla про dual-instance, dev маскирует unmet-deps): `docker run --rm --entrypoint sh <img> -c 'find /app/.output/server/node_modules ...'` → подтвердил 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 (= btoa/snolla-дроп бандл не сломал), robots/yandex из БД (ADR-0009 не регресс), форма `POST /snolla-forms/forms?path=/checkout` пустой→422 JSON (valid НЕ слал — реальное письмо менеджеру), `/nope`→404. **Гоча smoke-харнеса:** forms-api отдаёт 422 только при `Accept: application/json` (иначе `prefersJson(req)` false → ветка `res.status(result.status||404).end()` даёт 404 — это не баг приложения, а отсутствие Accept в curl). Источник: наряд [deploy-pilonuxt-forms-api-drop-snolla]. См. [[concepts/portainer-stack-management-vds]] § Stack redeploy.
## [2026-06-29] deploy | pilorama98.ru (контейнер `pilonuxt`) `40bb383` LIVE (стек 16) — DB-backed staticPages + robots.txt (ADR-0009). Build из монорепы victor/pilorama98.ru: ключевой шаг — **форс-регенерация gitignored-клиента** `apps/web/src/generated/` (`rm -rf` + `yarn workspace nuxt-app schema-gen` против live MSSQL), иначе ensure-schema skip-if-present собрал бы старый клиент без `getStaticPage`/`getRobotsTxt` → server-500. Push с дома повторил traefik-499 на `.output`-слое даже при образе 425МБ (не multi-GB) → канонический fallback `docker save | ssh vds 'docker load'` + push с VDS (registry локален) сработал. Перекат через Portainer PUT стек 16 (`pullImage:true`, байты+UTF8 gotcha #9). Smoke acceptance 6/6 телами: robots полный из БД (Yandex Clean-param + Sitemap, не статика), yandex/google верификации 200, /nope→Nuxt-404, /+/catalog→200. Источник: наряд [deploy-pilonuxt-static-pages-robots]. См. [[concepts/portainer-stack-management-vds]] § Stack redeploy.
## [2026-06-24] ingest | entities/vdsina-outline-3xui.md (new) + sources/vdsina-outline-3xui-inventory-2026-06-24.md (new) — задокументирован ранее неизвестный VDS: VDSina Амстердам `46.151.25.64` (`v1621967.hosted-by-vdsina.ru`), «основной боевой VPN» семьи. Два стека на одной машине: Outline/Shadowbox (docker, :443 chacha20, 11 access-keys, mgmt :8080) + 3x-UI/Xray 25.10.15 (панель :50806 `/ecCqrtFVl4zl5kFjdj/`, VLESS xhttp :43666 / grpc :46743, все security=none). Ubuntu 20.04.6 (панель VDSina врёт «22»), 1 vCPU/1 GiB/30 GiB, up 17д, healthy. Секреты → `pass vdsina-outline/full-env` (host-key, root pass, Outline mgmt apiUrl+cert+11 keys, panel user+secret). Открытое: пароль панели — только bcrypt-хеш, plaintext не извлечён. Кросс-линк добавлен в [[nl-vds-3xui]] (другой NL-VDS, Aeza). +index.md (2 строки). Источник: SSH-инвентаризация 2026-06-24.
## [2026-06-18] ingest | concepts/registry-oci-image-index-gc.md (раздел real-run) — первый боевой registryGc dryRun:false на dangling-фиксе (books master-289c660, под добро юзера): 63 DELETE, 0 ошибок, 0×405 → REGISTRY_STORAGE_DELETE_ENABLED=true подтверждён живьём; 61 dangling + 2 datable снесены, реестр почищен (web 20→4 и т.д.). Побочка-урок: master у books-api/books-ops-mcp был dangling (не пересобирались ~3нед) → снесён → :master 404 (outage нет, контейнеры на локальных образах, но redeploy упрётся). Политика: не удалять named-теги master/latest даже dangling (protectRe → +master/latest); проверить keep/drop digest-коллизию. Источник: real run + post-state probe books-* :master.
## [2026-06-18] ingest | concepts/registry-oci-image-index-gc.md (раздел dangling-индексы) — перепрогон registryGc dryRun на descent-фиксе (master-dcd7c91) вскрыл 2-й root-cause: реестр засорён dangling image-индексами (тег жив, платформенный sub-manifest отдаёт MANIFEST_UNKNOWN — вычищен прежним host-side `registry garbage-collect`, не следящим index→child для multi-arch). books-web: 19 тегов → 3 датируемых, 16 dangling. date-GC защищает их как null-dated → не удаляет, хотя они и есть мусор (логика задом наперёд). Рекомендация books: различать transient-error (protect) vs MANIFEST_UNKNOWN (eligible). freedBytes от dangling ~0 (слои уже вычищены). +index.md hook. Источник: dryRun #2 + полный manifest-обход books-web 2026-06-18.
## [2026-06-18] ingest | concepts/registry-oci-image-index-gc.md (new) + concepts/bindmount-config-edit-preserve-mode.md (расширен) — следствие закрытия [books-task-runner-registry-auth-cred]. (1) Новый концепт: books-* образы в registry = OCI image-index (buildx), top-level `.config`=null, дата `.created` в платформенном sub-manifest → наивный GC по top-level дате даёт null у всех → планировщик защищает null-dated группы → drop=0 всегда (keepLastN не применяется). Правила: Accept со всеми media-types, дата из sub, DELETE по index-digest (не sub), 1 версия=1 index+attestation. Зафиксировано на books registryGc dryRun (deleted=0 при 14 tags). (2) bindmount-постмортем дополнен: bind-mount затеняет config-каталог образа целиком → нужна полная кред-секция, не дельта; + worked example task-runner registry-секция (mode не слетел, постмортем сработал). +index.md (2 строки). Источник: dryRun-верификация registryGc + манифест-dump books-web 2026-06-18.
## [2026-06-18] ingest | concepts/registry-kzntsv-auth-model.md (new) + concepts/bindmount-config-edit-preserve-mode.md (new) — registry.kzntsv.site = standalone registry:2 + htpasswd Basic (бинарный доступ, НЕ Gitea-packages; нет per-repo ACL/robot-токенов; hot-reload htpasswd; GC через v2 DELETE + host garbage-collect; завёл юзера books-ci для books job-scheduler pull, кред в pass vds-kzntsv/registry-books-ci). Постмортем: правка bind-mounted `default.json` через mktemp+mv уронила режим 644→600 → `books-job-scheduler` EACCES crash-loop (прод-даун slovo-cron), fix chmod/chown --reference=backup. +entities/vds-kzntsv.md (registry-строка) +index.md. Источник: консультация+инфра-деплой books registry-auth + инцидент 2026-06-18.
## [2026-06-17] ingest | concepts/stostayer-web-deploy-runbook.md (new) — ранбук деплоя легаси web на прод stostayer: канал build(offline)→push docker.stostayer.ru→Portainer-стек 16 через container-IP API (172.18.0.2:9000) с хоста мимо Angie BA; rollback; БЛОКЕР ESM-стена (top-level await @stostayer/data на build + ERR_REQUIRE_ESM @snollajs/snolla в рантайме d02f740) → 0.3.18 остаётся; гоча VPN-IP бан хостером при retry-push-шторме. Источник: попытка деплоя `stostayer-web-complaint-form-deploy` 2026-06-17 (откат на 0.3.18). +index.md.
## [2026-06-17] update | entities/windows-recovery-host.md — уточнён live-state двух stostayer-IIS-сайтов: добавлена таблица порт→connection string→БД (`stostayer` :8090→`www.stostayer.ru,1433` внешний прод + S3 minio.stostayer.ru; `stostayer.old` :8091→`mssql.kzntsv.site,1433` наш VDS), путь админок `/admin`, live-проверка 2026-06-17 (W3SVC Running, :8090/:8091 LISTENING, :80 нет). Пофикшена стейловая строка в «Ключевые папки» (stostayer.old conn localhost→mssql.kzntsv.site). Источник: live-аудит IIS на DESKTOP-NSEF0UK.
## [2026-06-17] update | concepts/portainer-stack-management-vds.md +раздел «Stack redeploy (новый image tag)» (JWT→GET file байтами→подмена тега→PUT pullImage:true, env=[] плейсхолдером, тело UTF-8) + gotcha #9 (PS 5.1 Invoke-RestMethod декодит /file как ISO-8859-1 → кириллические compose-комментарии mojibake → PUT падает `yaml: could not find expected ':'`; fix = байты+UTF8) + #10 (пустой env схлопывается). Источник: redeploy-pilonuxt-gsc-structured-data (19a4a84 в проде, stack 16).
## [2026-06-14] update | concepts/minio-imgproxy-on-vds.md +раздел «S3 access для клиентских приложений» (lookup: endpoint `https://minio.kzntsv.site` / сырой `89.253.255.133:9000` / inter `http://minio:9000`, root accessKey `AKIAJ...` + secret в `pass minio-vds/full-env`, форма ключа, ssl/region/pathStyle). Закрывает класс-вопрос «дай MinIO endpoint+креды» БЕЗ SSH. Прецедент: inbox snolla 2026-06-14 (raw-stream content-api), ответ отправлен.
## [2026-06-11] ingest | verdaccio-restore-packument-desync — postmortem: disaster-restore вернул тарболлы но древний/пустой packument → publish свежей версии EEXISTS 409. Fix: снять только коллизирующий целевой .tgz (бэкап first), republish; НЕ rm -rf каталог (убьёт locally-published историю). Применено к @snollajs/{data,mailer,numbering,content-api}.
## [2026-06-11] ingest | verdaccio-token-lifecycle — restart trap (ephemeral secret → 401), max_users:-1 + pnpm 409 постмортем, JWT config fix, yarn classic vs berry token refresh recipe.
## [2026-06-11] ingest | yarn-npm-minimal-age-gate — Yarn ≥4.16 client-side supply-chain gate: версии <24ч → YN0016 «quarantined». Независим от auth (gate #2 после 401). default 1d, читается глобально (per-scope не работает), fix npmMinimalAgeGate:0 / YARN_NPM_MINIMAL_AGE_GATE=0. Диагностировано с pilorama98.ru (контейнер `pilonuxt`); опровергнута первичная гипотеза «серверный карантин verdaccio».
## [2026-06-11] update | MSSQL backup gap closed — `/opt/stacks/backup/scripts/run.sh` расширен: 5 prod DBs через `docker exec mssql sqlcmd BACKUP DATABASE ... WITH COPY_ONLY, INIT` (Express, без COMPRESSION); .bak в bind-mount → mv в DUMP_DIR → rsync kreknin. Обновлён [[concepts/mssql-on-vds]] (TODO→реализовано).
## [2026-06-11] lint | 10 issues found, 8 fixed inline
- 🔴 recovery-architecture-snapshot.md: добавлена пометка ИСТОРИЧЕСКАЯ ЗАПИСЬ (рерайт текущей архитектуры отложен — большая задача)
- 🔴 snolla-recovery-vm.md: помечена УДАЛЕНА 2026-06-08, updated date исправлен
- 🟡 ruvds-iis-host.md: зачёркнуты 2 закрытых риска (imgproxy SPOF снят, LE renewal закрыт); добавлена ссылка на winacme; updated 2026-06-11
- 🟡 future-resilient-architecture-goals.md: dead task link cms-stopgap-backup-daily заменён на текстовое примечание
- 🟡 mssql-on-vds.md: исправлен frontmatter (status→type, добавлен updated)
- 🟡 vds-kzntsv.md: добавлен MSSQL в software stack table
- 🟡 orphan winacme-iis-owin-catchall-http01: входящая ссылка добавлена из ruvds-iis-host
- ✅ ДОФИКСИРОВАНО: 3 broken links в recovery-architecture-snapshot убиты; TODO mssql backup integration закрыт (реализован)
- ⚪ НЕ ФИКСИРОВАНО: orphan mssql-container-data-restore — оставлен
## [2026-06-08] update | entities/windows-recovery-host — декоммишн после Synology-recovery: снесены traefik/mssql/minio/imgproxy/es + 21 husk-стек + VM snolla-recovery (95ГБ) + C:\nas-recovery (~150ГБ) + IIS-сайт snolla + C:\sites\snolla + локальный MSSQL. Осталось: IIS stostayer/stostayer.old (репойнт на mssql.kzntsv.site, orphan-fix), lightrag, markitdown-MCP, mutable-dev VM. C: free 62→275ГБ (+~176ГБ docker pending vhdx-compact). image-pipeline SPOF снят. Источник: `.tasks/decommission-windows-recovery-host.md`.
## [2026-06-08] ingest | concepts/morethencms-null-settingsdata-https-502 — RCA+fix: `maljarka.tandemmebel.ru` отдавал 502 только на HTTPS (HTTP=200). Корень = `dbo.Sites.SettingsData=NULL` у тенанта maljarka (нет блока `httpSecure`) → `KeyNotFoundException` в `SnollaMiddleware`/`Owin.ErrorHandler` на HTTPS-ветке. Fix = `UPDATE dbo.Sites SET SettingsData='{httpSecure:...}'` + `Restart-WebAppPool snolla`; verified 443→200 server-local + external. Audit: maljarka — единственный NULL-сайт с :443-биндингом; rimiz degraded по другой причине. Обновлён [[../entities/ruvds-iis-host]]. Источник: live-диагностика 2026-06-08.
## [2026-06-05] ingest | concepts/winacme-iis-owin-catchall-http01 — постоянный self-renewing LE pipeline на RUVDS IIS (win-acme v2.2.9, 25-SAN cert, SYSTEM scheduled task). Главное: OWIN-catch-all CMS жрёт `/.well-known/acme-challenge/` → решено отдельным IIS-приложением в пуле «No Managed Code» + патч шаблона `Web_Config.xml` (snять Owin-handler). Заменяет ручной [[traefik-acme-json-to-iis-cert-import]] для renewal'а; снимает зависимость RUVDS от домашнего traefik. Закрывает дедлайн cert-expiry 2026-07-22 (новый cert до 2026-09-03). Источник: `.tasks/iis-migration-to-ruvds.md` Decisions log 2026-06-05.
## [2026-05-28] ingest | concepts/es-destructive-delete-incident-2026-05-26 — RCA для удаления 3 user-индексов на canonical ES `elasticsearch.kzntsv.site` (books VDS stack 33) во время cutover-prep. Caller identity unrecoverable (audit log = X-Pack платный, traefik accessLog был выключен). 2 preventive controls applied + verified: ES env `action.destructive_requires_name=true` + traefik JSON accessLog. Источник: `.tasks/restore-elasticsearch-indices-books-vds.md` § Closure note.
## 2026-05-21
- bootstrap: empty wiki skeleton (CLAUDE.md, index.md, log.md, overview.md, raw/README.md) committed
## [2026-05-21] ingest | concepts/admin-infra-project
## [2026-05-21] seed | CLAUDE.md Domain conventions — design-context pointers (task: admin-infra-project-pointers)
## [2026-05-21] migrate | subtree-import 6 entities + 17 concepts + 3 sources from MoreThenCms (history preserved via subtree-split + temp-prefix merge)
## [2026-05-21] regen | index.md catalog refresh after subtree import
## [2026-05-21] ingest | concepts/ocis-on-vds-deploy-recipe — oCIS deploy recipe + gotchas (UID mismatch, PROXY_TLS, PROXY_ENABLE_BASIC_AUTH, LibreGraph user-create); updated entities/vds-kzntsv (added owncloud row to stack/hostnames/file-layout); source: .tasks/owncloud-vds-deploy.md
## [2026-05-22] update | concepts/ocis-on-vds-deploy-recipe — Gotcha 5: 60s HTTP timeout caps slow-uplink uploads at ~200MB on 3 MiB/s link; traefik buffering NOT the fix (500 from oxy buffer); workaround = VDS-side curl PUT loopback via throwaway sftp key. Source: .tasks/owncloud-vds-deploy.md close-note.
## [2026-05-23] ingest | concepts/windows-server-2025-core-bootstrap — default-blockers (SMB closed, IIS/URL-Rewrite/.NET unverified) + transfer-методов матрица (RDP-redirect / SMB / WinRM / SFTP); recommendation = SMB inbound с source-IP whitelist на время migration. Source: failed-robocopy инцидент 2026-05-23 18:08 (.tasks/iis-migration-to-ruvds.md Decisions log).
## [2026-05-24] ingest | concepts/books-ssh-access — SSH access audit на shared VDS pre-cutover Фазы 3 tenant-split. Retained keys table (1 key, vitya core dev), removed cosmetic dead root key, sshd hardening verified via `sshd -T` (not raw config), fail2ban active 2670 failed/37 banned, only vitya@94.19.247.14 в access log last 7 days. Source: .tasks/books-ssh-audit-shared-vds.md.
## [2026-05-24] ingest | RUVDS IIS migration + backup pipeline — entities/ruvds-iis-host (NEW, 80.64.31.36 Win Server 2025 Core, 25 SNI bindings, 2/24 hostnames DNS-flipped) + sources/iis-migration-to-ruvds-2026-05-23 (NEW, SSH/scp pivot после SMB-block by home-ISP) + sources/ruvds-backup-daily-kreknin-2026-05-24 (NEW, rclone+SFTP SYSTEM task daily 04:30) + concepts/traefik-acme-json-to-iis-cert-import (NEW, PFX+SNI recipe) + UPDATE concepts/windows-server-2025-core-bootstrap (SMB deprecate, HTTP middlebox warning, HTTP/2 note, backup-strategy + cert-import закрыты) + UPDATE entities/windows-recovery-host (IIS partial-cutover state, imgproxy SPOF carve-out) + UPDATE overview (RUVDS line). Sources: .tasks/iis-migration-to-ruvds.md, .tasks/ruvds-backup-daily-kreknin.md.
## [2026-05-28] ingest | vds-kzntsv DHCP outage postmortem — concepts/vds-kzntsv-dhcp-outage-2026-05-28 (NEW, симптомокартина + диагностический алгоритм + recovery runbook + RCA) + sources/vds-kzntsv-incident-2026-05-28 (NEW, timeline 05:25 backup OK → 08:15 detect → 08:43 statics fix → 08:50 disk GC; ticket text) + UPDATE entities/vds-kzntsv (Доступ §+subnet/gw/hypervisor/DNS resolvers; pass-store вместо .common/secrets; Known issues §NEW with 2026-05-28 incident; Open issues bump GC priority + iputils-ping note) + UPDATE concepts/rusonyx-vps-onboarding-quirks (quirk #9 NEW — gw в /18 надсети + recovery commands). Live session, no raw source ingested.
## [2026-05-28] update | vds-kzntsv DHCP outage post-resolution — после reset хостером в 13:52 MSK выяснилось что root cause — конфликт двух сетевых стеков (netplan+networkd поверх ожидаемого provider's ifupdown). Их `start/ipadd` ожидает чистый ifupdown, не мог auto-recover при разрыве DHCP binding. Resolution: mask netplan+systemd-networkd, reboot, provider положил `/etc/network/interfaces.d/ifcfg-eth0` с /18 netmask. UPDATE concepts/vds-kzntsv-dhcp-outage-2026-05-28 (revised RCA + permanent-fix § + anti-pattern + revised lessons-learned) + UPDATE sources/vds-kzntsv-incident-2026-05-28 (timeline до 14:00 + final config) + UPDATE entities/vds-kzntsv (mask /18, ifupdown stack, kernel cmdline net.ifnames=0) + UPDATE concepts/rusonyx-vps-onboarding-quirks (quirk #9 переписан про /18, quirk #10 NEW про ifupdown vs netplan stack). Memory `vds-kzntsv-rusonyx-network-recovery` переписан с новыми фактами.
## [2026-05-29] update | ES destructive-delete RECURRENCE — true root cause найден, диагноз сменился. UPDATE concepts/es-destructive-delete-incident-2026-05-26 (correction-блок + Hypothesis помечена ОПРОВЕРГНУТА + новая секция «Рецидив 2026-05-29 — true root cause»: ransom-бот через открытый `0.0.0.0:9200` мимо traefik+basicAuth, ES 7.10 free без auth; `read_me`=BTC-выкуп; снос by-name мимо Control #1; accessLog пуст т.к. бот шёл прямо в :9200; firewalld bypass docker-publish; Control #3 = убрать публикацию host-порта via Portainer PUT stack 33; restore из daily-2026-05-25; exposure-audit таблица — mongo/books-db/bookva-db/minio/bookva-minio тоже exposed но credentialed). bookva не пострадал (bookva-es порт не публикует). Live incident response session.
## [2026-06-05] ingest | NL VDS 3x-UI node + Reality PQ×dest root-cause
- new entities/nl-vds-3xui.md (213.176.64.253, Ubuntu 24.04, 3x-UI 3.2.7 / Xray 26.6.1); creds -> pass nl-vds-3xui/full-env
- new concepts/reality-pq-mldsa65-dest-incompatibility.md (root cause: ML-DSA-65 PQ ClientHello X25519MLKEM768 + Akamai dest www.intel.com -> HRR -> handshake fail; fix = PQ-capable dest e.g. www.microsoft.com, verified via isolation matrix)
- index.md updated (entity + concept). Fix NOT applied — awaiting user command.
## [2026-06-05] ingest | NL VDS 3x-UI session — honest final state + lessons
- NEW sources/nl-vds-3xui-setup-2026-06-05.md (chronicle: Reality PQ fix, port 443→2053, mtg MTProto, SOCKS5, friend-client instr; HONEST outcome — у реальных клиентов из РФ работает только plain VLESS 32030)
- NEW concepts/proxy-debugging-test-the-real-client.md (anti-pattern: свои curl/standalone-тесты ≠ боевой клиент; overclaim «РКН режет 443» / MSS-clamp по MTU-догадке сломал коннект)
- REWROTE entities/nl-vds-3xui.md — убраны ложные «Reality verified/fixed», «mtg неотличим»; честная таблица статусов (32030 🟢, Reality 2053 / MTProto 8443 / SOCKS 47020 🔴 у клиентов); MSS-clamp снят
- UPDATE concepts/reality-pq-mldsa65-dest-incompatibility.md — caveat «снятие PQ ≠ рабочий Reality у GUI-клиентов» + source link
- index.md updated (2 new pages). Креды/ссылки — pass nl-vds-3xui/full-env.
## [2026-06-06] decision | nl-vds-3xui: +3 classic Shadowsocks inbounds (ports 32031/32/33) for Outline-app, per-key revocable; protocol e2e-tested OK, RF reachability unverified (SS DPI-blocked in RF); creds in pass; discovered undocumented VLESS:13027.
## [2026-06-10] ingest | concepts/mcp-init-resilience
## [2026-06-12] migrate | galleries pilorama98 → S3. NEW concepts/galleries-storage-class-local-not-s3.md (root: storageClient `galleries` = Local disk class, never in S3; snolla ждёт s3://galleries/<siteId>/<file> → 404). Путь A: bucket `galleries` создан, 301 файл (77 MiB) с RUVDS IIS залиты verbatim, imgproxy 200 verified с books-vds. UPDATE concepts/minio-imgproxy-on-vds.md (stale-баннер: pipeline на books-vds с 08.06, не windows-host). Task migrate-gallery-originals-to-s3 🟢.
## [2026-06-27] refactor | books-vds: документирован публичный bookva-db endpoint :33306 (+ slovo/bookva DB-exposure, mongo/ES internal) в § Доступ; закрыт wiki-drift по порту
## [2026-07-05] decision | тираж snolla 0.42.1 (live-prod in-place bumps). NEW concepts/snolla-live-prod-inplace-image-bump.md (переиспользуемый рецепт build→staging-acceptance-С-VDS→env-preserving Portainer PUT `put-stack.js`→live-smoke). UPDATE 3 рунбука секцией «0.42.1 in-place bump»: labtools-vds-deploy-runbook (стек 17, `labtools:566d41c`), emspb-vds-deploy-runbook (18, `emspb:95a5c42`), labtools.pro-vds-deploy-runbook (19, `labtools-pro:0610432`). FIX orphan: все 3 рунбука добавлены в index.md (не были каталогизированы). Tasks `[labtools-ru|emspb|labtools-pro-deploy-snolla-0-42-1]` 🟢.
## [2026-07-24] ingest | concepts/stostayer-markdown-render-404-incident-2026-07.md — постмортем инцидента 404/500 на body_repair_service (stostayer.ru). Двойной корень: (1) код-баг snolla `import markdown from 'markdown-it'; markdown();` → инстанс выброшен, `markdown.render` undefined, краш на markdown-поле-шаблонах (латентный ~3 мес, с коммита 1e3f897 2026-04-21); (2) exact-pin-staleness — `@snollajs/content-api@0.8.0` `workspace:*` запёк `@snollajs/snolla: 0.7.4` exact → фиксовая 0.7.7 легла top-level, content-api юзал nested баговую 0.7.4. Фикс: snolla 0.7.7 + core 0.26.1 (код) + content-api 0.8.2 `^0.7.7` (упаковка). Деплой stostayer-web:0.3.22 через pscp SSH:20435 + sudo docker load + Portainer PUT pullImage:false (без push в :443, бана нет). Pre-build чек yarn.lock single-snolla ловит класс. index.md updated (+1). Cleanup `workspace-caret-interpackage-deps` — vitya scope. Memory: stostayer-deploy-image-via-pscp-load-avoid-443-push.md.
## [2026-08-13] refactor | concepts/stostayer-web-deploy-runbook — letter path `.claude-inbox` → `.agents/inbox` (rename idea 13 cascade)
## [2026-08-18] ingest | NEW concepts/sched-fnf-r8-verification-stand.md — стенд верификации F&F r8 (schedule-as-entity): путь, версии core 0.40.1/storage-* 0.3.1, карта 15 репро-скриптов (миграции/движок/политики), подтверждённый контракт, как продолжить №4/№5. NEW concepts/windows-docker-test-harness-gotchas.md — переиспользуемые грабли Windows-верификации (rmSync-EBUSY, vitest-DDL-таймаут, монотонность фейк-часов, изоляция DB, висельники node). index.md updated (+2).
## [2026-08-18] ingest | UPDATE concepts/sched-fnf-r8-verification-stand.md — сессия №4/№5: admin-api-test.mjs (52/52, F1 tz-hoist + F3 PATCH-merge подтверждены на core 0.40.2), UI-стенд ui-backend.mjs + sched-ui serve :8081 + браузерный CDP (13/13), находки U1 (форма-tz interval/once → 400) и U2 (таблица шире контейнера ~58px). index.md hook обновлён.
## [2026-08-18] ingest | NEW concepts/nvm-junction-ismain-gate-gotcha.md — ESM isMain-гейт молча no-op под junction-префиксом npm-рута (C:\nvm4w\nodejs → realpath …\nvm\v22.22.0): import.meta.url резолвится в realpath, argv[1] держит литеральный → main() не выполняется, exit 0. Бьёт global-install под любым version-manager'ом (nvm/mise/volta). Фикс realpath-сравнения (sched bd38aab, daemon 0.3.2). NEW concepts/nvm-windows-node-switch.md — Node 22→24.19.0 рецепт: junction-свап, per-version глобалы, PATH-кэш shell'а, npm 11 allow-scripts. Оба из сессии CLI-F&F sched 2026-08-18. index.md updated (+2).
## [2026-08-18] ingest | NEW concepts/openapi-generator-3-1-spec-gotchas.md — из F&F openapi-client sched (2026-08-18): генерация Python-клиента из 3.1-спеки openapi-generator 7.24.0. Гочи: (1) ответы `{}` в спеке + wrapped-тело ({task/run/schedule:…}) → pydantic ValidationError, фикс — реальные схемы ответов, обход raw-транспорт + by_alias=True; (2) `const:true` → строковый enum ('true') ломает boolean; (3) oneOf+discriminator → actual_instance=None, контент только через raw JSON; Java 11+ обязателен (системная 8 падает), Windows excluded-порты 8090/8091 (EACCES), консоль cp1251 без →/«». Полные находки F-1..F-5: sched/.agents/inbox репорт 19:15Z; пример sched/examples/admin-client-python (24/24). index.md updated.
## [2026-08-18] ingest | NEW concepts/sched-admin-client-openapi.md — F&F раунд openapi-claim sched (по предписанию таски): Python-клиент одной командой (openapi-generator 7.24.0 из спеки verdaccio 0.1.2), сьют 24/24 + smoke 19/19 против published daemon 0.3.4 (:8127), вердикт «claim подтверждён с оговорками» (8 операций требуют raw-обхода). Код sched/examples/admin-client-python (e89e868), репорт — sched инбокс 19:15Z, находки F-1..F-5 (спеку не чинил). Таска [sched-admin-client-openapi] 🟢. Гочи генерации — concepts/openapi-generator-3-1-spec-gotchas.md. index.md updated.
## [2026-08-18] ingest | UPDATE concepts/openapi-generator-3-1-spec-gotchas.md + concepts/sched-admin-client-openapi.md — round 2 ретест (0.2.0/daemon 0.3.5): все 5 находок F-1..F-5 закрыты, raw_ops.py удалён, 11/11 ранее-сырых операций typed-only, сьют 24/24 + smoke 19/19 (коммит sched d658ff0, репорт sched инбокс 22:55Z). Новые грабли в окружение: CLI спавнит java из PATH а не JAVA_HOME; wrapped-ответы в components.responses → InlineObjectN; info.version не бьётся с версией пакета. index.md hooks обновлены.
## [2026-08-22] ingest | NEW concepts/omniroute-combo-deepseek-web-tuning.md + sources/omniroute-session-2026-08-22.md — сессия настройки web-комбо omniroute (deepseek-v4-flash/pro-web). Корни вылетов агента: (1) system_message override режет скилы pi, (2) tool_filter_regex ^(bash|computer)$ режет 337/339 тулов, (3) search-модель генерит DEEP_SEARCH-мусор + «Link reading unavailable», (4) quality validation режет стрим на type:error, (5) 503 chat_admission_busy = capacity веб-чата. Фиксы применены: system_message="", regex расширен (базовые+mcp__*), flash think-search→think, maxRetries 2→4, retryDelayMs→4000. Эмуляция tool-calling проверена тестами — исправна. План кастомного провайдера (вытащить executor+translator, без quality validation, persistSession). index.md updated (+2).
## [2026-08-22] ingest | UPDATE concepts/omniroute-combo-deepseek-web-tuning.md — qwen-web стена baxia-WAF (подзадача llm-web-proxy-qwen-web): FAIL_SYS_USER_VALIDATE/RGV587 punish-капча на любой не-браузерный запрос (Node-fetch все варианты заголовков + свежий cookie-jar; даже Chrome-CDP chats/new проходит, completion виснет). Капча привязана к device-фингерпринту (bx-umidtoken из sg-wum.alibaba.com), не к cookie. Зацепка: в контейнере omniroute рабочая версия executors/qwen-web.ts ≠ GitHub main. Подзадача 🟡 paused. index.md hook обновлён.
## [2026-08-22] ingest | NEW concepts/forgetmeai-free-web-chat-proxies.md + raw/forgetmeai-repo-analysis-2026-08-22.md — анализ аккаунта ForgetMeAI (8 репо, все — бесплатные web-chat прокси, та же ниша lwp). Главное: (1) паттерн «не решать капчу, а обходить реальной браузерной сессией» (Chrome-расширение-экспортёр кук/localStorage, cookie export, persistent-профиль); (2) PoW-солвер DeepSeek (lib/pow.js, WASM compile-cache) — в lwp уже портирован; (3) qwen через baxia-WAF работает ТОЛЬКО браузерным путём: fetch внутри page.evaluate (FreeQwenApi) — настоящий umidtoken+canvas-фингерпринт; наш вывод подтверждён; (4) CloakBrowser-заметки (патченый Chromium, humanize, persistent-профили, geoip), классификаторы капчи (Aliyun WAF/FRONTEND_CAPTCHA_REQUIRED/punish/purecaptcha). Референс-код закоммичен в lwp docs/reference/forgetmeai/. index.md updated (+1).

View File

@@ -1,26 +1,3 @@
# admin — infrastructure agenda # ⛔ Файловый канал закрыт
Operational + roadmap workspace for personal-and-client infrastructure stack: **Не читать. Не править.** Канон — mappa (`mcp__mappa__*`): wiki-сущности проекта, конвенции — AGENTS-сущность. Скил: `mappa-knowledge`.
- **VDS** (`vds.kzntsv.site`, Rusonyx) — gitea, verdaccio, registry, shared DB park, ntfy, traefik+portainer
- **RUVDS IIS host** (`80.64.31.36`, Win Server 2025 Core) — CMS multi-tenant IIS site `snolla` (catch-all для 24+ hostnames), partial-cutover idёт 2026-05-24
- **NAS** (`kreknin`, Synology DSM) — backup target, Hyper Backup repo, secondary services
- **Windows recovery host** — раньше CMS prod IIS (мигрирует на RUVDS), всё ещё хостит imgproxy + `tandemmebel.ru` + parallel running docker/traefik как rollback
- **OpenWRT router** — NAT 80/443 → traefik (на windows-recovery-host)
## Current state
See `concepts/recovery-architecture-snapshot.md` (migrated 2026-05-21 from `MoreThenCms`).
## Goal
Build fault-tolerant infrastructure so the 2026-05-18 SMR-cascade incident does not repeat. Roadmap design in progress — see `concepts/future-resilient-architecture-goals.md` (migrated 2026-05-21) and active task `[resilience-roadmap-design]`.
## Related repos (out of admin)
- `OpeItcLoc03/vds-ops-mcp` — read-only docker ops MCP for VDS
- `OpeItcLoc03/synology-ops-mcp` — same for NAS
- `OpeItcLoc03/projects-meta-mcp` — cross-project state aggregator
- `OpeItcLoc03/agenda` — cross-project task meta-board
- `OpeItcLoc03/projects-wiki` — shared knowledge wiki
- `OpeItcLoc03/claude-skills` — global skill registry

View File

@@ -51,7 +51,7 @@ VNC консоль изначально не открывалась. Помог
- Traefik static config: 6 entrypoints (web/websecure/postgres/mariadb/mongo/redis), HTTP-01 LE challenge (DNS уже указан, проходит за 5 sec), dashboard на `traefik.vds.kzntsv.site` + basicAuth middleware из dynamic file provider. - Traefik static config: 6 entrypoints (web/websecure/postgres/mariadb/mongo/redis), HTTP-01 LE challenge (DNS уже указан, проходит за 5 sec), dashboard на `traefik.vds.kzntsv.site` + basicAuth middleware из dynamic file provider.
- LE issued cert at first request (5 validators с разных AWS regions → 200 OK). - LE issued cert at first request (5 validators с разных AWS regions → 200 OK).
**Portainer admin init — 4 итерации.** Hard min 12-char policy в Portainer 2.20+ (regression от 2.20.0+), CLI flag `--admin-password` + bcrypt **не bypass'ит** policy и фактически выходит broken (bcrypt сохраняется, но login fails). После проб с `$2y$`/`$2a$` prefix, single-quote escape, YAML list form, docker run direct — оказалось CLI flag тупо не работает в 2.21.5. Финал: голый `docker run`, без `--admin-password`, потом API admin/init с длинным паролем `Pryakhin9-VDS-2026` (18 chars). Деталь — [`portainer-2.21-admin-password-regression`](../concepts/portainer-2.21-admin-password-regression.md). **Portainer admin init — 4 итерации.** Hard min 12-char policy в Portainer 2.20+ (regression от 2.20.0+), CLI flag `--admin-password` + bcrypt **не bypass'ит** policy и фактически выходит broken (bcrypt сохраняется, но login fails). После проб с `$2y$`/`$2a$` prefix, single-quote escape, YAML list form, docker run direct — оказалось CLI flag тупо не работает в 2.21.5. Финал: голый `docker run`, без `--admin-password`, потом API admin/init с длинным паролем `<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>` (18 chars). Деталь — [`portainer-2.21-admin-password-regression`](../concepts/portainer-2.21-admin-password-regression.md).
API key сгенерирован через `/api/users/<id>/tokens`, local docker endpoint создан через `POST /api/endpoints` form-data. Сохранён в `vds-kzntsv.env`. API key сгенерирован через `/api/users/<id>/tokens`, local docker endpoint создан через `POST /api/endpoints` form-data. Сохранён в `vds-kzntsv.env`.
@@ -112,7 +112,7 @@ Pipeline:
- **Hermes defer** — user explicit. - **Hermes defer** — user explicit.
- **Registry — fresh install, no migration** — user mid-flight reversal (могу новых наделать). - **Registry — fresh install, no migration** — user mid-flight reversal (могу новых наделать).
- **`HostSNI(*)` + no tls.* для всех DBs** — uniform config работает для всех protocols (STARTTLS + TLS-from-start). - **`HostSNI(*)` + no tls.* для всех DBs** — uniform config работает для всех protocols (STARTTLS + TLS-from-start).
- **Portainer admin pass `Pryakhin9-VDS-2026` (18 chars)** вместо запрошенного `Pryakhin9` (9 chars) — Portainer 2.21+ hard min 12-char policy, `--admin-password` CLI flag broken в 2.20+. Сохранено в `vds-kzntsv.env`. - **Portainer admin pass `<PORTAINER_ADMIN_PASS: pass vds-kzntsv/full-env>` (18 chars)** вместо запрошенного `Pryakhin9` (9 chars) — Portainer 2.21+ hard min 12-char policy, `--admin-password` CLI flag broken в 2.20+. Сохранено в `vds-kzntsv.env`.
## Архитектурное замечание ## Архитектурное замечание

View File

@@ -1,4 +1,7 @@
# AGENTS.md (canon) # AGENTS.md (.admin) — указатель на канон
**Канон — mappa wiki-сущность `AGENTS` (.admin, wiki:2608).** Читать её через `wiki_get('.admin','AGENTS')`. Этот файл — только триггеры скилов для pi (грузит из cwd) + указатель. Содержимое правил — в wiki:2608, не дублировать здесь.
# Agent instructions. Each line is a trigger for an installed skill. # Agent instructions. Each line is a trigger for an installed skill.
talk like a caveman talk like a caveman
@@ -7,18 +10,31 @@ use task management system
check across all projects check across all projects
pull remote before work pull remote before work
session handoff: read on start, write on end session handoff: read on start, write on end
inbox monitor: raise on start
follow project discipline follow project discipline
delegate to interns when allowed delegate to interns when allowed
recommend, don't menu recommend, don't menu
admin runbooks: use index before any prod op, execute by runbook, write/update runbooks
admin artifacts: project skills in .agents/skills/, runbooks in mappa wiki, scripts in .tmp/ or scripts/
we're on Windows we're on Windows
# Secrets rule # Session live-ingest
Все креды (SSH, БД, panel, BA) лежат в `pass` (password-store). **Перед поиском доступов — `pass ls` / `pass show <path>`, а не grep по вики или `~/.ssh/config`.** `session sync: write to mappa` — opt-in для pi session-sync / CC-хука (w:2604): live-ingest сессий в mappa. Без opt-in сессии не пишутся в mappa (диагноз 2026-08-29: opt-in неполный → мусор mappa.ts:404 + live-ingest не поднят).
Серверы СТО Стайер: `stostayer/client` (`new.stostayer.ru:20435` — машина клиента, файлы 1С в `/var/from_1c/`), `stostayer/rusonyx`, `stostayer/client-wireguard`.
# VDS ops rule <!-- mappa:canon-block (auto; do not edit) -->
## Mappa canon
Все docker-compose stacks на VDS управляются через Portainer (`https://portainer.vds.kzntsv.site`). **Gates (canon/gates — shared):** краткая суть; полный текст — `wiki_get(slug='canon/gates', full=true)`
`ssh + docker compose up -d` на VDS — anti-pattern. См. [`portainer-stack-management-vds`](.wiki/concepts/portainer-stack-management-vds.md) для migration script + gotchas. - Г1. Знание и артефакты → mappa, не файлы — durable-знание и артефакты проекта живут в mappa; файловые каналы (`.brainstorm/`, `.tasks/`, `.wiki/`) и «сохранить рядом с проектом» закрыты.
Исключения: `traefik` + `portainer` (management plane, ad-hoc compose). - Г2. Контракт каналов — письмо (inbox) — носитель вердиктов/указаний/находок, полным телом; комментарии на тасках — короткий след. Адресация `about`/`to`/`thread` (XOR); lifecycle `ack`/`resolve`/`cancel`.
- Г3. Поиск до угадывания — не угадывать slug/роут: сначала `search`/`wiki_search`; `wiki_get` без project = shared; общая память — `search(mode='recall')`.
- Г4. Адресация и слаги — рефы полными именами (`[[task:N]]`/`[[wiki:slug]]`); слаги kebab-case, латиница; номера `task:N` выдаёт сервер.
- Г5. .mappa-гейт — папка участвует в mappa-операциях только с маркером `.mappa`; нет маркера → сказать человеку, мутации — отказ.
- Г6. Секреты — в mappa не пишутся (422); только `secret:<path>`-рефы, значения мимо.
- Г7. Degraded-режим — mappa недоступна: читать кэш `.mappa/` (canon/methodology/runbooks), мутации → `.mappa/pending/`; нет кэша → стоп, не импровизировать.
- Г8. Перед работой с вики/каноном — первым действием прочитать канон-блок AGENTS.md проекта.
- Г9. Живое состояние до заявления — статус заявлять только по свежему чтению mappa, не по памяти/кэшу/ответу create.
**Entity → runbook (runbooks/index — shared):** task → [[runbooks/tasks]] · wiki → [[runbooks/wiki]] · inbox → [[runbooks/inbox]] · **thread** → [[runbooks/threads]] · session → [[runbooks/session]] · search → [[runbooks/search]] · issue → [[runbooks/issue]] · **intent** → [[runbooks/intent]] · requirements → [[runbooks/requirements]] · plan → [[runbooks/plan]] · comment → [[runbooks/comment]] · tag → [[runbooks/tag]] · attachment → [[runbooks/attachment]] · release → [[runbooks/release]] · brainstorm → [[runbooks/brainstorm]] · agent → [[runbooks/agent-operator]] · repo → [[runbooks/repo-commit]] · project → [[runbooks/project]] · skill → [[runbooks/skill]] · entity-слой → [[runbooks/entity]] · sched → [[runbooks/sched-telemetry]]
**Methodology:** `methodology/kzntsv`
**Canon version:** 4
<!-- /mappa:canon-block -->

View File

@@ -1,18 +0,0 @@
node:internal/modules/cjs/loader:1386
throw err;
^
Error: Cannot find module 'C:\Users\vitya\projects\.admin\daemon3.js'
at Function._resolveFilename (node:internal/modules/cjs/loader:1383:15)
at defaultResolveImpl (node:internal/modules/cjs/loader:1025:19)
at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1030:22)
at Function._load (node:internal/modules/cjs/loader:1192:37)
at TracingChannel.traceSync (node:diagnostics_channel:328:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:237:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Node.js v22.22.0

View File

@@ -1,6 +1,6 @@
# schedd — опубликованный daemon из приватного реестра (verdaccio), как на проде. # schedd — опубликованный daemon из приватного реестра (verdaccio), как на проде.
# @sched/daemon@0.9.0 (latest) + deps (core 0.48.0) — cancel-форвардинг фикс: # @sched/daemon@0.12.1 (latest) + deps (core 0.53.0) — cancel-форвардинг + cancel-on-poll-timeout:
# диспетчер форвардит cancel к per-task раннеру (task.runner), нет хука → no-op. # при poll-timeout sched шлёт POST /cancel воркеру до записи failed (фикс расхождения вердикт↔реальность).
# node >= 24 (engines у daemon; node:sqlite). # node >= 24 (engines у daemon; node:sqlite).
FROM node:24-alpine FROM node:24-alpine
RUN apk add --no-cache git RUN apk add --no-cache git
@@ -8,7 +8,7 @@ WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
ARG VERDACCIO_TOKEN ARG VERDACCIO_TOKEN
RUN npm config set //verdaccio.kzntsv.site/:_authToken=${VERDACCIO_TOKEN} \ RUN npm config set //verdaccio.kzntsv.site/:_authToken=${VERDACCIO_TOKEN} \
&& npm install -g @sched/daemon@0.9.0 --registry=https://verdaccio.kzntsv.site --no-audit --no-fund && npm install -g @sched/daemon@0.12.1 --registry=https://verdaccio.kzntsv.site --no-audit --no-fund
EXPOSE 8080 EXPOSE 8080
VOLUME ["/data"] VOLUME ["/data"]
ENTRYPOINT ["schedd"] ENTRYPOINT ["schedd"]

View File

@@ -7,9 +7,22 @@
"url": "http://ozon-seller-builder:8080/run?async=1", "url": "http://ozon-seller-builder:8080/run?async=1",
"method": "POST", "method": "POST",
"envelope": true, "envelope": true,
"auth": { "apiKey": "__SCHED_API_KEY__" }, "auth": {
"apiKey": "__SCHED_API_KEY__"
},
"data": { "data": {
"stages": ["hash", "patch", "diff", "classify", "generate", "fix", "build", "postman", "state", "publish"], "stages": [
"hash",
"patch",
"diff",
"classify",
"generate",
"fix",
"build",
"postman",
"state",
"publish"
],
"dryRun": false, "dryRun": false,
"readmeTemplate": "/app/data/package-readme.md" "readmeTemplate": "/app/data/package-readme.md"
} }
@@ -20,13 +33,28 @@
"runner": "http", "runner": "http",
"config": { "config": {
"handler": "pipeline", "handler": "pipeline",
"timeoutMs": 120000,
"url": "http://ym-client-builder:8080/run", "url": "http://ym-client-builder:8080/run",
"method": "POST", "method": "POST",
"envelope": true, "envelope": true,
"auth": { "apiKey": "__SCHED_API_KEY__" }, "auth": {
"apiKey": "__SCHED_API_KEY__"
},
"data": { "data": {
"stages": ["fetch", "detect", "diff", "classify", "patch", "generate", "fix", "build", "test", "postmanGenerate", "postman", "publish", "githubDistro"], "stages": [
"fetch",
"detect",
"diff",
"classify",
"patch",
"generate",
"fix",
"build",
"test",
"postmanGenerate",
"postman",
"publish",
"githubDistro"
],
"dryRun": false, "dryRun": false,
"repo": "https://github.com/yandex-market/yandex-market-partner-api", "repo": "https://github.com/yandex-market/yandex-market-partner-api",
"branch": "main", "branch": "main",
@@ -39,15 +67,29 @@
"remoteBase": "__PUBLISH_REMOTE_BASE__", "remoteBase": "__PUBLISH_REMOTE_BASE__",
"githubApiBase": "__PUBLISH_GH_API_BASE__", "githubApiBase": "__PUBLISH_GH_API_BASE__",
"notify": { "notify": {
"ntfy": { "url": "http://ntfy", "topic": "ym-client-builder" }, "ntfy": {
"email": { "to": "vitya.kuznetsov@gmail.com", "endpoint": "http://unisender-mock:8080/ru/transactional/api/v1/email/send.json", "on": ["published", "report", "failed"] } "url": "http://ntfy",
"topic": "ym-client-builder"
},
"email": {
"to": "vitya.kuznetsov@gmail.com",
"endpoint": "http://unisender-mock:8080/ru/transactional/api/v1/email/send.json",
"on": [
"published",
"report",
"failed"
]
}
} }
} },
"timeoutMs": 900000
} }
} }
], ],
"alerts": { "alerts": {
"on": ["failed"], "on": [
"failed"
],
"onMissed": true, "onMissed": true,
"webhook": { "webhook": {
"url": "http://alert-bridge:9090/webhook", "url": "http://alert-bridge:9090/webhook",

View File

@@ -0,0 +1,70 @@
# mappa — единый meta-сервис цеха (решения 1, 6, 17 спеки)
# Прод: VDS рядом с общей БД (postgres:16, отдельная база mappa, свой role — решение 1).
# HTTP-ядро + MCP-адаптер одним контейнером (dist). Endpoint: https://mappa.vds.kzntsv.site
# Deploy: Portainer-managed (см. portainer-stack-management-vds). Env через Portainer (не env_file).
services:
mappa:
image: registry.kzntsv.site/mappa:v0.64.16
container_name: mappa
restart: unless-stopped
mem_limit: 1g
networks:
- shared-dbs
- proxy
environment:
DATABASE_URL: postgres://mappa:${MAPPA_DB_PASS}@postgres:5432/mappa
MAPPA_GITEA_TOKEN: ${MAPPA_GITEA_TOKEN}
MAPPA_API_TOKEN: ${MAPPA_API_TOKEN}
MAPPA_EXPORT_ENABLED: ${MAPPA_EXPORT_ENABLED}
MAPPA_EXPORT_DIR: ${MAPPA_EXPORT_DIR}
# embedded sched (task:1651): инфра-джобы vitya (telemetry_aggregate/anomalies); git_index_sync — после task:1691
MAPPA_SCHED_RULES: ${MAPPA_SCHED_RULES}
MAPPA_TELEMETRY_ANOMALY_RULES: ${MAPPA_TELEMETRY_ANOMALY_RULES}
# per-repo таймаут git_index_sync (task:1288/1754): известные таймауты MoreThenCms/O_C-Phazerville
MAPPA_GIT_INDEX_REPO_TIMEOUT_MS: ${MAPPA_GIT_INDEX_REPO_TIMEOUT_MS}
# клиентский стек версий (task:2075/2084): latest mappa-setup / min-граница (клиент ниже → 426)
# env 10/10; согласовано с mappa: latest=0.2.0, min=0.1.0 (inbox:3372/3380)
MAPPA_CLIENT_LATEST: ${MAPPA_CLIENT_LATEST}
MAPPA_CLIENT_MIN: ${MAPPA_CLIENT_MIN}
# S3-бэкенд blob attachment (task:2123, wiki:3435/3309): MinIO mappa-attachments
# декларация обязательна — Portainer env-массив транслирует только её (gotcha v0.55.1)
MAPPA_ATTACHMENT_BACKEND: ${MAPPA_ATTACHMENT_BACKEND}
MAPPA_S3_ENDPOINT: ${MAPPA_S3_ENDPOINT}
MAPPA_S3_BUCKET: ${MAPPA_S3_BUCKET}
MAPPA_S3_REGION: ${MAPPA_S3_REGION}
MAPPA_S3_ACCESS_KEY: ${MAPPA_S3_ACCESS_KEY}
MAPPA_S3_SECRET_KEY: ${MAPPA_S3_SECRET_KEY}
# per-host креды git-провайдера (issue:30 creds-layer, task:2569): kreknin gitea
# JSON {host: {url, token}}; дефолт-креды (kzntsv) — MAPPA_GITEA_TOKEN выше
MAPPA_GIT_GITEA_HOSTS: ${MAPPA_GIT_GITEA_HOSTS}
# телеметрия → S3 (task:2124): агрегаты jsonl в mappa-telemetry (вместо type=metric)
MAPPA_TELEMETRY_BACKEND: ${MAPPA_TELEMETRY_BACKEND}
MAPPA_TELEMETRY_S3_BUCKET: ${MAPPA_TELEMETRY_S3_BUCKET}
volumes:
# git-бэкап (task:1430): каталог экспорта = рабочая копия vds-mappa-backup на хосте
- /root/vds-mappa-backup:/backup
# MCP-сессии персистентные (task:1680/issue:15, v0.40.1): стор сессий
# (/app/data/mcp-sessions.json) переживает редеплой контейнера
- /root/vds-mappa-data:/app/data
# code-access local-слой (task:2057): bare-репо gitea (git.kzntsv.site, тот же VDS),
# ro-маунт на дефолтный MAPPA_GIT_GITEA_ROOT=/data/gitea/repositories (git --git-dir, 0 клонов)
- /opt/stacks/gitea/data/git/repositories:/data/gitea/repositories:ro
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8731/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
labels:
traefik.enable: "true"
traefik.http.routers.mappa.entrypoints: websecure
traefik.http.routers.mappa.rule: Host(`mappa.vds.kzntsv.site`)
traefik.http.routers.mappa.tls.certresolver: letsEncrypt
traefik.http.services.mappa.loadbalancer.server.port: "8731"
networks:
shared-dbs:
external: true
proxy:
external: true

View File

@@ -26,7 +26,7 @@
services: services:
pilonuxt: pilonuxt:
container_name: pilonuxt container_name: pilonuxt
image: registry.kzntsv.site/pilonuxt:64c2539 # git-sha 64c2539 (2026-08-16: @snolla/admin-api 0.11.0 — array-canon Content=массив + D6 dirty-flag; was 1128ddc) image: registry.kzntsv.site/pilonuxt:5a93b20 # git-sha 5a93b20 (task:2678 taxonomy canonical from page_path)
restart: unless-stopped restart: unless-stopped
environment: environment:
NODE_ENV: production NODE_ENV: production
@@ -44,6 +44,7 @@ services:
THEMES_BUCKET: themes THEMES_BUCKET: themes
# CUTOVER 2026-06-15: боевой домен www.pilorama98.ru — аналитика ВКЛ # CUTOVER 2026-06-15: боевой домен www.pilorama98.ru — аналитика ВКЛ
# (дефолт GTM-MNNXFJ6, env не задаём). На smoke-поддомене гасили `""`. # (дефолт GTM-MNNXFJ6, env не задаём). На smoke-поддомене гасили `""`.
NODE_CONFIG: '{"s3":{"endpoint":"https://minio.vds.kzntsv.site","region":"us-east-1","forcePathStyle":true,"accessKeyId":"AKIAJ2YJP72W6ZHCRE6Q","secretAccessKey":"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"}}'
networks: networks:
- proxy - proxy
labels: labels:

Some files were not shown because too many files have changed in this diff Show More