Files
admin/.tasks/ARCHIVE.md
vitya b7b8e27a27 tasks(books-ssh-audit-shared-vds): close 🟢 — audit clean (1 retained, 0 revoked)
SSH audit на shared VDS vds-kzntsv (89.253.255.94, hosts books Slovo +
Bookva в shared compose-стеке) перед Phase 3 cutover'ом tenant-split.

Findings:
- 1 retained key: vitya@DESKTOP-NSEF0UK (core dev, sole admin)
- 0 keys to revoke — никаких analyst / former employee / unknown keys
- 1 cosmetic cleanup: removed dead root authorized_keys entry (was
  duplicate of vitya's key, dead из-за `permitrootlogin no`)
- sshd hardening verified via `sshd -T` (effective config: root-no,
  password-no, kbd-no). **Gotcha** noted: raw grep of /etc/ssh/sshd_config
  shows defaults; sshd_config.d/ overrides делают effective. Future
  audits use `sshd -T`, not raw grep.
- fail2ban active, 2670 failed / 37 banned hist, currently 0
- last 7 days journalctl ssh: только vitya@94.19.247.14 (мой home IP)

Acceptance per spec (Фаза 3, шаг 2 tenant-split):
 authorized_keys reviewed
 Non-core keys revoked (N/A — none existed)
 Analyst keys revoked (N/A — never issued)
 Documented in .wiki/concepts/books-ssh-access.md

Ingest: .wiki/concepts/books-ssh-access.md — retained keys table +
sshd state + fail2ban + login history + add/revoke processes +
cross-refs. Logged + indexed.

STATUS.md  block → ARCHIVE.md (per-task file kept in .tasks/).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:59:50 +03:00

848 lines
83 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
---
## 🟢 [windows-host-fallback-backup-daily] — Closed 2026-05-24 — daily 03:00 MSK rclone SFTP windows-host → kreknin live; smoke #1 ✓ (4868 sec / 23 GB / 5 DBs)
**Closed:** 2026-05-24 15:25:09 — end-to-end pipeline verified, ntfy + email fired clean.
**Components synced (6 paths) on kreknin `/volume1/NetBackup/windows-host/2026-05-24/`:**
- `mssql/` 486 MB (5 .bak FULL+COMPRESSED+INIT: MoreThenCms 234.7 + StayerCalculator 49.8 + StayerPrice 6.3 + stostayer 193.6 + TireService 0.7)
- `sites/` 20 GB (`C:\sites\*` — snolla + stostayer.old + stostayer + snolla-identity-manager)
- `minio/` 3.1 GB (windows-host MinIO data dir)
- `traefik/` 1.1 MB (config + acme.json)
- `iis-config/` 68 KB (applicationHost.config)
- `iis-backup-webconfiguration/` 384 KB (Backup-WebConfiguration snapshot `daily-<date>`)
**TOTAL: 23 GB / 4868 sec (~81 min) на home uplink → kreknin (1.5 TB used / 5.6 TB free).**
**Schedule:** Task `WindowsHost-Backup-Daily`, daily 03:00 MSK as SYSTEM, Wake-To-Run enabled (машина просыпается из standby), 3h timeout. Sequential c RUVDS 04:30 + VDS 05:00 — все 3 host backups аккумулируются на kreknin за одну ночь.
**Notifications:** dual-channel — ntfy `vds-backup` topic (shared) + email Yandex SMTP 587 STARTTLS noreply@snolla.com → vitya.kuznetsov@gmail.com. Subject `windows-host backup <date> -- SUCCESS / FAILED`.
**Scripts:** `scripts/windows-host-fallback-backup-daily/` — `setup.ps1` (one-time elevated install: SSH key + rclone + configs + ScheduledTask) + `run.ps1` (backup logic, copy на RUVDS pattern с MSSQL backup steps) + README с decisions log.
**Findings (Decisions log → scripts README):**
- rclone в `C:\ProgramData\backup\rclone.exe` (не Program Files) — non-admin staging без admin elevation.
- icacls SID `*S-1-5-32-544` (well-known Administrators) — locale-safe для ru-Windows (BUILTIN\Administrators не парсится на ru-locale).
- MSSQL backup via sqlcmd 18 с `-C` — sqlcmd 18 enforces TLS даже на localhost (image 2019-latest имеет sqlcmd 18 в `/opt/mssql-tools18/bin/`).
- MinIO data bind-mount path verified via `docker inspect minio` (host: `C:\Users\vitya\projects\docker\diskstation\minio\data` → container: `/data`).
- MSSQL_SA_PASS в config.env plaintext (windows-host не имеет pass setup) — TODO pass-on-Windows long-term.
- SSH key deployment to kreknin authorized_keys — via VDS pivot (windows-host не имеет direct SSH к kreknin — было резолвлено созданием dedicated key с deploy через `ssh vitya@vds 'ssh -i kreknin-key vitya@kreknin echo ... >> authorized_keys'`).
- Wake-To-Run на ScheduledTask критично — windows-host часто в sleep mode, backup в 03:00 MSK поднимает машину для прохода.
- Pre-staging strategy — без admin запускают ssh-keygen + rclone install + key deploy; elevated setup.ps1 потом только Register-ScheduledTask + lock-down ACL. Минимизирует admin surface.
**Open follow-ups (не блокеры):**
- Smoke recovery test (restore `.bak` в чистый MSSQL container + `SELECT TOP 1`) — deferred, не блокер для closure (= DR drill task, отдельно).
- MSSQL_SA_PASS в config.env plaintext → pass-on-Windows long-term.
- Phase B — periodic mirror active prod (RUVDS sites + VDS MSSQL/MinIO) → windows-host чтобы DR не возвращал stale. Decision after 1-2 недели Phase A.
- Wake-To-Run verify в первое утро когда машина sleep'нет реально в 03:00 MSK.
**Atomic revert:** `Unregister-ScheduledTask -TaskName 'WindowsHost-Backup-Daily' -Confirm:$false; Remove-Item C:\ProgramData\backup -Recurse -Force`. На kreknin (via VDS pivot): `rm -rf /volume1/NetBackup/windows-host`. Удалить windows-host-backup pubkey из kreknin authorized_keys.
**Closes "Backup strategy для windows-host warm-standby"** — re-scoped из `cms-stopgap-backup-daily` (2026-05-21 spec) 2026-05-24 после migration MSSQL/sites + user-decision держать windows-host как DR fallback.
**Branch:** master
---
## 🟢 [books-ssh-audit-shared-vds] — Closed 2026-05-24 — audit clean, 0 keys revoked + cosmetic dead-key cleanup
**Closed:** 2026-05-24 — SSH audit на shared VDS (vds-kzntsv = 89.253.255.94 — где books Slovo+Bookva живут в shared compose-стеке) clean: 1 retained key (vitya core dev), 0 keys revoked, sshd hardening verified.
**Audit findings:**
- `/home/vitya/.ssh/authorized_keys` — 1 key (`ssh-ed25519 …ER/Z vitya@DESKTOP-NSEF0UK`)
- `/root/.ssh/authorized_keys` — 1 dead duplicate key (root SSH disabled via `permitrootlogin no`), removed cosmetically (backup overwrite gotcha but trivially restorable from vitya's key)
- 2 system users with shell — root + vitya (минимум)
- `sshd -T` effective: `permitrootlogin no` + `passwordauthentication no` + `kbdinteractiveauthentication no` ✅
- **Gotcha noted:** raw `/etc/ssh/sshd_config` grep показывал `PermitRootLogin yes` + `PasswordAuthentication yes` — defaults from package. Override'ы в `/etc/ssh/sshd_config.d/*.conf` make effective config. Future audits должны use `sshd -T`, not raw grep.
- fail2ban active: 2670 failed / 37 banned historically, currently 0
- journalctl `Accepted publickey` last 7 days — только `vitya from 94.19.247.14` (мой home public IP), no surprises
**Acceptance check (per spec §Acceptance):**
- ✅ `~/.ssh/authorized_keys` на shared VDS reviewed
- ✅ Non-core keys revoked — N/A (only my key existed)
- ✅ Analyst keys revoked — N/A (никогда не выдавались)
- ✅ Documented in `OpeItcLoc03/admin/.wiki/concepts/books-ssh-access.md` (retained keys table + sshd state + fail2ban + login history + add/revoke processes + cross-refs)
**Implications для tenant-split Phase 3 §SSH-аудит:** ✅ verified clean — на промежуточной стадии (1 VDS, 2 стека, 2 БД), хотя infra-level изоляция теоретически compromised (vitya с SSH мог бы прочитать обе БД через localhost), de-facto risk = nil. Single-developer setup, analyst access всегда был app-level only. Pre-cutover sigh of relief.
**Atomic revert (если понадобится restore dead root key):** `ssh vitya@89.253.255.94 'echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKwYw1sS0Dj5FQmNxqJUV9IxU+9hZcg8qYQRgLN5ER/Z vitya@DESKTOP-NSEF0UK" | sudo tee /root/.ssh/authorized_keys'`. Key dead anyway (root SSH disabled), restoration cosmetic-only.
**Detail:** `.tasks/books-ssh-audit-shared-vds.md` (per-task file with Decisions log + Completed steps).
**Closes** Фаза 3, шаг 2 в `victor/books/.wiki/concepts/tenant-split.md`.
**Branch:** master