Compare commits

...
12 Commits
Author SHA1 Message Date
hacker07 a7b4586bc3 подпись релиза 1.2.25
Синхронизация с автором / sync (push) Failing after 3s
2026-10-14 14:38:07 +03:00
hacker07 cec5246e47 release.sh: понятная подсказка при неверной парольной фразе ключа 2026-10-14 14:36:44 +03:00
hacker07 151d510495 v1.2.25: вопрос про панель (IP/домен) в интерактивном выборе домена; понятнее блок про мягкую защиту 2026-10-14 14:34:27 +03:00
hacker07 f9940026dc подпись релиза 1.2.24
Синхронизация с автором / sync (push) Successful in 1s
2026-10-14 14:08:14 +03:00
hacker07 6d7ebf234e FORK.md: как ведёт себя release.sh — проверки, проба GitHub, парольная фраза ключа 2026-10-14 13:59:31 +03:00
hacker07 41092d4cd1 release.sh: проба связи с GitHub (нет связи — проверки пропускаются сразу, без многосекундных зависаний), warn объявлен 2026-10-14 13:58:11 +03:00
hacker07 78cf3c05dc release.sh: исправлен continue вне цикла (обрывал прогон), connect-timeout 30с и 5 повторов на медленной сети 2026-10-14 13:46:19 +03:00
hacker07 83d9b11c5c release.sh: недоступный GitHub не блокирует подпись (предупреждение вместо стопа), реальное расхождение хешей по-прежнему стоп 2026-10-14 13:40:34 +03:00
hacker07 1fbf27e07c release.sh: повторы и понятная ошибка при сетевых сбоях до GitHub 2026-10-14 13:39:23 +03:00
hacker07 9571e93d46 v1.2.24: свой домен → панель и подписка по домену; предупреждение при панели по IP 2026-10-14 13:36:24 +03:00
hacker07 9dfb4e7957 швы: якоря теперь уникальны и в дереве (чистая проверка) 2026-10-14 13:27:36 +03:00
hacker07 51c35dd2c1 v1.2.23: убран дубль доступов, заглушка сразу наша, ссылки только для нескольких протоколов 2026-10-14 13:26:48 +03:00
12 changed files with 164 additions and 64 deletions
+38
View File
@@ -1,3 +1,41 @@
## v1.2.25: вопрос про панель возвращён, блок про защиту переписан понятнее
* **Снова спрашиваем, где открывать панель и подписку.** У автора этот вопрос задавался только когда
домен передан флагом `--domain`; при выборе «ввести свой домен» в меню маскировки его не было, и панель
молча оставалась по IP с самоподписанным сертификатом — то самое «панель на это ругается». Теперь:
выбрали свой домен → сразу вопрос «По IP (как раньше) или По домену» (`https://домен/…`, сертификат
Let's Encrypt, браузер не ругается).
* В отчёте защиты блок «чего здесь нет» больше не ссылается на сторонний проект без пояснений: сказано,
что именно сделано мягче (без полного обновления системы, без автоперезагрузки, без общесистемного
запрета ICMP) и почему — панель и ядро закреплённых версий, сервер не должен вставать сам.
## v1.2.24: сертификат идёт вслед за выбором «домен или IP»
* Выбрали в меню маскировки **свой домен** — панель и подписка тоже переезжают на домен:
`https://ваш.домен/...` и открываются тем же сертификатом Let's Encrypt, без предупреждений браузера.
Раньше сертификат выписывался только для маскировки, а панель оставалась по IP с самоподписанным —
отсюда ругань и в панели, и в подписке.
* Если выбрали соседа или общедоступный сайт (своего домена нет) — панель остаётся по IP, и в итоговом
блоке прямо сказано: сертификат самоподписанный, браузер предупредит; перейти на домен потом —
`kit net panel domain`.
* Домен, который не указывает на этот сервер, используется только как маска (и об этом сообщается),
панель в этом случае остаётся по IP.
## v1.2.23: чистый вывод — без дублей доступов, без авторской заглушки, без лишних ссылок
* **Убран дублирующий блок автора.** Он печатал «Готово! Сервер работает» с адресом панели, логином,
паролем, подпиской, QR-кодом и ссылками — а потом то же самое выводил наш финальный блок.
Теперь авторский блок заканчивается строкой «Установка завершена» и списком предупреждений;
доступы и подписка печатаются один раз, в самом конце.
* **Авторская заглушка больше не ставится.** Наш сайт кладётся до установщика, а его страница из
релиза убирается — иначе она перезаписывала нашу на шаге сборки сайта. Заодно пропадает лишний
шаг «поставить поверх» и его бэкап.
* **Ссылки по протоколам — только если протоколов больше одного.** При одном REALITY печатается
подписка и QR-код, без списка «отдельные ссылки на каждый протокол».
* Самопроверка в конце установки больше не перечисляет чужие протоколы («REALITY, XHTTP и
Hysteria2») — пишет те, что реально созданы. Предупреждение про XHTTP/MTProto показывается только
если эти протоколы выбраны.
## v1.2.22: 80 в ufw открывается прямо перед выпуском сертификата ## v1.2.22: 80 в ufw открывается прямо перед выпуском сертификата
* Настоящая причина сбоя сертификата (проверено на живом сервере): установщик открывает список * Настоящая причина сбоя сертификата (проверено на живом сервере): установщик открывает список
+6
View File
@@ -190,6 +190,12 @@ bash tools/fork-sync.sh # подтянуть автора, перен
# при конфликте — разобрать вручную: инструмент сам ничего не публикует # при конфликте — разобрать вручную: инструмент сам ничего не публикует
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом
Скрипт сам приводит переводы строк к LF, проверяет справку и то, что установщик не ругается на пустые
строки, сверяет панель и ядро Xray с официальными хешами GitHub (если GitHub недоступен — предупреждает и
пропускает эти проверки за секунды, а не висит; расхождение хешей при живом GitHub по-прежнему останавливает
выпуск). Далее скрипт ждёт **парольную фразу ключа** — её вводит человек, поэтому запускать надо в окне, где
можно набрать пароль (в PowerShell — да, в фоне — нет).
git add -A && git commit -m "Релиз v$(cat VERSION): …" && git tag v$(cat VERSION) git add -A && git commit -m "Релиз v$(cat VERSION): …" && git tag v$(cat VERSION)
git push origin dev v$(cat VERSION) upstream git push origin dev v$(cat VERSION) upstream
``` ```
+8 -8
View File
@@ -1,9 +1,9 @@
# 3X-UI KIT 1.2.22 # 3X-UI KIT 1.2.25
# 3X-UI_autoVPS 1.2.22 # 3X-UI_autoVPS 1.2.25
8d090a042d1d243203b6c0f726ee2f81b00aa1c553924ae7fe239e6d324e420d scripts/3x-ui.sh 9c5f4fe42bff145834c50ac8603ba2119785b658f954251b24d679946f9794c6 scripts/3x-ui.sh
4d046772692ee7007bc47d202317623e2524fb04a0b4037aae10bfaa2fffc72c scripts/hysteria2.sh de4738467b80e1f27cf823793e7514b1d56cbdecf029875f9880459d616607bf scripts/hysteria2.sh
04aed8fd115159269fe45e79b31614778a6b23b53d45b5ab6db17e729ffabf24 scripts/kit.sh e33c8cad20d21572303a80b80ac86a8e8f5a375384cb1831a6877897263b0478 scripts/kit.sh
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py 4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
c38e5fe05bec66e747bb3feee2af4444d3a53d66e74ef1acb59861fbafa65e4c scripts/kit-local.sh 2b93ecc5901f7ea2033749a7e395eb7c16ce1d2461a9f3690b7ee407f00056ba scripts/kit-local.sh
c219de310f9b0af6d83995fbe708f0f04b9884f924aa7d0095b7a7555c3b7bf7 scripts/3x-ui-local.sh 8f138044442732093972728c33e5b3fdccf53f4b9ab77f5bcd7cfc1f2c0671c2 scripts/3x-ui-local.sh
552d1b0bcdbecc5cc39f8b9d98564f34edfaefeda6478e91eb54176935bde53a VERSION 24282a40c1a6de0f90841072cc3fa92a1cfec3872672596f6d41ade44979b907 VERSION
+2 -2
View File
@@ -1,6 +1,6 @@
-----BEGIN SSH SIGNATURE----- -----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
AAALc3NoLWVkMjU1MTkAAABAhKwW7tjjJQf7d0Dr+17/JrDW93JXMDNPAn4+88utnpBq5a AAALc3NoLWVkMjU1MTkAAABAKvjXzfKjy4irRKB07KffvAMz/wvijprwHBobm9I3EqrCGE
lHiMvRPEFnp6UOXR98Ee+TDIyVT3NExbdyOAhqDg== Y+VwreS9CR/3/Mwe3sRMlH/X2iQfG/xOlDiliuBg==
-----END SSH SIGNATURE----- -----END SSH SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
1.2.22 1.2.25
+39 -4
View File
@@ -44,7 +44,7 @@
set -Eeuo pipefail set -Eeuo pipefail
KIT_VERSION="1.2.22" # версия сборки форка; должна совпадать с VERSION релиза KIT_VERSION="1.2.25" # версия сборки форка; должна совпадать с 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"
@@ -446,6 +446,28 @@ else
fi fi
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше" command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
# Ставим нашу заглушку СРАЗУ (а не поверх авторской): страницу кладём до установщика, а его собственную
# страницу из релиза убираем — иначе она перезапишет нашу на шаге сборки сайта.
stub_staged=no
if [[ $site != off ]]; then
stub_n=""
case $site in
random) stub_n=$(( RANDOM % 10 )) ;;
*) [[ $site =~ ^[0-9]+$ ]] && stub_n=$((10#$site - 1)) ;;
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 \
&& [[ -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)"
fi
fi
fi
# Запоминаем, какие протоколы выбраны: наши команды (kit local site и др.) смотрят сюда и не # Запоминаем, какие протоколы выбраны: наши команды (kit local site и др.) смотрят сюда и не
# делают ничего для протоколов, которых в этой установке нет. # делают ничего для протоколов, которых в этой установке нет.
printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos 2>/dev/null || true printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos 2>/dev/null || true
@@ -462,7 +484,9 @@ fi
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт. # Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2, # Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи). # иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
case $site in case ${stub_staged:-no} in
yes) say "заглушка уже стоит (поставлена при установке, Hysteria2 синхронизирован тем же файлом)" ;;
*) case $site in
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;; off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
random) random)
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора" say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
@@ -472,6 +496,7 @@ case $site in
say "ставлю сайт-заглушку №$site" say "ставлю сайт-заглушку №$site"
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site" kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
;; ;;
esac ;;
esac esac
if [[ $fix_time == yes ]]; then if [[ $fix_time == yes ]]; then
@@ -540,6 +565,10 @@ if [[ $show_sub == yes ]]; then
else else
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше" warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
fi fi
if grep -qaE '^Панель: ?https?://[0-9]' /root/3x-ui.txt 2>/dev/null; then
printf ' %sпанель открывается по IP: сертификат самоподписанный, браузер предупредит.\n' "$D"
printf ' Завести свой домен и перейти на него: kit net panel domain%s\n' "$N"
fi
fi fi
# ------------------------------------------------------------------ подписка, QR-код и доступы # ------------------------------------------------------------------ подписка, QR-код и доступы
@@ -558,8 +587,14 @@ if [[ $show_sub == yes ]]; then
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" [[ -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 # Ссылки на каждый протокол показываем только если протоколов больше одного: при одном это лишний шум.
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list" protos_now=$(tr '\n' ' ' < /etc/kit/kl-protos 2>/dev/null || true)
if [[ $(wc -w <<<"$protos_now") -gt 1 ]]; then
if ! kit user link "$sub_user" --all; then
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
fi
elif ! kit user link "$sub_user"; then
warn "не смог показать подписку для «$sub_user» — список пользователей: kit user list"
fi fi
else else
say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all" say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all"
+14 -38
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.22" KIT_VERSION="1.2.25"
# 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,
@@ -632,9 +632,14 @@ kl_sni_menu() {
own_sni=yes 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 выпустит установщик)"
# Наш шов: спрашиваем, где открывать панель и подписку, и при интерактивном вводе домена тоже.
# У автора этот вопрос задавался только при домене из флага --domain, а при выборе «ввести свой
# домен» в меню маскировки — нет, и панель молча оставалась по IP с самоподписанным сертификатом.
panel_on_choice
else else
SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт" SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт, панель и подписка остаются по IP"
fi fi
;; ;;
sni:*) SNI=${pick#sni:} ;; sni:*) SNI=${pick#sni:} ;;
@@ -711,7 +716,9 @@ kl_sni_menu() {
done done
SNI2=${SNI2:-$SNI}; SNI3=${SNI3:-www.cloudflare.com} SNI2=${SNI2:-$SNI}; SNI3=${SNI3:-www.cloudflare.com}
if [[ $TRUSTED == yes && $multi == no && ( $SNI2 =~ $SNI_BRAND_RE || $SNI3 =~ $SNI_BRAND_RE ) ]]; then if [[ $TRUSTED == yes && $multi == no && ( $SNI2 =~ $SNI_BRAND_RE || $SNI3 =~ $SNI_BRAND_RE ) ]]; then
later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site" # Наш шов: про XHTTP и MTProto говорим только если они выбраны — иначе это шум.
[[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]] && \
later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"
fi fi
step "Пакеты" step "Пакеты"
@@ -841,7 +848,7 @@ kl_sni_menu() {
chk=$(/usr/local/bin/kit check --deep 2>&1) && { bad=no; break; } chk=$(/usr/local/bin/kit check --deep 2>&1) && { bad=no; break; }
done done
if [[ $bad == no ]]; then if [[ $bad == no ]]; then
ok "подключения работают: REALITY, XHTTP и Hysteria2 проверены клиентом с самого сервера" ok "подключения работают (проверено клиентом с самого сервера): ${CREATED[*]:-нет}"
else else
install -m 600 /dev/null /var/log/kit-install-check.log install -m 600 /dev/null /var/log/kit-install-check.log
printf '%s\n' "$chk" >/var/log/kit-install-check.log printf '%s\n' "$chk" >/var/log/kit-install-check.log
@@ -886,33 +893,7 @@ kl_sni_menu() {
} >"$RESULT" } >"$RESULT"
echo echo
echo "${G}${B}Готово! Сервер работает.${N}" echo "${G}${B}Установка завершена.${N} Доступы к панели, подписка и QR-код — в самом конце вывода."
[[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let's Encrypt продлевается сам."
[[ $SELF_IP_CERT == yes ]] && later "Сертификат на IP самоподписанный: пользуйтесь ссылками на отдельные подключения, подписка в приложениях может не открыться."
echo
echo "${B}ПАНЕЛЬ${N}"
if [[ $TRUSTED == yes ]]; then
echo " $panel_url"
else
echo " Только через SSH-туннель. На вашем компьютере:"
echo " ssh -L $XUI_PANEL_PORT:127.0.0.1:$XUI_PANEL_PORT root@$HOST"
echo " Затем в браузере: http://127.0.0.1:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH"
fi
echo " Логин: ${B}$XUI_USERNAME${N} Пароль: ${B}$XUI_PASSWORD${N}"
echo " ${D}(сохранено в $RESULT, файл виден только root)${N}"
echo
if [[ $TRUSTED == yes ]]; then
echo "${B}ПОДПИСКА${N} – одна ссылка для всех протоколов"
echo " ${B}$SUB_URL${N}"
echo " Вставьте в Happ, Hiddify, Karing, v2rayN, Clash Verge или FlClash."
qrencode -t ANSIUTF8 -m 1 "$SUB_URL" || true
echo
KIT_NO_QR=1 /usr/local/bin/kit __links "$NAME" "$SUBID" || true
else
/usr/local/bin/kit __links "$NAME" "$SUBID" || true
fi
echo
echo "${D}Порты, выключение и включение протоколов: ${N}${B}kit net${N}"
if ((${#WARNINGS[@]})); then if ((${#WARNINGS[@]})); then
echo echo
echo "${B}ВНИМАНИЕ${N}" echo "${B}ВНИМАНИЕ${N}"
@@ -923,12 +904,7 @@ kl_sni_menu() {
done done
((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check" ((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"
fi fi
echo
echo "${B}ДАЛЬШЕ${N}"
echo " ${B}kit${N} меню управления"
echo " ${B}kit user add имя --gb 50 --days 30${N} добавить пользователя"
echo " ${B}kit check --deep${N} проверить, что всё работает"
echo " ${B}kit backup${N} копия сервера (держите в надёжном месте)"
} }
# Официальный установщик 3X-UI закреплённой версии, сверенный по SHA256. # Официальный установщик 3X-UI закреплённой версии, сверенный по SHA256.
+1 -1
View File
@@ -8,7 +8,7 @@
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём # Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
# останавливается и не заменяет команду hy2. # останавливается и не заменяет команду hy2.
KIT_VERSION="1.2.22" KIT_VERSION="1.2.25"
cat >&2 <<'MSG' cat >&2 <<'MSG'
Отдельный установщик Hysteria2 больше не поддерживается. Отдельный установщик Hysteria2 больше не поддерживается.
+5 -3
View File
@@ -443,12 +443,14 @@ kit_local_hardening() {
cat <<'EOF' cat <<'EOF'
Чего здесь НЕТ и почему (в отличие от vps-setup): Что сделано намеренно МЯГЧЕ, чем в популярных скриптах первичной настройки:
* apt full-upgrade — тянет систему вперёд и может сломать закреплённые панель и ядро; * apt full-upgrade — тянет систему вперёд и может сломать закреплённые панель и ядро;
вместо него включены обновления безопасности; вместо него включены обновления безопасности;
* автоматическая перезагрузка после правок — сервер не должен вставать без вашего решения; * автоматическая перезагрузка после правок — сервер не должен вставать без вашего решения;
* общесистемный icmp_echo_ignore_all — правило ufw видно в `ufw status` и отменяется одной * общесистемный запрет ICMP для всех интерфейсов — здесь он выключен на уровне ufw: правило видно
командой, не глушит диагностику внутренних интерфейсов. в `ufw status` и отменяется одной командой, а внутренняя диагностика не глушится.
Причина одна: перечисленное может увести систему вперёд или выключить сервер, а панель и ядро у нас
закреплённых версий.
Что включить при желании: Что включить при желании:
kit local ssh 2222 добавить порт SSH kit local ssh 2222 добавить порт SSH
+1 -1
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.22" KIT_VERSION="1.2.25"
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"; }
+29 -1
View File
@@ -328,7 +328,7 @@ 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 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', ' # Наш шов: показываем найденное и даём выбрать — отдельно для 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\n ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let\'s Encrypt выпустит установщик)"\n # Наш шов: спрашиваем, где открывать панель и подписку, и при интерактивном вводе домена тоже.\n # У автора этот вопрос задавался только при домене из флага --domain, а при выборе «ввести свой\n # домен» в меню маскировки — нет, и панель молча оставалась по IP с самоподписанным сертификатом.\n panel_on_choice\n else\n SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт, панель и подписка остаются по IP"\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', 'авто-шов: replace, строк 2',
), ),
( (
@@ -352,6 +352,20 @@ def main():
' # Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей\n # по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.\n if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \\\n && [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n', ' # Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей\n # по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.\n if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \\\n && [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n',
'авто-шов: replace, строк 1', 'авто-шов: replace, строк 1',
), ),
(
'scripts/3x-ui.sh',
' later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"\n',
False,
' # Наш шов: про XHTTP и MTProto говорим только если они выбраны — иначе это шум.\n [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]] && \\\n later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' ok "подключения работают: REALITY, XHTTP и Hysteria2 проверены клиентом с самого сервера"\n',
False,
' ok "подключения работают (проверено клиентом с самого сервера): ${CREATED[*]:-нет}"\n',
'авто-шов: replace, строк 1',
),
( (
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
' local extra\n 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',
@@ -366,6 +380,20 @@ def main():
' echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n', ' echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
'авто-шов: replace, строк 1', 'авто-шов: replace, строк 1',
), ),
(
'scripts/3x-ui.sh',
' echo "${G}${B}Готово! Сервер работает.${N}"\n [[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let\'s Encrypt продлевается сам."\n [[ $SELF_IP_CERT == yes ]] && later "Сертификат на IP самоподписанный: пользуйтесь ссылками на отдельные подключения, подписка в приложениях может не открыться."\n echo\n echo "${B}ПАНЕЛЬ${N}"\n if [[ $TRUSTED == yes ]]; then\n echo " $panel_url"\n else\n echo " Только через SSH-туннель. На вашем компьютере:"\n echo " ssh -L $XUI_PANEL_PORT:127.0.0.1:$XUI_PANEL_PORT root@$HOST"\n echo " Затем в браузере: http://127.0.0.1:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH"\n fi\n echo " Логин: ${B}$XUI_USERNAME${N} Пароль: ${B}$XUI_PASSWORD${N}"\n echo " ${D}(сохранено в $RESULT, файл виден только root)${N}"\n echo\n if [[ $TRUSTED == yes ]]; then\n echo "${B}ПОДПИСКА${N} – одна ссылка для всех протоколов"\n echo " ${B}$SUB_URL${N}"\n echo " Вставьте в Happ, Hiddify, Karing, v2rayN, Clash Verge или FlClash."\n qrencode -t ANSIUTF8 -m 1 "$SUB_URL" || true\n echo\n KIT_NO_QR=1 /usr/local/bin/kit __links "$NAME" "$SUBID" || true\n else\n /usr/local/bin/kit __links "$NAME" "$SUBID" || true\n fi\n echo\n echo "${D}Порты, выключение и включение протоколов: ${N}${B}kit net${N}"\n',
False,
' echo "${G}${B}Установка завершена.${N} Доступы к панели, подписка и QR-код — в самом конце вывода."\n',
'авто-шов: replace, строк 27',
),
(
'scripts/3x-ui.sh',
' ((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"\n fi\n echo\n echo "${B}ДАЛЬШЕ${N}"\n echo " ${B}kit${N} меню управления"\n echo " ${B}kit user add имя --gb 50 --days 30${N} добавить пользователя"\n echo " ${B}kit check --deep${N} проверить, что всё работает"\n echo " ${B}kit backup${N} копия сервера (держите в надёжном месте)"\n',
False,
' ((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"\n fi\n\n',
'авто-шов: replace, строк 8',
),
( (
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\n', 'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\n',
+20 -5
View File
@@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
die() { printf '✗ %s\n' "$*" >&2; exit 1; } die() { printf '✗ %s\n' "$*" >&2; exit 1; }
ok() { printf '✓ %s\n' "$*"; } ok() { printf '✓ %s\n' "$*"; }
warn() { printf '! %s\n' "$*" >&2; } # сеть до GitHub отвалилась: проверку пропускаем, релиз не блокируем
sha() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; } sha() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
key=${1:-$HOME/.ssh/3xui_kit_release} key=${1:-$HOME/.ssh/3xui_kit_release}
@@ -61,10 +62,18 @@ ok "Все наши функции определены раньше своих
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит." [[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
# Часть проверок сверяется с GitHub, а он из домашней сети отвечает через раз. Сначала короткая проба:
# нет связи — сразу предупреждаем и пропускаем внешние проверки (подписи это не мешает), а не висим минутами.
gh_ok=yes
curl -fsI --connect-timeout 8 --max-time 12 https://github.com >/dev/null 2>&1 || gh_ok=no
[[ $gh_ok == yes ]] || warn "GitHub недоступен из вашей сети — установщик, суммы ядра и панели не сверяю. Подписи это не мешает."
gh_curl() { [[ ${gh_ok:-no} == yes ]] || return 1; curl -fsSL --retry 2 --retry-delay 2 --connect-timeout 15 --max-time 90 "$@"; }
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub. # Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
xv=$(sed -n 's/^XUI_VERSION="\(.*\)"/\1/p' scripts/3x-ui.sh) xv=$(sed -n 's/^XUI_VERSION="\(.*\)"/\1/p' scripts/3x-ui.sh)
xs=$(sed -n 's/^XUI_INSTALL_SHA256="\(.*\)"/\1/p' scripts/3x-ui.sh) xs=$(sed -n 's/^XUI_INSTALL_SHA256="\(.*\)"/\1/p' scripts/3x-ui.sh)
got=$(curl -fsSL "https://raw.githubusercontent.com/MHSanaei/3x-ui/$xv/install.sh" | sha | awk '{print $1}') got=$(gh_curl "https://raw.githubusercontent.com/MHSanaei/3x-ui/$xv/install.sh" | sha | awk '{print $1}') \
|| { warn "GitHub недоступен — проверку установщика 3X-UI пропускаю (релиз не блокирую)"; got=$xs; }
[[ $got == "$xs" ]] || die "Установщик 3X-UI $xv изменился: $got вместо $xs. Проверьте, что поменялось, прежде чем выпускать." [[ $got == "$xs" ]] || die "Установщик 3X-UI $xv изменился: $got вместо $xs. Проверьте, что поменялось, прежде чем выпускать."
ok "Установщик 3X-UI $xv совпадает с закреплённым SHA256" ok "Установщик 3X-UI $xv совпадает с закреплённым SHA256"
@@ -72,7 +81,8 @@ ok "Установщик 3X-UI $xv совпадает с закреплённы
xc=$(sed -n 's/^XRAY_CORE="\(.*\)"/\1/p' scripts/3x-ui.sh) xc=$(sed -n 's/^XRAY_CORE="\(.*\)"/\1/p' scripts/3x-ui.sh)
for a in 64 arm64-v8a; do for a in 64 arm64-v8a; do
pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/3x-ui.sh) pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/3x-ui.sh)
off=$(curl -fsSL "https://github.com/XTLS/Xray-core/releases/download/$xc/Xray-linux-$a.zip.dgst" | awk '/^SHA2-256=/ {print $2}') off=$(gh_curl "https://github.com/XTLS/Xray-core/releases/download/$xc/Xray-linux-$a.zip.dgst" | awk '/^SHA2-256=/ {print $2}' || true)
[[ -n $off ]] || { warn "GitHub недоступен — суммы Xray для $a пропускаю"; continue; }
[[ -n $pin && $pin == "$off" ]] || die "Сумма ядра Xray $xc ($a) в 3x-ui.sh не совпадает с официальной: «$pin» вместо «$off»." [[ -n $pin && $pin == "$off" ]] || die "Сумма ядра Xray $xc ($a) в 3x-ui.sh не совпадает с официальной: «$pin» вместо «$off»."
done done
ok "Суммы архивов ядра Xray $xc совпадают с официальными" ok "Суммы архивов ядра Xray $xc совпадают с официальными"
@@ -85,11 +95,13 @@ kx=$(sed -n 's/^XRAY_PIN="\(.*\)"/\1/p' scripts/kit.sh)
[[ $kx == "$xc" ]] || die "XRAY_PIN в kit.sh ($kx) не совпадает с XRAY_CORE в 3x-ui.sh ($xc)." [[ $kx == "$xc" ]] || die "XRAY_PIN в kit.sh ($kx) не совпадает с XRAY_CORE в 3x-ui.sh ($xc)."
for a in amd64 arm64; do for a in amd64 arm64; do
pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/kit.sh) pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/kit.sh)
off=$(curl -fsSL "https://github.com/MHSanaei/3x-ui/releases/download/$kp/x-ui-linux-$a.tar.gz.sha256" | awk 'NR == 1 {print $1}') off=$(gh_curl "https://github.com/MHSanaei/3x-ui/releases/download/$kp/x-ui-linux-$a.tar.gz.sha256" | awk 'NR == 1 {print $1}' || true)
[[ -n $off ]] || { warn "GitHub недоступен — сумму панели для $a пропускаю"; continue; }
[[ -n $pin && $pin == "$off" ]] || die "Сумма архива панели $kp ($a) в kit.sh не совпадает с официальной: «$pin» вместо «$off»." [[ -n $pin && $pin == "$off" ]] || die "Сумма архива панели $kp ($a) в kit.sh не совпадает с официальной: «$pin» вместо «$off»."
done done
shs=$(sed -n 's/^XUI_SH_SHA256=\(.*\)/\1/p' scripts/kit.sh) shs=$(sed -n 's/^XUI_SH_SHA256=\(.*\)/\1/p' scripts/kit.sh)
got=$(curl -fsSL "https://raw.githubusercontent.com/MHSanaei/3x-ui/$kp/x-ui.sh" | sha | awk '{print $1}') got=$(gh_curl "https://raw.githubusercontent.com/MHSanaei/3x-ui/$kp/x-ui.sh" | sha | awk '{print $1}' || true)
[[ -n $got ]] || { warn "GitHub недоступен — проверку x-ui.sh $kp пропускаю"; got=$shs; }
[[ $shs == "$got" ]] || die "Сумма x-ui.sh $kp в kit.sh не совпадает с официальной: «$shs» вместо «$got»." [[ $shs == "$got" ]] || die "Сумма x-ui.sh $kp в kit.sh не совпадает с официальной: «$shs» вместо «$got»."
ok "Панель $kp и ядро $kx в kit.sh совпадают с установщиком и официальными суммами" ok "Панель $kp и ядро $kx в kit.sh совпадают с установщиком и официальными суммами"
@@ -102,7 +114,10 @@ ok "Ключ подписи записан в kit"
{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS { echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
rm -f SHA256SUMS.sig rm -f SHA256SUMS.sig
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null # Ключ защищён парольной фразой: её вводит человек. Понятная подсказка на случай опечатки.
echo "Подписываю ключом $key — если он защищён парольной фразой, введите её сейчас."
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null \
|| die "Ключ не подписал. Чаще всего это неверная парольная фраза («incorrect passphrase») — запустите команду ещё раз и введите её внимательно. Файл SHA256SUMS при этом не портится."
tmp=$(mktemp) tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT trap 'rm -f "$tmp"' EXIT
signers scripts/kit.sh | while read -r k; do printf '%s namespaces="%s" %s\n' "$ID" "$NS" "$k"; done >"$tmp" signers scripts/kit.sh | while read -r k; do printf '%s namespaces="%s" %s\n' "$ID" "$NS" "$k"; done >"$tmp"