Files
HY2XS_flamy/docs/architecture/03-server-hysteria2.md
T
founder cb20d8d28f fix(admin): связать отзыв учётных данных с идентичностью сессий и свести адрес control plane к одному
Отзыв секрета не сходился: `auth_id` при смене секрета оставался прежним,
поэтому сессия, установленная по отозванным учётным данным, была неотличима от
законной, и цикл учёта не имел признака, по которому её следовало завершить. У
состояния есть путь без единой неудачи — Hysteria регистрирует соединение в
Traffic Stats API только после возврата backend-auth, поэтому успешный /kick
может пройти мимо. Новое поколение credentials получает новый auth_id, kick идёт
по старому, пережившая сессия становится orphan.

Адрес Traffic Stats API имел два контракта: оркестратор принимал любой IPv4,
админка всегда шла на loopback. Валидная по всем гейтам конфигурация выключала
лимит устройств, учёт трафика и принудительное отключение разом. Адрес
зафиксирован, а расхождение файла с ним админка называет.

Состояние службы стало трёхзначным: util.Exec выбрасывал вывод systemctl при
ненулевом коде, поэтому «остановлена» и «спросить не удалось» приходили одним
значением, а доступность Traffic Stats API выводилась из него же. Журнал
Hysteria разбирается в фактическом формате upstream (time — дробное число),
страница конфигурации показывает файл вместо дефолтов UI и не возит секреты в
браузер, санитайзер выгрузки следует по YAML-якорям.

Разбор: docs/acceptance/2026-09-02-v1.0.0-rc4-preflight-findings.md
2026-09-02 23:24:01 +05:00

16 KiB
Raw Blame History

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:

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 несовместим, ломается сборка:

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:<PORT>

Порт

  • один фиксированный 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:

obfs:
  type: gecko
  gecko:
    password: "<сгенерированный пароль>"
    minPacketSize: 512
    maxPacketSize: 1200

Режим совместимости:

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.

Версия схемы конфигурации

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.

QUIC stateless reset

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 не используется
  13. trafficStats.listen слушает loopback: Traffic Stats API — внутренний control plane, и админка обращается к нему только по 127.0.0.1. Любой другой адрес разводит компоненты по разным адресам и выключает лимит устройств, учёт трафика и принудительное отключение разом
  14. Hysteria не проверяет обновления сама (HYSTERIA_DISABLE_UPDATE_CHECK=1): версией владеет versions.env -> сборка -> пакет -> оркестратор