# Codex: единый установщик с выбором роли

## Порядок

Это задание выполняется **после** `smm-codex-task-alpha15-2026-08-15.md`. Пока не поставляется cosign, установка на чистый сервер невозможна, и переделывать интерфейс установщика поверх неработающей установки бессмысленно.

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

- https://github.com/ochenstarik-ui/server-monitor-manager
- База: `main` после слияния alpha.15
- Ветка: `codex/unified-installer`

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

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

---

## Зачем

Живая установка на четырёх машинах показала, что оператор ошибается не в сложных местах, а в простых: путает роль, путает машину, оставляет плейсхолдер в аргументе, прерывает молчаливую загрузку. Владелец просит один файл, который спрашивает роль и делает остальное сам.

Второе изменение: код регистрации узла больше не выпускается на Hub через ssh. Его выдаёт приложение оператора — Windows-клиент или веб-консоль. Соответствующий эндпоинт делает Antigravity заданием `smm-antigravity-task-web-console-2026-08-17.md`. Установщику от этого нужно одно: принимать готовый код `SMMNODE2...`, как он делает и сейчас.

## Что сделать

Единый интерактивный режим `smm-setup.sh` без аргументов:

1. показать, что за машина: дистрибутив, версия, архитектура, публичный адрес, наличие уже установленных компонентов;
2. спросить роль — Hub или узел;
3. для Hub: спросить публичный адрес, предложив определённый автоматически как значение по умолчанию; порты предложить `7443` и `51820`;
4. для узла: попросить код `SMMNODE2...`, показать разобранный из кода адрес Hub и отпечаток CA, потребовать подтверждения;
5. проверить и доустановить зависимости, включая cosign;
6. скачать и проверить релизные файлы с видимым индикатором;
7. выполнить установку;
8. по завершении Hub напечатать **то, что нужно вставить в приложение оператора**: код регистрации устройства и отпечаток CA. Иначе получается замкнутый круг — приложение не может выдать код узла, пока само не подключено к Hub;
9. по завершении узла напечатать `SMMPEER1...` и прямо сказать, куда его вставить.

Требования к поведению:

- существующие неинтерактивные команды сохраняются полностью: они используются в `tests/acceptance/three-server-mesh.sh` и в проверке релиза. Интерактивный режим — это режим по умолчанию при запуске без аргументов, а не замена;
- при отсутствии терминала (запуск из скрипта, `stdin` не tty) интерактивный режим не включается, а выдаётся подсказка с неинтерактивными командами;
- ни один ввод не приводит к выполнению постороннего кода: код регистрации, адрес и порты проверяются до использования;
- повторный запуск на уже установленной машине распознаёт это и предлагает осмысленный выбор, а не падает и не ставит поверх.

## Состояние на 2026-08-18

**Установочная часть выполнена и слита** — PR #58, `main` @ `f7b10ac`: интерактивный режим без аргументов, выбор роли Hub/Node, показ состояния машины, доустановка зависимостей, разбор кода `SMMNODE2`, отказ без tty, тесты в `tests/bootstrap/test-unified-installer.sh`, обновлённая документация.

**Удаление не сделано.** В меню по-прежнему два пункта: `Choose role: 1) Hub 2) Node`, и `die "choose Hub (1) or Node (2)"` на всё остальное. Требование ниже появилось в задании 2026-08-18 и, судя по всему, до исполнителя не дошло. Оно остаётся в силе и выполняется **отдельным PR** поверх слитого.

Релиз с единым установщиком **не выпускать до готовности удаления**: смысл в том, чтобы владелец получил одну программу, умеющую и поставить, и снять. Выпуск половины означает второй релиз следом, а проект уже сжигал теги на такой спешке.

---

## Дополнение от владельца 2026-08-18 — полное удаление в том же файле

PR #58 закрывает установку, но не удаление. Владелец требует, чтобы единый файл умел и снимать систему целиком: третий пункт меню рядом с Hub и Node.

Сегодня `uninstall-agent` и `uninstall-control` снимают не всё. После них остаются системные пользователи и группы, конфигурация WireGuard и юнит `wg-quick@smm0`, таблица nftables `inet ochenstarik_smm`, каталоги состояния, запись в `sudoers.d`, файл в `sysctl.d`. Владелец проходил это вручную; точный список — в `smm-cleanup-and-install-2026-08-15.md`, раздел 1, проверен на живых серверах.

