Hysteria 2 каскад: различия между версиями
Владимир (обсуждение | вклад) Новая страница: « = Hysteria 2: каскад РФ → EU — полная инструкция = Инструкция охватывает полный цикл: поднять **сервер Hysteria 2** на EU-VPS, настроить на нём **релей sing-box** на РФ-VPS, сгенерировать ссылки для клиентов и выполнить обслуживание серверов. Все команды выполняются **по одно...» |
Владимир (обсуждение | вклад) |
||
| (не показаны 2 промежуточные версии этого же участника) | |||
| Строка 1: | Строка 1: | ||
= Hysteria2: установка каскадной цепочки Entry Node → Exit Node → WARP = | |||
= | == Что такое Hysteria2 и зачем он нужен == | ||
=== Проблема: ограничения традиционных прокси-протоколов === | |||
== | Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), применяемые интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса назначения, но и '''характер самого трафика'''. Даже зашифрованное соединение можно идентифицировать — по поведению, по паузам между пакетами, по размерам передаваемых блоков данных, по TLS-fingerprint. | ||
Большинство прокси-протоколов работают поверх '''TCP'''. Это означает: | |||
* Все потери пакетов обрабатываются дважды — сначала на уровне транспорта, затем внутри туннеля (head-of-line blocking). | |||
* При нестабильном канале (мобильная сеть, спутник, VPN на слабом железе) скорость резко падает. | |||
* TCP-fingerprint легче распознаётся и блокируется. | |||
Hysteria2 решает эту проблему фундаментально иначе: он работает поверх '''QUIC''' — транспортного протокола на базе UDP, разработанного в Google и стандартизированного в RFC 9000. | |||
=== Решение: QUIC/UDP с маскировкой под HTTPS === | |||
Hysteria2 строит зашифрованный туннель поверх UDP, используя QUIC: | |||
* '''Нет head-of-line blocking:''' потеря одного пакета не блокирует остальные потоки. | |||
* '''Быстрое восстановление соединения:''' QUIC переподключается за 0-RTT (без дополнительного round-trip). | |||
* '''Маскировка под HTTPS:''' снаружи трафик Hysteria2 выглядит как обычный QUIC/HTTP3 браузерный трафик — сервер «притворяется» обычным HTTPS-сайтом и возвращает реальный веб-контент при неавторизованных запросах. | |||
* '''Congestion control:''' поддерживает несколько алгоритмов управления перегрузкой, включая BBR. | |||
{| class="wikitable" | |||
|- | |||
! Протокол !! Транспорт !! Как выглядит для DPI !! Устойчивость к блокировке !! Скорость при потерях | |||
|- | |||
|| Обычный VPN (WireGuard, OpenVPN) || UDP / TCP || Характерный шаблон || Низкая — fingerprint легко детектируется || Высокая (WG) / Низкая (OpenVPN) | |||
|- | |||
|| Shadowsocks || TCP || Случайный зашифрованный поток || Средняя — выявляется по энтропии || Средняя | |||
|- | |||
|| VLESS + REALITY || TCP (TLS) || TLS с реальным сертификатом стороннего сайта || Высокая || Средняя | |||
|- | |||
|| NaïveProxy || TCP (HTTP/2 TLS) || Неотличим от Chrome HTTPS || Очень высокая || Средняя (TCP Head-of-Line) | |||
|- | |||
|| '''Hysteria2''' || '''UDP (QUIC/TLS)''' || '''QUIC/HTTP3, маскировка под HTTPS''' || '''Очень высокая''' || '''Высокая — устойчив к потерям''' | |||
|} | |||
=== Как работает Hysteria2 технически === | |||
Hysteria2 использует протокол QUIC в качестве транспорта: | |||
# Клиент устанавливает QUIC-соединение с сервером — это TLS 1.3 поверх UDP. | |||
# Внутри QUIC-соединения открываются независимые потоки (streams) для каждого проксируемого соединения. | |||
# Сервер обрабатывает входящий трафик: авторизует клиента (по паролю или через внешний скрипт), затем перенаправляет трафик через указанный outbound. | |||
# Если к серверу обращаются без правильного пароля — сервер ведёт себя как обычный HTTPS-сайт ('''masquerade''') и возвращает реальный контент с заданного URL. | |||
Особенности аутентификации в данной схеме: | |||
* '''EU сервер:''' использует одиночный пароль — только для входящих соединений от RU клиента (не клиентских устройств). | |||
* '''RU сервер:''' использует '''command-аутентификацию''' — при каждом подключении вызывается внешний bash-скрипт, который проверяет пароль по файлу <code>users.txt</code>. Это позволяет добавлять и удалять пользователей '''без перезапуска сервера'''. | |||
=== Каскадная цепочка: зачем два сервера === | |||
В данном решении используется '''каскад из двух серверов''': Entry Node (ближайший к клиентам сервер-вход) и Exit Node (сервер-выход в интернет). Это архитектурное решение принято намеренно по нескольким причинам: | |||
* '''Снижение риска для клиента:''' клиентские устройства подключаются только к ближайшему серверу. Соединение с соседним узлом менее заметно, чем прямое соединение с зарубежным сервером. | |||
* '''Раздельная аутентификация:''' пароль «клиент → Entry Node» знают только конечные пользователи. Пароль «Entry Node → Exit Node» знают только серверы. Компрометация клиентского пароля не раскрывает пароль соединения между серверами. | |||
* '''Анонимизация выхода:''' Exit Node передаёт трафик через Cloudflare WARP. Конечные сайты видят IP Cloudflare, а не IP ваших серверов. | |||
* '''Параллельные независимые каналы:''' Hysteria2 работает рядом с NaïveProxy и 3x-ui на тех же серверах, не конфликтуя с ними. Падение одного канала не блокирует другие. | |||
---- | |||
== Схема конкретного решения == | |||
=== Компоненты инфраструктуры === | |||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| | ! Компонент !! Роль !! Адрес / Домен !! ОС | ||
|Ubuntu 24.04 | |- | ||
| | || Клиентские устройства || Конечные пользователи || — || iOS, Android, Windows, macOS, Linux | ||
|- | |||
|| OpenWrt роутер || Прозрачный обход для всей домашней сети || — || OpenWrt 24.10 (podkop нативно поддерживает hy2 URI) | |||
|- | |||
|| RU сервер ('''Entry Node''') || Точка входа клиентов || <code>YOUR_RU_SERVER_IP</code> / <code>your-hy2-ru-domain.example.com</code> || Ubuntu 24.04 | |||
|- | |||
|| EU сервер ('''Exit Node''') || Точка выхода в интернет || <code>YOUR_EU_SERVER_IP</code> / <code>your-hy2-eu-domain.example.com</code> || Ubuntu 24.04 | |||
|- | |- | ||
|IP | || Cloudflare WARP || Финальный выход в интернет || IP Cloudflare || — | ||
| | |} | ||
| | |||
=== Полная схема прохождения трафика === | |||
[[File:hysteria2-chain-traffic-flow.png|center|768x768px|Схема трафика Hysteria2: Клиенты → RU Entry Node → EU Exit Node → WARP → Интернет]] | |||
=== Протоколы на каждом участке цепочки === | |||
{| class="wikitable" | |||
|- | |- | ||
! Участок !! Протокол !! Шифрование !! Что видит наблюдатель | |||
|- | |- | ||
| | || Устройство → Роутер || Обычный TCP/UDP || Нет (локальная сеть) || Локальный трафик | ||
| | |||
| | |||
|- | |- | ||
| | || Роутер / ПК → RU сервер || QUIC / UDP (Hysteria2) || TLS 1.3 (Let's Encrypt) || HTTPS/HTTP3 трафик к обычному сайту | ||
| | |||
|- | |- | ||
| | || RU клиент → EU сервер || QUIC / UDP (Hysteria2) || TLS 1.3 (Let's Encrypt) || HTTPS/HTTP3 трафик к обычному сайту | ||
| | |||
|- | |- | ||
| | || EU сервер → 3x-ui inbound || SOCKS5 (localhost) || — (только на EU сервере) || Только локально | ||
| | |||
| | |||
|- | |- | ||
| | || EU 3x-ui → WARP || WireGuard (gVisor TUN) || WireGuard || Зашифрованный WireGuard к Cloudflare | ||
| | |||
| | |||
|- | |- | ||
|IP | || WARP → Интернет || Обычный TCP/UDP || — || IP Cloudflare | ||
|} | |||
| | |||
=== Дополнительные каналы (параллельно Hysteria2) === | |||
Hysteria2 работает '''независимо''' от NaïveProxy и 3x-ui. Все три канала активны одновременно: | |||
{| class="wikitable" | |||
|- | |- | ||
! Канал !! Протокол !! Маршрут !! Статус | |||
|- | |- | ||
| | || 3x-ui каскад (основной) || VLESS / REALITY || Клиенты → RU 3x-ui → EU 3x-ui → Интернет || ✅ Активен | ||
| | |||
| | |||
|- | |- | ||
| | || NaïveProxy каскад || HTTP/2 naïve || Роутер/ПК → RU Caddy → EU Caddy → WARP || ✅ Активен | ||
| | |||
| | |||
|- | |- | ||
| | || '''Hysteria2 каскад (этот гайд)''' || '''QUIC/UDP hy2''' || '''Клиенты → RU hy2 → EU hy2 → WARP''' || '''✅ Активен''' | ||
| | |||
| | |||
|- | |- | ||
| | || MTProto (Telegram) || MTProto || Клиенты Telegram → EU :8443, :2443 || ✅ Активен | ||
| | |||
| | |||
|} | |} | ||
---- | ---- | ||
= | == Предварительные требования == | ||
=== Инфраструктура === | |||
{| class="wikitable" | |||
|- | |||
! Компонент !! Требования | |||
|- | |||
|| EU сервер || Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера | |||
|- | |||
|| RU сервер || Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера | |||
|- | |||
|| 3x-ui на EU сервере || Уже установлен и работает; настроен outbound WARP | |||
|- | |||
|| Docker || Установлен на обоих серверах | |||
|- | |||
|| Порт 2053 UDP || Должен быть свободен на обоих серверах | |||
|- | |||
|| TLS-сертификат || Получается через Caddy (caddy-naive), либо через certbot — описано в разделе ниже | |||
|} | |||
=== DNS-записи (создать заранее, до установки) === | |||
Let's Encrypt проверяет доступность домена по HTTP (порт 80) перед выдачей сертификата. DNS-записи должны быть созданы и распространены (propagated) до первого запуска Caddy. | |||
= | {| class="wikitable" | ||
|- | |||
! Запись !! Тип !! Значение !! Примечание | |||
|- | |||
</ | || <code>your-hy2-ru-domain.example.com</code> || A || IP RU сервера || Используется Hysteria2 сервером на RU | ||
|- | |||
|| <code>your-hy2-eu-domain.example.com</code> || A || IP EU сервера || Используется Hysteria2 сервером на EU | |||
|} | |||
'''Примечание:''' если NaïveProxy (Caddy) уже настроен на тех же серверах с теми же доменами, отдельные DNS-записи для Hysteria2 не нужны — используется тот же домен и тот же сертификат. | |||
=== Используемые заглушки === | |||
В данном руководстве применяются следующие заглушки. Замени их реальными значениями перед использованием: | |||
{| class="wikitable" | |||
|- | |||
! Заглушка !! Описание | |||
|- | |||
|| <code>YOUR_RU_SERVER_IP</code> || Публичный IP RU сервера | |||
|- | |||
|| <code>YOUR_EU_SERVER_IP</code> || Публичный IP EU сервера | |||
|- | |||
|| <code>your-hy2-ru-domain.example.com</code> || Домен для RU сервера (для TLS-сертификата) | |||
|- | |||
|| <code>your-hy2-eu-domain.example.com</code> || Домен для EU сервера (для TLS-сертификата) | |||
|- | |||
|| <code>YOUR_EMAIL@example.com</code> || Email для Let's Encrypt (уведомления об истечении сертификата) | |||
|- | |||
|| <code>EU_HY2_PASSWORD</code> || Пароль соединения RU клиент → EU сервер (генерируется один раз) | |||
|} | |||
'''Для генерации пароля EU_HY2_PASSWORD используй:''' | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
openssl rand -base64 18 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Сохрани результат — он понадобится в конфигах обоих серверов. | |||
---- | |||
== Часть 1: EU сервер (Exit Node) == | |||
Цель: развернуть Hysteria2 сервер в Docker, который принимает соединения от RU клиента и перенаправляет трафик через 3x-ui → Cloudflare WARP. | |||
'''Все команды в этом разделе выполняются на EU сервере под root.''' | |||
=== 1.1 Установка Docker === | |||
Если Docker уже установлен (проверить: <code>docker --version</code>) — пропусти этот шаг. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Обновить пакеты | |||
apt-get update && apt-get upgrade -y | |||
# Установить зависимости | |||
apt-get install -y ca-certificates curl gnupg lsb-release | |||
# Добавить GPG-ключ Docker | |||
install -m 0755 -d /etc/apt/keyrings | |||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | |||
-o /etc/apt/keyrings/docker.asc | |||
chmod a+r /etc/apt/keyrings/docker.asc | |||
== | # Добавить репозиторий Docker | ||
echo "deb [arch=$(dpkg --print-architecture) \ | |||
signed-by=/etc/apt/keyrings/docker.asc] \ | |||
https://download.docker.com/linux/ubuntu \ | |||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ | |||
> /etc/apt/sources.list.d/docker.list | |||
# Установить Docker Engine | |||
apt-get update | apt-get update | ||
apt-get install -y | apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin | ||
# Включить автозапуск | |||
systemctl enable docker | |||
systemctl start docker | |||
# Проверить | |||
docker --version | |||
docker compose version | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 1.2.2 Создать | Ожидаемый вывод: | ||
<pre> | |||
Docker version 27.x.x, build ... | |||
Docker Compose version v2.x.x | |||
</pre> | |||
=== 1.2 Настройка 3x-ui: создание inbound hy2-out → WARP === | |||
Hysteria2 Exit Node направляет трафик через 3x-ui SOCKS5 inbound, который в свою очередь маршрутизирует его в Cloudflare WARP. Этот шаг выполняется в веб-интерфейсе 3x-ui '''до''' запуска Hysteria2. | |||
'''Условие:''' в 3x-ui должен быть настроен outbound WARP (WireGuard/gVisor). | |||
==== 1.2.1 Создать новый inbound в 3x-ui ==== | |||
В веб-интерфейсе 3x-ui (EU сервер) перейди в '''Inbounds → Add Inbound''' и заполни следующие поля: | |||
{| class="wikitable" | |||
|- | |||
! Параметр !! Значение !! Пояснение | |||
|- | |||
|| Примечание (Remark) || <code>hy2-out</code> || Произвольное имя для идентификации в правилах маршрутизации | |||
|- | |||
|| Протокол || <code>mixed</code> || Поддерживает SOCKS5 и HTTP одновременно | |||
|- | |||
|| Listen IP || <code>127.0.0.1</code> || Только localhost — недоступен извне | |||
|- | |||
|| Порт || <code>24364</code> || Должен быть свободен; проверить: <code>ss -tlnp | grep 24364</code> | |||
|- | |||
|| Аутентификация || Выключена || Hysteria2 сам отвечает за авторизацию клиентов | |||
|- | |||
|| UDP || Включён || Требуется для DNS-запросов через туннель | |||
|} | |||
Нажми '''Save''' — inbound немедленно активируется. | |||
==== 1.2.2 Создать правило маршрутизации ==== | |||
В 3x-ui перейди в '''Routing → Add Rule''' (или Settings → Routing Rules): | |||
{| class="wikitable" | |||
|- | |||
! Параметр !! Значение | |||
|- | |||
|| Inbound Tag || <code>hy2-out</code> (тег созданного выше inbound) | |||
|- | |||
|| Outbound Tag || <code>warp</code> (или название твоего WARP outbound в 3x-ui) | |||
|} | |||
Сохрани и перезапусти Xray внутри 3x-ui. | |||
==== 1.2.3 Проверить, что порт 24364 слушает ==== | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
ss -tlnp | grep 24364 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 1. | Ожидаемый вывод: | ||
<pre> | |||
LISTEN 0 128 127.0.0.1:24364 0.0.0.0:* users:(("xray",...)) | |||
</pre> | |||
=== 1.3 TLS-сертификат для Hysteria2 === | |||
Hysteria2 требует действующий TLS-сертификат (самоподписанный не рекомендуется — клиентам потребуется флаг <code>insecure: true</code>). В нашей схеме используется '''Let's Encrypt сертификат''', получаемый и автоматически обновляемый через Caddy (caddy-naive стек). | |||
Caddy хранит сертификаты в именованном Docker volume <code>caddy-naive_caddy_data</code>, и Hysteria2 подключает их через bind mount (read-only) — никаких лишних сервисов, никакого дублирования. | |||
==== Вариант А: Caddy (caddy-naive) уже установлен — рекомендуется ==== | |||
Если NaïveProxy (caddy-naive) уже настроен и работает, сертификат уже получен. Проверь: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Найти путь к сертификату в Docker volume | |||
DOMAIN="your-hy2-eu-domain.example.com" | |||
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}" | |||
ls -la "${CERT_DIR}/" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Ожидаемый вывод — два файла: | |||
< | <pre> | ||
your-hy2-eu-domain.example.com.crt | |||
your-hy2-eu-domain.example.com.key | |||
</ | </pre> | ||
Если файлы есть — переходи к разделу '''1.4'''. Если volume или папка не существуют — сначала установи caddy-naive (NaïveProxy) или воспользуйся Вариантом Б. | |||
=== | ==== Вариант Б: получить сертификат через certbot (если Caddy не установлен) ==== | ||
Этот вариант используется, если caddy-naive '''не установлен''' и на серверах нет другого HTTP-сервера на портах 80/443. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Установить certbot | |||
apt-get install -y certbot | |||
# Получить сертификат (standalone — Certbot запустит временный HTTP-сервер на :80) | |||
certbot certonly --standalone \ | |||
-d your-hy2-eu-domain.example.com \ | |||
--email YOUR_EMAIL@example.com \ | |||
--agree-tos --non-interactive | |||
# Проверить | |||
ls /etc/letsencrypt/live/your-hy2-eu-domain.example.com/ | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Сертификаты будут по пути <code>/etc/letsencrypt/live/your-hy2-eu-domain.example.com/</code>. В этом случае в <code>docker-compose.yml</code> (раздел 1.6) замени volume на: | |||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
- /etc/letsencrypt/live/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro | |||
- /etc/letsencrypt/archive/your-hy2-eu-domain.example.com:/etc/letsencrypt/archive/your-hy2-eu-domain.example.com:ro | |||
</syntaxhighlight> | </syntaxhighlight> | ||
И в <code>server.yaml</code> пути к файлам: | |||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
tls: | |||
cert: /etc/hysteria/certs/fullchain.pem | |||
key: /etc/hysteria/certs/privkey.pem | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 1 | === 1.4 Создание структуры проекта === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
mkdir -p /opt/hysteria-eu | |||
</syntaxhighlight> | cd /opt/hysteria-eu | ||
</syntaxhighlight> | |||
Итоговая структура: | |||
<pre> | |||
/opt/hysteria-eu/ | |||
├── docker-compose.yml | |||
└── server.yaml | |||
</pre> | |||
=== 1. | === 1.5 Конфигурация сервера: server.yaml === | ||
Создай файл конфигурации. Замени <code>EU_HY2_PASSWORD</code> сгенерированным паролем: | |||
< | |||
</ | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-eu/server.yaml << 'EOF' | |||
listen: :2053 | |||
listen: : | |||
tls: | tls: | ||
cert: | cert: /etc/hysteria/certs/your-hy2-eu-domain.example.com.crt | ||
key: | key: /etc/hysteria/certs/your-hy2-eu-domain.example.com.key | ||
auth: | auth: | ||
type: password | type: password | ||
password: | password: EU_HY2_PASSWORD | ||
ignoreClientBandwidth: true | |||
masquerade: | masquerade: | ||
| Строка 225: | Строка 385: | ||
url: https://news.ycombinator.com/ | url: https://news.ycombinator.com/ | ||
rewriteHost: true | rewriteHost: true | ||
outbounds: | |||
- name: warp | |||
type: socks5 | |||
socks5: | |||
addr: 127.0.0.1:24364 | |||
acl: | |||
inline: | |||
- "warp(all)" | |||
EOF | EOF | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Описание ключевых параметров: | |||
{| class="wikitable" | |||
|- | |||
! Параметр !! Значение !! Пояснение | |||
|- | |||
|| <code>listen: :2053</code> || UDP порт 2053 || Hysteria2 слушает на всех интерфейсах | |||
|- | |||
|| <code>tls.cert / tls.key</code> || Пути к сертификату || Берутся из caddy-naive Docker volume (bind mount) | |||
|- | |||
|| <code>auth.type: password</code> || Одиночный пароль || Для соединения RU клиент → EU сервер (клиентские устройства этот пароль не знают) | |||
|- | |||
|| <code>ignoreClientBandwidth: true</code> || Игнорировать лимит клиента || Серверная сторона управляет congestion control самостоятельно | |||
|- | |||
|| <code>masquerade.type: proxy</code> || Проксировать реальный сайт || Неавторизованные запросы получают реальный контент ycombinator.com — сервер выглядит как обычный HTTPS-сайт | |||
|- | |||
|| <code>outbounds.warp</code> || SOCKS5 на 127.0.0.1:24364 || Исходящий трафик → 3x-ui inbound "hy2-out" → WARP | |||
|- | |||
|| <code>acl: warp(all)</code> || Направить весь трафик в warp || Синтаксис: имя outbound в скобках, адрес/маска после | |||
|} | |||
=== 1.6 Конфигурация Docker Compose: docker-compose.yml === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-eu/docker-compose.yml << 'EOF' | |||
services: | |||
hysteria-eu: | |||
image: tobyxdd/hysteria:latest | |||
container_name: hysteria-eu | |||
restart: unless-stopped | |||
network_mode: host | |||
command: server --config /etc/hysteria/server.yaml | |||
volumes: | |||
- ./server.yaml:/etc/hysteria/server.yaml:ro | |||
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro | |||
EOF | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Важные моменты: | |||
< | * <code>network_mode: host</code> — обязателен, чтобы контейнер видел <code>127.0.0.1:24364</code> (3x-ui inbound). | ||
* <code>:ro</code> — сертификаты подключены только для чтения, Hysteria2 их не изменяет. | |||
</ | * Путь к сертификату — bind mount из Docker volume caddy-naive. Если используется certbot (Вариант Б) — замени путь согласно инструкции раздела 1.3. | ||
=== 1 | === 1.7 Первый запуск === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /opt/hysteria-eu | |||
# Скачать образ и запустить | |||
docker compose pull | |||
docker compose up -d | |||
# Подождать 3 секунды и проверить логи | |||
sleep 3 && docker compose logs | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 1. | Ожидаемый вывод в логах: | ||
<pre> | |||
INFO server mode | |||
INFO server up and running {"listen": ":2053"} | |||
</pre> | |||
Если в логах ошибка — см. раздел «Диагностика проблем» (Часть 5). | |||
=== 1.8 Проверка: порт и соединение === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Проверить, что Hysteria2 слушает на UDP :2053 | |||
ss -ulnp | grep ':2053' | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Ожидаемый вывод: | |||
<pre> | |||
UNCONN 0 0 0.0.0.0:2053 0.0.0.0:* users:(("hysteria",...)) | |||
</pre> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Статус контейнера | |||
docker compose ps | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<pre> | |||
< | NAME IMAGE STATUS | ||
hysteria-eu tobyxdd/hysteria:latest Up X minutes | |||
</ | </pre> | ||
---- | ---- | ||
= Часть 2 | == Часть 2: RU сервер (Entry Node) == | ||
Цель: развернуть два Docker-контейнера: | |||
* <code>hysteria-ru-server</code> — принимает соединения от клиентов, аутентифицирует по <code>users.txt</code>. | |||
* <code>hysteria-ru-client</code> — подключается к EU серверу, слушает SOCKS5 на <code>127.0.0.1:10810</code>. | |||
Между ними: hysteria-ru-server направляет трафик через SOCKS5 → hysteria-ru-client → EU. | |||
== 2.1 | '''Все команды в этом разделе выполняются на RU сервере под root.''' | ||
=== 2.1 Установка Docker === | |||
Аналогично EU серверу (раздел 1.1). Если Docker уже установлен — пропусти. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
apt-get update && apt-get upgrade -y | |||
apt-get install -y ca-certificates curl gnupg lsb-release | |||
install -m 0755 -d /etc/apt/keyrings | |||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | |||
-o /etc/apt/keyrings/docker.asc | |||
chmod a+r /etc/apt/keyrings/docker.asc | |||
== | echo "deb [arch=$(dpkg --print-architecture) \ | ||
signed-by=/etc/apt/keyrings/docker.asc] \ | |||
https://download.docker.com/linux/ubuntu \ | |||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ | |||
> /etc/apt/sources.list.d/docker.list | |||
apt-get update | apt-get update | ||
apt-get install -y | apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin | ||
systemctl enable docker && systemctl start docker | |||
docker --version && docker compose version | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2.2. | === 2.2 TLS-сертификат для RU сервера === | ||
Аналогично EU серверу. На RU сервере также используется сертификат от Caddy (caddy-naive), если NaïveProxy настроен. | |||
==== Вариант А: через Caddy (caddy-naive) — рекомендуется ==== | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
DOMAIN="your-hy2-ru-domain.example.com" | |||
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}" | |||
ls -la "${CERT_DIR}/" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Если файлы <code>.crt</code> и <code>.key</code> присутствуют — всё готово, переходи к разделу 2.3. | |||
< | |||
</ | |||
=== | ==== Вариант Б: через certbot (если Caddy не установлен) ==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
apt-get install -y certbot | |||
certbot certonly --standalone \ | |||
-d your-hy2-ru-domain.example.com \ | |||
--email YOUR_EMAIL@example.com \ | |||
--agree-tos --non-interactive | |||
ls /etc/letsencrypt/live/your-hy2-ru-domain.example.com/ | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2.3 | === 2.3 Создание структуры проекта === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
mkdir -p /opt/hysteria-ru/users | |||
cd /opt/hysteria-ru | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2. | Итоговая структура: | ||
<pre> | |||
/opt/hysteria-ru/ | |||
├── docker-compose.yml | |||
├── server.yaml | |||
├── client.yaml | |||
└── users/ | |||
├── users.txt ← список пользователей (username password) | |||
└── check-auth.sh ← скрипт авторизации, вызывается при каждом подключении | |||
</pre> | |||
=== 2.4 Система аутентификации пользователей === | |||
RU сервер использует <code>auth.type: command</code> — при каждом входящем подключении Hysteria2 вызывает внешний скрипт и передаёт ему пароль клиента в аргументах. Скрипт ищет пароль в файле <code>users.txt</code>: если совпадение найдено — выводит имя пользователя (оно попадает в логи) и завершается с кодом 0. Если нет — завершается с кодом 1 (отказ в подключении). | |||
Преимущество этого подхода: <code>users.txt</code> можно редактировать в любой момент без перезапуска контейнера. Изменения применяются немедленно. | |||
==== 2.4.1 Файл пользователей: users/users.txt ==== | |||
Формат: одна строка на пользователя — имя пользователя и пароль, разделённые пробелом. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
openssl | # Сгенерировать первого пользователя (пример: "default") | ||
PASS=$(openssl rand -base64 18) | |||
echo "default $PASS" > /opt/hysteria-ru/users/users.txt | |||
echo "Пароль пользователя default: $PASS" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2. | Формат файла: | ||
<pre> | |||
username1 пароль1 | |||
username2 пароль2 | |||
</pre> | |||
'''Важно:''' не используй пробелы внутри пароля — пароль считывается по второму полю строки. | |||
==== 2.4.2 Скрипт авторизации: users/check-auth.sh ==== | |||
Hysteria2 вызывает скрипт с тремя аргументами: <code>$1</code> — адрес клиента, <code>$2</code> — пароль, <code>$3</code> — tx bandwidth. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-ru/users/check-auth.sh << 'EOF' | |||
#!/bin/sh | |||
AUTH_PAYLOAD="$2" | |||
USERS_FILE="/etc/hysteria/users/users.txt" | |||
USERNAME=$(awk -v pwd="$AUTH_PAYLOAD" '$2 == pwd {print $1; exit}' "$USERS_FILE" 2>/dev/null) | |||
if [ -n "$USERNAME" ]; then | |||
echo "$USERNAME" | |||
exit 0 | |||
fi | |||
exit 1 | |||
EOF | |||
chmod +x /opt/hysteria-ru/users/check-auth.sh | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== 2. | Почему используется <code>awk</code>, а не <code>grep</code>: | ||
* Hysteria2 использует Docker-образ на базе Alpine Linux с BusyBox. | |||
* BusyBox <code>grep</code> не поддерживает флаг <code>-P</code> (Perl-совместимые регулярки). | |||
* <code>awk</code> доступен во всех Unix/Linux окружениях и работает стабильно. | |||
=== 2.5 Конфигурация сервера: server.yaml === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-ru/server.yaml << 'EOF' | |||
listen: :2053 | |||
tls: | |||
cert: /etc/hysteria/certs/your-hy2-ru-domain.example.com.crt | |||
key: /etc/hysteria/certs/your-hy2-ru-domain.example.com.key | |||
auth: | |||
type: command | |||
command: /etc/hysteria/users/check-auth.sh | |||
ignoreClientBandwidth: true | |||
masquerade: | |||
type: proxy | |||
proxy: | |||
url: https://news.ycombinator.com/ | |||
rewriteHost: true | |||
outbounds: | |||
- name: chain | |||
type: socks5 | |||
socks5: | |||
addr: 127.0.0.1:10810 | |||
acl: | |||
inline: | |||
- "chain(all)" | |||
EOF | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Описание ключевых параметров: | |||
{| class="wikitable" | |||
|- | |||
</ | ! Параметр !! Значение !! Пояснение | ||
|- | |||
|| <code>auth.type: command</code> || Внешний скрипт || При каждом подключении вызывается <code>check-auth.sh</code> | |||
|- | |||
|| <code>auth.command</code> || Путь внутри контейнера || Скрипт смонтирован через volume: <code>./users:/etc/hysteria/users</code> | |||
|- | |||
|| <code>outbounds.chain</code> || SOCKS5 на 127.0.0.1:10810 || Трафик → hysteria-ru-client → EU | |||
|- | |||
|| <code>acl: chain(all)</code> || Весь трафик через chain || Синтаксис: имя outbound, затем адрес в скобках | |||
|} | |||
=== 2.6 Конфигурация клиента: client.yaml === | |||
RU клиент подключается к EU серверу и поднимает SOCKS5 на <code>127.0.0.1:10810</code>. | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-ru/client.yaml << 'EOF' | |||
server: your-hy2-eu-domain.example.com:2053 | |||
auth: EU_HY2_PASSWORD | |||
socks5: | |||
listen: 127.0.0.1:10810 | |||
EOF | EOF | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2. | Замени <code>EU_HY2_PASSWORD</code> на тот же пароль, который указан в <code>server.yaml</code> EU сервера. | ||
=== 2.7 Конфигурация Docker Compose: docker-compose.yml === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/hysteria-ru/docker-compose.yml << 'EOF' | |||
services: | |||
hysteria-ru-server: | |||
image: tobyxdd/hysteria:latest | |||
container_name: hysteria-ru-server | |||
restart: unless-stopped | |||
network_mode: host | |||
command: server --config /etc/hysteria/server.yaml | |||
volumes: | |||
- ./server.yaml:/etc/hysteria/server.yaml:ro | |||
- ./users:/etc/hysteria/users:ro | |||
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-ru-domain.example.com:/etc/hysteria/certs:ro | |||
hysteria-ru-client: | |||
image: tobyxdd/hysteria:latest | |||
container_name: hysteria-ru-client | |||
restart: unless-stopped | |||
network_mode: host | |||
command: client --config /etc/hysteria/client.yaml | |||
volumes: | |||
- ./client.yaml:/etc/hysteria/client.yaml:ro | |||
EOF | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 2 | '''Критически важные моменты:''' | ||
* Директория <code>./users</code> монтируется в оба контейнера... нет, только в <code>hysteria-ru-server</code>. Клиентский контейнер видит только свой <code>client.yaml</code>. | |||
* При изменении <code>docker-compose.yml</code> (добавлении volume) нужен пересоздать контейнер: <code>docker compose down && docker compose up -d</code>. Простой <code>restart</code> не применяет изменения volumes. | |||
=== 2.8 Первый запуск === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /opt/hysteria-ru | |||
# Скачать образ | |||
docker compose pull | |||
# Запустить оба контейнера | |||
docker compose up -d | |||
# Подождать и проверить логи | |||
sleep 3 && docker compose logs | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Ожидаемый вывод: | |||
<pre> | |||
hysteria-ru-server | INFO server mode | |||
hysteria-ru-server | INFO server up and running {"listen": ":2053"} | |||
hysteria-ru-client | INFO client mode | |||
hysteria-ru-client | INFO connected to server {"udpEnabled": true, "count": 1} | |||
hysteria-ru-client | INFO SOCKS5 server listening {"addr": "127.0.0.1:10810"} | |||
</pre> | |||
=== 2. | === 2.9 Проверка цепочки с RU сервера === | ||
Тест: запустить временный Hysteria2 клиент на RU сервере и проверить, что трафик выходит через WARP (IP Cloudflare): | |||
) | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /tmp/hy2- | # Создать временный конфиг тест-клиента | ||
server: 127.0.0.1: | cat > /tmp/hy2-test.yaml << EOF | ||
auth: ${ | server: 127.0.0.1:2053 | ||
auth: $(awk 'NR==1{print $2}' /opt/hysteria-ru/users/users.txt) | |||
tls: | tls: | ||
insecure: true | insecure: true | ||
sni: your-hy2-ru-domain.example.com | |||
socks5: | socks5: | ||
listen: 127.0.0.1: | listen: 127.0.0.1:11080 | ||
EOF | EOF | ||
# Запустить тест-клиент | |||
docker run -d --name hy2-test --network host \ | |||
-v /tmp/hy2-test.yaml:/etc/hysteria/client.yaml:ro \ | |||
sleep | tobyxdd/hysteria:latest client --config /etc/hysteria/client.yaml | ||
curl - | |||
# Ждать 3 секунды | |||
sleep 3 | |||
# Проверить IP выхода — должен быть Cloudflare WARP IP | |||
echo "IP через цепочку:" | |||
curl -s --max-time 15 -x socks5h://127.0.0.1:11080 https://ifconfig.me | |||
# Очистить | |||
docker rm -f hy2-test | |||
rm -f /tmp/hy2-test.yaml | |||
rm -f /tmp/hy2- | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Если IP вывода принадлежит Cloudflare (начинается с <code>2a09:</code> для IPv6 или относится к диапазонам WARP) — цепочка RU → EU → WARP работает корректно. | |||
'''Примечание:''' параметр <code>insecure: true</code> и <code>sni</code> нужны в тест-клиенте, потому что он подключается по IP (<code>127.0.0.1</code>), а сертификат выдан на домен. Реальные клиенты подключаются по домену и такие параметры не требуются. | |||
</ | |||
---- | |||
== Часть 3: Управление пользователями (hy2-users.sh) == | |||
=== 3.1 Что это и зачем === | |||
Для управления пользователями Hysteria2 на RU сервере предназначен скрипт <code>hy2-users.sh</code>. Он предоставляет интерактивное меню с набором операций над файлом <code>users.txt</code>. | |||
Скрипт решает следующие задачи: | |||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Операция !! Описание | |||
|- | |- | ||
| | || Список пользователей || Показывает всех пользователей из users.txt с маскированными паролями | ||
| | |||
|- | |- | ||
| | || Добавить пользователя || Запрашивает имя, автоматически генерирует криптостойкий пароль, сохраняет в users.txt, показывает готовый Hysteria2 URI | ||
| | |||
|- | |- | ||
| | || Удалить пользователя || Интерактивный выбор из списка, удаление строки из users.txt | ||
| | |||
|- | |- | ||
| | || Показать конфиг || Генерирует и показывает Hysteria2 URI для выбранного пользователя | ||
| | |||
|} | |} | ||
Ключевые особенности: | |||
* '''Нет перезапуска контейнера.''' Hysteria2 с <code>auth.type: command</code> читает файл при каждом подключении — изменения в <code>users.txt</code> применяются мгновенно. | |||
* '''Пароли генерируются автоматически''' через <code>openssl rand</code> — случайные, криптостойкие. | |||
* '''URI формат''' соответствует стандарту Hysteria2 и принимается всеми клиентами: NekoBox, husi, Exclave, podkop. | |||
=== 3.2 Установка скрипта === | |||
Скрипт хранится в репозитории по пути <code>scripts/hy2-users.sh</code>. Установка на сервер: | |||
-- | <syntaxhighlight lang="bash"> | ||
# Скопировать скрипт на сервер | |||
scp scripts/hy2-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/hy2-users.sh | |||
# Сделать исполняемым | |||
ssh root@YOUR_RU_SERVER_IP "chmod +x /usr/local/bin/hy2-users.sh" | |||
</syntaxhighlight> | |||
Или выполнить непосредственно на RU сервере (если скрипт уже там): | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
chmod +x /usr/local/bin/hy2-users.sh | |||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Проверка переменных в начале скрипта.''' Открой скрипт и убедись, что следующие переменные указывают на правильные значения: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
head -15 /usr/local/bin/hy2-users.sh | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | Должны быть: | ||
* <code>USERS_FILE="/opt/hysteria-ru/users/users.txt"</code> | |||
* <code>DOMAIN="your-hy2-ru-domain.example.com"</code> | |||
* <code>PORT="2053"</code> | |||
Если домен указан неверно — исправь: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
sed -i 's|DOMAIN=.*|DOMAIN="your-hy2-ru-domain.example.com"|' /usr/local/bin/hy2-users.sh | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === 3.3 Использование === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
sudo bash /usr/local/bin/hy2-users.sh | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | Скрипт показывает меню: | ||
<pre> | |||
┌─────────────────────────────────────────────┐ | |||
│ Hysteria2 — управление пользователями RU │ | |||
└─────────────────────────────────────────────┘ | |||
1. Список пользователей | |||
2. Добавить пользователя | |||
3. Удалить пользователя | |||
4. Показать конфиг клиента | |||
0. Выход | |||
Выбери действие: | |||
</pre> | |||
При добавлении нового пользователя скрипт выводит готовый URI: | |||
<pre> | |||
hysteria2://СГЕНЕРИРОВАННЫЙ_ПАРОЛЬ@your-hy2-ru-domain.example.com:2053 | |||
</pre> | |||
Этот URI вставляется напрямую в клиентское приложение (см. Часть 4). | |||
=== 3.4 Исходный код скрипта === | |||
<div class="mw-collapsible mw-collapsed" style="width:100%"> | |||
<div style="font-weight:bold; padding:4px 0;">▶ Показать исходный код hy2-users.sh</div> | |||
<div class="mw-collapsible-content"> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
#!/bin/bash | |||
# ============================================================= | |||
# hy2-users — управление пользователями Hysteria2 на RU сервере | |||
# users.txt: /opt/hysteria-ru/users/users.txt | |||
# Формат: username password (по одной строке) | |||
# Перезапуск контейнера НЕ требуется — изменения применяются мгновенно | |||
# ============================================================= | |||
USERS_FILE="/opt/hysteria-ru/users/users.txt" | |||
DOMAIN="your-hy2-ru-domain.example.com" | |||
PORT="2053" | |||
RED='\033[0;31m' | |||
GREEN='\033[0;32m' | |||
YELLOW='\033[1;33m' | |||
CYAN='\033[0;36m' | |||
BOLD='\033[1m' | |||
NC='\033[0m' | |||
# --- Вспомогательные функции --- | |||
check_root() { | |||
if [[ $EUID -ne 0 ]]; then | |||
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash hy2-users.sh)${NC}" | |||
exit 1 | |||
fi | |||
} | |||
check_users_file() { | |||
if [[ ! -f "$USERS_FILE" ]]; then | |||
echo -e "${RED}Ошибка: файл пользователей не найден: $USERS_FILE${NC}" | |||
exit 1 | |||
fi | |||
} | |||
get_usernames() { | |||
awk '{print $1}' "$USERS_FILE" | |||
} | |||
get_password() { | |||
local user="$1" | |||
awk -v u="$user" '$1 == u {print $2}' "$USERS_FILE" | |||
} | |||
press_enter() { | |||
echo "" | |||
read -rp "Нажми Enter для возврата в меню..." | |||
} | |||
print_config_for() { | |||
local user="$1" | |||
local pass="$2" | |||
echo "" | |||
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}" | |||
echo -e "${BOLD}│ Hysteria2 URI для клиента │${NC}" | |||
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}" | |||
echo "" | |||
echo -e "${BOLD} hysteria2://${pass}@${DOMAIN}:${PORT}${NC}" | |||
echo "" | |||
echo -e "${CYAN}Для podkop (OpenWrt):${NC} вставить URI как hy2 outbound" | |||
echo -e "${CYAN}Для мобильных клиентов:${NC} NekoBox, husi, Exclave — импорт URI" | |||
echo -e "${CYAN}Пользователь:${NC} ${user}" | |||
} | |||
# --- Функции меню --- | |||
list_users() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Список пользователей ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Пользователей нет.${NC}" | |||
else | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo "" | |||
echo -e " Всего: ${#users[@]} пользователь(ей)" | |||
fi | |||
press_enter | |||
} | |||
add_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Добавить пользователя ===${NC}" | |||
read -rp "Имя пользователя: " username | |||
username=$(echo "$username" | tr -d ' ') | |||
if [[ -z "$username" ]]; then | |||
echo -e "${RED}Имя не может быть пустым.${NC}" | |||
press_enter | |||
return | |||
fi | |||
if grep -q "^${username} " "$USERS_FILE" 2>/dev/null; then | |||
echo -e "${RED}Пользователь '${username}' уже существует.${NC}" | |||
press_enter | |||
return | |||
fi | |||
read -rp "Пароль (Enter = сгенерировать автоматически): " password | |||
if [[ -z "$password" ]]; then | |||
password=$(openssl rand -base64 18 | tr -d '=/+' | head -c 22) | |||
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}" | |||
fi | |||
echo "${username} ${password}" >> "$USERS_FILE" | |||
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}" | |||
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}" | |||
print_config_for "$username" "$password" | |||
press_enter | |||
} | |||
delete_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Удалить пользователя ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет пользователей для удаления.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер для удаления: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
target="${users[$((choice-1))]}" | |||
read -rp "Удалить пользователя '${target}'? (y/N): " confirm | |||
if [[ "$confirm" =~ ^[Yy]$ ]]; then | |||
sed -i "/^${target} /d" "$USERS_FILE" | |||
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}" | |||
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}" | |||
else | |||
echo "Отменено." | |||
fi | |||
press_enter | |||
} | |||
show_config() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== URI клиента ===${NC}" | |||
mapfile -t users < <(get_usernames) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет пользователей.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер пользователя: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
local user="${users[$((choice-1))]}" | |||
local pass | |||
pass=$(get_password "$user") | |||
print_config_for "$user" "$pass" | |||
press_enter | |||
} | |||
# --- Главное меню --- | |||
main_menu() { | |||
check_root | |||
check_users_file | |||
while true; do | |||
clear | |||
echo -e "${BOLD}${CYAN}" | |||
echo "╔══════════════════════════════════════╗" | |||
echo "║ Hysteria2 User Manager (RU) ║" | |||
echo "║ Домен: ${DOMAIN} ║" | |||
echo "╚══════════════════════════════════════╝" | |||
echo -e "${NC}" | |||
mapfile -t users < <(get_usernames) | |||
echo -e " Активных пользователей: ${BOLD}${#users[@]}${NC}" | |||
echo "" | |||
echo -e " ${BOLD}1.${NC} Список пользователей" | |||
echo -e " ${BOLD}2.${NC} Добавить пользователя" | |||
echo -e " ${BOLD}3.${NC} Удалить пользователя" | |||
echo -e " ${BOLD}4.${NC} Показать URI клиента" | |||
echo -e " ${BOLD}0.${NC} Выход" | |||
echo "" | |||
read -rp "Выбор: " option | |||
case "$option" in | |||
1) list_users ;; | |||
2) add_user ;; | |||
3) delete_user ;; | |||
4) show_config ;; | |||
0) echo "Выход."; exit 0 ;; | |||
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;; | |||
esac | |||
done | |||
} | |||
main_menu | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
</div> | |||
---- | |||
== Часть 4: Настройка клиентов == | |||
=== 4.1 Формат URI Hysteria2 === | |||
Hysteria2 использует стандартизированный URI для передачи параметров подключения клиентам: | |||
<pre> | |||
hysteria2://ПАРОЛЬ@ДОМЕН:ПОРТ | |||
</pre> | |||
Пример: | |||
<pre> | |||
hysteria2://aBcDeFgHiJkLmNoPqRsT@your-hy2-ru-domain.example.com:2053 | |||
</pre> | |||
URI можно получить через скрипт <code>hy2-users.sh</code> (пункт 4 меню — «Показать конфиг клиента»). | |||
=== 4.2 Мобильные клиенты (iOS / Android) === | |||
Рекомендуемые приложения с нативной поддержкой Hysteria2: | |||
=== | {| class="wikitable" | ||
<syntaxhighlight lang=" | |- | ||
! Приложение !! Платформа !! Как импортировать URI | |||
|- | |||
|| NekoBox || Android || Главный экран → + → Add → Hysteria2 → вставить URI | |||
|- | |||
|| husi || Android || Профили → + → Hysteria2 → вставить URI | |||
|- | |||
|| Exclave || iOS || Конфигурации → + → Hysteria2 → вставить URI | |||
|- | |||
|| Shadowrocket || iOS || Главная → + → Тип: Hysteria2 → вставить URI | |||
|} | |||
Все эти приложения принимают URI в формате <code>hysteria2://...</code> напрямую — через кнопку импорта, QR-код или буфер обмена. | |||
=== 4.3 ПК-клиенты (Windows / macOS / Linux) === | |||
==== Вариант А: официальный бинарь Hysteria2 ==== | |||
Скачать бинарь с официального репозитория: https://github.com/apernet/hysteria/releases | |||
Создать файл конфигурации <code>config.yaml</code>: | |||
<syntaxhighlight lang="yaml"> | |||
server: your-hy2-ru-domain.example.com:2053 | |||
auth: ВАШ_ПАРОЛЬ | |||
socks5: | |||
listen: 127.0.0.1:1080 | |||
http: | |||
listen: 127.0.0.1:8080 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Запустить: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Linux / macOS | |||
./hysteria client --config config.yaml | |||
# Windows (PowerShell) | |||
.\hysteria.exe client --config config.yaml | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | После запуска: | ||
* SOCKS5 прокси доступен на <code>127.0.0.1:1080</code> | |||
* HTTP прокси доступен на <code>127.0.0.1:8080</code> | |||
==== Вариант Б: через NekoRay / Hiddify Next (Windows / Linux) ==== | |||
Приложения NekoRay и Hiddify Next принимают Hysteria2 URI — импорт через меню или буфер обмена. Автоматически управляют запуском клиентского процесса. | |||
=== 4.4 OpenWrt / podkop === | |||
podkop (реализация на базе sing-box) нативно поддерживает Hysteria2 URI. Никакой дополнительной установки бинарей не требуется. | |||
В интерфейсе podkop (LuCI → Network → podkop → Outbound): | |||
* Тип: <code>Hysteria2</code> | |||
* URI: <code>hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053</code> | |||
Или через UCI: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
uci set podkop.main.proxy='hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053' | |||
uci commit podkop | |||
/etc/init.d/podkop restart | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | После сохранения все устройства домашней сети автоматически получают обход без дополнительной настройки. | ||
---- | |||
== Часть 5: Проверка всей цепочки == | |||
=== 5.1 Тест RU сервера: цепочка RU → EU → WARP === | |||
Выполнить на RU сервере — проверяет, что трафик выходит через WARP: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# SOCKS5 порт 10810 — это точка выхода RU клиента (= вход в EU сервер → WARP) | |||
echo " | curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me | ||
echo "" | |||
</syntaxhighlight> | |||
Результат должен быть '''IP Cloudflare WARP''' — например, начинается с <code>2a09:</code> (IPv6) или принадлежит AS13335 (Cloudflare). | |||
Проверить AS: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
IP=$(curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me) | |||
curl -s "https://ipinfo.io/${IP}/org" | |||
echo "" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | Ожидаемый вывод: строка содержит <code>Cloudflare</code>. | ||
< | |||
# | === 5.2 Тест с реального клиента === | ||
После добавления пользователя через <code>hy2-users.sh</code> и получения URI: | |||
# Вставь URI в клиентское приложение (NekoBox, husi, Shadowrocket). | |||
# Активируй профиль. | |||
# Открой <code>https://ifconfig.me</code> в браузере — должен отображаться IP Cloudflare WARP. | |||
# Открой <code>https://www.whatismybrowser.com/detect/what-is-my-ip-address</code> для проверки отсутствия WebRTC утечки. | |||
=== 5.3 Диагностика проблем === | |||
==== Проблема: hysteria-eu не запускается — ошибка TLS ==== | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
docker compose -f /opt/hysteria-eu/docker-compose.yml logs hysteria-eu | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Возможные причины: | |||
* Путь к сертификату неверен → проверь <code>ls</code> по пути, указанному в volumes. | |||
* Сертификат ещё не был получен Caddy → проверь, что caddy-naive запущен и прошёл ACME-проверку. | |||
* Caddy хранит сертификат под другим именем → выполни <code>find /var/lib/docker/volumes/caddy-naive_caddy_data/_data -name "*.crt"</code>. | |||
== | ==== Проблема: hysteria-ru-server выдаёт auth error ==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-server | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Проверить вручную (на RU сервере): | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Проверить, что скрипт находится на месте внутри контейнера | |||
docker exec hysteria-ru-server ls -la /etc/hysteria/users/ | |||
# Запустить скрипт вручную с тестовым паролем | |||
docker exec hysteria-ru-server /etc/hysteria/users/check-auth.sh addr ТЕСТОВЫЙ_ПАРОЛЬ 0 | |||
echo "Exit code: $?" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | Если <code>Exit code: 0</code> — скрипт работает. Если <code>1</code> — пароль не найден в <code>users.txt</code>. | ||
==== Проблема: hysteria-ru-client не подключается к EU ==== | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-client | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | Возможные причины: | ||
* <code>EU_HY2_PASSWORD</code> в <code>client.yaml</code> не совпадает с паролем в EU <code>server.yaml</code>. | |||
* UDP порт 2053 заблокирован на EU сервере (firewall) → проверь <code>ss -ulnp | grep 2053</code> на EU сервере. | |||
* EU сервер ещё не запущен. | |||
==== Проблема: ACL ошибка при запуске ==== | |||
<pre> | |||
FATAL failed to load server config: invalid config: acl.inline: invalid syntax at line 1 | |||
</pre> | |||
Неверный синтаксис ACL. Правильный формат: <code>"outbound_name(address)"</code>: | |||
* ✅ <code>"warp(all)"</code> — направить всё в outbound "warp" | |||
* ✅ <code>"chain(all)"</code> — направить всё в outbound "chain" | |||
* ❌ <code>"outbound(warp) all"</code> — неверно | |||
---- | |||
== Часть 6: Обслуживание == | |||
=== 6.1 Управление контейнерами === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# EU сервер | |||
cd /opt/hysteria-eu | |||
docker compose up -d # запустить | |||
docker compose down # остановить | |||
docker compose restart # перезапустить | |||
docker compose logs -f # следить за логами в реальном времени | |||
docker compose ps # статус | |||
# RU сервер (оба контейнера) | |||
cd /opt/hysteria-ru | |||
docker compose up -d | |||
docker compose down | |||
docker compose restart | |||
docker compose logs -f | |||
# RU сервер (по отдельности) | |||
docker compose restart hysteria-ru-server # только сервер | |||
docker compose restart hysteria-ru-client # только клиент | |||
docker compose logs -f hysteria-ru-server # логи только сервера | |||
docker compose logs -f hysteria-ru-client # логи только клиента | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | === 6.2 Обновление сертификатов === | ||
TLS-сертификаты обновляются Caddy автоматически (за 30 дней до истечения). После автообновления Hysteria2 начнёт использовать новый сертификат при следующем перезапуске. | |||
Для принудительного применения нового сертификата: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# EU сервер | |||
cd /opt/hysteria-eu && docker compose restart | |||
# RU сервер | |||
cd /opt/hysteria-ru && docker compose restart hysteria-ru-server | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Проверить дату истечения текущего сертификата: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# EU сервер | |||
DOMAIN="your-hy2-eu-domain.example.com" | |||
CERT="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}.crt" | |||
openssl x509 -in "${CERT}" -noout -enddate | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === 6.3 Обновление Docker-образа === | ||
Hysteria2 активно развивается. Для обновления до последней версии: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# EU сервер | |||
cd /opt/hysteria-eu | |||
docker compose pull # скачать новый образ | |||
docker compose up -d # перезапустить с новым образом | |||
docker compose logs --tail=5 # проверить старт | |||
# RU сервер | |||
cd /opt/hysteria-ru | |||
docker compose pull | |||
docker compose up -d | |||
docker compose logs --tail=5 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Старые неиспользуемые образы можно удалить: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
docker image prune -f | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === 6.4 Мониторинг логов === | ||
Hysteria2 пишет структурированные JSON-логи. Полезные паттерны для мониторинга: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Смотреть все подключения на RU сервере | |||
docker logs hysteria-ru-server 2>&1 | grep -E "connected|disconnected|auth" | |||
-- | |||
# Смотреть ошибки | |||
docker logs hysteria-ru-server 2>&1 | grep -i "error\|fatal\|warn" | |||
# Считать количество уникальных пользователей (поле "id") | |||
docker logs hysteria-ru-server 2>&1 | grep '"id"' | grep -oP '"id": "\K[^"]+' | sort -u | |||
# Следить за логами EU сервера в реальном времени | |||
docker logs -f hysteria-eu 2>&1 | |||
</syntaxhighlight> | |||
Пример нормального лога RU сервера при входящем подключении: | |||
{ | <pre> | ||
INFO client connected {"addr": "CLIENT_IP:PORT", "id": "username"} | |||
INFO TCP request {"addr": "CLIENT_IP:PORT", "id": "username", "reqAddr": "example.com:443"} | |||
INFO client disconnected {"addr": "CLIENT_IP:PORT", "id": "username", "error": "..."} | |||
</pre> | |||
Поле <code>"id"</code> — это имя пользователя, возвращённое скриптом <code>check-auth.sh</code>. Это позволяет отслеживать активность конкретных пользователей в логах без хранения паролей. | |||
Текущая версия от 10:14, 26 апреля 2026
Hysteria2: установка каскадной цепочки Entry Node → Exit Node → WARP
Что такое Hysteria2 и зачем он нужен
Проблема: ограничения традиционных прокси-протоколов
Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), применяемые интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса назначения, но и характер самого трафика. Даже зашифрованное соединение можно идентифицировать — по поведению, по паузам между пакетами, по размерам передаваемых блоков данных, по TLS-fingerprint.
Большинство прокси-протоколов работают поверх TCP. Это означает:
- Все потери пакетов обрабатываются дважды — сначала на уровне транспорта, затем внутри туннеля (head-of-line blocking).
- При нестабильном канале (мобильная сеть, спутник, VPN на слабом железе) скорость резко падает.
- TCP-fingerprint легче распознаётся и блокируется.
Hysteria2 решает эту проблему фундаментально иначе: он работает поверх QUIC — транспортного протокола на базе UDP, разработанного в Google и стандартизированного в RFC 9000.
Решение: QUIC/UDP с маскировкой под HTTPS
Hysteria2 строит зашифрованный туннель поверх UDP, используя QUIC:
- Нет head-of-line blocking: потеря одного пакета не блокирует остальные потоки.
- Быстрое восстановление соединения: QUIC переподключается за 0-RTT (без дополнительного round-trip).
- Маскировка под HTTPS: снаружи трафик Hysteria2 выглядит как обычный QUIC/HTTP3 браузерный трафик — сервер «притворяется» обычным HTTPS-сайтом и возвращает реальный веб-контент при неавторизованных запросах.
- Congestion control: поддерживает несколько алгоритмов управления перегрузкой, включая BBR.
| Протокол | Транспорт | Как выглядит для DPI | Устойчивость к блокировке | Скорость при потерях |
|---|---|---|---|---|
| Обычный VPN (WireGuard, OpenVPN) | UDP / TCP | Характерный шаблон | Низкая — fingerprint легко детектируется | Высокая (WG) / Низкая (OpenVPN) |
| Shadowsocks | TCP | Случайный зашифрованный поток | Средняя — выявляется по энтропии | Средняя |
| VLESS + REALITY | TCP (TLS) | TLS с реальным сертификатом стороннего сайта | Высокая | Средняя |
| NaïveProxy | TCP (HTTP/2 TLS) | Неотличим от Chrome HTTPS | Очень высокая | Средняя (TCP Head-of-Line) |
| Hysteria2 | UDP (QUIC/TLS) | QUIC/HTTP3, маскировка под HTTPS | Очень высокая | Высокая — устойчив к потерям |
Как работает Hysteria2 технически
Hysteria2 использует протокол QUIC в качестве транспорта:
- Клиент устанавливает QUIC-соединение с сервером — это TLS 1.3 поверх UDP.
- Внутри QUIC-соединения открываются независимые потоки (streams) для каждого проксируемого соединения.
- Сервер обрабатывает входящий трафик: авторизует клиента (по паролю или через внешний скрипт), затем перенаправляет трафик через указанный outbound.
- Если к серверу обращаются без правильного пароля — сервер ведёт себя как обычный HTTPS-сайт (masquerade) и возвращает реальный контент с заданного URL.
Особенности аутентификации в данной схеме:
- EU сервер: использует одиночный пароль — только для входящих соединений от RU клиента (не клиентских устройств).
- RU сервер: использует command-аутентификацию — при каждом подключении вызывается внешний bash-скрипт, который проверяет пароль по файлу
users.txt. Это позволяет добавлять и удалять пользователей без перезапуска сервера.
Каскадная цепочка: зачем два сервера
В данном решении используется каскад из двух серверов: Entry Node (ближайший к клиентам сервер-вход) и Exit Node (сервер-выход в интернет). Это архитектурное решение принято намеренно по нескольким причинам:
- Снижение риска для клиента: клиентские устройства подключаются только к ближайшему серверу. Соединение с соседним узлом менее заметно, чем прямое соединение с зарубежным сервером.
- Раздельная аутентификация: пароль «клиент → Entry Node» знают только конечные пользователи. Пароль «Entry Node → Exit Node» знают только серверы. Компрометация клиентского пароля не раскрывает пароль соединения между серверами.
- Анонимизация выхода: Exit Node передаёт трафик через Cloudflare WARP. Конечные сайты видят IP Cloudflare, а не IP ваших серверов.
- Параллельные независимые каналы: Hysteria2 работает рядом с NaïveProxy и 3x-ui на тех же серверах, не конфликтуя с ними. Падение одного канала не блокирует другие.
Схема конкретного решения
Компоненты инфраструктуры
| Компонент | Роль | Адрес / Домен | ОС |
|---|---|---|---|
| Клиентские устройства | Конечные пользователи | — | iOS, Android, Windows, macOS, Linux |
| OpenWrt роутер | Прозрачный обход для всей домашней сети | — | OpenWrt 24.10 (podkop нативно поддерживает hy2 URI) |
| RU сервер (Entry Node) | Точка входа клиентов | YOUR_RU_SERVER_IP / your-hy2-ru-domain.example.com |
Ubuntu 24.04 |
| EU сервер (Exit Node) | Точка выхода в интернет | YOUR_EU_SERVER_IP / your-hy2-eu-domain.example.com |
Ubuntu 24.04 |
| Cloudflare WARP | Финальный выход в интернет | IP Cloudflare | — |
Полная схема прохождения трафика

Протоколы на каждом участке цепочки
| Участок | Протокол | Шифрование | Что видит наблюдатель |
|---|---|---|---|
| Устройство → Роутер | Обычный TCP/UDP | Нет (локальная сеть) | Локальный трафик |
| Роутер / ПК → RU сервер | QUIC / UDP (Hysteria2) | TLS 1.3 (Let's Encrypt) | HTTPS/HTTP3 трафик к обычному сайту |
| RU клиент → EU сервер | QUIC / UDP (Hysteria2) | TLS 1.3 (Let's Encrypt) | HTTPS/HTTP3 трафик к обычному сайту |
| EU сервер → 3x-ui inbound | SOCKS5 (localhost) | — (только на EU сервере) | Только локально |
| EU 3x-ui → WARP | WireGuard (gVisor TUN) | WireGuard | Зашифрованный WireGuard к Cloudflare |
| WARP → Интернет | Обычный TCP/UDP | — | IP Cloudflare |
Дополнительные каналы (параллельно Hysteria2)
Hysteria2 работает независимо от NaïveProxy и 3x-ui. Все три канала активны одновременно:
| Канал | Протокол | Маршрут | Статус |
|---|---|---|---|
| 3x-ui каскад (основной) | VLESS / REALITY | Клиенты → RU 3x-ui → EU 3x-ui → Интернет | ✅ Активен |
| NaïveProxy каскад | HTTP/2 naïve | Роутер/ПК → RU Caddy → EU Caddy → WARP | ✅ Активен |
| Hysteria2 каскад (этот гайд) | QUIC/UDP hy2 | Клиенты → RU hy2 → EU hy2 → WARP | ✅ Активен |
| MTProto (Telegram) | MTProto | Клиенты Telegram → EU :8443, :2443 | ✅ Активен |
Предварительные требования
Инфраструктура
| Компонент | Требования |
|---|---|
| EU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| RU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| 3x-ui на EU сервере | Уже установлен и работает; настроен outbound WARP |
| Docker | Установлен на обоих серверах |
| Порт 2053 UDP | Должен быть свободен на обоих серверах |
| TLS-сертификат | Получается через Caddy (caddy-naive), либо через certbot — описано в разделе ниже |
DNS-записи (создать заранее, до установки)
Let's Encrypt проверяет доступность домена по HTTP (порт 80) перед выдачей сертификата. DNS-записи должны быть созданы и распространены (propagated) до первого запуска Caddy.
| Запись | Тип | Значение | Примечание |
|---|---|---|---|
your-hy2-ru-domain.example.com |
A | IP RU сервера | Используется Hysteria2 сервером на RU |
your-hy2-eu-domain.example.com |
A | IP EU сервера | Используется Hysteria2 сервером на EU |
Примечание: если NaïveProxy (Caddy) уже настроен на тех же серверах с теми же доменами, отдельные DNS-записи для Hysteria2 не нужны — используется тот же домен и тот же сертификат.
Используемые заглушки
В данном руководстве применяются следующие заглушки. Замени их реальными значениями перед использованием:
| Заглушка | Описание |
|---|---|
YOUR_RU_SERVER_IP |
Публичный IP RU сервера |
YOUR_EU_SERVER_IP |
Публичный IP EU сервера |
your-hy2-ru-domain.example.com |
Домен для RU сервера (для TLS-сертификата) |
your-hy2-eu-domain.example.com |
Домен для EU сервера (для TLS-сертификата) |
YOUR_EMAIL@example.com |
Email для Let's Encrypt (уведомления об истечении сертификата) |
EU_HY2_PASSWORD |
Пароль соединения RU клиент → EU сервер (генерируется один раз) |
Для генерации пароля EU_HY2_PASSWORD используй:
openssl rand -base64 18
Сохрани результат — он понадобится в конфигах обоих серверов.
Часть 1: EU сервер (Exit Node)
Цель: развернуть Hysteria2 сервер в Docker, который принимает соединения от RU клиента и перенаправляет трафик через 3x-ui → Cloudflare WARP.
Все команды в этом разделе выполняются на EU сервере под root.
1.1 Установка Docker
Если Docker уже установлен (проверить: docker --version) — пропусти этот шаг.
# Обновить пакеты
apt-get update && apt-get upgrade -y
# Установить зависимости
apt-get install -y ca-certificates curl gnupg lsb-release
# Добавить GPG-ключ Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Добавить репозиторий Docker
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
# Установить Docker Engine
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Включить автозапуск
systemctl enable docker
systemctl start docker
# Проверить
docker --version
docker compose version
Ожидаемый вывод:
Docker version 27.x.x, build ... Docker Compose version v2.x.x
1.2 Настройка 3x-ui: создание inbound hy2-out → WARP
Hysteria2 Exit Node направляет трафик через 3x-ui SOCKS5 inbound, который в свою очередь маршрутизирует его в Cloudflare WARP. Этот шаг выполняется в веб-интерфейсе 3x-ui до запуска Hysteria2.
Условие: в 3x-ui должен быть настроен outbound WARP (WireGuard/gVisor).
1.2.1 Создать новый inbound в 3x-ui
В веб-интерфейсе 3x-ui (EU сервер) перейди в Inbounds → Add Inbound и заполни следующие поля:
| Параметр | Значение | Пояснение |
|---|---|---|
| Примечание (Remark) | hy2-out |
Произвольное имя для идентификации в правилах маршрутизации |
| Протокол | mixed |
Поддерживает SOCKS5 и HTTP одновременно |
| Listen IP | 127.0.0.1 |
Только localhost — недоступен извне |
| Порт | 24364 |
grep 24364 |
| Аутентификация | Выключена | Hysteria2 сам отвечает за авторизацию клиентов |
| UDP | Включён | Требуется для DNS-запросов через туннель |
Нажми Save — inbound немедленно активируется.
1.2.2 Создать правило маршрутизации
В 3x-ui перейди в Routing → Add Rule (или Settings → Routing Rules):
| Параметр | Значение |
|---|---|
| Inbound Tag | hy2-out (тег созданного выше inbound)
|
| Outbound Tag | warp (или название твоего WARP outbound в 3x-ui)
|
Сохрани и перезапусти Xray внутри 3x-ui.
1.2.3 Проверить, что порт 24364 слушает
ss -tlnp | grep 24364
Ожидаемый вывод:
LISTEN 0 128 127.0.0.1:24364 0.0.0.0:* users:(("xray",...))
1.3 TLS-сертификат для Hysteria2
Hysteria2 требует действующий TLS-сертификат (самоподписанный не рекомендуется — клиентам потребуется флаг insecure: true). В нашей схеме используется Let's Encrypt сертификат, получаемый и автоматически обновляемый через Caddy (caddy-naive стек).
Caddy хранит сертификаты в именованном Docker volume caddy-naive_caddy_data, и Hysteria2 подключает их через bind mount (read-only) — никаких лишних сервисов, никакого дублирования.
Вариант А: Caddy (caddy-naive) уже установлен — рекомендуется
Если NaïveProxy (caddy-naive) уже настроен и работает, сертификат уже получен. Проверь:
# Найти путь к сертификату в Docker volume
DOMAIN="your-hy2-eu-domain.example.com"
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}"
ls -la "${CERT_DIR}/"
Ожидаемый вывод — два файла:
your-hy2-eu-domain.example.com.crt your-hy2-eu-domain.example.com.key
Если файлы есть — переходи к разделу 1.4. Если volume или папка не существуют — сначала установи caddy-naive (NaïveProxy) или воспользуйся Вариантом Б.
Вариант Б: получить сертификат через certbot (если Caddy не установлен)
Этот вариант используется, если caddy-naive не установлен и на серверах нет другого HTTP-сервера на портах 80/443.
# Установить certbot
apt-get install -y certbot
# Получить сертификат (standalone — Certbot запустит временный HTTP-сервер на :80)
certbot certonly --standalone \
-d your-hy2-eu-domain.example.com \
--email YOUR_EMAIL@example.com \
--agree-tos --non-interactive
# Проверить
ls /etc/letsencrypt/live/your-hy2-eu-domain.example.com/
Сертификаты будут по пути /etc/letsencrypt/live/your-hy2-eu-domain.example.com/. В этом случае в docker-compose.yml (раздел 1.6) замени volume на:
- /etc/letsencrypt/live/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro
- /etc/letsencrypt/archive/your-hy2-eu-domain.example.com:/etc/letsencrypt/archive/your-hy2-eu-domain.example.com:ro
И в server.yaml пути к файлам:
tls:
cert: /etc/hysteria/certs/fullchain.pem
key: /etc/hysteria/certs/privkey.pem
1.4 Создание структуры проекта
mkdir -p /opt/hysteria-eu
cd /opt/hysteria-eu
Итоговая структура:
/opt/hysteria-eu/ ├── docker-compose.yml └── server.yaml
1.5 Конфигурация сервера: server.yaml
Создай файл конфигурации. Замени EU_HY2_PASSWORD сгенерированным паролем:
cat > /opt/hysteria-eu/server.yaml << 'EOF'
listen: :2053
tls:
cert: /etc/hysteria/certs/your-hy2-eu-domain.example.com.crt
key: /etc/hysteria/certs/your-hy2-eu-domain.example.com.key
auth:
type: password
password: EU_HY2_PASSWORD
ignoreClientBandwidth: true
masquerade:
type: proxy
proxy:
url: https://news.ycombinator.com/
rewriteHost: true
outbounds:
- name: warp
type: socks5
socks5:
addr: 127.0.0.1:24364
acl:
inline:
- "warp(all)"
EOF
Описание ключевых параметров:
| Параметр | Значение | Пояснение |
|---|---|---|
listen: :2053 |
UDP порт 2053 | Hysteria2 слушает на всех интерфейсах |
tls.cert / tls.key |
Пути к сертификату | Берутся из caddy-naive Docker volume (bind mount) |
auth.type: password |
Одиночный пароль | Для соединения RU клиент → EU сервер (клиентские устройства этот пароль не знают) |
ignoreClientBandwidth: true |
Игнорировать лимит клиента | Серверная сторона управляет congestion control самостоятельно |
masquerade.type: proxy |
Проксировать реальный сайт | Неавторизованные запросы получают реальный контент ycombinator.com — сервер выглядит как обычный HTTPS-сайт |
outbounds.warp |
SOCKS5 на 127.0.0.1:24364 | Исходящий трафик → 3x-ui inbound "hy2-out" → WARP |
acl: warp(all) |
Направить весь трафик в warp | Синтаксис: имя outbound в скобках, адрес/маска после |
1.6 Конфигурация Docker Compose: docker-compose.yml
cat > /opt/hysteria-eu/docker-compose.yml << 'EOF'
services:
hysteria-eu:
image: tobyxdd/hysteria:latest
container_name: hysteria-eu
restart: unless-stopped
network_mode: host
command: server --config /etc/hysteria/server.yaml
volumes:
- ./server.yaml:/etc/hysteria/server.yaml:ro
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-eu-domain.example.com:/etc/hysteria/certs:ro
EOF
Важные моменты:
network_mode: host— обязателен, чтобы контейнер видел127.0.0.1:24364(3x-ui inbound).:ro— сертификаты подключены только для чтения, Hysteria2 их не изменяет.- Путь к сертификату — bind mount из Docker volume caddy-naive. Если используется certbot (Вариант Б) — замени путь согласно инструкции раздела 1.3.
1.7 Первый запуск
cd /opt/hysteria-eu
# Скачать образ и запустить
docker compose pull
docker compose up -d
# Подождать 3 секунды и проверить логи
sleep 3 && docker compose logs
Ожидаемый вывод в логах:
INFO server mode
INFO server up and running {"listen": ":2053"}
Если в логах ошибка — см. раздел «Диагностика проблем» (Часть 5).
1.8 Проверка: порт и соединение
# Проверить, что Hysteria2 слушает на UDP :2053
ss -ulnp | grep ':2053'
Ожидаемый вывод:
UNCONN 0 0 0.0.0.0:2053 0.0.0.0:* users:(("hysteria",...))
# Статус контейнера
docker compose ps
NAME IMAGE STATUS hysteria-eu tobyxdd/hysteria:latest Up X minutes
Часть 2: RU сервер (Entry Node)
Цель: развернуть два Docker-контейнера:
hysteria-ru-server— принимает соединения от клиентов, аутентифицирует поusers.txt.hysteria-ru-client— подключается к EU серверу, слушает SOCKS5 на127.0.0.1:10810.
Между ними: hysteria-ru-server направляет трафик через SOCKS5 → hysteria-ru-client → EU.
Все команды в этом разделе выполняются на RU сервере под root.
2.1 Установка Docker
Аналогично EU серверу (раздел 1.1). Если Docker уже установлен — пропусти.
apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl gnupg lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker && systemctl start docker
docker --version && docker compose version
2.2 TLS-сертификат для RU сервера
Аналогично EU серверу. На RU сервере также используется сертификат от Caddy (caddy-naive), если NaïveProxy настроен.
Вариант А: через Caddy (caddy-naive) — рекомендуется
DOMAIN="your-hy2-ru-domain.example.com"
CERT_DIR="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}"
ls -la "${CERT_DIR}/"
Если файлы .crt и .key присутствуют — всё готово, переходи к разделу 2.3.
Вариант Б: через certbot (если Caddy не установлен)
apt-get install -y certbot
certbot certonly --standalone \
-d your-hy2-ru-domain.example.com \
--email YOUR_EMAIL@example.com \
--agree-tos --non-interactive
ls /etc/letsencrypt/live/your-hy2-ru-domain.example.com/
2.3 Создание структуры проекта
mkdir -p /opt/hysteria-ru/users
cd /opt/hysteria-ru
Итоговая структура:
/opt/hysteria-ru/
├── docker-compose.yml
├── server.yaml
├── client.yaml
└── users/
├── users.txt ← список пользователей (username password)
└── check-auth.sh ← скрипт авторизации, вызывается при каждом подключении
2.4 Система аутентификации пользователей
RU сервер использует auth.type: command — при каждом входящем подключении Hysteria2 вызывает внешний скрипт и передаёт ему пароль клиента в аргументах. Скрипт ищет пароль в файле users.txt: если совпадение найдено — выводит имя пользователя (оно попадает в логи) и завершается с кодом 0. Если нет — завершается с кодом 1 (отказ в подключении).
Преимущество этого подхода: users.txt можно редактировать в любой момент без перезапуска контейнера. Изменения применяются немедленно.
2.4.1 Файл пользователей: users/users.txt
Формат: одна строка на пользователя — имя пользователя и пароль, разделённые пробелом.
# Сгенерировать первого пользователя (пример: "default")
PASS=$(openssl rand -base64 18)
echo "default $PASS" > /opt/hysteria-ru/users/users.txt
echo "Пароль пользователя default: $PASS"
Формат файла:
username1 пароль1 username2 пароль2
Важно: не используй пробелы внутри пароля — пароль считывается по второму полю строки.
2.4.2 Скрипт авторизации: users/check-auth.sh
Hysteria2 вызывает скрипт с тремя аргументами: $1 — адрес клиента, $2 — пароль, $3 — tx bandwidth.
cat > /opt/hysteria-ru/users/check-auth.sh << 'EOF'
#!/bin/sh
AUTH_PAYLOAD="$2"
USERS_FILE="/etc/hysteria/users/users.txt"
USERNAME=$(awk -v pwd="$AUTH_PAYLOAD" '$2 == pwd {print $1; exit}' "$USERS_FILE" 2>/dev/null)
if [ -n "$USERNAME" ]; then
echo "$USERNAME"
exit 0
fi
exit 1
EOF
chmod +x /opt/hysteria-ru/users/check-auth.sh
Почему используется awk, а не grep:
- Hysteria2 использует Docker-образ на базе Alpine Linux с BusyBox.
- BusyBox
grepне поддерживает флаг-P(Perl-совместимые регулярки). awkдоступен во всех Unix/Linux окружениях и работает стабильно.
2.5 Конфигурация сервера: server.yaml
cat > /opt/hysteria-ru/server.yaml << 'EOF'
listen: :2053
tls:
cert: /etc/hysteria/certs/your-hy2-ru-domain.example.com.crt
key: /etc/hysteria/certs/your-hy2-ru-domain.example.com.key
auth:
type: command
command: /etc/hysteria/users/check-auth.sh
ignoreClientBandwidth: true
masquerade:
type: proxy
proxy:
url: https://news.ycombinator.com/
rewriteHost: true
outbounds:
- name: chain
type: socks5
socks5:
addr: 127.0.0.1:10810
acl:
inline:
- "chain(all)"
EOF
Описание ключевых параметров:
| Параметр | Значение | Пояснение |
|---|---|---|
auth.type: command |
Внешний скрипт | При каждом подключении вызывается check-auth.sh
|
auth.command |
Путь внутри контейнера | Скрипт смонтирован через volume: ./users:/etc/hysteria/users
|
outbounds.chain |
SOCKS5 на 127.0.0.1:10810 | Трафик → hysteria-ru-client → EU |
acl: chain(all) |
Весь трафик через chain | Синтаксис: имя outbound, затем адрес в скобках |
2.6 Конфигурация клиента: client.yaml
RU клиент подключается к EU серверу и поднимает SOCKS5 на 127.0.0.1:10810.
cat > /opt/hysteria-ru/client.yaml << 'EOF'
server: your-hy2-eu-domain.example.com:2053
auth: EU_HY2_PASSWORD
socks5:
listen: 127.0.0.1:10810
EOF
Замени EU_HY2_PASSWORD на тот же пароль, который указан в server.yaml EU сервера.
2.7 Конфигурация Docker Compose: docker-compose.yml
cat > /opt/hysteria-ru/docker-compose.yml << 'EOF'
services:
hysteria-ru-server:
image: tobyxdd/hysteria:latest
container_name: hysteria-ru-server
restart: unless-stopped
network_mode: host
command: server --config /etc/hysteria/server.yaml
volumes:
- ./server.yaml:/etc/hysteria/server.yaml:ro
- ./users:/etc/hysteria/users:ro
- /var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your-hy2-ru-domain.example.com:/etc/hysteria/certs:ro
hysteria-ru-client:
image: tobyxdd/hysteria:latest
container_name: hysteria-ru-client
restart: unless-stopped
network_mode: host
command: client --config /etc/hysteria/client.yaml
volumes:
- ./client.yaml:/etc/hysteria/client.yaml:ro
EOF
Критически важные моменты:
- Директория
./usersмонтируется в оба контейнера... нет, только вhysteria-ru-server. Клиентский контейнер видит только свойclient.yaml. - При изменении
docker-compose.yml(добавлении volume) нужен пересоздать контейнер:docker compose down && docker compose up -d. Простойrestartне применяет изменения volumes.
2.8 Первый запуск
cd /opt/hysteria-ru
# Скачать образ
docker compose pull
# Запустить оба контейнера
docker compose up -d
# Подождать и проверить логи
sleep 3 && docker compose logs
Ожидаемый вывод:
hysteria-ru-server | INFO server mode
hysteria-ru-server | INFO server up and running {"listen": ":2053"}
hysteria-ru-client | INFO client mode
hysteria-ru-client | INFO connected to server {"udpEnabled": true, "count": 1}
hysteria-ru-client | INFO SOCKS5 server listening {"addr": "127.0.0.1:10810"}
2.9 Проверка цепочки с RU сервера
Тест: запустить временный Hysteria2 клиент на RU сервере и проверить, что трафик выходит через WARP (IP Cloudflare):
# Создать временный конфиг тест-клиента
cat > /tmp/hy2-test.yaml << EOF
server: 127.0.0.1:2053
auth: $(awk 'NR==1{print $2}' /opt/hysteria-ru/users/users.txt)
tls:
insecure: true
sni: your-hy2-ru-domain.example.com
socks5:
listen: 127.0.0.1:11080
EOF
# Запустить тест-клиент
docker run -d --name hy2-test --network host \
-v /tmp/hy2-test.yaml:/etc/hysteria/client.yaml:ro \
tobyxdd/hysteria:latest client --config /etc/hysteria/client.yaml
# Ждать 3 секунды
sleep 3
# Проверить IP выхода — должен быть Cloudflare WARP IP
echo "IP через цепочку:"
curl -s --max-time 15 -x socks5h://127.0.0.1:11080 https://ifconfig.me
# Очистить
docker rm -f hy2-test
rm -f /tmp/hy2-test.yaml
Если IP вывода принадлежит Cloudflare (начинается с 2a09: для IPv6 или относится к диапазонам WARP) — цепочка RU → EU → WARP работает корректно.
Примечание: параметр insecure: true и sni нужны в тест-клиенте, потому что он подключается по IP (127.0.0.1), а сертификат выдан на домен. Реальные клиенты подключаются по домену и такие параметры не требуются.
Часть 3: Управление пользователями (hy2-users.sh)
3.1 Что это и зачем
Для управления пользователями Hysteria2 на RU сервере предназначен скрипт hy2-users.sh. Он предоставляет интерактивное меню с набором операций над файлом users.txt.
Скрипт решает следующие задачи:
| Операция | Описание |
|---|---|
| Список пользователей | Показывает всех пользователей из users.txt с маскированными паролями |
| Добавить пользователя | Запрашивает имя, автоматически генерирует криптостойкий пароль, сохраняет в users.txt, показывает готовый Hysteria2 URI |
| Удалить пользователя | Интерактивный выбор из списка, удаление строки из users.txt |
| Показать конфиг | Генерирует и показывает Hysteria2 URI для выбранного пользователя |
Ключевые особенности:
- Нет перезапуска контейнера. Hysteria2 с
auth.type: commandчитает файл при каждом подключении — изменения вusers.txtприменяются мгновенно. - Пароли генерируются автоматически через
openssl rand— случайные, криптостойкие. - URI формат соответствует стандарту Hysteria2 и принимается всеми клиентами: NekoBox, husi, Exclave, podkop.
3.2 Установка скрипта
Скрипт хранится в репозитории по пути scripts/hy2-users.sh. Установка на сервер:
# Скопировать скрипт на сервер
scp scripts/hy2-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/hy2-users.sh
# Сделать исполняемым
ssh root@YOUR_RU_SERVER_IP "chmod +x /usr/local/bin/hy2-users.sh"
Или выполнить непосредственно на RU сервере (если скрипт уже там):
chmod +x /usr/local/bin/hy2-users.sh
Проверка переменных в начале скрипта. Открой скрипт и убедись, что следующие переменные указывают на правильные значения:
head -15 /usr/local/bin/hy2-users.sh
Должны быть:
USERS_FILE="/opt/hysteria-ru/users/users.txt"DOMAIN="your-hy2-ru-domain.example.com"PORT="2053"
Если домен указан неверно — исправь:
sed -i 's|DOMAIN=.*|DOMAIN="your-hy2-ru-domain.example.com"|' /usr/local/bin/hy2-users.sh
3.3 Использование
sudo bash /usr/local/bin/hy2-users.sh
Скрипт показывает меню:
┌─────────────────────────────────────────────┐ │ Hysteria2 — управление пользователями RU │ └─────────────────────────────────────────────┘ 1. Список пользователей 2. Добавить пользователя 3. Удалить пользователя 4. Показать конфиг клиента 0. Выход Выбери действие:
При добавлении нового пользователя скрипт выводит готовый URI:
hysteria2://СГЕНЕРИРОВАННЫЙ_ПАРОЛЬ@your-hy2-ru-domain.example.com:2053
Этот URI вставляется напрямую в клиентское приложение (см. Часть 4).
3.4 Исходный код скрипта
#!/bin/bash
# =============================================================
# hy2-users — управление пользователями Hysteria2 на RU сервере
# users.txt: /opt/hysteria-ru/users/users.txt
# Формат: username password (по одной строке)
# Перезапуск контейнера НЕ требуется — изменения применяются мгновенно
# =============================================================
USERS_FILE="/opt/hysteria-ru/users/users.txt"
DOMAIN="your-hy2-ru-domain.example.com"
PORT="2053"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# --- Вспомогательные функции ---
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash hy2-users.sh)${NC}"
exit 1
fi
}
check_users_file() {
if [[ ! -f "$USERS_FILE" ]]; then
echo -e "${RED}Ошибка: файл пользователей не найден: $USERS_FILE${NC}"
exit 1
fi
}
get_usernames() {
awk '{print $1}' "$USERS_FILE"
}
get_password() {
local user="$1"
awk -v u="$user" '$1 == u {print $2}' "$USERS_FILE"
}
press_enter() {
echo ""
read -rp "Нажми Enter для возврата в меню..."
}
print_config_for() {
local user="$1"
local pass="$2"
echo ""
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}"
echo -e "${BOLD}│ Hysteria2 URI для клиента │${NC}"
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}"
echo ""
echo -e "${BOLD} hysteria2://${pass}@${DOMAIN}:${PORT}${NC}"
echo ""
echo -e "${CYAN}Для podkop (OpenWrt):${NC} вставить URI как hy2 outbound"
echo -e "${CYAN}Для мобильных клиентов:${NC} NekoBox, husi, Exclave — импорт URI"
echo -e "${CYAN}Пользователь:${NC} ${user}"
}
# --- Функции меню ---
list_users() {
echo ""
echo -e "${BOLD}${CYAN}=== Список пользователей ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Пользователей нет.${NC}"
else
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo ""
echo -e " Всего: ${#users[@]} пользователь(ей)"
fi
press_enter
}
add_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Добавить пользователя ===${NC}"
read -rp "Имя пользователя: " username
username=$(echo "$username" | tr -d ' ')
if [[ -z "$username" ]]; then
echo -e "${RED}Имя не может быть пустым.${NC}"
press_enter
return
fi
if grep -q "^${username} " "$USERS_FILE" 2>/dev/null; then
echo -e "${RED}Пользователь '${username}' уже существует.${NC}"
press_enter
return
fi
read -rp "Пароль (Enter = сгенерировать автоматически): " password
if [[ -z "$password" ]]; then
password=$(openssl rand -base64 18 | tr -d '=/+' | head -c 22)
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}"
fi
echo "${username} ${password}" >> "$USERS_FILE"
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}"
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}"
print_config_for "$username" "$password"
press_enter
}
delete_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Удалить пользователя ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет пользователей для удаления.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер для удаления: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
target="${users[$((choice-1))]}"
read -rp "Удалить пользователя '${target}'? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
sed -i "/^${target} /d" "$USERS_FILE"
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}"
echo -e "${YELLOW}Изменения применяются мгновенно — перезапуск не нужен.${NC}"
else
echo "Отменено."
fi
press_enter
}
show_config() {
echo ""
echo -e "${BOLD}${CYAN}=== URI клиента ===${NC}"
mapfile -t users < <(get_usernames)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет пользователей.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер пользователя: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
local user="${users[$((choice-1))]}"
local pass
pass=$(get_password "$user")
print_config_for "$user" "$pass"
press_enter
}
# --- Главное меню ---
main_menu() {
check_root
check_users_file
while true; do
clear
echo -e "${BOLD}${CYAN}"
echo "╔══════════════════════════════════════╗"
echo "║ Hysteria2 User Manager (RU) ║"
echo "║ Домен: ${DOMAIN} ║"
echo "╚══════════════════════════════════════╝"
echo -e "${NC}"
mapfile -t users < <(get_usernames)
echo -e " Активных пользователей: ${BOLD}${#users[@]}${NC}"
echo ""
echo -e " ${BOLD}1.${NC} Список пользователей"
echo -e " ${BOLD}2.${NC} Добавить пользователя"
echo -e " ${BOLD}3.${NC} Удалить пользователя"
echo -e " ${BOLD}4.${NC} Показать URI клиента"
echo -e " ${BOLD}0.${NC} Выход"
echo ""
read -rp "Выбор: " option
case "$option" in
1) list_users ;;
2) add_user ;;
3) delete_user ;;
4) show_config ;;
0) echo "Выход."; exit 0 ;;
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;;
esac
done
}
main_menu
Часть 4: Настройка клиентов
4.1 Формат URI Hysteria2
Hysteria2 использует стандартизированный URI для передачи параметров подключения клиентам:
hysteria2://ПАРОЛЬ@ДОМЕН:ПОРТ
Пример:
hysteria2://aBcDeFgHiJkLmNoPqRsT@your-hy2-ru-domain.example.com:2053
URI можно получить через скрипт hy2-users.sh (пункт 4 меню — «Показать конфиг клиента»).
4.2 Мобильные клиенты (iOS / Android)
Рекомендуемые приложения с нативной поддержкой Hysteria2:
| Приложение | Платформа | Как импортировать URI |
|---|---|---|
| NekoBox | Android | Главный экран → + → Add → Hysteria2 → вставить URI |
| husi | Android | Профили → + → Hysteria2 → вставить URI |
| Exclave | iOS | Конфигурации → + → Hysteria2 → вставить URI |
| Shadowrocket | iOS | Главная → + → Тип: Hysteria2 → вставить URI |
Все эти приложения принимают URI в формате hysteria2://... напрямую — через кнопку импорта, QR-код или буфер обмена.
4.3 ПК-клиенты (Windows / macOS / Linux)
Вариант А: официальный бинарь Hysteria2
Скачать бинарь с официального репозитория: https://github.com/apernet/hysteria/releases
Создать файл конфигурации config.yaml:
server: your-hy2-ru-domain.example.com:2053
auth: ВАШ_ПАРОЛЬ
socks5:
listen: 127.0.0.1:1080
http:
listen: 127.0.0.1:8080
Запустить:
# Linux / macOS
./hysteria client --config config.yaml
# Windows (PowerShell)
.\hysteria.exe client --config config.yaml
После запуска:
- SOCKS5 прокси доступен на
127.0.0.1:1080 - HTTP прокси доступен на
127.0.0.1:8080
Вариант Б: через NekoRay / Hiddify Next (Windows / Linux)
Приложения NekoRay и Hiddify Next принимают Hysteria2 URI — импорт через меню или буфер обмена. Автоматически управляют запуском клиентского процесса.
4.4 OpenWrt / podkop
podkop (реализация на базе sing-box) нативно поддерживает Hysteria2 URI. Никакой дополнительной установки бинарей не требуется.
В интерфейсе podkop (LuCI → Network → podkop → Outbound):
- Тип:
Hysteria2 - URI:
hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053
Или через UCI:
uci set podkop.main.proxy='hysteria2://ПАРОЛЬ@your-hy2-ru-domain.example.com:2053'
uci commit podkop
/etc/init.d/podkop restart
После сохранения все устройства домашней сети автоматически получают обход без дополнительной настройки.
Часть 5: Проверка всей цепочки
5.1 Тест RU сервера: цепочка RU → EU → WARP
Выполнить на RU сервере — проверяет, что трафик выходит через WARP:
# SOCKS5 порт 10810 — это точка выхода RU клиента (= вход в EU сервер → WARP)
curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me
echo ""
Результат должен быть IP Cloudflare WARP — например, начинается с 2a09: (IPv6) или принадлежит AS13335 (Cloudflare).
Проверить AS:
IP=$(curl -s --max-time 20 -x socks5h://127.0.0.1:10810 https://ifconfig.me)
curl -s "https://ipinfo.io/${IP}/org"
echo ""
Ожидаемый вывод: строка содержит Cloudflare.
5.2 Тест с реального клиента
После добавления пользователя через hy2-users.sh и получения URI:
- Вставь URI в клиентское приложение (NekoBox, husi, Shadowrocket).
- Активируй профиль.
- Открой
https://ifconfig.meв браузере — должен отображаться IP Cloudflare WARP. - Открой
https://www.whatismybrowser.com/detect/what-is-my-ip-addressдля проверки отсутствия WebRTC утечки.
5.3 Диагностика проблем
Проблема: hysteria-eu не запускается — ошибка TLS
docker compose -f /opt/hysteria-eu/docker-compose.yml logs hysteria-eu
Возможные причины:
- Путь к сертификату неверен → проверь
lsпо пути, указанному в volumes. - Сертификат ещё не был получен Caddy → проверь, что caddy-naive запущен и прошёл ACME-проверку.
- Caddy хранит сертификат под другим именем → выполни
find /var/lib/docker/volumes/caddy-naive_caddy_data/_data -name "*.crt".
Проблема: hysteria-ru-server выдаёт auth error
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-server
Проверить вручную (на RU сервере):
# Проверить, что скрипт находится на месте внутри контейнера
docker exec hysteria-ru-server ls -la /etc/hysteria/users/
# Запустить скрипт вручную с тестовым паролем
docker exec hysteria-ru-server /etc/hysteria/users/check-auth.sh addr ТЕСТОВЫЙ_ПАРОЛЬ 0
echo "Exit code: $?"
Если Exit code: 0 — скрипт работает. Если 1 — пароль не найден в users.txt.
Проблема: hysteria-ru-client не подключается к EU
docker compose -f /opt/hysteria-ru/docker-compose.yml logs hysteria-ru-client
Возможные причины:
EU_HY2_PASSWORDвclient.yamlне совпадает с паролем в EUserver.yaml.- UDP порт 2053 заблокирован на EU сервере (firewall) → проверь
ss -ulnp | grep 2053на EU сервере. - EU сервер ещё не запущен.
Проблема: ACL ошибка при запуске
FATAL failed to load server config: invalid config: acl.inline: invalid syntax at line 1
Неверный синтаксис ACL. Правильный формат: "outbound_name(address)":
- ✅
"warp(all)"— направить всё в outbound "warp" - ✅
"chain(all)"— направить всё в outbound "chain" - ❌
"outbound(warp) all"— неверно
Часть 6: Обслуживание
6.1 Управление контейнерами
# EU сервер
cd /opt/hysteria-eu
docker compose up -d # запустить
docker compose down # остановить
docker compose restart # перезапустить
docker compose logs -f # следить за логами в реальном времени
docker compose ps # статус
# RU сервер (оба контейнера)
cd /opt/hysteria-ru
docker compose up -d
docker compose down
docker compose restart
docker compose logs -f
# RU сервер (по отдельности)
docker compose restart hysteria-ru-server # только сервер
docker compose restart hysteria-ru-client # только клиент
docker compose logs -f hysteria-ru-server # логи только сервера
docker compose logs -f hysteria-ru-client # логи только клиента
6.2 Обновление сертификатов
TLS-сертификаты обновляются Caddy автоматически (за 30 дней до истечения). После автообновления Hysteria2 начнёт использовать новый сертификат при следующем перезапуске.
Для принудительного применения нового сертификата:
# EU сервер
cd /opt/hysteria-eu && docker compose restart
# RU сервер
cd /opt/hysteria-ru && docker compose restart hysteria-ru-server
Проверить дату истечения текущего сертификата:
# EU сервер
DOMAIN="your-hy2-eu-domain.example.com"
CERT="/var/lib/docker/volumes/caddy-naive_caddy_data/_data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}.crt"
openssl x509 -in "${CERT}" -noout -enddate
6.3 Обновление Docker-образа
Hysteria2 активно развивается. Для обновления до последней версии:
# EU сервер
cd /opt/hysteria-eu
docker compose pull # скачать новый образ
docker compose up -d # перезапустить с новым образом
docker compose logs --tail=5 # проверить старт
# RU сервер
cd /opt/hysteria-ru
docker compose pull
docker compose up -d
docker compose logs --tail=5
Старые неиспользуемые образы можно удалить:
docker image prune -f
6.4 Мониторинг логов
Hysteria2 пишет структурированные JSON-логи. Полезные паттерны для мониторинга:
# Смотреть все подключения на RU сервере
docker logs hysteria-ru-server 2>&1 | grep -E "connected|disconnected|auth"
# Смотреть ошибки
docker logs hysteria-ru-server 2>&1 | grep -i "error\|fatal\|warn"
# Считать количество уникальных пользователей (поле "id")
docker logs hysteria-ru-server 2>&1 | grep '"id"' | grep -oP '"id": "\K[^"]+' | sort -u
# Следить за логами EU сервера в реальном времени
docker logs -f hysteria-eu 2>&1
Пример нормального лога RU сервера при входящем подключении:
INFO client connected {"addr": "CLIENT_IP:PORT", "id": "username"}
INFO TCP request {"addr": "CLIENT_IP:PORT", "id": "username", "reqAddr": "example.com:443"}
INFO client disconnected {"addr": "CLIENT_IP:PORT", "id": "username", "error": "..."}
Поле "id" — это имя пользователя, возвращённое скриптом check-auth.sh. Это позволяет отслеживать активность конкретных пользователей в логах без хранения паролей.