docs: clean-install-only, versions.env и очистка предыдущего поколения
Новый docs/14-legacy-cleanup.md: как выглядит отказ установщика, полный список маркеров чужой установки, что сохранить перед очисткой, работа purge-v0.sh, ручная процедура и отдельно - случай незавершённой установки текущего поколения, где нужен repair, а не очистка. Обновлено под фактическое поведение: - README и package/docs: установка описана как две фазы, PHASE 0 ничего не меняет; добавлен troubleshooting по отказу clean-host; версии toolchain больше не передаются через окружение; - 02-build-layer: раздел про versions.env (что в нём есть и чего нет и почему), verify_versions_contract, проверка происхождения артефакта по upstream hashes.txt; - 08-orchestrator-spec: двухфазный контракт, read-only guard, идентификация поколения в install-state, ownership-aware rollback, расширенная семантическая проверка конфига, структурная редакция; - 04-admin-panel: таблица удалённых маршрутов и почему они удалены, а не оставлены заглушками; сужена формулировка гарантии санитайза; - 11-testing: новые unit-наборы, полный список инвариантов конфига, раздел про одну реализацию URI вместо двух, сценарий проверки границы установки на живом сервере; - 12-operations и 13-runbook: диагностика отказов по поколению, поведение diagnostics-бандла; - tools/build/README: контракт версий, обе суммы Bun, hashes.txt. CHANGELOG: раздел Unreleased с разбором каждого исправленного дефекта.
This commit is contained in:
+128
-2
@@ -8,6 +8,127 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
Hardening-проход перед релизом `1.0.0`. Основная тема — сделать политику
|
||||||
|
«только чистая установка» настоящим системным инвариантом, а не строчкой в
|
||||||
|
документации.
|
||||||
|
|
||||||
|
### Исправлено
|
||||||
|
|
||||||
|
- **Установщик мог повредить работающий сервер до того, как откажется его
|
||||||
|
трогать.** `install.sh` переписывал `/usr/local/lib/hy2xs`, раскладывал
|
||||||
|
runtime-пакет и перезаписывал `/var/lib/hy2xs/install-state.json`, и лишь
|
||||||
|
потом запускал clean-host preflight. При ошибочном запуске поверх старого
|
||||||
|
сервера rollback дополнительно выполнял `stop` и `disable` для работающих
|
||||||
|
`hysteria-server` и `hy2xs-admin`.
|
||||||
|
|
||||||
|
Установка разделена на две фазы с жёсткой границей: **PHASE 0 — read only**,
|
||||||
|
**PHASE 1 — mutation**. Read-only проверка выполняется новой командой
|
||||||
|
`hy2xs-orchestrator preflight-install` из распакованного архива, а граница
|
||||||
|
держится runtime-guard'ом, а не соглашением.
|
||||||
|
|
||||||
|
- **Отсутствие `HY2XS_CONFIG_SCHEMA_VERSION` считалось текущей схемой.** До v1
|
||||||
|
этого поля не существовало, поэтому именно пустое значение — самый вероятный
|
||||||
|
признак конфигурации `0.x`. Теперь оно отклоняется как legacy с указанием на
|
||||||
|
чистую установку. Тест, закреплявший прежнее поведение, инвертирован.
|
||||||
|
|
||||||
|
- **`reconfigure` и `repair` работали поверх любого маркера установки.**
|
||||||
|
Проверялся только флаг `installed`, который мог остаться и от `0.x`.
|
||||||
|
Маркер получил идентификацию поколения (`product`, `release_line`,
|
||||||
|
`config_schema_version`), и обе команды проверяют её до всего остального.
|
||||||
|
|
||||||
|
- **Классификация отказа шла по тексту сообщения об ошибке.** Ошибка
|
||||||
|
preflight со словом `nftables` классифицировалась как отказ firewall и
|
||||||
|
приводила к откату чужого ruleset. Теперь классификация опирается на то, что
|
||||||
|
операция реально успела применить. `systemctl stop/disable` выполняется
|
||||||
|
только для юнитов, развёрнутых текущей операцией, а `fatal_pre_apply` по
|
||||||
|
определению не выполняет системный откат и не собирает diagnostics-бандл.
|
||||||
|
|
||||||
|
- **Diagnostics-бандл уносил machine token наружу.** Построчное правило
|
||||||
|
редакции `auth:` подставляло маркер в заголовок mapping'а и оставляло
|
||||||
|
нетронутым вложенный `auth.http.url` с `access_token=<секрет>` — тем самым,
|
||||||
|
что открывает и trafficStats API, и auth-endpoint. Редакция YAML переписана
|
||||||
|
структурно; в env-файлах секреты теперь вырезаются и из URL-значений
|
||||||
|
(`HY2_AUTH_URL` не подходил ни под один маркер имени).
|
||||||
|
|
||||||
|
- **`quic.maxIdleTimeout` не проверялся** семантической проверкой конфига, хотя
|
||||||
|
присутствовал в production-профиле. Заодно `auth.http.url` теперь сверяется
|
||||||
|
целиком (host/port/path/token), а не по наличию подстроки `access_token=`;
|
||||||
|
добавлены проверки `auth.http.insecure`, полей ACME и отсутствия посторонних
|
||||||
|
секций верхнего уровня.
|
||||||
|
|
||||||
|
- **Версия админки разъехалась с версией пакета**: пакет `1.0.0` сообщал
|
||||||
|
`HY2XS admin version v0.0.22`. Константа заменена переменной, которую
|
||||||
|
проставляет сборка через ldflags из `versions.env`.
|
||||||
|
|
||||||
|
- **Кнопки в панели, которые всегда возвращали ошибку.** «Перезапустить панель»
|
||||||
|
и загрузка сертификатов обращались к заглушкам. Маршруты и UI удалены.
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
|
||||||
|
- **`versions.env`** — единственный источник истины для контракта
|
||||||
|
«продукт / платформа / toolchain»: версия продукта, линия релиза, схема
|
||||||
|
конфигурации, целевая платформа, версии и контрольные суммы Go/Bun/Node/pnpm,
|
||||||
|
политика выбора Hysteria. Прикладные зависимости и конкретная версия
|
||||||
|
Hysteria сюда намеренно не переносятся: у них есть собственные lock-механизмы.
|
||||||
|
|
||||||
|
- **Шаг сборки `verify_versions_contract`.** Роняет сборку до создания tarball,
|
||||||
|
если разошлись `PACKAGE_VERSION`, `packageManager` в двух `package.json`,
|
||||||
|
схема в `package/config/hy2xs.env`, константы, скомпилированные в
|
||||||
|
оркестратор, директива `go` в `apps/go.mod`, metadata пакета или версия,
|
||||||
|
которую сообщает собранный `hy2xs-admin`.
|
||||||
|
|
||||||
|
- **Контрольные суммы toolchain в контракте**, включая **обе** сборки Bun
|
||||||
|
(`bun-linux-x64` и `bun-linux-x64-baseline`): артефакт выбирается по наличию
|
||||||
|
AVX2, поэтому одной суммы архитектурно недостаточно. Передавать суммы через
|
||||||
|
окружение больше не нужно — production-сборка запускается одной командой.
|
||||||
|
|
||||||
|
- **Проверка происхождения артефакта Hysteria.** Ожидаемый SHA-256 берётся из
|
||||||
|
upstream-ассета `hashes.txt` и сверяется со скачанным бинарником до записи в
|
||||||
|
HY2XS lock. Раньше сумма считалась локально от уже скачанного файла, то есть
|
||||||
|
была trust-on-first-use.
|
||||||
|
|
||||||
|
- **Полный clean-host контракт.** Список маркеров чужой установки расширен с
|
||||||
|
двух до четырнадцати: состояние, runtime-пакет, конфиги, бинарник Hysteria,
|
||||||
|
фрагмент nftables, systemd-юниты, база админки и наследие `0.x`. Пути
|
||||||
|
установки и данных берутся из конфигурации, а не захардкожены.
|
||||||
|
|
||||||
|
- **`tools/legacy/purge-v0.sh`** и [docs/14-legacy-cleanup.md](docs/14-legacy-cleanup.md) —
|
||||||
|
явная очистка сервера от предыдущего поколения. По умолчанию скрипт
|
||||||
|
показывает план и ничего не делает; выполнение требует
|
||||||
|
`--apply --yes-i-know`. Из установщика он не вызывается никогда: это вернуло
|
||||||
|
бы destructive migration logic в путь свежей установки.
|
||||||
|
|
||||||
|
- **Явный флаг `--allow-partial-state` для `repair`.** Прежде согласие на
|
||||||
|
работу поверх незавершённой установки подразумевалось молча.
|
||||||
|
|
||||||
|
### Изменено
|
||||||
|
|
||||||
|
- **E2E подключается по ссылке из production-кода.** Внутри
|
||||||
|
`tools/test/e2e-hysteria.sh` жила вторая реализация `hysteria2://` URI на
|
||||||
|
bash: дрейф любой из двух реализаций оставлял обе группы тестов зелёными.
|
||||||
|
Теперь ссылку выдаёт `service.BuildHysteria2ShareURI` через
|
||||||
|
`apps/tools/share-uri`. Единственное расхождение — `insecure=1` для
|
||||||
|
самоподписанного сертификата, и оно ограничено тестами с двух сторон.
|
||||||
|
|
||||||
|
- **Формулировка гарантии санитайза экспорта.** Вместо «любой будущий секрет
|
||||||
|
будет удалён» — «известные секреты и неизвестные поля с секретоподобным
|
||||||
|
именем». Список маркеров расширен (`apiKey`, `privateKey`, `authorization`,
|
||||||
|
`cookie`, `bearer`, `passphrase`, `signature`, …) и синхронизирован между
|
||||||
|
Go-админкой и оркестратором.
|
||||||
|
|
||||||
|
### Удалено
|
||||||
|
|
||||||
|
- Маршруты, операциями которых продукт не владеет:
|
||||||
|
`POST /hysteria2ChangeVersion`, `GET /listRelease`,
|
||||||
|
`POST /config/updateHysteria2Config`, `POST /config/importHysteria2Config`,
|
||||||
|
`POST /config/restartServer`, `POST /config/uploadCertFile`,
|
||||||
|
`GET /config/hysteria2AcmePath`. Вместе с ними — соответствующие сервисы,
|
||||||
|
клиентские функции фронтенда, кнопки и строки i18n.
|
||||||
|
|
||||||
|
Маршруты удалены, а не оставлены отвечающими «feature disabled»: API-контракт
|
||||||
|
не должен обещать updater, которого у продукта нет, а неиспользуемый маршрут
|
||||||
|
остаётся attack surface.
|
||||||
|
|
||||||
## [1.0.0] — 2026-08-27
|
## [1.0.0] — 2026-08-27
|
||||||
|
|
||||||
Первый релиз линейки `v1`.
|
Первый релиз линейки `v1`.
|
||||||
@@ -119,8 +240,13 @@
|
|||||||
Порядок перехода:
|
Порядок перехода:
|
||||||
|
|
||||||
1. Выпишите с работающего сервера список пиров и их секреты.
|
1. Выпишите с работающего сервера список пиров и их секреты.
|
||||||
2. Разверните `1.0.0` на чистом Debian 13 из release-пакета.
|
2. Очистите сервер: `tools/legacy/purge-v0.sh` или ручная процедура из
|
||||||
3. Заведите пиров заново и раздайте новые клиентские ссылки.
|
[docs/14-legacy-cleanup.md](docs/14-legacy-cleanup.md).
|
||||||
|
3. Разверните `1.0.0` на чистом Debian 13 из release-пакета.
|
||||||
|
4. Заведите пиров заново и раздайте новые клиентские ссылки.
|
||||||
|
|
||||||
|
Установщик `1.0.0` обнаружит остатки предыдущей установки на шаге PHASE 0,
|
||||||
|
откажется работать и **не изменит на сервере ничего**.
|
||||||
|
|
||||||
Клиентские ссылки `0.x` в любом случае перестанут работать: смена
|
Клиентские ссылки `0.x` в любом случае перестанут работать: смена
|
||||||
обфускации — это изменение wire-совместимости.
|
обфускации — это изменение wire-совместимости.
|
||||||
|
|||||||
@@ -138,7 +138,12 @@ hy2xs-install/
|
|||||||
└── metadata/
|
└── metadata/
|
||||||
```
|
```
|
||||||
|
|
||||||
При запуске `install.sh` пакет проверяет `metadata/checksums.txt`, устанавливает orchestrator в `/usr/local/lib/hy2xs/hy2xs-orchestrator`, создаёт symlink `/usr/local/bin/hy2xs-orchestrator`, копирует package assets в `/usr/local/lib/hy2xs/package` и передаёт управление install‑only orchestrator.
|
При запуске `install.sh` пакет сначала проверяет `metadata/checksums.txt` и
|
||||||
|
выполняет read‑only clean‑host preflight **из распакованного архива**. Только
|
||||||
|
после этого он устанавливает orchestrator в
|
||||||
|
`/usr/local/lib/hy2xs/hy2xs-orchestrator`, создаёт symlink
|
||||||
|
`/usr/local/bin/hy2xs-orchestrator`, копирует package assets в
|
||||||
|
`/usr/local/lib/hy2xs/package` и передаёт управление install‑only orchestrator.
|
||||||
|
|
||||||
## Сетевая модель по умолчанию
|
## Сетевая модель по умолчанию
|
||||||
|
|
||||||
@@ -169,13 +174,18 @@ HY2XS **не привязан к конкретному номеру верси
|
|||||||
build machine target server
|
build machine target server
|
||||||
───────────── ─────────────
|
───────────── ─────────────
|
||||||
определить последнюю стабильную ─┐
|
определить последнюю стабильную ─┐
|
||||||
скачать артефакт, посчитать SHA-256 │
|
взять ожидаемый SHA-256 из │
|
||||||
проверить, что бинарник принимает ├─► release‑пакет ──► скачать ровно
|
upstream hashes.txt │
|
||||||
канонический конфиг HY2XS │ version + url этот артефакт,
|
скачать артефакт и сверить его ├─► release‑пакет ──► скачать ровно
|
||||||
заморозить version/url/sha256 ─┘ + sha256 сверить SHA-256
|
проверить, что бинарник принимает │ version + url этот артефакт,
|
||||||
и `hysteria version`
|
канонический конфиг HY2XS │ + sha256 сверить SHA-256
|
||||||
|
заморозить version/url/sha256 ─┘ и `hysteria version`
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Контрольная сумма берётся из upstream‑ассета `hashes.txt`, а не считается
|
||||||
|
только локально: локальный пересчёт подтверждает, что файл не изменился после
|
||||||
|
скачивания, но не доказывает, что скачан именно ожидаемый upstream artifact.
|
||||||
|
|
||||||
Что это даёт:
|
Что это даёт:
|
||||||
|
|
||||||
- новая установка получает актуальную Hysteria без ручного обновления version lock;
|
- новая установка получает актуальную Hysteria без ручного обновления version lock;
|
||||||
@@ -459,19 +469,33 @@ HY2XS_UI_PUBLIC_ACCESS=false
|
|||||||
./install.sh --config /root/hy2xs-target.env --non-interactive
|
./install.sh --config /root/hy2xs-target.env --non-interactive
|
||||||
```
|
```
|
||||||
|
|
||||||
Во время установки HY2XS:
|
Установка идёт в две фазы с жёсткой границей между ними.
|
||||||
|
|
||||||
1. проверит checksums release‑пакета;
|
**PHASE 0 — только чтение.** До её успешного завершения на сервере не
|
||||||
2. установит orchestrator в `/usr/local/lib/hy2xs`;
|
изменяется ни один файл, включая `/usr/local/lib/hy2xs`:
|
||||||
3. создаст runtime‑каталоги и service users;
|
|
||||||
4. запишет `/etc/hy2xs/hy2xs.env`;
|
1. проверит, что запущено от root;
|
||||||
5. разложит bundled HY2XS admin;
|
2. проверит checksums release‑пакета;
|
||||||
6. скачает закреплённый в пакете Hysteria2 binary из upstream, проверит SHA256 и фактическую версию;
|
3. запустит clean‑host preflight **из распакованного архива**: платформа
|
||||||
7. создаст `/etc/hysteria/config.yaml`;
|
Debian 13 amd64, отсутствие предыдущей установки, валидность конфигурации.
|
||||||
8. установит systemd‑юниты;
|
|
||||||
9. применит nftables‑правила;
|
**PHASE 1 — применение изменений:**
|
||||||
10. выполнит smoke‑checks;
|
|
||||||
11. зафиксирует успешное состояние в `/var/lib/hy2xs/install-state.json`.
|
4. установит orchestrator в `/usr/local/lib/hy2xs` и разложит runtime‑пакет;
|
||||||
|
5. создаст runtime‑каталоги и service users;
|
||||||
|
6. запишет `/etc/hy2xs/hy2xs.env`;
|
||||||
|
7. разложит bundled HY2XS admin;
|
||||||
|
8. скачает закреплённый в пакете Hysteria2 binary из upstream, проверит SHA256 и фактическую версию;
|
||||||
|
9. создаст `/etc/hysteria/config.yaml`;
|
||||||
|
10. установит systemd‑юниты;
|
||||||
|
11. применит nftables‑правила;
|
||||||
|
12. выполнит smoke‑checks;
|
||||||
|
13. зафиксирует успешное состояние в `/var/lib/hy2xs/install-state.json`.
|
||||||
|
|
||||||
|
Если PHASE 0 не прошла, установщик завершается с ошибкой и **сервер остаётся в
|
||||||
|
том же состоянии, в котором был**. HY2XS v1 не устанавливается поверх
|
||||||
|
предыдущего поколения и не мигрирует его состояние: очистка старой установки —
|
||||||
|
отдельная явная операция, см. [docs/14-legacy-cleanup.md](docs/14-legacy-cleanup.md).
|
||||||
|
|
||||||
### 10. Получите bootstrap‑пароль админки
|
### 10. Получите bootstrap‑пароль админки
|
||||||
|
|
||||||
@@ -654,14 +678,21 @@ hy2xs-orchestrator reconfigure \
|
|||||||
|
|
||||||
| Команда | Назначение |
|
| Команда | Назначение |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `hy2xs-orchestrator preflight-install` | Read‑only проверка чистоты хоста; ничего не меняет |
|
||||||
| `hy2xs-orchestrator status` | Показать состояние платформы, сервисов, firewall и install marker |
|
| `hy2xs-orchestrator status` | Показать состояние платформы, сервисов, firewall и install marker |
|
||||||
| `hy2xs-orchestrator doctor` | Выполнить preflight и smoke‑checks текущей установки |
|
| `hy2xs-orchestrator doctor` | Выполнить preflight и smoke‑checks текущей установки |
|
||||||
| `hy2xs-orchestrator reconfigure --dry-run` | Проверить конфиг без применения |
|
| `hy2xs-orchestrator reconfigure --dry-run` | Проверить конфиг без применения |
|
||||||
| `hy2xs-orchestrator reconfigure --apply` | Применить runtime‑конфигурацию |
|
| `hy2xs-orchestrator reconfigure --apply` | Применить runtime‑конфигурацию |
|
||||||
| `hy2xs-orchestrator repair` | Попытаться восстановить partial install state |
|
| `hy2xs-orchestrator repair --allow-partial-state` | Довести до конца незавершённую установку **текущего поколения** |
|
||||||
| `hy2xs-orchestrator diagnostics collect` | Собрать diagnostic bundle в `/var/log/hy2xs/diagnostics` |
|
| `hy2xs-orchestrator diagnostics collect` | Собрать diagnostic bundle в `/var/log/hy2xs/diagnostics` |
|
||||||
| `hy2xs-orchestrator redact-config` | Отредактировать секреты в env/yaml перед публикацией логов |
|
| `hy2xs-orchestrator redact-config` | Отредактировать секреты в env/yaml перед публикацией логов |
|
||||||
|
|
||||||
|
`repair` без `--allow-partial-state` работает только поверх полностью успешной
|
||||||
|
установки. В обоих режимах он сначала проверяет, что
|
||||||
|
`/var/lib/hy2xs/install-state.json` принадлежит текущему поколению продукта
|
||||||
|
(`product`, `release_line`, `config_schema_version`), и отказывается работать
|
||||||
|
поверх чужого состояния.
|
||||||
|
|
||||||
Пример сбора диагностики:
|
Пример сбора диагностики:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -708,6 +739,35 @@ permitopen 127.0.0.1:8080 localhost:8080
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Установка отказывается: обнаружена предыдущая установка
|
||||||
|
|
||||||
|
```text
|
||||||
|
[hy2xs] ERROR: На сервере обнаружена предыдущая или посторонняя установка.
|
||||||
|
HY2XS v1 не поддерживает установку поверх и не мигрирует состояние 0.x.
|
||||||
|
Ни один файл на сервере не изменён.
|
||||||
|
```
|
||||||
|
|
||||||
|
Это ожидаемое поведение, а не сбой. Отказ происходит в PHASE 0, до любой
|
||||||
|
мутации: сервер остался в том состоянии, в котором был.
|
||||||
|
|
||||||
|
Что делать:
|
||||||
|
|
||||||
|
1. сохраните нужные данные (база пиров, конфиг) — см.
|
||||||
|
[docs/14-legacy-cleanup.md](docs/14-legacy-cleanup.md);
|
||||||
|
2. посмотрите план очистки: `sudo ./purge-v0.sh`;
|
||||||
|
3. выполните очистку: `sudo ./purge-v0.sh --apply --yes-i-know`;
|
||||||
|
4. повторите установку.
|
||||||
|
|
||||||
|
Отдельный случай — отказ вида
|
||||||
|
`HY2XS_CONFIG_SCHEMA_VERSION отсутствует в конфигурации`. Он означает, что
|
||||||
|
переданный `--config` относится к предыдущему поколению: до v1 этого поля не
|
||||||
|
существовало. Создайте конфиг заново по разделу «Создайте конфиг для своего
|
||||||
|
сервера».
|
||||||
|
|
||||||
|
Если установка HY2XS v1 упала **после** начала применения изменений, полная
|
||||||
|
очистка не нужна — используйте
|
||||||
|
`hy2xs-orchestrator repair --allow-partial-state`.
|
||||||
|
|
||||||
### Установка падает на DNS AAAA
|
### Установка падает на DNS AAAA
|
||||||
|
|
||||||
Причина: домен имеет IPv6 AAAA‑запись, а HY2XS production profile является IPv4‑only.
|
Причина: домен имеет IPv6 AAAA‑запись, а HY2XS production profile является IPv4‑only.
|
||||||
@@ -787,23 +847,27 @@ git status --short
|
|||||||
|
|
||||||
Подготовьте build env:
|
Подготовьте build env:
|
||||||
|
|
||||||
|
Версии и контрольные суммы toolchain **не задаются переменными окружения**: они
|
||||||
|
объявлены в корневом [`versions.env`](versions.env), и сборка берёт их оттуда.
|
||||||
|
Раньше их приходилось передавать снаружи, из-за чего воспроизводимая сборка в
|
||||||
|
чистой Debian‑среде требовала предварительного знания четырёх SHA‑256.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export PACKAGE_VERSION=1.0.0
|
|
||||||
export BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)
|
export BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
|
||||||
# Для переносимости между x86_64-серверами без AVX2 предпочтителен baseline artifact.
|
# Для переносимости между x86_64-серверами без AVX2 предпочтителен baseline artifact.
|
||||||
|
# Ожидаемый digest выбирается автоматически: в versions.env зафиксированы обе суммы.
|
||||||
export BUN_FLAVOR=x64-baseline
|
export BUN_FLAVOR=x64-baseline
|
||||||
|
|
||||||
# Builder требует SHA256 для скачиваемых toolchain-архивов.
|
|
||||||
# Значения нужно брать из официальных release/checksum источников для конкретных версий.
|
|
||||||
export GO_ARCHIVE_SHA256=<sha256-go1.21.13-linux-amd64.tar.gz>
|
|
||||||
export NODE_ARCHIVE_SHA256=<sha256-node-v20.19.0-linux-x64.tar.xz>
|
|
||||||
export BUN_ARCHIVE_SHA256=<sha256-bun-linux-x64-baseline-1.3.13.zip>
|
|
||||||
|
|
||||||
# Опционально: снимает anonymous rate limit при разрешении upstream-релиза.
|
# Опционально: снимает anonymous rate limit при разрешении upstream-релиза.
|
||||||
export GITHUB_TOKEN=<token>
|
export GITHUB_TOKEN=<token>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`PACKAGE_VERSION` тоже приходит из `versions.env` (`HY2XS_VERSION`). Шаг
|
||||||
|
`verify_versions_contract` роняет сборку, если версия продукта, схема
|
||||||
|
конфигурации, целевая платформа или `packageManager` в `package.json`
|
||||||
|
разошлись с контрактом.
|
||||||
|
|
||||||
Запустите сборку:
|
Запустите сборку:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -812,25 +876,28 @@ export GITHUB_TOKEN=<token>
|
|||||||
|
|
||||||
Сборка последовательно:
|
Сборка последовательно:
|
||||||
|
|
||||||
1. прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`);
|
1. проверяет контракт `versions.env` (`verify_versions_contract`);
|
||||||
2. определяет последнюю стабильную версию Hysteria, скачивает артефакт и считает SHA‑256;
|
2. прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`);
|
||||||
3. проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS для Gecko и для Salamander;
|
3. определяет последнюю стабильную версию Hysteria, берёт ожидаемый SHA‑256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт;
|
||||||
4. собирает orchestrator, frontend и backend;
|
4. проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS для Gecko и для Salamander;
|
||||||
5. прогоняет `go vet` и `go test` для HY2XS admin;
|
5. собирает orchestrator, frontend и backend, проставляя версию админки из контракта;
|
||||||
6. формирует архив и прогоняет acceptance‑проверки.
|
6. прогоняет `go vet` и `go test` для HY2XS admin;
|
||||||
|
7. формирует архив и прогоняет acceptance‑проверки.
|
||||||
|
|
||||||
Любой сбой на шагах 1–5 останавливает сборку до создания пакета.
|
Любой сбой на шагах 1–6 останавливает сборку до создания пакета.
|
||||||
|
|
||||||
Переменные, управляющие выбором версии Hysteria:
|
Переменные, управляющие выбором версии Hysteria:
|
||||||
|
|
||||||
| Переменная | По умолчанию | Назначение |
|
| Переменная | По умолчанию | Назначение |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `HYSTERIA_CHANNEL` | `stable` | `stable` — разрешить последнюю стабильную; `pinned` — офлайн‑сборка по `tools/build/hysteria-lock.env` |
|
| `HYSTERIA_CHANNEL` | из `versions.env` (`stable`) | `stable` — разрешить последнюю стабильную; `pinned` — офлайн‑сборка по `tools/build/hysteria-lock.env` |
|
||||||
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
||||||
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release‑сборок обязателен |
|
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release‑сборок обязателен |
|
||||||
|
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break‑glass |
|
||||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в lock‑файл |
|
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в lock‑файл |
|
||||||
|
|
||||||
Полный E2E с реальным клиентом Hysteria запускается отдельно:
|
Полный E2E с реальным клиентом Hysteria запускается отдельно (нужен Go: ссылка
|
||||||
|
берётся из production‑генератора, а не из отдельной реализации внутри теста):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
||||||
@@ -863,7 +930,9 @@ tar -tzf dist/hy2xs-install-1.0.0.tar.gz | grep -E \
|
|||||||
├── package/ # skeleton будущего install package
|
├── package/ # skeleton будущего install package
|
||||||
├── tools/build/ # production builder и packaging pipeline
|
├── tools/build/ # production builder и packaging pipeline
|
||||||
├── tools/test/ # end-to-end проверки с реальным клиентом Hysteria
|
├── tools/test/ # end-to-end проверки с реальным клиентом Hysteria
|
||||||
|
├── tools/legacy/ # purge-v0.sh: очистка сервера от предыдущего поколения
|
||||||
├── docs/ # спецификации baseline, тестов и эксплуатации
|
├── docs/ # спецификации baseline, тестов и эксплуатации
|
||||||
|
├── versions.env # контракт продукта, платформы и toolchain
|
||||||
├── CHANGELOG.md
|
├── CHANGELOG.md
|
||||||
├── README.md
|
├── README.md
|
||||||
└── LICENSE
|
└── LICENSE
|
||||||
|
|||||||
@@ -40,20 +40,103 @@ Builder не является частью target install flow: на target serv
|
|||||||
- шаблоны для `post-install.env`
|
- шаблоны для `post-install.env`
|
||||||
- package metadata
|
- package metadata
|
||||||
|
|
||||||
|
## Контракт версий: `versions.env`
|
||||||
|
|
||||||
|
Корневой `versions.env` — **единственный источник истины** для контракта
|
||||||
|
«продукт / платформа / toolchain».
|
||||||
|
|
||||||
|
Что в нём есть:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HY2XS_VERSION=1.0.0
|
||||||
|
HY2XS_RELEASE_LINE=1
|
||||||
|
HY2XS_CONFIG_SCHEMA_VERSION=2
|
||||||
|
|
||||||
|
HY2XS_BUILD_OS=debian
|
||||||
|
HY2XS_BUILD_OS_VERSION=13
|
||||||
|
HY2XS_BUILD_ARCH=amd64
|
||||||
|
HY2XS_TARGET_OS=debian
|
||||||
|
HY2XS_TARGET_OS_VERSION=13
|
||||||
|
HY2XS_TARGET_ARCH=amd64
|
||||||
|
|
||||||
|
GO_VERSION=1.21.13
|
||||||
|
GO_LINUX_AMD64_SHA256=<sha256>
|
||||||
|
BUN_VERSION=1.3.13
|
||||||
|
BUN_LINUX_X64_SHA256=<sha256>
|
||||||
|
BUN_LINUX_X64_BASELINE_SHA256=<sha256>
|
||||||
|
NODE_VERSION=20.19.0
|
||||||
|
NODE_LINUX_X64_SHA256=<sha256>
|
||||||
|
PNPM_VERSION=9.15.9
|
||||||
|
|
||||||
|
HYSTERIA_CHANNEL=stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Чего в нём **нет** и быть не должно:
|
||||||
|
|
||||||
|
1. **Прикладных зависимостей** (Vue, Gin, GORM, npm/Go модули). У них уже есть
|
||||||
|
канонические lock-механизмы: `apps/frontend/pnpm-lock.yaml`,
|
||||||
|
`orchestrator/bun.lock`, `apps/go.sum`. Второй слой неизбежно разъедется с
|
||||||
|
настоящим графом зависимостей.
|
||||||
|
2. **Конкретной версии Hysteria.** Здесь живёт только *политика* выбора
|
||||||
|
(`HYSTERIA_CHANNEL`); результат резолва замораживается в
|
||||||
|
`tools/build/hysteria-lock.env`. Пин версии здесь вернул бы ручное
|
||||||
|
обновление, от которого мы ушли.
|
||||||
|
|
||||||
|
Два Bun-артефакта зафиксированы отдельно намеренно: `select_bun_artifact()`
|
||||||
|
выбирает `bun-linux-x64` или `bun-linux-x64-baseline` по наличию AVX2, поэтому
|
||||||
|
одной контрольной суммы архитектурно недостаточно.
|
||||||
|
|
||||||
|
### Проверка, а не генерация
|
||||||
|
|
||||||
|
`profile.ts`, `package/config/hy2xs.env` и `packageManager` в двух `package.json`
|
||||||
|
остаются обычными файлами. Сборка их **не генерирует**, а сверяет шагом
|
||||||
|
`verify_versions_contract`.
|
||||||
|
|
||||||
|
Причина: генерируемые исходники ломают чистый чекаут — `bun test`, `tsc` и
|
||||||
|
`go test` должны работать до запуска сборки. Проверка даёт тот же инвариант
|
||||||
|
дешевле.
|
||||||
|
|
||||||
|
`verify_versions_contract` сверяет:
|
||||||
|
|
||||||
|
| Что | С чем |
|
||||||
|
| --- | --- |
|
||||||
|
| `PACKAGE_VERSION` | `HY2XS_VERSION` |
|
||||||
|
| `orchestrator/package.json` → `packageManager` | `bun@$BUN_VERSION` |
|
||||||
|
| `apps/frontend/package.json` → `packageManager` | `pnpm@$PNPM_VERSION` |
|
||||||
|
| `package/config/hy2xs.env` → схема | `HY2XS_CONFIG_SCHEMA_VERSION` |
|
||||||
|
| константы, **скомпилированные** в оркестратор | схема, release line, целевая платформа |
|
||||||
|
| `apps/go.mod` → директива `go` | `GO_VERSION` |
|
||||||
|
| `metadata/package.env` | версия, release line, схема, target |
|
||||||
|
| `hy2xs-admin version` (готовый бинарь) | `v$HY2XS_VERSION` |
|
||||||
|
|
||||||
|
Контракт оркестратора сверяется не grep'ом по исходникам, а выводом
|
||||||
|
`orchestrator/tools/print-contract.ts`: это доказывает, что в бинарь попало то
|
||||||
|
же значение.
|
||||||
|
|
||||||
|
Версия админки приходит в бинарь через ldflags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
-ldflags "-s -w -X 'hy2xs-admin/model/constant.Version=v${HY2XS_VERSION}'"
|
||||||
|
```
|
||||||
|
|
||||||
|
Собственной константы версии в Go-коде больше нет: она уже успела разъехаться с
|
||||||
|
версией пакета.
|
||||||
|
|
||||||
## Что делает builder
|
## Что делает builder
|
||||||
|
|
||||||
1. Проверяет структуру проекта.
|
1. Проверяет структуру проекта.
|
||||||
2. Прогоняет тесты и типы оркестратора.
|
2. Загружает и проверяет контракт `versions.env`.
|
||||||
3. Разрешает upstream-версию Hysteria и проходит compatibility gate.
|
3. Прогоняет тесты и типы оркестратора.
|
||||||
4. Компилирует оркестратор из Bun/TypeScript в install-артефакт.
|
4. Разрешает upstream-версию Hysteria и проходит compatibility gate.
|
||||||
5. Собирает / подготавливает HY2XS admin.
|
5. Компилирует оркестратор из Bun/TypeScript в install-артефакт.
|
||||||
6. Прогоняет тесты HY2XS admin (после сборки frontend: `go:embed all:dist` требует готовых ассетов).
|
6. Собирает / подготавливает HY2XS admin и сверяет его версию с контрактом.
|
||||||
7. Копирует артефакты UI в package staging directory.
|
7. Прогоняет тесты HY2XS admin (после сборки frontend: `go:embed all:dist` требует готовых ассетов).
|
||||||
8. Кладёт entrypoint, templates, docs и service files.
|
8. Копирует артефакты UI в package staging directory.
|
||||||
9. Формирует итоговый install package.
|
9. Кладёт entrypoint, templates, docs и service files.
|
||||||
10. Считает manifest/checksum.
|
10. Формирует итоговый install package.
|
||||||
11. Проверяет архив и прогоняет acceptance-проверки.
|
11. Считает manifest/checksum.
|
||||||
12. Выдаёт один переносимый результат для target machine.
|
12. Проверяет архив и прогоняет acceptance-проверки.
|
||||||
|
13. Выдаёт один переносимый результат для target machine.
|
||||||
|
|
||||||
## Что builder не делает
|
## Что builder не делает
|
||||||
|
|
||||||
@@ -110,14 +193,19 @@ project/
|
|||||||
|
|
||||||
`tools/build/build.sh` должен быть самодостаточным для Debian 13 amd64:
|
`tools/build/build.sh` должен быть самодостаточным для Debian 13 amd64:
|
||||||
|
|
||||||
1. Проверяет ОС и архитектуру.
|
1. Проверяет ОС и архитектуру по `versions.env` (`HY2XS_BUILD_*`).
|
||||||
2. Проверяет структуру репозитория и lock-файлы.
|
2. Проверяет структуру репозитория и lock-файлы.
|
||||||
3. Доставляет отсутствующие системные build-зависимости через `apt-get`.
|
3. Доставляет отсутствующие системные build-зависимости через `apt-get`.
|
||||||
4. Проверяет версии Go, Bun, Node.js и pnpm.
|
4. Проверяет версии Go, Bun, Node.js и pnpm по `versions.env`.
|
||||||
5. При несовпадении версий скачивает управляемый локальный toolchain в `.toolchain/`.
|
5. При несовпадении версий скачивает управляемый локальный toolchain в `.toolchain/`
|
||||||
|
и **сверяет каждый архив с контрольной суммой из `versions.env`**.
|
||||||
6. Собирает только Linux amd64 артефакты.
|
6. Собирает только Linux amd64 артефакты.
|
||||||
7. Записывает версии toolchain в metadata пакета.
|
7. Записывает версии toolchain в metadata пакета.
|
||||||
|
|
||||||
|
Собственных значений по умолчанию у `tools/build/lib/deps.sh` больше нет: без
|
||||||
|
загруженного контракта сборка падает сразу, а не собирает пакет на неизвестном
|
||||||
|
toolchain.
|
||||||
|
|
||||||
## Отношение к Hysteria2
|
## Отношение к Hysteria2
|
||||||
|
|
||||||
Сам бинарь Hysteria2 **не вендорится** в install package как baseline-правило.
|
Сам бинарь Hysteria2 **не вендорится** в install package как baseline-правило.
|
||||||
@@ -135,10 +223,13 @@ SOURCE
|
|||||||
resolve latest stable (HyNetworks/hysteria, только теги app/vX.Y.Z)
|
resolve latest stable (HyNetworks/hysteria, только теги app/vX.Y.Z)
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
resolve exact release asset (hysteria-linux-amd64)
|
resolve exact release asset (hysteria-linux-amd64 + hashes.txt)
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
download + compute SHA-256
|
download hashes.txt → ожидаемый SHA-256 от upstream
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
download artifact + сверка с ожидаемым SHA-256
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
compatibility gate (реальный бинарник принимает канонический конфиг HY2XS)
|
compatibility gate (реальный бинарник принимает канонический конфиг HY2XS)
|
||||||
@@ -165,8 +256,43 @@ TARGET SERVER
|
|||||||
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
||||||
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
||||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `tools/build/hysteria-lock.env` |
|
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `tools/build/hysteria-lock.env` |
|
||||||
|
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break-glass |
|
||||||
| `GITHUB_TOKEN` | пусто | Опционально, чтобы не упереться в anonymous rate limit |
|
| `GITHUB_TOKEN` | пусто | Опционально, чтобы не упереться в anonymous rate limit |
|
||||||
|
|
||||||
|
### Проверка происхождения артефакта
|
||||||
|
|
||||||
|
Раньше SHA-256 считался локально от уже скачанного файла. Это защищает target от
|
||||||
|
последующей подмены, но не доказывает, что builder скачал именно ожидаемый
|
||||||
|
upstream artifact: сумма фиксирует то, что пришло, каким бы оно ни было
|
||||||
|
(trust-on-first-use).
|
||||||
|
|
||||||
|
Upstream публикует контрольные суммы релиза отдельным ассетом `hashes.txt`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6493dfff…f94 build/hysteria-linux-amd64
|
||||||
|
f24f63be…189 build/hysteria-linux-amd64-avx
|
||||||
|
```
|
||||||
|
|
||||||
|
Поэтому порядок теперь такой:
|
||||||
|
|
||||||
|
```text
|
||||||
|
download hysteria-linux-amd64
|
||||||
|
download hashes.txt
|
||||||
|
↓
|
||||||
|
ожидаемый SHA-256 из upstream
|
||||||
|
↓
|
||||||
|
сверка скачанного бинарника
|
||||||
|
↓
|
||||||
|
и только после этого — запись SHA-256 в HY2XS lock и metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
Сопоставление идёт по базовому имени и строго на равенство: `build/` — часть
|
||||||
|
пути, а `hysteria-linux-amd64-avx` — другой артефакт, который не должен совпасть
|
||||||
|
по префиксу. Разбор вынесен в `parseUpstreamHashes` и покрыт тестами.
|
||||||
|
|
||||||
|
Источник ожидаемой суммы фиксируется в `metadata/package.env`
|
||||||
|
(`hysteria_sha_source=upstream-hashes | hy2xs-lock | local-download`).
|
||||||
|
|
||||||
Дополнительно:
|
Дополнительно:
|
||||||
- версия, URL и SHA256 фиксируются в metadata install package (`metadata/hysteria.version`, `metadata/hysteria.url`, `metadata/hysteria.sha256`);
|
- версия, URL и SHA256 фиксируются в metadata install package (`metadata/hysteria.version`, `metadata/hysteria.url`, `metadata/hysteria.sha256`);
|
||||||
- способ выбора версии фиксируется в `metadata/hysteria.resolution` и `metadata/package.env`;
|
- способ выбора версии фиксируется в `metadata/hysteria.resolution` и `metadata/package.env`;
|
||||||
@@ -180,7 +306,7 @@ Gate защищает от ситуации, когда upstream меняет с
|
|||||||
|
|
||||||
Порядок:
|
Порядок:
|
||||||
|
|
||||||
1. скачать артефакт и сверить SHA-256;
|
1. скачать артефакт и сверить SHA-256 с upstream `hashes.txt`;
|
||||||
2. сверить `hysteria version` с разрешённой версией;
|
2. сверить `hysteria version` с разрешённой версией;
|
||||||
3. отрендерить канонический конфиг HY2XS тем же кодом, что работает на target (`orchestrator/tools/render-canonical-config.ts`);
|
3. отрендерить канонический конфиг HY2XS тем же кодом, что работает на target (`orchestrator/tools/render-canonical-config.ts`);
|
||||||
4. запустить реальный бинарник Hysteria с этим конфигом — для Gecko и для Salamander;
|
4. запустить реальный бинарник Hysteria с этим конфигом — для Gecko и для Salamander;
|
||||||
@@ -206,3 +332,5 @@ BUILD FAILED: unsupported Hysteria stable v2.13.0
|
|||||||
6. Hysteria2 подтягивается install layer'ом с upstream по замороженным координатам, а не собирается на target из исходников
|
6. Hysteria2 подтягивается install layer'ом с upstream по замороженным координатам, а не собирается на target из исходников
|
||||||
7. выход новой версии Hysteria после сборки не меняет содержимое уже собранного пакета
|
7. выход новой версии Hysteria после сборки не меняет содержимое уже собранного пакета
|
||||||
8. несовместимый upstream ломает сборку, а не установку у пользователя
|
8. несовместимый upstream ломает сборку, а не установку у пользователя
|
||||||
|
9. контрольная сумма Hysteria подтверждена upstream-ассетом `hashes.txt`, а не только локальным пересчётом
|
||||||
|
10. версии продукта, платформы и toolchain объявлены в одном месте, а рассинхрон роняет сборку до создания tarball
|
||||||
|
|||||||
+52
-4
@@ -79,11 +79,28 @@ HY2XS admin работает как надстройка над Hysteria YAML/AP
|
|||||||
- `access_token` в auth-URL и учётные данные, встроенные в URL;
|
- `access_token` в auth-URL и учётные данные, встроенные в URL;
|
||||||
- `auth.password`, `auth.userpass`;
|
- `auth.password`, `auth.userpass`;
|
||||||
- учётные данные ACME DNS-провайдера;
|
- учётные данные ACME DNS-провайдера;
|
||||||
- любые **неизвестные** поля, имя которых содержит `password`, `secret`, `token` или `credential`.
|
- **неизвестные** поля с секретоподобным именем: `password`, `passwd`,
|
||||||
|
`passphrase`, `secret`, `token`, `credential`, `apiKey` / `api_key`,
|
||||||
|
`privateKey` / `private_key`, `accessKey`, `secretKey`, `authorization`,
|
||||||
|
`cookie`, `bearer`, `signature`.
|
||||||
|
|
||||||
Последний пункт — обратная сторона сохранения неизвестных полей: новое upstream-поле с секретом вырезается ещё до того, как HY2XS про него узнает.
|
Последний пункт — обратная сторона сохранения неизвестных полей: новое
|
||||||
|
upstream-поле с секретом вырезается ещё до того, как HY2XS про него узнает.
|
||||||
|
|
||||||
Пути к файлам (`tls.cert`, `tls.key`, `ech.keyPath`, `tls.clientCA`) секретами не считаются и остаются читаемыми — они нужны для диагностики.
|
### Как формулируется гарантия
|
||||||
|
|
||||||
|
Точная формулировка:
|
||||||
|
|
||||||
|
> вырезаются известные секреты и неизвестные поля с секретоподобным именем.
|
||||||
|
|
||||||
|
Не «любой будущий секрет будет автоматически удалён». Обобщённый sanitizer
|
||||||
|
работает по именам полей и не может предугадать произвольное имя, которое
|
||||||
|
upstream выберет для нового секрета. Список маркеров синхронизирован с
|
||||||
|
`orchestrator/src/lib/redaction.ts`; при появлении нового поля его нужно
|
||||||
|
добавить в оба места.
|
||||||
|
|
||||||
|
Пути к файлам (`tls.cert`, `tls.key`, `ech.keyPath`, `tls.clientCA`) секретами
|
||||||
|
не считаются и остаются читаемыми — они нужны для диагностики.
|
||||||
|
|
||||||
## Модель современной схемы Hysteria
|
## Модель современной схемы Hysteria
|
||||||
|
|
||||||
@@ -118,10 +135,41 @@ HY2XS admin работает как надстройка над Hysteria YAML/AP
|
|||||||
- Hysteria2 запускается отдельным `hysteria-server.service`;
|
- Hysteria2 запускается отдельным `hysteria-server.service`;
|
||||||
- HY2XS admin работает как operator UI и HTTP auth/traffic layer;
|
- HY2XS admin работает как operator UI и HTTP auth/traffic layer;
|
||||||
- HY2XS admin не запускается от root;
|
- HY2XS admin не запускается от root;
|
||||||
- смена версии Hysteria2 через UI отключена в baseline;
|
- смена версии Hysteria2 через UI **отсутствует как API**;
|
||||||
- список upstream releases не является частью operator UI baseline;
|
- список upstream releases не является частью operator UI baseline;
|
||||||
- port hopping не является частью production path.
|
- port hopping не является частью production path.
|
||||||
|
|
||||||
|
### Удалённые операции: почему не заглушки
|
||||||
|
|
||||||
|
Маршруты, которые продукт принципиально не поддерживает, **удалены**, а не
|
||||||
|
оставлены отвечающими «feature disabled»:
|
||||||
|
|
||||||
|
| Удалённый маршрут | Кто владеет операцией |
|
||||||
|
| --- | --- |
|
||||||
|
| `POST /hysteria2ChangeVersion` | install-оркестратор |
|
||||||
|
| `GET /listRelease` | build layer |
|
||||||
|
| `POST /config/updateHysteria2Config` | install-оркестратор |
|
||||||
|
| `POST /config/importHysteria2Config` | install-оркестратор |
|
||||||
|
| `POST /config/restartServer` | systemd |
|
||||||
|
| `POST /config/uploadCertFile` | оператор + оркестратор |
|
||||||
|
| `GET /config/hysteria2AcmePath` | не имел потребителя |
|
||||||
|
|
||||||
|
Причины две.
|
||||||
|
|
||||||
|
Во-первых, API-контракт не должен даже обещать updater, которого у продукта
|
||||||
|
нет: маршрут, всегда возвращающий отказ, вводит в заблуждение.
|
||||||
|
|
||||||
|
Во-вторых, это лишняя attack surface и технический мусор от прежней
|
||||||
|
архитектуры.
|
||||||
|
|
||||||
|
Вместе с маршрутами удалены соответствующие клиентские функции фронтенда,
|
||||||
|
кнопки и строки i18n. Кнопка, которая гарантированно возвращает ошибку, —
|
||||||
|
не «точка расширения на будущее», а дефект UX. Возвращение любого из этих
|
||||||
|
маршрутов ломает acceptance-проверку сборки.
|
||||||
|
|
||||||
|
Конфигурация Hysteria остаётся доступной панели **на чтение и на выгрузку**:
|
||||||
|
`GET /config/getHysteria2Config` и `POST /config/exportHysteria2Config`.
|
||||||
|
|
||||||
### Что нельзя делать
|
### Что нельзя делать
|
||||||
|
|
||||||
- собирать admin-компонент на target server;
|
- собирать admin-компонент на target server;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
## Главная роль оркестратора
|
## Главная роль оркестратора
|
||||||
|
|
||||||
Оркестратор работает **только на target machine** и умеет:
|
Оркестратор работает **только на target machine** и умеет:
|
||||||
|
- выполнить read-only проверку чистоты хоста (`preflight-install`)
|
||||||
- выполнить первичную установку (`install`)
|
- выполнить первичную установку (`install`)
|
||||||
- выполнить явную реконфигурацию (`reconfigure --dry-run|--apply`)
|
- выполнить явную реконфигурацию (`reconfigure --dry-run|--apply`)
|
||||||
- разложить bundled UI
|
- разложить bundled UI
|
||||||
@@ -48,6 +49,93 @@
|
|||||||
|
|
||||||
Если машина уже «жила своей жизнью», baseline не обещает корректной автоадаптации.
|
Если машина уже «жила своей жизнью», baseline не обещает корректной автоадаптации.
|
||||||
|
|
||||||
|
## Двухфазный контракт установки
|
||||||
|
|
||||||
|
Установка разделена на две фазы с жёсткой границей между ними:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PHASE 0 — READ ONLY
|
||||||
|
проверка прав
|
||||||
|
sha256sum -c metadata/checksums.txt
|
||||||
|
./orchestrator/hy2xs-orchestrator preflight-install --package-dir <распакованный пакет>
|
||||||
|
├── платформа Debian 13 amd64
|
||||||
|
├── clean-host контракт
|
||||||
|
└── валидация конфигурации
|
||||||
|
↓ ноль persistent writes
|
||||||
|
PHASE 0 PASSED
|
||||||
|
↓
|
||||||
|
PHASE 1 — MUTATION
|
||||||
|
install -d /usr/local/lib/hy2xs
|
||||||
|
раскладка оркестратора и runtime-пакета
|
||||||
|
hy2xs-orchestrator install
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевые свойства:
|
||||||
|
|
||||||
|
- `preflight-install` запускается **из распакованного пакета**, а не из
|
||||||
|
установленного `/usr/local/lib/hy2xs`: до PHASE 1 этого каталога может не
|
||||||
|
существовать, и создавать его нельзя.
|
||||||
|
- Граница держится не соглашением, а **read-only guard** (`lib/guard.ts`):
|
||||||
|
под ним `writeText`/`writeTextAtomic` и мутирующие раннеры `lib/process`
|
||||||
|
кидают ошибку. Это проверяется тестами.
|
||||||
|
- Внутри `install` **`preflight()` выполняется раньше первой записи
|
||||||
|
`install-state.json`**. Отказ на этом этапе означает, что на сервере не
|
||||||
|
изменено ничего.
|
||||||
|
|
||||||
|
Полный список маркеров чужой установки и порядок очистки —
|
||||||
|
[14-legacy-cleanup.md](14-legacy-cleanup.md).
|
||||||
|
|
||||||
|
## Маркер состояния установки
|
||||||
|
|
||||||
|
`/var/lib/hy2xs/install-state.json` отвечает на вопрос «эта машина — установка
|
||||||
|
**текущего поколения** HY2XS, и в каком она состоянии». Поэтому кроме фазы он
|
||||||
|
несёт идентификацию поколения:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"product": "hy2xs",
|
||||||
|
"release_line": 1,
|
||||||
|
"config_schema_version": 2,
|
||||||
|
"product_version": "1.0.0",
|
||||||
|
"installed": true,
|
||||||
|
"phase": "installed"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`reconfigure` и `repair` проверяют `product` / `release_line` /
|
||||||
|
`config_schema_version` **до** всего остального. Флага `installed: true`
|
||||||
|
недостаточно: такой же маркер мог остаться от 0.x.
|
||||||
|
|
||||||
|
`repair` дополнительно требует явного `--allow-partial-state`, чтобы работать
|
||||||
|
поверх незавершённой установки. Разрешение не подразумевается: молчаливое
|
||||||
|
согласие на произвольный partial marker и позволяло «чинить» чужое состояние.
|
||||||
|
|
||||||
|
## Ownership и rollback
|
||||||
|
|
||||||
|
Операция ведёт учёт того, что она реально успела применить:
|
||||||
|
|
||||||
|
```text
|
||||||
|
depsInstalled
|
||||||
|
filesystemPrepared
|
||||||
|
unitsDeployed
|
||||||
|
firewallTouched
|
||||||
|
postInstallWritten
|
||||||
|
servicesStarted
|
||||||
|
```
|
||||||
|
|
||||||
|
Классификация отказа строится **по этим флагам и фазе**, а не по тексту
|
||||||
|
сообщения об ошибке. Ранее классификация шла по подстрокам, из-за чего
|
||||||
|
preflight-ошибка со словом `nftables` приводила к откату чужого firewall.
|
||||||
|
|
||||||
|
Инварианты rollback:
|
||||||
|
|
||||||
|
- `fatal_pre_apply` по определению означает «ничего не применялось»:
|
||||||
|
system rollback не выполняется, `install-state.json` не пишется,
|
||||||
|
diagnostics-бандл не собирается (его сбор сам создал бы каталоги в
|
||||||
|
`/var/log/hy2xs`).
|
||||||
|
- `systemctl stop/disable` выполняется **только если текущая операция сама
|
||||||
|
развернула эти unit-файлы**.
|
||||||
|
|
||||||
## Что приходит на target
|
## Что приходит на target
|
||||||
|
|
||||||
На target должен попадать уже готовый package, содержащий:
|
На target должен попадать уже готовый package, содержащий:
|
||||||
@@ -74,7 +162,7 @@
|
|||||||
|
|
||||||
## Что делает оркестратор по шагам
|
## Что делает оркестратор по шагам
|
||||||
|
|
||||||
1. Проверяет, что ОС — Debian 13.
|
1. Проверяет, что ОС — Debian 13, и что хост чист (**до любой мутации**).
|
||||||
2. Проверяет базовые зависимости и install context.
|
2. Проверяет базовые зависимости и install context.
|
||||||
3. Создаёт каталоги установки.
|
3. Создаёт каталоги установки.
|
||||||
4. Разворачивает bundled HY2XS admin.
|
4. Разворачивает bundled HY2XS admin.
|
||||||
@@ -115,19 +203,70 @@
|
|||||||
## CLI baseline
|
## CLI baseline
|
||||||
|
|
||||||
Команды:
|
Команды:
|
||||||
|
- `preflight-install --package-dir <path> [--config <source-env>]`
|
||||||
- `install --package-dir <path> [--config <source-env>]`
|
- `install --package-dir <path> [--config <source-env>]`
|
||||||
- `reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --dry-run`
|
- `reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --dry-run`
|
||||||
- `reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --apply`
|
- `reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --apply`
|
||||||
|
- `repair --package-dir <path> --config /etc/hy2xs/hy2xs.env [--allow-partial-state]`
|
||||||
|
- `redact-config --config <path> (--in-place | --out <path>) [--format auto|env|yaml]`
|
||||||
|
|
||||||
|
`preflight-install` не принимает `--skip-*`: эти флаги влияют на мутацию, а
|
||||||
|
PHASE 0 ничего не меняет.
|
||||||
|
|
||||||
|
`--allow-partial-state` допустим только для `repair`.
|
||||||
|
|
||||||
Инварианты:
|
Инварианты:
|
||||||
- только IPv4 bind/listen;
|
- только IPv4 bind/listen;
|
||||||
- TLS modes: `acme | file | self_signed_dev`;
|
- TLS modes: `acme | file | self_signed_dev`;
|
||||||
- `trafficStats.secret` отдельный от `JWT_SECRET`;
|
- `trafficStats.secret` отдельный от `JWT_SECRET`;
|
||||||
|
- `HY2XS_CONFIG_SCHEMA_VERSION` — обязательное поле; его отсутствие трактуется
|
||||||
|
как legacy-конфигурация и отклоняется, а не заменяется значением по умолчанию;
|
||||||
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
|
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
|
||||||
- версия/URL/SHA256 Hysteria берутся из metadata install package;
|
- версия/URL/SHA256 Hysteria берутся из metadata install package;
|
||||||
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой.
|
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой;
|
||||||
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
|
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
|
||||||
|
|
||||||
|
## Семантическая проверка сгенерированного конфига
|
||||||
|
|
||||||
|
`assertHysteriaConfigMatchesProfile` разбирает YAML и сверяет его с
|
||||||
|
production-профилем, а не ищет подстроки. Проверяются, в частности:
|
||||||
|
|
||||||
|
- `listen`, ровно один подтип `obfs` и его соответствие `obfs.type`;
|
||||||
|
- размеры пакетов Gecko;
|
||||||
|
- `bandwidth`, `disableLossCompensation`, `ignoreClientBandwidth`;
|
||||||
|
- `congestion.type` / `bbrProfile`;
|
||||||
|
- весь QUIC baseline, **включая `maxIdleTimeout`**;
|
||||||
|
- `trafficStats.listen` и непустой `secret`;
|
||||||
|
- `auth.type`, **точный** `auth.http.url` (host/port/path/token) и
|
||||||
|
`auth.http.insecure`;
|
||||||
|
- ACME: `type`, `email`, `ca`, `dir`, `listenHost`, первый домен;
|
||||||
|
- отсутствие посторонних секций верхнего уровня.
|
||||||
|
|
||||||
|
Сообщение об ошибке для `auth.http.url` намеренно не печатает сам токен: текст
|
||||||
|
уходит в логи и в diagnostics-бандл.
|
||||||
|
|
||||||
|
## Редактирование секретов
|
||||||
|
|
||||||
|
`redact-config` и diagnostics-бандл используют **структурную** редакцию: YAML
|
||||||
|
разбирается и обходится как дерево.
|
||||||
|
|
||||||
|
Это не косметика. Построчное правило `auth:\s*(.*)` подставляло маркер в
|
||||||
|
заголовок mapping'а и оставляло нетронутым вложенный
|
||||||
|
`auth.http.url` с `access_token=<секрет>`, то есть бандл уносил machine token
|
||||||
|
наружу. Значение может лежать где угодно в дереве, поэтому обходить нужно
|
||||||
|
дерево.
|
||||||
|
|
||||||
|
Редактируются:
|
||||||
|
- поля с секретоподобным именем (`password`, `secret`, `token`, `apiKey`,
|
||||||
|
`privateKey`, `authorization`, `cookie`, `bearer`, `signature`, …);
|
||||||
|
- карты, где секретны все значения (`auth.userpass`, `acme.dns.config`);
|
||||||
|
- учётные данные и секретные query-параметры внутри URL — в том числе в
|
||||||
|
env-файлах, где имя ключа (`HY2_AUTH_URL`) ни под один маркер не подходит.
|
||||||
|
|
||||||
|
Гарантия формулируется честно: **known secrets + secret-shaped unknown
|
||||||
|
fields**. Обобщённый sanitizer не может пообещать, что под правило попадёт
|
||||||
|
любой будущий секрет.
|
||||||
|
|
||||||
## Что не реализовывать
|
## Что не реализовывать
|
||||||
|
|
||||||
- update subcommands
|
- update subcommands
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ cd orchestrator && bun install --frozen-lockfile && bun run check && bun test
|
|||||||
# Тесты и статический анализ HY2XS admin
|
# Тесты и статический анализ HY2XS admin
|
||||||
cd apps && go vet ./... && go test ./...
|
cd apps && go vet ./... && go test ./...
|
||||||
|
|
||||||
# Полный E2E с реальным клиентом Hysteria (Debian 13 amd64)
|
# Полный E2E с реальным клиентом Hysteria (Debian 13 amd64; нужен Go)
|
||||||
HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
||||||
|
|
||||||
# Production-сборка: прогоняет тесты, резолвер и compatibility gate
|
# Production-сборка: прогоняет тесты, резолвер и compatibility gate
|
||||||
@@ -98,6 +98,14 @@ HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
|||||||
| неположительный/нецелый `min` | отклонено |
|
| неположительный/нецелый `min` | отклонено |
|
||||||
| пустой obfs-пароль | автогенерация, а не пустое значение в конфиге |
|
| пустой obfs-пароль | автогенерация, а не пустое значение в конфиге |
|
||||||
| `HY2XS_CONFIG_SCHEMA_VERSION=1` | отклонено с указанием на чистую установку |
|
| `HY2XS_CONFIG_SCHEMA_VERSION=1` | отклонено с указанием на чистую установку |
|
||||||
|
| `HY2XS_CONFIG_SCHEMA_VERSION` отсутствует | отклонено как legacy-конфигурация |
|
||||||
|
| `HY2XS_CONFIG_SCHEMA_VERSION=` (пусто) | отклонено как legacy-конфигурация |
|
||||||
|
| `HY2XS_CONFIG_SCHEMA_VERSION=2` | принято |
|
||||||
|
|
||||||
|
Отсутствие маркера схемы отклоняется намеренно: до v1 этого поля не
|
||||||
|
существовало, поэтому именно пустое значение — самый вероятный признак
|
||||||
|
конфигурации 0.x. Любой fallback здесь молча превращал бы legacy-конфиг в
|
||||||
|
якобы валидный.
|
||||||
|
|
||||||
Отдельно — round-trip `parse(render(config)) == config`. Этот тест ловит класс ошибок «в рендер runtime-конфига попал литерал вместо значения из конфигурации».
|
Отдельно — round-trip `parse(render(config)) == config`. Этот тест ловит класс ошибок «в рендер runtime-конфига попал литерал вместо значения из конфигурации».
|
||||||
|
|
||||||
@@ -110,6 +118,63 @@ HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
|||||||
- пароль с пробелами и спецсимволами экранируется;
|
- пароль с пробелами и спецсимволами экранируется;
|
||||||
- YAML-инъекция через пароль отклоняется даже в обход env-валидации.
|
- YAML-инъекция через пароль отклоняется даже в обход env-валидации.
|
||||||
|
|
||||||
|
## A5. Граница установки и поколение (unit)
|
||||||
|
|
||||||
|
`orchestrator/test/clean-host.test.ts`:
|
||||||
|
|
||||||
|
- чистый хост проходит;
|
||||||
|
- **каждый** маркер по отдельности останавливает установку;
|
||||||
|
- список покрывает состояние, юниты, бинарник Hysteria и наследие 0.x;
|
||||||
|
- пути из конфигурации (`HY2XS_INSTALL_DIR`, `HY2XS_DATA_DIR`) попадают в
|
||||||
|
список, а не только значения по умолчанию;
|
||||||
|
- `/usr/local/lib/hy2xs/package` — маркер в PHASE 0, но не в PHASE 1: между
|
||||||
|
фазами его создаёт сам `install.sh`;
|
||||||
|
- сообщение перечисляет найденные маркеры и говорит, что хост не изменён.
|
||||||
|
|
||||||
|
`orchestrator/test/install-boundary.test.ts`:
|
||||||
|
|
||||||
|
- под read-only guard недоступны `writeText`, `writeTextAtomic`, `runVisible`,
|
||||||
|
`runHidden`, `runRawVisible`;
|
||||||
|
- классификация отказа зависит от ownership-флагов и фазы, а **не** от текста
|
||||||
|
ошибки;
|
||||||
|
- пока операция ничего не применила, отказ — `fatal_pre_apply`.
|
||||||
|
|
||||||
|
`orchestrator/test/install-state.test.ts`:
|
||||||
|
|
||||||
|
- маркер текущего поколения принимается;
|
||||||
|
- маркер без полей поколения отклоняется, **несмотря на `installed: true`**;
|
||||||
|
- чужой `product`, `release_line` или `config_schema_version` отклоняются;
|
||||||
|
- записываемый маркер всегда несёт идентификацию поколения;
|
||||||
|
- незавершённая установка подсказывает `repair --allow-partial-state`.
|
||||||
|
|
||||||
|
## A6. Редактирование секретов (unit)
|
||||||
|
|
||||||
|
`orchestrator/test/redaction.test.ts`:
|
||||||
|
|
||||||
|
- machine token не переживает редакцию серверного конфига — регрессия на
|
||||||
|
построчное правило `auth:`, оставлявшее нетронутым `auth.http.url`;
|
||||||
|
- obfs-пароль не переживает редакцию;
|
||||||
|
- результат остаётся валидным YAML;
|
||||||
|
- несекретные поля сохраняются: диагностика должна оставаться полезной;
|
||||||
|
- неизвестное поле с секретоподобным именем вырезается;
|
||||||
|
- `acme.dns.config` вырезается целиком;
|
||||||
|
- невалидный YAML не роняет редакцию и всё равно чистится;
|
||||||
|
- секрет внутри URL-значения в env вырезается, даже если имя ключа несекретное
|
||||||
|
(`HY2_AUTH_URL`).
|
||||||
|
|
||||||
|
## A7. Контракт версий (build)
|
||||||
|
|
||||||
|
Шаг `verify_versions_contract` (`tools/build/lib/versions.sh`) роняет сборку до
|
||||||
|
создания tarball при рассинхроне `versions.env` с `PACKAGE_VERSION`,
|
||||||
|
`packageManager` обоих `package.json`, схемой в `package/config/hy2xs.env`,
|
||||||
|
константами, скомпилированными в оркестратор, директивой `go` в `apps/go.mod`,
|
||||||
|
metadata пакета и версией, которую сообщает собранный `hy2xs-admin`.
|
||||||
|
|
||||||
|
Разбор upstream `hashes.txt` покрыт `orchestrator/test/hysteria-release.test.ts`:
|
||||||
|
реальный формат релиза, отсутствие путаницы `hysteria-linux-amd64` с
|
||||||
|
`hysteria-linux-amd64-avx`, форма `sha256:<hex>`, верхний регистр,
|
||||||
|
противоречивые записи, отсутствие нужной строки.
|
||||||
|
|
||||||
## B. Target install tests
|
## B. Target install tests
|
||||||
|
|
||||||
### На чистом Debian 13 проверяем
|
### На чистом Debian 13 проверяем
|
||||||
@@ -174,7 +239,8 @@ congestion:
|
|||||||
|
|
||||||
quic:
|
quic:
|
||||||
disableStatelessReset == false
|
disableStatelessReset == false
|
||||||
окна и таймауты == baseline
|
окна, maxIncomingStreams, disablePathMTUDiscovery == baseline
|
||||||
|
maxIdleTimeout == 30s
|
||||||
|
|
||||||
trafficStats:
|
trafficStats:
|
||||||
listen == runtime env
|
listen == runtime env
|
||||||
@@ -182,13 +248,27 @@ trafficStats:
|
|||||||
|
|
||||||
auth:
|
auth:
|
||||||
type == http
|
type == http
|
||||||
url содержит machine access_token
|
url == http://127.0.0.1:<UI_PORT>/hui/hysteria2/auth?access_token=<machine token>
|
||||||
|
insecure == (tlsMode == self_signed_dev)
|
||||||
|
|
||||||
TLS:
|
TLS:
|
||||||
acme-режим не содержит секции tls
|
acme-режим не содержит секции tls
|
||||||
|
acme: type/email/ca/dir/listenHost/первый домен == профиль
|
||||||
file-режим не содержит секции acme
|
file-режим не содержит секции acme
|
||||||
|
|
||||||
|
верхний уровень:
|
||||||
|
нет секций вне production-профиля
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`maxIdleTimeout` присутствовал в профиле, но не проверялся: конфиг с уехавшим
|
||||||
|
idle timeout проходил семантическую проверку. Точно так же `auth.http.url`
|
||||||
|
раньше сверялся только на наличие подстроки `access_token=`, из-за чего
|
||||||
|
уехавший порт или путь остались бы незамеченными — а это единственный канал
|
||||||
|
допуска пиров.
|
||||||
|
|
||||||
|
Сообщение об ошибке для `auth.http.url` намеренно не печатает сам токен: текст
|
||||||
|
уходит в логи и в diagnostics-бандл. Это закреплено отдельным тестом.
|
||||||
|
|
||||||
## C2. End-to-end с реальным клиентом
|
## C2. End-to-end с реальным клиентом
|
||||||
|
|
||||||
`tools/test/e2e-hysteria.sh`, отдельно для Gecko и Salamander:
|
`tools/test/e2e-hysteria.sh`, отдельно для Gecko и Salamander:
|
||||||
@@ -198,7 +278,7 @@ TLS:
|
|||||||
3. handshake с обфускацией;
|
3. handshake с обфускацией;
|
||||||
4. HTTP auth HY2XS: разрешённый пир принят;
|
4. HTTP auth HY2XS: разрешённый пир принят;
|
||||||
5. HTTP auth HY2XS: неразрешённый пир отклонён;
|
5. HTTP auth HY2XS: неразрешённый пир отклонён;
|
||||||
6. клиент подключается **именно по сгенерированной `hysteria2://` ссылке**;
|
6. клиент подключается **именно по ссылке, которую выдаёт production-код**;
|
||||||
7. TCP forwarding;
|
7. TCP forwarding;
|
||||||
8. UDP forwarding;
|
8. UDP forwarding;
|
||||||
9. `trafficStats` с валидным secret;
|
9. `trafficStats` с валидным secret;
|
||||||
@@ -209,6 +289,28 @@ TLS:
|
|||||||
|
|
||||||
Пункт 6 — тот самый, который ловит класс ошибок, неизбежный при наивном включении Gecko: сервер работает, ссылка формально валидна, а клиент по ней не подключается.
|
Пункт 6 — тот самый, который ловит класс ошибок, неизбежный при наивном включении Gecko: сервер работает, ссылка формально валидна, а клиент по ней не подключается.
|
||||||
|
|
||||||
|
### Одна реализация URI, а не две
|
||||||
|
|
||||||
|
Ссылка берётся из production-генератора через `apps/tools/share-uri`, который
|
||||||
|
вызывает ту же `service.BuildHysteria2ShareURI`, что и панель.
|
||||||
|
|
||||||
|
Раньше внутри e2e жила **вторая** реализация URI на bash. Go-юнит-тесты
|
||||||
|
проверяли production-генератор, e2e проверял свою функцию — и дрейф любой из
|
||||||
|
них оставлял обе группы тестов зелёными.
|
||||||
|
|
||||||
|
Единственное расхождение с пользовательской ссылкой — `insecure=1`: e2e
|
||||||
|
работает на самоподписанном сертификате. Это расхождение ограничено с двух
|
||||||
|
сторон:
|
||||||
|
|
||||||
|
- e2e отдельно печатает и проверяет **production-вариант** ссылки
|
||||||
|
(`insecure=0`, корректные `obfs` и `sni`);
|
||||||
|
- Go-тест `TestBuildHysteria2ShareURI_InsecureDiffersOnlyInThatParam`
|
||||||
|
доказывает, что кроме этого параметра ссылки совпадают побайтово;
|
||||||
|
- Go-тест `TestBuildHysteria2Url_ProductionPathNeverDisablesVerification`
|
||||||
|
фиксирует, что production-путь никогда не передаёт `insecure=1`.
|
||||||
|
|
||||||
|
Для запуска e2e нужен Go (`GO_BIN`).
|
||||||
|
|
||||||
## C3. Share URI (unit)
|
## C3. Share URI (unit)
|
||||||
|
|
||||||
`apps/service/hysteria2_api_test.go`:
|
`apps/service/hysteria2_api_test.go`:
|
||||||
@@ -232,6 +334,22 @@ TLS:
|
|||||||
- вырезается **неизвестное** поле с секретным именем;
|
- вырезается **неизвестное** поле с секретным именем;
|
||||||
- пути к файлам (`tls.key`, `ech.keyPath`, `clientCA`) остаются видимыми.
|
- пути к файлам (`tls.key`, `ech.keyPath`, `clientCA`) остаются видимыми.
|
||||||
|
|
||||||
|
## D0. Граница установки на живом сервере
|
||||||
|
|
||||||
|
Проверяется на хосте, где уже стоит предыдущая установка:
|
||||||
|
|
||||||
|
1. `install.sh` завершается отказом на PHASE 0;
|
||||||
|
2. `/usr/local/lib/hy2xs` **не создан и не изменён**;
|
||||||
|
3. `/var/lib/hy2xs/install-state.json` не перезаписан;
|
||||||
|
4. `hysteria-server` и `hy2xs-admin` остались `active`;
|
||||||
|
5. в тексте отказа перечислены найденные маркеры и указан
|
||||||
|
`docs/14-legacy-cleanup.md`;
|
||||||
|
6. после `tools/legacy/purge-v0.sh --apply --yes-i-know` установка проходит.
|
||||||
|
|
||||||
|
Пункты 2–4 — прямая регрессия: прежний установщик успевал переписать
|
||||||
|
`/usr/local/lib/hy2xs` и `install-state.json`, а затем откатом останавливал и
|
||||||
|
выключал работающие службы старой установки.
|
||||||
|
|
||||||
## D. Negative tests
|
## D. Negative tests
|
||||||
|
|
||||||
1. не Debian 13
|
1. не Debian 13
|
||||||
|
|||||||
@@ -104,6 +104,58 @@ curl -sS \
|
|||||||
|
|
||||||
## Типовые проблемы
|
## Типовые проблемы
|
||||||
|
|
||||||
|
### Установка отказывается: обнаружена предыдущая установка
|
||||||
|
|
||||||
|
Отказ происходит в **PHASE 0**, до любой мутации. Сервер остался в том
|
||||||
|
состоянии, в котором был: ни `/usr/local/lib/hy2xs`, ни
|
||||||
|
`/var/lib/hy2xs/install-state.json`, ни работающие службы не тронуты.
|
||||||
|
|
||||||
|
В тексте отказа перечислены конкретные найденные маркеры. Порядок действий —
|
||||||
|
[14-legacy-cleanup.md](14-legacy-cleanup.md): сохранить данные, посмотреть план
|
||||||
|
`tools/legacy/purge-v0.sh`, выполнить очистку, установить заново.
|
||||||
|
|
||||||
|
Проверить хост, ничего не устанавливая:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./orchestrator/hy2xs-orchestrator preflight-install --package-dir "$(pwd)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `reconfigure`/`repair` отказываются: маркер чужого поколения
|
||||||
|
|
||||||
|
```text
|
||||||
|
Маркер установки /var/lib/hy2xs/install-state.json не относится к текущему
|
||||||
|
поколению HY2XS.
|
||||||
|
```
|
||||||
|
|
||||||
|
`installed: true` сам по себе ничего не доказывает: такой же маркер мог
|
||||||
|
остаться от `0.x`. Обе команды проверяют `product`, `release_line` и
|
||||||
|
`config_schema_version`.
|
||||||
|
|
||||||
|
Посмотреть, что видит оркестратор:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package \
|
||||||
|
| grep -o '"install_state_generation":"[^"]*"'
|
||||||
|
```
|
||||||
|
|
||||||
|
`"current"` — маркер текущего поколения; `"foreign"` — требуется чистая
|
||||||
|
переустановка; `"absent"` — установки нет.
|
||||||
|
|
||||||
|
### Незавершённая установка текущего поколения
|
||||||
|
|
||||||
|
Если установка упала **после** начала применения изменений, полная очистка не
|
||||||
|
нужна:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hy2xs-orchestrator repair \
|
||||||
|
--package-dir /usr/local/lib/hy2xs/package \
|
||||||
|
--config /etc/hy2xs/hy2xs.env \
|
||||||
|
--allow-partial-state
|
||||||
|
```
|
||||||
|
|
||||||
|
Флаг обязателен и осознан: без него `repair` работает только поверх полностью
|
||||||
|
успешной установки.
|
||||||
|
|
||||||
### Сервер установился, но UI не работает
|
### Сервер установился, но UI не работает
|
||||||
Проверить:
|
Проверить:
|
||||||
- разложился ли bundled UI
|
- разложился ли bundled UI
|
||||||
|
|||||||
@@ -156,5 +156,20 @@ hy2xs-orchestrator redact-config --config /etc/hysteria/config.yaml --out /root/
|
|||||||
Инварианты:
|
Инварианты:
|
||||||
- команда не выводит исходные секреты в stdout;
|
- команда не выводит исходные секреты в stdout;
|
||||||
- требуется выбрать ровно один режим: `--in-place` или `--out <path>`;
|
- требуется выбрать ровно один режим: `--in-place` или `--out <path>`;
|
||||||
- `--format auto` пытается определить формат по имени файла, при неоднозначности используйте `--format env|yaml`.
|
- `--format auto` пытается определить формат по имени файла, при неоднозначности используйте `--format env|yaml`;
|
||||||
|
- YAML редактируется структурно (документ разбирается и обходится как дерево),
|
||||||
|
поэтому вложенные секреты вроде `auth.http.url?access_token=…` не переживают
|
||||||
|
редакцию, а результат остаётся валидным YAML;
|
||||||
|
- в env-файлах секрет вырезается и из URL-значения, даже если имя ключа
|
||||||
|
несекретное — например, `HY2_AUTH_URL` в `post-install.env`.
|
||||||
|
|
||||||
|
Та же редакция применяется к diagnostics-бандлу
|
||||||
|
(`hy2xs-orchestrator diagnostics collect`), который собирается автоматически при
|
||||||
|
неудачной установке или реконфигурации. Бандл предназначен для передачи наружу,
|
||||||
|
поэтому попадающие в него `hy2xs.env`, `post-install.env` и `config.yaml`
|
||||||
|
редактируются перед упаковкой.
|
||||||
|
|
||||||
|
При отказе **до** начала применения изменений (`fatal_pre_apply`) бандл не
|
||||||
|
собирается: его сбор сам создал бы каталоги в `/var/log/hy2xs` на сервере,
|
||||||
|
который мы обещали не трогать.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
# Очистка сервера от предыдущей установки
|
||||||
|
|
||||||
|
## Зачем этот документ
|
||||||
|
|
||||||
|
HY2XS v1 **не поддерживает установку поверх** и **не мигрирует состояние 0.x**.
|
||||||
|
Это осознанное решение продукта, а не временное ограничение: попытка угадать,
|
||||||
|
как устроен произвольный старый сервер, приводит к полурабочим установкам,
|
||||||
|
которые невозможно диагностировать.
|
||||||
|
|
||||||
|
Отсюда следует жёсткий системный инвариант:
|
||||||
|
|
||||||
|
```text
|
||||||
|
обнаружена старая установка
|
||||||
|
↓
|
||||||
|
НОЛЬ изменений на сервере
|
||||||
|
↓
|
||||||
|
понятный отказ
|
||||||
|
↓
|
||||||
|
явная очистка (этот документ)
|
||||||
|
↓
|
||||||
|
установка HY2XS v1 с нуля
|
||||||
|
```
|
||||||
|
|
||||||
|
Установщик **никогда** не выполняет очистку самостоятельно. Удаление чужого
|
||||||
|
состояния — операция оператора, а не побочный эффект запуска `install.sh`.
|
||||||
|
|
||||||
|
## Как выглядит отказ
|
||||||
|
|
||||||
|
Установщик проверяет чистоту хоста в **PHASE 0** — до того, как изменит хотя бы
|
||||||
|
один persistent path, включая `/usr/local/lib/hy2xs`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[hy2xs-install] PHASE 0: read-only checks (no persistent path is modified)
|
||||||
|
[hy2xs-install] verifying package checksums
|
||||||
|
[hy2xs-install] running clean-host preflight from the unpacked package
|
||||||
|
[hy2xs] ERROR: На сервере обнаружена предыдущая или посторонняя установка.
|
||||||
|
HY2XS v1 не поддерживает установку поверх и не мигрирует состояние 0.x.
|
||||||
|
Ни один файл на сервере не изменён.
|
||||||
|
|
||||||
|
Найденные маркеры:
|
||||||
|
- /etc/hysteria/post-install.env (post-install.env предыдущей установки HY2XS)
|
||||||
|
- hy2xs-admin.service (systemd-юнит админки HY2XS)
|
||||||
|
|
||||||
|
Очистите сервер и установите HY2XS заново: см. docs/14-legacy-cleanup.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Если вы видите этот текст — сервер в том же состоянии, в котором был до запуска.
|
||||||
|
|
||||||
|
Отдельный случай — конфигурация без маркера схемы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HY2XS_CONFIG_SCHEMA_VERSION отсутствует в конфигурации.
|
||||||
|
Похоже на конфигурацию предыдущего поколения (0.x) или на неизвестный формат.
|
||||||
|
```
|
||||||
|
|
||||||
|
До v1 поля `HY2XS_CONFIG_SCHEMA_VERSION` не существовало, поэтому его отсутствие
|
||||||
|
трактуется как legacy, а не как «текущая схема по умолчанию».
|
||||||
|
|
||||||
|
## Что именно проверяется
|
||||||
|
|
||||||
|
Контракт чистого хоста объявлен в `orchestrator/src/steps/cleanHost.ts` и покрыт
|
||||||
|
тестами. Установка отказывается, если найден хотя бы один из объектов:
|
||||||
|
|
||||||
|
| Объект | Что это |
|
||||||
|
| --- | --- |
|
||||||
|
| `/etc/hysteria/post-install.env` | post-install.env предыдущей установки |
|
||||||
|
| `/etc/hy2xs/hy2xs.env` | runtime-конфигурация предыдущей установки |
|
||||||
|
| `/etc/hy2xs/bootstrap-admin.secret` | bootstrap-секрет администратора |
|
||||||
|
| `/var/lib/hy2xs/install-state.json` | маркер состояния установки |
|
||||||
|
| `/usr/local/lib/hy2xs/package` | runtime-пакет предыдущей установки |
|
||||||
|
| `/etc/hysteria/config.yaml` | сгенерированный серверный конфиг |
|
||||||
|
| `/usr/local/bin/hysteria` | уже установленный бинарник Hysteria |
|
||||||
|
| `/etc/nftables.d/hy2xs.nft` | nftables-фрагмент HY2XS |
|
||||||
|
| `hy2xs-admin.service` | systemd-юнит админки |
|
||||||
|
| `hysteria-server.service` | systemd-юнит сервера Hysteria |
|
||||||
|
| `h-ui.service`, `/usr/local/h-ui` | наследие панели поколения 0.x |
|
||||||
|
| `HY2XS_INSTALL_DIR` (по умолчанию `/opt/hy2xs-admin`) | каталог приложения |
|
||||||
|
| `HY2XS_DATA_DIR` (по умолчанию `/var/lib/hy2xs-admin`) | каталог данных и БД |
|
||||||
|
|
||||||
|
Последние два пути берутся из конфигурации, а не захардкожены: нестандартная
|
||||||
|
установка тоже должна быть обнаружена.
|
||||||
|
|
||||||
|
## Перед очисткой
|
||||||
|
|
||||||
|
Очистка **разрушительная**. Она удаляет базу админки вместе с учётными записями
|
||||||
|
пиров: выданные пользователям ссылки перестанут работать.
|
||||||
|
|
||||||
|
Сохраните то, что вам нужно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ссылки и учётные записи пиров (если старая панель ещё работает)
|
||||||
|
sudo sqlite3 /var/lib/hy2xs-admin/h_ui.db '.dump' > ~/hy2xs-peers-dump.sql
|
||||||
|
|
||||||
|
# серверный конфиг Hysteria
|
||||||
|
sudo cp -a /etc/hysteria/config.yaml ~/hysteria-config.yaml.bak
|
||||||
|
|
||||||
|
# post-install-справка предыдущей установки
|
||||||
|
sudo cp -a /etc/hysteria/post-install.env ~/post-install.env.bak
|
||||||
|
```
|
||||||
|
|
||||||
|
Файлы содержат секреты. Снимите с них лишние права и не пересылайте как есть:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod 600 ~/hy2xs-peers-dump.sql ~/hysteria-config.yaml.bak ~/post-install.env.bak
|
||||||
|
```
|
||||||
|
|
||||||
|
Для безопасной передачи конфига наружу используйте редактирование секретов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hy2xs-orchestrator redact-config --config ~/hysteria-config.yaml.bak --out ~/hysteria-config.redacted.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Очистка скриптом
|
||||||
|
|
||||||
|
Скрипт `tools/legacy/purge-v0.sh` лежит в репозитории. Скопируйте его на сервер.
|
||||||
|
|
||||||
|
Сначала — план. Без флагов скрипт **ничего не меняет**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./purge-v0.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Он покажет, какие службы будут остановлены, какие пути удалены и какие из них
|
||||||
|
существуют прямо сейчас.
|
||||||
|
|
||||||
|
Затем — выполнение. Требуются оба флага, `--apply` без подтверждения не работает:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./purge-v0.sh --apply --yes-i-know
|
||||||
|
```
|
||||||
|
|
||||||
|
Если бинарник Hysteria нужно оставить (например, вы проверяете им что-то ещё):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./purge-v0.sh --apply --yes-i-know --keep-hysteria-binary
|
||||||
|
```
|
||||||
|
|
||||||
|
В конце скрипт сам проверяет, что хост стал чистым по тому же контракту, который
|
||||||
|
применяет установщик. Если что-то осталось, он назовёт конкретные объекты и
|
||||||
|
завершится с ошибкой.
|
||||||
|
|
||||||
|
## Что скрипт делает и чего не делает
|
||||||
|
|
||||||
|
Делает:
|
||||||
|
|
||||||
|
1. останавливает и выключает `hysteria-server`, `hy2xs-admin`, `h-ui`;
|
||||||
|
2. снимает таймеры отката firewall `hy2xs-fw-rollback-*` — они переживают
|
||||||
|
неудачную установку и иначе продолжили бы менять ruleset уже после очистки;
|
||||||
|
3. удаляет unit-файлы и выполняет `daemon-reload`;
|
||||||
|
4. удаляет каталоги приложения, конфигурации, данных и логов;
|
||||||
|
5. удаляет фрагмент `/etc/nftables.d/hy2xs.nft` и строку `include` для него из
|
||||||
|
`/etc/nftables.conf`, после чего перезагружает ruleset;
|
||||||
|
6. проверяет чистоту хоста.
|
||||||
|
|
||||||
|
Не делает:
|
||||||
|
|
||||||
|
- не трогает `sshd` и его конфигурацию;
|
||||||
|
- не удаляет `/etc/nftables.conf` целиком — остальной ruleset принадлежит
|
||||||
|
оператору;
|
||||||
|
- не удаляет системные пакеты, установленные ранее;
|
||||||
|
- не запускается автоматически из установщика.
|
||||||
|
|
||||||
|
## Ручная очистка
|
||||||
|
|
||||||
|
Если запускать скрипт нежелательно, те же шаги вручную:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop hysteria-server hy2xs-admin h-ui
|
||||||
|
sudo systemctl disable hysteria-server hy2xs-admin h-ui
|
||||||
|
sudo systemctl reset-failed hysteria-server hy2xs-admin h-ui
|
||||||
|
|
||||||
|
# таймеры отката firewall от незавершённой установки
|
||||||
|
sudo systemctl list-units --all 'hy2xs-fw-rollback-*'
|
||||||
|
# для каждого найденного юнита:
|
||||||
|
# sudo systemctl stop <unit> && sudo systemctl disable <unit>
|
||||||
|
# sudo rm -f /etc/systemd/system/<unit>
|
||||||
|
|
||||||
|
sudo rm -f /etc/systemd/system/hysteria-server.service \
|
||||||
|
/etc/systemd/system/hy2xs-admin.service \
|
||||||
|
/etc/systemd/system/h-ui.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
sudo rm -rf /etc/hy2xs /etc/hysteria /var/lib/hy2xs /var/lib/hy2xs-admin \
|
||||||
|
/var/lib/hysteria /var/log/hy2xs /opt/hy2xs-admin \
|
||||||
|
/usr/local/lib/hy2xs /usr/local/h-ui
|
||||||
|
sudo rm -f /usr/local/bin/hysteria /usr/local/bin/hy2xs-orchestrator
|
||||||
|
|
||||||
|
sudo rm -f /etc/nftables.d/hy2xs.nft
|
||||||
|
sudo sed -i '/nftables.d\/hy2xs.nft/d' /etc/nftables.conf
|
||||||
|
sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
## После очистки
|
||||||
|
|
||||||
|
Устанавливайте HY2XS v1 обычным путём. PHASE 0 установщика повторит проверку
|
||||||
|
чистоты хоста и подтвердит, что всё в порядке:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Незавершённая установка v1 — это другой случай
|
||||||
|
|
||||||
|
Если установка HY2XS v1 упала **после** начала применения изменений, полная
|
||||||
|
очистка не нужна. У такой машины есть корректный маркер состояния текущего
|
||||||
|
поколения, и её чинит `repair`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo hy2xs-orchestrator repair \
|
||||||
|
--package-dir /usr/local/lib/hy2xs/package \
|
||||||
|
--config /etc/hy2xs/hy2xs.env \
|
||||||
|
--allow-partial-state
|
||||||
|
```
|
||||||
|
|
||||||
|
Флаг `--allow-partial-state` обязателен и осознан: без него `repair` работает
|
||||||
|
только поверх полностью успешной установки. При этом `repair` всё равно
|
||||||
|
проверяет, что маркер принадлежит текущему поколению (`product`,
|
||||||
|
`release_line`, `config_schema_version`), и откажется чинить чужое состояние.
|
||||||
|
|
||||||
|
Отказ вида «install state marker … не относится к текущему поколению HY2XS»
|
||||||
|
означает, что `repair` неприменим и нужна очистка по этому документу.
|
||||||
+10
-1
@@ -11,6 +11,8 @@
|
|||||||
- standalone update / rollback / uninstall subcommands: **вне scope**
|
- standalone update / rollback / uninstall subcommands: **вне scope**
|
||||||
- сборка и упаковка: **отдельный локальный build layer**
|
- сборка и упаковка: **отдельный локальный build layer**
|
||||||
- post-install state: **`/etc/hysteria/post-install.env`**
|
- post-install state: **`/etc/hysteria/post-install.env`**
|
||||||
|
- установка: **только на чистый хост**, миграция с 0.x не поддерживается
|
||||||
|
- контракт версий продукта/платформы/toolchain: **корневой `versions.env`**
|
||||||
- клиентский delivery/access layer: **вне baseline этого пакета docs**
|
- клиентский delivery/access layer: **вне baseline этого пакета docs**
|
||||||
|
|
||||||
## Главная архитектурная схема
|
## Главная архитектурная схема
|
||||||
@@ -38,7 +40,13 @@
|
|||||||
4. На target нет `npm` / `pnpm` / `yarn` / `bun install` / transpile step.
|
4. На target нет `npm` / `pnpm` / `yarn` / `bun install` / transpile step.
|
||||||
5. На target нет standalone логики update / rollback / uninstall.
|
5. На target нет standalone логики update / rollback / uninstall.
|
||||||
6. В install/reconfigure есть bounded rollback для failure-сценариев firewall/systemd/config/smoke.
|
6. В install/reconfigure есть bounded rollback для failure-сценариев firewall/systemd/config/smoke.
|
||||||
7. Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры **не входят** в этот baseline.
|
Rollback опирается на то, что операция реально успела применить: сервисы,
|
||||||
|
которые она не разворачивала, не останавливаются никогда.
|
||||||
|
7. Установка двухфазная: **PHASE 0 — read only**, **PHASE 1 — mutation**.
|
||||||
|
До успешного clean-host preflight на сервере не изменяется ни один
|
||||||
|
persistent path. Очистка предыдущей установки — отдельная явная операция
|
||||||
|
оператора, см. [14-legacy-cleanup.md](14-legacy-cleanup.md).
|
||||||
|
8. Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры **не входят** в этот baseline.
|
||||||
|
|
||||||
## Состав документов
|
## Состав документов
|
||||||
|
|
||||||
@@ -55,6 +63,7 @@
|
|||||||
11. [11-testing-and-acceptance.md](11-testing-and-acceptance.md)
|
11. [11-testing-and-acceptance.md](11-testing-and-acceptance.md)
|
||||||
12. [12-operations-and-troubleshooting.md](12-operations-and-troubleshooting.md)
|
12. [12-operations-and-troubleshooting.md](12-operations-and-troubleshooting.md)
|
||||||
13. [13-production-runbook.md](13-production-runbook.md)
|
13. [13-production-runbook.md](13-production-runbook.md)
|
||||||
|
14. [14-legacy-cleanup.md](14-legacy-cleanup.md)
|
||||||
|
|
||||||
История изменений проекта — в [CHANGELOG.md](../CHANGELOG.md).
|
История изменений проекта — в [CHANGELOG.md](../CHANGELOG.md).
|
||||||
|
|
||||||
|
|||||||
@@ -23,3 +23,29 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
В baseline нет target-side JavaScript, TypeScript, frontend или Go build step.
|
В baseline нет target-side JavaScript, TypeScript, frontend или Go build step.
|
||||||
|
|
||||||
|
## Установка выполняется в две фазы
|
||||||
|
|
||||||
|
```text
|
||||||
|
PHASE 0 — READ ONLY
|
||||||
|
проверка прав и checksums пакета
|
||||||
|
clean-host preflight из распакованного архива
|
||||||
|
↓ ноль изменений на сервере
|
||||||
|
PHASE 1 — MUTATION
|
||||||
|
установка orchestrator, раскладка runtime-пакета
|
||||||
|
install
|
||||||
|
```
|
||||||
|
|
||||||
|
HY2XS v1 **не устанавливается поверх** предыдущей установки и не мигрирует её
|
||||||
|
состояние. Если PHASE 0 обнаружит старую установку, установщик завершится с
|
||||||
|
ошибкой и **не изменит на сервере ничего**.
|
||||||
|
|
||||||
|
Проверить хост, ничего не устанавливая:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./orchestrator/hy2xs-orchestrator preflight-install --package-dir "$(pwd)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Очистка предыдущей установки — отдельная явная операция оператора, она описана
|
||||||
|
в `docs/14-legacy-cleanup.md` репозитория проекта и выполняется скриптом
|
||||||
|
`tools/legacy/purge-v0.sh`. Установщик её никогда не запускает сам.
|
||||||
|
|||||||
+100
-33
@@ -16,7 +16,9 @@ dist/hy2xs-install-<version>.tar.gz
|
|||||||
|
|
||||||
Основные части проекта:
|
Основные части проекта:
|
||||||
|
|
||||||
|
- [`versions.env`](../../versions.env) — контракт продукта, платформы и toolchain. Единственный источник истины для версий и контрольных сумм.
|
||||||
- [`tools/build/build.sh`](build.sh) — главный entrypoint сборки.
|
- [`tools/build/build.sh`](build.sh) — главный entrypoint сборки.
|
||||||
|
- [`tools/build/lib/versions.sh`](lib/versions.sh) — загрузка `versions.env` и `verify_versions_contract`.
|
||||||
- [`tools/build/lib/deps.sh`](lib/deps.sh) — проверка Debian/amd64, установка build dependencies, установка Go/Bun/Node.js/pnpm.
|
- [`tools/build/lib/deps.sh`](lib/deps.sh) — проверка Debian/amd64, установка build dependencies, установка Go/Bun/Node.js/pnpm.
|
||||||
- [`tools/build/lib/package.sh`](lib/package.sh) — сборка orchestrator, сборка HY2XS admin, создание stage directory и tar.gz архива.
|
- [`tools/build/lib/package.sh`](lib/package.sh) — сборка orchestrator, сборка HY2XS admin, создание stage directory и tar.gz архива.
|
||||||
- [`tools/build/lib/verify.sh`](lib/verify.sh) — проверка структуры репозитория и итогового архива.
|
- [`tools/build/lib/verify.sh`](lib/verify.sh) — проверка структуры репозитория и итогового архива.
|
||||||
@@ -53,24 +55,49 @@ Windows и macOS можно использовать для редактиров
|
|||||||
|
|
||||||
При запуске builder:
|
При запуске builder:
|
||||||
|
|
||||||
1. Проверяет, что host — Debian 13 amd64.
|
1. Загружает и валидирует [`versions.env`](../../versions.env).
|
||||||
2. Проверяет структуру репозитория.
|
2. Проверяет, что host соответствует `HY2XS_BUILD_*` (по умолчанию Debian 13 amd64).
|
||||||
3. Устанавливает недостающие системные build dependencies через `apt-get`.
|
3. Проверяет структуру репозитория.
|
||||||
4. Проверяет или скачивает локальные версии:
|
4. Устанавливает недостающие системные build dependencies через `apt-get`.
|
||||||
- Go `1.21.13`;
|
5. Проверяет или скачивает локальные версии Go/Bun/Node.js/pnpm **из контракта**
|
||||||
- Bun `1.3.13`;
|
и сверяет каждый архив с контрольной суммой из `versions.env`.
|
||||||
- Node.js `20.19.0`;
|
6. Выполняет `verify_versions_contract`: рассинхрон версий роняет сборку до создания tarball.
|
||||||
- pnpm `9.15.9`.
|
7. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`).
|
||||||
5. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`).
|
8. Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт.
|
||||||
6. Разрешает upstream-версию Hysteria, скачивает артефакт и считает SHA-256.
|
9. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
|
||||||
7. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
|
10. Копирует package skeleton.
|
||||||
8. Копирует package skeleton.
|
11. Собирает install-only orchestrator в standalone binary.
|
||||||
9. Собирает install-only orchestrator в standalone binary.
|
12. Собирает frontend и backend HY2XS admin в Linux amd64 binary, проставляя версию админки через ldflags.
|
||||||
10. Собирает frontend и backend HY2XS admin в Linux amd64 binary.
|
13. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
|
||||||
11. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
|
14. Записывает metadata и checksums.
|
||||||
12. Записывает metadata и checksums.
|
15. Создаёт `dist/hy2xs-install-<version>.tar.gz`.
|
||||||
13. Создаёт `dist/hy2xs-install-<version>.tar.gz`.
|
16. Проверяет архив и прогоняет acceptance-проверки.
|
||||||
14. Проверяет архив и прогоняет acceptance-проверки.
|
|
||||||
|
## Контракт версий
|
||||||
|
|
||||||
|
Версии продукта, платформы и toolchain объявлены в корневом
|
||||||
|
[`versions.env`](../../versions.env). Собственных значений по умолчанию у
|
||||||
|
`deps.sh` больше нет: без загруженного контракта сборка падает сразу.
|
||||||
|
|
||||||
|
Подход — **проверка, а не генерация**. `profile.ts`,
|
||||||
|
`package/config/hy2xs.env` и `packageManager` в обоих `package.json` остаются
|
||||||
|
обычными файлами, чтобы `bun test`, `tsc` и `go test` работали из чистого
|
||||||
|
чекаута до запуска сборки. `verify_versions_contract` сверяет их с контрактом и
|
||||||
|
роняет сборку при расхождении.
|
||||||
|
|
||||||
|
Контракт оркестратора сверяется не grep'ом по исходникам, а выводом
|
||||||
|
[`orchestrator/tools/print-contract.ts`](../../orchestrator/tools/print-contract.ts):
|
||||||
|
это доказывает, что в бинарь попало то же значение.
|
||||||
|
|
||||||
|
Версия админки приезжает в бинарь через ldflags
|
||||||
|
(`-X 'hy2xs-admin/model/constant.Version=v${HY2XS_VERSION}'`) и проверяется
|
||||||
|
запуском собранного `hy2xs-admin version`. Захардкоженной константы версии в
|
||||||
|
Go-коде больше нет: она уже успела разъехаться с версией пакета.
|
||||||
|
|
||||||
|
Чего в `versions.env` нет намеренно: прикладных зависимостей (для них есть
|
||||||
|
`pnpm-lock.yaml`, `bun.lock`, `go.sum`) и конкретной версии Hysteria (здесь
|
||||||
|
только политика `HYSTERIA_CHANNEL`, результат резолва — в
|
||||||
|
[`hysteria-lock.env`](hysteria-lock.env)).
|
||||||
|
|
||||||
## Версия Hysteria: разрешение и compatibility gate
|
## Версия Hysteria: разрешение и compatibility gate
|
||||||
|
|
||||||
@@ -81,11 +108,32 @@ Builder не хранит версию Hysteria вручную. По умолч
|
|||||||
1. канонический upstream — `HyNetworks/hysteria`;
|
1. канонический upstream — `HyNetworks/hysteria`;
|
||||||
2. принимаются только стабильные релизы, без draft и prerelease;
|
2. принимаются только стабильные релизы, без draft и prerelease;
|
||||||
3. тег должен иметь вид `app/vX.Y.Z`;
|
3. тег должен иметь вид `app/vX.Y.Z`;
|
||||||
4. берётся ровно один артефакт `hysteria-linux-amd64`;
|
4. берётся ровно один артефакт `hysteria-linux-amd64` и ровно один `hashes.txt`;
|
||||||
5. URL используется в том виде, в каком его вернул upstream API, без пересборки строки;
|
5. URL используется в том виде, в каком его вернул upstream API, без пересборки строки;
|
||||||
6. SHA-256 считается локально от скачанного файла;
|
6. ожидаемый SHA-256 берётся из upstream `hashes.txt`, и скачанный бинарник сверяется с ним;
|
||||||
7. разрешённые значения попадают в metadata пакета.
|
7. разрешённые значения попадают в metadata пакета.
|
||||||
|
|
||||||
|
### Почему hashes.txt, а не локальный пересчёт
|
||||||
|
|
||||||
|
Раньше SHA-256 считался от уже скачанного файла. Это защищает target от
|
||||||
|
последующей подмены, но не доказывает, что builder скачал именно ожидаемый
|
||||||
|
upstream artifact: сумма фиксирует то, что пришло, каким бы оно ни было.
|
||||||
|
|
||||||
|
Формат ассета:
|
||||||
|
|
||||||
|
```text
|
||||||
|
6493dfff…f94 build/hysteria-linux-amd64
|
||||||
|
f24f63be…189 build/hysteria-linux-amd64-avx
|
||||||
|
```
|
||||||
|
|
||||||
|
Сопоставление идёт по базовому имени и строго на равенство: `build/` — часть
|
||||||
|
пути, а `hysteria-linux-amd64-avx` — другой артефакт, который не должен
|
||||||
|
совпасть по префиксу. Разбор вынесен в `parseUpstreamHashes`
|
||||||
|
([`hysteriaRelease.ts`](../../orchestrator/src/build/hysteriaRelease.ts)) и
|
||||||
|
покрыт юнит-тестами.
|
||||||
|
|
||||||
|
Источник ожидаемой суммы фиксируется в metadata как `hysteria_sha_source`.
|
||||||
|
|
||||||
Сравнение версий числовое, поэтому `v2.9.10` считается новее `v2.9.2`.
|
Сравнение версий числовое, поэтому `v2.9.10` считается новее `v2.9.2`.
|
||||||
|
|
||||||
После разрешения обязателен compatibility gate:
|
После разрешения обязателен compatibility gate:
|
||||||
@@ -114,9 +162,10 @@ BUILD FAILED: unsupported Hysteria stable v2.13.0
|
|||||||
|
|
||||||
| Переменная | По умолчанию | Назначение |
|
| Переменная | По умолчанию | Назначение |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `HYSTERIA_CHANNEL` | `stable` | `stable` — разрешить последнюю стабильную через upstream API; `pinned` — офлайн-сборка по `hysteria-lock.env` |
|
| `HYSTERIA_CHANNEL` | из `versions.env` (`stable`) | `stable` — разрешить последнюю стабильную через upstream API; `pinned` — офлайн-сборка по `hysteria-lock.env` |
|
||||||
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
||||||
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
||||||
|
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break-glass |
|
||||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `hysteria-lock.env` |
|
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `hysteria-lock.env` |
|
||||||
| `HYSTERIA_GATE_PORT` | `34443` | UDP-порт для временного запуска Hysteria в gate |
|
| `HYSTERIA_GATE_PORT` | `34443` | UDP-порт для временного запуска Hysteria в gate |
|
||||||
| `HYSTERIA_GATE_STATS_PORT` | `34712` | TCP-порт trafficStats в gate |
|
| `HYSTERIA_GATE_STATS_PORT` | `34712` | TCP-порт trafficStats в gate |
|
||||||
@@ -151,6 +200,16 @@ BUN_FLAVOR=auto
|
|||||||
- если CPU поддерживает AVX2, используется `bun-linux-x64`;
|
- если CPU поддерживает AVX2, используется `bun-linux-x64`;
|
||||||
- если CPU не поддерживает AVX2, используется `bun-linux-x64-baseline`.
|
- если CPU не поддерживает AVX2, используется `bun-linux-x64-baseline`.
|
||||||
|
|
||||||
|
Поскольку артефакта два, одной контрольной суммы архитектурно недостаточно. В
|
||||||
|
[`versions.env`](../../versions.env) зафиксированы обе:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BUN_LINUX_X64_SHA256=<sha256>
|
||||||
|
BUN_LINUX_X64_BASELINE_SHA256=<sha256>
|
||||||
|
```
|
||||||
|
|
||||||
|
Ожидаемый digest выбирается уже **после** `select_bun_artifact()`.
|
||||||
|
|
||||||
Можно принудительно задать flavor:
|
Можно принудительно задать flavor:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -171,12 +230,10 @@ BUN_FLAVOR=x64-baseline ./tools/build/build.sh
|
|||||||
./tools/build/build.sh
|
./tools/build/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
С явной версией и build id:
|
Версия пакета берётся из `versions.env`, поэтому обычно нужен только build id:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PACKAGE_VERSION=1.0.0 \
|
BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ) ./tools/build/build.sh
|
||||||
BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ) \
|
|
||||||
./tools/build/build.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Проверка результата
|
## Проверка результата
|
||||||
@@ -207,9 +264,13 @@ tar -xOzf dist/hy2xs-install-1.0.0.tar.gz hy2xs-install/metadata/package.env
|
|||||||
Обновлять и коммитить [`orchestrator/bun.lock`](../../orchestrator/bun.lock) следует только когда:
|
Обновлять и коммитить [`orchestrator/bun.lock`](../../orchestrator/bun.lock) следует только когда:
|
||||||
|
|
||||||
- изменился [`orchestrator/package.json`](../../orchestrator/package.json);
|
- изменился [`orchestrator/package.json`](../../orchestrator/package.json);
|
||||||
- изменился `BUN_REQUIRED` в [`tools/build/lib/deps.sh`](lib/deps.sh);
|
- изменился `BUN_VERSION` в [`versions.env`](../../versions.env);
|
||||||
- зависимости оркестратора обновляются осознанно.
|
- зависимости оркестратора обновляются осознанно.
|
||||||
|
|
||||||
|
При смене `BUN_VERSION` нужно обновить и `packageManager` в
|
||||||
|
`orchestrator/package.json`, и обе контрольные суммы Bun: иначе
|
||||||
|
`verify_versions_contract` остановит сборку.
|
||||||
|
|
||||||
Production builder всегда выполняет:
|
Production builder всегда выполняет:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -225,7 +286,7 @@ bun install --frozen-lockfile
|
|||||||
Правила управления пакетами frontend:
|
Правила управления пакетами frontend:
|
||||||
|
|
||||||
- [`apps/frontend/package.json`](../../apps/frontend/package.json) объявляет `"packageManager": "pnpm@9.15.9"`;
|
- [`apps/frontend/package.json`](../../apps/frontend/package.json) объявляет `"packageManager": "pnpm@9.15.9"`;
|
||||||
- builder использует закреплённый pnpm `9.15.9` из [`PNPM_REQUIRED`](lib/deps.sh);
|
- builder использует закреплённый pnpm из `PNPM_VERSION` в [`versions.env`](../../versions.env), и `verify_versions_contract` сверяет эти два значения;
|
||||||
- production-путь установки frontend всегда:
|
- production-путь установки frontend всегда:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -236,18 +297,21 @@ pnpm install --frozen-lockfile
|
|||||||
|
|
||||||
## Полезные переменные
|
## Полезные переменные
|
||||||
|
|
||||||
- `PACKAGE_VERSION=1.0.0`
|
|
||||||
- `BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)`
|
- `BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)`
|
||||||
- `BUN_FLAVOR=auto|x64|x64-baseline`
|
- `BUN_FLAVOR=auto|x64|x64-baseline`
|
||||||
- `FRONTEND_NODE_OLD_SPACE_SIZE=2048` (default memory limit for frontend build step)
|
- `FRONTEND_NODE_OLD_SPACE_SIZE=2048` (default memory limit for frontend build step)
|
||||||
- `TOOLCHAIN_DIR=/custom/path/.toolchain`
|
- `TOOLCHAIN_DIR=/custom/path/.toolchain`
|
||||||
- `VERIFY_TOOLCHAIN_CHECKSUMS=true`
|
- `VERIFY_TOOLCHAIN_CHECKSUMS=true`
|
||||||
|
- `VERSIONS_ENV_FILE=/custom/path/versions.env`
|
||||||
|
|
||||||
По умолчанию `VERIFY_TOOLCHAIN_CHECKSUMS=true` в [`tools/build/lib/deps.sh`](lib/deps.sh), поэтому для production-сборки обязательно передавать контрольные суммы:
|
`PACKAGE_VERSION` берётся из `versions.env` (`HY2XS_VERSION`); переопределять
|
||||||
|
его вручную нужно только для отладочных сборок, и `verify_versions_contract`
|
||||||
|
такую сборку отклонит.
|
||||||
|
|
||||||
- `GO_ARCHIVE_SHA256=<sha256>`
|
Контрольные суммы toolchain больше **не передаются через окружение**: они
|
||||||
- `NODE_ARCHIVE_SHA256=<sha256>`
|
объявлены в `versions.env`. Раньше воспроизводимая сборка в чистой Debian-среде
|
||||||
- `BUN_ARCHIVE_SHA256=<sha256>`
|
требовала предварительного знания четырёх SHA-256 и не запускалась одной
|
||||||
|
командой.
|
||||||
|
|
||||||
## Политика памяти при сборке frontend
|
## Политика памяти при сборке frontend
|
||||||
|
|
||||||
@@ -292,7 +356,7 @@ echo "bun_exit=$?"
|
|||||||
rm -rf .toolchain/bun .toolchain/bun-tmp
|
rm -rf .toolchain/bun .toolchain/bun-tmp
|
||||||
rm -f .toolchain/downloads/bun-linux-x64-*.zip
|
rm -f .toolchain/downloads/bun-linux-x64-*.zip
|
||||||
rm -f .toolchain/downloads/bun-linux-x64-baseline-*.zip
|
rm -f .toolchain/downloads/bun-linux-x64-baseline-*.zip
|
||||||
PACKAGE_VERSION=1.0.0 ./tools/build/build.sh
|
./tools/build/build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Проверить shell syntax:
|
Проверить shell syntax:
|
||||||
@@ -300,7 +364,10 @@ PACKAGE_VERSION=1.0.0 ./tools/build/build.sh
|
|||||||
```bash
|
```bash
|
||||||
bash -n tools/build/build.sh
|
bash -n tools/build/build.sh
|
||||||
bash -n tools/build/lib/common.sh
|
bash -n tools/build/lib/common.sh
|
||||||
|
bash -n tools/build/lib/versions.sh
|
||||||
bash -n tools/build/lib/deps.sh
|
bash -n tools/build/lib/deps.sh
|
||||||
|
bash -n tools/build/lib/hysteria.sh
|
||||||
bash -n tools/build/lib/package.sh
|
bash -n tools/build/lib/package.sh
|
||||||
bash -n tools/build/lib/verify.sh
|
bash -n tools/build/lib/verify.sh
|
||||||
|
bash -n tools/build/lib/acceptance.sh
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user