# Server Hysteria2 baseline ## Цель документа Зафиксировать правила для серверного слоя Hysteria2 в модели, где UI поставляется вместе с проектом, а Hysteria берётся из official upstream во время установки. ## Роль Hysteria2 Hysteria2 — основной транспортный компонент сервера. Он не вендорится и не собирается как часть HY2XS. ## Source policy Базовое правило: - Hysteria2 скачивается **во время установки** - источник — **официальный upstream** - install layer не должен подменять собой upstream-дистрибуцию Hysteria2 ## Версионная политика Ключевое правило: **«последняя стабильная» определяется на этапе сборки пакета, а не на целевом сервере.** Не «HY2XS использует Hysteria vX.Y.Z», а: > HY2XS по умолчанию берёт последний стабильный релиз Hysteria, доступный на момент сборки пакета. Разрешённая версия, URL артефакта и контрольная сумма замораживаются в получившемся install package. Практика: - builder обращается к каноническому upstream `HyNetworks/hysteria`; - принимаются только стабильные релизы с тегом вида `app/vX.Y.Z`, без draft и prerelease; - берётся ровно один артефакт `hysteria-linux-amd64`, URL используется в том виде, в каком его отдал upstream API; - SHA-256 считается локально от скачанного артефакта, а не берётся из стороннего файла; - версия, URL и SHA-256 фиксируются в metadata install package; - на target-сервере **никогда** не используется moving `latest`; - бинарник Hysteria2 устанавливается только на этапе `install`; - фактически установленная версия записывается в `post-install.env`; - `reconfigure` не обновляет и не откатывает бинарник Hysteria2. Следствие: если между сборкой пакета и его установкой выйдет новая версия Hysteria, содержимое установки **не изменится под ногами**. Повторная установка старого пакета поставит ту же версию, что и в день сборки. Переопределения builder: ```bash HYSTERIA_CHANNEL=stable # по умолчанию: разрешить последнюю стабильную HYSTERIA_CHANNEL=pinned # взять закоммиченный tools/build/hysteria-lock.env, без сети HYSTERIA_VERSION_OVERRIDE=v2.12.2 # закрепить конкретную версию ``` ## Compatibility gate Автоматический выбор «последней стабильной» без проверки опасен: upstream может изменить схему конфигурации, и builder молча соберёт неработающий HY2XS. Поэтому до создания release-пакета builder: 1. скачивает артефакт и сверяет SHA-256; 2. сверяет `hysteria version` с разрешённой версией; 3. рендерит канонический конфиг HY2XS **тем же кодом**, который работает на target-сервере; 4. запускает реальный бинарник Hysteria с этим конфигом — отдельно для Gecko и для Salamander; 5. только после этого формирует пакет. Если upstream несовместим, ломается **сборка**: ```text BUILD FAILED: unsupported Hysteria stable v2.13.0 ``` а не production-сервер оператора. ## Платформа - ОС: только Debian 13 - init/system management: systemd - сетевой фильтр: nftables - архитектура baseline: x86_64/amd64 ## Listen и сеть ### Listen - только IPv4 - формат: `0.0.0.0:` ### Порт - один фиксированный UDP-порт - этот порт должен совпадать в: - server config - firewall rules - `post-install.env` ## Обфускация Новые установки HY2XS используют **Gecko**. Gecko помечен upstream как **experimental**. Он достраивается поверх Salamander: помимо scramble он дополнительно фрагментирует QUIC handshake на пакеты случайного размера. HY2XS использует upstream-defaults размеров пакетов `512/1200` как проверенный production-профиль. **Salamander остаётся полностью поддержанным режимом совместимости.** Смена типа обфускации требует соответствующих изменений на клиенте: это изменение wire-совместимости, а не косметическая настройка. Baseline: ```yaml obfs: type: gecko gecko: password: "<сгенерированный пароль>" minPacketSize: 512 maxPacketSize: 1200 ``` Режим совместимости: ```yaml obfs: type: salamander salamander: password: "<сгенерированный пароль>" ``` Правила: - тип выбирается через `HY2XS_HYSTERIA_OBFS_TYPE` (`gecko` | `salamander`); - пароль должен быть сильным, генерируется автоматически при `__GENERATE__` или пустом значении; - пароль фиксируется в конфигурационном контуре и доступен оператору через runtime config и `post-install.env`; - `obfs`-блок формируется оркестратором целиком, а не собирается из отдельных placeholders внутри YAML — комбинация вида `type: gecko` рядом с блоком `salamander` структурно невозможна. ### Почему размеры пакетов Gecko не вынесены в env Официальная схема `hysteria2://` передаёт только тип обфускации и пароль. `minPacketSize` и `maxPacketSize` в ссылку не помещаются. Если разрешить оператору произвольные значения, сгенерированная клиентская ссылка перестанет полностью описывать подключение и потребуется отдельный формат — выгружаемый клиентский профиль. Пока такой задачи нет, фиксация `512/1200` даёт корректную ссылку и воспроизводимое поведение. Валидация (на случай будущего расширения) централизована в оркестраторе: `min > 0`, `max >= min`, `max <= 2048`. ## Версия схемы конфигурации ```bash HY2XS_CONFIG_SCHEMA_VERSION=2 ``` Пакет понимает только свою версию схемы. Конфигурация с другой версией отклоняется fail-fast, а не применяется частично. HY2XS `v1` **не мигрирует установки `0.x` на месте**: между `0.x` и `1.0.0` изменились схема конфигурации, тип обфускации по умолчанию и контракт выбора версии Hysteria. Переход выполняется чистой установкой. ## TLS Требования: - production default: `acme` - поддерживаемые режимы: `acme | file | self_signed_dev` - `self_signed_dev` только для dev/lab и только при явном `HY2XS_ALLOW_SELF_SIGNED_DEV=true` - корректный `server_name` / SNI на клиентах - одна понятная TLS policy - без смешивания нескольких несовместимых схем по умолчанию Инварианты: - `acme` -> только `acme` block в конфиге; - `acme` block обязан содержать `type: http|tls` из runtime env (`HY2XS_ACME_TYPE`); - `HY2XS_ACME_TYPE=dns` в production-профиле запрещён до отдельной реализации; - `HY2XS_HYSTERIA_AUTH_MODE` зафиксирован в `http` и валидируется fail-fast; - `HY2XS_HYSTERIA_OBFS_TYPE` принимает `gecko` (default) или `salamander` и валидируется fail-fast; - блок `masquerade` в baseline не задаётся: при включённой обфускации сервер и так перестаёт быть обычным HTTP/3 endpoint, поэтому masquerade не даёт выигрыша, а `404 Not Found` на обычный HTTP-трафик — ожидаемое поведение; - `ech` в baseline не включается: при включённой обфускации соединение целиком перестаёт выглядеть как обычный QUIC, поэтому ECH не даёт дополнительной выгоды (он полезен в bare-режиме); - `file` -> только `tls.cert`/`tls.key` block; - `self_signed_dev` -> только dev сценарии. ## Auth policy Для baseline выбирается одна предсказуемая auth-модель. Правила: - install flow должен оставить рабочий auth state - bootstrap auth material должен быть либо передан оператором, либо безопасно сгенерирован - дальнейшая модель выдачи доступа пользователям не фиксируется в этом пакете docs ## Bandwidth и congestion Серверная baseline policy: - `bandwidth.up = 50 mbps` - `bandwidth.down = 50 mbps` - `bandwidth.disableLossCompensation = false` - `ignoreClientBandwidth = false` - `congestion.type = bbr` - `congestion.bbrProfile = standard` Важно: - эти параметры сами по себе не исчерпывают speed policy; - корректный лимит ожидается только в паре с совместимым клиентским конфигом; - `congestion` — это **fallback** controller: он применяется, когда Brutal bandwidth не согласован сторонами. Подробнее — в [06-speed-limits-and-congestion.md](06-speed-limits-and-congestion.md). ## QUIC stateless reset ```yaml quic: disableStatelessReset: false ``` Начиная с Hysteria 2.12.1 сервер отправляет stateless reset, чтобы клиент со stale-соединением после перезапуска сервера или сна устройства переподключался сразу, а не по таймауту. В 2.12.2 появилась возможность это отключить. Для VPN-подобного применения HY2XS быстрый reconnect — плюс, поэтому механизм остаётся включённым, а значение фиксируется в конфиге явно. ## Возможности вне default-профиля HY2XS обязан **понимать** современную схему Hysteria, но не обязан включать всё подряд. Разделяются три уровня: | Возможность | Генерирует HY2XS | Читает и сохраняет | Отдельный профиль | | --- | :-: | :-: | :-: | | Gecko | да | да | — | | Salamander | fallback | да | — | | BBR / bbrProfile | да | да | — | | Loss compensation | да | да | — | | QUIC stateless reset | да | да | — | | ECH | нет | да | позже | | Mimic | нет | да | позже | | Realms | нет | да | позже | | Port hopping | нет | да | позже | | ACME DNS | нет | да | позже | | Masquerade | нет | да | позже | Причина не в качестве этих возможностей, а в том, что каждая меняет соседнюю подсистему: - **Mimic** — привилегии, eBPF/XDP, сторонний бинарник, требования к клиенту; текущий systemd-контракт намеренно запускает Hysteria под непривилегированным пользователем с `CapabilityBoundingSet=CAP_NET_BIND_SERVICE`, поэтому Mimic несовместим с ним по построению и требует отдельного security-профиля; - **Realms** — сетевая топология (STUN/hole punching вместо публичного IPv4 и own nftables); - **Port hopping** — nftables и capabilities; официально несовместим с Mimic; - **ECH** — жизненный цикл ключей и распространение конфигурации клиентам (Hysteria не генерирует ECH keypair сама); - **ACME DNS** — учётные данные провайдера и работа с секретами; - **Masquerade** — дополнительное web/proxy-поведение. Ни одна из них не должна включаться toggle'ом, который незаметно меняет systemd capabilities или топологию firewall. ## Рекомендуемые пути - `/etc/hysteria/config.yaml` - `/var/lib/hysteria/` - `/etc/hy2xs/hy2xs.env` - `/etc/hysteria/post-install.env` ## Серверные инварианты После установки должно быть верно: 1. Hysteria2 получена из official upstream по замороженному в пакете URL и SHA-256 2. фактическая версия совпадает с версией из metadata пакета и отражена в `post-install.env` 3. конфиг валиден 4. сервис стартует через systemd 5. нужный UDP-порт реально слушается 6. тестовый совместимый клиент может подключиться 7. bundled UI работает поверх актуального состояния сервера 8. `trafficStats.secret` отдельный от `JWT_SECRET` 9. IPv6 listen не используется 10. публичные клиентские endpoint/URL берутся из `HY2XS_PUBLIC_HOST` + `HY2XS_PUBLIC_PORT`, а не из `listen`/request-host 11. сгенерированная `hysteria2://` ссылка содержит фактический тип обфускации и пароль, и совместимый клиент подключается по ней напрямую 12. SNI в ссылке берётся из ACME-домена, затем из `HY2XS_DOMAIN`, затем из `HY2XS_PUBLIC_HOST`; IP-адрес как SNI не используется