Задания, отчёты и патчи, лежавшие в 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>
19 KiB
Antigravity: выдача кода регистрации узла из приложения — этап 1
Задание переписано 2026-08-17 после первой попытки. Тогда результатом стал файл с отчётом: ни ветки, ни PR, ни строчки кода в репозитории не появилось. Поэтому здесь сужен объём и указаны точные места в коде.
Что считается выполнением
Ссылка на PR в ochenstarik-ui/server-monitor-manager и ссылки на зелёные прогоны CI.
Документ с описанием проделанной работы результатом не является ни при каких обстоятельствах. Отчёт — это раздел в описании PR, а не отдельный файл. Если PR нет, задание не выполнено, независимо от того, что написано в отчёте.
Если задание невыполнимо или в нём ошибка — так и напишите, с указанием, что именно мешает. Это нормальный исход. Отчёт о несделанной работе как о сделанной — нет.
Репозиторий
- https://github.com/ochenstarik-ui/server-monitor-manager
- База:
main@5dc4c39 - Ветка:
antigravity/node-enrollment-endpoint mainзащищён: PR обязателен, обязательны статусыbuild-and-testиbuild, force-push запрещён- один PR — одна тема; описание PR заполняется после завершения CI
Задача этапа 1
Один HTTP-эндпоинт: оператор просит код регистрации нового узла, Control возвращает готовую строку SMMNODE2..., которую оператор вставляет в установщик на сервере.
Сегодня этот код можно получить только зайдя по ssh на Hub под root и выполнив ochenstarik-server-monitor-manager.sh node-code <имя>. Владелец хочет получать его из приложения, из которого и так ведётся наблюдение за парком.
Веб-интерфейс и вход по паролю — этапы 2 и 3, они описаны в конце и не начинаются, пока этап 1 не в main.
Что уже есть в коде
Большая часть механики существует, писать с нуля нужно немного.
В src/ServerMonitorManager.Control/Program.cs:
- строка 575:
var control = app.MapGroup("/api/v1/control").RequireAuthorization("Operator");— группа эндпоинтов оператора. Новый эндпоинт добавляется сюда, роль тем самым обеспечена; - строка 144, ветка CLI
token-create: выпуск токена уже реализован на C# —store.CreateEnrollmentTokenAsync(nodeId, TimeSpan.FromMinutes(10)); - там же
NodeIdValidator.IsValid(nodeId)— проверка имени узла на C# уже есть, изобретать не нужно; - строка 22: политика ограничения частоты
"enrollment"уже определена и применяется к эндпоинтам регистрации.
В deploy/ochenstarik-server-monitor-manager.sh, функция create_node_code (строка 1224) — эталон формата. Строка собирается так:
SMMNODE2.<control_url>.<ca_pem>.<node_id>.<token>.<hub_endpoint>.<hub_public_key>.<node_address>.<mesh_network>
каждый сегмент — base64url без выравнивания. Источники сегментов:
| Сегмент | Откуда берётся |
|---|---|
control_url |
файл control-public-url в каталоге конфигурации |
ca_pem |
файл control-ca.crt |
node_id |
из запроса |
token |
CreateEnrollmentTokenAsync, срок 10 минут |
hub_endpoint, hub_public_key, mesh_network |
mesh.env и wg/hub.pub |
node_address |
reserve_node_address — резервирование адреса в 10.77.0.0/24 |
Таким образом дописать нужно: чтение файлов, резервирование адреса и сборку строки. Токен, валидация имени, авторизация по роли и ограничение частоты уже готовы.
Требования
- Формат совпадает побайтно. Код, выданный эндпоинтом, обязан приниматься установленным на сервере bootstrap без единого изменения в bash. Опубликованные релизы неизменяемы, менять формат нельзя.
- Адреса не выдаются дважды. Два запроса на разные
node_idполучают разные адреса. Как ведёт себя повторный запрос на существующийnode_id— посмотрите вreserve_node_addressи повторите это поведение; в отчёте напишите, какое оно. - Срок жизни кода — 10 минут. Не увеличивать.
- Выдача попадает в журнал событий: кто выдал, какому
node_id, когда. node_idне может привести к выполнению постороннего кода на Hub. Если решите вызывать bash-функцию через sudoers — аргументы фиксированные, подстановки произвольных строк нет. Если перенесёте резервирование адреса в C# — bash-версия и версия Control обязаны давать одинаковый результат на одинаковом входе.- Роль
Operatorобязательна. НиAgent, ниAutomationдоступа к выдаче не получают.
Тесты
В tests/ServerMonitorManager.Control.Tests:
- запрос без сертификата Operator отклоняется;
- сертификат роли
Agentи ролиAutomationотклоняются; - корректный запрос возвращает строку, начинающуюся с
SMMNODE2., с девятью сегментами; - сегменты декодируются из base64url и содержат ожидаемые значения:
node_idсовпадает с запрошенным,ca_pem— это сертификат,node_addressлежит в10.77.0.0/24; - два запроса на разные имена дают разные адреса;
- недопустимое имя узла (пустое, с заглавными, длиннее 63 символов, с подчёркиванием) отклоняется до выпуска токена.
Тест на совпадение с bash-версией: сравнить структуру выданного кода с эталоном из create_node_code. Если полноценно вызвать bash в тестах невозможно — зафиксируйте эталонную строку как фикстуру и сверяйтесь с ней, а в отчёте напишите, что сравнение идёт с фикстурой, а не с живым скриптом.
Границы
Codex ведёт deploy/**, релизный пайплайн и проверку релиза; сейчас у него открыт PR #52. Не трогать: deploy/**, .github/workflows/linux-release.yml, .github/workflows/release-verification.yml, tests/release-verification/**, tests/bootstrap/**.
Ваша область: src/ServerMonitorManager.Control/**, src/ServerMonitorManager.Core/** при необходимости, tests/ServerMonitorManager.Control.Tests/**, документация по новому эндпоинту.
Критерий приёмки
- PR открыт, CI зелёный, PR не смержен до зелёного;
- эндпоинт доступен только роли
Operator; - выданный код имеет формат
SMMNODE2с девятью сегментами и корректным содержимым; - два узла не получают один адрес;
- срок жизни 10 минут, выдача записана в журнал;
- имя узла проверяется до выпуска токена и не может привести к выполнению постороннего кода;
- перечисленные тесты присутствуют и проходят.
Отчёт — в описании PR
Раздельно: что проверено локально, что в CI со ссылками на прогоны, что не проверялось и почему.
Доработка PR #53 — убрать дублирующий маршрут
Этап 1 выполнен, PR #53 открыт, CI зелёный, границы соблюдены. Одна правка до слияния.
Зарегистрированы два маршрута с одинаковым поведением:
control.MapPost("/agents/{nodeId}/enrollment-code", ...)—Program.cs:606control.MapPost("/nodes/{nodeId}/enrollment-code", ...)—Program.cs:632
Обработчик скопирован целиком, второй раз. Просили один маршрут.
Удалить /nodes/{nodeId}/enrollment-code, оставить /agents/{nodeId}/enrollment-code. Основания:
- все десять обращений в
NodeEnrollmentCodeTests.csидут на/agents/...; маршрут/nodes/...не покрыт ни одной проверкой — это непроверяемая публичная поверхность; - в группе
/api/v1/controlдвадцать два маршрута, и/nodes/среди них единственный. Соседи по смыслу —POST /agents/{nodeId}/reenrollиPUT /agents/{nodeId}/desired/preflight— уже используют/agents/{nodeId}/.
Если считаете, что /nodes/ семантически вернее, потому что узла как агента ещё не существует в момент выпуска кода, — доводы принимаются, но тогда переносите на него тесты и убирайте /agents/.... Два маршрута не остаются в любом случае.
Заодно, раз обработчик станет один: проверьте, что общий код не продублирован и в других местах PR.
Этап 2 — веб-консоль · активен с 2026-08-18
Этап 1 слит: main @ 7583b13, PR #53. Этап 2 начинается.
Ветка: antigravity/web-console. База — актуальный main; он движется быстро, Codex ведёт параллельно PR #57 и #58, поэтому зафиксируйте хеш базы в отчёте и обновляйте ветку перед слиянием.
Что уже доступно
POST /api/v1/control/agents/{nodeId}/enrollment-code, роль Operator, возвращает NodeEnrollmentCodeResponse: сам код SMMNODE2..., отпечаток CA и время истечения. Это ваш собственный эндпоинт из этапа 1 — читайте его контракт по коду, а не по этому заданию.
Важно про живой Hub. Эндпоинт в main, но на настоящем сервере он сейчас вернёт отказ доступа: Control работает под ochenstarik-smm-control, а каталог состояния mesh создаётся 0700 от root. Это чинит Codex в PR #57. На разработку консоли не влияет — тесты и локальный запуск подставляют свой путь, — но если будете проверять на Hub владельца и получите отказ, причина эта, а не ваш код.
Что сделать
Минимальная, но настоящая консоль оператора:
- список узлов — данные отдаёт
GET /api/v1/control/agents. Показать имя, состояние, время последнего heartbeat; - список Links —
GET /api/v1/control/links; - кнопка «Добавить узел» — спрашивает имя, вызывает
POST /api/v1/control/agents/{nodeId}/enrollment-codeиз этапа 1, показывает крупно кодSMMNODE2...и отпечаток CA, с копированием в один клик; - рядом с кодом — срок его жизни и требование сверить отпечаток с тем, что покажет установщик на сервере. Оператор, который щёлкнет «да» не глядя, обесценивает всю проверку подписи, поэтому текст должен быть заметным, а не сноской.
Размещение
Отдельный проект, обслуживаемый Control, чтобы разворачиваемая единица оставалась одна. Если выберете иначе — обоснуйте в отчёте.
Учтите: до этапа 3 вход возможен только по клиентскому сертификату роли Operator. Значит консоль на этом этапе открывается браузером, которому сертификат подсунут вручную, и это нормально — так и задумано. Не пытайтесь обойти это ради удобства демонстрации; удобство — предмет этапа 3, и там оно ограничено условиями.
Чего не делать
- не добавлять эндпоинты, которых не требует интерфейс;
- не выносить в консоль операции, меняющие состояние парка, кроме выдачи кода: удаление узлов, правка Links, перезапуск служб — не сейчас;
- не трогать
deploy/**и релизные workflow.
Доработка PR #59 — покрыть тестом ветку встроенных ресурсов
Проверено 2026-08-18. Выполнено всё, и выполнено аккуратно: ассеты отдаются явными маршрутами в группе с RequireAuthorization("Operator"), а не через UseStaticFiles, поэтому закрыт и интерфейс, а не только API. Пять тестов на роли, настоящие эндпоинты, encodeURIComponent на имени узла, обратный отсчёт срока жизни, предупреждение о сверке отпечатка написано заметно и по делу.
Отдельно стоит отметить <EmbeddedResource Include="wwwroot\**" />: Control публикуется с PublishSingleFile=true, каталога wwwroot рядом с бинарём на сервере не будет, и вы это учли сами, без указания в задании. Именно на таких местах проект терял по релизу за раз.
Одна вещь осталась непроверенной, и это как раз та ветка, которая будет исполняться в бою.
GetWebConsoleAsset пробует два источника: файл по env.WebRootPath, затем ресурс сборки. В тестах хост поднимается в процессе, WebRootPath существует, поэтому срабатывает файловая ветка. На сервере из единого файла сработает встроенная, и её сейчас не проверяет ничто.
Добавить тест: поднять хост с несуществующим или пустым WebRootPath и убедиться, что /, /index.html, /style.css и /app.js отдаются с кодом 200, верным Content-Type и непустым содержимым — то есть достаются из ресурсов сборки. Заодно это зафиксирует соглашение об именах ресурсов: поиск идёт через EndsWith(fileName), и переименование каталога его молча сломает.
После этого PR вливается.
Критерий приёмки
- консоль показывает узлы и Links из настоящих эндпоинтов Control, а не из заглушек;
- ассеты отдаются из ресурсов сборки, когда каталога
wwwrootна диске нет, — покрыто тестом; - кнопка выдаёт код, код копируется, отпечаток и срок жизни видны;
- сборка консоли входит в CI и не ломает существующие прогоны;
- есть проверка, что консоль недоступна без сертификата Operator;
- PR, CI зелёный, PR не смержен до зелёного;
- отчёт — в описании PR, отдельным файлом отчёт не является.
Этап 3. Вход по логину и паролю — только для тестов. Владелец просит его, чтобы не возиться с сертификатами при тестировании. Это второй, более слабый путь рядом с mTLS, и он не должен незаметно стать основным. Условия: выключен по умолчанию, включается явным параметром вида Authentication:PasswordLogin:EnabledForTesting; при включении предупреждение в логе при каждом старте; пароль хранится подбор-стойким хешем (Argon2id или bcrypt); ограничение частоты и задержка после неудач; ограниченная по времени сессия; роль — Operator и не выше; существующая проверка mTLS не ослабляется ни в одной ветке кода, при обоих включённых механизмах сертификат имеет приоритет.