Compare commits

...
10 Commits
Author SHA1 Message Date
hacker07 3f3db5b04c Релиз v1.2.22: подпись
Синхронизация с автором / sync (push) Successful in 3s
2026-10-14 12:51:11 +03:00
hacker07 8fd5572450 v1.2.22: 80 в ufw открывается перед выпуском сертификата; точная причина сбоя 2026-10-14 12:24:03 +03:00
hacker07 01ab468e24 Релиз v1.2.21: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-14 00:18:01 +03:00
hacker07 b20fd6af77 v1.2.21: проверка правил 80/443 в ufw после установки 2026-10-14 00:14:32 +03:00
hacker07 bc18fd354e v1.2.21: порты 80/443 и понятная ошибка сертификата; блок про SSH-порт в конце 2026-10-14 00:14:05 +03:00
hacker07 25e25232b2 Релиз v1.2.20: подпись
Синхронизация с автором / sync (push) Successful in 2s
2026-10-14 00:03:57 +03:00
hacker07 6a9a6ed07e v1.2.20: после сноса меню не показывается; update_hint терпит отсутствие /etc/kit 2026-10-14 00:02:26 +03:00
hacker07 d127a92785 v1.2.19: extra объявлена пустой (set -u валил установку) + правило в FORK.md 2026-10-13 23:59:29 +03:00
hacker07 f38aeefcbc Релиз v1.2.18: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-13 23:53:22 +03:00
hacker07 d0bfb2dc38 v1.2.18: свой домен из меню не подменяется запасным сайтом (не перезаписывать выбор, не проверять TLS своего домена) 2026-10-13 23:52:02 +03:00
10 changed files with 145 additions and 31 deletions
+51
View File
@@ -1,3 +1,54 @@
## v1.2.22: 80 в ufw открывается прямо перед выпуском сертификата
* Настоящая причина сбоя сертификата (проверено на живом сервере): установщик открывает список
портов в ufw **позже**, чем пробует выпустить сертификат. На свежем сервере ufw в этот момент
разрешает только 22, поэтому проверка Let's Encrypt доходила до сервера и «проглатывалась» —
в логе acme `Timeout during connect (likely firewall problem)`. Провайдер тут ни при чём:
с настоящим слушателем 80 и 443 отвечают снаружи нормально (проверено в том числе с другого сервера).
* Теперь 80/tcp открывается в ufw непосредственно перед `issue_domain_cert`, а формулировка ошибки
в установщике исправлена: сначала проверяем ufw, потом хостера.
## v1.2.21: порты до выпуска сертификата и понятный блок про SSH-порт
* **Порты до сертификата.** Установщик теперь сам открывает 80 и 443 в ufw перед работой и прямо
предупреждает, что такие же правила нужны в файрволе хостинга: частая причина срыва — порт 80
закрыт именно у провайдера, а не в сервере. Если сертификат всё же не выпустился, в выводе
разбирается причина: при `Timeout during connect (likely firewall problem)` пишется, что делать
(открыть 80 в панели хостинга) и предлагается вариант без своего домена (тогда сертификат домена
не нужен, панель остаётся на самоподписанном). После установки 80 и 443 в ufw проверяются ещё раз.
* **Смена порта SSH — с понятным завершением.** Прежние порты запоминаются до смены, и в конце
печатается блок: новый порт, какие прежние ещё открыты, команда проверки входа во втором окне и
готовая команда `kit local ssh --close-old <новый>` для закрытия прежних. Строка про SSH-порт
добавлена и в итоговый блок с доступом к панели и подпиской.
## v1.2.20: после сноса меню не показывается, и нет ошибок про latest-version
* Удаление из меню (`kit`, пункт 9) теперь завершается сразу: печатается «Готово: сервер в состоянии
до установки» — и выход. Раньше меню перерисовывалось на уже удалённом сервере и сыпало
`touch: cannot touch '/etc/kit/latest-version'`.
* `update_hint` больше не пытается что-либо писать, если каталога `/etc/kit` уже нет.
* Входит исправление 1.2.19: `extra` объявлена пустой (иначе `set -u` валил установку на шаге
«[6/6] Проверка» сообщением `extra: unbound variable`).
## v1.2.19: исправлено «extra: unbound variable» при установке
* На середине установки (после проверки протоколов) скрипт падал: `extra: unbound variable`.
Причина — моя правка v1.2.13: присваивание ссылок legacy-подписок завёрнуто в `if`, а переменная
объявлялась как `local extra` без значения. Авторский скрипт работает с `set -u`, поэтому при
невыполненном условии (AWG и MTProto не выбраны) чтение `$extra` валило установку. Объявлена пустой.
* Правило записано в FORK.md: оборачивая авторское присваивание в `if`, объявляй переменную пустой —
`local extra=""`, иначе `set -u` обрывает установку на середине.
## v1.2.18: выбранный «свой домен» больше не подменяется соседом
* Что было: после выбора «ввести свой домен» установщик всё равно брал запасной сайт из подсети.
Две причины, обе в моём коде: (1) выбор перезаписывался сырым значением меню (`own:домен`)
вместо самого домена; (2) домен проверялся на «TLS 1.3 + HTTP/2» — а до установки на нём ещё нет
сертификата, поэтому проверка честно не проходила и срабатывал откат на соседа. Теперь для своего
домена проверка TLS не делается (он указывает на этот сервер — установщик сам выпустит
сертификат Let's Encrypt), а автоподбор и запасной сайт включаются только если вы НЕ выбрали
ничего (автовыбор).
## v1.2.17: меню маскировки идёт ПЕРВЫМ (меню автора больше не перебивает) ## v1.2.17: меню маскировки идёт ПЕРВЫМ (меню автора больше не перебивает)
* Причина, по которой меню не было видно: **меню спрашивал сам установщик** («Свой домен / Сосед / * Причина, по которой меню не было видно: **меню спрашивал сам установщик** («Свой домен / Сосед /
+4
View File
@@ -130,6 +130,10 @@ Hysteria2 (UDP 443) работают как работали; исходящий
(проверка LF, гейт справки, шапка `SHA256SUMS` в двух строках, свой список файлов). Швов к нему нет; (проверка LF, гейт справки, шапка `SHA256SUMS` в двух строках, свой список файлов). Швов к нему нет;
при обновлении из авторского проекта файл конфликтует и решается в нашу пользу (как README). при обновлении из авторского проекта файл конфликтует и решается в нашу пользу (как README).
Важно при правках швов: авторский скрипт работает с `set -u`, поэтому если оборачивать его присваивание
в `if`, переменную нужно объявить пустой (`local extra=""`, а не `local extra`) — иначе при невыполненном
условии любое чтение `$extra` валит установку на середине с «unbound variable».
Проверка корректности швов (делать после каждой правки и после синхронизации с автором): Проверка корректности швов (делать после каждой правки и после синхронизации с автором):
накатать `tools/fork_apply.py` на чистую копию авторского кода и сверить с нашим деревом — расхождений накатать `tools/fork_apply.py` на чистую копию авторского кода и сверить с нашим деревом — расхождений
быть не должно, кроме наших собственных файлов. Это единственный способ увидеть правки, которых нет в швах. быть не должно, кроме наших собственных файлов. Это единственный способ увидеть правки, которых нет в швах.
+7 -7
View File
@@ -1,9 +1,9 @@
# 3X-UI KIT 1.2.17 # 3X-UI KIT 1.2.22
# 3X-UI_autoVPS 1.2.17 # 3X-UI_autoVPS 1.2.22
92e1ba60fa05d85d32e1a2feb2941323255a25a8cc7191fd2c3bbdd5991cfc77 scripts/3x-ui.sh 8d090a042d1d243203b6c0f726ee2f81b00aa1c553924ae7fe239e6d324e420d scripts/3x-ui.sh
4894afbdca1859bc856a62d7f805c060750af914148a6129d40bf81180d04067 scripts/hysteria2.sh 4d046772692ee7007bc47d202317623e2524fb04a0b4037aae10bfaa2fffc72c scripts/hysteria2.sh
975b421e22425ddeeab83e6816a40ed4239597719ef748bea2e98935e7b379ca scripts/kit.sh 04aed8fd115159269fe45e79b31614778a6b23b53d45b5ab6db17e729ffabf24 scripts/kit.sh
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py 4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
c38e5fe05bec66e747bb3feee2af4444d3a53d66e74ef1acb59861fbafa65e4c scripts/kit-local.sh c38e5fe05bec66e747bb3feee2af4444d3a53d66e74ef1acb59861fbafa65e4c scripts/kit-local.sh
8910165cf4dd156fc5c362cf244092e63fbf13ca80b062aa5d1a482b4a37a957 scripts/3x-ui-local.sh c219de310f9b0af6d83995fbe708f0f04b9884f924aa7d0095b7a7555c3b7bf7 scripts/3x-ui-local.sh
def65f6e126b8289916c66a36d8532fe268897e174531b44e2b3fb1b0022865b VERSION 552d1b0bcdbecc5cc39f8b9d98564f34edfaefeda6478e91eb54176935bde53a VERSION
+2 -2
View File
@@ -1,6 +1,6 @@
-----BEGIN SSH SIGNATURE----- -----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
AAALc3NoLWVkMjU1MTkAAABAE/O+/f6JrWhdvtE4m+QV7Tbbvud8JXbhZY/2q55P3V8pCo AAALc3NoLWVkMjU1MTkAAABAhKwW7tjjJQf7d0Dr+17/JrDW93JXMDNPAn4+88utnpBq5a
hLfPy5QqSY93ATeluWWujzAZLXUp9VOrMVB9UJCA== lHiMvRPEFnp6UOXR98Ee+TDIyVT3NExbdyOAhqDg==
-----END SSH SIGNATURE----- -----END SSH SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
1.2.17 1.2.22
+38 -1
View File
@@ -44,7 +44,7 @@
set -Eeuo pipefail set -Eeuo pipefail
KIT_VERSION="1.2.17" # версия сборки форка; должна совпадать с VERSION релиза KIT_VERSION="1.2.22" # версия сборки форка; должна совпадать с VERSION релиза
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}" GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
NS="3x-ui-kit-release" NS="3x-ui-kit-release"
SIG_ID="releases@3x-ui-kit" SIG_ID="releases@3x-ui-kit"
@@ -330,6 +330,14 @@ clean_previous() {
} }
installer_failed() { installer_failed() {
if grep -q "likely firewall problem" /var/log/kit-domain-cert.log 2>/dev/null \
|| grep -q "Timeout during connect" /var/log/3x-ui-install.log 2>/dev/null; then
printf '\n%s!%s %s\n' "$Y" "$N" \
"Let's Encrypt не смог достучаться до порта 80 (в логе acme «Timeout during connect»)." >&2
printf ' Проверьте по порядку: открыт ли 80 в ufw (скрипт делает это сам) и не режет ли его хостер.\n' >&2
printf ' Либо ставьте без своего домена: в меню маскировки выберите «общедоступный сайт» —\n' >&2
printf ' сертификат для домена тогда не нужен, панель останется на самоподписанном.\n' >&2
fi
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
printf '\n%s!%s %s\n' "$Y" "$N" \ printf '\n%s!%s %s\n' "$Y" "$N" \
"На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2 "На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2
@@ -406,6 +414,7 @@ if [[ $harden == auto ]] && interactive; then
fi fi
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port) [[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
ssh_ports_before=$(ssh_ports_now) # для подсказки в конце: какие порты закрывать
want_passwd=no want_passwd=no
if interactive; then if interactive; then
if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi
@@ -413,6 +422,15 @@ fi
[[ $clean == yes ]] && clean_previous [[ $clean == yes ]] && clean_previous
# Порты для сертификата и маскировки: открываем правила ufw ДО установщика и говорим, где смотреть,
# если сертификат не выпустится (частая причина — свой файрвол у хостера, а не ufw).
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then
for _p in 80/tcp 443/tcp; do ufw allow "$_p" >/dev/null 2>&1 || true; done
say "порты 80 и 443 открыты в ufw (нужны для сертификата и работы маскировки)"
fi
printf ' %sесли у хостера свой файрвол — 80 и 443 должны быть разрешены и там.%s\n' "$D" "$N"
printf ' %sпризнак закрытого 80: сертификат не выпускается, в логе acme «Timeout during connect (likely firewall problem)»%s\n' "$D" "$N"
# Меню выбора сайта маскировки: сканируем подсеть один раз и предлагаем выбрать сайт отдельно для # Меню выбора сайта маскировки: сканируем подсеть один раз и предлагаем выбрать сайт отдельно для
# REALITY и (если выбраны) для XHTTP/MTProto. Отключается флагом --auto-sni (тогда автоподбор автора). # REALITY и (если выбраны) для XHTTP/MTProto. Отключается флагом --auto-sni (тогда автоподбор автора).
if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi
@@ -436,6 +454,11 @@ printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos
# Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем. # Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем.
# 443 нужен для маскировки и VPN, 80 — для выпуска сертификата: проверяем правила ufw ещё раз.
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then
for _p in 80/tcp 443/tcp; do ufw allow "$_p" >/dev/null 2>&1 || true; done
fi
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт. # Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2, # Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи). # иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
@@ -494,6 +517,19 @@ ${G}${B}Готово.${N} Сервер поставлен из нашей сбо
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
EOF EOF
# ------------------------------------------------------------------ SSH: как закрыть прежние порты
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, терминалы, мониторинг
EOF
fi
# ------------------------------------------------------------------ доступ к панели (перед подпиской) # ------------------------------------------------------------------ доступ к панели (перед подпиской)
if [[ $show_sub == yes ]]; then if [[ $show_sub == yes ]]; then
@@ -521,6 +557,7 @@ if [[ $show_sub == yes ]]; then
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n' printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n' printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N" printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
[[ -n ${ssh_port:-} ]] && printf ' SSH: новый порт %s, прежние (%s) ещё открыты — закройте их после проверки входа: kit local ssh --close-old %s\n' "$ssh_port" "${ssh_ports_before:-22}" "$ssh_port"
if ! kit user link "$sub_user" --all; then if ! kit user link "$sub_user" --all; then
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list" warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
fi fi
+11 -11
View File
@@ -30,7 +30,7 @@ wait_apt_idle() {
XUI_VERSION="v3.9.0" XUI_VERSION="v3.9.0"
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30). # SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6" XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
KIT_VERSION="1.2.17" KIT_VERSION="1.2.22"
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main. # kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash, # Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
@@ -625,10 +625,11 @@ kl_sni_menu() {
NB_ALL=("${nb[@]}") NB_ALL=("${nb[@]}")
# Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto. # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.
if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then
local pick pick2 local pick pick2 own_sni=no
pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"}) pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})
case $pick in case $pick in
own:*) own:*)
own_sni=yes
DOMAIN=${pick#own:} DOMAIN=${pick#own:}
if domain_points_here "$DOMAIN" 2>/dev/null; then if domain_points_here "$DOMAIN" 2>/dev/null; then
SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let's Encrypt выпустит установщик)" SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let's Encrypt выпустит установщик)"
@@ -638,13 +639,8 @@ kl_sni_menu() {
;; ;;
sni:*) SNI=${pick#sni:} ;; sni:*) SNI=${pick#sni:} ;;
esac esac
if ((${#nb[@]})); then
[[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}
elif [[ -n $pick ]]; then
SNI=$pick
fi
if [[ -n $SNI ]]; then if [[ -n $SNI ]]; then
if ! sni_ok "$SNI"; then if [[ $own_sni == no ]] && ! sni_ok "$SNI"; then
warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети" warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"
SNI="${nb[0]:-$SNI}" SNI="${nb[0]:-$SNI}"
fi fi
@@ -663,7 +659,7 @@ kl_sni_menu() {
if [[ -z $SNI ]] && ((${#nb[@]})); then if [[ -z $SNI ]] && ((${#nb[@]})); then
SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-} SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}
ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный" ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"
else elif [[ -z $SNI ]]; then
warn "В подсети подходящего сайта не нашёл." warn "В подсети подходящего сайта не нашёл."
echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com" echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com"
if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then
@@ -866,7 +862,7 @@ kl_sni_menu() {
fi fi
links=$(sub_links "$SUBID") links=$(sub_links "$SUBID")
# У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg». # У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg».
local extra local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны
# Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их, # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,
# если эти протоколы не выбраны (никаких лишних обращений к панели). # если эти протоколы не выбраны (никаких лишних обращений к панели).
if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
@@ -2164,7 +2160,11 @@ PY
install -d -m 755 /var/www/kit install -d -m 755 /var/www/kit
[[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html [[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html
if [[ $SINGLE == yes ]]; then if [[ $SINGLE == yes ]]; then
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; } # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов
# позже (после сборки nginx), поэтому на свежем сервере проверка Let's Encrypt доходила до сервера и
# «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».
command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }
setup_nginx setup_nginx
fi fi
+1 -1
View File
@@ -8,7 +8,7 @@
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём # Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
# останавливается и не заменяет команду hy2. # останавливается и не заменяет команду hy2.
KIT_VERSION="1.2.17" KIT_VERSION="1.2.22"
cat >&2 <<'MSG' cat >&2 <<'MSG'
Отдельный установщик Hysteria2 больше не поддерживается. Отдельный установщик Hysteria2 больше не поддерживается.
+4 -3
View File
@@ -12,7 +12,7 @@
set -Eeuo pipefail set -Eeuo pipefail
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
KIT_VERSION="1.2.17" KIT_VERSION="1.2.22"
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
# Файлы новой версии берём из её тега, а не из меняющейся ветки main. # Файлы новой версии берём из её тега, а не из меняющейся ветки main.
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; } kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
@@ -478,7 +478,8 @@ newer() { [[ $1 != "$2" && $(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1) ==
auto_enabled() { systemctl is-enabled -q kit-update.timer 2>/dev/null; } auto_enabled() { systemctl is-enabled -q kit-update.timer 2>/dev/null; }
# Раз в сутки узнаём последнюю версию (не дольше 3 секунд) и подсказываем обновиться. # Раз в сутки узнаём последнюю версию (не дольше 3 секунд) и подсказываем обновиться.
update_hint() { update_hint() { # подсказка про новую версию
[[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)
local latest="" local latest=""
if [[ ! -f $KIT_LATEST ]] || (($(date +%s) - $(stat -c %Y "$KIT_LATEST") > 86400)); then if [[ ! -f $KIT_LATEST ]] || (($(date +%s) - $(stat -c %Y "$KIT_LATEST") > 86400)); then
latest=$(remote_version 3) || true latest=$(remote_version 3) || true
@@ -2569,7 +2570,7 @@ menu_main() {
6) run_action menu_auto; pause ;; 6) run_action menu_auto; pause ;;
7) run_action cmd_backup; pause ;; 7) run_action cmd_backup; pause ;;
8) run_action cmd_version; pause ;; 8) run_action cmd_version; pause ;;
9) kit_local_uninstall ;; 9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем
10 | 0 | "") return 0 ;; 10 | 0 | "") return 0 ;;
11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;; 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
*) echo "Нет такого пункта." ;; *) echo "Нет такого пункта." ;;
+26 -5
View File
@@ -121,6 +121,13 @@ def main():
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n', ' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n',
'авто-шов: replace, строк 2', 'авто-шов: replace, строк 2',
), ),
(
'scripts/kit.sh',
'update_hint() {\n',
False,
'update_hint() { # подсказка про новую версию\n [[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)\n',
'авто-шов: replace, строк 1',
),
( (
'scripts/kit.sh', 'scripts/kit.sh',
' echo "3X-UI KIT $KIT_VERSION"\n', ' echo "3X-UI KIT $KIT_VERSION"\n',
@@ -223,7 +230,7 @@ def main():
'scripts/kit.sh', 'scripts/kit.sh',
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 0 | "") return 0 ;;\n', ' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 0 | "") return 0 ;;\n',
False, False,
' 9) kit_local_uninstall ;;\n 10 | 0 | "") return 0 ;;\n 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n', ' 9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем\n 10 | 0 | "") return 0 ;;\n 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n',
'авто-шов: replace, строк 2', 'авто-шов: replace, строк 2',
), ),
( (
@@ -321,7 +328,14 @@ def main():
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n', ' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n',
False, False,
' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/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', ' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then\n local pick pick2 own_sni=no\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n case $pick in\n own:*)\n own_sni=yes\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 [[ -n $SNI ]]; then\n if [[ $own_sni == no ]] && ! 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',
),
(
'scripts/3x-ui.sh',
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n else\n',
False,
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n elif [[ -z $SNI ]]; then\n',
'авто-шов: replace, строк 2', 'авто-шов: replace, строк 2',
), ),
( (
@@ -340,10 +354,10 @@ def main():
), ),
( (
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
' extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n', ' local extra\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n',
False, False,
' # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,\n # если эти протоколы не выбраны (никаких лишних обращений к панели).\n if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n fi\n', ' local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны\n # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,\n # если эти протоколы не выбраны (никаких лишних обращений к панели).\n if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n fi\n',
'авто-шов: replace, строк 1', 'авто-шов: replace, строк 2',
), ),
( (
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
@@ -471,6 +485,13 @@ def main():
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."\n', ' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."\n',
'авто-шов: replace, строк 1', 'авто-шов: replace, строк 1',
), ),
(
'scripts/3x-ui.sh',
' [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
False,
' # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов\n # позже (после сборки nginx), поэтому на свежем сервере проверка Let\'s Encrypt доходила до сервера и\n # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».\n command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true\n [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
'авто-шов: replace, строк 1',
),
( (
'scripts/hysteria2.sh', 'scripts/hysteria2.sh',
'# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n', '# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',