1.1: исправления по аудиту безопасности
- hysteria2.sh: строгая проверка --email/--host/--sni, install.env через printf %q, SHA256 бинарника в самом скрипте, hy2 update из тега релиза - 3x-ui.sh: SHA256 установщика 3X-UI, проверка --sni/--host, kit и kit-sub из тега, kit-sub без root (DynamicUser + LoadCredential), без cron для меню x-ui - VERSION, CHANGELOG, SECURITY.md, CI (ShellCheck, Python, тесты генераторов), установка с проверкой в README Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7GrBSRPVXhrz3RAprWDnt
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4f1e5d98cc
commit
fdae4588ea
@@ -0,0 +1,25 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: ShellCheck
|
||||||
|
run: shellcheck -S warning -e SC1090,SC2034,SC2211 scripts/*.sh
|
||||||
|
- name: Python
|
||||||
|
run: python3 -m py_compile scripts/kit-sub.py
|
||||||
|
- name: Генераторы
|
||||||
|
run: node --test tools/test/*.test.js
|
||||||
|
- name: Версия в скриптах совпадает с VERSION
|
||||||
|
run: |
|
||||||
|
v=$(cat VERSION)
|
||||||
|
grep -q "^KIT_VERSION=\"$v\"" scripts/3x-ui.sh
|
||||||
|
grep -q "^KIT_VERSION=\"$v\"" scripts/hysteria2.sh
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Что нового
|
||||||
|
|
||||||
|
## v1.1: kit сходил к аудитору, сдал анализы и теперь не доверяет никому, даже GitHub
|
||||||
|
|
||||||
|
Подписчик прислал нам полноценный аудит безопасности. Мы почитали, покраснели и всё починили.
|
||||||
|
Спасибо: такие письма лучше любых звёзд на GitHub (но звёзды тоже ставьте).
|
||||||
|
|
||||||
|
**Безопасность**
|
||||||
|
|
||||||
|
- **Hysteria2 больше не верит на слово.** Почта, адрес сервера и сайт маскировки теперь проверяются
|
||||||
|
строго, а в файл настроек пишутся с экранированием. Раньше особо изобретательная почта могла
|
||||||
|
выполниться как команда. Теперь `me@gmail.com;rm -rf` получит вежливый отказ.
|
||||||
|
- **Установщик 3X-UI сверяем по отпечатку.** Мы закрепили не только версию, но и SHA256 файла.
|
||||||
|
Если на GitHub кто-то подменит установщик, скрипт посмотрит на него как на незнакомца в чате
|
||||||
|
с «выгодным предложением» и запускать не станет.
|
||||||
|
- **Hysteria проверяется нашим ключом, а не чужим.** Контрольная сумма бинарника теперь записана
|
||||||
|
в сам скрипт, а не скачивается из того же места, что и бинарник. Проверять человека по
|
||||||
|
справке, которую он же и принёс, так себе идея.
|
||||||
|
- **Всё качаем из релиза, а не из черновиков.** `kit`, `kit-sub` и `hy2 update` берут файлы
|
||||||
|
из тега версии, а не из ветки `main`, которая может поменяться посреди установки.
|
||||||
|
- **Подписка больше не root.** `kit-sub` работает от временного пользователя без прав: конфиг
|
||||||
|
и сертификат ему выдаёт systemd на время работы. Даже если его взломают, ключей от сервера
|
||||||
|
у него нет.
|
||||||
|
- **Меню x-ui оставили в покое.** Больше не переписываем чужие файлы по расписанию, только
|
||||||
|
один раз при установке.
|
||||||
|
- **Строгие имена.** Параметры `--sni` и `--host` у установщика 3X-UI тоже проверяются.
|
||||||
|
|
||||||
|
**Для тех, кто хочет проверить сам**
|
||||||
|
|
||||||
|
- В README появилась установка с проверкой: скачать, прочитать, сверить SHA256, запустить.
|
||||||
|
- `SECURITY.md`: как сообщить об уязвимости, не публикуя её на весь интернет.
|
||||||
|
- Проверка кода на каждый коммит: ShellCheck, Python, тесты генераторов.
|
||||||
|
|
||||||
|
**Как обновиться**
|
||||||
|
|
||||||
|
- Новые установки получают всё сразу.
|
||||||
|
- Hysteria2: `hy2 update`.
|
||||||
|
- Серверу 3X-UI из версии 1.0 ничего не угрожает: критическая ошибка была только в установщике
|
||||||
|
Hysteria2 и срабатывала при установке. Перевести подписку на работу без root поможет
|
||||||
|
`kit update` в версии 1.2. Пока можно просто продолжать пользоваться.
|
||||||
|
|
||||||
|
## v1.0
|
||||||
|
|
||||||
|
Первая версия: 11 протоколов одной командой, всё через 443, одна подписка на все приложения,
|
||||||
|
пользователи через `kit user`, генераторы конфигов для XKeen.
|
||||||
@@ -52,6 +52,26 @@ bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main
|
|||||||
```
|
```
|
||||||
|
|
||||||
Через пару минут скрипт покажет адрес панели, логин, пароль и подписку с QR-кодом.
|
Через пару минут скрипт покажет адрес панели, логин, пароль и подписку с QR-кодом.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>🔍 Хотите сначала посмотреть скрипт? Установка с проверкой</summary>
|
||||||
|
|
||||||
|
Скачайте скрипт конкретной версии, прочитайте его и сверьте SHA256 с тем, что указан
|
||||||
|
на странице [релиза](https://github.com/itsnotkubrick/3X-UI_KIT/releases/latest):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSLO https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1/scripts/3x-ui.sh
|
||||||
|
less 3x-ui.sh
|
||||||
|
sha256sum 3x-ui.sh
|
||||||
|
bash 3x-ui.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт версии 1.1 скачивает свои части (`kit`, `kit-sub`) из того же релиза, а установщик
|
||||||
|
3X-UI сверяет по SHA256 и не запускает, если он изменился. Первый раз удобнее ставить на
|
||||||
|
свежий VPS без ваших данных.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
Подробно — подключение приложений, дополнительные пользователи и параметры —
|
Подробно — подключение приложений, дополнительные пользователи и параметры —
|
||||||
в **[инструкции](manuals/3x-ui.md)**. Нужен только Hysteria2 — есть
|
в **[инструкции](manuals/3x-ui.md)**. Нужен только Hysteria2 — есть
|
||||||
[отдельный скрипт](manuals/hysteria2.md).
|
[отдельный скрипт](manuals/hysteria2.md).
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Безопасность
|
||||||
|
|
||||||
|
Нашли уязвимость? Пожалуйста, не публикуйте её в открытом issue.
|
||||||
|
|
||||||
|
- Напишите через [закрытый отчёт GitHub](https://github.com/itsnotkubrick/3X-UI_KIT/security/advisories/new)
|
||||||
|
(вкладка **Security → Report a vulnerability**) или в Telegram [@itsnotkubrick](https://t.me/itsnotkubrick).
|
||||||
|
- Опишите, где проблема (файл и строки), как её воспроизвести и чем она грозит.
|
||||||
|
|
||||||
|
Мы ответим, исправим и отметим вас в списке изменений, если вы не против.
|
||||||
|
|
||||||
|
## Что проект делает для безопасности
|
||||||
|
|
||||||
|
- Установщик 3X-UI закреплён по версии **и** по SHA256: если файл на GitHub изменится,
|
||||||
|
скрипт его не запустит.
|
||||||
|
- Бинарник Hysteria сверяется с SHA256, записанным в самом скрипте.
|
||||||
|
- Свои части (`kit`, `kit-sub`, `hy2 update`) скрипт берёт из тега релиза, а не из ветки `main`.
|
||||||
|
- Подписка `kit-sub` работает без прав root: systemd `DynamicUser` и `LoadCredential`.
|
||||||
|
- Все данные, которые попадают в файлы настроек, проверяются и экранируются.
|
||||||
|
- Генераторы конфигов для роутеров работают только в браузере и никуда не отправляют ваши ссылки.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
Скрипт ставит стороннее ПО: панель 3X-UI, ядро Xray, Hysteria, nginx. Мы закрепляем их версии,
|
||||||
|
но не проверяем их исходный код целиком. Сгенерированный конфиг Mihomo скачивает списки
|
||||||
|
сайтов (rule-set) и веб-панель из их проектов на GitHub.
|
||||||
+58
-29
@@ -13,6 +13,11 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
XUI_VERSION="v3.8.5"
|
XUI_VERSION="v3.8.5"
|
||||||
|
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||||
|
XUI_INSTALL_SHA256="4e3fe7fe00ef8e904ce6a0e9c36fd8a0c7179fe5e786f23e31801aee84c6347d"
|
||||||
|
KIT_VERSION="1.1"
|
||||||
|
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||||
|
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"
|
||||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||||
# Clash Verge, Mihomo в XKeen) не проходят REALITY — проверено 2026-09-25.
|
# Clash Verge, Mihomo в XKeen) не проходят REALITY — проверено 2026-09-25.
|
||||||
# 26.6.27 — последняя версия, с которой работают все клиенты и которую принимает 3X-UI.
|
# 26.6.27 — последняя версия, с которой работают все клиенты и которую принимает 3X-UI.
|
||||||
@@ -128,7 +133,7 @@ main() {
|
|||||||
warn "Панель 3X-UI удалена, но остались файлы прошлой установки — убираю их."
|
warn "Панель 3X-UI удалена, но остались файлы прошлой установки — убираю их."
|
||||||
systemctl disable --now kit-sub >/dev/null 2>&1 || true
|
systemctl disable --now kit-sub >/dev/null 2>&1 || true
|
||||||
rm -rf /etc/systemd/system/kit-sub.service /usr/local/lib/kit-sub /etc/kit-sub /etc/kit /usr/local/bin/kit \
|
rm -rf /etc/systemd/system/kit-sub.service /usr/local/lib/kit-sub /etc/kit-sub /etc/kit /usr/local/bin/kit \
|
||||||
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu "$RESULT"
|
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert "$RESULT"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
# Наш nginx держит 443 — без этого проверка порта ниже не пустит REALITY.
|
# Наш nginx держит 443 — без этого проверка порта ниже не пустит REALITY.
|
||||||
if [[ -f /etc/nginx/kit-stream.conf ]]; then
|
if [[ -f /etc/nginx/kit-stream.conf ]]; then
|
||||||
@@ -160,6 +165,9 @@ main() {
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
||||||
|
local re_host='^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$'
|
||||||
|
[[ -z $SNI || $SNI =~ $re_host ]] || die "--sni: нужно имя сайта, например dl.google.com"
|
||||||
|
[[ -z $HOST || $HOST =~ $re_host || $HOST =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || die "--host: нужен IP или домен"
|
||||||
[[ $NAME =~ ^[A-Za-z0-9_.-]{1,32}$ ]] || die "Имя: латиница, цифры, _ . - (до 32 символов)."
|
[[ $NAME =~ ^[A-Za-z0-9_.-]{1,32}$ ]] || die "Имя: латиница, цифры, _ . - (до 32 символов)."
|
||||||
[[ $PANEL_SSL =~ ^(auto|ip|none)$ ]] || die "--panel-ssl: auto, ip или none"
|
[[ $PANEL_SSL =~ ^(auto|ip|none)$ ]] || die "--panel-ssl: auto, ip или none"
|
||||||
if [[ -n $ucert || -n $ukey ]]; then
|
if [[ -n $ucert || -n $ukey ]]; then
|
||||||
@@ -234,6 +242,8 @@ main() {
|
|||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
say "Ставлю 3X-UI $XUI_VERSION официальным установщиком (пара минут)"
|
say "Ставлю 3X-UI $XUI_VERSION официальным установщиком (пара минут)"
|
||||||
curl -fsSL --retry 3 -o "$tmp" "https://raw.githubusercontent.com/$XUI_REPO/$XUI_VERSION/install.sh"
|
curl -fsSL --retry 3 -o "$tmp" "https://raw.githubusercontent.com/$XUI_REPO/$XUI_VERSION/install.sh"
|
||||||
|
[[ $(sha256sum "$tmp" | awk '{print $1}') == "$XUI_INSTALL_SHA256" ]] \
|
||||||
|
|| die "Установщик 3X-UI $XUI_VERSION не совпал с проверенным (SHA256) – не запускаю. Сообщите нам: github.com/itsnotkubrick/3X-UI_KIT/issues"
|
||||||
if ! XUI_NONINTERACTIVE=1 XUI_SSL_MODE="${PANEL_SSL/custom/none}" XUI_SERVER_IP="$HOST" \
|
if ! XUI_NONINTERACTIVE=1 XUI_SSL_MODE="${PANEL_SSL/custom/none}" XUI_SERVER_IP="$HOST" \
|
||||||
XUI_PANEL_PORT="$panel_port" XUI_WEB_BASE_PATH="$panel_path" \
|
XUI_PANEL_PORT="$panel_port" XUI_WEB_BASE_PATH="$panel_path" \
|
||||||
XUI_USERNAME="$panel_user" XUI_PASSWORD="$panel_pass" \
|
XUI_USERNAME="$panel_user" XUI_PASSWORD="$panel_pass" \
|
||||||
@@ -712,7 +722,7 @@ awg_attach() { # имя subId [лимит-байт] [срок-мс] [устро
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
KIT_CLI_URL="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/kit.sh"
|
KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"
|
||||||
|
|
||||||
install_kit_cli() {
|
install_kit_cli() {
|
||||||
install -d -m 700 /etc/kit
|
install -d -m 700 /etc/kit
|
||||||
@@ -737,7 +747,7 @@ KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubri
|
|||||||
|
|
||||||
# После «x-ui → Uninstall» меню подсказывает команду официального установщика —
|
# После «x-ui → Uninstall» меню подсказывает команду официального установщика —
|
||||||
# меняем её на нашу. Только в echo: вызов установщика в «Update» не трогаем.
|
# меняем её на нашу. Только в echo: вызов установщика в «Update» не трогаем.
|
||||||
# Меню обновляется вместе с панелью, поэтому раз в сутки подсказку правит cron.
|
# Правим один раз при установке, без cron: чужие файлы по расписанию не трогаем.
|
||||||
brand_xui_menu() {
|
brand_xui_menu() {
|
||||||
printf '%s\n' '/echo.*mhsanaei\/3x-ui\/[a-z]*\/install\.sh/ s#bash <(curl -Ls https://raw\.githubusercontent\.com/mhsanaei/3x-ui/[a-z]*/install\.sh)#'"$KIT_INSTALL_CMD"'#' \
|
printf '%s\n' '/echo.*mhsanaei\/3x-ui\/[a-z]*\/install\.sh/ s#bash <(curl -Ls https://raw\.githubusercontent\.com/mhsanaei/3x-ui/[a-z]*/install\.sh)#'"$KIT_INSTALL_CMD"'#' \
|
||||||
>/etc/kit/xui-menu.sed
|
>/etc/kit/xui-menu.sed
|
||||||
@@ -745,8 +755,7 @@ brand_xui_menu() {
|
|||||||
for f in /usr/bin/x-ui /usr/local/x-ui/x-ui.sh; do
|
for f in /usr/bin/x-ui /usr/local/x-ui/x-ui.sh; do
|
||||||
[[ -f $f ]] && sed -i -f /etc/kit/xui-menu.sed "$f"
|
[[ -f $f ]] && sed -i -f /etc/kit/xui-menu.sed "$f"
|
||||||
done
|
done
|
||||||
echo '23 4 * * * root for f in /usr/bin/x-ui /usr/local/x-ui/x-ui.sh; do [ -f "$f" ] && sed -i -f /etc/kit/xui-menu.sed "$f"; done' \
|
rm -f /etc/cron.d/kit-xui-menu
|
||||||
>/etc/cron.d/kit-xui-menu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- всё на 443: nginx ----------
|
# ---------- всё на 443: nginx ----------
|
||||||
@@ -910,7 +919,44 @@ setup_subscription() {
|
|||||||
SUB_FETCH="$(if [[ $TRUSTED == yes ]]; then echo https; else echo http; fi)://$HOST:$SUB_PORT$SUB_PATH$SUBID"
|
SUB_FETCH="$(if [[ $TRUSTED == yes ]]; then echo https; else echo http; fi)://$HOST:$SUB_PORT$SUB_PATH$SUBID"
|
||||||
}
|
}
|
||||||
|
|
||||||
KIT_SUB_URL="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/kit-sub.py"
|
# Юнит kit-sub: без root (DynamicUser), конфиг и сертификат – через LoadCredential.
|
||||||
|
# Сертификат Let's Encrypt на IP продлевается раз в несколько дней, поэтому при отдельном
|
||||||
|
# порте kit-sub перезапускается раз в сутки и берёт свежий.
|
||||||
|
kit_sub_unit() { # путь-к-сертификату путь-к-ключу (пусто – за nginx)
|
||||||
|
cat <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
||||||
|
After=network-online.target x-ui.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/lib/kit-sub/kit_sub.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
DynamicUser=yes
|
||||||
|
LoadCredential=config.json:/etc/kit-sub/config.json
|
||||||
|
${1:+LoadCredential=cert.pem:$1}
|
||||||
|
${2:+LoadCredential=key.pem:$2}
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
MemoryMax=64M
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
}
|
||||||
|
|
||||||
|
KIT_SUB_URL="$KIT_RAW/scripts/kit-sub.py"
|
||||||
|
|
||||||
install_kit_sub() {
|
install_kit_sub() {
|
||||||
say "Ставлю подписку с учётом приложения (kit-sub)"
|
say "Ставлю подписку с учётом приложения (kit-sub)"
|
||||||
@@ -934,29 +980,12 @@ install_kit_sub() {
|
|||||||
'{listen: "0.0.0.0", port: $port, path: $path, upstream: $up, cert: $cert, key: $key, host: $host}' >/etc/kit-sub/config.json
|
'{listen: "0.0.0.0", port: $port, path: $path, upstream: $up, cert: $cert, key: $key, host: $host}' >/etc/kit-sub/config.json
|
||||||
fi
|
fi
|
||||||
chmod 600 /etc/kit-sub/config.json
|
chmod 600 /etc/kit-sub/config.json
|
||||||
cat >/etc/systemd/system/kit-sub.service <<'UNIT'
|
if [[ $SINGLE == yes ]]; then
|
||||||
[Unit]
|
kit_sub_unit >/etc/systemd/system/kit-sub.service
|
||||||
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
else
|
||||||
After=network-online.target x-ui.service
|
kit_sub_unit "$CERT" "$KEY" >/etc/systemd/system/kit-sub.service
|
||||||
Wants=network-online.target
|
echo '19 4 * * * root systemctl restart kit-sub >/dev/null 2>&1' >/etc/cron.d/kit-sub-cert
|
||||||
|
fi
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/python3 /usr/local/lib/kit-sub/kit_sub.py
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
NoNewPrivileges=true
|
|
||||||
ProtectSystem=strict
|
|
||||||
ProtectHome=read-only
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
||||||
MemoryMax=64M
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
UNIT
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable kit-sub >/dev/null 2>&1
|
systemctl enable kit-sub >/dev/null 2>&1
|
||||||
systemctl restart kit-sub
|
systemctl restart kit-sub
|
||||||
|
|||||||
+32
-15
@@ -12,7 +12,16 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
HY_VERSION="2.12.3"
|
HY_VERSION="2.12.3"
|
||||||
HY_REPO="HyNetworks/hysteria"
|
HY_REPO="HyNetworks/hysteria"
|
||||||
SELF_URL="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/hysteria2.sh"
|
# SHA256 бинарников этой версии, записанные в сам скрипт: проверка не зависит от файла
|
||||||
|
# hashes.txt, который лежит там же, где бинарник (2026-09-30, hashes.txt релиза app/v2.12.3).
|
||||||
|
declare -A HY_SHA256=(
|
||||||
|
[amd64]=8c7a68a906998b747a0db87586e364f995fbfddb95693ae6e2fdb68a6e920d3e
|
||||||
|
[arm64]=c8dc653c3ba0a28d29a26b8fa52d2086f27c0927afddce95c09965e7174e78b0
|
||||||
|
)
|
||||||
|
KIT_VERSION="1.1"
|
||||||
|
KIT_REPO_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT"
|
||||||
|
# Скрипт берём из тега релиза, а не из меняющейся ветки main.
|
||||||
|
SELF_URL="$KIT_REPO_RAW/v$KIT_VERSION/scripts/hysteria2.sh"
|
||||||
|
|
||||||
BIN=/usr/local/bin/hysteria
|
BIN=/usr/local/bin/hysteria
|
||||||
CLI=/usr/local/bin/hy2
|
CLI=/usr/local/bin/hy2
|
||||||
@@ -94,8 +103,7 @@ install_binary() {
|
|||||||
tmp=$(mktemp -d)
|
tmp=$(mktemp -d)
|
||||||
say "Скачиваю Hysteria $HY_VERSION ($arch) с GitHub"
|
say "Скачиваю Hysteria $HY_VERSION ($arch) с GitHub"
|
||||||
curl -fsSL --retry 3 -o "$tmp/hysteria" "$url/hysteria-linux-$arch"
|
curl -fsSL --retry 3 -o "$tmp/hysteria" "$url/hysteria-linux-$arch"
|
||||||
curl -fsSL --retry 3 -o "$tmp/hashes.txt" "$url/hashes.txt"
|
expected=${HY_SHA256[$arch]:-}
|
||||||
expected=$(awk -v f="build/hysteria-linux-$arch" '$2==f {print $1}' "$tmp/hashes.txt")
|
|
||||||
actual=$(sha256sum "$tmp/hysteria" | awk '{print $1}')
|
actual=$(sha256sum "$tmp/hysteria" | awk '{print $1}')
|
||||||
[[ -n $expected && $expected == "$actual" ]] || { rm -rf "$tmp"; die "Контрольная сумма не совпала — файл повреждён или подменён."; }
|
[[ -n $expected && $expected == "$actual" ]] || { rm -rf "$tmp"; die "Контрольная сумма не совпала — файл повреждён или подменён."; }
|
||||||
install -m 755 "$tmp/hysteria" "$BIN"
|
install -m 755 "$tmp/hysteria" "$BIN"
|
||||||
@@ -277,10 +285,14 @@ cmd_install() {
|
|||||||
read -rp "Домен (Enter — без домена): " DOMAIN
|
read -rp "Домен (Enter — без домена): " DOMAIN
|
||||||
if [[ -n $DOMAIN ]]; then read -rp "Почта для Let's Encrypt: " EMAIL; fi
|
if [[ -n $DOMAIN ]]; then read -rp "Почта для Let's Encrypt: " EMAIL; fi
|
||||||
fi
|
fi
|
||||||
|
# Значения попадают в install.env и конфиг Hysteria — пропускаем только допустимые символы.
|
||||||
|
local re_host='^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$'
|
||||||
if [[ -n $DOMAIN ]]; then
|
if [[ -n $DOMAIN ]]; then
|
||||||
[[ $DOMAIN =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] || die "Похоже, это не домен: $DOMAIN"
|
[[ $DOMAIN =~ $re_host ]] || die "Похоже, это не домен: $DOMAIN"
|
||||||
[[ $EMAIL == *@*.* ]] || die "Для Let's Encrypt нужна почта: --email you@example.com"
|
[[ $EMAIL =~ ^[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+\.)+[A-Za-z]{2,63}$ ]] || die "Для Let's Encrypt нужна почта: --email you@example.com"
|
||||||
fi
|
fi
|
||||||
|
[[ -z $SNI || $SNI =~ $re_host ]] || die "--sni: нужно имя сайта, например www.bing.com"
|
||||||
|
[[ -z $HOST || $HOST =~ $re_host || $HOST =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || die "--host: нужен IP или домен"
|
||||||
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
||||||
[[ $USERNAME =~ ^[A-Za-z0-9_.-]{1,32}$ ]] || die "Имя пользователя: латиница, цифры, _ . - (до 32 символов)."
|
[[ $USERNAME =~ ^[A-Za-z0-9_.-]{1,32}$ ]] || die "Имя пользователя: латиница, цифры, _ . - (до 32 символов)."
|
||||||
|
|
||||||
@@ -310,14 +322,15 @@ cmd_install() {
|
|||||||
write_masq
|
write_masq
|
||||||
[[ -z $DOMAIN ]] && make_self_signed "$SNI"
|
[[ -z $DOMAIN ]] && make_self_signed "$SNI"
|
||||||
|
|
||||||
cat >"$STATE" <<EOF
|
# printf %q: файл потом читается через source, значения не должны превратиться в команды.
|
||||||
DOMAIN=$DOMAIN
|
{
|
||||||
EMAIL=$EMAIL
|
printf 'DOMAIN=%q\n' "$DOMAIN"
|
||||||
PORT=$PORT
|
printf 'EMAIL=%q\n' "$EMAIL"
|
||||||
HOST=$HOST
|
printf 'PORT=%q\n' "$PORT"
|
||||||
SNI=$SNI
|
printf 'HOST=%q\n' "$HOST"
|
||||||
UFW=$UFW
|
printf 'SNI=%q\n' "$SNI"
|
||||||
EOF
|
printf 'UFW=%q\n' "$UFW"
|
||||||
|
} >"$STATE"
|
||||||
chmod 600 "$STATE"
|
chmod 600 "$STATE"
|
||||||
printf '%s %s\n' "$USERNAME" "$(rand 16)" >"$USERS"
|
printf '%s %s\n' "$USERNAME" "$(rand 16)" >"$USERS"
|
||||||
chmod 600 "$USERS"
|
chmod 600 "$USERS"
|
||||||
@@ -424,9 +437,13 @@ cmd_status() {
|
|||||||
cmd_update() {
|
cmd_update() {
|
||||||
require_installed
|
require_installed
|
||||||
local tmp
|
local tmp
|
||||||
|
local latest
|
||||||
|
latest=$(curl -fsS -m 8 "$KIT_REPO_RAW/main/VERSION" | tr -d '[:space:]') || true
|
||||||
|
[[ $latest =~ ^[0-9]+(\.[0-9]+)+$ ]] || die "Не удалось узнать последнюю версию: GitHub недоступен с сервера."
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
say "Скачиваю свежую версию скрипта"
|
say "Скачиваю скрипт версии $latest"
|
||||||
curl -fsSL --retry 3 -o "$tmp" "$SELF_URL"
|
curl -fsSL --retry 3 -o "$tmp" "$KIT_REPO_RAW/v$latest/scripts/hysteria2.sh"
|
||||||
|
bash -n "$tmp" || { rm -f "$tmp"; die "Скачанный скрипт повреждён – ничего не менял."; }
|
||||||
bash "$tmp" __update_binary
|
bash "$tmp" __update_binary
|
||||||
install -m 755 "$tmp" "$CLI"
|
install -m 755 "$tmp" "$CLI"
|
||||||
rm -f "$tmp"
|
rm -f "$tmp"
|
||||||
|
|||||||
+7
-1
@@ -27,7 +27,11 @@ import urllib.request
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
CONFIG = os.environ.get("KIT_SUB_CONFIG", "/etc/kit-sub/config.json")
|
# Под systemd kit-sub работает без root (DynamicUser): конфиг и сертификат ему передаёт
|
||||||
|
# systemd через LoadCredential в $CREDENTIALS_DIRECTORY.
|
||||||
|
CREDS = os.environ.get("CREDENTIALS_DIRECTORY", "")
|
||||||
|
CONFIG = os.environ.get("KIT_SUB_CONFIG") or (
|
||||||
|
os.path.join(CREDS, "config.json") if CREDS and os.path.exists(os.path.join(CREDS, "config.json")) else "/etc/kit-sub/config.json")
|
||||||
CLASH_UA = re.compile(r"clash|mihomo|flclash|stash|nyanpasu|meta", re.I)
|
CLASH_UA = re.compile(r"clash|mihomo|flclash|stash|nyanpasu|meta", re.I)
|
||||||
# AmneziaWG добавляем только приложениям на ядре Mihomo. Karing, Hiddify и другие на sing-box
|
# AmneziaWG добавляем только приложениям на ядре Mihomo. Karing, Hiddify и другие на sing-box
|
||||||
# тоже могут просить формат Clash (Karing так и делает), но AmneziaWG не умеют.
|
# тоже могут просить формат Clash (Karing так и делает), но AmneziaWG не умеют.
|
||||||
@@ -211,6 +215,8 @@ class Server(http.server.ThreadingHTTPServer):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
cert, key = CONF.get("cert"), CONF.get("key")
|
cert, key = CONF.get("cert"), CONF.get("key")
|
||||||
|
if cert and CREDS and os.path.exists(os.path.join(CREDS, "cert.pem")):
|
||||||
|
cert, key = os.path.join(CREDS, "cert.pem"), os.path.join(CREDS, "key.pem")
|
||||||
if not cert:
|
if not cert:
|
||||||
srv = Server((CONF.get("listen", "127.0.0.1"), int(CONF["port"])), Handler)
|
srv = Server((CONF.get("listen", "127.0.0.1"), int(CONF["port"])), Handler)
|
||||||
log(f"kit-sub слушает http://{CONF.get('listen', '127.0.0.1')}:{CONF['port']}{PATH} (TLS снимает nginx)")
|
log(f"kit-sub слушает http://{CONF.get('listen', '127.0.0.1')}:{CONF['port']}{PATH} (TLS снимает nginx)")
|
||||||
|
|||||||
Reference in New Issue
Block a user