Compare commits

...
2 Commits
10 changed files with 136 additions and 86 deletions
+16
View File
@@ -1,3 +1,19 @@
## v1.2.1.4: короткий итог установки, автовыбор в общедоступных сайтах, команда про порты
* **Итог установки вместо простыни текста** — компактный блок: сборка, сервер, протоколы, состояние
сертификата панели, одна строка проверки. Дальше только то, что нужно человеку: доступ к панели (адрес,
логин, пароль — по просьбе из ранних правок), указатель на меню kit для ссылок и QR, открытые порты и
команда для закрытия лишних. Длинные отчёты (состояние защиты, полный вывод kit check) убраны из
установки — они остались доступны командами, в том числе `kit local hardening`.
* **Ссылки на подключения больше не попадают в вывод установки даже косвенно**: раньше печатался весь
файл /root/3x-ui.txt, а в нём лежат и ссылки vless://. Теперь из него берутся только строки панели —
адрес, логин, пароль.
* **В меню общедоступных сайтов последним пунктом добавлен автовыбор** (раньше можно было только выбрать
номером или вернуться).
* **Новая команда `kit local ports`** — что открыто в ufw, что реально нужно (порты подключений и SSH),
что лишнее; `kit local ports --close-extra` закрывает лишнее.
* Ошибка подготовки нашей заглушки теперь печатает причину, а не просто «не смог».
## v1.2.1.3: ссылки и QR — в меню kit; сертификат панели видно в итоге
* **Ссылки для подключения и QR-код больше не печатаются в выводе установки.** Их смотрят позже, когда
+8 -8
View File
@@ -1,9 +1,9 @@
# 3X-UI KIT 1.2.1.2
# 3X-UI_autoVPS 1.2.1.2
38f3ed0ed83ce13db2a8c432f972367d0203766d2d74a1d1873faf66149a5b61 scripts/3x-ui.sh
818da230052d20d0d2a8b2917ad171064d1779aeb73d9547856a271ad838d88e scripts/hysteria2.sh
e27a6fa4006aa9da1a2cb07c6fc738a8e8b2a6034796072b72783dd762283b69 scripts/kit.sh
# 3X-UI KIT 1.2.1.3
# 3X-UI_autoVPS 1.2.1.3
dc3668609015e68fe008026cd9e3456305f019cd04bffccf59c56987d4511e6c scripts/3x-ui.sh
020884ccc78e07108b145975377b29213e2d0d04b21ece51c1bf5028071748ff scripts/hysteria2.sh
5c50d0f6d192952ddf685f7834e5ff1ce4f23edab3721576319caacd23b7d25b scripts/kit.sh
f214671464e1c2e174e5af9df42c95567a9b4a9141f31a39f7df2e971e89d240 scripts/kit-sub.py
79e53e4c4ffcfdbcf1209196901ac48c4c76c32ae677ef5800d3658f51ca1a55 scripts/kit-local.sh
c3c78f83b45a2d91770dc176868d39615af8810c24788ecc2182f01a6f42d17b scripts/3x-ui-local.sh
e883e04df32625e016ce82cceb298382bbf2516732904ae678adb404e35f9f02 VERSION
04855a17e360eefb025f4fd6d1e8e49cb8fa09287b9b8884a4cb53d4b96815a1 scripts/kit-local.sh
33c9dab6ce6f82a4a0ead93108b20e25c1030c9863e92a3fa6128cc674df187e scripts/3x-ui-local.sh
3030c01eabf883790e48256b43cf177b159b87f2f69a6c196c8d13bcf765cdd3 VERSION
+2 -2
View File
@@ -1,6 +1,6 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
AAALc3NoLWVkMjU1MTkAAABAU4cGdoBrnz4R6adcDRQFepIa/JUVRlPmMeGMdRBd+5nZ+S
qy/zrjukL+j4OXyy5pGEMGdYwNbIZAPnCe7C89Dg==
AAALc3NoLWVkMjU1MTkAAABA14Sm1dcHury6A2+pEYc8Tj4tK3iEMVgp1DibjGQxLWdedu
kMFFfU3f+3pWyLm0yw/piCgRY+9NVxoFXap1ogDw==
-----END SSH SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
1.2.1.3
1.2.1.4
+51 -63
View File
@@ -44,7 +44,7 @@
set -Eeuo pipefail
KIT_VERSION="1.2.1.3" # версия сборки форка; должна совпадать с VERSION релиза
KIT_VERSION="1.2.1.4" # версия сборки форка; должна совпадать с VERSION релиза
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
NS="3x-ui-kit-release"
SIG_ID="releases@3x-ui-kit"
@@ -457,13 +457,13 @@ if [[ $site != off ]]; then
esac
if [[ -n $stub_n ]]; then
install -d -m 755 /var/www/kit
if ( source "$tmp/scripts/kit-local.sh" 2>/dev/null; kl_site_html "$stub_n" ) >/var/www/kit/index.html 2>/dev/null \
if ( source "$tmp/scripts/kit-local.sh" 2>/dev/null; kl_site_html "$stub_n" ) >/var/www/kit/index.html 2>"$tmp/stub.err" \
&& [[ -s /var/www/kit/index.html ]]; then
stub_staged=yes
rm -f "$tmp/var/www/kit/index.html"
say "заглушка: наш сайт №$((stub_n + 1)) — ставится сразу, авторская страница не используется"
else
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site)"
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site). Причина: $(head -c 200 "$tmp/stub.err" 2>/dev/null | tr '\n' ' ')"
fi
fi
fi
@@ -525,78 +525,66 @@ if [[ -n $ssh_port ]]; then
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
fi
# ------------------------------------------------------------------ проверка
# ------------------------------------------------------------------ итог: коротко и по делу
if [[ $do_check == yes ]]; then
say "проверяю, что получилось"
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix"
kit local hardening
if [[ $do_check == yes || $show_sub == yes ]]; then
# kit check печатает много строк: человеку нужны только замечания и одна строка «всё ли в порядке».
chk=$(kit check 2>&1 || true)
problems=$(printf '%s\n' "$chk" | grep -cE '^❌|^⚠|^!' || true)
[[ $problems == 0 ]] || printf '%s\n' "$chk" | grep -E '^❌|^⚠|^!' | sed 's/^/ /'
ports_open=""
command -v ufw >/dev/null 2>&1 && ports_open=$(ufw status 2>/dev/null | awk '$2 == "ALLOW" { printf "%s ", $1 }')
ip_show=$(hostname -I 2>/dev/null | awk '{ print $1 }')
os_show=$( . /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}" )
cert_show="без сертификата: панель открывается через SSH-туннель"
if [[ -s /root/cert/ip/fullchain.pem ]]; then
if openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
cert_show="${G}Let's Encrypt на IP — доверенный, браузер не ругается${N}"
else
cert_show="${Y}самоподписанный — браузер предупредит${N}"
fi
fi
protos_show=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
cat <<EOF
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea).
${B}────────────── Установка завершена ──────────────${N}
сборка: 3X-UI_autoVPS $KIT_VERSION (канал: наш Gitea)
сервер: ${ip_show:-?}${os_show:+ · $os_show}
протоколы: ${protos_show:-—}
сертификат: $cert_show
проверка: $([[ $problems == 0 ]] && echo 'замечаний нет' || echo "замечаний: $problems (подробности: kit check)")
${B}───────────────────────────────────────────────────${N}
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
если что-то не так: kit check --deep, затем kit fix
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
${B}Доступ к панели${N} (файл /root/3x-ui.txt, виден только root)
EOF
if grep -aqE '^ (Панель|Логин|Пароль):' /root/3x-ui.txt 2>/dev/null; then
grep -aE '^ (Панель|Логин|Пароль):' /root/3x-ui.txt | sed 's/^/ /'
else
warn "данных панели в /root/3x-ui.txt нет — смотрите вывод установщика выше"
fi
cat <<EOF
# ------------------------------------------------------------------ SSH: как закрыть прежние порты
${B}Ссылки для подключения и QR-код${N} — в меню kit
kit → Пользователи → Показать ссылки из консоли: kit local links
EOF
if [[ -n $ports_open ]]; then
cat <<EOF
${B}Порты${N}
открыто: $ports_open
что нужно и что лишнее: kit local ports закрыть лишние: kit local ports --close-extra
EOF
fi
if [[ -n ${ssh_port:-} ]]; then
cat <<EOF
${Y}${B}SSH переведён на порт $ssh_port.${N} Прежние порты (${ssh_ports_before:-22}) пока работают — это
сделано специально, чтобы вы не потеряли доступ. Порядок обязательный:
1) во ВТОРОМ окне проверьте вход: ssh -p $ssh_port root@${HOST:-<IP сервера>}
2) вход работает — закройте прежние порты: ${B}kit local ssh --close-old $ssh_port${N}
3) поправьте порт у себя: ~/.ssh/config, терминалы, мониторинг
${Y}SSH переведён на порт $ssh_port.${N} Прежние (${ssh_ports_before:-22}) пока открыты намеренно:
1) проверьте вход во втором окне: ssh -p $ssh_port root@${ip_show:-<IP>}
2) закройте прежние порты: kit local ssh --close-old $ssh_port
EOF
fi
cat <<EOF
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
if [[ $show_sub == yes ]]; then
say "панель 3X-UI: адрес, логин и пароль"
if [[ -s /root/3x-ui.txt ]]; then
printf ' файл: /root/3x-ui.txt (виден только root). Сохраните данные себе:\n'
sed 's/^/ /' /root/3x-ui.txt
else
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
fi
# Наш шов: показываем состояние сертификата панели. Панель и подписка живут на IP, поэтому
# сертификат Let's Encrypt на IP — не мелочь: с ним браузер и приложения не ругаются.
cert=/root/cert/ip/fullchain.pem
if [[ -s $cert ]] && openssl x509 -in "$cert" -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
printf ' %sсертификат панели: Let'"'"'s Encrypt на IP — доверенный, браузер не ругается%s\n' "$G" "$N"
elif [[ -s $cert ]]; then
printf ' %sсертификат панели: самоподписанный — браузер предупредит, подписка в приложениях может не открыться%s\n' "$Y" "$N"
till2=
till2=$(grep -rhoE 'retry after [0-9-]+ [0-9:]+ UTC' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed 's/^retry after //')
if [[ -n $till2 ]]; then
printf ' %sLet'"'"'s Encrypt на IP: лимит выпусков, повтор возможен после %s — тогда запустите установщик снова, он возьмёт сертификат%s\n' "$D" "$till2" "$N"
fi
fi
if grep -qaE '^Панель: ?https?://[0-9]' /root/3x-ui.txt 2>/dev/null; then
printf ' Завести свой домен и перейти на него: kit net panel domain%s\n' "$N"
fi
fi
# ------------------------------------------------------------------ подписка, QR-код и доступы
# Показываем в самом конце, после всех проверок: это то, что человек уносит с собой.
# Печатает авторская команда (подписка + QR-код через qrencode + ссылки по протоколам),
# поэтому вывод не расходится с тем, что показывает `kit` потом.
if [[ $show_sub == yes ]]; then
sub_user=admin
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
[[ ${INSTALLER_ARGS[_i]} == --user ]] && sub_user=${INSTALLER_ARGS[_i + 1]:-admin}
done
# Наш шов: ссылки для подключения и QR-код в выводе установки не печатаем — их смотрят потом,
# когда настраивают телефон, и они всегда доступны заново в меню kit. Здесь только путь к ним.
say "ссылки для подключения и QR-код — в меню kit"
printf ' %skit%s → Пользователи → Показать ссылки подписка, QR-код и ссылки по протоколам\n' "$B" "$N"
printf ' из командной строки: %s kit local links %s (подписка и QR) · kit user link %s --all\n' "$D" "$N" "$sub_user"
[[ -n ${ssh_port:-} ]] && printf ' SSH: новый порт %s, прежние (%s) ещё открыты — закройте их, когда убедитесь, что вход работает: kit local ssh --close-old\n' "$ssh_port" "22" >&2
если что-то не так: kit check --deep, затем kit fix
EOF
fi
+8 -1
View File
@@ -39,7 +39,7 @@ wait_apt_idle() {
XUI_VERSION="v3.9.0"
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
KIT_VERSION="1.2.1.3"
KIT_VERSION="1.2.1.4"
# 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,
@@ -756,6 +756,8 @@ kl_sni_menu() {
if ((i == known_idx)); then
printf '\nОбщедоступные сайты:\n' >&2
for k in "${!known[@]}"; do printf ' %d - %s\n' "$((k + 1))" "${known[k]}" >&2; done
# Наш шов: последним пунктом — автовыбор из этого списка (раньше выбор был только по номеру).
printf ' %d - автовыбор из списка\n' "$((${#known[@]} + 1))" >&2
printf ' 0 - назад\nВаш выбор: ' >&2
ans=""
read -r -t 300 ans <"$src" 2>/dev/null || true
@@ -763,6 +765,11 @@ kl_sni_menu() {
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then
printf 'sni:%s' "${known[10#$ans - 1]}"; return 0
fi
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans == ${#known[@]} + 1)); then
local autopick="${known[RANDOM % ${#known[@]}]}"
printf ' автовыбор: %s\n' "$autopick" >&2
printf 'sni:%s' "$autopick"; return 0
fi
printf ' ничего не выбрано — оставляю автовыбор\n' >&2
return 0
fi
+1 -1
View File
@@ -8,7 +8,7 @@
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
# останавливается и не заменяет команду hy2.
KIT_VERSION="1.2.1.3"
KIT_VERSION="1.2.1.4"
cat >&2 <<'MSG'
Отдельный установщик Hysteria2 больше не поддерживается.
+39
View File
@@ -74,6 +74,7 @@ kit_local_help() {
kit local masking состояние маскировки (у REALITY должен быть свой домен)
kit local hardening сводка по защите сервера и что ещё можно включить
kit local links подписка, QR-код и ссылки по протоколам (как в меню kit)
kit local ports открытые порты: что нужно, что лишнее и как закрыть
kit local uninstall то же, что kit uninstall: снести всё, включая сам kit (SSH не трогает)
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
@@ -902,6 +903,7 @@ kit_local_cmd() {
hardening | hard) shift; kit_local_hardening "$@" ;;
uninstall | remove) shift; kit_local_uninstall "$@" ;;
links | link) shift; kit_local_links "$@" ;;
ports) shift; kit_local_ports "$@" ;;
site | stub | stub-site) shift; kit_local_site "$@" ;;
*)
printf 'Неизвестная команда форка: %s\n' "$1" >&2
@@ -924,6 +926,42 @@ kit_local_cmd() {
#
# kit local uninstall спросит подтверждение словом «удалить»
# kit local uninstall --dry-run только покажет, что будет удалено
kit_local_ports() { # [--close-extra] — что открыто в ufw, что реально нужно и как закрыть лишнее
local close=no a
for a in "$@"; do
case $a in
--close-extra) close=yes ;;
--help | -h) printf 'kit local ports [--close-extra] — открытые порты, нужные и лишние\n'; return 0 ;;
*) kl_die "kit local ports [--close-extra]"; return 1 ;;
esac
done
command -v ufw >/dev/null 2>&1 || { kl_warn "ufw не установлен — порты смотреть нечем"; return 0; }
local -a opened=() needed=() extra=()
mapfile -t opened < <(ufw status 2>/dev/null | awk '$2 == "ALLOW" { print $1 }' | sort -u)
# Нужные порты: те, что слушают наши подключения (x-ui и ядро Xray), плюс SSH.
mapfile -t needed < <({ ss -tlnp 2>/dev/null; ss -ulnp 2>/dev/null; } \
| awk '/x-ui|xray/ { print $4 }' | sed 's/.*://' | sort -un; ss -tlnp 2>/dev/null | awk '/sshd/ { print $4 }' | sed 's/.*://' | sort -un)
local p q keep
for p in "${opened[@]}"; do
keep=no
case $p in 80/tcp | 443/tcp) keep=yes ;; esac # 443 — работа, 80 — продление сертификата
for q in "${needed[@]}"; do [[ $p == "$q/tcp" || $p == "$q/udp" || $p == "$q" ]] && keep=yes; done
[[ $keep == yes ]] && continue
extra+=("$p")
done
printf ' открыто в ufw: %s\n' "$(printf '%s ' "${opened[@]}")"
if ((${#needed[@]})); then printf ' нужны: %s\n' "$(printf '%s/tcp %s/udp ' "${needed[@]}" "${needed[@]}")"
else printf ' нужны: (не определил — смотрите kit check)\n'; fi
if ((${#extra[@]})); then
printf ' лишние: %s\n' "$(printf '%s ' "${extra[@]}")"
printf ' закрыть лишние: kit local ports --close-extra\n'
if [[ $close == yes ]]; then
for p in "${extra[@]}"; do ufw --force delete allow "$p" >/dev/null 2>&1 && printf ' закрыл %s\n' "$p"; done
fi
else
printf ' лишних нет\n'
fi
}
kit_local_links() { # [имя] — подписка, QR-код и ссылки по протоколам (то же, что «kit → Пользователи → Показать ссылки»)
local u=${1:-} now
if [[ -z $u ]]; then
@@ -1002,6 +1040,7 @@ ${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то ж
Проверка и починка
kit local links [имя] подписка, QR-код и ссылки по протоколам (по умолчанию — первый пользователь)
kit local ports [--close-extra] открытые порты: нужные, лишние; --close-extra закрывает лишние
kit local uninstall то же, что kit uninstall: снести установку целиком и сам kit; SSH не трогает
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
kit fix [--dry-run] безопасные починки: время, службы, конфиги
+1 -1
View File
@@ -12,7 +12,7 @@
set -Eeuo pipefail
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
KIT_VERSION="1.2.1.3"
KIT_VERSION="1.2.1.4"
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"; }
+1 -1
View File
@@ -321,7 +321,7 @@ 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 \'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',
' 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 # Наш шов: последним пунктом — автовыбор из этого списка (раньше выбор был только по номеру).\n printf \' %d - автовыбор из списка\\n\' "$((${#known[@]} + 1))" >&2\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 if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans == ${#known[@]} + 1)); then\n local autopick="${known[RANDOM % ${#known[@]}]}"\n printf \' автовыбор: %s\\n\' "$autopick" >&2\n printf \'sni:%s\' "$autopick"; 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',
),
(