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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user