Files
3X-UI_autoVPS/tools/fork_apply.py
T

640 lines
43 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""3X-UI_KIT (форк hacker07): накатывает наши «швы» на чистый код автора.
Это источник правды для форка: единственное место, где описано, ЧТО именно мы правим в
файлах автора. Всё, что мы дописываем по существу, живёт в scripts/kit-local.sh — файле,
который автор не трогает, поэтому обновления автора его не ломают.
Правила:
* каждый якорь обязан найтись РОВНО один раз, иначе — стоп (автор изменил код, правку
надо пересмотреть руками);
* при любой ошибке ничего не пишется наполовину: сначала прогоняй --check;
* открытый ключ подписи берётся из tools/fork-signer.pub (закрытая часть — только на
рабочей станции владельца, в репозиторий не попадает).
Использование:
python3 tools/fork_apply.py --check [корень-репозитория]
python3 tools/fork_apply.py [корень-репозитория]
"""
import os
import pathlib
import re
import sys
GITEA = os.environ.get("FORK_GITEA_BASE", "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS")
SIGNER_FILE = os.environ.get("FORK_SIGNER_PUB", "tools/fork-signer.pub")
# README.md форка — НАШ файл целиком: авторское описание из него убрано, чтобы никто не
# ставил сервер авторской командой и не оказался на авторском канале обновлений. Шва здесь нет:
# при обновлении из авторского проекта README конфликтует и решается в нашу пользу (см. FORK.md).
HOOK = """
# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---
# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.
# Все команды форка — под «kit local …» (см. kit local help).
KIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"
if [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi
"""
def die(msg):
print("! " + msg, file=sys.stderr)
sys.exit(1)
def ok(msg):
print("==> " + msg)
def count(text, anchor, regex):
if regex:
return len(re.findall(anchor, text, re.MULTILINE))
return text.count(anchor)
def main():
args = [a for a in sys.argv[1:]]
check = False
if "--check" in args:
check = True
args.remove("--check")
root = pathlib.Path(args[0] if args else ".").resolve()
if not (root / "VERSION").exists():
die("%s не похож на репозиторий 3X-UI_autoVPS (нет VERSION)" % root)
os.chdir(root)
version = (root / "VERSION").read_text(encoding="utf-8").strip()
if not re.fullmatch(r"[0-9]+(\.[0-9]+)+", version):
die("VERSION=%r: нужен числовой номер вида 1.2.1" % version)
signer_path = pathlib.Path(SIGNER_FILE)
if not signer_path.exists():
die(
"нет %s — сначала создай ключ подписи:\n"
" ssh-keygen -t ed25519 -C \"3X-UI KIT fork releases\" -f ~/.ssh/3x-ui-kit-fork/release_key\n"
" mkdir -p tools && ssh-keygen -y -f ~/.ssh/3x-ui-kit-fork/release_key > tools/fork-signer.pub"
% SIGNER_FILE
)
pub = ""
for line in signer_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line.startswith(("ssh-ed25519 ", "ssh-rsa ")):
pub = line
break
if not pub:
die("%s: не нашёл строку открытого ключа (ssh-ed25519 …)" % signer_path)
version_re = r'^KIT_VERSION="[0-9.]+"$'
edits = [
(
'scripts/kit.sh',
'# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.\n',
False,
'# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }\n',
False,
'kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }\n\n# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---\n# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.\n# Все команды форка — под «kit local …» (см. kit local help).\nKIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"\nif [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi\n\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)\n',
False,
'Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут\n',
False,
'Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' # SHA256:VDuGgJ8dOeCNXB4nBZf3+kRlWthw3+vh8rfMxzMSRIM (itsnotkubrick, 2026-09-30)\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrTGCDhhnm8XO1ekpPJuSWRVCJiFiupEspfQxcbEBmz 3X-UI KIT releases"\n',
False,
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
' echo "3X-UI KIT $KIT_VERSION"\n',
False,
' echo "3X-UI_autoVPS $KIT_VERSION"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'\n',
False,
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py; do\n',
False,
' grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
False,
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: автообновление kit и kit-sub (только подписанные релизы)\n',
False,
'Description=3X-UI_autoVPS: автообновление kit и kit-sub (только подписанные релизы)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: проверка обновлений раз в сутки\n',
False,
'Description=3X-UI_autoVPS: проверка обновлений раз в сутки\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI KIT $KIT_VERSION → $latest"; fi\n',
False,
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI_autoVPS $KIT_VERSION → $latest"; fi\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n',
False,
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n install -d -m 755 /usr/local/lib/kit\n install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n',
'авто-шов: insert, строк 1',
),
(
'scripts/kit.sh',
' echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
False,
' echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"\n',
False,
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI_autoVPS"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' grep -q \'"Name":"3X-UI KIT"\' <<<"$v"\n',
False,
' grep -q \'"Name":"3X-UI_autoVPS"\' <<<"$v"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' box "3X-UI KIT $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
False,
' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"\n',
False,
' "7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \\\n "11. Панель 3X-UI (меню x-ui)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' ask_num "Выбор [0-9]: "; c=$REPLY\n',
False,
' ask_num "Выбор [1-11]: "; c=$REPLY\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 0 | "") return 0 ;;\n',
False,
' 9) kit_local_uninstall ;;\n 10 | 0 | "") return 0 ;;\n 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
'${B}3X-UI KIT $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
False,
'${B}3X-UI_autoVPS $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"\n',
False,
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)\n',
False,
'# Наш шов: добавлены известные сайты (Microsoft, AMD, Intel, NVIDIA) — их можно выбрать вручную.\nSNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com www.microsoft.com www.amd.com www.intel.com www.nvidia.com)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
" seq 1 254 | xargs -P 8 -I{} bash -c '\n",
False,
" # Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.\n seq 1 254 | xargs -P 32 -I{} bash -c '\n",
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true\n',
False,
' echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' local me a b c base n names found=0\n',
False,
' local me a b c base n names out\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n',
False,
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n # Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная\n # причина «долго сканирует». Функции передаём в подпроцессы через export -f.\n export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip\n export HOST\n',
'авто-шов: insert, строк 2',
),
(
'scripts/3x-ui.sh',
' names=$(nearby_scan "$base")\n for n in $names; do\n [[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n if same_net "$n" && sni_alive "$n" && sni_trusted "$n" && sni_quality "$n"; then echo "$n"; found=$((found + 1)); ((found >= 8)) && return 0; fi\n done\n ((found > 0)) && return 0\n',
False,
' names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)\n [[ -n $names ]] || continue\n out=$(while IFS= read -r n; do\n [[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n printf \'%s\\n\' "$n"\n done <<<"$names" | xargs -P 8 -I{} bash -c \'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\\n" "$1"\' _ {} 2>/dev/null | head -8)\n [[ -n $out ]] && { printf \'%s\\n\' "$out"; return 0; }\n',
'авто-шов: replace, строк 6',
),
(
'scripts/3x-ui.sh',
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
False,
" _ _ _ ___ _____\n | | | | __ _ ___| | _____ _ __ / _ \\___ |\n | |_| |/ _` |/ __| |/ / _ \\ '__| | | | | / /\n | _ | (_| | (__| < __/ | | |_| |/ /\n |_| |_|\\__,_|\\___|_|\\_\\___|_| \\___//_/\n",
'авто-шов: replace, строк 5',
),
(
'scripts/3x-ui.sh',
' echo "${B}3X-UI KIT $KIT_VERSION${N} · it\'s not Kubrick. it\'s just a VPN."\n',
False,
' echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n',
False,
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# role — подпись для человека: REALITY или XHTTP/MTProto.\nkl_sni_menu() {\n local role=$1; shift\n local -a found=("$@")\n local -a known=("${SNI_CANDIDATES[@]}")\n local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try\n n=${#found[@]}\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((n)); then\n printf \'Найдено в подсети:\\n\\n\' >&2\n for i in "${!found[@]}"; do printf \' %d - %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))\n printf \' %d - автовыбор из списка\\n\' "$auto_idx" >&2\n printf \' %d - выбрать общедоступный (например %s, %s)\\n\' "$known_idx" "${known[0]}" "${known[1]}" >&2\n printf \' %d - ввести свой домен\\n\\n\' "$own_idx" >&2\n else\n auto_idx=0; known_idx=1; own_idx=2\n printf \'В подсети доменов не найдено, выберите:\\n\\n\' >&2\n printf \' 1 - общедоступный (например %s, %s)\\n\' "${known[0]}" "${known[1]}" >&2\n printf \' 2 - ввести свой домен\\n\\n\' >&2\n fi\n printf \'Ваш выбор (Enter — автовыбор): \' >&2\n for try in 1 2 3; do\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ -z $ans ]] && return 0\n [[ $ans =~ ^[0-9]+$ ]] || { printf \' введите номер из списка\\n\' >&2; continue; }\n i=$((10#$ans))\n if ((n)) && ((i >= 1 && i <= n)); then printf \'sni:%s\' "${found[i - 1]}"; return 0; fi\n ((i == auto_idx)) && return 0\n if ((i == known_idx)); then\n printf \'\\nОбщедоступные сайты:\\n\' >&2\n for k in "${!known[@]}"; do printf \' %d - %s\\n\' "$((k + 1))" "${known[k]}" >&2; done\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'sni:%s\' "${known[10#$ans - 1]}"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'own:%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n',
'авто-шов: insert, строк 3',
),
(
'scripts/3x-ui.sh',
' elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
False,
' # Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе\n # человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.\n elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"\n',
False,
' echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."\n echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n',
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',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n else\n',
False,
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n elif [[ -z $SNI ]]; then\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
False,
' if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then\n : # сайт уже выбран в нашем меню — вопрос автора не нужен\n elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then\n',
False,
' # Наш шов: если 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',
),
(
'scripts/3x-ui.sh',
' local extra\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n',
False,
' local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны\n # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,\n # если эти протоколы не выбраны (никаких лишних обращений к панели).\n if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n fi\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' echo "3X-UI KIT (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
False,
' echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\n',
False,
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"\n',
'авто-шов: insert, строк 1',
),
(
'scripts/3x-ui.sh',
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n',
False,
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n install -d -m 755 /usr/local/lib/kit\n if [[ -n $src && -f $d/kit-local.sh ]]; then\n install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n else\n curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n fi\n',
'авто-шов: insert, строк 1',
),
(
'scripts/3x-ui.sh',
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"\n',
False,
'KIT_INSTALL_CMD="bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui.sh)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo "# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске."\n',
False,
' echo "# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске.\n',
False,
'# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске.\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI KIT" --arg uri "$uri" \'\n',
False,
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI_autoVPS" --arg uri "$uri" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI KIT" \'\n',
False,
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI_autoVPS" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
False,
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' && die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
False,
' && die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
False,
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' *) die "Это не резервная копия 3X-UI KIT или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
False,
' *) die "Это не резервная копия 3X-UI_autoVPS или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."\n',
False,
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена. Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (порт панели). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (порт панели). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."\n',
False,
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (адрес сервера). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/hysteria2.sh',
'# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
False,
'# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
'авто-шов: replace, строк 1',
),
(
'scripts/hysteria2.sh',
'Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).\n',
False,
'Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
False,
' data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
False,
' data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' main()\n',
False,
' main()\n\n',
'авто-шов: insert, строк 1',
),
(
'scripts/kit.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/3x-ui.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/hysteria2.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/kit.sh',
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
False,
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"',
'канал обновлений: наш Gitea вместо GitHub',
),
]
bad = 0
for path, anchor, is_re, new, why in edits:
p = pathlib.Path(path)
if not p.exists():
print("✗ %s: файла нет" % path)
bad += 1
continue
text = p.read_text(encoding="utf-8", newline="")
if check:
# --check смотрит на готовое дерево dev: наш шов обязан быть на месте и ровно один раз
n = text.count(new)
what, shown = "наш шов", new
else:
# применение — только на чистое дерево автора: якорь обязан найтись ровно один раз
n = count(text, anchor, is_re)
what, shown = "якорь автора", anchor
short = shown if len(shown) <= 54 else shown[:51] + "…"
short = short.replace("\n", "⏎")
if n != 1:
print("✗ %-20s %-56s %s найден %d раз (%s)" % (path, short, what, n, why))
bad += 1
continue
if check:
print("✓ %-20s %-56s (%s)" % (path, short, why))
continue
if is_re:
text = re.sub(anchor, new, text, count=1, flags=re.MULTILINE)
else:
text = text.replace(anchor, new, 1)
p.write_text(text, encoding="utf-8", newline="")
print("✓ %-20s %-56s (%s)" % (path, short, why))
# Массовая замена бренда в файлах автора: строки, которые видит человек (справка, версия,
# описания служб, баннер). Строку проверки шапки SHA256SUMS не трогаем — её правит отдельный шов,
# а сама шапка ещё один релиз пишется в старом виде (иначе уже установленные серверы откажутся
# принимать обновление: они сверяют точный вид строки).
globals_ = [
("scripts/kit.sh", "3X-UI KIT", "3X-UI_autoVPS"),
("scripts/3x-ui.sh", "3X-UI KIT", "3X-UI_autoVPS"),
("scripts/hysteria2.sh", "3X-UI KIT", "3X-UI_autoVPS"),
("scripts/kit-sub.py", "3X-UI KIT", "3X-UI_autoVPS"),
]
for path, old, new in globals_:
p = pathlib.Path(path)
if not p.exists():
print("✗ %s: файла нет" % path)
bad += 1
continue
text = p.read_text(encoding="utf-8", newline="")
# строки, где старое имя — часть служебной сверки, не переименовываем
keep = lambda line: "grep -qx" in line
if check:
if new not in text:
print("✗ %-20s нет нашего имени «%s»" % (path, new))
bad += 1
continue
left = [l for l in text.splitlines() if old in l and not keep(l)]
if left:
print("✗ %-20s осталось старое имя в %d строк(ах)" % (path, len(left)))
bad += 1
continue
print("✓ %-20s бренд: %s" % (path, new))
continue
out = []
changed = 0
for line in text.splitlines(keepends=True):
if old in line and not keep(line):
out.append(line.replace(old, new))
changed += line.count(old)
else:
out.append(line)
if not changed:
if new in text:
print("– %-20s уже переименовано" % path)
continue
print("✗ %-20s не нашёл «%s»" % (path, old))
bad += 1
continue
p.write_text("".join(out), encoding="utf-8", newline="")
print("✓ %-20s переименовано вхождений: %d" % (path, changed))
if bad:
if check:
die("якорей не найдено: %d — автор изменил код, пересмотри правки в tools/fork_apply.py" % bad)
die("якорей не найдено: %d — правки не применены" % bad)
if check:
ok("все якоря на месте (версия сборки %s)" % version)
else:
ok("швы накатаны, версия сборки %s" % version)
if __name__ == "__main__":
main()