From 25df91e79a3cf548047745ee94fdc2fd284f6805 Mon Sep 17 00:00:00 2001 From: maxdmitrov <56165502+maxdmitrov@users.noreply.github.com> Date: Tue, 6 Oct 2026 18:40:01 +0300 Subject: [PATCH] =?UTF-8?q?Hysteria2:=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83?= =?UTF-8?q?=D1=88=D0=BA=D0=B0=20=D0=B1=D0=B0=D0=B9=D1=82=20=D0=B2=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B9=D1=82=20=D0=BA=D0=B0=D0=BA=20=D1=83=20nginx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $(cat …) срезал последний перевод строки, и страница masquerade была на байт короче той, что отдаёт nginx. Читаю файл целиком (установщик и kit net masq on), запасная запись страницы – без лишнего перевода строки. --- CHANGELOG.md | 2 +- scripts/3x-ui.sh | 5 +++-- scripts/kit.sh | 3 ++- tools/test/stub_test.py | 23 +++++++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f28d9a3..3b7c8b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * Раздельная маршрутизация: `kit net split on` – и через VPN идёт только список популярных иностранных сервисов (нейросети, видео, соцсети, мессенджеры, GitHub, Google и другие), а всё остальное напрямую. Правила приезжают вместе с подпиской, домашним ничего настраивать не нужно: приложения на Mihomo (Clash Verge, FlClash, Mihomo Party) берут их из подписки, Happ – заголовком, приложения на JSON-подписке Xray – из конфига. DNS не течёт, проверено клиентами Mihomo и Xray. Список в `/etc/kit-sub/rules.yaml`, проверка по базам категорий `kit net split check`, после правки `kit net split apply`, он входит в `kit backup`. Идея и первый набросок – [VasilevVitalii](https://github.com/VasilevVitalii), спасибо. У v2rayN, Karing, Hiddify и приложений с обычным списком ссылок подписка прежняя. * Исправил `--restore` на сервер с другим IP: ссылки части протоколов (адреса в таблице `hosts` панели и в настройках kit-sub) оставались со старым IP, а проверка панели после запуска могла ложно сообщить «Панель не отвечает», если панель стартует не сразу. А файлы настроек из копии `--restore` теперь читает строго по списку, как охранник на входе: лишнее имя – и копия не принимается, ничего не меняется. Охранник теперь смотрит и в лицо: каждое значение проверяется по своему формату (порт – цифры, путь – буквы и цифры, адрес – адрес), а странное – повод не пустить копию, а не молча что-то поправить. * Исправил `kit backup`: из `/root` он падал с «No such file or directory», работал только из `/`. Теперь работает откуда угодно. Если токен API панели в `/etc/x-ui/install-result.env` не подошёл (так бывает после обновления с 1.0), `kit` сам выпускает новый и сохраняет его, а если панель всё же не отвечает – говорит по-человечески (код ответа и вероятная причина), а не «Unsupported HTTP version in response». -* Сайт-заглушка теперь у каждого сервера своя: при установке она собирается из двадцати тем и пяти вёрсток со случайным названием, текстами и цветами. Пекарня, переводчик или мастерская по велосипедам – как повезёт. Раньше у многих серверов был один и тот же «облачный» сосед. +* Сайт-заглушка теперь у каждого сервера своя: при установке она собирается из двадцати тем и пяти вёрсток со случайным названием, текстами и цветами. Пекарня, переводчик или мастерская по велосипедам – как повезёт. Раньше у многих серверов был один и тот же «облачный» сосед. Hysteria2 показывает ту же страницу байт в байт: раньше теряла по дороге последний перевод строки – мелочь, а близнецы должны быть одинаковыми. * `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела, а на бессмыслицу вместо запроса – честным `400 Bad Request` со строкой статуса. Ещё он говорит `Connection: keep-alive` и соглашается на `http/1.1` при знакомстве по TLS. Как настоящий nginx, а не его двоюродный брат. * Исправил `kit net masq on`: Hysteria2 после него переставала слушать порт до перезапуска панели, а `kit net fp` и `kit net on` спотыкались о то же. Панель меняет подключение на ходу, а ядро такую правку не принимает. Теперь `kit` проверяет порты после каждой правки и, если что-то закрылось, перезапускает ядро и честно об этом говорит. А `kit check` научился смотреть, запущено ли ядро Xray и слушает ли каждое подключение свой порт, TCP и UDP отдельно: nginx на 443/tcp больше не выдаёт себя за Hysteria2 на 443/udp. * `--restore` на новый IP, где Let's Encrypt не выдал сертификат (лимит или закрытый порт 80), раньше говорил «Готово», а ядро Xray не запускалось: подключения ждали сертификат, которого нет. Теперь я ставлю самоподписанный, закрепляю его отпечаток в ссылках и прямо говорю об этом. А если ядро всё же не поднялось, «Готово» не будет: покажу, где смотреть. Копию с пустой папкой сертификата `--restore` теперь отклоняет сразу, а не на полпути установки. diff --git a/scripts/3x-ui.sh b/scripts/3x-ui.sh index 9809237..8630cf7 100755 --- a/scripts/3x-ui.sh +++ b/scripts/3x-ui.sh @@ -691,7 +691,8 @@ main() { install -d -m 755 /var/www/kit [[ -s /var/www/kit/index.html ]] || stub_site >/var/www/kit/index.html chmod 644 /var/www/kit/index.html - STUB_HTML=$(cat /var/www/kit/index.html) + # Байт в байт как у nginx: $(…) срезал бы последний перевод строки, и длина ответа Hysteria2 отличалась бы. + STUB_HTML=$(cat /var/www/kit/index.html; printf x); STUB_HTML=${STUB_HTML%x} local p for p in "${PROTOS[@]}"; do "proto_$p"; done # Первый пользователь – сразу на всех протоколах (как «kit user add»). @@ -1413,7 +1414,7 @@ setup_nginx() { fi install -d -m 755 /var/www/kit - [[ -f /var/www/kit/index.html ]] || { if [[ -n ${STUB_HTML:-} ]]; then printf '%s\n' "$STUB_HTML"; else stub_site; fi; } >/var/www/kit/index.html + [[ -f /var/www/kit/index.html ]] || { if [[ -n ${STUB_HTML:-} ]]; then printf '%s' "$STUB_HTML"; else stub_site; fi; } >/var/www/kit/index.html # Маршруты – из текущих подключений панели: сайты REALITY, пути WebSocket, сервисы gRPC. local list reality_sni xhttp_sni mt_sni steal_domain locs="" kind path port diff --git a/scripts/kit.sh b/scripts/kit.sh index 7a4ff4e..2a6d90a 100644 --- a/scripts/kit.sh +++ b/scripts/kit.sh @@ -1764,7 +1764,8 @@ hy_masq() { # on|off id=$(api GET inbounds/list | jq -r '[.[] | select(.protocol == "hysteria")][0].id // empty') [[ -n $id ]] || die "На сервере нет Hysteria2." if [[ $act == on ]]; then - page=$(cat /var/www/kit/index.html 2>/dev/null || true) + # Байт в байт как у nginx (с последним переводом строки). + page=$(cat /var/www/kit/index.html 2>/dev/null && printf x || true); page=${page%x} # Страницы нет только у старых установок с отдельными портами – тогда простая страница без названия. [[ -n $page ]] || page='Welcome

Welcome

This site is being updated. Please check back soon.

' inbound_patch "$id" '.streamSettings |= ((if type == "string" then fromjson else . end) | .hysteriaSettings.masquerade = {type: "string", content: $c, statusCode: 200, headers: {"content-type": "text/html; charset=utf-8"}})' --arg c "$page" \ diff --git a/tools/test/stub_test.py b/tools/test/stub_test.py index 4786f3e..fad86f9 100644 --- a/tools/test/stub_test.py +++ b/tools/test/stub_test.py @@ -73,5 +73,28 @@ class Stub(unittest.TestCase): self.assertLess(ends.count(top), N * 0.08, (top, ends.count(top))) +class SamePage(unittest.TestCase): + """Hysteria2 (masquerade) отдаёт заглушку байт в байт как nginx – с последним переводом строки.""" + + def read(self, line, var): + import tempfile + d = tempfile.mkdtemp() + f = os.path.join(d, "index.html") + with open(f, "wb") as fh: + fh.write(b"\n

x

\n\n") + line = line.replace("/var/www/kit/index.html", f) + r = subprocess.run(["bash", "-c", line + '\nprintf %s "$' + var + '"'], capture_output=True) + return r.stdout + + def test_installer(self): + line = re.search(r"^ STUB_HTML=\$\(cat /var/www/kit/index\.html.*$", src, re.M).group(0) + self.assertEqual(self.read(line, "STUB_HTML"), b"\n

x

\n\n") + + def test_kit_masq(self): + kit = open(os.path.join(root, "scripts", "kit.sh"), encoding="utf-8").read() + line = re.search(r"^ page=\$\(cat /var/www/kit/index\.html.*$", kit, re.M).group(0) + self.assertEqual(self.read(line, "page"), b"\n

x

\n\n") + + if __name__ == "__main__": unittest.main()