Задания, отчёты и патчи, лежавшие в C:\Users\Ochenstarik\projects и в домашней папке, перенесены в agents/. Разложено по агентам там, где имя файла позволяло определить автора; остальное — в _salvage-2026-08-18/ и разбирается вручную. Патчи в notes/salvage-2026-08-18/ — незакоммиченная работа из брошенных рабочих копий: она существовала только на диске. Тяжёлое (релизные архивы, инсталляторы, наборы данных) в репозиторий не попало: оно лежит рядом, в Agent_projects/_archive и Agent_projects/_data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
283 lines
25 KiB
Markdown
283 lines
25 KiB
Markdown
# План доделывания Server Monitor Manager
|
||
|
||
Для исполнителя (Codex). Составлен 2026-08-04 против `main` @ `b11c277` плюс незакоммиченная ветка `hermes/task3-b3-fact-reconciliation`.
|
||
|
||
Нормативные документы в репозитории после коммита `4de849e`: [`docs/product-horizons.md`](../server-monitor-manager/docs/product-horizons.md), [`docs/approval-policies.md`](../server-monitor-manager/docs/approval-policies.md), [`docs/integration-kagent.md`](../server-monitor-manager/docs/integration-kagent.md), обновлённые `docs/roadmap.md` и `docs/security-model.md`.
|
||
|
||
---
|
||
|
||
## 0. Правила работы
|
||
|
||
Проверены на четырёх блоках, менять не нужно.
|
||
|
||
1. **Один PR — одна тема.** Смешанные PR не проходят review.
|
||
2. **Независимому review передаётся полный текст задачи**, а не только диф. Три из четырёх блоков дали находки класса «пропущенное требование», которые при diff-only ревью не ищутся вовсе.
|
||
3. **`SHA256SUMS` считается последним действием**, после всех правок отчётов, и включает все файлы пакета.
|
||
4. **Статус после merge — `merged / verified`**, а не «выполнено», если хотя бы один критерий сформулирован через фактическое поведение и не измерялся на реальной топологии.
|
||
5. **Гейты горизонтов обязательны.** Задача следующего горизонта не начинается, пока не закрыт предыдущий, даже если она кажется мелкой.
|
||
6. Формат пакета сдачи: `REPORT.md`, `TEST_EVIDENCE.md`, патч и/или ZIP, `CI_CHECKS.txt`, `INDEPENDENT_REVIEW.md`, `SHA256SUMS`.
|
||
|
||
Размеры: **S** — до дня, **M** — 2–4 дня, **L** — неделя и больше.
|
||
|
||
---
|
||
|
||
## Горизонт 0 — закрыть начатое
|
||
|
||
Ничто из горизонтов 1–3 не начинается до полного закрытия этого раздела.
|
||
|
||
### T0.1 — завершить B-3R · M · в работе
|
||
|
||
Ветка `hermes/task3-b3-fact-reconciliation`. Контракт: `B3R_SPEC.md`, уже принятый исполнителем.
|
||
|
||
Осталось по состоянию на 04.08: пакетная финальная верификация (`1 + k` вызовов `link-list` вместо двух), класс `Deferred` с инвариантом `Converged + Failed + Deferred == Examined`, R4 (source-generated DTO для orphan audit), R5 (Linux integration test на fact-first протокол), R6 (убрать fail-open default в `ILinkPolicyApplier`), R7 (форматирование вложенных scope).
|
||
|
||
Дополнительно, найдено при проверке: любое не-`active` значение статуса в `nodes.tsv` сейчас даёт exit 80 и уходит в `Deferred` навсегда. Мусор в поле статуса должен давать 78 — это повреждённое состояние, а не ожидаемое.
|
||
|
||
**Готово, когда:** PR создан, Linux CI зелёный впервые для этой ветки, merge выполнен.
|
||
|
||
### T0.2 — физический acceptance · S · внешний блокер
|
||
|
||
```bash
|
||
SMM_ACCEPT_RESTORE=1 SMM_ACCEPT_REBOOT=1 tests/acceptance/three-server-mesh.sh
|
||
```
|
||
|
||
Требуются `HUB_SSH_HOST`, `HUB_SSH_USER`, `SOURCE_SSH_HOST`, `SOURCE_SSH_USER`, `HOME_WG_IP`, `SECOND_WG_IP`, `SSH_IDENTITY_FILE`. Harness готов и содержит шаги firewall-restore без перезапуска Node и инъекции orphan-правила.
|
||
|
||
Исполнитель закрыть не может — нужна выдача доступов. Висит четвёртый блок подряд и блокирует критерий B6, Этап 3 и Этап 5 roadmap.
|
||
|
||
**Готово, когда:** прогон завершился `THREE_SERVER_ACCEPTANCE=PASS`, вывод приложен к пакету.
|
||
|
||
### T0.3 — роль Monitor в bootstrap · M
|
||
|
||
Сегодня SSH-мониторинг — функция, ради которой существует Desktop, — не имеет серверной части. В `deploy/ochenstarik-server-monitor-manager.sh` нет действия `install-monitor`, forced-command скрипт не поставляется, а парсер `SshMonitorService.QueryAsync` ожидает ключи (`PROTOCOL`, `CPU_COUNT`, `MEM_AVAILABLE_KB`, `DISK_INODES_TOTAL`…), которые в репозитории никто не производит.
|
||
|
||
Объём:
|
||
|
||
- `install-monitor PUBLIC_KEY` и `uninstall-monitor`;
|
||
- системный пользователь `ochenstarik-monitor`: nologin, без пароля, без sudo;
|
||
- `/usr/local/libexec/ochenstarik-smm-metrics`, root-owned `0755`, выдающий ровно снимок из `docs/installer-contract.md` §7 и read-only `mesh status`;
|
||
- `authorized_keys` с `command="…",restrict,no-pty,no-agent-forwarding,no-port-forwarding,no-X11-forwarding`;
|
||
- идемпотентность повторного запуска;
|
||
- контрактный тест формата снимка, общий для скрипта и парсера Desktop, чтобы они не разъезжались.
|
||
|
||
**Готово, когда:** чистый сервер попадает под мониторинг одной командой, README-инструкция соответствует коду, тест формата в CI.
|
||
|
||
### T0.4 — подписанная поставка · M
|
||
|
||
Сейчас bootstrap сверяет `ARCHIVE.sha256`, лежащий рядом в том же релизе, а manifest не подписан и содержит только хэш самого bootstrap. Кто может опубликовать релиз — ставит произвольный root-код на весь парк через `update-agent`.
|
||
|
||
Объём:
|
||
|
||
- manifest v2: хэши всех артефактов, версии Control/Agent/helper/Desktop, минимальные совместимые версии, `helper_protocol`;
|
||
- подпись `cosign sign-blob` keyless либо `minisign`; публичный ключ или issuer **вшит константой** в bootstrap и Desktop, не скачивается вместе с релизом;
|
||
- `verify-manifest` как отдельное действие; `verify_archive` сверяет хэш с manifest, а не с соседним файлом; старый путь только под явным `SMM_ALLOW_UNSIGNED=1` с предупреждением;
|
||
- отказ `update-control`/`update-agent` при несовместимой паре версий;
|
||
- `PROGRAM_VERSION` подставляется из тега при упаковке вместо `0.2.0-dev`;
|
||
- негативные тесты в CI: изменённый байт архива, подменённый хэш в manifest, manifest без подписи — все три отвергаются.
|
||
|
||
**Готово, когда:** установка и обновление возможны только из подписанного релиза, три негативных теста зелёные.
|
||
|
||
### T0.5 — жизненный цикл сертификатов · S
|
||
|
||
`CertificateAuthority.cs:55` выдаёт клиентские сертификаты на год, автопродления нет. Через год весь парк отваливается одновременно.
|
||
|
||
- сократить срок до 30–90 дней;
|
||
- автопродление Agent за треть срока до истечения, через существующий mTLS-канал;
|
||
- событие `certificate.expiring` в поток событий;
|
||
- документированная процедура ротации Control CA и повторной регистрации парка.
|
||
|
||
### T0.6 — гигиена репозитория и цепочки поставки · S
|
||
|
||
- пиннинг всех GitHub Actions по commit SHA (сейчас плавающие `@v6`, `@v5`, `@v2`);
|
||
- `.github/dependabot.yml` для `github-actions` и `nuget`;
|
||
- сужение `permissions: contents: write` до job'а публикации;
|
||
- `SECURITY.md` — обязателен для инструмента безопасности; `CHANGELOG.md`, `CONTRIBUTING.md`, `CODEOWNERS`, шаблоны issue/PR;
|
||
- SBOM (`dotnet CycloneDX`) в артефакты релиза;
|
||
- удалить слитые ветки `agent/remove-lightweight-server-references`, `codex/ttl-backup-acceptance`.
|
||
|
||
### T0.7 — базовая инженерная оснастка · S
|
||
|
||
Делается один раз и удешевляет всё последующее.
|
||
|
||
- `Directory.Build.props`: `Nullable`, `TreatWarningsAsErrors`, `EnableNETAnalyzers`, `AnalysisLevel latest-recommended`, детерминированная сборка;
|
||
- `Directory.Packages.props` (central package management) и `RestorePackagesWithLockFile` — сейчас сборки невоспроизводимы, что противоречит требованию закреплённой поставки;
|
||
- разделить тесты: `Core.Tests`, `Control.Tests`, `Agent.Tests`, `Helper.Tests`. Сейчас `LinuxMetricsTests`, `MetricBufferTests` и `ProvisioningHelperTests` лежат в проекте Control;
|
||
- тесты round-trip сериализации контрактов — при `PublishTrimmed=true` молчаливая потеря поля ничем не ловится.
|
||
|
||
**Гейт Горизонта 0.** Чистый сервер ставится из подписанного релиза одной командой, регистрируется одноразовым кодом, попадает под мониторинг без ручной правки `authorized_keys`, и после перезагрузки Hub и Node фактическое состояние Links совпадает с желаемым — измерено связностью на реальной топологии.
|
||
|
||
---
|
||
|
||
## Горизонт 1 — продукт для одного владельца
|
||
|
||
### T1.1 — каркас provisioning-модулей · M · делать первым
|
||
|
||
Сейчас вся логика мутации живёт в `TimezoneProvisioningExecutor` одним классом на 320+ строк, а `IsTimezoneOnly` жёстко фиксирует `VmSwappiness == 60`, пустые пакеты и `RebootPolicy = never`. Второй модуль скопирует backup/verify/rollback целиком.
|
||
|
||
```csharp
|
||
IProvisioningModule
|
||
string ActionType { get; }
|
||
string ModuleHash { get; }
|
||
ValidationResult Validate(JsonElement parameters);
|
||
Plan BuildPlan(JsonElement parameters); // без мутаций
|
||
ExecutionResult Execute(Plan plan, Grant grant); // backup → mutate → verify → rollback
|
||
FactualState Observe();
|
||
```
|
||
|
||
Перенести timezone на каркас без изменения поведения, покрыть тем же набором тестов. Добавление модуля после этого — один файл, одна схема, один тест.
|
||
|
||
**Готово, когда:** timezone работает через каркас, существующие тесты зелёные без правок логики.
|
||
|
||
### T1.2 — provisioning-модули по одному · L
|
||
|
||
Порядок по возрастанию риска: `system.locale` → `system.packages` (versioned allowlist уже есть) → `system.swap` → `user.create` → `user.disable` → `ssh.key.add` → `ssh.key.remove`.
|
||
|
||
Каждый модуль: versioned JSON schema, immutable plan, подтверждение по политике, factual verification, rollback, audit, тесты, прогон на VM-матрице. Новый пользователь по умолчанию без sudo. Приватные ключи на Hub не попадают.
|
||
|
||
### T1.3 — политики подтверждения · M
|
||
|
||
По `docs/approval-policies.md`. Девять режимов вместо бинарного подтверждения. Решение принимает Control, не клиент. Понижение режима для конкретного задания невозможно, изменение политики — аудируемое событие.
|
||
|
||
Зависимость: T1.1, потому что режим выбирается по `ActionType`.
|
||
|
||
### T1.4 — firewall и двухфазная миграция SSH · L · самая опасная задача проекта
|
||
|
||
По `docs/provisioning-vpn-requirements.md` §10. Порядок шагов там уже выписан и его надо соблюсти буквально.
|
||
|
||
Критическое требование: порт 22 невозможно закрыть до успешной второй SSH-сессии на новом порту. Отдельное подтверждение на закрытие старого порта. При любой ошибке — откат drop-in и правила firewall с сохранением активной сессии.
|
||
|
||
Обязательный тест: новый порт не поднялся → 22 остался открыт и доступен.
|
||
|
||
Делать только после T1.1–T1.3 и полного Горизонта 0.
|
||
|
||
### T1.5 — alert engine · M
|
||
|
||
Пороги, warning/critical, duration, cooldown, дедупликация, режим обслуживания, подтверждение, эскалация, история инцидентов, автозакрытие, тихие часы. Формат правила уже согласован:
|
||
|
||
```yaml
|
||
name: Disk almost full
|
||
metric: disk.used_percent
|
||
condition: "> 90"
|
||
duration: 10m
|
||
severity: critical
|
||
channels: [telegram, email]
|
||
cooldown: 30m
|
||
```
|
||
|
||
### T1.6 — Telegram, только чтение · S
|
||
|
||
`/status`, `/servers`, `/alerts`, `/links`, `/jobs`. Никаких мутаций, приватных ключей, управления CA и произвольных команд. Токен — секрет; до появления Vault хранить как остальные секреты Control, с маскированием в диагностике и аудите.
|
||
|
||
### T1.7 — наблюдение Docker и systemd · M
|
||
|
||
Сначала только чтение: контейнеры, образы, healthcheck, счётчики рестартов, отсутствующие лимиты, failed units, journald. Действия — отдельной задачей, только по allowlist, с подтверждением и аудитом, без произвольных аргументов.
|
||
|
||
### T1.8 — Backup Manager · M
|
||
|
||
Источники: каталоги, конфигурации, Docker volumes, Control DB и CA. Хранилища local и S3. Расписания, шифрование, retention, контрольные суммы, проверка архива, тестовое восстановление, оповещения об ошибке и об устаревшей копии.
|
||
|
||
### T1.9 — технический долг Control и Desktop · M · фоном
|
||
|
||
- `ControlStore.cs` 1576 строк: разделить на `ControlSchema`, `IdentityStore`, `MetricStore`, `LinkStore`, `AuditStore`, `IdempotencyStore`;
|
||
- `MainPage.xaml.cs` 1124 строки без MVVM и DI: вынести ViewModel, добавить unit-тесты Desktop;
|
||
- локализация: русские строки захардкожены в бизнес-логике (`SshMonitorService`, `ControlClientService`); ввести `.resw`, в исключениях — коды, текст на уровне UI;
|
||
- `HttpClient` создаётся на каждый запрос в `ControlClientService` и `AgentClient`: один `SocketsHttpHandler` с `PooledConnectionLifetime`;
|
||
- поток событий: durable log, `GET /events?since=<sequence>`, keepalive раз в 15 с, лимит подписок, экспоненциальный backoff с джиттером на клиенте;
|
||
- rate limiting не только на enrollment: политики на группы `agents` и `control`, `MaxConcurrentConnections`;
|
||
- единый источник валидаторов: `NodeIdValidator` и прочие живут в `Program.cs` и продублированы в bash и helper — три реализации одних правил.
|
||
|
||
**Гейт Горизонта 1.** Каждый мутирующий модуль имеет схему, immutable plan, подтверждение, factual verification, rollback и audit и прошёл физическую приёмку на VM-матрице.
|
||
|
||
---
|
||
|
||
## Горизонт 2 — платформа
|
||
|
||
### T2.1 — решение по публичному интерфейсу · S · до всякого кода
|
||
|
||
Заполнить раздел «Публичный интерфейс» в `docs/security-model.md`: отдельный listener и порт, отношение веб-сессий к mTLS-идентичностям, перечень операций, недоступных веб-сессии в принципе, привязка сессии к устройству, CSRF, rate limiting, повторная аутентификация.
|
||
|
||
Без этого решения Web UI не начинается.
|
||
|
||
### T2.2 — Secret Vault · M
|
||
|
||
Envelope encryption, версии, ротация, маскированный показ, scoped delivery, TTL, отзыв, redaction. Предусловие для Telegram-токена, ключей S3 и VPN-подписок.
|
||
|
||
### T2.3 — Web UI и аутентификация · L
|
||
|
||
Разделы: Dashboard, Servers, Metrics, Links, Provisioning, Alerts, Audit, Users, Automation identities, Backups, Settings. Аутентификация: пароль, TOTP, коды восстановления, WebAuthn, passkeys, доверенные устройства, сессии, RBAC, повторная аутентификация.
|
||
|
||
Операции класса firewall / CA / удаление Node / экспорт секретов — только по mTLS-идентичности, не по веб-сессии.
|
||
|
||
### T2.4 — решение по хранилищу логов · S · до кода
|
||
|
||
Логи journald + Docker + nftables + SSH со всех узлов не лягут на Control SQLite и раздуют резервные копии. Выбрать: отдельное хранилище, выгрузка вовне, либо только tail на узле без центрального retention.
|
||
|
||
### T2.5 — централизованные логи · L
|
||
|
||
После T2.4. Поиск, фильтры, уровни, retention, redaction, таймлайн инцидента, экспорт диагностики, корреляция.
|
||
|
||
### T2.6 — security posture · M
|
||
|
||
Оценка, объяснения, план исправления, безопасное применение, история, исключения, принятые риски, отчёты.
|
||
|
||
### T2.7 — multi-Hub, фаза 1 · M
|
||
|
||
Резервные копии БД и CA, шифрованное хранилище, проверка восстановления, ручное переключение. Репликация, active/passive и кворум — вне плана.
|
||
|
||
### T2.8 — один VPN-модуль · L
|
||
|
||
Xray VLESS Reality, полный жизненный цикл: preflight, установка, обновление, отключение, verification, rollback timer, routing exclusions, kill switch с management exceptions, аварийное отключение без Control, reboot-тест, reconciliation, ротация секретов.
|
||
|
||
Второй VPN-модуль не начинается, пока первый не прошёл физическую приёмку. Шесть наполовину сделанных VPN — это шесть способов потерять доступ к серверу.
|
||
|
||
---
|
||
|
||
## Горизонт 3 — интеграция с KAgent
|
||
|
||
По `docs/integration-kagent.md`. Строго в порядке: T3.1 → T3.4.
|
||
|
||
### T3.1 — обнаружение и идентичность · M
|
||
|
||
Unix-сокет `/run/server-monitor-manager/integration.sock`, `root:smm-integrations`, `0640`, каталог `0750`. Обязательно переиспользовать меры из `ProvisioningHelperServer`: `SO_PEERCRED` со сверкой uid, обработка вне цикла accept, ограничение параллелизма, таймаут соединения, буферное чтение, лимиты частоты. Роль `KAgentIntegration` с одноразовым token, сроком, областью Nodes и отзывом.
|
||
|
||
### T3.2 — чтение · M
|
||
|
||
Nodes, метрики, здоровье, события. Согласование версии протокола и фактически выданных возможностей. Невыдаваемые возможности отсутствуют как код, а не выключены настройкой.
|
||
|
||
### T3.3 — планы и запросы · L
|
||
|
||
`*.plan` и `*.request` порождают обычный typed provisioning job с immutable plan, режимом подтверждения, execution grant, factual verification и audit. Отдельного пути для AI нет. `*.request` возвращает идентификатор задания, а не результат.
|
||
|
||
### T3.4 — жизненный цикл Worker · L · наивысший риск в проекте
|
||
|
||
Инвариант недоверенного исполнителя из `docs/security-model.md` — главное требование, всё остальное вторично. Отдельный пользователь без sudo, контейнер с read-only rootfs, seccomp, AppArmor, сброшенными capabilities; нет доступа к сокету helper, к `agent.pfx`, `control-ca.crt`, `nodes.tsv`, каталогу rollback; нет сетевого доступа к Control API; порт наружу не публикуется.
|
||
|
||
Задачные Links: обязательный TTL, запрет destination = Hub или другой Worker, снятие по завершении, отказу, таймауту, offline, истечению аренды и аварийной остановке, с фактической проверкой снятия.
|
||
|
||
Аварийные средства: отзыв сертификата, остановка всех Worker, отключение задачных Links, блокировка новых запросов, режим только чтения, карантин, отключение сети, сохранение журналов.
|
||
|
||
---
|
||
|
||
## Что не делать
|
||
|
||
- Не начинать VPN-модули до закрытия Горизонта 1. Kill switch при незакрытом расхождении desired/factual — гарантированная потеря доступа к серверу.
|
||
- Не добавлять второй provisioning-модуль копированием timezone-исполнителя. Сначала T1.1.
|
||
- Не проектировать API KAgent против сущностей, которых нет: `containers`, `services`, `logs`, `backups` появляются вместе с самими подсистемами.
|
||
- Не начинать macOS/Linux Desktop и мобильные клиенты до стабилизации контрактов `Core` — они меняются почти каждым PR.
|
||
- Не делать репликацию и кворум multi-Hub.
|
||
- Не расширять расширенный мониторинг (SMART, RAID, GPU, UPS) — каждый пункт требует привилегированного сборщика и отдельной модели угроз.
|
||
|
||
---
|
||
|
||
## Критический путь
|
||
|
||
```
|
||
T0.1 ─┐
|
||
T0.3 ─┼─► T0.2 (внешний блокер) ─► гейт Г0 ─► T1.1 ─► T1.2 ─► T1.3 ─► T1.4 ─► гейт Г1 ─► Горизонт 2 ─► Горизонт 3
|
||
T0.4 ─┤
|
||
T0.5 ─┘
|
||
```
|
||
|
||
T0.6 и T0.7 идут параллельно и ничего не блокируют. T1.5–T1.9 параллельны T1.2–T1.4.
|
||
|
||
Единственное, что нельзя обойти работой исполнителя, — **T0.2**. Он дешевле любой другой задачи в этом плане и блокирует всё.
|