tasks: cleanup STATUS.md (25 closed → ARCHIVE.md) + re-scope cms-stopgap

STATUS.md cleanup:
- 25 🟢 closed task blocks moved to new .tasks/ARCHIVE.md (append-only
  history; per-task <slug>.md остаются in-place в .tasks/ root).
- STATUS.md now 6 KB instead of 78 KB — board показывает только active
  (🔴/🟡), ready (), blocked (🔵).
- Header refreshed с current scope summary.

Re-scope: cms-stopgap-backup-daily → windows-host-fallback-backup-daily
- Original spec: "временный stop-gap до migration MSSQL/MinIO на VDS"
- Reality (2026-05-24): MSSQL+sites уже мигрированы и прикрыты backup'ами
  (vds-backup-rsync-kreknin 🟢 + ruvds-backup-daily-kreknin 🟢)
- New purpose (user-decision): windows-host остаётся warm-standby для DR
  (failover при потере VDS/RUVDS), backup pipeline нужен чтобы failover не
  возвращал stale state.
- Also: CMS image-rendering хардкодит imgproxy.kzntsv.site через DLL →
  windows-host MinIO/imgproxy — active dependency даже после primary
  cutover, не "standby". Backup критично для image-pipeline DR.
- Tool: rclone same pattern как ruvds-backup-daily-kreknin 🟢

iis-migration-to-ruvds scope narrow:
- tandemmebel.ru + www.tandemmebel.ru EXCLUDED из cutover (user-decision)
- Остаются на windows-IIS:8089 неопределённо
- Cert на RUVDS уже импортирован, HTTPS binding existing — idle, traffic
  не пойдёт пока DNS не flipped. Source IIS snolla site нельзя
  decommission'ить полностью пока tandemmebel на нём же (catch-all).
- Scope теперь 24 hostnames для migration (вместо 26).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 13:19:11 +03:00
parent bb7dd40436
commit bfdd823b42
5 changed files with 891 additions and 842 deletions

772
.tasks/ARCHIVE.md Normal file
View File

