ADR 0150 — Установщик одной строкой, с выбранным источником и маркером
Статус
Proposed. Зависит от ADR 0149: без подписи этот документ предлагает ровно то, что отвергла ФАЗА 28.
Контекст
Установка Brabo сегодня требует checkout, пяти секретов, экспортированных вручную, и знаний, которых нет нигде, — три жеста, которые описывает документ фазы. Три факта на диске ограничивают любой дизайн установщика:
Compose продакшена — не манифест установки. Все сервисы
docker/docker-compose.prod.yml указывают на brabo-*:prod с локальным
блоком build: (:102-105, :144-147, :253-256, :381-384); в
docker/ нет ни одной ссылки на ghcr.io. Заголовок файла говорит о себе, в
:1 и :8, что это "compose de VALIDAÇÃO das imagens de produção" и что
"o que este arquivo NÃO é: um manifesto de produção endurecido".
Единственный потребитель .release/images.json в репозитории —
scripts/ci/aplicar-imagens.ts, который пишет в overlay kustomize
(Makefile:35-36).
Образец согласия уже существует, и он жёсткий в нужном месте.
scripts/dev/consentir-base.mjs:195-208: без TTY скрипт сообщает и
возвращает 0 — не соглашается, не записывает, не создаёт папку. И его
порядок — закон: validarBase выполняется до любого mkdir
(:228-242), запись в .env — последний шаг (:272), а проваленная
проверка отказывает, не записывая (:246-258).
Пять секретов уже где-то генерируются. docker/smoke.sh:31-57 генерирует
все пять через openssl rand, уважая уже экспортированное значение
(${VAR:-$(gerar)}), включая деталь, что NEO4J_PASSWORD должен быть -hex,
потому что / ломает разбор NEO4J_AUTH (:53-56). Эту часть не нужно
изобретать, лишь сделать её постоянной.
Решение
1. Вызов сохраняет stdin
sh -c "$(curl -fsSL https://github.com/daneiel/brabo/releases/latest/download/install.sh)"
А не curl … | sh. Причина механическая, а не эстетическая: когда скрипт
приходит через pipe, stdin процесса является загрузкой, и read, чтобы
что-то спросить, читает байты самого скрипта или встречает EOF. Установщик,
который не может спросить, должен был бы сам выбирать, где создавать папки на
чужом компьютере, — а мерка репозитория обратная
(consentir-base.mjs:195-208).
Без TTY установщик сообщает и выходит, в том же формате, что образец: печатает, что сделал бы, что нашёл, и команду для запуска в терминале.
2. Скрипт проверяет собственное происхождение до действия
install.sh версионируется в репозитории и публикуется как asset
Release. Он несёт собственную версию и первым делом скачивает подписанный
checksums.txt этой Release, проверяет подпись (ADR 0149) и сверяет свой
хеш с ней. Расходится — останавливается.
Это расширяет — а не цитирует как уже сказанное — пункт 5
ADR 0146:195-212, который версионирует
consentir-base.mjs с доводом, что «скрипт, который настраивает хост
пользователя, создаёт папку и пишет в .env, — это именно тот артефакт, чьё
происхождение должно быть аудируемым», и чьё следствие — api не получает
маршрута, выдающего скрипт хоста. install.sh — артефакт того же вида, с
большим охватом; следствие действует так же.
3. Обнаружение называет найденное и только потом спрашивает
Маркер живёт в $XDG_STATE_HOME/brabo/install-state.json, с запасным
~/.local/state/brabo/ — та же идиома, которую servico.ts:250-261 уже
использует для XDG_CONFIG_HOME. Не в ~/.brabo/, где runner хранит вещи
по проекту: состояние установки и конфигурация проекта — разные жизни, и
их объединение заставило бы удаление одного стирать другое.
Маркер версионирован (schemaVersion) и хранит: установленную версию,
источник (ghcr или local), применённые digests, пути (.env, база
проектов, каталог compose) и дату. Именно он делает второй запуск diff, а
не догадкой.
Без маркера — каждая установка до этой версии — скрипт ищет признаки:
проект compose с сервисами brabo-*, units brabo-runner-*.service, .env с
пятью ключами. И называет найденное до любого вопроса. Пути «что-то нашёл,
решаю сам» не существует.
4. Удаление говорит, что стирает и чего никогда не стирает
| стирает (с подтверждением, перечислено заранее) | никогда не стирает |
|---|---|
именованные тома compose, включая pgdata | базу проектов пользователя |
сгенерированный .env | папку зеркала (RN-516: зеркало никогда не стирает, и установщик тоже) |
| units сервиса runner | любую папку вне путей маркера |
| ключ устройства на диске | то, что ключ защищает на сервере, — отзыв это другое действие |
Вторая колонка — суть. Папка пользователя — накопление, а не состояние продукта; стирать её значило бы, что установщик принимает решение, которое ему никто не делегировал.
5. Два источника образов, и «официальный» — результат проверки
--source=ghcr(по умолчанию): читает.release/images.jsonиз Release, разрешает каждый образ по digest и проверяет подпись (ADR 0149).--source=local:docker buildx bakeсdocker-bake.hcl:81-83, что производит ровно четыре имени, которых ожидает compose. Требует чистого рабочего дерева на теге; иначе отказывает, потому что образ, собранный из грязного дерева, не является той версией, которой себя называет.
Слово «официальный» появляется в выводе только тогда, когда проверка прошла. Смена источника потом — это смена образа поверх тех же томов — данные не перемещаются.
6. Собственный compose установки
Рождается docker/docker-compose.install.yml, который потребляет digests
из .release/images.json и не имеет блока build:.
docker-compose.prod.yml остаётся нетронутым: это compose проверки, его
использует docker/smoke.sh (:19-20, намеренно собирая локально), и
возведение его в манифест установки сломало бы уже имеющееся использование и
противоречило бы его собственному заголовку.
Цена объявлена: два файла с одной топологией сервисов. Разделяет их то, на что каждый отвечает, — «собирается ли этот тег?» против «запускает ли эта машина опубликованное?».
7. Broker в этой фазе не поднимается, и последствие названо
У docker-bake.hcl:81-83 четыре цели, а scripts/ci/images-manifest.ts:54
принимает четыре; brabo-broker:prod (docker-compose.prod.yml:333)
существует только собранным локально, и поэтому profile container-broker
недостижим из артефактов Release.
Установщик не предлагает этот profile и говорит, что это значит: с
--source=ghcr проект в режиме mounted — который
ADR 0146 сделал локальным значением по
умолчанию — не поднимает контейнер, потому что поднимает его broker
(ADR 0144). Разговор и backlog работают;
исполнение dev agent в этом режиме — нет.
Публикация broker пятым образом — исправление, и оно маленькое (цель в
bake, id в images-manifest.ts, ещё одна подпись). Оно вне этого ADR по
рамкам, а не из сомнения: входит отдельным пунктом, и пока не вошло,
--source=local — путь, дающий полный mounted.
8. Windows — названный отказ
Как уже в RN-518 (servico.ts:517-532): установщик называет платформу,
говорит, что она вне рамок по объявленному решению, и указывает путь
переднего плана. Это не общий отказ и не молчание.
9. Чистый bash, с тестируемым решением
install.sh — это bash, потому что он должен работать до того, как на
машине появится какая-либо зависимость. Логика решения (обнаружить, сравнить
маркер, выбрать источник, собрать план удаления) живёт в чистых функциях, а
скрипт выставляет --print-plan / --print-state, чтобы spec на vitest
проверял их, ничего не исполняя, — прецедент реален и из того же
репозитория: у scripts/dev/bootstrap.sh есть --print-commands, и его
тестирует scripts/dev/bootstrap.spec.ts.
10. Проверка после подъёма собственная, и smoke.sh не переиспользуется
docker/smoke.sh генерирует пять секретов (:31-57) и является прецедентом
решения 5 — но вызывать его не годится: он поднимает с --build (собирает
то, что эта установка только что скачала по digest) и сносит стек в конце, если
только не SMOKE_KEEP_UP=1. Это тест CI, а установка — не тест.
Установщик вместо этого делает честный минимум: после up --wait обращается к
/health api и engine и только тогда говорит, что установил. --wait уже
ждёт healthcheck — но кто объявляет состояние, должен его спросить, а это ровно
урок, которого стоил reset-total.sh (он объявлял «reset completo» при api в
Exited (1)).
По тому же рассуждению нет шага migrate: compose сцепляет api →
migrate-api с service_completed_successfully, и второе место, приказывающее
мигрировать, было бы вторым источником одной и той же истины.
Что этот ADR явно отвергает
curl | sh. По решению 1: закрывает stdin, а с ним и согласие.- Возведение
docker-compose.prod.ymlв манифест установки. Ломаетsmoke.sh, который намеренно собирает локально, и противоречит заголовку файла. - Маршрут api, генерирующий скрипт. Явное следствие пункта 5 ADR 0146, и здесь оно действует с ещё большей силой.
- Угаданная миграция. Предыдущая установка без маркера — не «вероятно, такая же»: она называется, и путь — путь ADR 0152.
- Стирание чего-либо вне путей маркера.
Последствия
- Установка перестаёт требовать checkout, а пять секретов начинают
генерироваться один раз и сохраняться с режимом
600— сегодняsmoke.shгенерирует их и выбрасывает вместе со стеком. - Два compose с одной топологией, и расхождение между ними становится тем, за чем надо следить. Потолок: compose установки не получает сервиса, которого нет у compose проверки.
- С
--source=ghcrmountedостаётся без контейнера, пока broker не опубликован (решение 7). Объявлено в выводе установщика, а не обнаружено потом. - Второй запуск установщика становится читаемым diff. Первый запуск на машине, где уже был Brabo, остаётся трудным случаем, и он единственный зависит от другого ADR.