From 73754dbc0130794e525a8b1e0dff234bc2d3f458 Mon Sep 17 00:00:00 2001 From: hacker07 Date: Tue, 13 Oct 2026 23:39:50 +0300 Subject: [PATCH] =?UTF-8?q?v1.2.17:=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BC?= =?UTF-8?q?=D0=B0=D1=81=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D1=8B=D0=BC=20(=D0=BC=D0=B5=D0=BD=D1=8E=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B0=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B1=D0=B8=D0=B2=D0=B0=D0=B5=D1=82)=20+=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20ask=5Fyes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FORK-CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- scripts/3x-ui-local.sh | 2 +- scripts/3x-ui.sh | 25 ++++++++++++++++++++----- scripts/hysteria2.sh | 2 +- scripts/kit.sh | 2 +- tools/fork_apply.py | 11 +++++++++-- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/FORK-CHANGELOG.md b/FORK-CHANGELOG.md index 3a60cec..6943f8e 100644 --- a/FORK-CHANGELOG.md +++ b/FORK-CHANGELOG.md @@ -1,3 +1,15 @@ +## v1.2.17: меню маскировки идёт ПЕРВЫМ (меню автора больше не перебивает) + +* Причина, по которой меню не было видно: **меню спрашивал сам установщик** («Свой домен / Сосед / + Свой сайт») — и до нашего скана подсети дело не доходило. Теперь, когда включено наше меню, + меню автора не показывается вообще: сразу скан, список найденных доменов и выбор — из найденных, + автовыбор, общедоступный или свой домен. Отключить и вернуть поведение автора: `--auto-sni`. +* Ответ меню помечается: если вы ввели домен, который указывает на этот сервер, маскировка идёт + «под свой домен» (сертификат Let's Encrypt выпустит установщик); если не указывает — маскируюсь + под него как под чужой сайт и прямо об этом сообщаю. +* Входит исправление 1.2.16: возвращён `ask_yes` (в 1.2.13–1.2.15 при установке печаталось + `ask_yes: command not found`, и значения брались по умолчанию) + гейт «определение раньше вызова». + ## v1.2.16: возвращён ask_yes (вылезало «command not found» при установке) * При переносе вопросов наверх (v1.2.13) потерялось определение функции `ask_yes` — установщик diff --git a/VERSION b/VERSION index f69752a..b66183a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.16 +1.2.17 diff --git a/scripts/3x-ui-local.sh b/scripts/3x-ui-local.sh index 851bdd3..720cbd5 100644 --- a/scripts/3x-ui-local.sh +++ b/scripts/3x-ui-local.sh @@ -44,7 +44,7 @@ set -Eeuo pipefail -KIT_VERSION="1.2.16" # версия сборки форка; должна совпадать с VERSION релиза +KIT_VERSION="1.2.17" # версия сборки форка; должна совпадать с VERSION релиза GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}" NS="3x-ui-kit-release" SIG_ID="releases@3x-ui-kit" diff --git a/scripts/3x-ui.sh b/scripts/3x-ui.sh index b8fc56f..de36957 100755 --- a/scripts/3x-ui.sh +++ b/scripts/3x-ui.sh @@ -30,7 +30,7 @@ wait_apt_idle() { XUI_VERSION="v3.9.0" # SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30). XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6" -KIT_VERSION="1.2.16" +KIT_VERSION="1.2.17" # kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main. KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION" # Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash, @@ -576,7 +576,7 @@ kl_sni_menu() { [[ -z $ans ]] && return 0 [[ $ans =~ ^[0-9]+$ ]] || { printf ' введите номер из списка\n' >&2; continue; } i=$((10#$ans)) - if ((n)) && ((i >= 1 && i <= n)); then printf '%s' "${found[i - 1]}"; return 0; fi + if ((n)) && ((i >= 1 && i <= n)); then printf 'sni:%s' "${found[i - 1]}"; return 0; fi ((i == auto_idx)) && return 0 if ((i == known_idx)); then printf '\nОбщедоступные сайты:\n' >&2 @@ -586,7 +586,7 @@ kl_sni_menu() { read -r -t 300 ans <"$src" 2>/dev/null || true ans=${ans//[[:space:]]/} if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then - printf '%s' "${known[10#$ans - 1]}"; return 0 + printf 'sni:%s' "${known[10#$ans - 1]}"; return 0 fi printf ' ничего не выбрано — оставляю автовыбор\n' >&2 return 0 @@ -596,7 +596,7 @@ kl_sni_menu() { ans="" read -r -t 300 ans <"$src" 2>/dev/null || true ans=${ans//[[:space:]]/} - [[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] && { printf '%s' "$ans"; return 0; } + [[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] && { printf 'own:%s' "$ans"; return 0; } printf ' не похоже на адрес сайта — оставляю автовыбор\n' >&2 return 0 fi @@ -609,7 +609,9 @@ kl_sni_menu() { if [[ -n $DOMAIN ]]; then [[ $PANEL_SSL == ip ]] || die "Свой домен работает с сертификатом панели Let's Encrypt на IP: освободите порт 80 и не указывайте --cert, --key и --panel-ssl none (сертификат для домена установщик получит сам)." domain_points_here "$DOMAIN" || die "Исправьте A-запись домена и запустите скрипт снова (DNS обновляется от нескольких минут до нескольких часов)." - elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : /dev/null; then + # Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе + # человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит. + elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : /dev/null; then choose_masking fi if [[ -n $DOMAIN ]]; then @@ -625,6 +627,17 @@ kl_sni_menu() { if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : /dev/null; then local pick pick2 pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"}) + case $pick in + own:*) + DOMAIN=${pick#own:} + if domain_points_here "$DOMAIN" 2>/dev/null; then + SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let's Encrypt выпустит установщик)" + else + SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт" + fi + ;; + sni:*) SNI=${pick#sni:} ;; + esac if ((${#nb[@]})); then [[ -n $pick ]] && SNI=$pick || SNI=${nb[0]} elif [[ -n $pick ]]; then @@ -639,6 +652,8 @@ kl_sni_menu() { fi if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"}) + [[ $pick2 == sni:* ]] && pick2=${pick2#sni:} + [[ $pick2 == own:* ]] && pick2=${pick2#own:} [[ -n $pick2 ]] && SNI2=$pick2 [[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}" fi diff --git a/scripts/hysteria2.sh b/scripts/hysteria2.sh index d7074eb..1664f57 100755 --- a/scripts/hysteria2.sh +++ b/scripts/hysteria2.sh @@ -8,7 +8,7 @@ # Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём # останавливается и не заменяет команду hy2. -KIT_VERSION="1.2.16" +KIT_VERSION="1.2.17" cat >&2 <<'MSG' Отдельный установщик Hysteria2 больше не поддерживается. diff --git a/scripts/kit.sh b/scripts/kit.sh index 9a57077..6c8faac 100644 --- a/scripts/kit.sh +++ b/scripts/kit.sh @@ -12,7 +12,7 @@ set -Eeuo pipefail export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам -KIT_VERSION="1.2.16" +KIT_VERSION="1.2.17" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main" # Файлы новой версии берём из её тега, а не из меняющейся ветки main. kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; } diff --git a/tools/fork_apply.py b/tools/fork_apply.py index 09305c9..bca4b97 100644 --- a/tools/fork_apply.py +++ b/tools/fork_apply.py @@ -300,9 +300,16 @@ def main(): 'scripts/3x-ui.sh', ' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n', False, - ' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# role — подпись для человека: REALITY или XHTTP/MTProto.\nkl_sni_menu() {\n local role=$1; shift\n local -a found=("$@")\n local -a known=("${SNI_CANDIDATES[@]}")\n local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try\n n=${#found[@]}\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((n)); then\n printf \'Найдено в подсети:\\n\\n\' >&2\n for i in "${!found[@]}"; do printf \' %d - %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))\n printf \' %d - автовыбор из списка\\n\' "$auto_idx" >&2\n printf \' %d - выбрать общедоступный (например %s, %s)\\n\' "$known_idx" "${known[0]}" "${known[1]}" >&2\n printf \' %d - ввести свой домен\\n\\n\' "$own_idx" >&2\n else\n auto_idx=0; known_idx=1; own_idx=2\n printf \'В подсети доменов не найдено, выберите:\\n\\n\' >&2\n printf \' 1 - общедоступный (например %s, %s)\\n\' "${known[0]}" "${known[1]}" >&2\n printf \' 2 - ввести свой домен\\n\\n\' >&2\n fi\n printf \'Ваш выбор (Enter — автовыбор): \' >&2\n for try in 1 2 3; do\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ -z $ans ]] && return 0\n [[ $ans =~ ^[0-9]+$ ]] || { printf \' введите номер из списка\\n\' >&2; continue; }\n i=$((10#$ans))\n if ((n)) && ((i >= 1 && i <= n)); then printf \'%s\' "${found[i - 1]}"; return 0; fi\n ((i == auto_idx)) && return 0\n if ((i == known_idx)); then\n printf \'\\nОбщедоступные сайты:\\n\' >&2\n for k in "${!known[@]}"; do printf \' %d - %s\\n\' "$((k + 1))" "${known[k]}" >&2; done\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'%s\' "${known[10#$ans - 1]}"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n', + ' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# role — подпись для человека: REALITY или XHTTP/MTProto.\nkl_sni_menu() {\n local role=$1; shift\n local -a found=("$@")\n local -a known=("${SNI_CANDIDATES[@]}")\n local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try\n n=${#found[@]}\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((n)); then\n printf \'Найдено в подсети:\\n\\n\' >&2\n for i in "${!found[@]}"; do printf \' %d - %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))\n printf \' %d - автовыбор из списка\\n\' "$auto_idx" >&2\n printf \' %d - выбрать общедоступный (например %s, %s)\\n\' "$known_idx" "${known[0]}" "${known[1]}" >&2\n printf \' %d - ввести свой домен\\n\\n\' "$own_idx" >&2\n else\n auto_idx=0; known_idx=1; own_idx=2\n printf \'В подсети доменов не найдено, выберите:\\n\\n\' >&2\n printf \' 1 - общедоступный (например %s, %s)\\n\' "${known[0]}" "${known[1]}" >&2\n printf \' 2 - ввести свой домен\\n\\n\' >&2\n fi\n printf \'Ваш выбор (Enter — автовыбор): \' >&2\n for try in 1 2 3; do\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ -z $ans ]] && return 0\n [[ $ans =~ ^[0-9]+$ ]] || { printf \' введите номер из списка\\n\' >&2; continue; }\n i=$((10#$ans))\n if ((n)) && ((i >= 1 && i <= n)); then printf \'sni:%s\' "${found[i - 1]}"; return 0; fi\n ((i == auto_idx)) && return 0\n if ((i == known_idx)); then\n printf \'\\nОбщедоступные сайты:\\n\' >&2\n for k in "${!known[@]}"; do printf \' %d - %s\\n\' "$((k + 1))" "${known[k]}" >&2; done\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'sni:%s\' "${known[10#$ans - 1]}"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'own:%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n', 'авто-шов: insert, строк 3', ), + ( + 'scripts/3x-ui.sh', + ' elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : /dev/null; then\n', + False, + ' # Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе\n # человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.\n elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : /dev/null; then\n', + 'авто-шов: replace, строк 1', + ), ( 'scripts/3x-ui.sh', ' echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"\n', @@ -314,7 +321,7 @@ def main(): 'scripts/3x-ui.sh', ' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n', False, - ' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : /dev/null; then\n local pick pick2\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n if ((${#nb[@]})); then\n [[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}\n elif [[ -n $pick ]]; then\n SNI=$pick\n fi\n if [[ -n $SNI ]]; then\n if ! sni_ok "$SNI"; then\n warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"\n SNI="${nb[0]:-$SNI}"\n fi\n ok "маскировка REALITY: ${B}$SNI${N}"\n fi\n if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})\n [[ -n $pick2 ]] && SNI2=$pick2\n [[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"\n fi\n fi\n if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"\n else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi\n if [[ -z $SNI ]] && ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}\n', + ' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : /dev/null; then\n local pick pick2\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n case $pick in\n own:*)\n DOMAIN=${pick#own:}\n if domain_points_here "$DOMAIN" 2>/dev/null; then\n SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let\'s Encrypt выпустит установщик)"\n else\n SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт"\n fi\n ;;\n sni:*) SNI=${pick#sni:} ;;\n esac\n if ((${#nb[@]})); then\n [[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}\n elif [[ -n $pick ]]; then\n SNI=$pick\n fi\n if [[ -n $SNI ]]; then\n if ! sni_ok "$SNI"; then\n warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"\n SNI="${nb[0]:-$SNI}"\n fi\n ok "маскировка REALITY: ${B}$SNI${N}"\n fi\n if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})\n [[ $pick2 == sni:* ]] && pick2=${pick2#sni:}\n [[ $pick2 == own:* ]] && pick2=${pick2#own:}\n [[ -n $pick2 ]] && SNI2=$pick2\n [[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"\n fi\n fi\n if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"\n else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi\n if [[ -z $SNI ]] && ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}\n', 'авто-шов: replace, строк 2', ), (