# Codex: cosign в поставке и работающий автозапуск проверки релиза → `v0.1.0-alpha.15`

Задание самодостаточно: истории работы по этому репозиторию у вас нет, всё нужное ниже.

## Репозиторий

- https://github.com/ochenstarik-ui/server-monitor-manager
- База: `main` @ `0405b0b` (merge PR #42)
- Ветка: `codex/cosign-provisioning`

Server Monitor Manager — Windows-клиент, ASP.NET Core Control Hub, Linux-агент и WireGuard-mesh с политиками nftables. На серверы ставится bash-скриптом `deploy/ochenstarik-server-monitor-manager.sh` (bootstrap); `deploy/smm-setup.sh` — обёртка, скачивающая релизные ассеты и дающая команды `install-hub` и `install-node`.

## Правила репозитория

- `main` защищён: PR обязателен, обязательны статусы `build-and-test` и `build`, force-push запрещён;
- один PR — одна тема;
- описание PR заполняется **после** завершения CI, со ссылками на конкретные прогоны;
- отсутствие проверки не является дефектом отчёта; утверждение о проверке, противоречащее статусу CI, — является;
- `docs/release-policy.md` запрещает двигать опубликованные теги и заменять ассеты: исправление выпускается следующей версией;
- `.github/workflows/linux-release.yml` — единственный публикатор релизов.

---

## Что произошло

`v0.1.0-alpha.14` выпущен и содержит полный набор из 17 ассетов, включая `server-monitor-manager-manifest.pem`. Это первый релиз, подпись которого проверяема.

`Release Verification` против него **упал**, и это ровно тот результат, ради которого workflow писался. Он нашёл два независимых дефекта.

### Дефект 1 — cosign не поставляется, установка на чистый сервер невозможна

Прогон [31894428693](https://github.com/ochenstarik-ui/server-monitor-manager/actions/runs/31894428693), шаг `Run Positive Installation`:

```
ochenstarik-server-monitor-manager.sh: OK
[ochenstarik-server-monitor-manager] Supported platform: ubuntu 24.04, x86_64
[ochenstarik-server-monitor-manager] ERROR: Required command is missing: cosign
```

`verify_manifest` в bootstrap (`deploy/ochenstarik-server-monitor-manager.sh:298`) вызывает `require_command cosign`. При этом:

- `cosign` не входит в стандартные репозитории Ubuntu;
- `install_dependencies` ставит только `wireguard-tools nftables iproute2`;
- `smm-setup.sh` не упоминает cosign вообще;
- `docs/linux-bootstrap.md` объясняет, **зачем** нужен сертификат, но нигде не говорит, **откуда взять** cosign.

Следствие: `install-hub` на чистой Ubuntu скачивает все файлы, доходит до проверки подписи и умирает. Владелец проекта не может поставить систему на свои серверы — при том, что установка «одной командой» и была смыслом PR #42.

Дополнительно, в CI отказ произошёл по смежной причине, которую тоже надо устранить: шаг `Setup cosign` (`sigstore/cosign-installer`) кладёт бинарь в каталог, который добавляется в `PATH` через `GITHUB_PATH`, а тест вызывает bootstrap через `sudo`, и `secure_path` в `/etc/sudoers` эту запись отбрасывает. То есть даже установленный на runner'е cosign невидим для проверяемого кода.

### Дефект 2 — `Release Verification` никогда не запускается сам

`.github/workflows/release-verification.yml` объявлен с `on: release: types: [published]`. Релиз создаёт `linux-release.yml` под `GITHUB_TOKEN`, а события, порождённые этим токеном, по правилам GitHub не запускают другие workflow — защита от рекурсии. Прогон по `alpha.14` пришлось запускать вручную через `workflow_dispatch`.

Механизм выглядит настроенным и не может сработать ни разу. Пункт «`Release Verification` отработал автоматически» в приёмке предыдущего задания был недостижим в принципе — это дефект задания, не исполнителя.

---

## Часть 1 — обеспечить cosign

Требование: **оператор на чистой Ubuntu выполняет одну команду и получает установленную систему с проверенной подписью.** Ни ручной установки cosign, ни `SMM_ALLOW_UNSIGNED`.

Реализовать провижининг cosign в поставке:

- закреплённая версия и закреплённая контрольная сумма **на каждую архитектуру**, обе константами в скрипте, а не вычисляемые на лету;
- скачивание бинаря публичным `curl` с `https://github.com/sigstore/cosign/releases/download/<version>/cosign-linux-<amd64|arm64>`;
- сверка sha256 с закреплённой константой **до** запуска бинаря; несовпадение — отказ;
- установка в `/usr/local/bin/cosign` режимом `0755` (каталог входит в `secure_path`, поэтому cosign станет виден и под `sudo`);
- если пригодный `cosign` уже в `PATH` — не трогать существующий, но проверить, что он запускается;
- версия и её контрольные суммы записываются в `docs/linux-bootstrap.md`, чтобы обновление было осознанным действием, а не молчаливым дрейфом.

Где размещать вызов — ваше решение, но оно должно закрывать оба пути: короткий (`install-hub` / `install-node` в `smm-setup.sh`) и сквозной (`preflight` / `verify-release` в bootstrap). Второй используется в `tests/acceptance/three-server-mesh.sh`.

Отдельно предусмотреть отказ с внятным текстом, если скачать cosign невозможно (нет сети, недоступен github). Сообщение должно называть версию, ожидаемую сумму и путь установки — оператору должно хватить его, чтобы поставить cosign вручную.

## Часть 2 — сделать проверку честной

Удалить шаг `Setup cosign` из `.github/workflows/release-verification.yml`.

Пока cosign даёт runner, тест проверяет окружение GitHub, а не поставку. После удаления шага `Run Positive Installation` проходит только если cosign обеспечивает сам установщик — то есть ровно в том случае, когда пройдёт и установка у оператора.

Убедиться, что негативные проверки подписи (`run-negative-tests.sh`) остаются работоспособны и продолжают отвергать подделанные manifest, подпись и сертификат.

## Часть 3 — починить автозапуск

Добиться того, чтобы публикация тега давала результат проверки **без действий человека**. Два пригодных пути:

- перевести `release-verification.yml` на `on: workflow_run` по завершении `Release pipeline` с фильтром по успешному завершению и по тегу — `workflow_run` срабатывает на завершение другого workflow и ограничением `GITHUB_TOKEN` не блокируется;
- либо вынести проверку в отдельный job внутри `linux-release.yml`, зависящий от публикации.

Первый путь сохраняет разделение и оставляет `workflow_dispatch` для ручного перепрогона; второй проще, но смешивает выпуск и проверку в одном workflow. Выберите один и обоснуйте выбор в отчёте.

`workflow_dispatch` с параметром `tag` сохранить в любом случае — им перепроверяют старые релизы.

## Часть 4 — выпустить `v0.1.0-alpha.15`

1. **До тегирования** прогнать `Release pipeline` через `workflow_dispatch` на своей ветке. Шаги публикации защищены `if: startsWith(github.ref, 'refs/tags/')` и пропустятся, но сборка и подпись выполнятся. Пропуск этого шага уже сжёг два номера (`alpha.10`, `alpha.11`).
2. Создать и запушить тег обычным `git push` — не через `gh release create`: релиз создаёт сам workflow по событию `push: tags: v*`.
3. Убедиться, что `Release Verification` запустился **сам** и зелёный. Это главный результат задания.
4. Тег не двигать ни при каких обстоятельствах.

## Часть 5 — эргономика `install-hub` и `install-node`

Найдено живой установкой на Ubuntu 24.04, обе вещи стоили оператору прерванных попыток.

**Аргументы проверяются после скачивания.** Вызов `install-hub ВАШ-ХОСТ 178.212.13.102` — плейсхолдер вместо адреса, IP на месте порта — прошёл проверку арности, после чего скрипт скачал 24 МБ и только потом дошёл бы до разбора значений. Валидация `PUBLIC_HOST`, `HTTPS_PORT` и `WG_PORT` должна выполняться **до первого сетевого запроса**. Отдельно отвергать очевидный плейсхолдер: значение, не являющееся ни IPv4-литералом, ни синтаксически годным DNS-именем.

**Скачивание идёт молча.** `curl -fsSL` на 24 МБ не печатает ничего, и оператор дважды принимал это за зависание и жал Ctrl+C. Показывать индикатор и имя файла: `curl -fL --progress-bar -o <имя>`. То же относится к любому крупному файлу, который скрипт тянет сам.

**Имя `install-node` перекрыто.** У обёртки есть своя команда `install-node` без аргументов, а у bootstrap — одноимённая, принимающая путь к архиву. Оператор, скачавший файлы заранее, получает `install-node takes no arguments` и не имеет способа догадаться, что нужен разделитель `--`. Либо принимать необязательный путь к архиву и, если он задан, пропускать скачивание, либо в тексте отказа прямо называть обходной путь: `smm-setup.sh -- install-node ARCHIVE`. Первое лучше: скачивание заново — это ещё 24 МБ поверх уже имеющихся.

**Проверка подписи виснет молча и без таймаута.** `verify_manifest` вызывает `cosign verify-blob ... >/dev/null 2>&1`. При медленной или частично недоступной сети cosign уходит к службам sigstore и не возвращается, а оператор видит только строку `Verifying manifest signature...` и не имеет ни малейшего указания, что происходит. Наблюдалось на живой машине. Требуется: не глушить вывод cosign (или сохранять его в лог и печатать при отказе), обернуть вызов в `timeout` с внятным сообщением по истечении, и в тексте отказа прямо называть службы, к которым нужен доступ. Отдельно задокументировать в `docs/linux-bootstrap.md`, что проверка подписи требует исходящего доступа к sigstore — сейчас об этом не сказано нигде, и оператор в закрытом контуре узнает это зависанием.

**`mesh-status` печатает сырой Unix-time.** Колонка `HANDSHAKE` показывает `1786843345`. Оператор смотрит в неё ровно затем, чтобы понять, свежий ли handshake, и обязан считать это в уме. Выводить относительное время (`12s ago`, `3m ago`) либо `never`, если рукопожатия не было.

Покрыть контрактным тестом: неверный порт и плейсхолдер в `PUBLIC_HOST` отвергаются без обращения в сеть; `install-node` с путём к архиву не приводит к сообщению об аргументах.

## Часть 6 — записать судьбу alpha.14

В `docs/release-policy.md`:

- `v0.1.0-alpha.14` — первый релиз с полным набором подписи, но установка из него на чистый хост невозможна: cosign не поставляется. Проверяем, не устанавливаем;
- `v0.1.0-alpha.15` — первый релиз, устанавливаемый на чистый хост.

---

## Границы

Antigravity ведёт проверку подписи на стороне Desktop. **Не трогать:** `src/ServerMonitorManager.Desktop/**`, `tests/ServerMonitorManager.Desktop.Security.Tests/**`, `.github/workflows/windows-build.yml`.

Ваша область: `deploy/**`, `.github/workflows/linux-release.yml`, `.github/workflows/release-verification.yml`, `tests/release-verification/**`, `tests/bootstrap/**`, `docs/linux-bootstrap.md`, `docs/release-policy.md`.

## Критерий приёмки

- cosign обеспечивается поставкой; закреплены версия и контрольные суммы обеих архитектур;
- подделанная контрольная сумма cosign даёт отказ — покрыто тестом;
- шаг `Setup cosign` из `release-verification.yml` удалён;
- `Release Verification` по `v0.1.0-alpha.15` запустился **по событию, без `workflow_dispatch`**, и зелёный; ссылка на прогон в отчёте;
- `workflow_dispatch` с параметром `tag` продолжает работать; ссылка на прогон по произвольному тегу;
- `install-hub` и `install-node` доводят установку до конца на хосте без cosign;
- неверные аргументы отвергаются до первого сетевого запроса; крупные загрузки показывают индикатор;
- `SMM_ALLOW_UNSIGNED` не используется ни в одном новом пути и остаётся тем, чем был — аварийным флагом;
- `workflow_dispatch`-прогон `Release pipeline` выполнен до тегирования, ссылка в отчёте;
- судьба `alpha.14` записана в `docs/release-policy.md`;
- всё через PR, CI зелёный, PR не смержен до зелёного.

## Отчёт

Раздельно: что проверено локально, что в CI со ссылками на прогоны, что не проверялось и почему.

Физическая приёмка на реальных серверах в задание не входит — её выполняет владелец после выпуска релиза.
