Страница подписки: адреса Clash и JSON работают через kit-sub и nginx (с «Авто» и DNS), подключаются при установке и при kit update
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@
|
||||
* Установка теперь по шагам, а все ссылки разложены по полочкам: основные и запасные. Раньше они сыпались потоком, как лента новостей.
|
||||
* Сайт для маскировки подбирается среди соседей по подсети, а не «у всех один». Свой домен по-прежнему лучший вариант. REALITY теперь с `xtls-rprx-vision`, Hysteria2 отвечает на чужой запрос страницей сайта.
|
||||
* `kit check --deep` подключается к серверу настоящим клиентом, `--fix` лечит безопасное. Общий лимит трафика считается по всем подключениям пользователя, а в подписке Clash есть «Авто»: клиент сам выбирает быстрейший протокол. DNS в подписках теперь по DoH через прокси: запросы имён не гуляют где попало.
|
||||
* Рядом с подпиской теперь ссылки «открыть в приложении»: одно нажатие, и подписка добавлена с названием. Со своим доменом панель и подписку можно открывать и по домену: `--panel-on domain` при установке или `kit net panel domain` потом. Вернулся `--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`.
|
||||
|
||||
|
||||
@@ -701,6 +701,9 @@ main() {
|
||||
|
||||
step "Защита и обновления"
|
||||
install_kit_cli
|
||||
if [[ $TRUSTED == yes ]]; then
|
||||
/usr/local/bin/kit __subextra >/dev/null 2>&1 || later "Адреса Clash и JSON на странице подписки не подключились: kit update --force"
|
||||
fi
|
||||
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
|
||||
|
||||
+11
-8
@@ -53,15 +53,17 @@ PASS_HEADERS = ("content-type", "content-disposition", "profile-title", "profile
|
||||
with open(CONFIG, encoding="utf-8") as f:
|
||||
CONF = json.load(f)
|
||||
PATH = "/" + CONF["path"].strip("/") + "/"
|
||||
# Дополнительные пути, которые панель показывает на странице подписки (Clash и JSON): обрабатываются так же, как основной.
|
||||
EXTRA = ["/" + x.strip("/") + "/" for x in CONF.get("extra_paths", []) if isinstance(x, str) and re.fullmatch(r"/?[A-Za-z0-9_-]+/?", x)]
|
||||
|
||||
|
||||
def log(msg):
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def upstream(sub_id, ua, host, accept):
|
||||
def upstream(sub_id, ua, host, accept, prefix=None):
|
||||
"""GET к подписке 3X-UI. Возвращает (код, заголовки, тело) или (None, {}, b"")."""
|
||||
req = urllib.request.Request(CONF["upstream"].rstrip("/") + PATH + sub_id, headers={
|
||||
req = urllib.request.Request(CONF["upstream"].rstrip("/") + (prefix or PATH) + sub_id, headers={
|
||||
"User-Agent": ua, "Host": host, "Accept": accept or "*/*"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as r:
|
||||
@@ -248,19 +250,20 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path.split("?", 1)[0]
|
||||
if not path.startswith(PATH):
|
||||
prefix = next((x for x in (PATH, *EXTRA) if path.startswith(x)), None)
|
||||
if prefix is None:
|
||||
return self.send_html_error(404)
|
||||
sub_id = path[len(PATH):]
|
||||
if not SUB_ID.match(sub_id) and not ASSET.match(sub_id):
|
||||
sub_id = path[len(prefix):]
|
||||
if not SUB_ID.match(sub_id) and not (prefix == PATH and ASSET.match(sub_id)):
|
||||
return self.send_html_error(404)
|
||||
ua = self.headers.get("User-Agent", "")
|
||||
host = CONF.get("link_host") or self.headers.get("Host", CONF.get("host", ""))
|
||||
accept = self.headers.get("Accept", "")
|
||||
code, headers, body = upstream(sub_id, ua, host, accept)
|
||||
code, headers, body = upstream(sub_id, ua, host, accept, prefix)
|
||||
if code is None:
|
||||
return self.send_html_error(502)
|
||||
|
||||
clash = bool(CLASH_UA.search(ua)) and "yaml" in headers.get("content-type", "")
|
||||
clash = (bool(CLASH_UA.search(ua)) or prefix != PATH) and "yaml" in headers.get("content-type", "")
|
||||
awg = clash and not NO_AWG_UA.search(ua)
|
||||
# В журнал – только приложение и что ему отдали, без IP.
|
||||
log(f"{ua[:80]!r} → {'clash+awg' if awg else 'clash' if clash else headers.get('content-type', '?').split(';')[0]}")
|
||||
@@ -269,7 +272,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
body = strip_awg(body)
|
||||
elif code == 200 and awg and not sub_id.endswith(("-awg", "-tg")):
|
||||
# Установки до kit 1.1 держали AmneziaWG в подписке «<id>-awg» – подмешиваем её.
|
||||
acode, _, abody = upstream(sub_id + "-awg", ua, host, accept)
|
||||
acode, _, abody = upstream(sub_id + "-awg", ua, host, accept, prefix)
|
||||
if acode == 200 and abody:
|
||||
body = merge_awg(body, abody)
|
||||
elif code == 200 and "text/plain" in headers.get("content-type", ""):
|
||||
|
||||
+61
-8
@@ -556,6 +556,7 @@ needs_migration() {
|
||||
[[ -f /etc/systemd/system/kit-sub.service ]] && ! grep -q '^DynamicUser=yes' /etc/systemd/system/kit-sub.service && return 0
|
||||
[[ ! -f $KIT_MANUAL ]] && ! auto_enabled && return 0
|
||||
limit_timer_enabled || return 0
|
||||
[[ -f /etc/kit-sub/config.json ]] && ! jq -e 'has("extra_paths")' /etc/kit-sub/config.json >/dev/null 2>&1 && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -682,6 +683,8 @@ cmd_update() {
|
||||
|
||||
# Через rename: bash дочитывает текущий kit по ходу работы, его файл трогать нельзя.
|
||||
install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit
|
||||
# Новые пути подписки (Clash и JSON) подключает уже новая версия kit.
|
||||
/usr/local/bin/kit __subextra >/dev/null 2>&1 || warn "Дополнительные пути подписки не подключились: kit update --force"
|
||||
echo
|
||||
echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."
|
||||
[[ $unattended == yes ]] && return
|
||||
@@ -1743,6 +1746,28 @@ def servers(t):
|
||||
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)
|
||||
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 op == "addloc":
|
||||
# addloc конфиг путь-подписки доп.пути…: в блок сайта по IP добавляются места для доп. путей (копия места подписки)
|
||||
if web is None:
|
||||
sys.exit(2)
|
||||
wt = text[web[0]:web[1]]
|
||||
base = location(wt, sys.argv[3])
|
||||
if not base:
|
||||
sys.exit(3)
|
||||
new_wt = wt
|
||||
for e in sys.argv[4:]:
|
||||
if location(new_wt, e):
|
||||
continue
|
||||
copy = base.replace(sys.argv[3], e, 1)
|
||||
new_wt = new_wt.replace(base, base + "\n" + copy, 1)
|
||||
sys.stdout.write(text[:web[0]] + new_wt + text[web[1]:])
|
||||
sys.exit(0)
|
||||
if steal is None:
|
||||
sys.exit(2)
|
||||
sb = text[steal[0]:steal[1]]
|
||||
@@ -1754,15 +1779,10 @@ 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]
|
||||
extras = sys.argv[6:]
|
||||
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)
|
||||
locs = "\n".join(x for x in [location(wb, sub_path), location(wb, panel_path)] + [location(wb, e) for e in extras] if x)
|
||||
if not locs:
|
||||
sys.exit(3)
|
||||
new = f"""server {{
|
||||
@@ -1817,6 +1837,36 @@ open(p, "w").write("\n".join(lines) + "\n")
|
||||
PY
|
||||
}
|
||||
|
||||
# Страница подписки в браузере показывает ещё два адреса (Clash и JSON) с отдельными секретными путями. Пропускаем их через
|
||||
# kit-sub (те же «Авто» и DNS) и через nginx. Повторный запуск безопасен.
|
||||
sub_extra_sync() {
|
||||
local cfg=/etc/kit-sub/config.json all jp cp extras tmp bak panel_path
|
||||
[[ -f $cfg ]] || return 0
|
||||
all=$(api POST setting/all '{}')
|
||||
jp=$(jq -r '.subJsonPath // ""' <<<"$all"); cp=$(jq -r '.subClashPath // ""' <<<"$all")
|
||||
extras=$(jq -nc --arg a "$jp" --arg b "$cp" --arg s "${SUB_PATH:-}" '[$a, $b] | map(select(. != "" and . != "/")) | map("/" + (ltrimstr("/") | rtrimstr("/")) + "/")
|
||||
| map(select(test("^/[A-Za-z0-9_-]+/$") and . != $s)) | unique')
|
||||
tmp=$(mktemp)
|
||||
jq --argjson e "$extras" '.extra_paths = $e' "$cfg" >"$tmp" && install -m 600 "$tmp" "$cfg"
|
||||
rm -f "$tmp"
|
||||
if [[ ${SINGLE:-no} == yes && -f $PANEL_CONF ]]; then
|
||||
local -a ex=()
|
||||
mapfile -t ex < <(jq -r '.[]' <<<"$extras")
|
||||
tmp=$(mktemp); bak=$(mktemp); cp -p "$PANEL_CONF" "$bak"
|
||||
if panel_conf_py addloc "$PANEL_CONF" "$SUB_PATH" "${ex[@]}" >"$tmp" && [[ -s $tmp ]]; then
|
||||
cat "$tmp" >"$PANEL_CONF"
|
||||
if [[ ${PANEL_ON:-ip} == domain ]]; then
|
||||
panel_path=/${XUI_WEB_BASE_PATH#/}; panel_path=${panel_path%/}/
|
||||
panel_conf_py render "$PANEL_CONF" domain "$SUB_PATH" "$panel_path" "${ex[@]}" >"$tmp" && [[ -s $tmp ]] && cat "$tmp" >"$PANEL_CONF"
|
||||
fi
|
||||
if nginx -t >/dev/null 2>&1; then systemctl reload nginx
|
||||
else cat "$bak" >"$PANEL_CONF"; warn "nginx не принял новые пути подписки – оставил прежние настройки."; fi
|
||||
fi
|
||||
rm -f "$tmp" "$bak"
|
||||
fi
|
||||
systemctl restart kit-sub
|
||||
}
|
||||
|
||||
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 при установке)."
|
||||
@@ -1836,7 +1886,9 @@ net_panel() { # [domain|ip]
|
||||
[[ -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. Ничего не изменилось."; }
|
||||
local -a ex=()
|
||||
mapfile -t ex < <(jq -r '.extra_paths[]?' /etc/kit-sub/config.json 2>/dev/null || true)
|
||||
panel_conf_py render "$PANEL_CONF" "$want" "$SUB_PATH" "$panel_path" "${ex[@]}" >"$tmp" || { rm -f "$tmp" "$bak"; die "Не удалось собрать настройки nginx. Ничего не изменилось."; }
|
||||
local -a rids=()
|
||||
mapfile -t rids < <(sni_targets | awk -F'\t' '$2 == "self" {print $1}')
|
||||
((${#rids[@]})) || { rm -f "$tmp" "$bak"; die "Не нашёл REALITY со своим доменом."; }
|
||||
@@ -2267,6 +2319,7 @@ case "$cmd_key" in
|
||||
inbound_patch "$_id" '.streamSettings |= ((if type == "string" then fromjson else . end) | .realitySettings.xver = $x)' --argjson x "$2" || true
|
||||
done < <(sni_targets | awk -F'\t' '$2 == "self" {print $1}') ;;
|
||||
"__links "*) links_block "${2:-}" "${3:-}" ;;
|
||||
"__subextra "*) sub_extra_sync ;;
|
||||
"__applinks "*) app_links "${2:-}" ;;
|
||||
"update "*) shift; cmd_update "$@" ;;
|
||||
"backup "*) cmd_backup ;;
|
||||
|
||||
Reference in New Issue
Block a user