Панель и подписка по домену или по IP: kit net panel, --panel-on при установке, выбор в диалоге, возврат при --restore

This commit is contained in:
maxdmitrov
2026-10-05 16:38:41 +03:00
parent f22e043902
commit 00319a6655
5 changed files with 226 additions and 7 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
* Установка теперь по шагам, а все ссылки разложены по полочкам: основные и запасные. Раньше они сыпались потоком, как лента новостей. * Установка теперь по шагам, а все ссылки разложены по полочкам: основные и запасные. Раньше они сыпались потоком, как лента новостей.
* Сайт-прикрытие подбирается среди соседей по подсети, а не «у всех один». Свой домен по-прежнему лучший вариант. REALITY теперь с `xtls-rprx-vision`, Hysteria2 отвечает на чужой запрос страницей сайта. * Сайт-прикрытие подбирается среди соседей по подсети, а не «у всех один». Свой домен по-прежнему лучший вариант. REALITY теперь с `xtls-rprx-vision`, Hysteria2 отвечает на чужой запрос страницей сайта.
* `kit check --deep` подключается к серверу настоящим клиентом, `--fix` лечит безопасное. Общий лимит трафика считается по всем подключениям пользователя, а в подписке Clash есть «Авто»: клиент сам выбирает быстрейший протокол. DNS в подписках теперь по DoH через прокси: запросы имён не гуляют где попало. * `kit check --deep` подключается к серверу настоящим клиентом, `--fix` лечит безопасное. Общий лимит трафика считается по всем подключениям пользователя, а в подписке Clash есть «Авто»: клиент сам выбирает быстрейший протокол. DNS в подписках теперь по DoH через прокси: запросы имён не гуляют где попало.
* Вернулся `--restore`: чистый сервер поднимается из `kit backup`, как из резервной копии настроек телефона. Ссылки `kit user link --amnezia` и `--telegram` для AmneziaVPN и Telegram теперь одной командой, а исправление времени на Debian 13 прислал [xlok1e](https://github.com/xlok1e), спасибо. * Со своим доменом панель и подписку можно открывать и по домену: `--panel-on domain` при установке или `kit net panel domain` потом. Вернулся `--restore`: чистый сервер поднимается из `kit backup`, как из резервной копии настроек телефона. Ссылки `kit user link --amnezia` и `--telegram` для AmneziaVPN и Telegram теперь одной командой, а исправление времени на Debian 13 прислал [xlok1e](https://github.com/xlok1e), спасибо.
Сервер обновится сам этой ночью, нетерпеливым – `kit update`. Сервер обновится сам этой ночью, нетерпеливым – `kit update`.
+1
View File
@@ -105,6 +105,7 @@ kit net off имя / kit net on имя # выключит
kit net fp firefox # сменить отпечаток клиента у всех подключений kit net fp firefox # сменить отпечаток клиента у всех подключений
kit net masq on # Hysteria2 отвечает на чужой запрос страницей сайта kit net masq on # Hysteria2 отвечает на чужой запрос страницей сайта
kit net dns on # DNS в подписке по DoH через прокси (ставится при установке) kit net dns on # DNS в подписке по DoH через прокси (ставится при установке)
kit net panel domain | ip # панель и подписка по своему домену или по IP (при установке: --panel-on)
kit check [--deep] [--fix] # проверить сервер; --deep – ещё и подключиться клиентом, --fix – исправить безопасное kit check [--deep] [--fix] # проверить сервер; --deep – ещё и подключиться клиентом, --fix – исправить безопасное
kit update [--panel] # обновить сейчас; --panel – панель 3X-UI до проверенной версии kit update [--panel] # обновить сейчас; --panel – панель 3X-UI до проверенной версии
+4
View File
@@ -179,8 +179,12 @@ kit net off vmess-ws # выключить протокол и закры
kit net on reality2 # создать второй REALITY на высоком порту kit net on reality2 # создать второй REALITY на высоком порту
kit net fp firefox # сменить отпечаток клиента у всех подключений kit net fp firefox # сменить отпечаток клиента у всех подключений
kit net masq on # Hysteria2 отвечает на чужой запрос страницей сайта kit net masq on # Hysteria2 отвечает на чужой запрос страницей сайта
kit net panel domain # панель и подписка открываются и по домену (только со своим доменом); kit net panel ip – как раньше
``` ```
Со своим доменом панель и подписка по умолчанию открываются по IP (`https://IP/путь/`). Если нужен адрес с доменом (например, для Telegram-бота панели
или ссылки, которая не зависит от IP), выберите «по домену» при установке (`--panel-on domain`) или позже: `kit net panel domain`. Путь у них секретный и по домену, как и по IP.
Лучший сайт маскировки – ваш собственный домен (`--domain` при установке): сервер изображает ваш сайт, а сертификат Лучший сайт маскировки – ваш собственный домен (`--domain` при установке): сервер изображает ваш сайт, а сертификат
и имя совпадают. Без домена установщик ищет сайт среди соседей по подсети сервера: он естественнее, чем известный и имя совпадают. Без домена установщик ищет сайт среди соседей по подсети сервера: он естественнее, чем известный
сайт на чужом IP. Основные протоколы – REALITY, XHTTP и Hysteria2; остальные запасные, их можно выключить командой сайт на чужом IP. Основные протоколы – REALITY, XHTTP и Hysteria2; остальные запасные, их можно выключить командой
+34 -3
View File
@@ -65,6 +65,7 @@ declare -A INNER=([reality]=10443 [xhttp]=10444 [mtproto]=10445 [web]=10446 [sel
SNI2=""; SNI3="" SNI2=""; SNI3=""
# Свой домен (self-steal): REALITY маскируется под сайт на этом же сервере, а не под чужой. # Свой домен (self-steal): REALITY маскируется под сайт на этом же сервере, а не под чужой.
DOMAIN="" DOMAIN=""
PANEL_ON=ip # где открываются панель и подписка: ip или domain (domain – только со своим доменом)
LINK_HOST="" # адрес в ссылках: свой домен, если он выбран, иначе IP LINK_HOST="" # адрес в ссылках: свой домен, если он выбран, иначе IP
DOMAIN_CERT_DIR=/root/cert/domain DOMAIN_CERT_DIR=/root/cert/domain
SELF_IP_CERT=no # yes – сертификат на IP самоподписанный (Let's Encrypt отказал, пользователь согласился) SELF_IP_CERT=no # yes – сертификат на IP самоподписанный (Let's Encrypt отказал, пользователь согласился)
@@ -210,6 +211,20 @@ ask_tty() { # приглашение; ответ – в REPLY; не 0, если
read -r -p "$1" REPLY </dev/tty || { REPLY=""; return 1; } read -r -p "$1" REPLY </dev/tty || { REPLY=""; return 1; }
} }
# Где открывать панель и подписку, если выбран свой домен.
panel_on_choice() {
[[ $PANEL_ON == domain ]] && return 0
echo
echo "${B}Где открывать панель и подписку?${N}"
echo " ${B}1${N} По IP https://$HOST/… как раньше ${G}[по умолчанию]${N}"
echo " ${B}2${N} По домену https://$DOMAIN/… сертификат домена, ссылка не зависит от IP"
echo " ${D}Менять можно потом: kit net panel domain | ip${N}"
ask_tty "Выбор [1]: " || return 0
REPLY=${REPLY//[[:space:]]/}
[[ ${REPLY%.} == 2 ]] && PANEL_ON=domain
return 0
}
# Вопрос пользователю: свой домен, сосед по подсети (по умолчанию) или свой сайт. # Вопрос пользователю: свой домен, сосед по подсети (по умолчанию) или свой сайт.
choose_masking() { choose_masking() {
echo echo
@@ -242,7 +257,7 @@ choose_masking() {
[[ -n $d ]] || d=$prev [[ -n $d ]] || d=$prev
[[ -n $d ]] || return 0 [[ -n $d ]] || return 0
if [[ ! $d =~ $re_host ]]; then warn "Это не похоже на домен (нужно только имя, без https://). Пример: vpn.example.com"; prev=""; continue; fi if [[ ! $d =~ $re_host ]]; then warn "Это не похоже на домен (нужно только имя, без https://). Пример: vpn.example.com"; prev=""; continue; fi
if domain_points_here "$d"; then DOMAIN=$d; return 0; fi if domain_points_here "$d"; then DOMAIN=$d; panel_on_choice; return 0; fi
prev=$d prev=$d
ask_tty "Enter – проверить ещё раз (DNS обновляется не сразу), s – стандартный сайт, q – выйти: " || return 0 ask_tty "Enter – проверить ещё раз (DNS обновляется не сразу), s – стандартный сайт, q – выйти: " || return 0
REPLY=${REPLY//[[:space:]]/} REPLY=${REPLY//[[:space:]]/}
@@ -400,7 +415,7 @@ main() {
local PORT=443 SNI="" PANEL_SSL=auto HOST="" UFW=yes NAME="admin" yes=no protos=all ucert="" ukey="" multi=no restore="" local PORT=443 SNI="" PANEL_SSL=auto HOST="" UFW=yes NAME="admin" yes=no protos=all ucert="" ukey="" multi=no restore=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
--port | --sni | --panel-ssl | --host | --user | --protocols | --domain | --cert | --key | --restore) --port | --sni | --panel-ssl | --host | --user | --protocols | --domain | --cert | --key | --restore | --panel-on)
[[ -n ${2-} ]] || die "У параметра $1 нет значения (см. --help)" ;; [[ -n ${2-} ]] || die "У параметра $1 нет значения (см. --help)" ;;
esac esac
case $1 in case $1 in
@@ -415,6 +430,7 @@ main() {
--multi-port) multi=yes; shift ;; --multi-port) multi=yes; shift ;;
--key) ukey=$2; shift 2 ;; --key) ukey=$2; shift 2 ;;
--restore) restore=$2; shift 2 ;; --restore) restore=$2; shift 2 ;;
--panel-on) PANEL_ON=${2,,}; shift 2 ;;
--no-ufw) UFW=no; shift ;; --no-ufw) UFW=no; shift ;;
-y|--yes) yes=yes; shift ;; -y|--yes) yes=yes; shift ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
@@ -443,6 +459,8 @@ main() {
[[ -z $DOMAIN || $DOMAIN =~ $re_host ]] || die "--domain: нужно имя вашего домена, например vpn.example.com" [[ -z $DOMAIN || $DOMAIN =~ $re_host ]] || die "--domain: нужно имя вашего домена, например vpn.example.com"
[[ -z $DOMAIN || -z $SNI ]] || die "--sni и --domain вместе не нужны: выберите либо чужой сайт (--sni), либо свой домен (--domain)." [[ -z $DOMAIN || -z $SNI ]] || die "--sni и --domain вместе не нужны: выберите либо чужой сайт (--sni), либо свой домен (--domain)."
[[ -z $DOMAIN || $multi == no ]] || die "Свой домен работает только в режиме «всё на 443» – уберите --multi-port." [[ -z $DOMAIN || $multi == no ]] || die "Свой домен работает только в режиме «всё на 443» – уберите --multi-port."
[[ $PANEL_ON == ip || $PANEL_ON == domain ]] || die "--panel-on: ip или domain"
[[ $PANEL_ON == ip || -n $DOMAIN || $yes == no ]] || die "--panel-on domain работает только со своим доменом (--domain vpn.example.com)."
[[ -z $HOST || $HOST =~ $re_host || $HOST =~ ^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$ ]] || die "--host: нужен IP (например 1.2.3.4) или домен" [[ -z $HOST || $HOST =~ $re_host || $HOST =~ ^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$ ]] || die "--host: нужен IP (например 1.2.3.4) или домен"
[[ $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"
@@ -550,6 +568,7 @@ main() {
die "$SNI не отвечает по TLS 1.3 + HTTP/2 – REALITY с ним работать не будет. Выберите другой сайт." die "$SNI не отвечает по TLS 1.3 + HTTP/2 – REALITY с ним работать не будет. Выберите другой сайт."
fi fi
LINK_HOST=${DOMAIN:-$HOST} LINK_HOST=${DOMAIN:-$HOST}
[[ $PANEL_ON == ip || -n $DOMAIN ]] || { PANEL_ON=ip; later "--panel-on domain нужен со своим доменом: панель и подписка открываются по IP."; }
# Режим «всё на 443»: XHTTP и MTProto различаются по имени сайта, берём и им соседей по подсети. # Режим «всё на 443»: XHTTP и MTProto различаются по имени сайта, берём и им соседей по подсети.
if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then
local -a nb2=() local -a nb2=()
@@ -667,6 +686,9 @@ main() {
step "Защита и обновления" step "Защита и обновления"
install_kit_cli install_kit_cli
if [[ $PANEL_ON == domain ]]; then
/usr/local/bin/kit net panel domain >/dev/null 2>&1 || { PANEL_ON=ip; later "Панель и подписку по домену включить не удалось: оставил по IP. Попробуйте позже: kit net panel domain"; }
fi
brand_xui_menu brand_xui_menu
# Автообновление kit и kit-sub: только подписанные релизы, выключается kit update --manual. # Автообновление kit и kit-sub: только подписанные релизы, выключается kit update --manual.
/usr/local/bin/kit update --auto >/dev/null 2>&1 || later "Автообновление не включилось – включите позже: kit update --auto" /usr/local/bin/kit update --auto >/dev/null 2>&1 || later "Автообновление не включилось – включите позже: kit update --auto"
@@ -697,8 +719,10 @@ main() {
# --- итог --- # --- итог ---
local panel_url links local panel_url links
local ph=$HOST
if [[ $PANEL_ON == domain ]]; then ph=$DOMAIN; SUB_URL=${SUB_URL/\/\/$HOST\//\/\/$DOMAIN\/}; fi
if [[ $SINGLE == yes ]]; then if [[ $SINGLE == yes ]]; then
panel_url="https://$HOST/${XUI_WEB_BASE_PATH#/}" panel_url="https://$ph/${XUI_WEB_BASE_PATH#/}"
panel_url="${panel_url%/}/" panel_url="${panel_url%/}/"
elif [[ $TRUSTED == yes ]]; then elif [[ $TRUSTED == yes ]]; then
panel_url="https://$HOST:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH" panel_url="https://$HOST:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH"
@@ -1232,6 +1256,7 @@ install_kit_cli() {
{ {
printf 'HOST=%q\n' "$HOST" printf 'HOST=%q\n' "$HOST"
printf 'LINK_HOST=%q\n' "${LINK_HOST:-$HOST}" printf 'LINK_HOST=%q\n' "${LINK_HOST:-$HOST}"
printf 'PANEL_ON=%q\n' "ip"
printf 'SUB_BASE=%q\n' "${SUB_URL%$SUBID}" printf 'SUB_BASE=%q\n' "${SUB_URL%$SUBID}"
printf 'SUB_PATH=%q\n' "$SUB_PATH" printf 'SUB_PATH=%q\n' "$SUB_PATH"
printf 'SUB_INTERNAL=%q\n' "${SUB_INTERNAL:-$SUB_PORT}" printf 'SUB_INTERNAL=%q\n' "${SUB_INTERNAL:-$SUB_PORT}"
@@ -1787,6 +1812,11 @@ PY
if [[ -f /etc/kit/kit.env ]]; then if [[ -f /etc/kit/kit.env ]]; then
install_kit_file install_kit_file
# Панель по домену: nginx собран заново, значит блок домена нужно вернуть (домен должен указывать на этот сервер).
if grep -q '^PANEL_ON=domain' /etc/kit/kit.env; then
sed -i 's/^PANEL_ON=.*/PANEL_ON=ip/' /etc/kit/kit.env
/usr/local/bin/kit net panel domain >/dev/null 2>&1 || warn "Панель по домену не включилась (домен должен указывать на этот сервер): kit net panel domain"
fi
brand_xui_menu brand_xui_menu
/usr/local/bin/kit update --auto >/dev/null 2>&1 || later "Автообновление не включилось – включите позже: kit update --auto" /usr/local/bin/kit update --auto >/dev/null 2>&1 || later "Автообновление не включилось – включите позже: kit update --auto"
/usr/local/bin/kit __limit-timer on >/dev/null 2>&1 || later "Проверка общего лимита трафика не включилась – включите позже: kit fix" /usr/local/bin/kit __limit-timer on >/dev/null 2>&1 || later "Проверка общего лимита трафика не включилась – включите позже: kit fix"
@@ -1843,6 +1873,7 @@ usage() {
--user admin имя первого клиента --user admin имя первого клиента
--host 1.2.3.4 адрес в ссылке, если IP определился неверно --host 1.2.3.4 адрес в ссылке, если IP определился неверно
--restore файл поднять сервер из резервной копии kit backup (на чистом VPS) --restore файл поднять сервер из резервной копии kit backup (на чистом VPS)
--panel-on ip|domain панель и подписка по IP (по умолчанию) или по своему домену (нужен --domain)
--no-ufw не трогать файрвол --no-ufw не трогать файрвол
-y не задавать вопросов -y не задавать вопросов
EOF EOF
+186 -3
View File
@@ -1678,6 +1678,179 @@ net_dns() { # [on|off]
esac esac
} }
# ---------- панель и подписка: по IP или по домену ----------
# Со своим доменом (режим «всё на 443») заход по имени домена попадает к сайту-прикрытию. Панель и подписка по
# умолчанию открываются по IP; `kit net panel domain` добавляет их и в блок домена (сертификат Let's Encrypt домена),
# `kit net panel ip` возвращает как было. Пути у них секретные, как и на IP.
PANEL_CONF=/etc/nginx/conf.d/kit.conf
# Разбор и пересборка блока домена в конфиге nginx. info – домен, сертификат, ключ; render – новый текст конфига.
panel_conf_py() {
python3 - "$@" <<'PY'
import re, sys
op, conf = sys.argv[1], sys.argv[2]
text = open(conf).read()
def match_brace(t, start):
depth = 0
for j in range(start, len(t)):
if t[j] == "{":
depth += 1
elif t[j] == "}":
depth -= 1
if depth == 0:
return j + 1
return -1
def servers(t):
out, i = [], 0
while True:
m = re.search(r"^server\s*\{", t[i:], re.M)
if not m:
return out
a = i + m.start()
b = match_brace(t, a)
out.append((a, b))
i = b
blocks = servers(text)
web = next((b for b in blocks if "server_name _;" in text[b[0]:b[1]]), None)
steal = next((b for b in blocks if "server_name _;" not in text[b[0]:b[1]] and re.search(r"server_name\s+\S+;", text[b[0]:b[1]])), None)
if steal is None:
sys.exit(2)
sb = text[steal[0]:steal[1]]
domain = re.search(r"server_name\s+(\S+);", sb).group(1)
cert = re.search(r"ssl_certificate\s+(\S+);", sb).group(1)
key = re.search(r"ssl_certificate_key\s+(\S+);", sb).group(1)
port = re.search(r"listen\s+127\.0\.0\.1:(\d+)", sb).group(1)
if op == "info":
print(domain, cert, key)
sys.exit(0)
mode, sub_path, panel_path = sys.argv[3], sys.argv[4], sys.argv[5]
def location(t, path):
m = re.search(r"^[ \t]*location\s+" + re.escape(path) + r"\s*\{", t, re.M)
if not m:
return ""
b = match_brace(t, m.end() - 1)
return t[m.start():b]
if mode == "domain":
wb = text[web[0]:web[1]] if web else ""
locs = "\n".join(x for x in (location(wb, sub_path), location(wb, panel_path)) if x)
if not locs:
sys.exit(3)
new = f"""server {{
listen 127.0.0.1:{port} ssl http2 proxy_protocol;
server_name {domain};
ssl_certificate {cert};
ssl_certificate_key {key};
ssl_protocols TLSv1.2 TLSv1.3;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
server_tokens off;
absolute_redirect off;
access_log off;
# kit:panel-on-domain
{locs}
location / {{
root /var/www/kit;
index index.html;
}}
}}"""
else:
new = f"""server {{
listen 127.0.0.1:{port} ssl http2;
server_name {domain};
ssl_certificate {cert};
ssl_certificate_key {key};
ssl_protocols TLSv1.2 TLSv1.3;
server_tokens off;
access_log off;
location / {{
root /var/www/kit;
index index.html;
}}
}}"""
sys.stdout.write(text[:steal[0]] + new + text[steal[1]:])
PY
}
kit_env_set() { # ключ значение: меняет или добавляет строку в /etc/kit/kit.env
python3 - "$KIT_ENV" "$1" "$2" <<'PY'
import re, shlex, sys
p, k, v = sys.argv[1:4]
lines = open(p).read().splitlines()
new = f"{k}={shlex.quote(v)}"
for i, l in enumerate(lines):
if l.startswith(k + "="):
lines[i] = new
break
else:
lines.append(new)
open(p, "w").write("\n".join(lines) + "\n")
PY
}
net_panel() { # [domain|ip]
local want=${1:-} info domain cert key cur new tmp bak rid panel_path old_host new_host all
[[ ${SINGLE:-no} == yes && -f $PANEL_CONF ]] || die "Работает в режиме «всё на 443» со своим доменом (--domain при установке)."
info=$(panel_conf_py info "$PANEL_CONF") || die "В настройках nginx нет блока своего домена: панель по домену доступна только при установке с --domain."
read -r domain cert key <<<"$info"
cur=${PANEL_ON:-ip}
if [[ -z $want ]]; then
if [[ $cur == domain ]]; then echo "Панель и подписка открываются по домену $domain (вернуть на IP: kit net panel ip)"
else echo "Панель и подписка открываются по IP ${HOST:-?}; домен $domain – сайт-прикрытие (открывать и по домену: kit net panel domain)"; fi
return 0
fi
[[ $want == domain || $want == ip ]] || die "kit net panel [domain|ip]"
[[ $want != "$cur" ]] || { say "Уже так: панель и подписка по $([[ $want == domain ]] && echo "домену $domain" || echo "IP")."; return 0; }
panel_path=/${XUI_WEB_BASE_PATH#/}; panel_path=${panel_path%/}/
if [[ $want == domain ]]; then
getent ahostsv4 "$domain" 2>/dev/null | awk '{print $1}' | grep -qx "$(host_ip)" || die "Домен $domain не указывает на этот сервер ($(host_ip)). Исправьте A-запись и повторите."
[[ -s $cert && -s $key ]] || die "Нет сертификата домена ($cert) – не трогаю nginx."
fi
tmp=$(mktemp); bak=$(mktemp); cp -p "$PANEL_CONF" "$bak"
panel_conf_py render "$PANEL_CONF" "$want" "$SUB_PATH" "$panel_path" >"$tmp" || { rm -f "$tmp" "$bak"; die "Не удалось собрать настройки nginx. Ничего не изменилось."; }
rid=$(sni_targets | awk -F'\t' '$2 == "self" && $3 == "REALITY" {print $1; exit}')
[[ -n $rid ]] || { rm -f "$tmp" "$bak"; die "Не нашёл REALITY со своим доменом."; }
# Передача настоящего IP клиента сайту домена (PROXY protocol) нужна, чтобы панель видела, кто заходит.
local xv=0; [[ $want == domain ]] && xv=1
panel_revert() { cat "$bak" >"$PANEL_CONF"; inbound_patch "$rid" '.streamSettings |= ((if type == "string" then fromjson else . end) | .realitySettings.xver = $x)' --argjson x "$((1 - xv))" || true; nginx -t >/dev/null 2>&1 && systemctl reload nginx || true; }
if [[ $want == domain ]]; then
cat "$tmp" >"$PANEL_CONF"
nginx -t >/dev/null 2>&1 || { cat "$bak" >"$PANEL_CONF"; rm -f "$tmp" "$bak"; die "nginx не принял настройки. Ничего не изменилось."; }
inbound_patch "$rid" '.streamSettings |= ((if type == "string" then fromjson else . end) | .realitySettings.xver = $x)' --argjson x 1 || { cat "$bak" >"$PANEL_CONF"; rm -f "$tmp" "$bak"; die "Панель не приняла изменение. Ничего не изменилось."; }
systemctl reload nginx || { panel_revert; rm -f "$tmp" "$bak"; die "nginx не перезагрузился. Всё возвращено."; }
else
inbound_patch "$rid" '.streamSettings |= ((if type == "string" then fromjson else . end) | .realitySettings.xver = $x)' --argjson x 0 || { rm -f "$tmp" "$bak"; die "Панель не приняла изменение. Ничего не изменилось."; }
cat "$tmp" >"$PANEL_CONF"
if ! nginx -t >/dev/null 2>&1 || ! systemctl reload nginx; then panel_revert; rm -f "$tmp" "$bak"; die "nginx не принял настройки. Всё возвращено."; fi
fi
rm -f "$tmp" "$bak"
# Ссылки: адрес подписки в kit, панели и файле с данными входа.
old_host=${HOST}; new_host=$domain
if [[ $want == ip ]]; then old_host=$domain; new_host=${HOST}; fi
kit_env_set PANEL_ON "$want"
kit_env_set SUB_BASE "https://$([[ $want == domain ]] && echo "$domain" || echo "$HOST")${SUB_PATH}"
all=$(api POST setting/all '{}')
api POST setting/update "$(jq -c --arg u "https://$([[ $want == domain ]] && echo "$domain" || echo "$HOST")${SUB_PATH}" '.subURI = $u' <<<"$all")" >/dev/null || warn "Не обновил адрес подписки в панели."
[[ -f /root/3x-ui.txt ]] && python3 - /root/3x-ui.txt "https://$old_host" "https://$new_host" <<'PY'
import sys
p, a, b = sys.argv[1:4]
s = open(p).read()
open(p, "w").write(s.replace(a + "/", b + "/").replace(a + ":", b + ":"))
PY
if [[ $want == domain ]]; then
say "Панель и подписка теперь открываются и по домену $domain."
echo "Панель: https://$domain$panel_path"
echo "Подписка: https://$domain${SUB_PATH}ПОДПИСКА_ПОЛЬЗОВАТЕЛЯ (ссылки: kit user link имя)"
echo "По IP они тоже работают. Вернуть как было: kit net panel ip"
else
say "Панель и подписка снова только по IP ${HOST}."
echo "Панель: https://${HOST}$panel_path"
fi
}
# ---------- kit net: протоколы, порты, сайт маскировки, отпечаток ---------- # ---------- kit net: протоколы, порты, сайт маскировки, отпечаток ----------
net_main_name() { case $1 in REALITY | XHTTP | Hysteria2) return 0 ;; *) return 1 ;; esac; } net_main_name() { case $1 in REALITY | XHTTP | Hysteria2) return 0 ;; *) return 1 ;; esac; }
@@ -1731,6 +1904,9 @@ net_show() {
[[ $shown_reality2 == yes ]] || printf ' %s %s %s %s\n' "$(padr REALITY-2 20)" "$(padr – 12)" "$(padr выкл 8)" "включить: kit net on reality2" [[ $shown_reality2 == yes ]] || printf ' %s %s %s %s\n' "$(padr REALITY-2 20)" "$(padr – 12)" "$(padr выкл 8)" "включить: kit net on reality2"
echo echo
echo "Отпечаток клиента: ${fpl:-?} (сменить: kit net fp firefox)" echo "Отпечаток клиента: ${fpl:-?} (сменить: kit net fp firefox)"
if [[ ${SINGLE:-no} == yes && -f $PANEL_CONF ]] && panel_conf_py info "$PANEL_CONF" >/dev/null 2>&1; then
net_panel
fi
if jq -e 'any(.[]; .protocol == "hysteria")' <<<"$list" >/dev/null; then if jq -e 'any(.[]; .protocol == "hysteria")' <<<"$list" >/dev/null; then
if [[ -n $(hy_masq_state) ]]; then echo "Hysteria2 отвечает на посторонний запрос страницей сайта (выключить: kit net masq off)" if [[ -n $(hy_masq_state) ]]; then echo "Hysteria2 отвечает на посторонний запрос страницей сайта (выключить: kit net masq off)"
else echo "Hysteria2 без маскировки под сайт (включить: kit net masq on)"; fi else echo "Hysteria2 без маскировки под сайт (включить: kit net masq on)"; fi
@@ -1798,8 +1974,9 @@ cmd_net() {
fp) net_fp "$@" ;; fp) net_fp "$@" ;;
masq) hy_masq "$@" ;; masq) hy_masq "$@" ;;
dns) net_dns "$@" ;; dns) net_dns "$@" ;;
panel) net_panel "$@" ;;
vision) case ${1:-} in on) cmd_vision --all ;; off) cmd_vision --all off ;; *) die "kit net vision on|off" ;; esac ;; vision) case ${1:-} in on) cmd_vision --all ;; off) cmd_vision --all off ;; *) die "kit net vision on|off" ;; esac ;;
*) die "kit net [site | port имя порт | off имя | on имя | fp отпечаток | masq on|off | dns on|off | vision on|off]" ;; *) die "kit net [site | port имя порт | off имя | on имя | fp отпечаток | masq on|off | dns on|off | panel domain|ip | vision on|off]" ;;
esac esac
} }
@@ -1928,6 +2105,11 @@ n_toggle() {
*) net_toggle on "$PICK" ;; *) net_toggle on "$PICK" ;;
esac esac
} }
n_panel() {
net_panel
ask_num "1 – открывать по домену, 2 – только по IP, Enter – оставить: "
case $REPLY in 1) net_panel domain ;; 2) net_panel ip ;; esac
}
n_fp() { n_fp() {
local -a fps=(chrome firefox safari edge ios android qq 360 random randomized); local i local -a fps=(chrome firefox safari edge ios android qq 360 random randomized); local i
for i in "${!fps[@]}"; do printf ' %2d %s\n' "$((i + 1))" "${fps[$i]}"; done for i in "${!fps[@]}"; do printf ' %2d %s\n' "$((i + 1))" "${fps[$i]}"; done
@@ -1940,8 +2122,8 @@ menu_net() {
local c local c
while :; do while :; do
echo echo
box "Протоколы, порты и маскировка" "1. Показать протоколы и порты" "2. Сменить сайт маскировки" "3. Сменить порт протокола" "4. Выключить или включить протокол" "5. Сменить отпечаток клиента" "6. Второй REALITY на высоком порту" "" "0. Назад" box "Протоколы, порты и маскировка" "1. Показать протоколы и порты" "2. Сменить сайт маскировки" "3. Сменить порт протокола" "4. Выключить или включить протокол" "5. Сменить отпечаток клиента" "6. Второй REALITY на высоком порту" "7. Панель и подписка: по IP или по домену" "" "0. Назад"
ask_num "Выбор [0-6]: "; c=$REPLY ask_num "Выбор [0-7]: "; c=$REPLY
case $c in case $c in
1) run_action net_show; pause ;; 1) run_action net_show; pause ;;
2) run_action n_site; pause ;; 2) run_action n_site; pause ;;
@@ -1949,6 +2131,7 @@ menu_net() {
4) run_action n_toggle; pause ;; 4) run_action n_toggle; pause ;;
5) run_action n_fp; pause ;; 5) run_action n_fp; pause ;;
6) run_action net_toggle on reality2; pause ;; 6) run_action net_toggle on reality2; pause ;;
7) run_action n_panel; pause ;;
0 | "") return 0 ;; 0 | "") return 0 ;;
*) echo "Нет такого пункта." ;; *) echo "Нет такого пункта." ;;
esac esac