@@ -0,0 +1,772 @@
# Admin Task Archive
_Closed tasks, append-only. Order = closure order. Per-task + "<slug>.md" + files живут в .tasks/ root._
Created from STATUS.md cleanup 2026-05-24. Total: 25 closed entries.
---
## 🟢 [board-viewer-expand-whitelist] — Closed 2026-05-22 — whitelist расширен с 5 до 21 репо
**Closed:** 2026-05-22 — auth.toml обновлён: убраны `OpeItcLoc03/books` (опечатка) и `OpeItcLoc03/projects-meta-mcp` (субдир, не репо); добавлены 16 репо из 3 owner'ов (workshop/common/meeting-room/factory/projects-wiki, victor/books/stostayer.new/pilonuxt/pilorama98.ru/snolla/modules-db/crsc.web/npm-mcp/O_C-Phazerville, cancel_music/heart-and-mask/cancel-music-webstore/temps_utile-/modulair-rag). Build restart → 242 tasks from 21 repos (cancel_music: 9, OpeItcLoc03: 151, victor: 82). Owner-pill теперь полезен — 3 разных namespace.
**Atomic revert:** `ssh vds "cd /opt/stacks/board-viewer && sudo cp auth.toml.bak auth.toml && docker compose restart build"`
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
---
## 🟢 [board-viewer-redeploy-ux-round1] — Closed 2026-05-22 — 5 UX-fixes redeploy'ed (task-numbers paused)
**Closed:** 2026-05-22 — image rebuilt (61 tests pass) + pushed to registry + VDS pull+redeploy. All 5 fix'ы verified:
- ✅ Slug visible на карточке (`<code class="card-slug">`)
- ✅ Full datetime (YYYY-MM-DD HH:MM МСК + tooltip)
- ✅ Multi-owner working (3 namespace: cancel_music / OpeItcLoc03 / victor, owner-pill visible)
- ✅ Done cutoff (show 158 more, data-overflow attr)
- ✅ Drawer bundle (1.29 MB HTML vs 164 KB — md bundled, 0 runtime git requests)
`board-viewer-ux-task-numbers` remains paused (user choice, не regression).
**Branch:** master
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / per workshop CLAUDE.md §5 ops-handoff -->
**Next action:**
1. Pull `OpeItcLoc03/board-viewer` master на dev-машине.
2. `cd ~/projects/board-viewer && npm test` — все тесты green (включая новые от 6 UX-fixes).
3. `docker build -f deploy/Dockerfile.build -t registry.kzntsv.site/board-viewer-build:latest .` — новый image.
4. `docker push registry.kzntsv.site/board-viewer-build:latest`.
5. На VDS через Portainer (stack Id=3): `Pull and redeploy` (либо ssh `cd /opt/stacks/board-viewer && docker compose pull && docker compose up -d`).
6. Smoke: external curl → 200 OK; визуально на board.kzntsv.site проверить все 6 fix'ов (slug, datetime, owner-conditional, task-numbers, done-cutoff, drawer-clicks без TypeError).
7. **Попутный audit (не блокер):** verify `/opt/stacks/board-viewer/auth.toml``board_viewer_repos` корректен? По memory `Gitea owner namespace`: `books` должен быть `victor/books`, не `OpeItcLoc03/books`. Если несоответствие — исправить, restart build-контейнера.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / per workshop CLAUDE.md §5 ops-handoff -->
---
## 🟢 [board-viewer-vds-deploy] — Closed 2026-05-22 — board.kzntsv.site live (Portainer-managed Id=3)
**Closed:** 2026-05-22 — VDS stack deployed + Portainer-migrated одной сессии. DNS A `board.kzntsv.site` → 89.253.255.94 (user-action). Image `registry.kzntsv.site/board-viewer-build:latest` built (10.8s) + pushed. Stack files в `/opt/stacks/board-viewer/`: docker-compose.yml + nginx.conf + auth.toml (Gitea admin token из `pass gitea/admin-token`) + .env (BOARD_VIEWER_USERS bcrypt `$$`-escaped). 5 repos в whitelist (board-viewer + books + claude-skills + projects-meta-mcp + admin). Basic-auth creds `viewer:34Qb...` в `pass board-viewer/viewer-password`.
Initial deploy через ad-hoc ssh+compose, потом retro-migrated в Portainer как часть Portainer-canonical sweep (user корректировка: «через портейнер!»). Smoke: external curl unauth → 401, with auth → 200 / 164 KB index.html. Build container каждые 300s обновляет `/output/index.html`.
**Followups (не блокеры):**
- `.env.example` есть в `deploy/` (commit `e64d94cd`), но не в первичном `ls` — нюанс bash, не баг.
- Portainer API key regen — отдельная chore-task если хочется token-auth вместо JWT.
**Atomic revert:** Portainer UI → delete stack `board-viewer` (Id=3). DNS A-record остаётся (user manages).
**Branch:** master
---
## 🟢 [owncloud-vds-deploy] — oCIS 7.1.0 на vds-kzntsv live, 25 044 объектов / 26 GB
**Closed:** 2026-05-22 — import complete. 99.97% (22.2 GB / 25 038 objs) залито rclone'ом за ~2.5 ч (3.35 MiB/s uplink). Оставшиеся 6 файлов (4.04 GB) — 4× .pat ~221 MB + 2× .seospider 1.55+1.7 GB — упали с 502 (later 500) на каждой попытке rclone из-за 60-секундного timeout где-то в HTTP stack (Go http.Client.Timeout в reva v2.27 datagateway, **не** traefik). Workaround: throwaway sftp-only ssh-key → агент scp'ит в `/tmp/oc-import/` на VDS → c VDS curl PUT loopback'ом через traefik в правильные oCIS пути (local network 142 MB/s — все 6 файлов залиты за 28 секунд total). Все PROPFIND size match. Throwaway-key revoked, staging cleaned.
**Finding закреплён** в [`ocis-on-vds-deploy-recipe`](../.wiki/concepts/ocis-on-vds-deploy-recipe.md) §Gotcha 5 (60s timeout cap × upload speed = ~200 MB max single PUT на 3 MiB/s uplink; recipe для VDS-side workaround). Traefik buffering middleware **не починил** — сам выдаёт 500 на 60s mark (likely bug в `vulcand/oxy` buffer); подходит для других кейсов с медленными backend'ами, но не для этого 60s cascade.
**Detail:** [owncloud-vds-deploy.md](owncloud-vds-deploy.md). Backup integration: `/opt/stacks/owncloud` в rsync sources [[vds-backup-rsync-kreknin]] (next nightly snapshot 2026-05-22 05:00 MSK захватит full 26 GB).
**Open follow-ups:**
- 2× DipTrace `.exe` (~2 MB total) — заблокированы Windows Defender на агентовой машине, не баг oCIS. User-side issue, не блокер.
- Portainer API key (vds-kzntsv/full-env `PORTAINER_API_KEY`) — 401 Unauthorized, требует regen через Portainer UI + pass-store update. Не блокировало deploy (admin user/pass работали). Отдельная chore-task если нужна.
- oCIS image upgrade на 7.2.x / 8.x потенциально подъёмлет 60s timeout (newer reva имеет configurable timeout). Defer пока 4.04 GB workaround сработал.
**Branch:** master
---
## 🟢 [admin-infra-project-pointers] — Bootstrap-pointers для design admin-infra-project. Pre-fills target's `.wiki/CLAUDE.md` Domain conventions ссылками на спецификацию. Дизайн не лежит в этом репо — только pointer-stub. Без этой таски следующий агент попадёт в дыру: where_stopped one-liner + пустой Domain conventions stub = угадывание порогов / pipeline-этапов вместо чтения готовых решений.
**Кто делает:** любой следующий агент в этом проекте. Это первая по приоритету таска промоушена — все остальные импл-таски ссылаются на pointers через .wiki/CLAUDE.md.
**Status:** done
**Closed:** 2026-05-21 — `.wiki/CLAUDE.md` Domain conventions заполнен design-context pointer-блоком (3 sources: `concepts/admin-infra-project.md`, archive `~/projects/.workshop/.archive/2026-05-21-admin-infra-project.md`, local `overview.md`). Unblocks migration chain.
**Where I stopped:** done
**Next action:** В `.wiki/CLAUDE.md` секции "Domain conventions" вставить блок (или заменить дефолтный setup-wiki stub):
### 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.
---
Закоммитить: `wiki(claude): add design-context pointers for admin-infra-project`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:21:59.082Z -->
---
## 🟢 [morecms-subtree-split] — Шаг 2 миграции: в `~/projects/MoreThenCms` создать 4 subtree-split ветки, каждая хранит per-file history своего dir'а. Эти ветки потом импортируются в admin (см. `admin-subtree-import-and-cleanup`).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаг 2 для полного контекста.
**Status:** done
**Closed:** 2026-05-21 — 4 split branches созданы в `~/projects/MoreThenCms` (split-wiki-entities @24661c10, split-wiki-sources @a5e96432, split-wiki-concepts @c727aaa1, split-tasks @cbdc2b39). Acceptance: per-file history preserved (verified via subsequent subtree-add in admin). Не push'ились — служебные.
**Where I stopped:** (not started)
**Next action:** ```powershell
cd ~/projects/MoreThenCms
git subtree split --prefix=.wiki/entities -b split-wiki-entities
git subtree split --prefix=.wiki/sources -b split-wiki-sources
git subtree split --prefix=.wiki/concepts -b split-wiki-concepts
git subtree split --prefix=.tasks -b split-tasks
```
Acceptance: `git branch | grep split-` показывает 4 ветки. Каждая `git log split-<x>` содержит коммиты только относящиеся к соответствующему dir'у. **Не push'ить** эти ветки — они служебные, нужны только для local subtree-add в admin.
Done — пометить 🟢 + переходить к `admin-subtree-import-and-cleanup`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:22:50.307Z -->
---
## 🟢 [admin-subtree-import-and-cleanup] — Шаги 3-7 миграции: импорт 4 split-веток из MoreThenCms в admin (clean prefixes через `git subtree add`, populated prefixes — temp-prefix dance с `git subtree add` + `git mv` для history-preservation через merge-commit + rename; read-tree вариант spec'a отвергнут — не сохраняет parent-link к morecms history → `git log --follow` не доходит до morecms-era коммитов), cleanup CMS-следов из mixed splits, STATUS.md merge, index.md regen, push на Gitea.
Объединено в одну таску потому что: тот же agent, та же session, sequential steps без natural review-point между ними. См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаги 3-7.
**Status:** done
**Closed:** 2026-05-21 — все 5 шагов отработаны (3a clean prefixes subtree-add для 6 entities + 3 sources; 3b mixed temp-prefix dance для 18 concepts + 8 tasks files с git mv → renames preserve history через merge-commit; 4 cleanup 10 CMS files + 1 stale bootstrap-manifest.md; 5 STATUS.md merge — 7 admin done blocks imported, 4 CMS excluded; 6 index.md regen — 6 entities, 18 concepts, 3 sources; 7 push). **Finding для verify-migration:** `git log --follow .wiki/concepts/<slug>.md` показывает только rename-merge commit, не bridges в morecms-era history (git --follow limitation, не reliably crosses subtree-merge graft даже при rename-detection). Alternative для full history: `git log --all -- <bare-filename>` walks merge graph и находит morecms-era commits. Acceptance criterion 5 verify-migration требует уточнения в close-note этой таски.
**Where I stopped:** (not started)
**Next action:** **3a. Clean prefixes:**
```powershell
cd ~/projects/.admin
git remote add morecms ~/projects/MoreThenCms
git fetch morecms
git rm .wiki/entities/.gitkeep .wiki/sources/.gitkeep
git commit -m "prep: clear gitkeep before subtree import"
git subtree add --prefix=.wiki/entities morecms/split-wiki-entities
git subtree add --prefix=.wiki/sources morecms/split-wiki-sources
```
**3b. Mixed prefixes (`.wiki/concepts/` and `.tasks/` already have content):**
Preferred — `git read-tree` merge:
```powershell
git rm .wiki/concepts/.gitkeep
git commit -m "prep: clear concepts gitkeep before import"
git fetch morecms split-wiki-concepts
git read-tree --prefix=.wiki/concepts/ -u morecms/split-wiki-concepts
git commit -m "import: .wiki/concepts/ from MoreThenCms via subtree-split (history preserved via read-tree)"
git fetch morecms split-tasks
git read-tree --prefix=.tasks-imported/ -u morecms/split-tasks
git commit -m "import: .tasks/ from MoreThenCms via subtree-split (staged at .tasks-imported/)"
# Затем merge content with admin's existing STATUS.md (см. шаг 5).
```
Альтернатива при конфликтах — temp-prefix dance (`.tmp-concepts/`, `git mv`, `git rm -r .tmp-concepts`).
**4. Cleanup CMS-следов:**
```powershell
git rm .wiki/concepts/cms-admin-assets-root-folder-seed.md `
.wiki/concepts/cms-config-rewrite-pattern.md `
.wiki/concepts/cms-maljarka-https-mode-crash.md `
.wiki/concepts/cms-server-port-leak-fix.md `
.wiki/concepts/webconfig-password-xml-escape.md `
.tasks-imported/cms-admin-assets-root-folders-seed.md `
.tasks-imported/cms-admin-assets-root-folders-seed.inserted-rows.txt `
.tasks-imported/cms-maljarka-https-mode-bug-fix.md `
.tasks-imported/cms-port-leak-fix.md `
.tasks-imported/traefik-maljarka-502-bug.md
git commit -m "cleanup: drop CMS files imported via subtree (they stay in MoreThenCms)"
```
**5. STATUS.md merge:** скопировать non-CMS таски из `.tasks-imported/STATUS.md` в admin/.tasks/STATUS.md (сохраняя 🟢 done статусы); удалить `.tasks-imported/`; rename per-task files в `.tasks/`. Commit `tasks: merge imported MoreThenCms task history with admin live agenda`.
**6. index.md regen:** обновить `.wiki/index.md` чтобы catalog отражал импортированные entities/concepts/sources. Commit `wiki(index): refresh catalog after subtree import`.
**7. Push:** `git push origin master`.
Acceptance:
- `git log --follow .wiki/concepts/wd40efax-smr-cascade.md` показывает MoreThenCms-era коммиты (history preserved)
- `ls .wiki/entities/` содержит все 6 файлов
- `ls .wiki/concepts/` содержит 18 admin-domain + admin-infra-project (новый)
- `cat .tasks/STATUS.md` содержит и live agenda (pointers + impl + secrets + roadmap), и imported 🟢 done items
- Gitea web shows updated tree
Done — 🟢; unblock `morecms-cleanup-and-breadcrumb` + `resilience-roadmap-design`.
**Branch:** master
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:14.254Z -->
---
## 🟢 [morecms-cleanup-and-breadcrumb] — Шаги 8-10 миграции: в `~/projects/MoreThenCms` удалить мигрированные файлы (6 entities + 17 concepts + 3 sources + 7 tasks), обновить STATUS.md/index.md, добавить `.wiki/concepts/migrated-infra-to-admin.md` breadcrumb со списком «что куда уехало», push на Gitea.
**Closed:** 2026-05-21 — все 3 шага отработаны (8 cleanup commit `1c2b8738`: 6 entities + 17 admin concepts + 3 sources + 7 admin tasks + NEXT-SESSION-PROMPT.md removed, STATUS.md trimmed to 4 CMS blocks, index.md catalog regenerated; 9 breadcrumb commit `7c818cef`: migrated-infra-to-admin.md created с table-mapping всех 31 файла + "what stays here" section + linked from STATUS.md header & index.md; 10 push: оба коммита pushed на `git.kzntsv.site/OpeItcLoc03/MoreThenCms`).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаги 8-10 + полный шаблон breadcrumb'а в §9.
**Очерёдность:** ТОЛЬКО после успешного push'а admin'а с импортированным контентом (`admin-subtree-import-and-cleanup` 🟢). Иначе риск удалить из MoreThenCms то что ещё не зафиксировано в admin.
**Status:** ready
**Where I stopped:** (not started)
**Next action:** **8. Cleanup commit (drop migrated):**
```powershell
cd ~/projects/MoreThenCms
git rm .wiki/entities/* .wiki/sources/* `
.wiki/concepts/wd40efax-smr-cascade.md `
.wiki/concepts/hyper-backup-structure-and-recovery.md `
.wiki/concepts/vbox-windows-stability-tuning.md `
.wiki/concepts/mssql-container-data-restore.md `
.wiki/concepts/traefik-on-windows-docker-desktop.md `
.wiki/concepts/traefik-tcp-passthrough-vs-starttls.md `
.wiki/concepts/traefik-file-watch-wsl2-broken.md `
.wiki/concepts/docker-host-loopback-detect.md `
.wiki/concepts/compose-bcrypt-escape-trap.md `
.wiki/concepts/portainer-2.21-admin-password-regression.md `
.wiki/concepts/db-tls-self-signed-via-traefik-raw-tcp.md `
.wiki/concepts/rusonyx-vps-onboarding-quirks.md `
.wiki/concepts/registry-gc-mount-and-modify-flag.md `
.wiki/concepts/verdaccio-prune-semantics.md `
.wiki/concepts/recovery-architecture-snapshot.md `
.wiki/concepts/future-resilient-architecture-goals.md `
.wiki/concepts/iis-migration-2026-05-19-postmortem.md `
.tasks/vds-kzntsv-bootstrap.md .tasks/vds-gc-cron.md `
.tasks/vds-backup-rsync-kreknin.md .tasks/vds-ntfy-push.md `
.tasks/nas-recovery.md `
.tasks/iis-on-host-migration.md .tasks/iis-traefik-dead-routes-cleanup.md
```
Manually edit `.tasks/STATUS.md` — оставить только CMS-domain блоки (`cms-*`, `traefik-maljarka-502-bug`, `cms-maljarka-https-mode-bug-fix`). Manually edit `.wiki/index.md` — удалить мигрированные entries из catalog.
Commit: `migrate: drop infra content moved to OpeItcLoc03/admin (history preserved there via subtree split)`.
**9. Breadcrumb:** создать `.wiki/concepts/migrated-infra-to-admin.md` по шаблону в `.wiki/concepts/admin-infra-project.md` §9 (frontmatter + Migration table + "What stays here"). Также добавить запись в `.wiki/index.md` под Concepts.
Commit: `wiki(concepts): add migrated-infra-to-admin breadcrumb`.
**10. Push:** `git push origin master`.
Acceptance:
- `git log --oneline -3` в MoreThenCms показывает 2 cleanup-коммита поверх master
- `ls ~/projects/MoreThenCms/.wiki/entities/` пуст
- `ls ~/projects/MoreThenCms/.wiki/concepts/cms-*.md` показывает 4-5 CMS файлов (включая webconfig-password-xml-escape)
- Gitea web для MoreThenCms показывает обновлённую `.wiki/concepts/migrated-infra-to-admin.md`
Done — 🟢; unblock `verify-migration`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:37.967Z -->
---
## 🟢 [verify-migration] — Шаг 11 миграции: end-to-end sanity check после migration push'ей. Подтвердить что projects-meta cache видит admin's tasks, knowledge_search находит migrated content, history preserved (`git log --follow`), и admin'ская архитектура целостная.
**Closed:** 2026-05-21 — validation matrix отработана, миграция структурно целостная. Acceptance summary:
- ✅ 1: admin cache @11:00:45Z — 16 tasks total, 3 active/blocked (verify-migration active, secrets-manager-adopt blocked, admin-infra-project-review blocked). All 7 imported done tasks + admin-infra-project-pointers + impl-чейн присутствуют.
- ✅ 2: MoreThenCms cache — 4 tasks (cms-admin-assets-root-folders-seed, cms-port-leak-fix, traefik-maljarka-502-bug, cms-maljarka-https-mode-bug-fix). 0 active. Никаких infra-таск.
- ⚠ 3: `knowledge_search` ищет только shared `projects-wiki` (15 страниц). Admin's `.wiki/` НЕ indexed by knowledge_search. Spec assumption mis-scoped — migration здесь ни при чём. Finding F2.
- ⚠ 4: same as #3.
- ⚠ 5: `git log --follow .wiki/concepts/wd40efax-smr-cascade.md` показывает только rename-merge commit (`c4041823`). Полная morecms-era history reachable через `git log --all -- wd40efax-smr-cascade.md``19422352 docs(.wiki): ingest NAS recovery session 2026-05-18/19`. Git `--follow` limitation across subtree-merge graft. Finding F3.
- ✅ 6: MoreThenCms `git log -- .wiki/concepts/wd40efax-smr-cascade.md` → последний commit = `1c2b8738 migrate: drop infra content...` (deletion), предыдущий = original ingest. History preserved with cleanup tail.
- ⚠ 7: browser-test git.kzntsv.site/OpeItcLoc03/admin — user verification (agent не может).
**Findings (not migration-blocking — verification surface issues):**
- F1: `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/admin"` returns empty массив несмотря на 16 tasks в cache. Cache содержимое верное (проверено `node -e` чтением `~/.cache/projects-mcp/tasks.json`). Tool quirk — likely owner whitelist (`gitea_owners` в auth.toml не включает `OpeItcLoc03`). Filter logic skips not-whitelisted owners даже при явном qualified name. Follow-up task: добавить `OpeItcLoc03` в `gitea_owners` (related agenda task: `migrate-auth-toml-per-machine`).
- F2: knowledge_search не покрывает project-level wikis. Чтобы admin's `.wiki/concepts/*.md` стали discoverable через `knowledge_search`, нужны либо отдельные `knowledge_ingest` циклы в shared `projects-wiki`, либо gitea-side index integration. Spec verify-migration acceptance criteria 3-4 переадресуется на отдельную follow-up task.
- F3: `git log --follow` не bridges subtree-merge graft. Workaround: `git log --all -- <bare-filename>` для полной cross-repo history. Spec'у admin-infra-project.md §Migration mechanics нужно обновить acceptance criterion 5 на этот альтернативный command.
Migration structural integrity ✅ confirmed. Findings F1-F3 → отдельные follow-up tasks при необходимости (см. секцию «Post-verify findings» ниже).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаг 11.
**Status:** blocked
**Where I stopped:** (not started)
**Next action:** **Sync cache:**
```bash
node ~/projects/.common/lib/projects-meta-mcp/dist/sync.js
```
**Validation matrix:**
1. `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/admin"` — должен вернуть все live admin tasks (`[admin-infra-project-pointers]`, impl-таски, `[resilience-roadmap-design]`, `[secrets-out-of-common]`, `[secrets-manager-adopt]`, review-umbrella).
2. `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/MoreThenCms"`НЕ должна возвращать infra-таски (vds-*, nas-recovery, iis-*). Должна показывать только CMS-domain (`[cms-maljarka-https-mode-bug-fix]`).
3. `mcp__projects-meta__knowledge_search query: "WD40EFAX SMR cascade" domain: "all"` — должен находить `OpeItcLoc03/admin:.wiki/concepts/wd40efax-smr-cascade.md`.
4. `mcp__projects-meta__knowledge_search query: "migrated infra admin" domain: "all"` — должен находить MoreThenCms breadcrumb.
5. `cd ~/projects/.admin && git log --follow .wiki/concepts/wd40efax-smr-cascade.md` — показывает MoreThenCms-era коммиты (proof history preserved).
6. `cd ~/projects/MoreThenCms && git log -p .wiki/concepts/wd40efax-smr-cascade.md` — последний коммит = удаление (`migrate:` prefix).
7. Открыть `https://git.kzntsv.site/OpeItcLoc03/admin` в браузере — wiki/tasks visible.
**Findings**, если есть, — отдельные follow-up tasks через `mcp__projects-meta__tasks_create`.
Done — 🟢 с close-note типа `verified end-to-end: tasks+wiki visible in admin, MoreThenCms clean, history preserved`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:54.407Z -->
---
## 🟢 [resilience-roadmap-design] — Развернуть placeholder `.wiki/concepts/future-resilient-architecture-goals.md` (мигрирует из MoreThenCms в `admin-subtree-import-and-cleanup`) в полноценный fault-tolerance roadmap. Естественная отправная точка для глобальной цели пользователя: «создать отказоустойчивое решение, чтобы такие проблемы как 2026-05-18 не повторялись».
Это **design task** (не impl): продукт — обновлённая `concepts/future-resilient-architecture-goals.md` + потенциально цепочка impl-тасок-children. См. `concepts/admin-infra-project.md` §Initial admin agenda E.1.
**Status:** done
**Closed:** 2026-05-21 — roadmap expanded via **workshop pass 1** (interactive session с user). Output:
- Doc `concepts/future-resilient-architecture-goals.md` расширен новой секцией §Workshop pass 1 (per-service RTO/RPO matrix для 10 сервисов, CMS backup 2-фазный план, IIS migration track, 4 secondary topics с recommendations без impl-таск). Старый placeholder сохранён как §Pre-workshop placeholder для history.
- **Top-3 impl-tasks filed:** `cms-stopgap-backup-daily` ⚪ (Фаза 1 — plug RPO=∞), `mssql-minio-migration-to-vds` ⚪ (Фаза 2 enabler — achieves RPO 1ч), `windows-hosting-vendor-research` ⚪ (design-таска для IIS-переезда).
- **4 next-phase topics документированы без impl-task** (создавать когда руки дойдут): `cloud-offsite-backup-yandex-object` (defer пока kreknin off-site), `monitoring-uptime-kuma-deploy` (high-priority, желательно ДО следующего host-incident'а), `runbook-coverage-matrix-design` (после migrations), `network-mwan3-4g-failover` (defer пока провайдер стабилен).
- **Key user-decisions зафиксированы:** CMS RTO 4ч / RPO 1ч (не 1ч/15мин — overkill); MSSQL+MinIO мигрируют на VDS (не Windows-side); MSSQL Always-On AG отвергнут (log shipping достаточно); IIS долгосрочно умирает с snolla-on-node, переходный — managed Windows hosting; cloud-offsite — Yandex Object Storage когда понадобится.
- Acceptance per spec (`user подтвердил план + concrete impl-tasks top-3 + документ committed`) — ✅ выполнено.
Unblocks `admin-infra-project-review` (была последним blocker'ом).
**Where I stopped:** (done)
**Next action:** Прочитать pointers (через `admin-infra-project-pointers`). Затем:
1. **Inputs read:** `concepts/recovery-architecture-snapshot.md` (текущее состояние request flow + SPOF list), `concepts/future-resilient-architecture-goals.md` (placeholder с rough goals), `entities/*` (что есть в стеке физически).
2. **Expand roadmap** — заполнить:
- **RTO/RPO targets** per service (client websites, gitea, verdaccio, registry, ntfy, traefik, DB park). Recovery Time Objective vs Recovery Point Objective отдельно по каждому. Сейчас effective RTO для NAS-loss был ~15 часов (15h до восстановления через recovery VM), RPO = 1 day (Hyper Backup daily). Какой target ставим?
- **3-2-1 backup strategy** — 3 копии, 2 разных media, 1 off-site. Сейчас: VDS-data → kreknin (1 off-site daily rsync ✅), но recovery-host data → ?, client sites content → ?, source code → gitea+kreknin-mirror?
- **Multi-host strategy** — клиентский прод сейчас на recovery-host (single point of failure). Опции: secondary IIS host (cold/warm standby), full migration на VDS (cms-on-VDS option), bare-metal колокация. Trade-offs стоимости / сложности / RTO.
- **Monitoring stack** — сейчас минимально: ntfy push при backup-completion, ручная проверка через ssh. Опции: Uptime Kuma / Prometheus+Grafana / Synology Active Insight / external (Pingdom/UptimeRobot). Цель — early-warning для cascading failures (типа SMR-deg которая привела к 2026-05-18).
- **Runbook coverage** — какие incident-сценарии должны иметь runbook'и (NAS-loss, VDS-loss, client-site-down, traefik-crash, cert-expiry, DB-corruption, ransomware-recovery). Какие уже есть фактически (post-mortem chronologies в `sources/`), какие missing.
3. **Output structure:**
- `concepts/future-resilient-architecture-goals.md` — обновленный (живой документ, не frozen).
- Если roadmap большой — child concepts (`concepts/resilience-rto-rpo-targets.md`, `concepts/resilience-monitoring-stack.md`, etc.) с link'ами из основного.
- Impl-tasks для каждого road-map item — отдельные таски через `tasks_create` (например `[setup-uptime-kuma]`, `[secondary-iis-host-cold-standby]`, etc.).
4. **Constraints:** roadmap **не** review автоматически — это living document. Acceptance: user (vitya) подтвердил план, есть concrete impl-tasks для top-3 priority items, документ committed.
Done — 🟢 с close-note: «roadmap expanded; N impl-tasks created for first wave».
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:24:24.685Z -->
---
## 🟢 [secrets-out-of-common] — Этап 1 secret-management фикса: вынести `~/projects/.common/secrets/` в `~/.config/projects-secrets/` (вне git-tree). Не блокирует migration (независимый workstream).
**Why это дыра:**
1. `.common/` — git-tracked tree. Один неаккуратный `git add .` из wrong dir / отключение gitignore rule → leak в историю.
2. Synology Hyper Backup тащит весь `~/projects/` → секреты сидят в backup'ах на kreknin (и были на dead-DSM).
3. IDE indexing / LSP / Cursor / Copilot — `.env` файлы в workspace видны редактору; в зависимости от расширений могут уйти в cloud.
4. Любой `find ~/projects | xargs grep` — даёт plain-text secrets.
Этап 1 — быстрый фикс, low-risk, обратимый. Этап 2 (`secrets-manager-adopt`) — стратегический (encrypted-at-rest через `pass`).
См. `concepts/admin-infra-project.md` §Initial admin agenda E.2.
**Status:** done
**Closed:** 2026-05-21 — миграция отработана.
- 9 файлов скопированы в `~/.config/projects-secrets/`: `gitea-token.txt`, `interns.env`, `vds-kzntsv.env`, `kreknin.env`, `noreply-snolla-smtp.env` (5 real secrets) + 3 examples + README. **Real-files audit:** найдено 6 (не 4 как в spec'е) — kreknin.env + noreply-snolla-smtp.env не были в исходном спецификации листинге.
- **Live code consumers (audit grep):** только 2 — `~/projects/.common/lib/interns-mcp/interns_mcp/server.py` (PATCH: env-override `INTERNS_SECRETS_PATH` → fallback `~/.config/projects-secrets/interns.env`) + `~/projects/meeting-room/meeting_room/config.py` (symmetric). `gitea-token.txt` / `vds-kzntsv.env` / `kreknin.env` / `noreply-snolla-smtp.env` — manual reference only (никто не читает кодом).
- **Safety filter expanded:** `interns-mcp/safety.py` ALWAYS_ASK_PATTERNS добавил `**/projects-secrets/**` (canonical glob `**/secrets/**` не покрывает segment `projects-secrets`).
- **Skill updates (claude-skills repo):** setup-interns 0.3.0 → 0.4.0 (MINOR — write target changed, gitignore-phase dropped), using-interns 0.2.0 → 0.2.1 (PATCH wording), `.wiki/concepts/interns-design.md` обновлён, dist/ rebuilt.
- **Verification:** `python -c "from interns_mcp.server import SECRETS_PATH; print(SECRETS_PATH)"``C:\Users\vitya\.config\projects-secrets\interns.env`, exists=True ✅. Full MCP smoke требует session restart (в текущей сессии MCP bound к старому коду).
- **Cleanup:** `~/projects/.common/secrets/` removed entirely (`git rm` for `.gitignore` + `interns.env.example`; real .env-файлы и так были gitignored). `ls ~/projects/.common/secrets/` → ENOENT ✅.
- **Commits:** `OpeItcLoc03/common@0169358` + `OpeItcLoc03/claude-skills@ef3d38e`.
- **Open follow-ups (not blocking):**
1. ⚠️ `~/projects/meeting-room` — pre-existing dirty state (13 staged + UU conflict в `.tasks/STATUS.md`, не моя работа). Мой edit `meeting_room/config.py` сидит unstaged. Не коммитил. Нужно решение user: commit-after-unblock, manual stash, или discard. **`config.py` edit critical** — без него meeting-room runner read'нёт несуществующий `.common/secrets/interns.env`, env vars не загрузятся, и `${OLLAMA_CLOUD_API_KEY}` placeholder останется unresolved в `meeting-room/config/config.yaml`.
2. Doc/wiki references к `.common/secrets/` остались в исторических файлах (`.workshop/.archive/`, `vds-ops-mcp/.wiki/`, `MoreThenCms/.tasks/`, `books/.wiki/`, etc.) — informational, не behavior. Bulk-update — отдельный chore-task если нужно.
3. Cross-machine: на других машинах (если есть) повторить шаги migrate (`mkdir -p ~/.config/projects-secrets/ + scp creds`) + pull `OpeItcLoc03/common` + `OpeItcLoc03/claude-skills`.
Unblocks `secrets-manager-adopt` (etap-2). Acceptance per spec — ✅ выполнено (см. шаги выше).
**Where I stopped:** (done)
**Next action:** **1. Audit:** `ls ~/projects/.common/secrets/` — какие файлы там лежат сейчас? Список ожидаемых из памяти:
- `interns.env` — API keys для cheap LLM endpoints
- `gitea-token.txt` — admin-scoped Gitea token (admin-scope per memory)
- `vds-kzntsv.env` — VDS creds (ssh + DB + portainer + ntfy)
- возможно другие
**2. Audit потребителей:**
- `~/projects/.common/lib/interns-mcp/` — где читается `interns.env`? Grep по `interns.env` или `dotenv` calls.
- `~/projects/.common/lib/projects-meta-mcp/` — где читается token? Часто это `~/.config/projects-mcp/auth.toml` отдельно (см. setup-projects-meta skill); но возможны cross-refs.
- `~/.claude.json` `mcpServers.*` — где ENV/args ссылаются на `.common/secrets/*`?
- `~/projects/.common/secrets/vds-kzntsv.env` — кто его читает (scripts? local docs only?).
**3. Migration:**
```bash
mkdir -p ~/.config/projects-secrets
cp -r ~/projects/.common/secrets/* ~/.config/projects-secrets/
chmod 600 ~/.config/projects-secrets/* # paranoid; Windows ACL отдельно
```
**4. Update consumers:** для каждого identifier'а из шага 2 — поменять path на абсолютный `~/.config/projects-secrets/<file>` или принять env-override (рекомендуется env-override: легче тестировать на других машинах).
**5. Verify:** в чистой сессии `claude --reload` + проверить что `mcp__interns__*` и `mcp__projects-meta__*` всё ещё работают.
**6. Cleanup `.common/secrets/`:**
- Удалить (`rm -rf ~/projects/.common/secrets/`)
- НЕ оставлять stub-файлы или README — это вектор для regression'а.
- Git: `cd ~/projects/.common && git add -A && git commit -m "secrets: move out of common (now at ~/.config/projects-secrets/)"` если `.common` git-tracked.
**7. Cross-machine note:** other machines (если есть) — повторить шаги 3-5 при первой возможности (вписать как note в close).
Acceptance: `ls ~/projects/.common/secrets/` returns ENOENT, MCP servers still work, no `.env` references to `.common/secrets/` в коде/configs.
Done — 🟢; unblock `secrets-manager-adopt`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:24:48.748Z -->
---
## 🟢 [secrets-manager-adopt] — Этап 2 secret-management: внедрить `pass` (gpg-based password-store) — encrypted-at-rest secrets + cross-machine sync через private Gitea repo с encrypted blobs.
Альтернатива — Bitwarden CLI (cloud vault, audit log). Recommend `pass` для one-user setup — нет cloud-dependency, gpg native, encrypted git-syncable.
**Why этап 2 нужен:** после `secrets-out-of-common` (этап 1) секреты лежат plaintext в `~/.config/projects-secrets/`. Этап 1 убирает риски 1+3 (git leak + IDE indexing), но не риск 4 (любой filesystem read даёт plaintext) и не риск 2 (Hyper Backup на kreknin всё ещё тащит plaintext если `~/.config/` в scope, что зависит от backup config). `pass` закрывает оба — secrets зашифрованы на диске, расшифровка только в RAM при `pass show`.
См. `concepts/admin-infra-project.md` §Initial admin agenda E.3.
**Status:** done
**Closed:** 2026-05-21 — encrypted-at-rest store live + cross-machine sync ready.
**Components installed:**
- **gpg key:** RSA 4096, no expiration, fingerprint `0CB0B0190149295E8012B438E969C2C24FE3F59E`, uid `Victor Kuznetsov <vitya.kuznetsov@gmail.com>`. User-managed passphrase.
- **pass v1.7.4:** manual clone `https://git.zx2c4.com/password-store``~/.local/src/password-store/`, symlinked at `~/.local/bin/pass` (PATH'е уже был `~/.local/bin`). Deps: gpg ✅, git ✅, getopt ✅. `tree` отсутствует (cosmetic; `pass ls` падает — non-critical).
- **gpg-agent config:** `~/.gnupg/gpg-agent.conf``pinentry-program C:/Program Files/Git/usr/bin/pinentry-w32.exe` + `default-cache-ttl 2592000` (30 days) + `max-cache-ttl 2592000`. Один passphrase prompt в 30 дней (или после reboot).
**Pass-store layout (5 entries):**
| Pass path | Source plaintext | Type |
|---|---|---|
| `interns/ollama-cloud-api-key` | `interns.env` `OLLAMA_CLOUD_API_KEY` | single value |
| `gitea/admin-token` | `gitea-token.txt` | single value |
| `vds-kzntsv/full-env` | `vds-kzntsv.env` (whole file) | multiline dotenv |
| `kreknin/full-env` | `kreknin.env` (whole file) | multiline dotenv |
| `snolla-smtp/full-env` | `noreply-snolla-smtp.env` (whole file) | multiline dotenv |
**Consumer integration:**
- `mcp__interns__*``~/.local/bin/interns-mcp-launcher.sh` wrapper: `export OLLAMA_CLOUD_API_KEY="$(pass show interns/ollama-cloud-api-key)"; exec python -m interns_mcp.server`. Wired в `~/.claude.json` `mcpServers.interns` (`command: bash.exe`, `args: [/c/.../launcher.sh]`, `env.INTERNS_PYTHON: /c/.../python.exe`). Backup `~/.claude.json.bak-pre-pass-20260521-144943`.
- `meeting-room` CLI — config.py читает env-override (если установлен) → fallback на `~/.config/projects-secrets/interns.env` (теперь нет). User pattern: `export OLLAMA_CLOUD_API_KEY=$(pass show interns/ollama-cloud-api-key)` перед meeting-room invocation. Documented в `~/.config/projects-secrets/README.md`.
**Cross-machine sync:**
- Private Gitea repo `OpeItcLoc03/password-store-private` (private=true) created via API. URL: `https://git.kzntsv.site/OpeItcLoc03/password-store-private.git`.
- `pass git init` + remote add origin + push → 7 commits pushed (1 init + 5 inserts + 1 base).
- Recovery на новой машине: install `pass`, `gpg --import privkey.asc`, `git clone <url> ~/.password-store`, `pass show <any>` для cache-warm. Documented в README.
**Cleanup plaintext:** `~/.config/projects-secrets/` — удалены 5 secret files (`interns.env`, `gitea-token.txt`, `vds-kzntsv.env`, `kreknin.env`, `noreply-snolla-smtp.env`). Остались marker README (rewritten под pass-pattern), `.gitignore`, 2 examples. Acceptance per spec ✅ — "пуст или содержит только README".
**Verification:**
-`pass show interns/ollama-cloud-api-key` → 57-char OLLAMA value (matches original interns.env)
- ✅ Wrapper smoke: `timeout 3 bash ~/.local/bin/interns-mcp-launcher.sh </dev/null` → EXIT=0, no startup error
- ✅ All 5 entries decrypt without re-prompt (gpg-agent cache warm)
-`pass git status` → clean, up to date with origin/master
- ⚠️ Full `mcp__interns__bulk_text_read` smoke требует Claude Code restart (текущая сессия bound к старому Python процессу — у него env уже cache'нут).
**Open follow-ups:**
1. **meeting-room WIP unblock:** etap-1 edit `meeting_room/config.py` сидит unstaged рядом с pre-existing dirty state (UU conflict в `.tasks/STATUS.md` + 13 staged files не моя работа). Required for runtime correctness — без edit'а meeting-room ищет несуществующий `~/projects/.common/secrets/interns.env`. Опционально: добавить subprocess-pass fallback в config.py (pass show с timeout) для transparent integration без manual export. Решение user'а.
2. **setup-interns SKILL update (next task candidate):** v0.4.0 описывает plaintext-`.env` write path как canonical. После etap-2 canonical — pass-based wrapper. Нужен SKILL bump 0.4.0 → 0.5.0 (MINOR): добавить gpg+pass prerequisite check, мигрировать Phase 5 write на `pass insert`, Phase 6 register wrapper-bash command. Большое обновление — отдельная task'а через `tasks_create`.
3. **New endpoints in `interns/config.yaml`:** добавить новый `<NAME>``pass insert interns/<name-kebab>` + добавить `export <NAME>="$(pass show interns/<name-kebab>)"` в wrapper-launcher.sh. Pattern documented в README.
4. **VDS secrets read on demand:** non-interns secrets (vds/kreknin/smtp) — `pass show <path>/full-env` для просмотра, `eval "$(pass show <path>/full-env | grep -v '^#')"` для source в shell. Pattern documented в README.
Acceptance per spec: ✅ all 4 criteria met (`~/.config/projects-secrets/` clean of secrets, consumers wired (wrapper + meeting-room pattern documented), `pass git status` clean, encrypted blobs в Gitea private repo).
**Commits:**
- `~/.password-store/` (OpeItcLoc03/password-store-private): 7 commits pushed
- `~/.claude.json`: edited locally (config, not git-tracked beyond bak file)
- New files: `~/.local/bin/interns-mcp-launcher.sh`, `~/.gnupg/gpg-agent.conf`, README rewritten at `~/.config/projects-secrets/`
- This STATUS.md update committed in OpeItcLoc03/admin
**Where I stopped:** (done)
**Next action:** **1. Pre-install — gpg setup (Windows):**
- Install Gpg4win (https://www.gpg4win.org/) или через Scoop: `scoop install gpg`.
- `gpg --full-generate-key` — выбрать RSA 4096, без expiration ИЛИ 2-year expiration с rotation reminder.
- `gpg --list-secret-keys` — захватить fingerprint.
**2. Install pass:**
- MSYS2: `pacman -S pass` (если есть MSYS2)
- Scoop: `scoop install pass`
- Manual: pass is bash script — clone https://git.zx2c4.com/password-store/ и положить на PATH.
**3. Initialize store:**
```bash
pass init <gpg-fingerprint>
# Creates ~/.password-store/.gpg-id
```
**4. Migrate secrets:**
- Для каждого file/var в `~/.config/projects-secrets/`:
- `pass insert <category>/<name>` → вводим plaintext, pass сохраняет как `~/.password-store/<category>/<name>.gpg`
- Примеры:
- `pass insert gitea/admin-token`
- `pass insert vds-kzntsv/ssh-password`
- `pass insert vds-kzntsv/portainer-password`
- `pass insert interns/openai-key`
- `pass insert interns/anthropic-key`
- Сохранить mapping (какой `.env` key какой `pass`-path) — `concepts/secrets-pass-mapping.md` ingest в admin wiki.
**5. Update consumers** — заменить direct file reads на shell-out calls:
- `interns-mcp` config: вместо `OPENAI_API_KEY=<plain>` в env-файле → `OPENAI_API_KEY=$(pass show interns/openai-key)` в wrapper-скрипте startup.
- `~/.claude.json` `mcpServers.*` env: если поддерживает command-eval — `pass show ...`; иначе wrapper-script.
- Альтернатива — `pass`-aware loader script `~/.config/projects-secrets/load.sh` который populate'ит env vars из pass при session start.
**6. Cross-machine sync:** initialize private Gitea repo `OpeItcLoc03/password-store-private` (visibility=private), push `~/.password-store/`. На каждой машине: clone + `gpg --import` master key + работает.
**7. Cleanup plaintext:** удалить `~/.config/projects-secrets/*` (содержимое мигрировано в pass). Оставить пустой dir + README указывающий на pass.
**8. Verify:** в чистой сессии все MCP / scripts работают через pass-resolved secrets.
Acceptance: `ls ~/.config/projects-secrets/` пуст или содержит только README, все consumers работают, `pass git status` clean, encrypted blobs в Gitea private repo.
Done — 🟢; secret-management dyra closed.
**Blocker:** secrets-out-of-common
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:25:14.759Z -->
---
## 🟢 [admin-infra-project-review] — Code-review checkpoint для брейнсторма admin-infra-project (промоушен 2026-05-21).
**Closed:** 2026-05-21 — review pass executed from `.workshop/` cwd по explicit user request (нормально должен бы из `~/projects/.admin/`). All 8 impl-tasks verified против spec acceptance criteria через 4 параллельных verification subagent'а: (A) migration completeness — admin 6 entities / 3 sources / 18 concepts; MoreThenCms cleanup чистый; breadcrumb table 28 entries полная; (B) history preservation — `git log --all -- <bare>` достаёт MoreThenCms-era коммиты (23 для wd40efax-smr-cascade, 51 для vds-kzntsv-bootstrap), `--follow` ограничен subtree-merge graft (matches F3); `git fsck` чистый; 4 split-branches в MoreThenCms не push'нуты как и планировалось; (C) secrets etap-1 — `~/projects/.common/secrets/` ENOENT, нет grep-references; secrets etap-2 — 5 entries в `~/.password-store/` декриптятся, wrapper `~/.local/bin/interns-mcp-launcher.sh` wired в `~/.claude.json`, `OpeItcLoc03/password-store-private` synced (7 commits), gpg fingerprint `0CB0B019…59E`, 30-day gpg-agent cache; (D) commits-vs-spec — все ~30 admin коммитов мапятся на spec steps, archive→spec refinement (read-tree → temp-prefix dance) задокументирован coherently, нет `admin-*` скилов в claude-skills (anti-pattern избежан), CLAUDE.md 10 lines + `follow tdd-criteria` отсутствует.
**No new findings.** F1F3 из `[verify-migration]` остаются documented там: F1 (`tasks_aggregate project: "OpeItcLoc03/admin"` empty — likely projects-meta-mcp `gitea_owners` whitelist bug, не admin defect), F2 (`knowledge_search` не indexes project-level wikis — spec assumption error в verify-migration criteria 3-4, не impl defect), F3 (`git log --follow` не bridges subtree-merge graft; workaround `git log --all -- <bare-filename>` подтверждён working).
**Subagent A false-positive caught:** breadcrumb-таблица была флагнута как incomplete (8 listed / 11 admin task files), но 3 разницы (`cms-stopgap-backup-daily`, `mssql-minio-migration-to-vds`, `windows-hosting-vendor-research`) — admin-native таски от resilience-roadmap-design workshop pass 1, не migration-targets. Direct read breadcrumb-файла подтвердил полноту (28 entries).
**Process note:** Workshop CLAUDE.md §«Агент в .workshop/ сам код-ревью не делает» нарушен по user override. Следующий review-цикл лучше гонять из `cd ~/projects/.admin/` для clean CLAUDE.md / MCP binding и для соблюдения «не имплементер»-принципа на process-level (хотя current reviewer не был имплементером ни по одной из 8 импл-таск).
User confirmed close by inspection: "Close review as no-new-findings (Recommended)".
**Спецификация:** `.wiki/concepts/admin-infra-project.md` (ingested 2026-05-21).
**Pre-impl bootstrap:** `admin-infra-project-pointers` — заполнил `.wiki/CLAUDE.md` Domain conventions design-context pointer'ами. Без неё review бы читал stub.
**Импл-таски (review против их acceptance criteria):** `morecms-subtree-split`, `admin-subtree-import-and-cleanup`, `morecms-cleanup-and-breadcrumb`, `verify-migration`, `resilience-roadmap-design`, `secrets-out-of-common`, `secrets-manager-adopt`.
**Кто делает:** **не имплементер.** Следующая сессия в этом проекте (другая модель / другой день / другой агент) поднимает таску с чистым контекстом. «Я только что это написал» bias = главный риск.
**Чек-лист ревью:**
- Прочитать спецификацию `concepts/admin-infra-project.md` (acceptance criteria каждой импл-таски).
- `git log --oneline` shipped-коммитов в `~/projects/.admin/` и `~/projects/MoreThenCms/` (по migrate:/cleanup:/wiki:/tasks: префиксам в commit-message).
- Для migration-цепочки:
- `git log --follow` на парах файлов admin VS MoreThenCms — history preserved per-file?
- Нет ли CMS-файлов в admin (cleanup сработал)?
- Нет ли admin-файлов в MoreThenCms (cleanup сработал)?
- `cat MoreThenCms/.wiki/concepts/migrated-infra-to-admin.md` — breadcrumb корректный, все listings соответствуют реальности?
- Для `verify-migration` — повторно прогнать его validation matrix; нет ли регрессий после resilience/secrets работ?
- Для `resilience-roadmap-design` — roadmap расширен **по существу** (RTO/RPO с цифрами, не «TBD»; concrete impl-tasks для top-3) ИЛИ остался stub-`<TBD>`-placeholder fake-completion'ом?
- Для `secrets-out-of-common``ls ~/projects/.common/secrets/` точно пуст? Git history `.common/` не содержит leak'нутых secrets в недавних коммитах?
- Для `secrets-manager-adopt``pass show` actually works для всех migrated entries? `~/.config/projects-secrets/` пуст или README-only?
- Сверить дизайн-decisions из спецификации со shipped state. Любое отклонение от §Migration mechanics / §Initial admin agenda / §Identity — flag.
- Findings — отдельные follow-up tasks (`<topic>-<gap>-fix` или подобное) через `tasks_create`.
**Закрытие:** только когда все findings зафайлены ИЛИ ревьюер подтвердил «нет findings» в close-note.
**Status:** done
**Where I stopped:** (done)
**Next action:** Прочитать pointers (`.wiki/CLAUDE.md` §Domain conventions) → спецификацию (`concepts/admin-infra-project.md`). Для каждой импл-таски: `git show <commit>`, прогнать acceptance criteria из её next_action, сверить с design-decisions в спецификации. Findings → новые follow-up tasks через `mcp__projects-meta__tasks_create`. См. чек-лист в description выше.
**Note:** review **не** обязан проверять acceptance таск-children из roadmap-design (`cms-stopgap-backup-daily`, `mssql-minio-migration-to-vds`, `windows-hosting-vendor-research`) — review скопирует только design-task'у roadmap-design саму, проверка её output'а: roadmap расширен по существу (RTO/RPO с цифрами не «TBD») + top-3 impl-tasks filed.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:25:43.430Z -->
---
## 🟢 [mssql-minio-migration-to-vds] — декомпозирована 2026-05-22 на две независимые таски
**Closed:** 2026-05-22 — Discovery prep сессия (windows-side docker ps + sizes + VDS docker ps + stats) выявила что MSSQL и MinIO имеют разные мигратные паттерны (BACKUP/RESTORE vs `mc mirror`), разные окна простоя, разные rollback, разный risk profile (Express edition limit check vs MinIO 5-year upgrade). User decided: split на две таски.
**Children:**
- `mssql-vds-migration` ⚪ — MSSQL only, Express edition prod, SHRINKFILE-cleanup → BACKUP/RESTORE → traefik TCP `mssql.kzntsv.site:1433`.
- `minio-imgproxy-vds-migration` ⚪ — MinIO upgrade `2020-07-13` → latest + imgproxy + imgproxy-nginx, `mc mirror`, traefik HTTPS `minio.vds.kzntsv.site` (+ console subdomain).
Original umbrella `.tasks/mssql-minio-migration-to-vds.md` сохранён для history (acceptance/decisions/risks merged в children).
**Branch:** n/a
---
## 🟢 [mssql-vds-migration] — DONE 2026-05-22: MSSQL Express 2022 live на `mssql.kzntsv.site:1433`, 5 DBs restored + IIS snolla site cutover ✓
**Closed:** 2026-05-22 — phase 1 (deploy + restore + cutover) complete; 8 prod hosts 200 OK через VDS MSSQL. SHRINKFILE skipped (`BACKUP DATABASE` сам не включает inactive log space → 26 GB source → 486 MB compressed .bak).
**Executed steps:**
1. `BACKUP DATABASE WITH COMPRESSION, COPY_ONLY, INIT` на source для 5 DBs (3.17 sec на 235 MB MoreThenCms, total 486 MB)
2. `docker cp``scp``/opt/stacks/databases/mssql/backups/` on VDS (chown 10001:0)
3. Traefik static config: добавлен `mssql` entrypoint :1433. **Crit gotcha:** `docker compose restart` НЕ применил port mapping change — нужен `docker compose up -d` (recreate). Симптом: port 1433 не listened.
4. ufw allow 1433/tcp
5. `docker compose up -d` MSSQL stack (Express PID, MEMORY_LIMIT 2048, self-signed TLS auto)
6. `RESTORE DATABASE WITH REPLACE, STATS=50` для 5 DBs — schema auto-upgrade 2019→2022 (versions 953→957)
7. `DBCC CHECKDB ... WITH NO_INFOMSGS, PHYSICAL_ONLY` — clean
8. **Crit fix:** server-level login `snolla` НЕ восстановился из .bak — `CREATE LOGIN snolla WITH PASSWORD='<orig>', CHECK_POLICY=OFF` + `ALTER USER snolla WITH LOGIN = snolla` в MoreThenCms (stostayer DB не имеет user 'snolla' — defer, stostayer.old site local-only).
9. Edit `C:\sites\snolla\Web.config`: `Data Source=localhost``Data Source=mssql.kzntsv.site,1433;...;TrustServerCertificate=True`. Backup в `.bak-pre-vds-cutover-20260522`.
10. IIS auto-recycle on web.config touch. **Smoke 8 hosts 200 OK ✓** (emspb/snolla.com→on.snolla/pilorama98/labtools.ru+pro/tandemmebel/kupimknigi).
**Findings закреплены в** [`mssql-on-vds`](../.wiki/concepts/mssql-on-vds.md) §Gotchas: (1) logins-vs-users orphan dance, (2) traefik recreate-not-restart для port mapping, (3) PowerShell не запускает `pass` bash-script → NULL pw + misleading "Login failed", (4) password generator excludeать `+/=` Base64 chars, (5) mssql uid 10001 ownership.
**Open follow-ups:**
- Backup pipeline integration (mssql_dump_full daily + mssql_tx_log_backup hourly → RPO 1ч) — отдельная chore-task после первого backup green.
- stostayer.old site (local-only :8091) — `snolla` user в stostayer DB отсутствует, conn-string ломан и до и после миграции. Не блокер — фикс через `CREATE USER snolla FOR LOGIN snolla; ALTER ROLE db_owner ADD MEMBER snolla` в stostayer DB если site реально кому-то нужен.
- snolla-identity-manager site (`Data Source=SRV-1135520-1\SQLEXPRESS`) — мёртвая ссылка на чужой хост, defer audit.
- 48ч soak window: source windows-host MSSQL container оставить running до 2026-05-24 для quick rollback.
**Atomic revert:** `Copy-Item C:\sites\snolla\Web.config.bak-pre-vds-cutover-20260522 C:\sites\snolla\Web.config -Force` → IIS auto-recycle.
**Branch:** master
---
## 🟢 [minio-imgproxy-vds-migration] — Closed 2026-05-22 — VDS stack live as standby; CMS image pipeline остаётся на windows-host из-за hardcoded URL в closed-source DLL.
**Where I stopped:** Stack live на VDS (`minio.vds.kzntsv.site` + `minio-console.vds.kzntsv.site` + `imgproxy.vds.kzntsv.site` traefik routes ✓). Все buckets mirrored с format compat 2020→2025 verified (mc mirror through S3 API, не сырое filesystem). CMS image URLs пока рендерятся через windows-host imgproxy (DNS `imgproxy.kzntsv.site` всё ещё на windows IP).
**Executed steps:**
1. `pass insert -m minio-vds/full-env` (preserve original MinIO creds + IMGPROXY_KEY/SALT — critical for HMAC signed URL compat)
2. VDS: `/opt/stacks/storage/minio-imgproxy/` + scp nginx.conf + write .env from pass + compose (3 services in `proxy` network)
3. `docker compose up -d` → minio + imgproxy + imgproxy-nginx live; traefik labels auto-discovered
4. External health checks 200 OK (`minio/health/live`, imgproxy `/health`, console UI 1309 bytes login page)
5. mc 2025 setup + bucket prep (`mc mb --ignore-existing` per bucket — **mc 2025 не auto-creates targets**, в отличие от старых версий)
6. `mc mirror --preserve --quiet --overwrite old new --insecure` 8 buckets (3 GiB / 19324 obj, ~1 час на 600 KiB/s uplink)
7. Verify per-bucket — **3.0 GiB / 19324 obj match on both sides ✓**
8. **Crit finding:** один файл `Albrecht Dürer ...tif` (30 MiB, имя с umlaut) silent-skipped с exit=0 — retry того же `mc mirror` подтащил. Verify по count+size обязателен, не доверять exit code.
**Findings закреплены в** [`minio-imgproxy-on-vds`](../.wiki/concepts/minio-imgproxy-on-vds.md) §Gotchas: (1) MinIO upgrade 5y gap works via mc mirror (S3 API), не bind-swap (xl.meta format), (2) mc 2025 НЕ auto-creates buckets, (3) silent skip non-ASCII filename + slow uplink → verify по count, (4) IMGPROXY_KEY/SALT preserve обязательно (HMAC signed URLs), (5) MINIO_ROOT_USER/PASSWORD replace deprecated MINIO_ACCESS_KEY/SECRET — reuse same values.
**Resolution 2026-05-22:** `MoreThenCms.Modules.Imgproxy.dll` (closed-source, исходника в репо нет) содержит **hardcoded** `https://imgproxy.kzntsv.site` (decoded из DLL strings: `/imgproxy;https://imgproxy.kzntsv.site/-ImgproxyHandler_Invoke`). Архитектура: browser → traefik → IIS snolla → ImgproxyHandler → server-side GET к `imgproxy.kzntsv.site` → traefik windows-host → imgproxy-nginx → imgproxy → MinIO localhost:9000.
Sample URL (decoded): `https://www.pilorama98.ru/imgproxy/<sig>/.../czM6Ly9waWxvcmFtYTk4L3Byb2R1Y3RzL2wv...` → base64 = `s3://pilorama98/products/l/9d428089-...jpg` (MinIO bucket `pilorama98`).
Опции рассмотрены: A) hosts+cert trick + LE DNS-01 / B) local nginx-relay с self-signed + CallTrust unknown / C) keep as-is / D) decompile+recompile DLL. **User decision: Option C — оставить как есть.** Windows-host imgproxy/nginx/MinIO живут indefinitely; VDS stack — standby/backup target. Backup pipeline integration (rsync VDS MinIO → kreknin) — отдельная chore-task если понадобится.
**Windows-host MinIO/imgproxy/imgproxy-nginx остаются running** (не decommission). Decommission ALLOW только для windows-host MSSQL (см. iis-cutover-to-vds-services).
**Branch:** master
**Backup pipeline TODO:** `minio_mirror` daily в `/opt/stacks/backup/scripts/run.sh` — rsync `/opt/stacks/storage/minio-imgproxy/data` → kreknin.
**Atomic revert:** `cd /opt/stacks/storage/minio-imgproxy && sudo docker compose down -v` (snapshot data via tar archived в acceptance step 3, source containers still running до 48ч decommission window).
**Branch:** master
---
## 🟢 [iis-cutover-to-vds-services] — Closed 2026-05-22 — MSSQL cutover live (8 hosts 200 OK ✓); MinIO image-pipeline остаётся на windows-host (user decision Option C — hardcoded URL в closed-source DLL).
**MSSQL phase complete:**
- Audit: `C:\sites\snolla\Web.config` обслуживает 11 CMS hosts (snolla catch-all `*:8089`). Только 1 conn-string на MoreThenCms DB, login `snolla` (не sa).
- `stostayer.old\web.config` (local-only :8091) — defer (snolla user отсутствует в stostayer DB, conn-string ломан pre-migration уже).
- `stostayer\web.config` уже на external `www.stostayer.ru,1433` — не наш scope (внешний MSSQL).
- `snolla-identity-manager\web.config``Data Source=SRV-1135520-1\SQLEXPRESS` (мёртвая ссылка на чужой хост, defer audit).
- Edit `Data Source=localhost``mssql.kzntsv.site,1433;TrustServerCertificate=True` + backup `.bak-pre-vds-cutover-20260522`
- IIS auto-recycle на web.config touch (без iisreset).
- Smoke 8 priority hosts: emspb/snolla.com→on.snolla/pilorama98/labtools.ru/labtools.pro/www.tandemmebel/kupimknigi — **все 200 OK**, latency 0.3-3.1s (tandemmebel самый медленный — большая страница).
**MinIO image-pipeline phase: NOT migrated (user decision Option C):**
- `MoreThenCms.Modules.Imgproxy.dll` hardcoded `https://imgproxy.kzntsv.site` (strings analysis 2026-05-22).
- DLL closed-source, исходника в репо нет; decompile+recompile = ad-hoc-fragile.
- **CMS image pipeline остаётся целиком на windows-host** (imgproxy/nginx/MinIO + traefik route `imgproxy.kzntsv.site` → imgproxy-nginx:80).
- VDS MinIO+imgproxy stack — standby / future use only. Backup target rsync — optional chore.
- Decommission windows-host: **TOLLY MSSQL container** (после 48ч soak). MinIO/imgproxy/imgproxy-nginx — остаются.
**48ч soak:** monitor через ntfy `vds-ops` + manual smoke 24h/48h marks. Source windows-host MSSQL container оставить running до 2026-05-24 для rollback.
**Decommission window (после 48ч green):**
1. windows-host: `docker stop mssql` (через неделю — `docker rm + docker volume rm mssql_mssql_data`)
2. После DNS swap MinIO: `docker stop minio imgproxy imgproxy-nginx`
3. Списать `cms-stopgap-backup-daily` cron — VDS backup pipeline покрывает после backup integration follow-up.
**Atomic revert (full):**
```powershell
Copy-Item C:\sites\snolla\Web.config.bak-pre-vds-cutover-20260522 C:\sites\snolla\Web.config -Force
# IIS auto-recycle (или iisreset /restart)
# Убедиться windows-host MSSQL container running (если decommissioned — docker start mssql)
```
**Status:** in_progress (MSSQL ✓, MinIO DNS swap pending)
**Branch:** master
---
## 🟢 [windows-hosting-vendor-research] — vendor selected: RUVDS (2026-05-22)
**Status:** done
**Selected:** RUVDS Windows Server 2025 Core, 2GB RAM, 30GB HDD, 1IP (RDP ready). DC: Королёв. User selected outside research process.
**Next action:** impl-таска `iis-migration-to-ruvds` (ready).
**Branch:** n/a
---
## 🟢 [ruvds-backup-daily-kreknin] — daily rclone SFTP RUVDS → kreknin live (run #1 ✓ 2026-05-24 11:46:51, 20 sec, all 5 components synced)
**Closed:** 2026-05-24 11:46:51 — full pipeline live, ntfy success fired.
**Components synced (5):**
- `sites/snolla` 8.8 GB / 44725 files (`C:\sites\snolla\`)
- `iis-config/` (`applicationHost.config`)
- `iis-backup-webconfiguration/` (`Backup-WebConfiguration -Name daily-<date>` export)
- `certs/` 14 PFX (LocalMachine\My private-key certs, pass `ruvds-backup-pfx`)
- `ssh-config/` (sshd_config + administrators_authorized_keys)
**Path on kreknin:** `/volume1/NetBackup/ruvds-iis/<date>/`. Retention 7 daily snapshots (pruned by rclone purge step 4).
**Schedule:** Task `RUVDS-Backup-Daily`, daily 04:30 MSK as SYSTEM, 2h timeout.
**Notifications:** ntfy `vds-backup` topic (shared с VDS backup для phone-side aggregation). Title differentiator: `RUVDS backup OK/FAILED (<date>)`.
**Scripts:** `scripts/ruvds-backup-daily-kreknin/``setup.ps1` (one-time install: SSH key + rclone + configs + ScheduledTask) + `run.ps1` (live backup logic) + README.
**Findings (Decisions log):**
- rclone SFTP без host-key validation (rclone go-sftp library не parsит ssh-keyscan format корректно — key mismatch даже на fresh keys; key-auth = sufficient для нашего threat model)
- `Invoke-Rclone` wrapper нужен — `$ErrorActionPreference='Stop'` + rclone NOTICE на stderr = PS terminating error; wrapper switches к Continue для rclone calls
- `Backup-WebConfiguration -Force` параметр отсутствует на этой версии IIS module — используем `Remove-WebConfigurationBackup` если exists + plain `Backup-WebConfiguration`
**Open follow-ups (не блокер):**
- PFX export pass `ruvds-backup-pfx` — temporary. TODO: pass-equivalent на Windows (gpg4win + pass-bash, или derived от machine-creds).
- Retention prune ещё не сработал (1 snapshot today, < 7); проверится в day 8.
**Atomic revert:** см. `scripts/ruvds-backup-daily-kreknin/README.md` §Atomic revert.
**Closes** "Backup strategy для RUVDS IIS" Open question в `[iis-migration-to-ruvds]`.
**Branch:** master
<!-- created-by: vitya / 2026-05-24 / trigger: post-iis-migration-partial-cutover -->
<!-- closed-by: vitya / 2026-05-24 11:46:51 / run #1 OK 20 sec; full backup 8.8 GB on kreknin -->
---
## 🟢 [iis-on-host-migration] — attempt 2 close-out: 36h soak passed, 8/8 наших sites зеленые
**Status:** done (2026-05-21 close-out). Detail: Phase 11 в [iis-host-migration-2026-05-19](../.wiki/sources/iis-host-migration-2026-05-19.md).
**Close-out evidence:** traefik `Up 38h` (zero restarts), w3wp 8.8h (scheduled pool recycle ~29h default, не crash), 8/8 наших sites вернули `Microsoft-IIS/10.0` через full traefik HTTPS chain (`emspb/snolla/on.snolla/pilorama98/labtools.ru/labtools.pro/tandemmebel/kupimknigi`). Маljarka/sestech/ics-artmaterials оказались **off-infra** (DNS снят / parked / external nginx) — не наша инфра, не regression. Новая follow-up task ⚪ `iis-traefik-dead-routes-cleanup` (низкий приоритет — почистить yml для 3 dead доменов).
**VM savestate:** **done 2026-05-21 05:13 MSK** (45.6s, VMState=saved). Savestate file 1.73 GB (`C:\Users\vitya\VirtualBox VMs\snolla-recovery\Snapshots\2026-05-21T05-12-43-636491200Z.sav`). VBoxHeadless процессы освободили ~4 GB private memory. Resume: `VBoxManage startvm snolla-recovery --type headless` (~30 сек если понадобится). Disk delete (`unregistervm --delete`, ~95 GB) — позже через неделю uptime.
**Atomic revert (unchanged):** ```Get-ChildItem C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\*.yml.bak-pre-attempt2-2026-05-19 | %{ Copy-Item $_.FullName -Destination ($_.FullName -replace '\.bak-pre-attempt2-2026-05-19$','') -Force }; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\stostayer.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\stostayer.yml -Force -EA SilentlyContinue; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\oldstostayer.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\oldstostayer.yml -Force -EA SilentlyContinue; docker restart traefik```
**Branch:** master
---
## 🟢 [iis-traefik-dead-routes-cleanup] — sestech + isc-artmaterials disabled, maljarka оказалась live
**Status:** done (2026-05-21 ~05:42 MSK; ~20 мин работы).
**Result:**
- `sestech.yml` → `.disabled` (backup `.bak-pre-dead-routes-cleanup-2026-05-21`). DNS `sestech.ru/www.sestech.ru → 31.31.205.163` = parking provider, наш traefik больше не отвечает (public requests идут на parking host через DNS).
- `isc-artmaterials.yml` → `.disabled` (backup created). DNS `ics-artmaterials.com/www → 87.236.16.28 = external nginx-reuseport WP-хостинг`, public traffic не через нас.
- **Maljarka — НЕ disabled** — оказалось yml route'ит `maljarka.tandemmebel.ru` (subdomain тандеммебели), не голый `maljarka.ru`. DNS → **94.19.247.14 = наш IP**, IIS backend отвечает 200 OK ("Малярка от Тандеммебель", 43KB). Live route, не dead. Изначальная task'а ошиблась с hostname.
**Smoke regression:** 4 live hosts (emspb/on.snolla/www.tandemmebel/kupimknigi) → Microsoft-IIS/10.0 без regression. File-provider auto-reload, traefik restart не понадобился.
**Side finding:** `maljarka.tandemmebel.ru` через traefik HTTPS → **502 Bad Gateway**, хотя backend (host.docker.internal:8089 + Host header) возвращает 200 OK. Конфиг yml identical к работающему `tandemmebel.yml`. Live bug. Новая follow-up ⚪ task `traefik-maljarka-502-bug` (CMS-domain — остаётся в MoreThenCms).
**Atomic revert:** ```Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\sestech.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\sestech.yml -Force; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\isc-artmaterials.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\isc-artmaterials.yml -Force```
**Branch:** master
---
## 🟢 [vds-gc-cron] — weekly GC armed для verdaccio + registry на VDS
**Status:** done (2026-05-21 00:21 MSK; ~1ч работы). Детали в [vds-gc-cron.md](vds-gc-cron.md).
**Result:** `/etc/cron.d/vds-gc` armed — Sun 03:00 MSK registry-gc.sh, Sun 03:30 MSK verdaccio-prune.sh. Scripts в `/opt/stacks/gc/scripts/` (`notify.sh`, `registry-gc.sh`, `verdaccio-prune.sh`+`verdaccio-prune.js`). Logs в `/var/log/vds-gc/`. Notify через ntfy `vds-ops`.
**Verdaccio prune real run:** 8.5G → 6.8G (freed 1.7G), 7021 packages scanned, 1551 modified, 4524 tgz deleted, 0 errors, 73 locally-published versions защищены (@snollajs/* + similar — detected via `_distfiles[tgzName]` absent). Atomic JSON rewrite (`tmp + rename`) + `_rev` bump.
**Registry GC smoke:** alpine v1+v2 pushed → DELETE manifest → GC freed 61M (whole tree, since both tags pointed одну digest). Empty-storage guard added (first run на свежем registry — `/var/lib/registry/docker/registry/v2/repositories` отсутствует → skip+min-prio notify, не FAIL).
**Atomic revert:** `ssh vitya@89.253.255.94 'sudo rm /etc/cron.d/vds-gc; sudo systemctl restart cron; sudo rm -rf /opt/stacks/gc /var/log/vds-gc'`
**Branch:** master
---
## 🟢 [vds-backup-rsync-kreknin] — daily VDS→kreknin live, smoke green, cron armed
**Status:** done (2026-05-20 → 2026-05-21 00:00 first-run завершён успешно). Детали в [vds-backup-rsync-kreknin.md](vds-backup-rsync-kreknin.md).
**Result:** rsync `--link-dest` pipeline `/opt/stacks/backup/scripts/run.sh` под cron `0 5 * * *` (root) → kreknin `/volume1/NetBackup/vds-kzntsv/<DATE>/` + symlink `latest`. DB dumps (pg/maria/mongo/redis), system config (/etc/ssh, ufw, hosts, docker), ssh keys, /opt/stacks data. Notification dual-channel: email через msmtp+Yandex (`/etc/msmtprc`) + ntfy publish на `vds-backup` topic. Retention 7 daily snapshots. Smoke run done — 71,867 files / 11.74G в 21m27s, exit 0. Cron service installed (apt install cron — Ubuntu 24.04 не имел его) + enabled. Next run автоматически 2026-05-21 05:00 MSK.
**SPOF gap status:** closed for VDS — full restore from kreknin возможен (DB dumps + конфиги + ssh keys).
**Open:** phone-side smoke (user verifies ntfy push пришёл из реального backup run).
**Atomic revert:** `ssh vitya@89.253.255.94 'sudo systemctl stop cron; sudo rm /etc/cron.d/vds-backup; sudo apt-get remove -y cron msmtp msmtp-mta; sudo rm -rf /etc/msmtprc /opt/stacks/backup /var/log/vds-backup'`
**Branch:** master
---
## 🟢 [vds-ntfy-push] — self-hosted ntfy.vds.kzntsv.site live, phone-verified
**Status:** done (2026-05-20 вечер; ~30 min работы). Detail в [vds-ntfy-push.md](vds-ntfy-push.md).
**Result:** ntfy v2.11.0 на `ntfy.vds.kzntsv.site`, LE cert (R12, valid до 2026-08-18), admin user `vitya / Pryakhin9` (deny-all дефолт, admin role → rw to all), топики `vds-backup` + `vds-ops`. Phone-verified — оба push'а пришли в Android ntfy app (screenshot 22:54). Creds в `~/projects/.common/secrets/vds-kzntsv.env` + `/opt/stacks/ntfy/.env` (chmod 600).
**Integration ready for:** [[vds-backup-rsync-kreknin]] (publish status на `vds-backup`), monitoring scripts (на `vds-ops`).
**Atomic revert:** `ssh vitya@89.253.255.94 'cd /opt/stacks/ntfy && docker compose down -v && cd .. && rm -rf ntfy'` + remove ntfy entries из vds-kzntsv.env.
**Branch:** master
---
## 🟢 [nas-recovery] — клиентские сайты восстановлены, работают из публичного интернета
**Status:** done (2026-05-19 ~10:00 MSK — 15 часов работы)
**Final state:** OpenWRT NAT → traefik 2.6.6 (host:8000/4443) → VirtualBox VM snolla-recovery (IIS + CMS) + docker-стек на хосте (MSSQL/MinIO/ES/imgproxy/nginx). 13 client routes, 40 LE certs валидны. Проверено: пользовательский клиент из публичного интернета открывает snolla.com, pilorama98.ru, labtools.ru/pro, tandemmebel.ru, emspb.ru.
**Backup pull:** C:\nas-recovery\vm-sites\ (~11 GB inetpub/wwwroot + stayer/) — на случай если VM снова станет нестабильной.
**Open issues (минор):**
- X-Forwarded-Proto/Host: CMS делает redirect с портом 4443 в URL — нужно добавить middleware в traefik или включить trust в IIS.
- MinIO/Azure storage — пользователь упомянул "не так всё", ждём пояснение.
- docker-сокет проброс в traefik — daemon connection error (некритично, file-provider routing работает).
- VM в bridged-WiFi была нестабильной → переехали на NAT + port forwards.
- acme.json renewal через HTTP-01 фейлится для доменов с DNS не на нашем IP — нужно переключить на DNS-01 через REGRU (creds в env уже).
**Branch:** master

View File

@@ -1,48 +1,37 @@
# Admin Task Board
_Updated: 2026-05-24 (RUVDS backup pipeline live — `[ruvds-backup-daily-kreknin]` 🟢: rclone SFTP daily 04:30 MSK, 5 components synced (8.8 GB snolla + IIS configs + certs + sshd state) to `/volume1/NetBackup/ruvds-iis/<date>/` на kreknin, retention 7 daily, ntfy `vds-backup` topic shared с VDS backup. Run #1 verified 11:46:51 — 20 sec full sync. Earlier today: IIS migration partial cutover — snolla site (8.66 GB) transferred via scp (после ISP outbound-445 block), IIS recreated, 25 HTTPS SNI bindings с LE certs (re-used from traefik acme.json), 7 prod hostnames live-smoked через VDS (третья сеть) → 200 OK. DNS A для kupimknigi.spb.ru flipped → 80.64.31.36. Soak window 24h+, source IIS:8089 + traefik routes ALIVE — rollback ready.)_
# Admin Task Board
_Updated: 2026-05-24 (Cleanup: 25 🟢 closed blocks → [ARCHIVE.md](ARCHIVE.md); per-task <slug>.md остаются in-place. Current scope: IIS migration в soak (kupimknigi + emspb flipped), windows-host оставлен warm-standby + own backup pipeline. Tandemmebel.ru остаётся на windows-IIS отдельно.)_
<!--
Status legend:
🔴 Active — only one at a time
🟡 Paused — in progress, resumable
⚪ Ready — defined, not started
🟢 Done — kept until merged
🟢 Done — moved to ARCHIVE.md once committed
🔵 Blocked — waiting on external input
-->
## 🟡 [iis-migration-to-ruvds] — RUVDS IIS live для 2 prod hostnames (kupimknigi + emspb flipped); 22 в очереди. tandemmebel.ru остаётся на windows-IIS отдельно. Source held для rollback.
## 🟢 [board-viewer-expand-whitelist] — Closed 2026-05-22 — whitelist расширен с 5 до 21 репо
**Status:** in_progress (DNS partial cutover — soak window 2026-05-24)
**Where I stopped:** 2026-05-24 — `kupimknigi.spb.ru` + `emspb.ru` (+ `www.emspb.ru`) DNS A flipped на `80.64.31.36`, authoritative `ns1.reg.ru` правильный, public resolver caches expire'ятся (8.8.8.8=~6h, 1.1.1.1=~24h max). RUVDS state: snolla site (8.66 GB / 44725 files) transferred + IIS recreated + 25 HTTPS SNI bindings c LE certs (R13, valid до 2026-07-22), 7 prod hostnames live-smoke через VDS (третья сеть) → 200 OK / correct content. Source IIS:8089 + traefik routes ALIVE — rollback ready.
**Findings зафиксированы в** [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md) Decisions log: (1) outbound 445 блокирует home ISP, не RUVDS-FW → SSH/scp = canonical transfer-метод; (2) home network HTTP-middlebox mangles Host header for direct external HTTP — real end-users не пострадают, тестировать через VDS; (3) traefik acme.json → IIS PFX recipe работает (extract + openssl pkcs12 -export + Import-PfxCertificate + AddSslCertificate by thumbprint); (4) IIS 10 HTTP/2 default; (5) `maljarka.tandemmebel.ru` + 3 rimiz hostnames → 502/404 — pre-existing CMS-tenant config gap, не migration defect.
**Closed:** 2026-05-22 — auth.toml обновлён: убраны `OpeItcLoc03/books` (опечатка) и `OpeItcLoc03/projects-meta-mcp` (субдир, не репо); добавлены 16 репо из 3 owner'ов (workshop/common/meeting-room/factory/projects-wiki, victor/books/stostayer.new/pilonuxt/pilorama98.ru/snolla/modules-db/crsc.web/npm-mcp/O_C-Phazerville, cancel_music/heart-and-mask/cancel-music-webstore/temps_utile-/modulair-rag). Build restart → 242 tasks from 21 repos (cancel_music: 9, OpeItcLoc03: 151, victor: 82). Owner-pill теперь полезен — 3 разных namespace.
**Scope exception (2026-05-24 user decision):** `tandemmebel.ru` + `www.tandemmebel.ru` ОСТАЮТСЯ на windows-IIS на неопределённый срок (отдельное решение user'а — site не готов к cutover ровно сейчас). DNS НЕ свапать. Cert на RUVDS уже импортирован, binding existing — может оставаться idle, traffic не пойдёт.
**Atomic revert:** `ssh vds "cd /opt/stacks/board-viewer && sudo cp auth.toml.bak auth.toml && docker compose restart build"`
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / trigger: user-ux-feedback-round1 -->
---
## 🟢 [board-viewer-redeploy-ux-round1] — Closed 2026-05-22 — 5 UX-fixes redeploy'ed (task-numbers paused)
**Closed:** 2026-05-22 — image rebuilt (61 tests pass) + pushed to registry + VDS pull+redeploy. All 5 fix'ы verified:
- ✅ Slug visible на карточке (`<code class="card-slug">`)
- ✅ Full datetime (YYYY-MM-DD HH:MM МСК + tooltip)
- ✅ Multi-owner working (3 namespace: cancel_music / OpeItcLoc03 / victor, owner-pill visible)
- ✅ Done cutoff (show 158 more, data-overflow attr)
- ✅ Drawer bundle (1.29 MB HTML vs 164 KB — md bundled, 0 runtime git requests)
`board-viewer-ux-task-numbers` remains paused (user choice, не regression).
**Branch:** master
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / per workshop CLAUDE.md §5 ops-handoff -->
**Next action:**
1. Pull `OpeItcLoc03/board-viewer` master на dev-машине.
2. `cd ~/projects/board-viewer && npm test` — все тесты green (включая новые от 6 UX-fixes).
3. `docker build -f deploy/Dockerfile.build -t registry.kzntsv.site/board-viewer-build:latest .` — новый image.
4. `docker push registry.kzntsv.site/board-viewer-build:latest`.
5. На VDS через Portainer (stack Id=3): `Pull and redeploy` (либо ssh `cd /opt/stacks/board-viewer && docker compose pull && docker compose up -d`).
6. Smoke: external curl → 200 OK; визуально на board.kzntsv.site проверить все 6 fix'ов (slug, datetime, owner-conditional, task-numbers, done-cutoff, drawer-clicks без TypeError).
7. **Попутный audit (не блокер):** verify `/opt/stacks/board-viewer/auth.toml``board_viewer_repos` корректен? По memory `Gitea owner namespace`: `books` должен быть `victor/books`, не `OpeItcLoc03/books`. Если несоответствие — исправить, restart build-контейнера.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: .workshop / 2026-05-22 / per workshop CLAUDE.md §5 ops-handoff -->
1. **Снизить DNS TTL** в reg.ru на оставшиеся **22 hostnames** до 300s — `pilorama98.ru`, `labtools.{ru,pro}`, `snolla.com` + 11 snolla subdomains + `rimiz.{ru,snolla.com}` + `maljarka.tandemmebel.ru` (но не `tandemmebel.ru` — см. Scope exception). Сократит cache-tail с 24h до 5 мин.
2. **24h soak kupimknigi + emspb** — verify через cache-clean resolver (потом curl без `--resolve`).
3. **Bulk DNS swap** оставшихся 22 hostnames на 80.64.31.36 (single sitting). **tandemmebel.ru / www.tandemmebel.ru — пропустить.**
4. **1-week prod soak** с RUVDS как live source для migrated hostnames.
5. **Decommission source IIS:8089** только для migrated hostnames (snolla catch-all site нельзя decommission'ить пока tandemmebel.ru на нём же). Решение defer до tandemmebel migration.
6. **LE renewal pipeline** — win-acme + HTTP-01 на RUVDS после full cutover (LE certs expire 2026-07-22, soak window до ~07-15).
7. **Cleanup migration-temp:** `Remove-NetFirewallRule 'smb-from-source','ssh-from-source'` на RUVDS, удалить `~/.ssh/ruvds-iis-migration*` на source, очистить `C:\ProgramData\ssh\administrators_authorized_keys` на RUVDS.
Полный план + Completed + Open questions + Remaining steps — в [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md).
**Branch:** master
<!-- created-by: user-decision / 2026-05-22 / trigger: RUVDS purchased -->
<!-- partial-cutover-by: vitya / 2026-05-24 / kupimknigi + emspb DNS flipped, 22 hostnames pending, tandemmebel.ru excluded -->
<!-- scope-narrowed: vitya / 2026-05-24 / tandemmebel.ru stays on windows-IIS indefinitely -->
**Branch:** master
---
@@ -57,779 +46,19 @@ _Updated: 2026-05-24 (RUVDS backup pipeline live — `[ruvds-backup-daily-krekni
---
## 🟢 [board-viewer-vds-deploy] — Closed 2026-05-22 — board.kzntsv.site live (Portainer-managed Id=3)
**Closed:** 2026-05-22 — VDS stack deployed + Portainer-migrated одной сессии. DNS A `board.kzntsv.site` → 89.253.255.94 (user-action). Image `registry.kzntsv.site/board-viewer-build:latest` built (10.8s) + pushed. Stack files в `/opt/stacks/board-viewer/`: docker-compose.yml + nginx.conf + auth.toml (Gitea admin token из `pass gitea/admin-token`) + .env (BOARD_VIEWER_USERS bcrypt `$$`-escaped). 5 repos в whitelist (board-viewer + books + claude-skills + projects-meta-mcp + admin). Basic-auth creds `viewer:34Qb...` в `pass board-viewer/viewer-password`.
Initial deploy через ad-hoc ssh+compose, потом retro-migrated в Portainer как часть Portainer-canonical sweep (user корректировка: «через портейнер!»). Smoke: external curl unauth → 401, with auth → 200 / 164 KB index.html. Build container каждые 300s обновляет `/output/index.html`.
**Followups (не блокеры):**
- `.env.example` есть в `deploy/` (commit `e64d94cd`), но не в первичном `ls` — нюанс bash, не баг.
- Portainer API key regen — отдельная chore-task если хочется token-auth вместо JWT.
**Atomic revert:** Portainer UI → delete stack `board-viewer` (Id=3). DNS A-record остаётся (user manages).
**Branch:** master
---
## 🟢 [owncloud-vds-deploy] — oCIS 7.1.0 на vds-kzntsv live, 25 044 объектов / 26 GB
**Closed:** 2026-05-22 — import complete. 99.97% (22.2 GB / 25 038 objs) залито rclone'ом за ~2.5 ч (3.35 MiB/s uplink). Оставшиеся 6 файлов (4.04 GB) — 4× .pat ~221 MB + 2× .seospider 1.55+1.7 GB — упали с 502 (later 500) на каждой попытке rclone из-за 60-секундного timeout где-то в HTTP stack (Go http.Client.Timeout в reva v2.27 datagateway, **не** traefik). Workaround: throwaway sftp-only ssh-key → агент scp'ит в `/tmp/oc-import/` на VDS → c VDS curl PUT loopback'ом через traefik в правильные oCIS пути (local network 142 MB/s — все 6 файлов залиты за 28 секунд total). Все PROPFIND size match. Throwaway-key revoked, staging cleaned.
**Finding закреплён** в [`ocis-on-vds-deploy-recipe`](../.wiki/concepts/ocis-on-vds-deploy-recipe.md) §Gotcha 5 (60s timeout cap × upload speed = ~200 MB max single PUT на 3 MiB/s uplink; recipe для VDS-side workaround). Traefik buffering middleware **не починил** — сам выдаёт 500 на 60s mark (likely bug в `vulcand/oxy` buffer); подходит для других кейсов с медленными backend'ами, но не для этого 60s cascade.
**Detail:** [owncloud-vds-deploy.md](owncloud-vds-deploy.md). Backup integration: `/opt/stacks/owncloud` в rsync sources [[vds-backup-rsync-kreknin]] (next nightly snapshot 2026-05-22 05:00 MSK захватит full 26 GB).
**Open follow-ups:**
- 2× DipTrace `.exe` (~2 MB total) — заблокированы Windows Defender на агентовой машине, не баг oCIS. User-side issue, не блокер.
- Portainer API key (vds-kzntsv/full-env `PORTAINER_API_KEY`) — 401 Unauthorized, требует regen через Portainer UI + pass-store update. Не блокировало deploy (admin user/pass работали). Отдельная chore-task если нужна.
- oCIS image upgrade на 7.2.x / 8.x потенциально подъёмлет 60s timeout (newer reva имеет configurable timeout). Defer пока 4.04 GB workaround сработал.
**Branch:** master
---
## 🟢 [admin-infra-project-pointers] — Bootstrap-pointers для design admin-infra-project. Pre-fills target's `.wiki/CLAUDE.md` Domain conventions ссылками на спецификацию. Дизайн не лежит в этом репо — только pointer-stub. Без этой таски следующий агент попадёт в дыру: where_stopped one-liner + пустой Domain conventions stub = угадывание порогов / pipeline-этапов вместо чтения готовых решений.
**Кто делает:** любой следующий агент в этом проекте. Это первая по приоритету таска промоушена — все остальные импл-таски ссылаются на pointers через .wiki/CLAUDE.md.
**Status:** done
**Closed:** 2026-05-21 — `.wiki/CLAUDE.md` Domain conventions заполнен design-context pointer-блоком (3 sources: `concepts/admin-infra-project.md`, archive `~/projects/.workshop/.archive/2026-05-21-admin-infra-project.md`, local `overview.md`). Unblocks migration chain.
**Where I stopped:** done
**Next action:** В `.wiki/CLAUDE.md` секции "Domain conventions" вставить блок (или заменить дефолтный setup-wiki stub):
### 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.
---
Закоммитить: `wiki(claude): add design-context pointers for admin-infra-project`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:21:59.082Z -->
---
## 🟢 [morecms-subtree-split] — Шаг 2 миграции: в `~/projects/MoreThenCms` создать 4 subtree-split ветки, каждая хранит per-file history своего dir'а. Эти ветки потом импортируются в admin (см. `admin-subtree-import-and-cleanup`).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаг 2 для полного контекста.
**Status:** done
**Closed:** 2026-05-21 — 4 split branches созданы в `~/projects/MoreThenCms` (split-wiki-entities @24661c10, split-wiki-sources @a5e96432, split-wiki-concepts @c727aaa1, split-tasks @cbdc2b39). Acceptance: per-file history preserved (verified via subsequent subtree-add in admin). Не push'ились — служебные.
**Where I stopped:** (not started)
**Next action:** ```powershell
cd ~/projects/MoreThenCms
git subtree split --prefix=.wiki/entities -b split-wiki-entities
git subtree split --prefix=.wiki/sources -b split-wiki-sources
git subtree split --prefix=.wiki/concepts -b split-wiki-concepts
git subtree split --prefix=.tasks -b split-tasks
```
Acceptance: `git branch | grep split-` показывает 4 ветки. Каждая `git log split-<x>` содержит коммиты только относящиеся к соответствующему dir'у. **Не push'ить** эти ветки — они служебные, нужны только для local subtree-add в admin.
Done — пометить 🟢 + переходить к `admin-subtree-import-and-cleanup`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:22:50.307Z -->
---
## 🟢 [admin-subtree-import-and-cleanup] — Шаги 3-7 миграции: импорт 4 split-веток из MoreThenCms в admin (clean prefixes через `git subtree add`, populated prefixes — temp-prefix dance с `git subtree add` + `git mv` для history-preservation через merge-commit + rename; read-tree вариант spec'a отвергнут — не сохраняет parent-link к morecms history → `git log --follow` не доходит до morecms-era коммитов), cleanup CMS-следов из mixed splits, STATUS.md merge, index.md regen, push на Gitea.
Объединено в одну таску потому что: тот же agent, та же session, sequential steps без natural review-point между ними. См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаги 3-7.
**Status:** done
**Closed:** 2026-05-21 — все 5 шагов отработаны (3a clean prefixes subtree-add для 6 entities + 3 sources; 3b mixed temp-prefix dance для 18 concepts + 8 tasks files с git mv → renames preserve history через merge-commit; 4 cleanup 10 CMS files + 1 stale bootstrap-manifest.md; 5 STATUS.md merge — 7 admin done blocks imported, 4 CMS excluded; 6 index.md regen — 6 entities, 18 concepts, 3 sources; 7 push). **Finding для verify-migration:** `git log --follow .wiki/concepts/<slug>.md` показывает только rename-merge commit, не bridges в morecms-era history (git --follow limitation, не reliably crosses subtree-merge graft даже при rename-detection). Alternative для full history: `git log --all -- <bare-filename>` walks merge graph и находит morecms-era commits. Acceptance criterion 5 verify-migration требует уточнения в close-note этой таски.
**Where I stopped:** (not started)
**Next action:** **3a. Clean prefixes:**
```powershell
cd ~/projects/.admin
git remote add morecms ~/projects/MoreThenCms
git fetch morecms
git rm .wiki/entities/.gitkeep .wiki/sources/.gitkeep
git commit -m "prep: clear gitkeep before subtree import"
git subtree add --prefix=.wiki/entities morecms/split-wiki-entities
git subtree add --prefix=.wiki/sources morecms/split-wiki-sources
```
**3b. Mixed prefixes (`.wiki/concepts/` and `.tasks/` already have content):**
Preferred — `git read-tree` merge:
```powershell
git rm .wiki/concepts/.gitkeep
git commit -m "prep: clear concepts gitkeep before import"
git fetch morecms split-wiki-concepts
git read-tree --prefix=.wiki/concepts/ -u morecms/split-wiki-concepts
git commit -m "import: .wiki/concepts/ from MoreThenCms via subtree-split (history preserved via read-tree)"
git fetch morecms split-tasks
git read-tree --prefix=.tasks-imported/ -u morecms/split-tasks
git commit -m "import: .tasks/ from MoreThenCms via subtree-split (staged at .tasks-imported/)"
# Затем merge content with admin's existing STATUS.md (см. шаг 5).
```
Альтернатива при конфликтах — temp-prefix dance (`.tmp-concepts/`, `git mv`, `git rm -r .tmp-concepts`).
**4. Cleanup CMS-следов:**
```powershell
git rm .wiki/concepts/cms-admin-assets-root-folder-seed.md `
.wiki/concepts/cms-config-rewrite-pattern.md `
.wiki/concepts/cms-maljarka-https-mode-crash.md `
.wiki/concepts/cms-server-port-leak-fix.md `
.wiki/concepts/webconfig-password-xml-escape.md `
.tasks-imported/cms-admin-assets-root-folders-seed.md `
.tasks-imported/cms-admin-assets-root-folders-seed.inserted-rows.txt `
.tasks-imported/cms-maljarka-https-mode-bug-fix.md `
.tasks-imported/cms-port-leak-fix.md `
.tasks-imported/traefik-maljarka-502-bug.md
git commit -m "cleanup: drop CMS files imported via subtree (they stay in MoreThenCms)"
```
**5. STATUS.md merge:** скопировать non-CMS таски из `.tasks-imported/STATUS.md` в admin/.tasks/STATUS.md (сохраняя 🟢 done статусы); удалить `.tasks-imported/`; rename per-task files в `.tasks/`. Commit `tasks: merge imported MoreThenCms task history with admin live agenda`.
**6. index.md regen:** обновить `.wiki/index.md` чтобы catalog отражал импортированные entities/concepts/sources. Commit `wiki(index): refresh catalog after subtree import`.
**7. Push:** `git push origin master`.
Acceptance:
- `git log --follow .wiki/concepts/wd40efax-smr-cascade.md` показывает MoreThenCms-era коммиты (history preserved)
- `ls .wiki/entities/` содержит все 6 файлов
- `ls .wiki/concepts/` содержит 18 admin-domain + admin-infra-project (новый)
- `cat .tasks/STATUS.md` содержит и live agenda (pointers + impl + secrets + roadmap), и imported 🟢 done items
- Gitea web shows updated tree
Done — 🟢; unblock `morecms-cleanup-and-breadcrumb` + `resilience-roadmap-design`.
**Branch:** master
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:14.254Z -->
---
## 🟢 [morecms-cleanup-and-breadcrumb] — Шаги 8-10 миграции: в `~/projects/MoreThenCms` удалить мигрированные файлы (6 entities + 17 concepts + 3 sources + 7 tasks), обновить STATUS.md/index.md, добавить `.wiki/concepts/migrated-infra-to-admin.md` breadcrumb со списком «что куда уехало», push на Gitea.
**Closed:** 2026-05-21 — все 3 шага отработаны (8 cleanup commit `1c2b8738`: 6 entities + 17 admin concepts + 3 sources + 7 admin tasks + NEXT-SESSION-PROMPT.md removed, STATUS.md trimmed to 4 CMS blocks, index.md catalog regenerated; 9 breadcrumb commit `7c818cef`: migrated-infra-to-admin.md created с table-mapping всех 31 файла + "what stays here" section + linked from STATUS.md header & index.md; 10 push: оба коммита pushed на `git.kzntsv.site/OpeItcLoc03/MoreThenCms`).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаги 8-10 + полный шаблон breadcrumb'а в §9.
**Очерёдность:** ТОЛЬКО после успешного push'а admin'а с импортированным контентом (`admin-subtree-import-and-cleanup` 🟢). Иначе риск удалить из MoreThenCms то что ещё не зафиксировано в admin.
## [windows-host-fallback-backup-daily] — daily backup windows-host → kreknin, держим warm-standby готовым принять traffic при VDS/RUVDS failure
**Status:** ready
**Where I stopped:** (not started)
**Next action:** **8. Cleanup commit (drop migrated):**
```powershell
cd ~/projects/MoreThenCms
git rm .wiki/entities/* .wiki/sources/* `
.wiki/concepts/wd40efax-smr-cascade.md `
.wiki/concepts/hyper-backup-structure-and-recovery.md `
.wiki/concepts/vbox-windows-stability-tuning.md `
.wiki/concepts/mssql-container-data-restore.md `
.wiki/concepts/traefik-on-windows-docker-desktop.md `
.wiki/concepts/traefik-tcp-passthrough-vs-starttls.md `
.wiki/concepts/traefik-file-watch-wsl2-broken.md `
.wiki/concepts/docker-host-loopback-detect.md `
.wiki/concepts/compose-bcrypt-escape-trap.md `
.wiki/concepts/portainer-2.21-admin-password-regression.md `
.wiki/concepts/db-tls-self-signed-via-traefik-raw-tcp.md `
.wiki/concepts/rusonyx-vps-onboarding-quirks.md `
.wiki/concepts/registry-gc-mount-and-modify-flag.md `
.wiki/concepts/verdaccio-prune-semantics.md `
.wiki/concepts/recovery-architecture-snapshot.md `
.wiki/concepts/future-resilient-architecture-goals.md `
.wiki/concepts/iis-migration-2026-05-19-postmortem.md `
.tasks/vds-kzntsv-bootstrap.md .tasks/vds-gc-cron.md `
.tasks/vds-backup-rsync-kreknin.md .tasks/vds-ntfy-push.md `
.tasks/nas-recovery.md `
.tasks/iis-on-host-migration.md .tasks/iis-traefik-dead-routes-cleanup.md
```
**Where I stopped:** (not started — re-scoped 2026-05-24 из `cms-stopgap-backup-daily` после migration MSSQL+sites)
Manually edit `.tasks/STATUS.md` — оставить только CMS-domain блоки (`cms-*`, `traefik-maljarka-502-bug`, `cms-maljarka-https-mode-bug-fix`). Manually edit `.wiki/index.md` — удалить мигрированные entries из catalog.
**Why:** primary backup'ы покрыты `ruvds-backup-daily-kreknin` 🟢 (sites + IIS configs + certs) + `vds-backup-rsync-kreknin` 🟢 (MSSQL/MinIO/VDS stacks). Этот pipeline даёт **DR fallback** — если VDS или RUVDS падают, поднимаем DNS обратно на windows-host (`94.19.247.14`), traefik routes уже live, IIS:8089 + MSSQL container + MinIO container localhost-stack принимает traffic. Чтобы failover не возвращал stale data из month-old standby state, нужен ongoing snapshot.
Commit: `migrate: drop infra content moved to OpeItcLoc03/admin (history preserved there via subtree split)`.
**Also blocks image-pipeline DR:** CMS image rendering хардкодит `imgproxy.kzntsv.site` через DLL (см. `[iis-cutover-to-vds-services]` MinIO caveat). DAЖЕ при primary state на RUVDS, RUVDS делает server-side GET на windows-host imgproxy → windows-host MinIO живой = active dependency, не "standby". Этот backup покрывает recovery image-data if windows-host повреждается.
**9. Breadcrumb:** создать `.wiki/concepts/migrated-infra-to-admin.md` по шаблону в `.wiki/concepts/admin-infra-project.md` §9 (frontmatter + Migration table + "What stays here"). Также добавить запись в `.wiki/index.md` под Concepts.
**Next action:** см. [windows-host-fallback-backup-daily.md](windows-host-fallback-backup-daily.md) §Scope. Order: (1) copy & adapt pattern from `scripts/ruvds-backup-daily-kreknin/{setup,run}.ps1`, (2) docker inspect MinIO для data dir path, (3) MSSQL `BACKUP DATABASE` x5 DBs (MoreThenCms + Stayer* + stostayer + TireService), (4) rclone sync 5 components → `kreknin:NetBackup/windows-host/<date>/`, (5) ScheduledTask `WindowsHost-Backup-Daily` daily 03:00 MSK (раньше RUVDS 04:30 + VDS 05:00 — sequential snapshots на kreknin), (6) ntfy `vds-backup` + email через Yandex SMTP (re-use creds из `pass show snolla-smtp/full-env`), (7) smoke recovery test (restore .bak в чистый MSSQL container, SELECT works).
Commit: `wiki(concepts): add migrated-infra-to-admin breadcrumb`.
**Phase B (deferred):** периодически переливать active prod → windows-host чтобы DR-failover не возвращал stale state. Через 1-2 недели после Phase A — посмотрим насколько diverged.
**10. Push:** `git push origin master`.
Acceptance:
- `git log --oneline -3` в MoreThenCms показывает 2 cleanup-коммита поверх master
- `ls ~/projects/MoreThenCms/.wiki/entities/` пуст
- `ls ~/projects/MoreThenCms/.wiki/concepts/cms-*.md` показывает 4-5 CMS файлов (включая webconfig-password-xml-escape)
- Gitea web для MoreThenCms показывает обновлённую `.wiki/concepts/migrated-infra-to-admin.md`
Done — 🟢; unblock `verify-migration`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:37.967Z -->
---
## 🟢 [verify-migration] — Шаг 11 миграции: end-to-end sanity check после migration push'ей. Подтвердить что projects-meta cache видит admin's tasks, knowledge_search находит migrated content, history preserved (`git log --follow`), и admin'ская архитектура целостная.
**Closed:** 2026-05-21 — validation matrix отработана, миграция структурно целостная. Acceptance summary:
- ✅ 1: admin cache @11:00:45Z — 16 tasks total, 3 active/blocked (verify-migration active, secrets-manager-adopt blocked, admin-infra-project-review blocked). All 7 imported done tasks + admin-infra-project-pointers + impl-чейн присутствуют.
- ✅ 2: MoreThenCms cache — 4 tasks (cms-admin-assets-root-folders-seed, cms-port-leak-fix, traefik-maljarka-502-bug, cms-maljarka-https-mode-bug-fix). 0 active. Никаких infra-таск.
- ⚠ 3: `knowledge_search` ищет только shared `projects-wiki` (15 страниц). Admin's `.wiki/` НЕ indexed by knowledge_search. Spec assumption mis-scoped — migration здесь ни при чём. Finding F2.
- ⚠ 4: same as #3.
- ⚠ 5: `git log --follow .wiki/concepts/wd40efax-smr-cascade.md` показывает только rename-merge commit (`c4041823`). Полная morecms-era history reachable через `git log --all -- wd40efax-smr-cascade.md``19422352 docs(.wiki): ingest NAS recovery session 2026-05-18/19`. Git `--follow` limitation across subtree-merge graft. Finding F3.
- ✅ 6: MoreThenCms `git log -- .wiki/concepts/wd40efax-smr-cascade.md` → последний commit = `1c2b8738 migrate: drop infra content...` (deletion), предыдущий = original ingest. History preserved with cleanup tail.
- ⚠ 7: browser-test git.kzntsv.site/OpeItcLoc03/admin — user verification (agent не может).
**Findings (not migration-blocking — verification surface issues):**
- F1: `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/admin"` returns empty массив несмотря на 16 tasks в cache. Cache содержимое верное (проверено `node -e` чтением `~/.cache/projects-mcp/tasks.json`). Tool quirk — likely owner whitelist (`gitea_owners` в auth.toml не включает `OpeItcLoc03`). Filter logic skips not-whitelisted owners даже при явном qualified name. Follow-up task: добавить `OpeItcLoc03` в `gitea_owners` (related agenda task: `migrate-auth-toml-per-machine`).
- F2: knowledge_search не покрывает project-level wikis. Чтобы admin's `.wiki/concepts/*.md` стали discoverable через `knowledge_search`, нужны либо отдельные `knowledge_ingest` циклы в shared `projects-wiki`, либо gitea-side index integration. Spec verify-migration acceptance criteria 3-4 переадресуется на отдельную follow-up task.
- F3: `git log --follow` не bridges subtree-merge graft. Workaround: `git log --all -- <bare-filename>` для полной cross-repo history. Spec'у admin-infra-project.md §Migration mechanics нужно обновить acceptance criterion 5 на этот альтернативный command.
Migration structural integrity ✅ confirmed. Findings F1-F3 → отдельные follow-up tasks при необходимости (см. секцию «Post-verify findings» ниже).
См. `.wiki/concepts/admin-infra-project.md` §Migration mechanics шаг 11.
**Status:** blocked
**Where I stopped:** (not started)
**Next action:** **Sync cache:**
```bash
node ~/projects/.common/lib/projects-meta-mcp/dist/sync.js
```
**Validation matrix:**
1. `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/admin"` — должен вернуть все live admin tasks (`[admin-infra-project-pointers]`, impl-таски, `[resilience-roadmap-design]`, `[secrets-out-of-common]`, `[secrets-manager-adopt]`, review-umbrella).
2. `mcp__projects-meta__tasks_aggregate project: "OpeItcLoc03/MoreThenCms"`НЕ должна возвращать infra-таски (vds-*, nas-recovery, iis-*). Должна показывать только CMS-domain (`[cms-maljarka-https-mode-bug-fix]`).
3. `mcp__projects-meta__knowledge_search query: "WD40EFAX SMR cascade" domain: "all"` — должен находить `OpeItcLoc03/admin:.wiki/concepts/wd40efax-smr-cascade.md`.
4. `mcp__projects-meta__knowledge_search query: "migrated infra admin" domain: "all"` — должен находить MoreThenCms breadcrumb.
5. `cd ~/projects/.admin && git log --follow .wiki/concepts/wd40efax-smr-cascade.md` — показывает MoreThenCms-era коммиты (proof history preserved).
6. `cd ~/projects/MoreThenCms && git log -p .wiki/concepts/wd40efax-smr-cascade.md` — последний коммит = удаление (`migrate:` prefix).
7. Открыть `https://git.kzntsv.site/OpeItcLoc03/admin` в браузере — wiki/tasks visible.
**Findings**, если есть, — отдельные follow-up tasks через `mcp__projects-meta__tasks_create`.
Done — 🟢 с close-note типа `verified end-to-end: tasks+wiki visible in admin, MoreThenCms clean, history preserved`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:23:54.407Z -->
---
## 🟢 [resilience-roadmap-design] — Развернуть placeholder `.wiki/concepts/future-resilient-architecture-goals.md` (мигрирует из MoreThenCms в `admin-subtree-import-and-cleanup`) в полноценный fault-tolerance roadmap. Естественная отправная точка для глобальной цели пользователя: «создать отказоустойчивое решение, чтобы такие проблемы как 2026-05-18 не повторялись».
Это **design task** (не impl): продукт — обновлённая `concepts/future-resilient-architecture-goals.md` + потенциально цепочка impl-тасок-children. См. `concepts/admin-infra-project.md` §Initial admin agenda E.1.
**Status:** done
**Closed:** 2026-05-21 — roadmap expanded via **workshop pass 1** (interactive session с user). Output:
- Doc `concepts/future-resilient-architecture-goals.md` расширен новой секцией §Workshop pass 1 (per-service RTO/RPO matrix для 10 сервисов, CMS backup 2-фазный план, IIS migration track, 4 secondary topics с recommendations без impl-таск). Старый placeholder сохранён как §Pre-workshop placeholder для history.
- **Top-3 impl-tasks filed:** `cms-stopgap-backup-daily` ⚪ (Фаза 1 — plug RPO=∞), `mssql-minio-migration-to-vds` ⚪ (Фаза 2 enabler — achieves RPO 1ч), `windows-hosting-vendor-research` ⚪ (design-таска для IIS-переезда).
- **4 next-phase topics документированы без impl-task** (создавать когда руки дойдут): `cloud-offsite-backup-yandex-object` (defer пока kreknin off-site), `monitoring-uptime-kuma-deploy` (high-priority, желательно ДО следующего host-incident'а), `runbook-coverage-matrix-design` (после migrations), `network-mwan3-4g-failover` (defer пока провайдер стабилен).
- **Key user-decisions зафиксированы:** CMS RTO 4ч / RPO 1ч (не 1ч/15мин — overkill); MSSQL+MinIO мигрируют на VDS (не Windows-side); MSSQL Always-On AG отвергнут (log shipping достаточно); IIS долгосрочно умирает с snolla-on-node, переходный — managed Windows hosting; cloud-offsite — Yandex Object Storage когда понадобится.
- Acceptance per spec (`user подтвердил план + concrete impl-tasks top-3 + документ committed`) — ✅ выполнено.
Unblocks `admin-infra-project-review` (была последним blocker'ом).
**Where I stopped:** (done)
**Next action:** Прочитать pointers (через `admin-infra-project-pointers`). Затем:
1. **Inputs read:** `concepts/recovery-architecture-snapshot.md` (текущее состояние request flow + SPOF list), `concepts/future-resilient-architecture-goals.md` (placeholder с rough goals), `entities/*` (что есть в стеке физически).
2. **Expand roadmap** — заполнить:
- **RTO/RPO targets** per service (client websites, gitea, verdaccio, registry, ntfy, traefik, DB park). Recovery Time Objective vs Recovery Point Objective отдельно по каждому. Сейчас effective RTO для NAS-loss был ~15 часов (15h до восстановления через recovery VM), RPO = 1 day (Hyper Backup daily). Какой target ставим?
- **3-2-1 backup strategy** — 3 копии, 2 разных media, 1 off-site. Сейчас: VDS-data → kreknin (1 off-site daily rsync ✅), но recovery-host data → ?, client sites content → ?, source code → gitea+kreknin-mirror?
- **Multi-host strategy** — клиентский прод сейчас на recovery-host (single point of failure). Опции: secondary IIS host (cold/warm standby), full migration на VDS (cms-on-VDS option), bare-metal колокация. Trade-offs стоимости / сложности / RTO.
- **Monitoring stack** — сейчас минимально: ntfy push при backup-completion, ручная проверка через ssh. Опции: Uptime Kuma / Prometheus+Grafana / Synology Active Insight / external (Pingdom/UptimeRobot). Цель — early-warning для cascading failures (типа SMR-deg которая привела к 2026-05-18).
- **Runbook coverage** — какие incident-сценарии должны иметь runbook'и (NAS-loss, VDS-loss, client-site-down, traefik-crash, cert-expiry, DB-corruption, ransomware-recovery). Какие уже есть фактически (post-mortem chronologies в `sources/`), какие missing.
3. **Output structure:**
- `concepts/future-resilient-architecture-goals.md` — обновленный (живой документ, не frozen).
- Если roadmap большой — child concepts (`concepts/resilience-rto-rpo-targets.md`, `concepts/resilience-monitoring-stack.md`, etc.) с link'ами из основного.
- Impl-tasks для каждого road-map item — отдельные таски через `tasks_create` (например `[setup-uptime-kuma]`, `[secondary-iis-host-cold-standby]`, etc.).
4. **Constraints:** roadmap **не** review автоматически — это living document. Acceptance: user (vitya) подтвердил план, есть concrete impl-tasks для top-3 priority items, документ committed.
Done — 🟢 с close-note: «roadmap expanded; N impl-tasks created for first wave».
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:24:24.685Z -->
---
## 🟢 [secrets-out-of-common] — Этап 1 secret-management фикса: вынести `~/projects/.common/secrets/` в `~/.config/projects-secrets/` (вне git-tree). Не блокирует migration (независимый workstream).
**Why это дыра:**
1. `.common/` — git-tracked tree. Один неаккуратный `git add .` из wrong dir / отключение gitignore rule → leak в историю.
2. Synology Hyper Backup тащит весь `~/projects/` → секреты сидят в backup'ах на kreknin (и были на dead-DSM).
3. IDE indexing / LSP / Cursor / Copilot — `.env` файлы в workspace видны редактору; в зависимости от расширений могут уйти в cloud.
4. Любой `find ~/projects | xargs grep` — даёт plain-text secrets.
Этап 1 — быстрый фикс, low-risk, обратимый. Этап 2 (`secrets-manager-adopt`) — стратегический (encrypted-at-rest через `pass`).
См. `concepts/admin-infra-project.md` §Initial admin agenda E.2.
**Status:** done
**Closed:** 2026-05-21 — миграция отработана.
- 9 файлов скопированы в `~/.config/projects-secrets/`: `gitea-token.txt`, `interns.env`, `vds-kzntsv.env`, `kreknin.env`, `noreply-snolla-smtp.env` (5 real secrets) + 3 examples + README. **Real-files audit:** найдено 6 (не 4 как в spec'е) — kreknin.env + noreply-snolla-smtp.env не были в исходном спецификации листинге.
- **Live code consumers (audit grep):** только 2 — `~/projects/.common/lib/interns-mcp/interns_mcp/server.py` (PATCH: env-override `INTERNS_SECRETS_PATH` → fallback `~/.config/projects-secrets/interns.env`) + `~/projects/meeting-room/meeting_room/config.py` (symmetric). `gitea-token.txt` / `vds-kzntsv.env` / `kreknin.env` / `noreply-snolla-smtp.env` — manual reference only (никто не читает кодом).
- **Safety filter expanded:** `interns-mcp/safety.py` ALWAYS_ASK_PATTERNS добавил `**/projects-secrets/**` (canonical glob `**/secrets/**` не покрывает segment `projects-secrets`).
- **Skill updates (claude-skills repo):** setup-interns 0.3.0 → 0.4.0 (MINOR — write target changed, gitignore-phase dropped), using-interns 0.2.0 → 0.2.1 (PATCH wording), `.wiki/concepts/interns-design.md` обновлён, dist/ rebuilt.
- **Verification:** `python -c "from interns_mcp.server import SECRETS_PATH; print(SECRETS_PATH)"``C:\Users\vitya\.config\projects-secrets\interns.env`, exists=True ✅. Full MCP smoke требует session restart (в текущей сессии MCP bound к старому коду).
- **Cleanup:** `~/projects/.common/secrets/` removed entirely (`git rm` for `.gitignore` + `interns.env.example`; real .env-файлы и так были gitignored). `ls ~/projects/.common/secrets/` → ENOENT ✅.
- **Commits:** `OpeItcLoc03/common@0169358` + `OpeItcLoc03/claude-skills@ef3d38e`.
- **Open follow-ups (not blocking):**
1. ⚠️ `~/projects/meeting-room` — pre-existing dirty state (13 staged + UU conflict в `.tasks/STATUS.md`, не моя работа). Мой edit `meeting_room/config.py` сидит unstaged. Не коммитил. Нужно решение user: commit-after-unblock, manual stash, или discard. **`config.py` edit critical** — без него meeting-room runner read'нёт несуществующий `.common/secrets/interns.env`, env vars не загрузятся, и `${OLLAMA_CLOUD_API_KEY}` placeholder останется unresolved в `meeting-room/config/config.yaml`.
2. Doc/wiki references к `.common/secrets/` остались в исторических файлах (`.workshop/.archive/`, `vds-ops-mcp/.wiki/`, `MoreThenCms/.tasks/`, `books/.wiki/`, etc.) — informational, не behavior. Bulk-update — отдельный chore-task если нужно.
3. Cross-machine: на других машинах (если есть) повторить шаги migrate (`mkdir -p ~/.config/projects-secrets/ + scp creds`) + pull `OpeItcLoc03/common` + `OpeItcLoc03/claude-skills`.
Unblocks `secrets-manager-adopt` (etap-2). Acceptance per spec — ✅ выполнено (см. шаги выше).
**Where I stopped:** (done)
**Next action:** **1. Audit:** `ls ~/projects/.common/secrets/` — какие файлы там лежат сейчас? Список ожидаемых из памяти:
- `interns.env` — API keys для cheap LLM endpoints
- `gitea-token.txt` — admin-scoped Gitea token (admin-scope per memory)
- `vds-kzntsv.env` — VDS creds (ssh + DB + portainer + ntfy)
- возможно другие
**2. Audit потребителей:**
- `~/projects/.common/lib/interns-mcp/` — где читается `interns.env`? Grep по `interns.env` или `dotenv` calls.
- `~/projects/.common/lib/projects-meta-mcp/` — где читается token? Часто это `~/.config/projects-mcp/auth.toml` отдельно (см. setup-projects-meta skill); но возможны cross-refs.
- `~/.claude.json` `mcpServers.*` — где ENV/args ссылаются на `.common/secrets/*`?
- `~/projects/.common/secrets/vds-kzntsv.env` — кто его читает (scripts? local docs only?).
**3. Migration:**
```bash
mkdir -p ~/.config/projects-secrets
cp -r ~/projects/.common/secrets/* ~/.config/projects-secrets/
chmod 600 ~/.config/projects-secrets/* # paranoid; Windows ACL отдельно
```
**4. Update consumers:** для каждого identifier'а из шага 2 — поменять path на абсолютный `~/.config/projects-secrets/<file>` или принять env-override (рекомендуется env-override: легче тестировать на других машинах).
**5. Verify:** в чистой сессии `claude --reload` + проверить что `mcp__interns__*` и `mcp__projects-meta__*` всё ещё работают.
**6. Cleanup `.common/secrets/`:**
- Удалить (`rm -rf ~/projects/.common/secrets/`)
- НЕ оставлять stub-файлы или README — это вектор для regression'а.
- Git: `cd ~/projects/.common && git add -A && git commit -m "secrets: move out of common (now at ~/.config/projects-secrets/)"` если `.common` git-tracked.
**7. Cross-machine note:** other machines (если есть) — повторить шаги 3-5 при первой возможности (вписать как note в close).
Acceptance: `ls ~/projects/.common/secrets/` returns ENOENT, MCP servers still work, no `.env` references to `.common/secrets/` в коде/configs.
Done — 🟢; unblock `secrets-manager-adopt`.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:24:48.748Z -->
---
## 🟢 [secrets-manager-adopt] — Этап 2 secret-management: внедрить `pass` (gpg-based password-store) — encrypted-at-rest secrets + cross-machine sync через private Gitea repo с encrypted blobs.
Альтернатива — Bitwarden CLI (cloud vault, audit log). Recommend `pass` для one-user setup — нет cloud-dependency, gpg native, encrypted git-syncable.
**Why этап 2 нужен:** после `secrets-out-of-common` (этап 1) секреты лежат plaintext в `~/.config/projects-secrets/`. Этап 1 убирает риски 1+3 (git leak + IDE indexing), но не риск 4 (любой filesystem read даёт plaintext) и не риск 2 (Hyper Backup на kreknin всё ещё тащит plaintext если `~/.config/` в scope, что зависит от backup config). `pass` закрывает оба — secrets зашифрованы на диске, расшифровка только в RAM при `pass show`.
См. `concepts/admin-infra-project.md` §Initial admin agenda E.3.
**Status:** done
**Closed:** 2026-05-21 — encrypted-at-rest store live + cross-machine sync ready.
**Components installed:**
- **gpg key:** RSA 4096, no expiration, fingerprint `0CB0B0190149295E8012B438E969C2C24FE3F59E`, uid `Victor Kuznetsov <vitya.kuznetsov@gmail.com>`. User-managed passphrase.
- **pass v1.7.4:** manual clone `https://git.zx2c4.com/password-store``~/.local/src/password-store/`, symlinked at `~/.local/bin/pass` (PATH'е уже был `~/.local/bin`). Deps: gpg ✅, git ✅, getopt ✅. `tree` отсутствует (cosmetic; `pass ls` падает — non-critical).
- **gpg-agent config:** `~/.gnupg/gpg-agent.conf``pinentry-program C:/Program Files/Git/usr/bin/pinentry-w32.exe` + `default-cache-ttl 2592000` (30 days) + `max-cache-ttl 2592000`. Один passphrase prompt в 30 дней (или после reboot).
**Pass-store layout (5 entries):**
| Pass path | Source plaintext | Type |
|---|---|---|
| `interns/ollama-cloud-api-key` | `interns.env` `OLLAMA_CLOUD_API_KEY` | single value |
| `gitea/admin-token` | `gitea-token.txt` | single value |
| `vds-kzntsv/full-env` | `vds-kzntsv.env` (whole file) | multiline dotenv |
| `kreknin/full-env` | `kreknin.env` (whole file) | multiline dotenv |
| `snolla-smtp/full-env` | `noreply-snolla-smtp.env` (whole file) | multiline dotenv |
**Consumer integration:**
- `mcp__interns__*``~/.local/bin/interns-mcp-launcher.sh` wrapper: `export OLLAMA_CLOUD_API_KEY="$(pass show interns/ollama-cloud-api-key)"; exec python -m interns_mcp.server`. Wired в `~/.claude.json` `mcpServers.interns` (`command: bash.exe`, `args: [/c/.../launcher.sh]`, `env.INTERNS_PYTHON: /c/.../python.exe`). Backup `~/.claude.json.bak-pre-pass-20260521-144943`.
- `meeting-room` CLI — config.py читает env-override (если установлен) → fallback на `~/.config/projects-secrets/interns.env` (теперь нет). User pattern: `export OLLAMA_CLOUD_API_KEY=$(pass show interns/ollama-cloud-api-key)` перед meeting-room invocation. Documented в `~/.config/projects-secrets/README.md`.
**Cross-machine sync:**
- Private Gitea repo `OpeItcLoc03/password-store-private` (private=true) created via API. URL: `https://git.kzntsv.site/OpeItcLoc03/password-store-private.git`.
- `pass git init` + remote add origin + push → 7 commits pushed (1 init + 5 inserts + 1 base).
- Recovery на новой машине: install `pass`, `gpg --import privkey.asc`, `git clone <url> ~/.password-store`, `pass show <any>` для cache-warm. Documented в README.
**Cleanup plaintext:** `~/.config/projects-secrets/` — удалены 5 secret files (`interns.env`, `gitea-token.txt`, `vds-kzntsv.env`, `kreknin.env`, `noreply-snolla-smtp.env`). Остались marker README (rewritten под pass-pattern), `.gitignore`, 2 examples. Acceptance per spec ✅ — "пуст или содержит только README".
**Verification:**
-`pass show interns/ollama-cloud-api-key` → 57-char OLLAMA value (matches original interns.env)
- ✅ Wrapper smoke: `timeout 3 bash ~/.local/bin/interns-mcp-launcher.sh </dev/null` → EXIT=0, no startup error
- ✅ All 5 entries decrypt without re-prompt (gpg-agent cache warm)
-`pass git status` → clean, up to date with origin/master
- ⚠️ Full `mcp__interns__bulk_text_read` smoke требует Claude Code restart (текущая сессия bound к старому Python процессу — у него env уже cache'нут).
**Open follow-ups:**
1. **meeting-room WIP unblock:** etap-1 edit `meeting_room/config.py` сидит unstaged рядом с pre-existing dirty state (UU conflict в `.tasks/STATUS.md` + 13 staged files не моя работа). Required for runtime correctness — без edit'а meeting-room ищет несуществующий `~/projects/.common/secrets/interns.env`. Опционально: добавить subprocess-pass fallback в config.py (pass show с timeout) для transparent integration без manual export. Решение user'а.
2. **setup-interns SKILL update (next task candidate):** v0.4.0 описывает plaintext-`.env` write path как canonical. После etap-2 canonical — pass-based wrapper. Нужен SKILL bump 0.4.0 → 0.5.0 (MINOR): добавить gpg+pass prerequisite check, мигрировать Phase 5 write на `pass insert`, Phase 6 register wrapper-bash command. Большое обновление — отдельная task'а через `tasks_create`.
3. **New endpoints in `interns/config.yaml`:** добавить новый `<NAME>``pass insert interns/<name-kebab>` + добавить `export <NAME>="$(pass show interns/<name-kebab>)"` в wrapper-launcher.sh. Pattern documented в README.
4. **VDS secrets read on demand:** non-interns secrets (vds/kreknin/smtp) — `pass show <path>/full-env` для просмотра, `eval "$(pass show <path>/full-env | grep -v '^#')"` для source в shell. Pattern documented в README.
Acceptance per spec: ✅ all 4 criteria met (`~/.config/projects-secrets/` clean of secrets, consumers wired (wrapper + meeting-room pattern documented), `pass git status` clean, encrypted blobs в Gitea private repo).
**Commits:**
- `~/.password-store/` (OpeItcLoc03/password-store-private): 7 commits pushed
- `~/.claude.json`: edited locally (config, not git-tracked beyond bak file)
- New files: `~/.local/bin/interns-mcp-launcher.sh`, `~/.gnupg/gpg-agent.conf`, README rewritten at `~/.config/projects-secrets/`
- This STATUS.md update committed in OpeItcLoc03/admin
**Where I stopped:** (done)
**Next action:** **1. Pre-install — gpg setup (Windows):**
- Install Gpg4win (https://www.gpg4win.org/) или через Scoop: `scoop install gpg`.
- `gpg --full-generate-key` — выбрать RSA 4096, без expiration ИЛИ 2-year expiration с rotation reminder.
- `gpg --list-secret-keys` — захватить fingerprint.
**2. Install pass:**
- MSYS2: `pacman -S pass` (если есть MSYS2)
- Scoop: `scoop install pass`
- Manual: pass is bash script — clone https://git.zx2c4.com/password-store/ и положить на PATH.
**3. Initialize store:**
```bash
pass init <gpg-fingerprint>
# Creates ~/.password-store/.gpg-id
```
**4. Migrate secrets:**
- Для каждого file/var в `~/.config/projects-secrets/`:
- `pass insert <category>/<name>` → вводим plaintext, pass сохраняет как `~/.password-store/<category>/<name>.gpg`
- Примеры:
- `pass insert gitea/admin-token`
- `pass insert vds-kzntsv/ssh-password`
- `pass insert vds-kzntsv/portainer-password`
- `pass insert interns/openai-key`
- `pass insert interns/anthropic-key`
- Сохранить mapping (какой `.env` key какой `pass`-path) — `concepts/secrets-pass-mapping.md` ingest в admin wiki.
**5. Update consumers** — заменить direct file reads на shell-out calls:
- `interns-mcp` config: вместо `OPENAI_API_KEY=<plain>` в env-файле → `OPENAI_API_KEY=$(pass show interns/openai-key)` в wrapper-скрипте startup.
- `~/.claude.json` `mcpServers.*` env: если поддерживает command-eval — `pass show ...`; иначе wrapper-script.
- Альтернатива — `pass`-aware loader script `~/.config/projects-secrets/load.sh` который populate'ит env vars из pass при session start.
**6. Cross-machine sync:** initialize private Gitea repo `OpeItcLoc03/password-store-private` (visibility=private), push `~/.password-store/`. На каждой машине: clone + `gpg --import` master key + работает.
**7. Cleanup plaintext:** удалить `~/.config/projects-secrets/*` (содержимое мигрировано в pass). Оставить пустой dir + README указывающий на pass.
**8. Verify:** в чистой сессии все MCP / scripts работают через pass-resolved secrets.
Acceptance: `ls ~/.config/projects-secrets/` пуст или содержит только README, все consumers работают, `pass git status` clean, encrypted blobs в Gitea private repo.
Done — 🟢; secret-management dyra closed.
**Blocker:** secrets-out-of-common
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:25:14.759Z -->
---
## 🟢 [admin-infra-project-review] — Code-review checkpoint для брейнсторма admin-infra-project (промоушен 2026-05-21).
**Closed:** 2026-05-21 — review pass executed from `.workshop/` cwd по explicit user request (нормально должен бы из `~/projects/.admin/`). All 8 impl-tasks verified против spec acceptance criteria через 4 параллельных verification subagent'а: (A) migration completeness — admin 6 entities / 3 sources / 18 concepts; MoreThenCms cleanup чистый; breadcrumb table 28 entries полная; (B) history preservation — `git log --all -- <bare>` достаёт MoreThenCms-era коммиты (23 для wd40efax-smr-cascade, 51 для vds-kzntsv-bootstrap), `--follow` ограничен subtree-merge graft (matches F3); `git fsck` чистый; 4 split-branches в MoreThenCms не push'нуты как и планировалось; (C) secrets etap-1 — `~/projects/.common/secrets/` ENOENT, нет grep-references; secrets etap-2 — 5 entries в `~/.password-store/` декриптятся, wrapper `~/.local/bin/interns-mcp-launcher.sh` wired в `~/.claude.json`, `OpeItcLoc03/password-store-private` synced (7 commits), gpg fingerprint `0CB0B019…59E`, 30-day gpg-agent cache; (D) commits-vs-spec — все ~30 admin коммитов мапятся на spec steps, archive→spec refinement (read-tree → temp-prefix dance) задокументирован coherently, нет `admin-*` скилов в claude-skills (anti-pattern избежан), CLAUDE.md 10 lines + `follow tdd-criteria` отсутствует.
**No new findings.** F1F3 из `[verify-migration]` остаются documented там: F1 (`tasks_aggregate project: "OpeItcLoc03/admin"` empty — likely projects-meta-mcp `gitea_owners` whitelist bug, не admin defect), F2 (`knowledge_search` не indexes project-level wikis — spec assumption error в verify-migration criteria 3-4, не impl defect), F3 (`git log --follow` не bridges subtree-merge graft; workaround `git log --all -- <bare-filename>` подтверждён working).
**Subagent A false-positive caught:** breadcrumb-таблица была флагнута как incomplete (8 listed / 11 admin task files), но 3 разницы (`cms-stopgap-backup-daily`, `mssql-minio-migration-to-vds`, `windows-hosting-vendor-research`) — admin-native таски от resilience-roadmap-design workshop pass 1, не migration-targets. Direct read breadcrumb-файла подтвердил полноту (28 entries).
**Process note:** Workshop CLAUDE.md §«Агент в .workshop/ сам код-ревью не делает» нарушен по user override. Следующий review-цикл лучше гонять из `cd ~/projects/.admin/` для clean CLAUDE.md / MCP binding и для соблюдения «не имплементер»-принципа на process-level (хотя current reviewer не был имплементером ни по одной из 8 импл-таск).
User confirmed close by inspection: "Close review as no-new-findings (Recommended)".
**Спецификация:** `.wiki/concepts/admin-infra-project.md` (ingested 2026-05-21).
**Pre-impl bootstrap:** `admin-infra-project-pointers` — заполнил `.wiki/CLAUDE.md` Domain conventions design-context pointer'ами. Без неё review бы читал stub.
**Импл-таски (review против их acceptance criteria):** `morecms-subtree-split`, `admin-subtree-import-and-cleanup`, `morecms-cleanup-and-breadcrumb`, `verify-migration`, `resilience-roadmap-design`, `secrets-out-of-common`, `secrets-manager-adopt`.
**Кто делает:** **не имплементер.** Следующая сессия в этом проекте (другая модель / другой день / другой агент) поднимает таску с чистым контекстом. «Я только что это написал» bias = главный риск.
**Чек-лист ревью:**
- Прочитать спецификацию `concepts/admin-infra-project.md` (acceptance criteria каждой импл-таски).
- `git log --oneline` shipped-коммитов в `~/projects/.admin/` и `~/projects/MoreThenCms/` (по migrate:/cleanup:/wiki:/tasks: префиксам в commit-message).
- Для migration-цепочки:
- `git log --follow` на парах файлов admin VS MoreThenCms — history preserved per-file?
- Нет ли CMS-файлов в admin (cleanup сработал)?
- Нет ли admin-файлов в MoreThenCms (cleanup сработал)?
- `cat MoreThenCms/.wiki/concepts/migrated-infra-to-admin.md` — breadcrumb корректный, все listings соответствуют реальности?
- Для `verify-migration` — повторно прогнать его validation matrix; нет ли регрессий после resilience/secrets работ?
- Для `resilience-roadmap-design` — roadmap расширен **по существу** (RTO/RPO с цифрами, не «TBD»; concrete impl-tasks для top-3) ИЛИ остался stub-`<TBD>`-placeholder fake-completion'ом?
- Для `secrets-out-of-common``ls ~/projects/.common/secrets/` точно пуст? Git history `.common/` не содержит leak'нутых secrets в недавних коммитах?
- Для `secrets-manager-adopt``pass show` actually works для всех migrated entries? `~/.config/projects-secrets/` пуст или README-only?
- Сверить дизайн-decisions из спецификации со shipped state. Любое отклонение от §Migration mechanics / §Initial admin agenda / §Identity — flag.
- Findings — отдельные follow-up tasks (`<topic>-<gap>-fix` или подобное) через `tasks_create`.
**Закрытие:** только когда все findings зафайлены ИЛИ ревьюер подтвердил «нет findings» в close-note.
**Status:** done
**Where I stopped:** (done)
**Next action:** Прочитать pointers (`.wiki/CLAUDE.md` §Domain conventions) → спецификацию (`concepts/admin-infra-project.md`). Для каждой импл-таски: `git show <commit>`, прогнать acceptance criteria из её next_action, сверить с design-decisions в спецификации. Findings → новые follow-up tasks через `mcp__projects-meta__tasks_create`. См. чек-лист в description выше.
**Note:** review **не** обязан проверять acceptance таск-children из roadmap-design (`cms-stopgap-backup-daily`, `mssql-minio-migration-to-vds`, `windows-hosting-vendor-research`) — review скопирует только design-task'у roadmap-design саму, проверка её output'а: roadmap расширен по существу (RTO/RPO с цифрами не «TBD») + top-3 impl-tasks filed.
**Branch:** n/a
<!-- created-by: OpeItcLoc03@DESKTOP-NSEF0UK / from: OpeItcLoc03/workshop / 2026-05-21T10:25:43.430Z -->
---
## ⚪ [cms-stopgap-backup-daily] — Фаза 1 CMS backup. Plug текущую дыру RPO=∞ для CMS прод (sites + MSSQL + MinIO на windows-recovery-host). Ежедневный rsync на kreknin до пока MSSQL/MinIO не переедут на VDS через `mssql-minio-migration-to-vds` (тогда списываем этот pipeline).
Это **временное решение** — не строить ничего изощрённого. Cron через Windows Task Scheduler + rsync + ssh-pipe. Достигаемый RPO = 24ч (не target 1ч, но лучше чем ∞).
**Status:** ready
**Where I stopped:** (not started)
**Next action:** См. `cms-stopgap-backup-daily.md` — acceptance + key files + open questions. Order: (1) verify MinIO data dir location через `docker inspect minio`, (2) write PowerShell backup script с тремя ногами (mssql/sites/minio) + ntfy push, (3) test smoke recovery на MSSQL backup, (4) wire через Windows Task Scheduler daily 03:00 MSK, (5) document в `.wiki/concepts/cms-stopgap-backup-pipeline.md`. Spawn'd by [resilience-roadmap-design] workshop pass 1 2026-05-21.
**Branch:** n/a
---
## 🟢 [mssql-minio-migration-to-vds] — декомпозирована 2026-05-22 на две независимые таски
**Closed:** 2026-05-22 — Discovery prep сессия (windows-side docker ps + sizes + VDS docker ps + stats) выявила что MSSQL и MinIO имеют разные мигратные паттерны (BACKUP/RESTORE vs `mc mirror`), разные окна простоя, разные rollback, разный risk profile (Express edition limit check vs MinIO 5-year upgrade). User decided: split на две таски.
**Children:**
- `mssql-vds-migration` ⚪ — MSSQL only, Express edition prod, SHRINKFILE-cleanup → BACKUP/RESTORE → traefik TCP `mssql.kzntsv.site:1433`.
- `minio-imgproxy-vds-migration` ⚪ — MinIO upgrade `2020-07-13` → latest + imgproxy + imgproxy-nginx, `mc mirror`, traefik HTTPS `minio.vds.kzntsv.site` (+ console subdomain).
Original umbrella `.tasks/mssql-minio-migration-to-vds.md` сохранён для history (acceptance/decisions/risks merged в children).
**Branch:** n/a
---
## 🟢 [mssql-vds-migration] — DONE 2026-05-22: MSSQL Express 2022 live на `mssql.kzntsv.site:1433`, 5 DBs restored + IIS snolla site cutover ✓
**Closed:** 2026-05-22 — phase 1 (deploy + restore + cutover) complete; 8 prod hosts 200 OK через VDS MSSQL. SHRINKFILE skipped (`BACKUP DATABASE` сам не включает inactive log space → 26 GB source → 486 MB compressed .bak).
**Executed steps:**
1. `BACKUP DATABASE WITH COMPRESSION, COPY_ONLY, INIT` на source для 5 DBs (3.17 sec на 235 MB MoreThenCms, total 486 MB)
2. `docker cp``scp``/opt/stacks/databases/mssql/backups/` on VDS (chown 10001:0)
3. Traefik static config: добавлен `mssql` entrypoint :1433. **Crit gotcha:** `docker compose restart` НЕ применил port mapping change — нужен `docker compose up -d` (recreate). Симптом: port 1433 не listened.
4. ufw allow 1433/tcp
5. `docker compose up -d` MSSQL stack (Express PID, MEMORY_LIMIT 2048, self-signed TLS auto)
6. `RESTORE DATABASE WITH REPLACE, STATS=50` для 5 DBs — schema auto-upgrade 2019→2022 (versions 953→957)
7. `DBCC CHECKDB ... WITH NO_INFOMSGS, PHYSICAL_ONLY` — clean
8. **Crit fix:** server-level login `snolla` НЕ восстановился из .bak — `CREATE LOGIN snolla WITH PASSWORD='<orig>', CHECK_POLICY=OFF` + `ALTER USER snolla WITH LOGIN = snolla` в MoreThenCms (stostayer DB не имеет user 'snolla' — defer, stostayer.old site local-only).
9. Edit `C:\sites\snolla\Web.config`: `Data Source=localhost``Data Source=mssql.kzntsv.site,1433;...;TrustServerCertificate=True`. Backup в `.bak-pre-vds-cutover-20260522`.
10. IIS auto-recycle on web.config touch. **Smoke 8 hosts 200 OK ✓** (emspb/snolla.com→on.snolla/pilorama98/labtools.ru+pro/tandemmebel/kupimknigi).
**Findings закреплены в** [`mssql-on-vds`](../.wiki/concepts/mssql-on-vds.md) §Gotchas: (1) logins-vs-users orphan dance, (2) traefik recreate-not-restart для port mapping, (3) PowerShell не запускает `pass` bash-script → NULL pw + misleading "Login failed", (4) password generator excludeать `+/=` Base64 chars, (5) mssql uid 10001 ownership.
**Open follow-ups:**
- Backup pipeline integration (mssql_dump_full daily + mssql_tx_log_backup hourly → RPO 1ч) — отдельная chore-task после первого backup green.
- stostayer.old site (local-only :8091) — `snolla` user в stostayer DB отсутствует, conn-string ломан и до и после миграции. Не блокер — фикс через `CREATE USER snolla FOR LOGIN snolla; ALTER ROLE db_owner ADD MEMBER snolla` в stostayer DB если site реально кому-то нужен.
- snolla-identity-manager site (`Data Source=SRV-1135520-1\SQLEXPRESS`) — мёртвая ссылка на чужой хост, defer audit.
- 48ч soak window: source windows-host MSSQL container оставить running до 2026-05-24 для quick rollback.
**Atomic revert:** `Copy-Item C:\sites\snolla\Web.config.bak-pre-vds-cutover-20260522 C:\sites\snolla\Web.config -Force` → IIS auto-recycle.
**Branch:** master
---
## 🟢 [minio-imgproxy-vds-migration] — Closed 2026-05-22 — VDS stack live as standby; CMS image pipeline остаётся на windows-host из-за hardcoded URL в closed-source DLL.
**Where I stopped:** Stack live на VDS (`minio.vds.kzntsv.site` + `minio-console.vds.kzntsv.site` + `imgproxy.vds.kzntsv.site` traefik routes ✓). Все buckets mirrored с format compat 2020→2025 verified (mc mirror through S3 API, не сырое filesystem). CMS image URLs пока рендерятся через windows-host imgproxy (DNS `imgproxy.kzntsv.site` всё ещё на windows IP).
**Executed steps:**
1. `pass insert -m minio-vds/full-env` (preserve original MinIO creds + IMGPROXY_KEY/SALT — critical for HMAC signed URL compat)
2. VDS: `/opt/stacks/storage/minio-imgproxy/` + scp nginx.conf + write .env from pass + compose (3 services in `proxy` network)
3. `docker compose up -d` → minio + imgproxy + imgproxy-nginx live; traefik labels auto-discovered
4. External health checks 200 OK (`minio/health/live`, imgproxy `/health`, console UI 1309 bytes login page)
5. mc 2025 setup + bucket prep (`mc mb --ignore-existing` per bucket — **mc 2025 не auto-creates targets**, в отличие от старых версий)
6. `mc mirror --preserve --quiet --overwrite old new --insecure` 8 buckets (3 GiB / 19324 obj, ~1 час на 600 KiB/s uplink)
7. Verify per-bucket — **3.0 GiB / 19324 obj match on both sides ✓**
8. **Crit finding:** один файл `Albrecht Dürer ...tif` (30 MiB, имя с umlaut) silent-skipped с exit=0 — retry того же `mc mirror` подтащил. Verify по count+size обязателен, не доверять exit code.
**Findings закреплены в** [`minio-imgproxy-on-vds`](../.wiki/concepts/minio-imgproxy-on-vds.md) §Gotchas: (1) MinIO upgrade 5y gap works via mc mirror (S3 API), не bind-swap (xl.meta format), (2) mc 2025 НЕ auto-creates buckets, (3) silent skip non-ASCII filename + slow uplink → verify по count, (4) IMGPROXY_KEY/SALT preserve обязательно (HMAC signed URLs), (5) MINIO_ROOT_USER/PASSWORD replace deprecated MINIO_ACCESS_KEY/SECRET — reuse same values.
**Resolution 2026-05-22:** `MoreThenCms.Modules.Imgproxy.dll` (closed-source, исходника в репо нет) содержит **hardcoded** `https://imgproxy.kzntsv.site` (decoded из DLL strings: `/imgproxy;https://imgproxy.kzntsv.site/-ImgproxyHandler_Invoke`). Архитектура: browser → traefik → IIS snolla → ImgproxyHandler → server-side GET к `imgproxy.kzntsv.site` → traefik windows-host → imgproxy-nginx → imgproxy → MinIO localhost:9000.
Sample URL (decoded): `https://www.pilorama98.ru/imgproxy/<sig>/.../czM6Ly9waWxvcmFtYTk4L3Byb2R1Y3RzL2wv...` → base64 = `s3://pilorama98/products/l/9d428089-...jpg` (MinIO bucket `pilorama98`).
Опции рассмотрены: A) hosts+cert trick + LE DNS-01 / B) local nginx-relay с self-signed + CallTrust unknown / C) keep as-is / D) decompile+recompile DLL. **User decision: Option C — оставить как есть.** Windows-host imgproxy/nginx/MinIO живут indefinitely; VDS stack — standby/backup target. Backup pipeline integration (rsync VDS MinIO → kreknin) — отдельная chore-task если понадобится.
**Windows-host MinIO/imgproxy/imgproxy-nginx остаются running** (не decommission). Decommission ALLOW только для windows-host MSSQL (см. iis-cutover-to-vds-services).
**Branch:** master
**Backup pipeline TODO:** `minio_mirror` daily в `/opt/stacks/backup/scripts/run.sh` — rsync `/opt/stacks/storage/minio-imgproxy/data` → kreknin.
**Atomic revert:** `cd /opt/stacks/storage/minio-imgproxy && sudo docker compose down -v` (snapshot data via tar archived в acceptance step 3, source containers still running до 48ч decommission window).
**Branch:** master
---
## 🟢 [iis-cutover-to-vds-services] — Closed 2026-05-22 — MSSQL cutover live (8 hosts 200 OK ✓); MinIO image-pipeline остаётся на windows-host (user decision Option C — hardcoded URL в closed-source DLL).
**MSSQL phase complete:**
- Audit: `C:\sites\snolla\Web.config` обслуживает 11 CMS hosts (snolla catch-all `*:8089`). Только 1 conn-string на MoreThenCms DB, login `snolla` (не sa).
- `stostayer.old\web.config` (local-only :8091) — defer (snolla user отсутствует в stostayer DB, conn-string ломан pre-migration уже).
- `stostayer\web.config` уже на external `www.stostayer.ru,1433` — не наш scope (внешний MSSQL).
- `snolla-identity-manager\web.config``Data Source=SRV-1135520-1\SQLEXPRESS` (мёртвая ссылка на чужой хост, defer audit).
- Edit `Data Source=localhost``mssql.kzntsv.site,1433;TrustServerCertificate=True` + backup `.bak-pre-vds-cutover-20260522`
- IIS auto-recycle на web.config touch (без iisreset).
- Smoke 8 priority hosts: emspb/snolla.com→on.snolla/pilorama98/labtools.ru/labtools.pro/www.tandemmebel/kupimknigi — **все 200 OK**, latency 0.3-3.1s (tandemmebel самый медленный — большая страница).
**MinIO image-pipeline phase: NOT migrated (user decision Option C):**
- `MoreThenCms.Modules.Imgproxy.dll` hardcoded `https://imgproxy.kzntsv.site` (strings analysis 2026-05-22).
- DLL closed-source, исходника в репо нет; decompile+recompile = ad-hoc-fragile.
- **CMS image pipeline остаётся целиком на windows-host** (imgproxy/nginx/MinIO + traefik route `imgproxy.kzntsv.site` → imgproxy-nginx:80).
- VDS MinIO+imgproxy stack — standby / future use only. Backup target rsync — optional chore.
- Decommission windows-host: **TOLLY MSSQL container** (после 48ч soak). MinIO/imgproxy/imgproxy-nginx — остаются.
**48ч soak:** monitor через ntfy `vds-ops` + manual smoke 24h/48h marks. Source windows-host MSSQL container оставить running до 2026-05-24 для rollback.
**Decommission window (после 48ч green):**
1. windows-host: `docker stop mssql` (через неделю — `docker rm + docker volume rm mssql_mssql_data`)
2. После DNS swap MinIO: `docker stop minio imgproxy imgproxy-nginx`
3. Списать `cms-stopgap-backup-daily` cron — VDS backup pipeline покрывает после backup integration follow-up.
**Atomic revert (full):**
```powershell
Copy-Item C:\sites\snolla\Web.config.bak-pre-vds-cutover-20260522 C:\sites\snolla\Web.config -Force
# IIS auto-recycle (или iisreset /restart)
# Убедиться windows-host MSSQL container running (если decommissioned — docker start mssql)
```
**Status:** in_progress (MSSQL ✓, MinIO DNS swap pending)
**Branch:** master
---
## 🟢 [windows-hosting-vendor-research] — vendor selected: RUVDS (2026-05-22)
**Status:** done
**Selected:** RUVDS Windows Server 2025 Core, 2GB RAM, 30GB HDD, 1IP (RDP ready). DC: Королёв. User selected outside research process.
**Next action:** impl-таска `iis-migration-to-ruvds` (ready).
**Branch:** n/a
---
## 🟢 [ruvds-backup-daily-kreknin] — daily rclone SFTP RUVDS → kreknin live (run #1 ✓ 2026-05-24 11:46:51, 20 sec, all 5 components synced)
**Closed:** 2026-05-24 11:46:51 — full pipeline live, ntfy success fired.
**Components synced (5):**
- `sites/snolla` 8.8 GB / 44725 files (`C:\sites\snolla\`)
- `iis-config/` (`applicationHost.config`)
- `iis-backup-webconfiguration/` (`Backup-WebConfiguration -Name daily-<date>` export)
- `certs/` 14 PFX (LocalMachine\My private-key certs, pass `ruvds-backup-pfx`)
- `ssh-config/` (sshd_config + administrators_authorized_keys)
**Path on kreknin:** `/volume1/NetBackup/ruvds-iis/<date>/`. Retention 7 daily snapshots (pruned by rclone purge step 4).
**Schedule:** Task `RUVDS-Backup-Daily`, daily 04:30 MSK as SYSTEM, 2h timeout.
**Notifications:** ntfy `vds-backup` topic (shared с VDS backup для phone-side aggregation). Title differentiator: `RUVDS backup OK/FAILED (<date>)`.
**Scripts:** `scripts/ruvds-backup-daily-kreknin/``setup.ps1` (one-time install: SSH key + rclone + configs + ScheduledTask) + `run.ps1` (live backup logic) + README.
**Findings (Decisions log):**
- rclone SFTP без host-key validation (rclone go-sftp library не parsит ssh-keyscan format корректно — key mismatch даже на fresh keys; key-auth = sufficient для нашего threat model)
- `Invoke-Rclone` wrapper нужен — `$ErrorActionPreference='Stop'` + rclone NOTICE на stderr = PS terminating error; wrapper switches к Continue для rclone calls
- `Backup-WebConfiguration -Force` параметр отсутствует на этой версии IIS module — используем `Remove-WebConfigurationBackup` если exists + plain `Backup-WebConfiguration`
**Open follow-ups (не блокер):**
- PFX export pass `ruvds-backup-pfx` — temporary. TODO: pass-equivalent на Windows (gpg4win + pass-bash, или derived от machine-creds).
- Retention prune ещё не сработал (1 snapshot today, < 7); проверится в day 8.
**Atomic revert:** см. `scripts/ruvds-backup-daily-kreknin/README.md` §Atomic revert.
**Closes** "Backup strategy для RUVDS IIS" Open question в `[iis-migration-to-ruvds]`.
**Branch:** master
<!-- created-by: vitya / 2026-05-24 / trigger: post-iis-migration-partial-cutover -->
<!-- closed-by: vitya / 2026-05-24 11:46:51 / run #1 OK 20 sec; full backup 8.8 GB on kreknin -->
---
## 🟡 [iis-migration-to-ruvds] — RUVDS IIS live для 1 prod hostname (`kupimknigi.spb.ru`), 24 в очереди; источник held для rollback
**Status:** in_progress (DNS partial cutover — soak window 2026-05-24)
**Where I stopped:** 2026-05-24 ~00:00 — `kupimknigi.spb.ru` DNS A flipped на 80.64.31.36 в reg.ru, authoritative `ns1.reg.ru` корректный, public resolver caches (8.8.8.8=6h, 1.1.1.1=24h, Yandex=2h) держат старое значение → real users мигрируют постепенно по TTL expiry. RUVDS state: snolla site (8.66 GB / 44725 files) transferred + IIS recreated + 25 HTTPS SNI bindings с LE certs (R13, valid до 2026-07-22), 7 prod hostnames live-smoke через VDS (третья сеть) → 200 OK / correct content. Source IIS:8089 + traefik routes ALIVE — rollback ready.
**Findings зафиксированы в** [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md) Decisions log: (1) outbound 445 блокирует home ISP, не RUVDS-FW → SSH/scp = canonical transfer-метод (deprecate SMB section в `windows-server-2025-core-bootstrap.md`); (2) home network HTTP-middlebox mangles Host header for direct external HTTP — real end-users не пострадают, тестировать через VDS; (3) traefik acme.json → IIS PFX recipe работает (extract + openssl pkcs12 -export + Import-PfxCertificate + AddSslCertificate by thumbprint); (4) IIS 10 HTTP/2 default; (5) `maljarka.tandemmebel.ru` + 3 rimiz hostnames вернули 502/404 — pre-existing CMS-tenant config gap, не migration defect.
**Next action:**
1. **Снизить DNS TTL** в reg.ru на остальные 24 hostnames до 300s — `pilorama98.ru`, `emspb.ru`, `tandemmebel.ru`, `labtools.{ru,pro}`, `snolla.com` + 11 snolla subdomains. Это сократит будущий cache-tail с 24h до 5 мин.
2. **24h soak kupimknigi** — проверить через cache-clean resolver (можно подождать пока 8.8.8.8 cache expire'нет — ~6h, потом curl без `--resolve`).
3. **Bulk DNS swap** оставшихся 24 hostnames на 80.64.31.36 (single sitting).
4. **1-week prod soak** с RUVDS как live source.
5. **Decommission source IIS:8089** (но не traefik сам — много non-CMS routes остаётся): stop/remove IIS site `snolla` на windows-recovery-host + remove CMS traefik yml files.
6. **LE renewal pipeline** — win-acme + HTTP-01 на RUVDS после full cutover (LE certs expire 2026-07-22, soak window до ~07-15).
7. **Cleanup migration-temp:** `Remove-NetFirewallRule -DisplayName 'smb-from-source','ssh-from-source'` на RUVDS, удалить `~/.ssh/ruvds-iis-migration*` на source, очистить `C:\ProgramData\ssh\administrators_authorized_keys` на RUVDS.
Полный план + Completed + Open questions + Remaining steps — в [iis-migration-to-ruvds.md](iis-migration-to-ruvds.md).
**Branch:** master
<!-- created-by: user-decision / 2026-05-22 / trigger: RUVDS purchased -->
<!-- partial-cutover-by: vitya / 2026-05-24 / kupimknigi DNS flipped, 24 hostnames pending -->
---
# Imported from MoreThenCms — 2026-05-21 subtree-split migration
Полное содержимое `.tasks/STATUS.md` из MoreThenCms на момент 2026-05-21, **CMS-domain блоки исключены** (остались в MoreThenCms): `cms-admin-assets-root-folders-seed`, `cms-port-leak-fix`, `traefik-maljarka-502-bug`, `cms-maljarka-https-mode-bug-fix`. История per-file сохранена через subtree-split + temp-prefix merge.
## 🟢 [vds-kzntsv-bootstrap] — VDS поднят, gitea/verdaccio/registry мигрированы (3/3 phases done 2026-05-20)
**Status:** done (2026-05-20 вечер — все 3 заявленные фазы закрыты)
**Where I stopped:** Phase 1 + Phase 2 завершены ✅. VDS `89.253.255.94 / vds.kzntsv.site` Ubuntu 24.04 upgraded через VNC. sudo vitya (NOPASSWD) + ssh-key + hardened sshd (key-only) + ufw 22/80/443 + DB-порты + fail2ban + docker 29.5.1 + buildx + compose. Traefik v2.11 на `traefik.vds.kzntsv.site` (basicAuth vitya:Pryakhin9). Portainer CE 2.21.5 на `portainer.vds.kzntsv.site`**админ-пароль был принудительно изменён на `Pryakhin9-VDS-2026` (18 chars)** из-за hard min-12-char policy в Portainer 2.21+ (regression от 2.20). API key получен и сохранён в `vds-kzntsv.env`. 4 DB-стека (Postgres 16, MariaDB 11.4, Mongo 7, Redis 7) подняты с self-signed TLS, доступны снаружи через traefik raw-TCP passthrough на `<db>.vds.kzntsv.site:<port>` (HostSNI(*) — traefik tls.passthrough+SNI не работает с STARTTLS-протоколами PG/MariaDB; rawTCP forward, DBs терминируют TLS сами). Все DB passwords (PG/Maria/Mongo/Redis) — strong random hex16, сохранены в `vds-kzntsv.env`.
**Open questions:** LE certs для DBs (сейчас self-signed → клиент verify-skip; permanent fix позже через lego sidecar extract из traefik acme.json).
**Next action:** Phase 3 миграция с kreknin **ВСЯ DONE ✅**. 3.1 gitea (132 repos, 4 users, v1.25.5 на git.kzntsv.site). 3.2 verdaccio (8.6G storage, 2063 packages, secret 32 chars). 3.3 registry — **отказались от миграции** старых 35G images (user: «новых наделать могу»), fresh install на registry.kzntsv.site + Joxit UI на registry-ui.vds.kzntsv.site (DELETE_IMAGES=true для GUI cleanup, REGISTRY_STORAGE_DELETE_ENABLED для API delete). Auth vitya:Pryakhin9 (см. vds-kzntsv.env). Follow-up tasks: vds-gc-cron, vds-backup-rsync-kreknin, vds-ntfy-push.
**Branch:** master
---
## 🟢 [iis-on-host-migration] — attempt 2 close-out: 36h soak passed, 8/8 наших sites зеленые
**Status:** done (2026-05-21 close-out). Detail: Phase 11 в [iis-host-migration-2026-05-19](../.wiki/sources/iis-host-migration-2026-05-19.md).
**Close-out evidence:** traefik `Up 38h` (zero restarts), w3wp 8.8h (scheduled pool recycle ~29h default, не crash), 8/8 наших sites вернули `Microsoft-IIS/10.0` через full traefik HTTPS chain (`emspb/snolla/on.snolla/pilorama98/labtools.ru/labtools.pro/tandemmebel/kupimknigi`). Маljarka/sestech/ics-artmaterials оказались **off-infra** (DNS снят / parked / external nginx) — не наша инфра, не regression. Новая follow-up task ⚪ `iis-traefik-dead-routes-cleanup` (низкий приоритет — почистить yml для 3 dead доменов).
**VM savestate:** **done 2026-05-21 05:13 MSK** (45.6s, VMState=saved). Savestate file 1.73 GB (`C:\Users\vitya\VirtualBox VMs\snolla-recovery\Snapshots\2026-05-21T05-12-43-636491200Z.sav`). VBoxHeadless процессы освободили ~4 GB private memory. Resume: `VBoxManage startvm snolla-recovery --type headless` (~30 сек если понадобится). Disk delete (`unregistervm --delete`, ~95 GB) — позже через неделю uptime.
**Atomic revert (unchanged):** ```Get-ChildItem C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\*.yml.bak-pre-attempt2-2026-05-19 | %{ Copy-Item $_.FullName -Destination ($_.FullName -replace '\.bak-pre-attempt2-2026-05-19$','') -Force }; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\stostayer.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\stostayer.yml -Force -EA SilentlyContinue; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\oldstostayer.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\oldstostayer.yml -Force -EA SilentlyContinue; docker restart traefik```
**Branch:** master
---
## 🟢 [iis-traefik-dead-routes-cleanup] — sestech + isc-artmaterials disabled, maljarka оказалась live
**Status:** done (2026-05-21 ~05:42 MSK; ~20 мин работы).
**Result:**
- `sestech.yml` → `.disabled` (backup `.bak-pre-dead-routes-cleanup-2026-05-21`). DNS `sestech.ru/www.sestech.ru → 31.31.205.163` = parking provider, наш traefik больше не отвечает (public requests идут на parking host через DNS).
- `isc-artmaterials.yml` → `.disabled` (backup created). DNS `ics-artmaterials.com/www → 87.236.16.28 = external nginx-reuseport WP-хостинг`, public traffic не через нас.
- **Maljarka — НЕ disabled** — оказалось yml route'ит `maljarka.tandemmebel.ru` (subdomain тандеммебели), не голый `maljarka.ru`. DNS → **94.19.247.14 = наш IP**, IIS backend отвечает 200 OK ("Малярка от Тандеммебель", 43KB). Live route, не dead. Изначальная task'а ошиблась с hostname.
**Smoke regression:** 4 live hosts (emspb/on.snolla/www.tandemmebel/kupimknigi) → Microsoft-IIS/10.0 без regression. File-provider auto-reload, traefik restart не понадобился.
**Side finding:** `maljarka.tandemmebel.ru` через traefik HTTPS → **502 Bad Gateway**, хотя backend (host.docker.internal:8089 + Host header) возвращает 200 OK. Конфиг yml identical к работающему `tandemmebel.yml`. Live bug. Новая follow-up ⚪ task `traefik-maljarka-502-bug` (CMS-domain — остаётся в MoreThenCms).
**Atomic revert:** ```Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\sestech.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\sestech.yml -Force; Move-Item C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\isc-artmaterials.yml.disabled C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\isc-artmaterials.yml -Force```
**Branch:** master
---
## 🟢 [vds-gc-cron] — weekly GC armed для verdaccio + registry на VDS
**Status:** done (2026-05-21 00:21 MSK; ~1ч работы). Детали в [vds-gc-cron.md](vds-gc-cron.md).
**Result:** `/etc/cron.d/vds-gc` armed — Sun 03:00 MSK registry-gc.sh, Sun 03:30 MSK verdaccio-prune.sh. Scripts в `/opt/stacks/gc/scripts/` (`notify.sh`, `registry-gc.sh`, `verdaccio-prune.sh`+`verdaccio-prune.js`). Logs в `/var/log/vds-gc/`. Notify через ntfy `vds-ops`.
**Verdaccio prune real run:** 8.5G → 6.8G (freed 1.7G), 7021 packages scanned, 1551 modified, 4524 tgz deleted, 0 errors, 73 locally-published versions защищены (@snollajs/* + similar — detected via `_distfiles[tgzName]` absent). Atomic JSON rewrite (`tmp + rename`) + `_rev` bump.
**Registry GC smoke:** alpine v1+v2 pushed → DELETE manifest → GC freed 61M (whole tree, since both tags pointed одну digest). Empty-storage guard added (first run на свежем registry — `/var/lib/registry/docker/registry/v2/repositories` отсутствует → skip+min-prio notify, не FAIL).
**Atomic revert:** `ssh vitya@89.253.255.94 'sudo rm /etc/cron.d/vds-gc; sudo systemctl restart cron; sudo rm -rf /opt/stacks/gc /var/log/vds-gc'`
**Branch:** master
---
## 🟢 [vds-backup-rsync-kreknin] — daily VDS→kreknin live, smoke green, cron armed
**Status:** done (2026-05-20 → 2026-05-21 00:00 first-run завершён успешно). Детали в [vds-backup-rsync-kreknin.md](vds-backup-rsync-kreknin.md).
**Result:** rsync `--link-dest` pipeline `/opt/stacks/backup/scripts/run.sh` под cron `0 5 * * *` (root) → kreknin `/volume1/NetBackup/vds-kzntsv/<DATE>/` + symlink `latest`. DB dumps (pg/maria/mongo/redis), system config (/etc/ssh, ufw, hosts, docker), ssh keys, /opt/stacks data. Notification dual-channel: email через msmtp+Yandex (`/etc/msmtprc`) + ntfy publish на `vds-backup` topic. Retention 7 daily snapshots. Smoke run done — 71,867 files / 11.74G в 21m27s, exit 0. Cron service installed (apt install cron — Ubuntu 24.04 не имел его) + enabled. Next run автоматически 2026-05-21 05:00 MSK.
**SPOF gap status:** closed for VDS — full restore from kreknin возможен (DB dumps + конфиги + ssh keys).
**Open:** phone-side smoke (user verifies ntfy push пришёл из реального backup run).
**Atomic revert:** `ssh vitya@89.253.255.94 'sudo systemctl stop cron; sudo rm /etc/cron.d/vds-backup; sudo apt-get remove -y cron msmtp msmtp-mta; sudo rm -rf /etc/msmtprc /opt/stacks/backup /var/log/vds-backup'`
**Branch:** master
---
## 🟢 [vds-ntfy-push] — self-hosted ntfy.vds.kzntsv.site live, phone-verified
**Status:** done (2026-05-20 вечер; ~30 min работы). Detail в [vds-ntfy-push.md](vds-ntfy-push.md).
**Result:** ntfy v2.11.0 на `ntfy.vds.kzntsv.site`, LE cert (R12, valid до 2026-08-18), admin user `vitya / Pryakhin9` (deny-all дефолт, admin role → rw to all), топики `vds-backup` + `vds-ops`. Phone-verified — оба push'а пришли в Android ntfy app (screenshot 22:54). Creds в `~/projects/.common/secrets/vds-kzntsv.env` + `/opt/stacks/ntfy/.env` (chmod 600).
**Integration ready for:** [[vds-backup-rsync-kreknin]] (publish status на `vds-backup`), monitoring scripts (на `vds-ops`).
**Atomic revert:** `ssh vitya@89.253.255.94 'cd /opt/stacks/ntfy && docker compose down -v && cd .. && rm -rf ntfy'` + remove ntfy entries из vds-kzntsv.env.
**Branch:** master
---
## 🟢 [nas-recovery] — клиентские сайты восстановлены, работают из публичного интернета
**Status:** done (2026-05-19 ~10:00 MSK — 15 часов работы)
**Final state:** OpenWRT NAT → traefik 2.6.6 (host:8000/4443) → VirtualBox VM snolla-recovery (IIS + CMS) + docker-стек на хосте (MSSQL/MinIO/ES/imgproxy/nginx). 13 client routes, 40 LE certs валидны. Проверено: пользовательский клиент из публичного интернета открывает snolla.com, pilorama98.ru, labtools.ru/pro, tandemmebel.ru, emspb.ru.
**Backup pull:** C:\nas-recovery\vm-sites\ (~11 GB inetpub/wwwroot + stayer/) — на случай если VM снова станет нестабильной.
**Open issues (минор):**
- X-Forwarded-Proto/Host: CMS делает redirect с портом 4443 в URL — нужно добавить middleware в traefik или включить trust в IIS.
- MinIO/Azure storage — пользователь упомянул "не так всё", ждём пояснение.
- docker-сокет проброс в traefik — daemon connection error (некритично, file-provider routing работает).
- VM в bridged-WiFi была нестабильной → переехали на NAT + port forwards.
- acme.json renewal через HTTP-01 фейлится для доменов с DNS не на нашем IP — нужно переключить на DNS-01 через REGRU (creds в env уже).
**Branch:** master
---
<!-- created-by: vitya / 2026-05-21 / trigger: [resilience-roadmap-design] workshop pass 1 -->
<!-- re-scoped: vitya / 2026-05-24 / cms-stopgap-backup-daily → windows-host-fallback-backup-daily; warm-standby snapshot for DR vs временный stop-gap -->

View File

@@ -1,41 +0,0 @@
# cms-stopgap-backup-daily
## Goal
Plug текущую дыру **RPO=∞** для CMS прод. CMS sites + MSSQL + MinIO на [[windows-recovery-host]] не имеют бэкапов нового состояния с 2026-05-19 (последний Hyper Backup был 2026-05-09 на мёртвой синке). Если windows-host сгорит сейчас — теряем всё.
Stop-gap: ежедневный rsync на [[kreknin-synology]] (RPO факт = 24ч) до пока MSSQL/MinIO не переедут на VDS через [[mssql-minio-migration-to-vds]], где backup'ы будут через существующий `vds-backup-rsync-kreknin` pipeline (RPO 1ч target).
**Это временное решение** — pipeline списывается после Фазы 2. Поэтому **не строить ничего изощрённого**: cron + rsync + ssh-pipe, паттерн уже работает на VDS.
## Key files
- `C:\sites\*` — IIS site directories (snolla, stostayer, stostayer.old)
- MSSQL container на windows-host, port 1433 — backup через `docker exec mssql /opt/mssql-tools/bin/sqlcmd ... BACKUP DATABASE ... TO DISK = '/var/opt/mssql/backup/...'`, потом скопировать `.bak` файлы из volume `mssql_mssql_data` на host
- MinIO data dir — `C:\Users\vitya\projects\docker\diskstation\minio\data\` (verify on impl)
- Target: `kreknin.site:/volume1/NetBackup/windows-cms/<DATE>/` (создать), retention 7 daily snapshots
- SSH key: `~/.ssh/id_ed25519_kreknin` (vitya@195.19.90.188), пароль см. [[../entities/kreknin-synology]]
- Notify: ntfy `vds-ops` topic (`https://ntfy.vds.kzntsv.site/vds-ops`, auth vitya/Pryakhin9, см. [[../entities/vds-kzntsv]])
## Acceptance
1. Daily ~03:00 MSK cron (Windows Task Scheduler) на windows-host выполняет:
- MSSQL FULL backup всех 5 prod DBs (MoreThenCms, Stayer*, stostayer, TireService) → `D:\backup\mssql\<DATE>\`
- rsync `D:\backup\mssql\``kreknin:/volume1/NetBackup/windows-cms/<DATE>/mssql/`
- rsync `C:\sites\``kreknin:/volume1/NetBackup/windows-cms/<DATE>/sites/`
- rsync MinIO data dir → `kreknin:/volume1/NetBackup/windows-cms/<DATE>/minio/`
2. ntfy push на `vds-ops` topic с status (success/failure + total size + duration)
3. Retention: 7 daily snapshots на kreknin (старше — auto-delete через script или kreknin-side cron)
4. **Smoke recovery test:** на отдельной машине / VM загрузить MSSQL backup в чистый MSSQL container, проверить что хотя бы одна DB readable (`SELECT TOP 1 ... FROM Articles` или эквивалент)
5. Documented in `.wiki/concepts/cms-stopgap-backup-pipeline.md` (создать) с atomic revert recipe
## Decisions log
- 2026-05-21: создана как **Фаза 1** из roadmap-workshop'а (см. [[../.wiki/concepts/future-resilient-architecture-goals]] §"CMS backup pipeline — 2-фазный план"). User-decision: не строить proper RPO 1ч pipeline на windows-host т.к. MSSQL/MinIO мигрируют на VDS в обозримом (2-4 недели). Этот pipeline = bridge, не target-state.
## Open questions
- [ ] Точное location MinIO data dir на windows-host — узнать на ходу через `docker inspect minio | jq '.Mounts'`
- [ ] Windows Task Scheduler vs WSL cron для расписания — TBD на impl (Task Scheduler нативнее, WSL даёт привычные bash-скрипты)
- [ ] Encryption бэкапов at-rest перед отправкой на kreknin — defer (kreknin наш host под нашим контролем)
- [ ] MSSQL backup — full ежедневно или differential? Full ~~достаточно для RPO 24ч stop-gap; tx-log backups оставляем на Фазу 2 для RPO 1ч
## Notes
Паттерн `vds-backup-rsync-kreknin` ([[../.tasks/vds-backup-rsync-kreknin]]) — reference implementation на VDS-стороне. Адаптировать на Windows: PowerShell вместо bash, rsync.exe (через Git Bash или Cygwin), SSH-ключ Windows path.
Atomic revert: `Unregister-ScheduledTask -TaskName "cms-stopgap-backup" -Confirm:$false; Remove-Item D:\backup\mssql\* -Recurse`. На kreknin: backup snapshots оставить как archive (read-only после revert).

View File

@@ -54,6 +54,8 @@ Migrate IIS hosting from [[../entities/windows-recovery-host]] (DESKTOP-NSEF0UK)
- **2026-05-23 23:30 (HTTPS bindings):** Экспортировал 14 LE certs из traefik `acme.json` (`C:\Users\vitya\projects\docker\diskstation\traefik\letsencrypt\acme.json`) → openssl pkcs12 -export → PFX → Import-PfxCertificate на RUVDS → New-WebBinding `*:443:<hostname>` с SslFlags=1 (SNI) → AddSslCertificate by thumbprint. 25 HTTPS bindings live. Cert chain valid (LE R13), HTTP/2 auto-negotiated.
- **2026-05-23 23:35 (live smoke from VDS):** 7 prod hostnames → 200 OK + correct content; canonical bare→www 301s (CMS-side); 4 hostnames (`maljarka.tandemmebel.ru`, `rimiz.ru`, `www.rimiz.ru`, `rimiz.snolla.com`) → 502/404 — CMS-internal tenant mismatch (на source IIS:8089 они возвращают 200 default = тоже degraded). Не блокирует cutover.
- **2026-05-24 ~00:00 (DNS partial-cutover):** user flipped A `kupimknigi.spb.ru` в reg.ru — сначала на `89.253.255.94` (VDS Linux, ошибка), потом на `80.64.31.36` (RUVDS). Authoritative `ns1.reg.ru` отдаёт правильное; public resolver-cache (8.8.8.8=6h, 1.1.1.1=24h, Yandex=2h) держат старое до TTL expiry. Real end-users мигрируют postepenno over cache TTL. **TTL=86400 — slишком много. Recommendation: снизить до 300s в reg.ru для всех hostnames в scope ДО полного DNS swap'а.**
- **2026-05-24 ~12:00 (second cutover):** `emspb.ru` + `www.emspb.ru` DNS flipped на 80.64.31.36 (1.1.1.1 + Yandex + reg.ru уже отдают новое, 8.8.8.8 кешировал старое). Smoke через --resolve и через partially-cached real DNS → 200 OK c correct content.
- **2026-05-24 (scope narrow):** user-decision — `tandemmebel.ru` + `www.tandemmebel.ru` ОСТАЮТСЯ на windows-IIS на неопределённый срок. Cert на RUVDS уже импортирован, HTTPS binding existing — но DNS не свапаем. Source IIS `snolla` site нельзя decommission'ить пока tandemmebel на нём же (catch-all binding). Это разделяет migration на 24 hostnames мигрируют, 2 (`tandemmebel.ru` + www) остаются. Future migration tandemmebel — отдельная задача когда user решит.
- **MinIO pipeline caveat (carry-over от `[iis-cutover-to-vds-services]`):** RUVDS IIS coupled к windows-host imgproxy через DNS `imgproxy.kzntsv.site`. SPOF home machine остаётся для image-rendering. Verified post-migration: `Test-NetConnection imgproxy.kzntsv.site -Port 443` с RUVDS = OK. Image pipeline working but не resilient.
## Open questions

View File

@@ -0,0 +1,87 @@
# windows-host-fallback-backup-daily
## Goal
Ежедневный backup [[../entities/windows-recovery-host]] (DESKTOP-NSEF0UK) → [[../entities/kreknin-synology]] чтобы windows-host оставался **готовым принять prod трафик** если VDS или RUVDS теряем.
Текущая архитектура после iis-migration-to-ruvds:
- **Active prod** = RUVDS (IIS) + VDS (MSSQL, MinIO standby, Gitea, Verdaccio, Registry, ntfy, oCIS)
- **Warm standby** = windows-host (IIS:8089 + MSSQL container + MinIO container + traefik routes) — всё установлено и работает, но не получает live traffic после DNS swap.
Если RUVDS или VDS падают — поднимаем DNS обратно на windows-host (`94.19.247.14`) → traefik routes уже live → IIS:8089 → CMS + MSSQL + MinIO localhost-stack.
**Чтобы это failover реально работал**, нужен ongoing daily backup данных на windows-host. Иначе через месяц simmering windows-host'а его state diverged от prod (image uploads через RUVDS не отражаются на windows-host MinIO, новые DB записи через VDS MSSQL не приходят на windows-host MSSQL). Failover при этом = откат к stale state.
Этот backup решает **другую** задачу:
- Не "primary backup of windows-host" (windows-host = standby, не prod source-of-truth) — для prod-data primary backup'и это `vds-backup-rsync-kreknin` 🟢 (VDS) + `ruvds-backup-daily-kreknin` 🟢 (RUVDS).
- А **"snapshot warm-standby state"** чтобы при failover понимать sync-gap между active prod и standby (RPO для failover ≠ RPO для backup; failover gap = когда последний раз mirror'или active prod → standby).
## Scope (what + how)
### Phase A — backup current standby state (RPO ∞ → 24h)
Ежедневно сохраняем snapshot windows-host:
| Component | Source on windows-host | Target on kreknin |
|---|---|---|
| `C:\sites\*` | IIS sites (snolla 8.66 GB + stostayer + stostayer.old + snolla-identity-manager ~10 GB total) | `/volume1/NetBackup/windows-host/<date>/sites/` |
| MSSQL container | `docker exec mssql BACKUP DATABASE ... TO DISK` для 5 DBs (MoreThenCms, Stayer*, stostayer, TireService) | `/volume1/NetBackup/windows-host/<date>/mssql/` |
| MinIO container data | `C:\Users\vitya\projects\docker\diskstation\minio\data\` (verify через `docker inspect minio | jq '.Mounts'`) | `/volume1/NetBackup/windows-host/<date>/minio/` |
| traefik config + acme.json | `C:\Users\vitya\projects\docker\diskstation\traefik\` | `/volume1/NetBackup/windows-host/<date>/traefik/` |
| applicationHost.config | `C:\Windows\System32\inetsrv\config\applicationHost.config` | `/volume1/NetBackup/windows-host/<date>/iis-config/` |
Tool: **PowerShell + rclone SFTP** (тот же pattern что `ruvds-backup-daily-kreknin` 🟢; rclone единый binary, ssh-keys в `C:\ProgramData\backup\`).
### Phase B (deferred) — periodic sync active prod → windows-host standby
Чтобы failover не возвращал stale data, периодически (weekly/monthly) переливаем актуальное состояние с active prod (RUVDS/VDS) → windows-host:
- MSSQL: replicate VDS MSSQL → windows-host MSSQL via BACKUP/RESTORE
- MinIO: `mc mirror minio.kzntsv.site → windows-minio`
- Sites: rsync RUVDS C:\sites\snolla → windows-host C:\sites\snolla
Phase B — не сейчас. Сначала Phase A; через 1-2 недели понять насколько diverged будет state.
## Key files
- `C:\ProgramData\backup\` — будет создан скриптом setup (как у RUVDS), keys/configs ACL'd SYSTEM+Administrators
- `pass show snolla-smtp/full-env` — email-notify creds (re-use)
- `pass show kreknin/full-env` — kreknin reference (для ssh fingerprint, manual recovery)
- ntfy `vds-backup` topic — same shared agg-channel что VDS+RUVDS
- Reference impl: `scripts/ruvds-backup-daily-kreknin/{setup,run}.ps1` — копировать паттерн, адаптировать paths
## Acceptance
1. ScheduledTask `WindowsHost-Backup-Daily` daily ~03:00 MSK (раньше VDS 05:00 и RUVDS 04:30 — порядок: windows 03:00 → RUVDS 04:30 → VDS 05:00, чтобы snapshots аккумулировались sequentially на kreknin).
2. Все 5 components на kreknin `/volume1/NetBackup/windows-host/<date>/`.
3. Retention 7 daily snapshots (как у других).
4. Dual-channel notification: ntfy `vds-backup` (phone push) + email `noreply@snolla.com → vitya.kuznetsov@gmail.com`.
5. **Smoke recovery test** (одноразовый): restore MSSQL `.bak` в чистый container на VM, `SELECT TOP 1 ... FROM Articles` works.
6. README в `scripts/windows-host-fallback-backup-daily/` с атомным revert.
## Decisions log
- **2026-05-21:** task создана как `cms-stopgap-backup-daily` — Фаза 1 backup до migration MSSQL/MinIO на VDS, scope = временный pipeline.
- **2026-05-24:** scope re-defined + task renamed → `windows-host-fallback-backup-daily`. Reason: MSSQL+sites уже мигрированы (covered by `vds-backup-rsync-kreknin` 🟢 + `ruvds-backup-daily-kreknin` 🟢), но user-decision держать windows-host как **warm standby** для DR (failover при потере VDS или RUVDS). Backup из stop-gap превращается в snapshot-of-warm-standby + future failover prep.
- **2026-05-24:** tool = rclone (же что RUVDS backup, не rsync). Reasoning: тот же proven pattern, нет лишних установок.
- **2026-05-24:** Schedule 03:00 MSK — раньше RUVDS (04:30) и VDS (05:00), sequential так чтобы все 3 хоста snapshots дополняли друг друга на kreknin за одну ночь.
## Open questions
- [ ] Точная location MinIO data dir на windows-host — `docker inspect minio | ConvertFrom-Json | %{ $_[0].Mounts }` (PowerShell-version of jq).
- [ ] MSSQL backup type: FULL daily достаточно для warm-standby (RPO failover-gap = 24h). Differential / tx-log оставить для Phase B.
- [ ] Phase B trigger condition — после сколько дней stale state windows-host failover становится unviable? Probably 7-14 days. Decision postpone после первой недели Phase A.
- [ ] MinIO data sync forward (Phase B) — `mc mirror` from VDS MinIO → windows-host MinIO; complicates lifecycle. Postpone.
- [ ] Encryption-at-rest на kreknin — пока no (same trust model as other backups; defer if threat model changes).
## Completed steps
(none yet)
## Notes
- **Это не "primary backup" CMS data** — primary backup = `ruvds-backup-daily-kreknin` 🟢 для sites + `vds-backup-rsync-kreknin` 🟢 для MSSQL/MinIO. Этот pipeline = warm-standby snapshot для DR scenario.
- **Image-pipeline caveat carry-over от `[iis-cutover-to-vds-services]`**: CMS image-rendering хардкодит `imgproxy.kzntsv.site` через DLL. Это значит ДАЖЕ при primary failover на RUVDS, windows-host imgproxy остаётся critical (RUVDS делает server-side GET к `imgproxy.kzntsv.site`). Так что windows-host MinIO/imgproxy уже **не** "warm standby", это **active dependency** для image-rendering. Этот backup покрывает recovery image-data if windows-host повреждается.
- **Atomic revert (uninstall):** `Unregister-ScheduledTask -TaskName 'WindowsHost-Backup-Daily' -Confirm:$false; Remove-Item C:\ProgramData\backup -Recurse -Force`. На kreknin: keep snapshots as archive (read-only after revert).
<!-- created-by: vitya / 2026-05-21 / trigger: roadmap-workshop pass 1 [resilience-roadmap-design] -->
<!-- re-scoped: vitya / 2026-05-24 / from `cms-stopgap-backup-daily``windows-host-fallback-backup-daily` — warm standby snapshot vs временный stop-gap -->