# HY2XS Core

HY2XS logo

HY2XS — production‑установщик Hysteria2‑сервера с локальной HY2XS admin‑панелью, systemd‑юнитами, nftables‑firewall и воспроизводимой моделью release‑пакета для чистого Debian 13.

Что это · Возможности · Быстрый старт · Конфигурация · Версии · Сборка · Changelog · Лицензия

Target OS Architecture Firewall Runtime License

--- ## Что это HY2XS — это install‑only пакет для развёртывания Hysteria2‑узла на чистом Debian 13 amd64. Проект разделяет сборку и установку: разработчик один раз собирает release‑архив на Debian 13 build‑хосте, а целевой сервер получает уже готовый пакет и запускает `install.sh` без `git clone`, `go build`, `bun install`, `pnpm install` и сборки frontend‑ассетов. HY2XS подходит для сценария, где нужен один production‑сервер с Hysteria2, локальной админ‑панелью, управляемым firewall и предсказуемой конфигурацией. Проект не является универсальным server manager, update manager или панелью для любых Linux‑дистрибутивов. Ключевая идея: на target‑сервере выполняется только установка готового пакета. Вся тяжёлая сборочная часть остаётся на build‑машине. ## Возможности HY2XS release‑пакет разворачивает и настраивает: - официальный upstream‑бинарник Hysteria2: последняя стабильная версия выбирается при сборке пакета, закрепляется в его metadata и проверяется по SHA256; - HY2XS admin — встроенную админ‑панель для управления users/peers, трафиком, конфигурацией, логами и состоянием сервера; - systemd‑юнит `hysteria-server` для Hysteria2; - systemd‑юнит `hy2xs-admin` для админ‑панели; - runtime‑конфиг `/etc/hy2xs/hy2xs.env`; - Hysteria2‑конфиг `/etc/hysteria/config.yaml`; - post‑install snapshot `/etc/hysteria/post-install.env`; - bootstrap‑секреты администратора в `/etc/hy2xs/bootstrap-admin.secret`; - nftables‑правила с staged apply и rollback guard; - smoke‑проверки после установки; - команды диагностики, статуса, реконфигурации и сбора support‑bundle. HY2XS admin запускается локально по умолчанию на `127.0.0.1:8080`. Наружу публикуется Hysteria2‑транспорт, а доступ к панели выполняется через SSH local port forwarding. ## Архитектура ```mermaid graph LR operator[Оператор на Windows/Linux/macOS] -->|SSH 2323/tcp| ssh[sshd на target] operator -->|SSH tunnel 127.0.0.1:8080| ui[HY2XS admin 127.0.0.1:8080] internet[Интернет] -->|443/udp| hy2[Hysteria2 server] letsencrypt[Let's Encrypt] -->|80/tcp при ACME http| acme[ACME challenge] hy2 -->|HTTP auth localhost + machine token| ui ui -->|trafficStats localhost| hy2 hy2 --> cfg["/etc/hysteria/config.yaml"] ui --> env["/etc/hy2xs/hy2xs.env"] fw[nftables] --> ssh fw --> hy2 fw --> acme ``` В production‑профиле проект работает как IPv4‑only stack. IPv6 намеренно не входит в текущий baseline. Если у домена есть AAAA‑запись и включён строгий режим `HY2XS_DNS_AAAA_POLICY=strict`, установка останавливается до применения конфигурации. ## Поддерживаемые платформы ### Target‑сервер | Компонент | Поддерживается | | --- | --- | | ОС | Debian 13 | | Архитектура | amd64 / x86_64 | | Init system | systemd | | Firewall | nftables | | Сетевой профиль | IPv4‑only | | Hysteria2 target | linux‑amd64 | | Admin UI | локальный bind по умолчанию: `127.0.0.1:8080` | ### Build‑машина | Компонент | Поддерживается | | --- | --- | | ОС | Debian 13 | | Архитектура | amd64 / x86_64 | | Git tree | обязателен | | Dirty tree | запрещён по умолчанию | | Интернет | нужен для apt, Go, Bun, Node.js, pnpm/npm registry, GitHub и upstream Hysteria2 | Windows и macOS можно использовать для разработки, редактирования кода и работы с GitHub, но production‑архив должен собираться на Debian 13 amd64. ## Что HY2XS не делает Это важные границы проекта: - не собирает проект на target‑сервере; - не поддерживает Debian 12, Ubuntu, CentOS, Alpine и другие ОС; - не поддерживает arm64 в текущем release‑профиле; - не включает IPv6‑production baseline; - не настраивает `sshd` автоматически; - не предоставляет полноценный uninstall/update framework; - не обновляет Hysteria2 на уже работающем сервере: `reconfigure` намеренно не является Hysteria updater; - не мигрирует установки `0.x` на `1.0.0` — переход выполняется чистой установкой, см. [CHANGELOG](CHANGELOG.md); - не выполняет сложную миграцию старых неизвестных состояний сервера; - не реализует Telegram‑бота, port hopping и универсальный access‑delivery workflow; - не предназначен для установки поверх давно используемого сервера с неизвестными firewall/systemd‑правками. ## Release‑пакет Production‑сборка создаёт один архив: ```text hy2xs-install-.tar.gz ``` Внутри архива находятся: ```text hy2xs-install/ ├── install.sh ├── orchestrator/hy2xs-orchestrator ├── ui/hy2xs-admin/hy2xs-admin ├── config/hy2xs.env ├── templates/ ├── systemd/ ├── docs/ └── metadata/ ``` При запуске `install.sh` пакет проверяет `metadata/checksums.txt` и выполняет read‑only clean‑host preflight **из распакованного архива**. После этого он передаёт управление install‑only orchestrator через `exec` — и больше не делает ничего: сам `install.sh` не изменяет на сервере ни одного файла. Всю раскладку выполняет уже оркестратор: ставит себя в `/usr/local/lib/hy2xs/hy2xs-orchestrator`, создаёт symlink `/usr/local/bin/hy2xs-orchestrator`, копирует package assets в `/usr/local/lib/hy2xs/package` и продолжает установку. Это сделано ради одного свойства: у изменений сервера ровно один владелец, поэтому при любом отказе известно, что именно было создано и что откатывать. ## Сетевая модель по умолчанию | Назначение | Значение по умолчанию | | --- | --- | | SSH оператора | `2323/tcp` | | Hysteria2 | `443/udp` | | ACME HTTP challenge | `80/tcp`, если `HY2XS_TLS_MODE=acme` и `HY2XS_ACME_TYPE=http` | | HY2XS admin | `127.0.0.1:8080` | | TrafficStats Hysteria2 | `127.0.0.1:36712` | | Firewall mode | `takeover` в packaged baseline | | Hysteria2 auth | `http` через локальный HY2XS admin | | Hysteria2 obfs | `gecko` (512/1200); `salamander` доступен как режим совместимости | | Congestion fallback | `bbr`, профиль `standard` | | QUIC stateless reset | включён | Важно: `HY2XS_SSH_PORT` нужен HY2XS для nftables‑правил и проверки доступности SSH‑порта. Сам `sshd` проект не перенастраивает. SSH на `2323` и вход только по ключу нужно настроить до запуска `./install.sh`. ## Версионная политика Hysteria2 HY2XS **не привязан к конкретному номеру версии Hysteria**. > Источник по умолчанию берёт последний стабильный релиз Hysteria, доступный на момент сборки пакета. Разрешённая версия, URL артефакта и контрольная сумма замораживаются в получившемся install‑пакете. Как это работает: ```text build machine target server ───────────── ───────────── определить последнюю стабильную ─┐ взять ожидаемый 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; - если между сборкой пакета и его установкой выйдет новая версия, **содержимое установки не изменится**; - повторная установка старого пакета поставит ту же версию, что и в день сборки; - несовместимый upstream ломает сборку, а не сервер оператора. Переопределения при сборке: ```bash # по умолчанию: последняя стабильная ./tools/build/build.sh # закрепить конкретную версию HYSTERIA_VERSION_OVERRIDE=v2.12.2 ./tools/build/build.sh # офлайн-сборка по закоммиченному tools/build/hysteria-lock.env HYSTERIA_CHANNEL=pinned ./tools/build/build.sh ``` Фактически установленная версия видна в `/etc/hysteria/post-install.env` (`HY2_VERSION`), а способ её выбора — в `HY2_RESOLUTION`. Обновление Hysteria на уже работающем сервере в текущем релизе не поддерживается: `reconfigure` намеренно не является Hysteria updater. Это сохраняет immutable‑контракт развёртывания. ## Обфускация Новые установки HY2XS используют **Gecko**. Gecko помечен upstream как **experimental**. Он достраивается поверх Salamander: помимо scramble он дополнительно фрагментирует QUIC handshake на пакеты случайного размера. HY2XS использует upstream‑defaults размеров пакетов `512/1200` как проверенный production‑профиль. **Salamander остаётся поддержанным режимом совместимости.** Смена типа обфускации требует соответствующих изменений на клиенте: это изменение wire‑совместимости, а не косметическая настройка. | | Gecko | Salamander | | --- | --- | --- | | Статус upstream | experimental | stable | | Роль в HY2XS | default для новых установок | режим совместимости | | Параметр | `HY2XS_HYSTERIA_OBFS_TYPE=gecko` | `HY2XS_HYSTERIA_OBFS_TYPE=salamander` | | В клиентской ссылке | `obfs=gecko` | `obfs=salamander` | Экспериментальность upstream остаётся контролируемым риском, потому что одновременно выполняются три условия: Salamander доступен как fallback, каждая разрешённая версия проходит compatibility gate до выпуска пакета, и существующие серверы никогда не переводятся на Gecko молча. Размеры пакетов Gecko не выносятся в конфигурацию: официальная схема `hysteria2://` не умеет их передавать, поэтому нестандартные значения сделали бы клиентскую ссылку неполной. ## Быстрый старт для нового сервера Ниже приведён полный путь для оператора, который работает с Windows и ставит HY2XS на чистый Debian 13 сервер. В примерах используются условные значения: | Параметр | Пример | | --- | --- | | IP сервера | `SERVER_IP` | | Домен сервера | `vpn.example.com` | | Email для ACME | `admin@example.com` | | Release‑архив | `hy2xs-install-0.2.2.tar.gz` | | SSH‑ключ | `id_ed25519_hy2xs` | Замените эти значения на свои. ### 1. Проверьте DNS и внешние порты До установки домен должен указывать A‑записью на IPv4‑адрес target‑сервера: ```text vpn.example.com -> SERVER_IP ``` Для стандартной установки с `HY2XS_TLS_MODE=acme` и `HY2XS_ACME_TYPE=http` снаружи должны быть доступны: | Порт | Протокол | Назначение | | --- | --- | --- | | `2323` | TCP | SSH оператора | | `80` | TCP | ACME HTTP challenge | | `443` | UDP | Hysteria2 | Если у домена есть AAAA‑запись, при строгой политике `HY2XS_DNS_AAAA_POLICY=strict` установка будет остановлена, потому что текущий production‑профиль HY2XS является IPv4‑only. A‑запись должна указывать именно на этот сервер, а не просто существовать. Preflight сверяет её с публичными IPv4, назначенными интерфейсам машины, и останавливает установку при расхождении: ```text DNS IPv4 mismatch for HY2XS_PUBLIC_HOST vpn.example.com: DNS A records: 185.xxx.xxx.10 server public IPv4: 185.xxx.xxx.27 Update the DNS A record before using this server. ``` Та же проверка выполняется в `reconfigure` и `doctor`, поэтому принудительная смена IPv4 провайдером не остаётся незамеченной. Адрес сервера определяется локально, без обращения к внешним сервисам определения IP. Если сервер работает за NAT или на floating IP — это топология вне текущего baseline; осознанное решение оформляется значением `HY2XS_PUBLIC_ENDPOINT_POLICY=warn`. ### 2. Создайте SSH‑ключ на Windows Откройте PowerShell: ```powershell ssh-keygen -t ed25519 -a 100 -f "$env:USERPROFILE\.ssh\id_ed25519_hy2xs" -C "hy2xs-target-1" ``` Выведите публичный ключ: ```powershell type "$env:USERPROFILE\.ssh\id_ed25519_hy2xs.pub" ``` Скопируйте всю строку вида: ```text ssh-ed25519 AAAA... hy2xs-target-1 ``` Именно её нужно добавить на сервер в `/root/.ssh/authorized_keys`. ### 3. Войдите на сервер по паролю провайдера Первый вход обычно выполняется по временному root‑паролю от провайдера: ```powershell ssh root@SERVER_IP ``` На сервере установите базовые пакеты: ```bash apt-get update apt-get install -y openssh-server sudo ca-certificates curl tar ``` ### 4. Добавьте публичный ключ на сервер На сервере замените пример ключа на свой реальный публичный ключ: ```bash install -d -m 700 -o root -g root /root/.ssh cat > /root/.ssh/authorized_keys <<'EOF' ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY hy2xs-target-1 EOF chmod 600 /root/.ssh/authorized_keys chown -R root:root /root/.ssh ``` ### 5. Переведите SSH на порт 2323 и отключите парольный вход Не закрывайте текущую SSH‑сессию, пока не проверите новый вход по ключу. Сначала посмотрите, какие SSH‑директивы уже заданы провайдером: ```bash grep -RniE '^[[:space:]]*(Include|Port|PermitRootLogin|PasswordAuthentication|KbdInteractiveAuthentication|ChallengeResponseAuthentication|PubkeyAuthentication|AllowTcpForwarding|PermitOpen|GatewayPorts|Match)\b' \ /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true ``` OpenSSH для многих параметров использует первое найденное значение. Поэтому поздний файл вида `99-*.conf` может не перекрыть ранний provider‑конфиг. Безопаснее сначала закомментировать конфликтующие директивы, затем создать ранний `00-hy2xs-operator.conf`. ```bash for f in /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf; do [ -f "$f" ] || continue cp -a "$f" "$f.bak.$(date +%Y%m%d-%H%M%S)" sed -i -E 's/^[[:space:]]*(Port|PermitRootLogin|PasswordAuthentication|KbdInteractiveAuthentication|ChallengeResponseAuthentication|PubkeyAuthentication|AllowTcpForwarding|PermitOpen|GatewayPorts|X11Forwarding|AllowAgentForwarding|MaxAuthTries|LoginGraceTime|ClientAliveInterval|ClientAliveCountMax)[[:space:]]+/# disabled-by-hy2xs-hardening: &/' "$f" done cat > /etc/ssh/sshd_config.d/00-hy2xs-operator.conf <<'EOF' Port 2323 PubkeyAuthentication yes PasswordAuthentication no KbdInteractiveAuthentication no ChallengeResponseAuthentication no PermitRootLogin prohibit-password AllowTcpForwarding local PermitOpen 127.0.0.1:8080 localhost:8080 GatewayPorts no X11Forwarding no AllowAgentForwarding no MaxAuthTries 3 LoginGraceTime 20 ClientAliveInterval 300 ClientAliveCountMax 2 EOF sshd -t systemctl reload ssh || systemctl restart ssh ``` Проверьте effective‑конфиг: ```bash sshd -T | grep -E '^(port|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin|allowtcpforwarding|permitopen|gatewayports) ' ss -H -ltn | grep -E ':(22|2323) ' ``` Ожидаемо: ```text port 2323 permitrootlogin without-password pubkeyauthentication yes passwordauthentication no kbdinteractiveauthentication no gatewayports no allowtcpforwarding local permitopen 127.0.0.1:8080 localhost:8080 ``` В списке listen‑портов должен остаться `2323`, а `22` не должен слушаться. ### 6. Проверьте вход по ключу В новом PowerShell‑окне: ```powershell ssh -p 2323 -i "$env:USERPROFILE\.ssh\id_ed25519_hy2xs" root@SERVER_IP ``` Проверьте, что парольный вход запрещён: ```powershell ssh -p 2323 -o PreferredAuthentications=password -o PubkeyAuthentication=no root@SERVER_IP ``` Правильный результат: ```text Permission denied (publickey). ``` После этого можно дополнительно заблокировать пароль root на уровне Linux‑аккаунта: ```bash passwd -l root passwd -S root ``` Это не ломает вход по SSH‑ключу, но защищает от случайного возврата парольной SSH‑аутентификации в будущем. ### 7. Скопируйте release‑архив на сервер С Windows: ```powershell scp -P 2323 -i "$env:USERPROFILE\.ssh\id_ed25519_hy2xs" .\hy2xs-install-0.2.2.tar.gz root@SERVER_IP:/root/ ``` На сервере: ```bash cd /root tar -xzf hy2xs-install-0.2.2.tar.gz cd /root/hy2xs-install ``` ### 8. Создайте конфиг для своего сервера Не редактируйте исходный шаблон внутри пакета без необходимости. Создайте отдельный source‑config: ```bash cp config/hy2xs.env /root/hy2xs-target.env nano /root/hy2xs-target.env ``` Минимально проверьте и измените: ```env HY2XS_DOMAIN=vpn.example.com HY2XS_PUBLIC_HOST=vpn.example.com HY2XS_ACME_EMAIL=admin@example.com HY2XS_SSH_PORT=2323 HY2XS_UI_BIND_HOST=127.0.0.1 HY2XS_UI_PORT=8080 HY2XS_HYSTERIA_PORT=443 HY2XS_FIREWALL_MODE=takeover HY2XS_FIREWALL_STAGED_APPLY=true ``` Для стандартной production‑установки оставьте: ```env HY2XS_IPV6_ENABLED=false HY2XS_TLS_MODE=acme HY2XS_ACME_TYPE=http HY2XS_HYSTERIA_AUTH_MODE=http HY2XS_HYSTERIA_OBFS_TYPE=gecko HY2XS_UI_PUBLIC_ACCESS=false ``` Если нужен режим совместимости со старыми клиентами, укажите `HY2XS_HYSTERIA_OBFS_TYPE=salamander`. Подробнее — в разделе [Обфускация](#обфускация). ### 9. Запустите установку ```bash ./install.sh --config /root/hy2xs-target.env --non-interactive ``` Установка идёт в две фазы с жёсткой границей между ними. **PHASE 0 — только чтение.** До её успешного завершения на сервере не изменяется ни один файл, включая `/usr/local/lib/hy2xs`: 1. проверит, что запущено от root; 2. проверит checksums release‑пакета; 3. запустит clean‑host preflight **из распакованного архива**: платформа Debian 13 amd64, отсутствие предыдущей установки, валидность конфигурации. **PHASE 1 — применение изменений.** Её целиком выполняет оркестратор, которому `install.sh` передал управление через `exec`: 4. установит сам себя в `/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/operations/14-legacy-cleanup.md](docs/operations/14-legacy-cleanup.md). ### 10. Получите bootstrap‑пароль админки ```bash cat /etc/hy2xs/bootstrap-admin.secret ``` Файл содержит: ```env ADMIN_USER=... ADMIN_INITIAL_PASSWORD=... ADMIN_CON_PASS=... ``` `ADMIN_INITIAL_PASSWORD` нужен для первого входа в HY2XS admin. После первого входа смените пароль через интерфейс. `ADMIN_CON_PASS` — bootstrap‑секрет Hysteria2 peer/auth слоя. Не публикуйте этот файл и не отправляйте его в issue/log без редактирования. ### 11. Откройте HY2XS admin через SSH‑туннель На локальной машине: ```powershell ssh -p 2323 ` -i "$env:USERPROFILE\.ssh\id_ed25519_hy2xs" ` -N ` -L 127.0.0.1:8080:127.0.0.1:8080 ` root@SERVER_IP ``` Пример для Linux/macOS (одной строкой): ```bash ssh -p 2323 -i ~/.ssh/id_ed25519_hy2xs -N -L 8080:127.0.0.1:8080 root@SERVER_IP ``` После этого откройте в браузере: ```text http://127.0.0.1:8080/ ``` Если порт `8080` на локальной машине занят, можно пробросить другой локальный порт: ```powershell ssh -p 2323 ` -i "$env:USERPROFILE\.ssh\id_ed25519_hy2xs" ` -N ` -L 127.0.0.1:18080:127.0.0.1:8080 ` root@SERVER_IP ``` И открыть: ```text http://127.0.0.1:18080/ ``` ### 12. Проверьте установку На сервере: ```bash systemctl status hysteria-server --no-pager systemctl status hy2xs-admin --no-pager ss -H -lun | grep ':443' ss -H -ltn | grep ':8080' ss -H -ltn | grep ':2323' nft list ruleset cat /var/lib/hy2xs/install-state.json ``` Выполните doctor‑проверку: ```bash hy2xs-orchestrator doctor \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env ``` Проверьте сводный статус: ```bash hy2xs-orchestrator status \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env ``` ## Конфигурация `hy2xs.env` После установки основным редактируемым файлом является: ```text /etc/hy2xs/hy2xs.env ``` Менять runtime‑параметры нужно через этот файл и затем применять `reconfigure`. Редактирование `post-install.env` не меняет runtime‑состояние. ### Основные параметры | Переменная | Назначение | Значение по умолчанию в packaged baseline | | --- | --- | --- | | `HY2XS_CONFIG_SCHEMA_VERSION` | Версия схемы конфигурации HY2XS. Конфигурация другой схемы отклоняется fail‑fast | `2` | | `HY2XS_IPV6_ENABLED` | IPv6‑режим. В production baseline должен быть `false` | `false` | | `HY2XS_DOMAIN` | Домен для ACME и deploy‑профиля | `fi.api.withen.pro` | | `HY2XS_DNS_AAAA_POLICY` | Поведение при наличии AAAA‑записи: `strict`, `warn`, `off` | `strict` | | `HY2XS_PUBLIC_ENDPOINT_POLICY` | Строгость проверки того, что A‑записи публичного endpoint ведут на IPv4 этого сервера: `strict`, `warn`, `off` | `strict` | | `HY2XS_PUBLIC_HOST` | Публичный host без схемы, порта и path | `fi.api.withen.pro` | | `HY2XS_PUBLIC_PORT` | Публичный порт Hysteria2 endpoint | `443` | | `HY2XS_SSH_PORT` | SSH‑порт, который будет разрешён firewall‑правилами | `2323` | | `HY2XS_FIREWALL_MODE` | Режим управления nftables: `managed`, `takeover`, `external`, `off` | `takeover` | | `HY2XS_FIREWALL_STAGED_APPLY` | Включает staged apply и rollback guard firewall | `true` | | `HY2XS_UI_BIND_HOST` | IPv4 bind HY2XS admin | `127.0.0.1` | | `HY2XS_UI_PUBLIC_ACCESS` | Флаг публичного UI‑доступа. В baseline оставляйте `false` | `false` | | `HY2XS_UI_PORT` | Порт HY2XS admin | `8080` | | `HY2XS_ADMIN_USER` | Bootstrap‑логин администратора: 6-32 символа из набора `a-z A-Z 0-9 !@#$%^&*()_+,-./:;<=`. Значение вне контракта роняет установку — панель его не приняла бы на форме входа | `hy2xsadmin` | | `HY2XS_ADMIN_INITIAL_PASSWORD` | Bootstrap‑пароль администратора: 6-64 символа Unicode **и** не более 72 байт в UTF‑8 (предел bcrypt); валидный UTF‑8 в документированном домене `EnvironmentFile=` (в частности, без U+FEFF), без управляющих символов; пробелы по краям — часть пароля, поэтому такое значение записывается в двойных кавычках; `__GENERATE__` генерируется при install | `__GENERATE__` | | `HY2XS_ADMIN_CON_PASS` | Bootstrap‑секрет peer/auth слоя; `__GENERATE__` генерируется при install | `__GENERATE__` | | `HY2XS_FORCE_PASSWORD_CHANGE` | Принудительная смена пароля. UX‑flow пока не включён в production baseline | `false` | | `HY2XS_ALLOW_SELF_SIGNED_DEV` | Разрешает `self_signed_dev` TLS‑режим | `false` | | `HY2XS_TLS_MODE` | TLS‑режим: `acme`, `file`, `self_signed_dev` | `acme` | | `HY2XS_ACME_TYPE` | ACME challenge: `http`, `tls`, `dns`; `dns` пока не поддержан production‑профилем | `http` | | `HY2XS_ACME_EMAIL` | Email для ACME | `admin@withen.pro` | | `HY2XS_TLS_CERT_PATH` | Путь к cert при `file`/`self_signed_dev` | `/etc/hysteria/server.crt` | | `HY2XS_TLS_KEY_PATH` | Путь к key при `file`/`self_signed_dev` | `/etc/hysteria/server.key` | | `HY2XS_HYSTERIA_BIND_HOST` | Bind Hysteria2. В production profile фиксируется на `0.0.0.0` | `0.0.0.0` | | `HY2XS_HYSTERIA_PORT` | UDP‑порт Hysteria2 | `443` | | `HY2XS_HYSTERIA_AUTH_MODE` | Auth mode Hysteria2. Фиксированное значение production‑профиля | `http` | | `HY2XS_HYSTERIA_TRAFFIC_STATS_HOST` | Host trafficStats API. Фиксированное значение production‑профиля: админка обращается к нему только по loopback, поэтому любой другой адрес выключает лимит устройств, учёт трафика и принудительное отключение | `127.0.0.1` | | `HY2XS_HYSTERIA_TRAFFIC_STATS_PORT` | Порт trafficStats API | `36712` | | `HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET` | Secret для trafficStats и machine auth | `__GENERATE__` | | `HY2XS_HYSTERIA_OBFS_TYPE` | Тип обфускации: `gecko` или `salamander`. Смена меняет wire‑совместимость | `gecko` | | `HY2XS_HYSTERIA_OBFS_PASSWORD` | Пароль обфускации; `__GENERATE__` генерируется при install | `__GENERATE__` | | `HY2XS_HYSTERIA_BANDWIDTH_UP` | Hysteria2 upstream bandwidth | `50 mbps` | | `HY2XS_HYSTERIA_BANDWIDTH_DOWN` | Hysteria2 downstream bandwidth | `50 mbps` | | `HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH` | Игнорировать bandwidth клиента | `false` | | `HY2XS_HYSTERIA_CONFIG_PATH` | Путь Hysteria2 config. В production baseline фиксирован | `/etc/hysteria/config.yaml` | | `HY2XS_INSTALL_DIR` | Каталог установки HY2XS admin | `/opt/hy2xs-admin` | | `HY2XS_DATA_DIR` | Data‑каталог HY2XS admin | `/var/lib/hy2xs-admin` | | `HY2XS_LOG_DIR` | Log‑каталог HY2XS | `/var/log/hy2xs` | ### Режимы firewall | Режим | Поведение | | --- | --- | | `managed` | HY2XS управляет nftables, но останавливается при обнаружении чужого `/etc/nftables.conf` | | `takeover` | HY2XS явно берёт управление nftables на себя | | `external` | HY2XS не меняет nftables; оператор сам отвечает за firewall | | `off` | Firewall‑слой HY2XS отключён | При `managed` и `takeover` генерируется nftables‑конфигурация с default drop policy, разрешением loopback, established/related, SSH‑порта, ACME challenge‑порта, Hysteria2 UDP‑порта и ICMP echo‑request. ### Защита от потери доступа при смене firewall При `HY2XS_FIREWALL_STAGED_APPLY=true` (значение по умолчанию) перед применением новых правил HY2XS взводит rollback guard — транзиентный systemd‑юнит с окном 45 секунд. Если операция не снимет его вовремя, guard вернёт прежний firewall, и SSH останется доступным. Таймеру явно задаётся `AccuracySec=1s`, поэтому «45 секунд» — это реальный контракт, а не приблизительный: по умолчанию `systemd.timer` разрешает себе сработать в окне `[цель; цель + AccuracySec]`, где `AccuracySec` — одна минута, и обещанное окно превращалось бы в 45–105 секунд. Вторым свойством задаётся `RemainAfterElapse=no`: отработавший таймер обязан выгрузиться, иначе он навсегда блокировал бы следующую операцию (см. [«Одна операция за раз»](#одна-операция-за-раз)). Окно намеренно короткое и **не** обязано покрывать smoke‑checks: на медленном сервере они идут дольше. Вместо этого guard оставляет за собой факт срабатывания в `/run/hy2xs/rollback//auto-rollback-fired`, и операция не имеет права объявить себя успешной, если этот файл появился, — сервер в такой момент работает на прежнем firewall, а не на том, который она сгенерировала. Установка завершится отказом с `phase: firewall_guard_fired`, и её нужно повторить после устранения причины медленного прохода. Дополнительно smoke сверяет, что действующий firewall — именно тот, который сгенерирован для текущей конфигурации: разбора `/etc/nftables.conf` для этого недостаточно, потому что прежний ruleset тоже валиден. ## Одна операция за раз `install`, `reconfigure`, `repair` и `doctor` сериализованы эксклюзивным замком `/run/lock/hy2xs-orchestrator.lock`. Вторая операция отказывает сразу и **до первой мутации**: ```text another HY2XS operation is already in progress: reconfigure (pid 4242, started at …) ``` Это не перестраховка: конфиги, unit‑файлы, `/etc/nftables.conf` и маркер установки — общие, и две одновременные операции записывают их поверх друг друга, после чего откат одной «восстанавливает» состояние поверх изменений другой. `status` и `diagnostics collect` замок не берут — они нужны в том числе во время долгой операции, — но сообщают о ней в своём выводе. Замок снимается сам при любом завершении держателя, включая `Ctrl+C`, SIGTERM и обрыв SSH. Если процесс был убит `kill -9`, следующая операция обнаружит мёртвого держателя и переиспользует замок самостоятельно. Замка при этом недостаточно: он действует, пока жив процесс‑держатель, а rollback guard firewall — отдельный объект systemd, который свой процесс переживает. Аварийно умершая операция оставляет guard вооружённым, и он способен вернуть прежний firewall уже посреди следующей операции. Поэтому условие старта — не «предыдущая операция мертва», а «у неё не осталось исполнителей, способных изменить систему»: ```text previous HY2XS operation is no longer running, but its firewall rollback guard is still armed: hy2xs-fw-rollback-.timer (active/waiting) ``` Ждать в этом случае нужно не дольше 45–46 секунд с момента применения firewall. Покоем считаются ровно два состояния юнита — `inactive` и `failed`: отработавший guard больше ничего не сделает, а отказ по `failed` заблокировал бы `repair`, которым чинят последствия. Всё остальное, включая незнакомые барьеру состояния systemd, операцию запрещает. Отдельный случай — когда состояние guard'а вообще не удалось выяснить: ```text unable to verify firewall rollback guard state; systemd query failed, refusing to start a lifecycle operation ``` Здесь ждать нечего: отсутствие ответа systemd — это отсутствие доказательства, а не доказательство покоя, и разбираться нужно с systemd. Барьер обязан **доказать**, что у предыдущей операции не осталось исполнителей, способных изменить firewall; молчаливое «наверное, всё в порядке» однажды означало бы срабатывание старого таймера поверх новой операции. ## Реконфигурация После изменения `/etc/hy2xs/hy2xs.env` сначала выполните dry‑run: ```bash hy2xs-orchestrator reconfigure \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env \ --dry-run ``` Если ошибок нет, примените изменения: ```bash hy2xs-orchestrator reconfigure \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env \ --apply ``` `reconfigure --apply` выполняет backup, генерирует конфиги, обновляет systemd‑юниты, применяет firewall, выполняет smoke‑checks и при ошибке пытается откатить изменённое состояние. Важно: `HY2XS_ADMIN_INITIAL_PASSWORD` и `HY2XS_ADMIN_CON_PASS` являются install‑only bootstrap‑полями. Их изменение в `/etc/hy2xs/hy2xs.env` после первичной установки не ротирует уже созданные credentials в SQLite автоматически. ## Команды управления | Команда | Назначение | | --- | --- | | `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 --allow-partial-state` | Довести до конца незавершённую установку **текущего поколения** | | `hy2xs-orchestrator diagnostics collect` | Собрать diagnostic bundle в `/var/lib/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 hy2xs-orchestrator diagnostics collect \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env ``` Бандл не содержит сырых промежуточных копий конфигурации или журналов: редакция выполняется в памяти до записи. Файл с повреждённым UTF-8 не декодируется с заменой и не попадает в архив; вместо него записывается безопасная причина пропуска. Архив создаётся в `/var/lib/hy2xs/diagnostics` с режимом `0600`. Этот каталог принадлежит `root:root`, имеет режим `0700` и отделён от `HY2XS_LOG_DIR`, которым владеет сервисный пользователь `hy2xs-admin`. Оркестратор отказывает, если каталог подменён symlink, имеет другого владельца или ослабленные права. Незавершённый staging-каталог после упаковки удаляется. ## Проверка безопасности после установки Минимальный набор проверок: ```bash # SSH слушает только ожидаемый порт ss -H -ltn | grep -E ':(22|2323) ' # password auth отключён sshd -T | grep -E '^(port|permitrootlogin|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|allowtcpforwarding|permitopen) ' # UI не слушает публичный 0.0.0.0 ss -H -ltn | grep ':8080' # Hysteria2 слушает UDP-порт ss -H -lun | grep ':443' # nftables-конфиг валиден nft -c -f /etc/nftables.conf # сервисы активны systemctl is-active hysteria-server hy2xs-admin ``` Ожидаемые SSH‑значения: ```text port 2323 permitrootlogin without-password pubkeyauthentication yes passwordauthentication no kbdinteractiveauthentication no allowtcpforwarding local permitopen 127.0.0.1:8080 localhost:8080 ``` ## Troubleshooting ### Установка отказывается: обнаружена предыдущая установка ```text [hy2xs] ERROR: На сервере обнаружена предыдущая или посторонняя установка. HY2XS v1 не поддерживает установку поверх и не мигрирует состояние 0.x. Ни один файл на сервере не изменён. ``` Это ожидаемое поведение, а не сбой. Отказ происходит в PHASE 0, до любой мутации: сервер остался в том состоянии, в котором был. Что делать: 1. сохраните нужные данные (база пиров, конфиг) — см. [docs/operations/14-legacy-cleanup.md](docs/operations/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. Решения: 1. удалить AAAA‑запись у домена; 2. либо временно установить `HY2XS_DNS_AAAA_POLICY=warn`, если оператор осознанно принимает риск клиентских IPv6‑маршрутов вне текущего baseline. ### `DNS IPv4 mismatch`: DNS ведёт не на этот сервер Причина: A‑запись публичного endpoint указывает на адрес, которого нет среди публичных IPv4 этого сервера. Типичный случай — провайдер принудительно сменил IP, а DNS остался старым: сервисы на машине живы, но клиентская ссылка отправляет людей на другой адрес. Решения: 1. сверить фактический адрес сервера и обновить A‑запись: ```bash ip -4 addr show scope global ``` 2. дождаться истечения TTL и повторить `hy2xs-orchestrator doctor`; 3. если в строке `server public IPv4:` пусто — на интерфейсах нет публичного IPv4 (сервер за NAT). Это вне baseline; при осознанном решении установите `HY2XS_PUBLIC_ENDPOINT_POLICY=warn`. Если A‑записей несколько и среди них есть посторонняя, проверка тоже отказывает: HY2XS — single‑host профиль, и второй backend за тем же именем означает, что часть клиентов попадёт не на этот сервер. ### Установка падает на проверке SSH‑порта HY2XS firewall‑слой проверяет, что порт из `HY2XS_SSH_PORT` уже слушается. Если указано `2323`, но `sshd` продолжает слушать только `22`, установка остановится. Проверьте: ```bash ss -H -ltn | grep -E ':(22|2323) ' sshd -T | grep '^port ' ``` ### Вход по паролю всё ещё работает Проверьте ранние provider‑конфиги: ```bash grep -RniE '^[[:space:]]*(PermitRootLogin|PasswordAuthentication|KbdInteractiveAuthentication|PubkeyAuthentication)\b' \ /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf ``` Если есть ранний файл с `PasswordAuthentication yes`, он может применяться раньше вашего hardening‑файла. Закомментируйте конфликтующие директивы и создайте `00-hy2xs-operator.conf`, как показано в quick start. ### UI не открывается в браузере Проверьте, что SSH‑туннель запущен и сервис слушает localhost: ```bash systemctl status hy2xs-admin --no-pager ss -H -ltn | grep ':8080' curl -sS http://127.0.0.1:8080/healthz ``` На локальной машине проверьте, что порт не занят другим приложением. При необходимости используйте локальный порт `18080` вместо `8080`. ### Нужно отправить логи без секретов Соберите диагностику: ```bash hy2xs-orchestrator diagnostics collect \ --package-dir /usr/local/lib/hy2xs/package \ --config /etc/hy2xs/hy2xs.env ``` Перед публикацией отдельных env/yaml файлов используйте redaction: ```bash hy2xs-orchestrator redact-config \ --config /etc/hy2xs/hy2xs.env \ --out /root/hy2xs.env.redacted \ --format env ``` ## Сборка release‑пакета Обычному пользователю не нужно собирать проект из исходников. Этот раздел нужен maintainer’у, который готовит release‑архив. ### Перед работой: сверьте среду с контрактом ```bash ./tools/dev/doctor.sh # Linux/macOS .\tools\dev\doctor.ps1 # Windows (PowerShell 7+) ``` ```text HY2XS development environment contract: versions.env (HY2XS 1.0.0, release line 1) Go: required: 1.26.8 found: 1.25.6 FAIL — локальный Go собирает не ту stdlib, что уедет в релиз; поставьте 1.26.8 Node: required: 24.20.0 found: 24.20.0 OK ``` Скрипт ничего не устанавливает и не меняет — он отвечает на один вопрос: совпадает ли эта машина с контрактом сборки. Раньше `versions.env` был контрактом только для сборки: она скачивает Go, Node и Bun ровно тех версий, что там записаны, сверяя контрольные суммы, а машина разработчика не проверялась никак. Расхождение обнаруживалось на Debian, внутри release‑сборки, и выглядело как «у меня работало». Расхождение не гипотетическое. Директива `go` в `apps/go.mod` — это языковой baseline модуля, а не выбор компилятора, поэтому локальный Go другой минорной линии собирал проект успешно, пока релизный бинарь компилировался на 1.26.7 и наследовал **её** stdlib: проверялся не тот код, который уезжает в production. Поэтому `go.mod` теперь объявляет `toolchain` явно, а `doctor` показывает расхождение до сборки, а не после. Сборка поддерживается на Debian 13 amd64 из чистого git work tree. ```bash apt-get update apt-get install -y git ca-certificates curl unzip tar xz-utils build-essential pkg-config bash coreutils findutils grep sed gawk openssl git clone cd git status --short ``` Подготовьте build env: Версии и контрольные суммы toolchain **не задаются переменными окружения**: они объявлены в корневом [`versions.env`](versions.env), и сборка берёт их оттуда. Раньше их приходилось передавать снаружи, из-за чего воспроизводимая сборка в чистой Debian‑среде требовала предварительного знания четырёх SHA‑256. ```bash 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 # Опционально: снимает anonymous rate limit при разрешении upstream-релиза. export GITHUB_TOKEN= ``` `PACKAGE_VERSION` тоже приходит из `versions.env` (`HY2XS_VERSION`). Шаг `verify_versions_contract` роняет сборку, если версия продукта, схема конфигурации, целевая платформа или `packageManager` в `package.json` разошлись с контрактом. Запустите сборку: ```bash ./tools/build/build.sh ``` Сборка последовательно: 1. проверяет контракт `versions.env` (`verify_versions_contract`); 2. прогоняет тесты и типы оркестратора (`bun test`, `tsc --noEmit`); 3. прогоняет контрактные тесты панели (спрайт иконок, словари локализации, коды ошибок, атрибуция); 4. определяет последнюю стабильную версию Hysteria, берёт ожидаемый SHA‑256 из upstream `hashes.txt` и сверяет с ним скачанный артефакт; 5. проходит compatibility gate: реальный бинарник Hysteria должен принять канонический конфиг HY2XS для Gecko и для Salamander; 6. собирает orchestrator, frontend и backend, проставляя версию админки из контракта; 7. прогоняет `go vet` и `go test` для HY2XS admin; 8. проверяет граф зависимостей на известные уязвимости (`govulncheck ./...` и `pnpm audit` по всему lock‑графу); 9. формирует архив и прогоняет acceptance‑проверки. Любой сбой на шагах 1–8 останавливает сборку до создания пакета. Тесты и типы (шаги 2, 3 и 7) — такой же обязательный гейт, как проверка зависимостей: переменной, которая их отключает, не существует. Готовый пакет объявляет об этом полем `tests_gate=true` в `metadata/package.env`, и это утверждение опирается на фактический прогон, а не на намерение. Для локальной работы обходить нечего: `bun test`, `bun x tsc --noEmit`, `go vet ./...`, `go test ./...` и `bun test tools/test/frontend-sprite.test.ts tools/test/frontend-contract.test.ts` запускаются напрямую и tarball не создают. Переменные, управляющие выбором версии Hysteria: | Переменная | По умолчанию | Назначение | | --- | --- | --- | | `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‑файл | Проверка зависимостей переменными не управляется: у неё **нет аварийного выхода**. Релизный артефакт HY2XS невозможно собрать с непройденным гейтом, и поле `dependency_security_gate` в `metadata/package.env` принимает единственное значение `true`. Раньше здесь были описаны два способа выпустить релиз, зная об уязвимости. Ими они не являлись: финальная приёмка архива требует буквально `dependency_security_gate=true`, поэтому сборка с любым из них проходила весь цикл и падала на последнем шаге. Документированная операция, которую продукт сам же запрещает, — хуже отсутствующей. `pnpm audit` при этом проверяет **весь** lock‑граф frontend, а не только production‑подграф. Причина в том, что build tooling исполняется на build‑машине и порождает production‑бандл: уязвимость в `vite`/`rollup` уезжает в артефакт, хотя сами они на сервер не копируются. Ровно такой случай и был найден — DOM clobbering в Rollup затрагивал генерируемый бандл, а проверка по одному production‑подграфу его не показывала. Если advisory вышло в неудачный момент, чинится это обновлением графа (`apps/go.sum`, `apps/frontend/pnpm-lock.yaml`) или версии toolchain в `versions.env`. Для локальной работы обходить нечего: `go test ./...`, `govulncheck ./...` и `pnpm audit` запускаются напрямую и tarball не создают. Проверка versions‑контракта и проверка зависимостей отвечают на разные вопросы. Первая следит, что зафиксированные версии **согласованы между собой**; вторая — что про эти версии **не стало известно плохого**. Зафиксированный граф не стареет только на бумаге: advisory по нему выходят и после фиксации, а сборка релиза — единственный момент, когда это расхождение ловится дёшево. Полный E2E с реальным клиентом Hysteria запускается отдельно (нужен Go: ссылка берётся из production‑генератора, а не из отдельной реализации внутри теста): ```bash HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh ``` Результат: ```text dist/hy2xs-install-.tar.gz ``` Проверьте архив: ```bash ls -lh dist/hy2xs-install-*.tar.gz sha256sum dist/hy2xs-install-*.tar.gz tar -tzf dist/hy2xs-install-1.0.0.tar.gz | grep -E \ '^(hy2xs-install/install.sh|hy2xs-install/orchestrator/hy2xs-orchestrator|hy2xs-install/ui/hy2xs-admin/hy2xs-admin|hy2xs-install/metadata/checksums.txt)$' ``` Подробная документация по сборочному слою находится в [`tools/build/README.md`](tools/build/README.md). ## Структура репозитория ```text . ├── apps/ # HY2XS admin: Go backend + Vue/Vite frontend ├── orchestrator/ # install-only orchestrator на Bun + TypeScript ├── package/ # skeleton будущего install package ├── tools/build/ # production builder и packaging pipeline ├── tools/dev/ # doctor: сверка среды разработки с versions.env ├── tools/test/ # e2e с реальным клиентом Hysteria и контракты панели ├── tools/legacy/ # purge-v0.sh: очистка сервера от предыдущего поколения ├── docs/ # документация, разложенная по слоям │ ├── architecture/ # baseline-модель и рамки │ ├── build/ # builder layer и состав пакета │ ├── runtime/ # оркестратор, systemd, post-install │ ├── admin/ # HY2XS admin и контракты панели │ ├── operations/ # runbook, разбор отказов, очистка 0.x │ ├── testing/ # набор проверок по слоям │ └── acceptance/ # отчёты о фактических прогонах приёмки ├── versions.env # контракт продукта, платформы и toolchain ├── CHANGELOG.md ├── README.md └── LICENSE ``` Каталог `dist/` создаётся builder’ом и не должен храниться в git. Точка входа в документацию — [docs/README.md](docs/README.md). ## Для кого этот проект HY2XS рассчитан на операторов, которым нужен воспроизводимый способ поставить Hysteria2‑сервер с локальной панелью управления, не собирая проект на production‑сервере и не открывая admin UI наружу. Проект особенно полезен, если важны: - строгая target‑платформа; - установка из одного release‑архива; - локальный UI через SSH‑туннель; - systemd/nftables baseline; - проверяемая установка со smoke‑checks; - понятная диагностика при сбоях. ## Лицензия HY2XS распространяется на условиях **GNU Affero General Public License v3.0 only** (`AGPL-3.0-only`). Полный текст лицензии находится в [`LICENSE`](LICENSE). Сторонние библиотеки и зависимости сохраняют собственные лицензии. ---

Разработано во Flamy Studio.