### Что сделать

Третий выбор роли: **удалить всё**. Поведение:

1. **Сначала показать, что будет удалено**, и только потом спрашивать подтверждение. Список строится по фактическому состоянию машины, а не печатается шаблоном: какие юниты найдены, какие пользователи существуют, есть ли таблица nftables, есть ли `smm0`, какие каталоги непусты.
2. **Подтверждение — набранным словом, не `y/N`.** Прецедент есть: `uninstall-control` требует `--confirm-destroy-control`. Случайное нажатие не должно сносить Hub вместе с его CA.
3. **Порядок: сначала остановить службы, потом удалять юниты.** Это не педантизм. Владелец однажды удалил юниты при работающем процессе, тот осиротел, продолжил держать порт 7443, и `userdel` отказал, потому что у пользователя оставался живой процесс. Правильно так: `systemctl disable --now`, затем `pkill -u` по каждому пользователю, пауза, `pkill -9 -u`, и только потом удаление файлов и учётных записей.
4. **Идемпотентность.** На машине, где ничего не стояло, режим отрабатывает молча и с нулевым кодом возврата.
5. **Ничего чужого.** На серверах владельца работает 3x-ui. Удаляются только объекты с собственными именами: юниты `ochenstarik-smm-*`, пользователи `ochenstarik-smm-control`, `ochenstarik-smm-agent`, `ochenstarik-monitor`, таблица `inet ochenstarik_smm`, интерфейс `smm0`, каталоги под своими путями. Никаких `rm -rf` по маскам, способным задеть соседа.
6. **Сохранение данных — отдельный вопрос.** Два варианта: снять программу, оставив резервные копии и базу Control, либо снести всё вместе с ними. Второй — с отдельным подтверждением: вместе с базой уходит CA, а с ним все выданные сертификаты и вся регистрация узлов.
7. **В конце — проверка**: напечатать те же четыре строки, которыми владелец проверяет чистоту вручную: свободны ли порты `7443` и `51820`, нет ли маршрутов `10.77.0.`, остались ли юниты, остались ли пользователи.

Сквозной неинтерактивный вариант тоже нужен — для скриптов и повторной приёмки: команда с обязательным флагом подтверждения, без вопросов.

### Тесты удаления

- на машине без установленной системы режим отрабатывает без ошибок и ничего не меняет;
- после полной установки и последующего удаления не остаётся ни юнитов, ни пользователей, ни таблицы nftables, ни интерфейса, ни каталогов состояния;
- посторонний юнит и посторонний пользователь, созданные до удаления, остаются нетронутыми;
- без флага подтверждения неинтерактивный вариант отказывается работать;
- удаление при работающих службах не оставляет процессов, удерживающих порты.

## Что проверить тестами

- интерактивный сценарий Hub и узла целиком, на подставном вводе;
- отказ интерактивного режима без tty;
- отклонение испорченного кода `SMMNODE2` — обрезанного, с лишним сегментом, с подменённым отпечатком;
- сохранность неинтерактивных команд: существующие тесты `tests/bootstrap/**` и проверка релиза продолжают проходить без изменений в вызовах.

## Границы

Antigravity ведёт Control и веб-консоль. **Не трогать:** `src/**`, `.github/workflows/windows-build.yml`.

Ваша область: `deploy/**`, `tests/bootstrap/**`, `docs/linux-bootstrap.md`.

Формат `SMMNODE2` и `SMMPEER1` не менять: его разбирает установленный на серверах bootstrap, а опубликованные релизы неизменяемы.

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

- `smm-setup.sh` без аргументов проводит установку Hub и узла целиком, включая cosign и зависимости;
- Hub по завершении печатает то, что вставляется в приложение оператора;
- неинтерактивные команды не изменены, приёмочный скрипт и проверка релиза проходят;
- без tty интерактивный режим не запускается;
- испорченный код отвергается;
- всё через PR, CI зелёный, PR не смержен до зелёного.

## Отчёт

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