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]
|
||||
|
||||
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
|
||||
|
||||
Первый релиз линейки `v1`.
|
||||
@@ -119,8 +240,13 @@
|
||||
Порядок перехода:
|
||||
|
||||
1. Выпишите с работающего сервера список пиров и их секреты.
|
||||
2. Разверните `1.0.0` на чистом Debian 13 из release-пакета.
|
||||
3. Заведите пиров заново и раздайте новые клиентские ссылки.
|
||||
2. Очистите сервер: `tools/legacy/purge-v0.sh` или ручная процедура из
|
||||
[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` в любом случае перестанут работать: смена
|
||||
обфускации — это изменение wire-совместимости.
|
||||
|
||||
@@ -138,7 +138,12 @@ hy2xs-install/
|
||||
└── 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
|
||||
───────────── ─────────────
|
||||
определить последнюю стабильную ─┐
|
||||
скачать артефакт, посчитать SHA-256 │
|
||||
проверить, что бинарник принимает ├─► release‑пакет ──► скачать ровно
|
||||
канонический конфиг HY2XS │ version + url этот артефакт,
|
||||
заморозить version/url/sha256 ─┘ + sha256 сверить SHA-256
|
||||
и `hysteria version`
|
||||
взять ожидаемый SHA-256 из │
|
||||
upstream hashes.txt │
|
||||
скачать артефакт и сверить его ├─► release‑пакет ──► скачать ровно
|
||||
проверить, что бинарник принимает │ version + url этот артефакт,
|
||||
канонический конфиг HY2XS │ + sha256 сверить SHA-256
|
||||
заморозить version/url/sha256 ─┘ и `hysteria version`
|
||||
```
|
||||
|
||||
Контрольная сумма берётся из upstream‑ассета `hashes.txt`, а не считается
|
||||
только локально: локальный пересчёт подтверждает, что файл не изменился после
|
||||
скачивания, но не доказывает, что скачан именно ожидаемый upstream artifact.
|
||||
|
||||
Что это даёт:
|
||||
|
||||
- новая установка получает актуальную Hysteria без ручного обновления version lock;
|
||||
@@ -459,19 +469,33 @@ HY2XS_UI_PUBLIC_ACCESS=false
|
||||
./install.sh --config /root/hy2xs-target.env --non-interactive
|
||||
```
|
||||
|
||||
Во время установки HY2XS:
|
||||
Установка идёт в две фазы с жёсткой границей между ними.
|
||||
|
||||
1. проверит checksums release‑пакета;
|
||||
2. установит orchestrator в `/usr/local/lib/hy2xs`;
|
||||
3. создаст runtime‑каталоги и service users;
|
||||
4. запишет `/etc/hy2xs/hy2xs.env`;
|
||||
5. разложит bundled HY2XS admin;
|
||||
6. скачает закреплённый в пакете Hysteria2 binary из upstream, проверит SHA256 и фактическую версию;
|
||||
7. создаст `/etc/hysteria/config.yaml`;
|
||||
8. установит systemd‑юниты;
|
||||
9. применит nftables‑правила;
|
||||
10. выполнит smoke‑checks;
|
||||
11. зафиксирует успешное состояние в `/var/lib/hy2xs/install-state.json`.
|
||||
**PHASE 0 — только чтение.** До её успешного завершения на сервере не
|
||||
изменяется ни один файл, включая `/usr/local/lib/hy2xs`:
|
||||
|
||||
1. проверит, что запущено от root;
|
||||
2. проверит checksums release‑пакета;
|
||||
3. запустит clean‑host preflight **из распакованного архива**: платформа
|
||||
Debian 13 amd64, отсутствие предыдущей установки, валидность конфигурации.
|
||||
|
||||
**PHASE 1 — применение изменений:**
|
||||
|
||||
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‑пароль админки
|
||||
|
||||
@@ -654,14 +678,21 @@ hy2xs-orchestrator reconfigure \
|
||||
|
||||
| Команда | Назначение |
|
||||
| --- | --- |
|
||||
| `hy2xs-orchestrator preflight-install` | Read‑only проверка чистоты хоста; ничего не меняет |
|
||||
| `hy2xs-orchestrator status` | Показать состояние платформы, сервисов, firewall и install marker |
|
||||
| `hy2xs-orchestrator doctor` | Выполнить preflight и smoke‑checks текущей установки |
|
||||
| `hy2xs-orchestrator reconfigure --dry-run` | Проверить конфиг без применения |
|
||||
| `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 redact-config` | Отредактировать секреты в env/yaml перед публикацией логов |
|
||||
|
||||
`repair` без `--allow-partial-state` работает только поверх полностью успешной
|
||||
установки. В обоих режимах он сначала проверяет, что
|
||||
`/var/lib/hy2xs/install-state.json` принадлежит текущему поколению продукта
|
||||
(`product`, `release_line`, `config_schema_version`), и отказывается работать
|
||||
поверх чужого состояния.
|
||||
|
||||
Пример сбора диагностики:
|
||||
|
||||
```bash
|
||||
@@ -708,6 +739,35 @@ permitopen 127.0.0.1:8080 localhost:8080
|
||||
|
||||
## 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
|
||||
|
||||
Причина: домен имеет IPv6 AAAA‑запись, а HY2XS production profile является IPv4‑only.
|
||||
@@ -787,23 +847,27 @@ git status --short
|
||||
|
||||
Подготовьте build env:
|
||||
|
||||
Версии и контрольные суммы toolchain **не задаются переменными окружения**: они
|
||||
объявлены в корневом [`versions.env`](versions.env), и сборка берёт их оттуда.
|
||||
Раньше их приходилось передавать снаружи, из-за чего воспроизводимая сборка в
|
||||
чистой Debian‑среде требовала предварительного знания четырёх SHA‑256.
|
||||
|
||||
```bash
|
||||
export PACKAGE_VERSION=1.0.0
|
||||
export BUILD_ID=prod-$(date -u +%Y%m%dT%H%M%SZ)
|
||||
|
||||
# Для переносимости между x86_64-серверами без AVX2 предпочтителен baseline artifact.
|
||||
# Ожидаемый digest выбирается автоматически: в versions.env зафиксированы обе суммы.
|
||||
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-релиза.
|
||||
export GITHUB_TOKEN=<token>
|
||||
```
|
||||
|
||||
`PACKAGE_VERSION` тоже приходит из `versions.env` (`HY2XS_VERSION`). Шаг
|
||||
`verify_versions_contract` роняет сборку, если версия продукта, схема
|
||||
конфигурации, целевая платформа или `packageManager` в `package.json`
|
||||
разошлись с контрактом.
|
||||
|
||||
Запустите сборку:
|
||||
|
||||
```bash
|
||||
@@ -812,25 +876,28 @@ export GITHUB_TOKEN=<token>
|
||||
|
||||
Сборка последовательно:
|
||||
|
||||
1. прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`);
|
||||
2. определяет последнюю стабильную версию Hysteria, скачивает артефакт и считает SHA‑256;
|
||||
3. проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS для Gecko и для Salamander;
|
||||
4. собирает orchestrator, frontend и backend;
|
||||
5. прогоняет `go vet` и `go test` для HY2XS admin;
|
||||
6. формирует архив и прогоняет acceptance‑проверки.
|
||||
1. проверяет контракт `versions.env` (`verify_versions_contract`);
|
||||
2. прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`);
|
||||
3. определяет последнюю стабильную версию Hysteria, берёт ожидаемый SHA‑256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт;
|
||||
4. проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS для Gecko и для Salamander;
|
||||
5. собирает orchestrator, frontend и backend, проставляя версию админки из контракта;
|
||||
6. прогоняет `go vet` и `go test` для HY2XS admin;
|
||||
7. формирует архив и прогоняет acceptance‑проверки.
|
||||
|
||||
Любой сбой на шагах 1–5 останавливает сборку до создания пакета.
|
||||
Любой сбой на шагах 1–6 останавливает сборку до создания пакета.
|
||||
|
||||
Переменные, управляющие выбором версии 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_COMPAT_GATE` | `true` | Compatibility gate; для release‑сборок обязателен |
|
||||
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break‑glass |
|
||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в lock‑файл |
|
||||
|
||||
Полный E2E с реальным клиентом Hysteria запускается отдельно:
|
||||
Полный E2E с реальным клиентом Hysteria запускается отдельно (нужен Go: ссылка
|
||||
берётся из production‑генератора, а не из отдельной реализации внутри теста):
|
||||
|
||||
```bash
|
||||
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
|
||||
├── tools/build/ # production builder и packaging pipeline
|
||||
├── tools/test/ # end-to-end проверки с реальным клиентом Hysteria
|
||||
├── tools/legacy/ # purge-v0.sh: очистка сервера от предыдущего поколения
|
||||
├── docs/ # спецификации baseline, тестов и эксплуатации
|
||||
├── versions.env # контракт продукта, платформы и toolchain
|
||||
├── CHANGELOG.md
|
||||
├── README.md
|
||||
└── LICENSE
|
||||
|
||||
@@ -40,20 +40,103 @@ Builder не является частью target install flow: на target serv
|
||||
- шаблоны для `post-install.env`
|
||||
- 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
|
||||
|
||||
1. Проверяет структуру проекта.
|
||||
2. Прогоняет тесты и типы оркестратора.
|
||||
3. Разрешает upstream-версию Hysteria и проходит compatibility gate.
|
||||
4. Компилирует оркестратор из Bun/TypeScript в install-артефакт.
|
||||
5. Собирает / подготавливает HY2XS admin.
|
||||
6. Прогоняет тесты HY2XS admin (после сборки frontend: `go:embed all:dist` требует готовых ассетов).
|
||||
7. Копирует артефакты UI в package staging directory.
|
||||
8. Кладёт entrypoint, templates, docs и service files.
|
||||
9. Формирует итоговый install package.
|
||||
10. Считает manifest/checksum.
|
||||
11. Проверяет архив и прогоняет acceptance-проверки.
|
||||
12. Выдаёт один переносимый результат для target machine.
|
||||
2. Загружает и проверяет контракт `versions.env`.
|
||||
3. Прогоняет тесты и типы оркестратора.
|
||||
4. Разрешает upstream-версию Hysteria и проходит compatibility gate.
|
||||
5. Компилирует оркестратор из Bun/TypeScript в install-артефакт.
|
||||
6. Собирает / подготавливает HY2XS admin и сверяет его версию с контрактом.
|
||||
7. Прогоняет тесты HY2XS admin (после сборки frontend: `go:embed all:dist` требует готовых ассетов).
|
||||
8. Копирует артефакты UI в package staging directory.
|
||||
9. Кладёт entrypoint, templates, docs и service files.
|
||||
10. Формирует итоговый install package.
|
||||
11. Считает manifest/checksum.
|
||||
12. Проверяет архив и прогоняет acceptance-проверки.
|
||||
13. Выдаёт один переносимый результат для target machine.
|
||||
|
||||
## Что builder не делает
|
||||
|
||||
@@ -110,14 +193,19 @@ project/
|
||||
|
||||
`tools/build/build.sh` должен быть самодостаточным для Debian 13 amd64:
|
||||
|
||||
1. Проверяет ОС и архитектуру.
|
||||
1. Проверяет ОС и архитектуру по `versions.env` (`HY2XS_BUILD_*`).
|
||||
2. Проверяет структуру репозитория и lock-файлы.
|
||||
3. Доставляет отсутствующие системные build-зависимости через `apt-get`.
|
||||
4. Проверяет версии Go, Bun, Node.js и pnpm.
|
||||
5. При несовпадении версий скачивает управляемый локальный toolchain в `.toolchain/`.
|
||||
4. Проверяет версии Go, Bun, Node.js и pnpm по `versions.env`.
|
||||
5. При несовпадении версий скачивает управляемый локальный toolchain в `.toolchain/`
|
||||
и **сверяет каждый архив с контрольной суммой из `versions.env`**.
|
||||
6. Собирает только Linux amd64 артефакты.
|
||||
7. Записывает версии toolchain в metadata пакета.
|
||||
|
||||
Собственных значений по умолчанию у `tools/build/lib/deps.sh` больше нет: без
|
||||
загруженного контракта сборка падает сразу, а не собирает пакет на неизвестном
|
||||
toolchain.
|
||||
|
||||
## Отношение к Hysteria2
|
||||
|
||||
Сам бинарь Hysteria2 **не вендорится** в install package как baseline-правило.
|
||||
@@ -135,10 +223,13 @@ SOURCE
|
||||
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)
|
||||
@@ -165,8 +256,43 @@ TARGET SERVER
|
||||
| `HYSTERIA_VERSION_OVERRIDE` | пусто | Закрепить конкретную версию `vX.Y.Z` |
|
||||
| `HYSTERIA_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `tools/build/hysteria-lock.env` |
|
||||
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break-glass |
|
||||
| `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`);
|
||||
- способ выбора версии фиксируется в `metadata/hysteria.resolution` и `metadata/package.env`;
|
||||
@@ -180,7 +306,7 @@ Gate защищает от ситуации, когда upstream меняет с
|
||||
|
||||
Порядок:
|
||||
|
||||
1. скачать артефакт и сверить SHA-256;
|
||||
1. скачать артефакт и сверить SHA-256 с upstream `hashes.txt`;
|
||||
2. сверить `hysteria version` с разрешённой версией;
|
||||
3. отрендерить канонический конфиг HY2XS тем же кодом, что работает на target (`orchestrator/tools/render-canonical-config.ts`);
|
||||
4. запустить реальный бинарник Hysteria с этим конфигом — для Gecko и для Salamander;
|
||||
@@ -206,3 +332,5 @@ BUILD FAILED: unsupported Hysteria stable v2.13.0
|
||||
6. Hysteria2 подтягивается install layer'ом с upstream по замороженным координатам, а не собирается на target из исходников
|
||||
7. выход новой версии Hysteria после сборки не меняет содержимое уже собранного пакета
|
||||
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;
|
||||
- `auth.password`, `auth.userpass`;
|
||||
- учётные данные 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
|
||||
|
||||
@@ -118,10 +135,41 @@ HY2XS admin работает как надстройка над Hysteria YAML/AP
|
||||
- Hysteria2 запускается отдельным `hysteria-server.service`;
|
||||
- HY2XS admin работает как operator UI и HTTP auth/traffic layer;
|
||||
- HY2XS admin не запускается от root;
|
||||
- смена версии Hysteria2 через UI отключена в baseline;
|
||||
- смена версии Hysteria2 через UI **отсутствует как API**;
|
||||
- список upstream releases не является частью operator UI baseline;
|
||||
- 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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
## Главная роль оркестратора
|
||||
|
||||
Оркестратор работает **только на target machine** и умеет:
|
||||
- выполнить read-only проверку чистоты хоста (`preflight-install`)
|
||||
- выполнить первичную установку (`install`)
|
||||
- выполнить явную реконфигурацию (`reconfigure --dry-run|--apply`)
|
||||
- разложить bundled UI
|
||||
@@ -48,6 +49,93 @@
|
||||
|
||||
Если машина уже «жила своей жизнью», 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 должен попадать уже готовый package, содержащий:
|
||||
@@ -74,7 +162,7 @@
|
||||
|
||||
## Что делает оркестратор по шагам
|
||||
|
||||
1. Проверяет, что ОС — Debian 13.
|
||||
1. Проверяет, что ОС — Debian 13, и что хост чист (**до любой мутации**).
|
||||
2. Проверяет базовые зависимости и install context.
|
||||
3. Создаёт каталоги установки.
|
||||
4. Разворачивает bundled HY2XS admin.
|
||||
@@ -115,19 +203,70 @@
|
||||
## CLI baseline
|
||||
|
||||
Команды:
|
||||
- `preflight-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 --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;
|
||||
- TLS modes: `acme | file | self_signed_dev`;
|
||||
- `trafficStats.secret` отдельный от `JWT_SECRET`;
|
||||
- `HY2XS_CONFIG_SCHEMA_VERSION` — обязательное поле; его отсутствие трактуется
|
||||
как legacy-конфигурация и отклоняется, а не заменяется значением по умолчанию;
|
||||
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
|
||||
- версия/URL/SHA256 Hysteria берутся из metadata install package;
|
||||
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой.
|
||||
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой;
|
||||
- при `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
|
||||
|
||||
@@ -13,7 +13,7 @@ cd orchestrator && bun install --frozen-lockfile && bun run check && bun test
|
||||
# Тесты и статический анализ HY2XS admin
|
||||
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
|
||||
|
||||
# Production-сборка: прогоняет тесты, резолвер и compatibility gate
|
||||
@@ -98,6 +98,14 @@ HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
||||
| неположительный/нецелый `min` | отклонено |
|
||||
| пустой obfs-пароль | автогенерация, а не пустое значение в конфиге |
|
||||
| `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-конфига попал литерал вместо значения из конфигурации».
|
||||
|
||||
@@ -110,6 +118,63 @@ HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
||||
- пароль с пробелами и спецсимволами экранируется;
|
||||
- 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
|
||||
|
||||
### На чистом Debian 13 проверяем
|
||||
@@ -174,7 +239,8 @@ congestion:
|
||||
|
||||
quic:
|
||||
disableStatelessReset == false
|
||||
окна и таймауты == baseline
|
||||
окна, maxIncomingStreams, disablePathMTUDiscovery == baseline
|
||||
maxIdleTimeout == 30s
|
||||
|
||||
trafficStats:
|
||||
listen == runtime env
|
||||
@@ -182,13 +248,27 @@ trafficStats:
|
||||
|
||||
auth:
|
||||
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:
|
||||
acme-режим не содержит секции tls
|
||||
acme: type/email/ca/dir/listenHost/первый домен == профиль
|
||||
file-режим не содержит секции acme
|
||||
|
||||
верхний уровень:
|
||||
нет секций вне production-профиля
|
||||
```
|
||||
|
||||
`maxIdleTimeout` присутствовал в профиле, но не проверялся: конфиг с уехавшим
|
||||
idle timeout проходил семантическую проверку. Точно так же `auth.http.url`
|
||||
раньше сверялся только на наличие подстроки `access_token=`, из-за чего
|
||||
уехавший порт или путь остались бы незамеченными — а это единственный канал
|
||||
допуска пиров.
|
||||
|
||||
Сообщение об ошибке для `auth.http.url` намеренно не печатает сам токен: текст
|
||||
уходит в логи и в diagnostics-бандл. Это закреплено отдельным тестом.
|
||||
|
||||
## C2. End-to-end с реальным клиентом
|
||||
|
||||
`tools/test/e2e-hysteria.sh`, отдельно для Gecko и Salamander:
|
||||
@@ -198,7 +278,7 @@ TLS:
|
||||
3. handshake с обфускацией;
|
||||
4. HTTP auth HY2XS: разрешённый пир принят;
|
||||
5. HTTP auth HY2XS: неразрешённый пир отклонён;
|
||||
6. клиент подключается **именно по сгенерированной `hysteria2://` ссылке**;
|
||||
6. клиент подключается **именно по ссылке, которую выдаёт production-код**;
|
||||
7. TCP forwarding;
|
||||
8. UDP forwarding;
|
||||
9. `trafficStats` с валидным secret;
|
||||
@@ -209,6 +289,28 @@ TLS:
|
||||
|
||||
Пункт 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)
|
||||
|
||||
`apps/service/hysteria2_api_test.go`:
|
||||
@@ -232,6 +334,22 @@ TLS:
|
||||
- вырезается **неизвестное** поле с секретным именем;
|
||||
- пути к файлам (`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
|
||||
|
||||
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 не работает
|
||||
Проверить:
|
||||
- разложился ли bundled UI
|
||||
|
||||
@@ -156,5 +156,20 @@ hy2xs-orchestrator redact-config --config /etc/hysteria/config.yaml --out /root/
|
||||
Инварианты:
|
||||
- команда не выводит исходные секреты в stdout;
|
||||
- требуется выбрать ровно один режим: `--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**
|
||||
- сборка и упаковка: **отдельный локальный build layer**
|
||||
- post-install state: **`/etc/hysteria/post-install.env`**
|
||||
- установка: **только на чистый хост**, миграция с 0.x не поддерживается
|
||||
- контракт версий продукта/платформы/toolchain: **корневой `versions.env`**
|
||||
- клиентский delivery/access layer: **вне baseline этого пакета docs**
|
||||
|
||||
## Главная архитектурная схема
|
||||
@@ -38,7 +40,13 @@
|
||||
4. На target нет `npm` / `pnpm` / `yarn` / `bun install` / transpile step.
|
||||
5. На target нет standalone логики update / rollback / uninstall.
|
||||
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)
|
||||
12. [12-operations-and-troubleshooting.md](12-operations-and-troubleshooting.md)
|
||||
13. [13-production-runbook.md](13-production-runbook.md)
|
||||
14. [14-legacy-cleanup.md](14-legacy-cleanup.md)
|
||||
|
||||
История изменений проекта — в [CHANGELOG.md](../CHANGELOG.md).
|
||||
|
||||
|
||||
@@ -23,3 +23,29 @@
|
||||
```
|
||||
|
||||
В 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/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/package.sh`](lib/package.sh) — сборка orchestrator, сборка HY2XS admin, создание stage directory и tar.gz архива.
|
||||
- [`tools/build/lib/verify.sh`](lib/verify.sh) — проверка структуры репозитория и итогового архива.
|
||||
@@ -53,24 +55,49 @@ Windows и macOS можно использовать для редактиров
|
||||
|
||||
При запуске builder:
|
||||
|
||||
1. Проверяет, что host — Debian 13 amd64.
|
||||
2. Проверяет структуру репозитория.
|
||||
3. Устанавливает недостающие системные build dependencies через `apt-get`.
|
||||
4. Проверяет или скачивает локальные версии:
|
||||
- Go `1.21.13`;
|
||||
- Bun `1.3.13`;
|
||||
- Node.js `20.19.0`;
|
||||
- pnpm `9.15.9`.
|
||||
5. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`).
|
||||
6. Разрешает upstream-версию Hysteria, скачивает артефакт и считает SHA-256.
|
||||
7. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
|
||||
8. Копирует package skeleton.
|
||||
9. Собирает install-only orchestrator в standalone binary.
|
||||
10. Собирает frontend и backend HY2XS admin в Linux amd64 binary.
|
||||
11. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
|
||||
12. Записывает metadata и checksums.
|
||||
13. Создаёт `dist/hy2xs-install-<version>.tar.gz`.
|
||||
14. Проверяет архив и прогоняет acceptance-проверки.
|
||||
1. Загружает и валидирует [`versions.env`](../../versions.env).
|
||||
2. Проверяет, что host соответствует `HY2XS_BUILD_*` (по умолчанию Debian 13 amd64).
|
||||
3. Проверяет структуру репозитория.
|
||||
4. Устанавливает недостающие системные build dependencies через `apt-get`.
|
||||
5. Проверяет или скачивает локальные версии Go/Bun/Node.js/pnpm **из контракта**
|
||||
и сверяет каждый архив с контрольной суммой из `versions.env`.
|
||||
6. Выполняет `verify_versions_contract`: рассинхрон версий роняет сборку до создания tarball.
|
||||
7. Прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`).
|
||||
8. Разрешает upstream-версию Hysteria, берёт ожидаемый SHA-256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт.
|
||||
9. Проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS.
|
||||
10. Копирует package skeleton.
|
||||
11. Собирает install-only orchestrator в standalone binary.
|
||||
12. Собирает frontend и backend HY2XS admin в Linux amd64 binary, проставляя версию админки через ldflags.
|
||||
13. Прогоняет `go vet` и `go test` для HY2XS admin (после сборки frontend, потому что `go:embed all:dist` требует готовых ассетов).
|
||||
14. Записывает metadata и checksums.
|
||||
15. Создаёт `dist/hy2xs-install-<version>.tar.gz`.
|
||||
16. Проверяет архив и прогоняет 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
|
||||
|
||||
@@ -81,11 +108,32 @@ Builder не хранит версию Hysteria вручную. По умолч
|
||||
1. канонический upstream — `HyNetworks/hysteria`;
|
||||
2. принимаются только стабильные релизы, без draft и prerelease;
|
||||
3. тег должен иметь вид `app/vX.Y.Z`;
|
||||
4. берётся ровно один артефакт `hysteria-linux-amd64`;
|
||||
4. берётся ровно один артефакт `hysteria-linux-amd64` и ровно один `hashes.txt`;
|
||||
5. URL используется в том виде, в каком его вернул upstream API, без пересборки строки;
|
||||
6. SHA-256 считается локально от скачанного файла;
|
||||
6. ожидаемый SHA-256 берётся из upstream `hashes.txt`, и скачанный бинарник сверяется с ним;
|
||||
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`.
|
||||
|
||||
После разрешения обязателен 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_COMPAT_GATE` | `true` | Compatibility gate; для release-сборок обязателен |
|
||||
| `HYSTERIA_VERIFY_UPSTREAM_HASHES` | `true` | Сверять артефакт с upstream `hashes.txt`; отключение — только break-glass |
|
||||
| `HYSTERIA_WRITE_LOCK` | `false` | Записать разрешённые значения обратно в `hysteria-lock.env` |
|
||||
| `HYSTERIA_GATE_PORT` | `34443` | UDP-порт для временного запуска Hysteria в 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-baseline`.
|
||||
|
||||
Поскольку артефакта два, одной контрольной суммы архитектурно недостаточно. В
|
||||
[`versions.env`](../../versions.env) зафиксированы обе:
|
||||
|
||||
```bash
|
||||
BUN_LINUX_X64_SHA256=<sha256>
|
||||
BUN_LINUX_X64_BASELINE_SHA256=<sha256>
|
||||
```
|
||||
|
||||
Ожидаемый digest выбирается уже **после** `select_bun_artifact()`.
|
||||
|
||||
Можно принудительно задать flavor:
|
||||
|
||||
```bash
|
||||
@@ -171,12 +230,10 @@ BUN_FLAVOR=x64-baseline ./tools/build/build.sh
|
||||
./tools/build/build.sh
|
||||
```
|
||||
|
||||
С явной версией и build id:
|
||||
Версия пакета берётся из `versions.env`, поэтому обычно нужен только build id:
|
||||
|
||||
```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/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 всегда выполняет:
|
||||
|
||||
```bash
|
||||
@@ -225,7 +286,7 @@ bun install --frozen-lockfile
|
||||
Правила управления пакетами frontend:
|
||||
|
||||
- [`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 всегда:
|
||||
|
||||
```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)`
|
||||
- `BUN_FLAVOR=auto|x64|x64-baseline`
|
||||
- `FRONTEND_NODE_OLD_SPACE_SIZE=2048` (default memory limit for frontend build step)
|
||||
- `TOOLCHAIN_DIR=/custom/path/.toolchain`
|
||||
- `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>`
|
||||
- `NODE_ARCHIVE_SHA256=<sha256>`
|
||||
- `BUN_ARCHIVE_SHA256=<sha256>`
|
||||
Контрольные суммы toolchain больше **не передаются через окружение**: они
|
||||
объявлены в `versions.env`. Раньше воспроизводимая сборка в чистой Debian-среде
|
||||
требовала предварительного знания четырёх SHA-256 и не запускалась одной
|
||||
командой.
|
||||
|
||||
## Политика памяти при сборке frontend
|
||||
|
||||
@@ -292,7 +356,7 @@ echo "bun_exit=$?"
|
||||
rm -rf .toolchain/bun .toolchain/bun-tmp
|
||||
rm -f .toolchain/downloads/bun-linux-x64-*.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:
|
||||
@@ -300,7 +364,10 @@ PACKAGE_VERSION=1.0.0 ./tools/build/build.sh
|
||||
```bash
|
||||
bash -n tools/build/build.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/hysteria.sh
|
||||
bash -n tools/build/lib/package.sh
|
||||
bash -n tools/build/lib/verify.sh
|
||||
bash -n tools/build/lib/acceptance.sh
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user