# Изменения HY2XS Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/). Проект использует [семантическое версионирование](https://semver.org/lang/ru/). Версия относится к самому HY2XS, а не к Hysteria: версия Hysteria выбирается на этапе сборки пакета и фиксируется в его metadata. ## [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`. > **Обновление с `0.x` не поддерживается.** > Между `0.x` и `1.0.0` изменились схема конфигурации, тип обфускации > по умолчанию и контракт выбора версии Hysteria. Сервер, установленный > из пакета `0.x`, нужно поднимать заново: очистка и установка с нуля. > Подробности — в разделе «Миграция с 0.x» ниже. ### Добавлено - **Разрешение версии Hysteria на этапе сборки.** Builder по умолчанию сам определяет последний стабильный upstream-релиз, скачивает артефакт, вычисляет SHA-256 и замораживает `version` + `url` + `sha256` в metadata пакета. Target-сервер по-прежнему скачивает конкретный неизменяемый артефакт и никогда не обращается к moving `latest`. - **Compatibility gate в сборке.** До создания release-пакета builder рендерит канонический конфиг HY2XS тем же кодом, что и оркестратор, и запускает с ним реальный бинарник Hysteria — для обоих профилей обфускации. Несовместимый upstream ломает сборку, а не сервер оператора. - **Поддержка Gecko-обфускации** (Hysteria 2.9.2+) со сквозной интеграцией: оркестратор, шаблон конфига, модель админки, генерация `hysteria2://` URI, типы и формы фронтенда. - **Версия схемы конфигурации** `HY2XS_CONFIG_SCHEMA_VERSION=2`. Пакет отказывается работать с конфигурацией неизвестной схемы вместо того, чтобы молча применить чужие значения. - **Современный серверный baseline** в генерируемом конфиге: `congestion.type: bbr` + `bbrProfile: standard`, `bandwidth.disableLossCompensation: false`, `quic.disableStatelessReset: false`, а также `maxIdleTimeout`, `maxIncomingStreams`, `disablePathMTUDiscovery`. - **Модель современной схемы Hysteria в админке**: `obfs.gecko`, `ech`, `congestion`, `mimic`, `realm`, `tls.clientCA`, `quic.disableStatelessReset`, `bandwidth.disableLossCompensation`, `masquerade.proxy.xForwarded`. Поля читаются и отображаются, даже если HY2XS не включает их в default-профиль. - **Тесты оркестратора** (`bun test`): разбор env, рендер конфига, семантические инварианты профиля, резолвер upstream-релизов, release rollover. - **E2E-проверка с реальным клиентом Hysteria** — `tools/test/e2e-hysteria.sh`: TLS и obfs handshake, HTTP auth (допуск и отказ), TCP и UDP forwarding, trafficStats, per-peer accounting, переподключение после перезапуска сервера и подключение клиента именно по сгенерированной ссылке. - **`CHANGELOG.md`** в корне репозитория. ### Изменено - **Обфускация по умолчанию для новых установок — Gecko.** Salamander остаётся полностью поддержанным режимом совместимости и выбирается через `HY2XS_HYSTERIA_OBFS_TYPE=salamander`. - **Gecko использует upstream-defaults `512/1200`** и не выносит размеры пакетов в env: официальная URI-схема не умеет их передавать, поэтому нестандартные значения сделали бы клиентскую ссылку неполной. - **Тип обфускации больше не собирается внутри статического YAML.** Оркестратор формирует проверенный `obfs`-блок целиком, поэтому комбинация вида `type: gecko` рядом с блоком `salamander` структурно невозможна. - **Экспорт конфига Hysteria работает от исходного YAML**, а не от типизированной модели: поля, о которых HY2XS ещё не знает, переживают выгрузку. - **Smoke-проверки разбирают YAML** и сверяют его с production-профилем, вместо поиска подстрок. - **Канонический upstream-репозиторий** — `HyNetworks/hysteria` (вместо устаревших ссылок на `apernet`). - **Реестр ACME DNS-провайдеров во фронтенде** приведён к актуальному upstream: добавлены `namecheap`, `njalla`, `porkbun`. - **Дефолты формы Hysteria во фронтенде** отражают baseline HY2XS (50/50 Mbps, Let's Encrypt, каталог ACME), а не пример из upstream-доки. - Сборка запускает тесты оркестратора и админки до упаковки. ### Исправлено - **`hysteria2://` для Gecko.** Генератор ссылок был завязан на `Obfs.Salamander.Password`, поэтому при любой другой обфускации выдавал формально корректную, но неработающую ссылку без параметров `obfs`. - **SNI в клиентской ссылке при файловых сертификатах.** SNI брался только из ACME-блока, поэтому при `HY2XS_TLS_MODE=file` уходил пустым. Теперь источник — ACME-домен, затем `HY2XS_DOMAIN`, затем `HY2XS_PUBLIC_HOST`; IP-адрес в качестве SNI не используется. - **Утечка секретов в экспорте конфига.** Выгружаемый оператору YAML содержал `trafficStats.secret`, `access_token` в auth-URL и пароль обфускации. Секреты вырезаются, включая поля, о которых HY2XS ещё не знает. - **Расхождение runtime-конфига с разобранным.** `renderRuntimeEnv` печатал тип обфускации и режим auth литералами, игнорируя фактическую конфигурацию, — из-за чего запись `/etc/hy2xs/hy2xs.env` могла разойтись с тем, что реально применено. - **Смешение веток `obfs` в UI.** Форма склеивала дефолт с ответом API и показывала блок обфускации, которого нет в конфиге сервера. - **`namedotcom` в списке ACME DNS-провайдеров.** Провайдер удалён из Hysteria в 2.11.0; конфигурация с ним больше не запускается. - **Отсутствие подсказок в форме создания пира.** У полей «Пир», «Комментарий» и «Секрет» не было ни примеров, ни пояснений: оператор не мог понять без документации, что секрет необязателен и генерируется автоматически. ### Безопасность - Переход на Hysteria 2.12.2 закрывает исправления, вышедшие после 2.8.2, включая обход UDP ACL, возможный OOM через sniff и обход ACL через домены с завершающей точкой (2.9.2). - Экспорт конфига больше не выносит секреты за пределы сервера. ### Миграция с 0.x Автоматическая миграция не предусмотрена и не планируется. Порядок перехода: 1. Выпишите с работающего сервера список пиров и их секреты. 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-совместимости. [Unreleased]: https://git.flamy.studio/prod/HY2XS_flamy/compare/v1.0.0...HEAD [1.0.0]: https://git.flamy.studio/prod/HY2XS_flamy/releases/tag/v1.0.0