Compare commits
14
Commits
v1.2.1.1
...
333b8edc9a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
333b8edc9a | ||
|
|
13dfb44f3f | ||
|
|
3a8518651c | ||
|
|
915dd35f42 | ||
|
|
edabfd9a83 | ||
|
|
741a70c9bf | ||
|
|
0d2af45bdb | ||
|
|
a02650311e | ||
|
|
2b57e6f6d7 | ||
|
|
df8a99bb78 | ||
|
|
47328b33b3 | ||
|
|
776b7a8c5e | ||
|
|
2d63136853 | ||
|
|
1b85a4bb23 |
@@ -1,3 +1,84 @@
|
||||
## v1.2.1.8: подсказка меню исправлена, установка стала тише
|
||||
|
||||
* **«Выбор [0-10]» при 11 пунктах** — подсказка главного меню отстала, когда мы добавили пункт «Доступ к
|
||||
панели, ссылки и заглушка». Теперь «Выбор [0-11]». Чтобы это не повторилось, добавлен тест
|
||||
`tools/check-menus.py`: он сверяет подсказку каждого меню с числом пунктов и запускается в релизном
|
||||
гейте — если мы снова добавим пункт и забудем подсказку, выпуск остановится.
|
||||
* **`kit fix` больше не выполняется при установке** (в выводе это выглядело как «Чинить нечего: всё в
|
||||
порядке»). Нужен — запускается из меню или ключом `--time`.
|
||||
* **Вывод про автообновления безопасности сокращён до одной строки** («автообновления безопасности
|
||||
включены…»): три строки про ядро и перезагрузку убраны из установки.
|
||||
|
||||
## v1.2.1.7: исправлена установка (моя регрессия в 1.2.1.6)
|
||||
|
||||
* В 1.2.1.6 я переписал загрузку файлов релиза и оставил проверку SHA256 установщика **раньше**
|
||||
скачивания — установка обрывалась на первой же строке: `sha256sum: .../scripts/3x-ui.sh: No such file
|
||||
or directory`. Теперь сначала скачиваются все файлы релиза (каждый сверяется с подписанным списком),
|
||||
и только потом проверяется установщик.
|
||||
* Проверено не на глаз: воспроизвёл ошибку у себя тем же путём (`--verify-only`) — красное, затем после
|
||||
правки зелёное. Этот прогон добавлен в процедуру выпуска: перед подписью я запускаю установщик в режиме
|
||||
`--verify-only` и убеждаюсь, что путь «скачать и проверить релиз» проходит целиком.
|
||||
|
||||
## v1.2.1.6: доступы к панели в конце установки и рабочая заглушка
|
||||
|
||||
* **Исправлено: доступы к панели не выводились.** Автор пишет строки в `/root/3x-ui.txt` без ведущих
|
||||
пробелов, а наш фильтр требовал два пробела — отсюда «данных панели нет». Теперь адрес панели, логин и
|
||||
пароль печатаются в конце установки (и в пункте меню «9. Доступ к панели, ссылки и заглушка»).
|
||||
Рядом указывается, что подписка и ссылки по протоколам лежат в том же файле.
|
||||
* **Исправлено: наша заглушка не готовилась до установщика.** Обёртка скачивала из релиза только три файла,
|
||||
а `scripts/kit-local.sh` (там функция страницы) не качала — падало с «kl_site_html: command not found».
|
||||
Теперь качаются все файлы релиза, и каждый сверяется с подписанным списком (проверка целостности стала
|
||||
полной, а не только для установщика).
|
||||
|
||||
## v1.2.1.5: доступ к панели, ссылки и заглушка — одним пунктом меню
|
||||
|
||||
* **В меню kit пункт «9. Доступ к панели, ссылки и заглушка»** — открывает одним экраном: адрес панели,
|
||||
логин и пароль (из /root/3x-ui.txt), состояние сертификата панели (доверенный на IP или
|
||||
самоподписанный, с датой повтора при лимите Let's Encrypt), ссылки на подключения с QR-кодом и что
|
||||
сейчас стоит заглушкой. Раньше доступ к панели в меню не показывался вообще, а про заглушку было
|
||||
непонятно, как её посмотреть.
|
||||
* Меню перенумеровано: «10. Панель x-ui», «11. Удалить KIT с сервера» (логика удаления — авторская).
|
||||
* То же самое доступно из консоли: `kit local access [имя]`.
|
||||
|
||||
## v1.2.1.4: короткий итог установки, автовыбор в общедоступных сайтах, команда про порты
|
||||
|
||||
* **Итог установки вместо простыни текста** — компактный блок: сборка, сервер, протоколы, состояние
|
||||
сертификата панели, одна строка проверки. Дальше только то, что нужно человеку: доступ к панели (адрес,
|
||||
логин, пароль — по просьбе из ранних правок), указатель на меню kit для ссылок и QR, открытые порты и
|
||||
команда для закрытия лишних. Длинные отчёты (состояние защиты, полный вывод kit check) убраны из
|
||||
установки — они остались доступны командами, в том числе `kit local hardening`.
|
||||
* **Ссылки на подключения больше не попадают в вывод установки даже косвенно**: раньше печатался весь
|
||||
файл /root/3x-ui.txt, а в нём лежат и ссылки vless://. Теперь из него берутся только строки панели —
|
||||
адрес, логин, пароль.
|
||||
* **В меню общедоступных сайтов последним пунктом добавлен автовыбор** (раньше можно было только выбрать
|
||||
номером или вернуться).
|
||||
* **Новая команда `kit local ports`** — что открыто в ufw, что реально нужно (порты подключений и SSH),
|
||||
что лишнее; `kit local ports --close-extra` закрывает лишнее.
|
||||
* Ошибка подготовки нашей заглушки теперь печатает причину, а не просто «не смог».
|
||||
|
||||
## v1.2.1.3: ссылки и QR — в меню kit; сертификат панели видно в итоге
|
||||
|
||||
* **Ссылки для подключения и QR-код больше не печатаются в выводе установки.** Их смотрят позже, когда
|
||||
настраивают телефон, и они всегда доступны заново: `kit → Пользователи → Показать ссылки`. В итоговом
|
||||
блоке вместо них — короткий путь к этому пункту и команды `kit local links` (подписка и QR) и
|
||||
`kit user link <имя> --all` (все протоколы). Доступы к панели (адрес, логин, пароль) по-прежнему
|
||||
в конце установки: их вы просили показывать один раз и сразу.
|
||||
* **Добавлена команда `kit local links`** — подписка, QR-код и ссылки по протоколам одной командой;
|
||||
пользователя можно указать, иначе берётся первый.
|
||||
* **В итоговом блоке видно состояние сертификата панели.** Панель и подписка висят на IP, поэтому
|
||||
печатается: «Let's Encrypt на IP — доверенный, браузер не ругается» либо «самоподписанный» с датой,
|
||||
после которой Let's Encrypt выпустит сертификат на IP (тот же лимит, что и для домена).
|
||||
|
||||
## v1.2.1.2: сертификат на IP — с датой повтора; «без домена» продолжает установку
|
||||
|
||||
* **Отказ Let's Encrypt на IP теперь объясняется честно.** Как и для домена, при лимите в выводе дата,
|
||||
после которой выпуск возможен, и прямо сказано, что порт 80 и файрвол тут не при чём. Раньше печатались
|
||||
«частые причины» и человек шёл проверять хостера.
|
||||
* **Пункт «без своего домена» больше не останавливает установку.** При отказе с сертификатом домена выбор
|
||||
«3» продолжает работу сразу: маскировка берётся из соседей по подсети (или известный сайт, если соседей
|
||||
нет), панель и подписка переключаются на IP, дальше установка идёт своим ходом. Перезапускать скрипт
|
||||
и заново отвечать на вопросы не нужно.
|
||||
|
||||
## v1.2.1.1: сборка на новой базе автора (3X-UI KIT 1.2.1)
|
||||
|
||||
**Правило нумерации:** берём версию автора и через точку добавляем свою, начиная с 1. У автора 1.2.1 —
|
||||
|
||||
@@ -191,6 +191,10 @@ bash tools/fork-sync.sh # подтянуть автора, перен
|
||||
|
||||
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом
|
||||
|
||||
**Перед подписью — обязательный прогон.** `bash scripts/3x-ui-local.sh --verify-only` проверяет ровно тот
|
||||
путь установки, которым пойдут серверы: скачивание всех файлов релиза и сверка каждого с подписанным
|
||||
списком. На Windows запускать с `TMPDIR=C:/…` (нативные утилиты не понимают MSYS-пути).
|
||||
|
||||
**Нумерация версий.** Основа — версия автора, наша надстройка добавляется через точку с единицы:
|
||||
у автора 1.2.1 → наша сборка 1.2.1.1; вышла его 1.2.2 → наша 1.2.2.1. Так на сервере всегда видно,
|
||||
на чьей базе собрана сборка.
|
||||
|
||||
+8
-8
@@ -1,9 +1,9 @@
|
||||
# 3X-UI KIT 1.2.1.1
|
||||
# 3X-UI_autoVPS 1.2.1.1
|
||||
f2c191e06de726a68f69453b33a4c042b09f50e4feb0285459a8ad8b696a9b18 scripts/3x-ui.sh
|
||||
6f8187aef75658bbd77adf1ad560aaebd103ad6f13654851f7e6ddbcce95d05b scripts/hysteria2.sh
|
||||
1e5338461ef9cec1445335812cf73659329f16a0ac92a5b622e9788b74fdc773 scripts/kit.sh
|
||||
# 3X-UI KIT 1.2.1.7
|
||||
# 3X-UI_autoVPS 1.2.1.7
|
||||
3a5f36b8d551d23837f48cf1dea4822be94cd91eb8cc7b06c20e0789289690dc scripts/3x-ui.sh
|
||||
f91d95eea94f49ce2fc1b56114230edd85eb3be0fb0c5bf5a3431e36bf5f1cf5 scripts/hysteria2.sh
|
||||
96e4ca314846cf118f195bb3beb01497fd2e09945e168876b6a34f1288115937 scripts/kit.sh
|
||||
f214671464e1c2e174e5af9df42c95567a9b4a9141f31a39f7df2e971e89d240 scripts/kit-sub.py
|
||||
79e53e4c4ffcfdbcf1209196901ac48c4c76c32ae677ef5800d3658f51ca1a55 scripts/kit-local.sh
|
||||
7df814ffab37d8e1c48b895677b63311a0e993a275c2095fbb97c5d359ba1bed scripts/3x-ui-local.sh
|
||||
91c47310dd65fd1f86ad29aad31e72c5d5661532039230a52d5a90f630ef21ff VERSION
|
||||
75cc7bc4e048c43989f66ca3b9e9addff8d7a434e651daba6db894e18109b45b scripts/kit-local.sh
|
||||
1f72217fd4d286b65aa0883e0aa44be0758bfb081b496eb0b32f35503c699420 scripts/3x-ui-local.sh
|
||||
0aa3ba2dc8cf93b813171a7cb596b12a25bc5babd13bb6b3308d8f8d637369a5 VERSION
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABAXrsYVkh26UyCTiwNfxwKgBQzrKOSAubhberognGoaeNfTp
|
||||
YRJzjFe7Y03DQBilCb39W2RKLLOlZyz2ncRCWGDQ==
|
||||
AAALc3NoLWVkMjU1MTkAAABAwcJjUxl9upludWNJ0ghcbrRDqzIBpNCLmpCYuLBvXswYU2
|
||||
Ou29XvPQN9n9oAbXz36N5/iJSt09dhVHcAWeM5BA==
|
||||
-----END SSH SIGNATURE-----
|
||||
|
||||
+80
-77
@@ -32,7 +32,7 @@
|
||||
# --no-harden не трогать ping и автообновления
|
||||
# --ssh-port N добавить порт SSH N (прежний порт продолжает работать)
|
||||
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
|
||||
# --no-time не запускать kit fix
|
||||
# --time запустить kit fix (по умолчанию не запускается)
|
||||
# --no-check не запускать в конце kit check
|
||||
# --clean убрать прошлую установку и поставить заново (SSH не трогает)
|
||||
# --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети)
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
KIT_VERSION="1.2.1.1" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
KIT_VERSION="1.2.1.8" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||
NS="3x-ui-kit-release"
|
||||
SIG_ID="releases@3x-ui-kit"
|
||||
@@ -83,7 +83,7 @@ usage() {
|
||||
--no-sub не показывать в конце подписку, доступ к панели и QR-код
|
||||
--clean снести прошлую установку и поставить заново (если прошлый запуск оборвался)
|
||||
--auto-sni не предлагать выбор сайта маскировки (автоподбор автора)
|
||||
--no-time не запускать kit fix (время и безопасные починки)
|
||||
--time запустить kit fix (время и безопасные починки; по умолчанию не запускается)
|
||||
--no-check не запускать в конце kit check
|
||||
--verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
--dry-run показать план и ничего не делать
|
||||
@@ -93,7 +93,7 @@ EOF
|
||||
|
||||
# ------------------------------------------------------------------ разбор флагов
|
||||
|
||||
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
|
||||
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=no; do_check=yes; dry=no; verify_only=no; assume_yes=no
|
||||
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
|
||||
show_sub=yes # в самом конце показать подписку, QR-код и ссылки
|
||||
clean=no # --clean: убрать прошлую установку перед новой
|
||||
@@ -112,6 +112,7 @@ while (($#)); do
|
||||
--clean) clean=yes ;;
|
||||
--auto-sni) pick_sni=no ;;
|
||||
--no-time) fix_time=no ;;
|
||||
--time) fix_time=yes ;;
|
||||
--no-check) do_check=no ;;
|
||||
--verify-only) verify_only=yes ;;
|
||||
--dry-run) dry=yes ;;
|
||||
@@ -162,7 +163,6 @@ if [[ $dry == yes ]]; then
|
||||
random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;;
|
||||
*) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;;
|
||||
esac
|
||||
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
|
||||
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
|
||||
[[ $show_sub == yes ]] && pre "в самом конце: доступ к панели 3X-UI, подписка, QR-код и ссылки (--no-sub — не надо)"
|
||||
echo
|
||||
@@ -179,7 +179,7 @@ say "беру релиз v$KIT_VERSION и проверяю подпись"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
raw="$GITEA/raw/tag/v$KIT_VERSION"
|
||||
for f in SHA256SUMS SHA256SUMS.sig scripts/3x-ui.sh; do
|
||||
for f in SHA256SUMS SHA256SUMS.sig; do
|
||||
mkdir -p "$tmp/$(dirname "$f")"
|
||||
curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)"
|
||||
done
|
||||
@@ -188,9 +188,21 @@ ssh-keygen -Y verify -f "$tmp/allowed_signers" -I "$SIG_ID" -n "$NS" -s "$tmp/SH
|
||||
|| die "подпись релиза v$KIT_VERSION не сошлась с ключом форка — это не наш релиз, ничего не запускаю"
|
||||
sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp/SHA256SUMS")
|
||||
[[ $(printf '%s' "$sum" | wc -c) -eq 64 ]] || die "в SHA256SUMS нет установщика — релиз собран неверно"
|
||||
# Качаем ВСЕ файлы релиза и сверяем каждый с подписанным списком. Так у нас есть и scripts/3x-ui.sh
|
||||
# (установщик), и scripts/kit-local.sh (наша страница-заглушка), и всё остальное. Раньше брали только
|
||||
# установщик: заглушка не готовилась («kl_site_html: command not found»), а проверка целостности была неполной.
|
||||
while read -r sum2 name2; do
|
||||
case $sum2 in \#* | "") continue ;; esac
|
||||
case $name2 in SHA256SUMS | SHA256SUMS.sig) continue ;; esac
|
||||
mkdir -p "$tmp/$(dirname "$name2")"
|
||||
curl -fsSL --retry 3 -m 60 -o "$tmp/$name2" "$raw/$name2" || die "не скачался $name2 из $raw (Gitea доступна с сервера?)"
|
||||
got2=$(sha256sum "$tmp/$name2" | awk '{print $1}')
|
||||
[[ $got2 == "$sum2" ]] || die "SHA256 не совпал у $name2 — не запускаю"
|
||||
done <"$tmp/SHA256SUMS"
|
||||
got=$(sha256sum "$tmp/scripts/3x-ui.sh" | awk '{print $1}')
|
||||
[[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю"
|
||||
bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса"
|
||||
[[ -r $tmp/scripts/kit-local.sh ]] || die "в релизе нет scripts/kit-local.sh — сборка неполная"
|
||||
printf ' ✓ подпись сошлась, SHA256 установщика совпал\n'
|
||||
|
||||
if [[ $verify_only == yes ]]; then
|
||||
@@ -457,13 +469,13 @@ if [[ $site != off ]]; then
|
||||
esac
|
||||
if [[ -n $stub_n ]]; then
|
||||
install -d -m 755 /var/www/kit
|
||||
if ( source "$tmp/scripts/kit-local.sh" 2>/dev/null; kl_site_html "$stub_n" ) >/var/www/kit/index.html 2>/dev/null \
|
||||
if ( source "$tmp/scripts/kit-local.sh" 2>/dev/null; kl_site_html "$stub_n" ) >/var/www/kit/index.html 2>"$tmp/stub.err" \
|
||||
&& [[ -s /var/www/kit/index.html ]]; then
|
||||
stub_staged=yes
|
||||
rm -f "$tmp/var/www/kit/index.html"
|
||||
say "заглушка: наш сайт №$((stub_n + 1)) — ставится сразу, авторская страница не используется"
|
||||
else
|
||||
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site)"
|
||||
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site). Причина: $(head -c 200 "$tmp/stub.err" 2>/dev/null | tr '\n' ' ')"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -525,77 +537,68 @@ if [[ -n $ssh_port ]]; then
|
||||
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ проверка
|
||||
# ------------------------------------------------------------------ итог: коротко и по делу
|
||||
|
||||
if [[ $do_check == yes ]]; then
|
||||
say "проверяю, что получилось"
|
||||
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix"
|
||||
kit local hardening
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea).
|
||||
|
||||
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||
EOF
|
||||
|
||||
# ------------------------------------------------------------------ SSH: как закрыть прежние порты
|
||||
|
||||
if [[ -n ${ssh_port:-} ]]; then
|
||||
if [[ $do_check == yes || $show_sub == yes ]]; then
|
||||
# kit check печатает много строк: человеку нужны только замечания и одна строка «всё ли в порядке».
|
||||
chk=$(kit check 2>&1 || true)
|
||||
problems=$(printf '%s\n' "$chk" | grep -cE '^❌|^⚠|^!' || true)
|
||||
[[ $problems == 0 ]] || printf '%s\n' "$chk" | grep -E '^❌|^⚠|^!' | sed 's/^/ /'
|
||||
ports_open=""
|
||||
command -v ufw >/dev/null 2>&1 && ports_open=$(ufw status 2>/dev/null | awk '$2 == "ALLOW" { printf "%s ", $1 }')
|
||||
ip_show=$(hostname -I 2>/dev/null | awk '{ print $1 }')
|
||||
os_show=$( . /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}" )
|
||||
cert_show="без сертификата: панель открывается через SSH-туннель"
|
||||
if [[ -s /root/cert/ip/fullchain.pem ]]; then
|
||||
if openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
|
||||
cert_show="${G}Let's Encrypt на IP — доверенный, браузер не ругается${N}"
|
||||
else
|
||||
cert_show="${Y}самоподписанный — браузер предупредит${N}"
|
||||
fi
|
||||
fi
|
||||
protos_show=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
cat <<EOF
|
||||
|
||||
${Y}${B}SSH переведён на порт $ssh_port.${N} Прежние порты (${ssh_ports_before:-22}) пока работают — это
|
||||
сделано специально, чтобы вы не потеряли доступ. Порядок обязательный:
|
||||
1) во ВТОРОМ окне проверьте вход: ssh -p $ssh_port root@${HOST:-<IP сервера>}
|
||||
2) вход работает — закройте прежние порты: ${B}kit local ssh --close-old $ssh_port${N}
|
||||
3) поправьте порт у себя: ~/.ssh/config, терминалы, мониторинг
|
||||
${B}────────────── Установка завершена ──────────────${N}
|
||||
сборка: 3X-UI_autoVPS $KIT_VERSION (канал: наш Gitea)
|
||||
сервер: ${ip_show:-?}${os_show:+ · $os_show}
|
||||
протоколы: ${protos_show:-—}
|
||||
сертификат: $cert_show
|
||||
проверка: $([[ $problems == 0 ]] && echo 'замечаний нет' || echo "замечаний: $problems (подробности: kit check)")
|
||||
${B}───────────────────────────────────────────────────${N}
|
||||
|
||||
${B}Доступ к панели${N} (файл /root/3x-ui.txt, виден только root)
|
||||
EOF
|
||||
# Автор пишет эти строки без ведущих пробелов (RESULT=/root/3x-ui.txt), поэтому ищем их как есть.
|
||||
if grep -aqE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt 2>/dev/null; then
|
||||
grep -aE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt | sed 's/^[[:space:]]*/ /'
|
||||
printf ' файл целиком (там же подписка и ссылки по протоколам): /root/3x-ui.txt\n'
|
||||
else
|
||||
warn "данных панели в /root/3x-ui.txt нет — смотрите вывод установщика выше"
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
${B}Ссылки для подключения и QR-код${N} — в меню kit
|
||||
kit → Пользователи → Показать ссылки из консоли: kit local links
|
||||
EOF
|
||||
if [[ -n $ports_open ]]; then
|
||||
cat <<EOF
|
||||
|
||||
${B}Порты${N}
|
||||
открыто: $ports_open
|
||||
что нужно и что лишнее: kit local ports закрыть лишние: kit local ports --close-extra
|
||||
EOF
|
||||
fi
|
||||
if [[ -n ${ssh_port:-} ]]; then
|
||||
cat <<EOF
|
||||
|
||||
${Y}SSH переведён на порт $ssh_port.${N} Прежние (${ssh_ports_before:-22}) пока открыты намеренно:
|
||||
1) проверьте вход во втором окне: ssh -p $ssh_port root@${ip_show:-<IP>}
|
||||
2) закройте прежние порты: kit local ssh --close-old $ssh_port
|
||||
EOF
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
||||
|
||||
if [[ $show_sub == yes ]]; then
|
||||
say "панель 3X-UI: адрес, логин и пароль"
|
||||
if [[ -s /root/3x-ui.txt ]]; then
|
||||
printf ' файл: /root/3x-ui.txt (виден только root). Сохраните данные себе:\n'
|
||||
sed 's/^/ /' /root/3x-ui.txt
|
||||
else
|
||||
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
|
||||
fi
|
||||
if grep -qaE '^Панель: ?https?://[0-9]' /root/3x-ui.txt 2>/dev/null; then
|
||||
printf ' %sпанель открывается по IP: сертификат самоподписанный, браузер предупредит.\n' "$D"
|
||||
printf ' Завести свой домен и перейти на него: kit net panel domain%s\n' "$N"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ подписка, QR-код и доступы
|
||||
|
||||
# Показываем в самом конце, после всех проверок: это то, что человек уносит с собой.
|
||||
# Печатает авторская команда (подписка + QR-код через qrencode + ссылки по протоколам),
|
||||
# поэтому вывод не расходится с тем, что показывает `kit` потом.
|
||||
if [[ $show_sub == yes ]]; then
|
||||
sub_user=admin
|
||||
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
|
||||
[[ ${INSTALLER_ARGS[_i]} == --user ]] && sub_user=${INSTALLER_ARGS[_i + 1]:-admin}
|
||||
done
|
||||
say "подписка и доступы — это то, что стоит сохранить"
|
||||
printf ' подписка — одна ссылка на все устройства; QR-код ниже наводите камерой в приложении\n'
|
||||
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
||||
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
||||
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
||||
[[ -n ${ssh_port:-} ]] && printf ' SSH: новый порт %s, прежние (%s) ещё открыты — закройте их после проверки входа: kit local ssh --close-old %s\n' "$ssh_port" "${ssh_ports_before:-22}" "$ssh_port"
|
||||
# Ссылки на каждый протокол показываем только если протоколов больше одного: при одном это лишний шум.
|
||||
protos_now=$(tr '\n' ' ' < /etc/kit/kl-protos 2>/dev/null || true)
|
||||
if [[ $(wc -w <<<"$protos_now") -gt 1 ]]; then
|
||||
if ! kit user link "$sub_user" --all; then
|
||||
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
||||
fi
|
||||
elif ! kit user link "$sub_user"; then
|
||||
warn "не смог показать подписку для «$sub_user» — список пользователей: kit user list"
|
||||
fi
|
||||
else
|
||||
say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all"
|
||||
fi
|
||||
|
||||
+27
-2
@@ -39,7 +39,7 @@ wait_apt_idle() {
|
||||
XUI_VERSION="v3.9.0"
|
||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
||||
KIT_VERSION="1.2.1.1"
|
||||
KIT_VERSION="1.2.1.8"
|
||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||
@@ -451,11 +451,21 @@ issue_domain_cert() {
|
||||
ip_cert_self_signed_fallback() {
|
||||
local san="IP:$HOST"
|
||||
[[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"
|
||||
# Наш шов: если причина — лимит Let's Encrypt, говорим это прямо и с датой повтора, как для домена.
|
||||
# Иначе человек читает «частые причины» и ищет проблему у хостера, которой нет.
|
||||
local till
|
||||
till=$(grep -rhoE 'retry after [0-9-]+ [0-9:]+ UTC' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed 's/^retry after //')
|
||||
if grep -rqsE 'rateLimited|too many certificates' /var/log/3x-ui-install.log 2>/dev/null; then
|
||||
warn "Причина: лимит Let's Encrypt — на этот IP за 7 суток уже выпущено 5 сертификатов${till:+; повтор возможен после $till}. Это ограничение Let's Encrypt, а не вашего сервера: порт 80 и файрвол тут не при чём."
|
||||
else
|
||||
warn "Let's Encrypt не выдал сертификат на IP $HOST. Частые причины:"
|
||||
{
|
||||
echo " – лимит Let's Encrypt: 5 сертификатов в неделю на один IP;"
|
||||
echo " – входящий порт 80 закрыт (у хостера или в файрволе);"
|
||||
echo " – сбой у самого Let's Encrypt (лог: /var/log/3x-ui-install.log)."
|
||||
} >&2
|
||||
fi
|
||||
{
|
||||
echo " Можно продолжить с самоподписанным сертификатом: ссылки на подключения работают, браузер покажет"
|
||||
echo " предупреждение, а подписка в приложениях может не открыться."
|
||||
} >&2
|
||||
@@ -746,6 +756,8 @@ kl_sni_menu() {
|
||||
if ((i == known_idx)); then
|
||||
printf '\nОбщедоступные сайты:\n' >&2
|
||||
for k in "${!known[@]}"; do printf ' %d - %s\n' "$((k + 1))" "${known[k]}" >&2; done
|
||||
# Наш шов: последним пунктом — автовыбор из этого списка (раньше выбор был только по номеру).
|
||||
printf ' %d - автовыбор из списка\n' "$((${#known[@]} + 1))" >&2
|
||||
printf ' 0 - назад\nВаш выбор: ' >&2
|
||||
ans=""
|
||||
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||
@@ -753,6 +765,11 @@ kl_sni_menu() {
|
||||
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then
|
||||
printf 'sni:%s' "${known[10#$ans - 1]}"; return 0
|
||||
fi
|
||||
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans == ${#known[@]} + 1)); then
|
||||
local autopick="${known[RANDOM % ${#known[@]}]}"
|
||||
printf ' автовыбор: %s\n' "$autopick" >&2
|
||||
printf 'sni:%s' "$autopick"; return 0
|
||||
fi
|
||||
printf ' ничего не выбрано — оставляю автовыбор\n' >&2
|
||||
return 0
|
||||
fi
|
||||
@@ -1022,7 +1039,15 @@ kl_sni_menu() {
|
||||
[[ -n $d ]] || continue
|
||||
if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN
|
||||
else warn "оставляю прежний домен $DOMAIN — новый не указывает на этот сервер"; fi ;;
|
||||
3) die "Установка остановлена по вашему выбору. Чтобы поставить без домена — перезапустите и выберите соседа по подсети или общедоступный сайт." ;;
|
||||
3) # Наш шов: продолжаем без своего домена прямо здесь — маскировка соседом по подсети
|
||||
# (или известным сайтом), панель и подписка по IP. Перезапускать установку не нужно.
|
||||
local nb2
|
||||
nb2=$(nearby_sites 2>/dev/null | shuf | head -1)
|
||||
[[ -n $nb2 ]] || nb2=dl.google.com
|
||||
DOMAIN=""; PANEL_ON=ip; LINK_HOST=$HOST
|
||||
SNI=$nb2; SNI2=${SNI2:-$nb2}
|
||||
ok "продолжаю без своего домена: маскировка ${B}$SNI${N}, панель и подписка — по IP"
|
||||
break ;;
|
||||
*) sleep 5 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||
# останавливается и не заменяет команду hy2.
|
||||
|
||||
KIT_VERSION="1.2.1.1"
|
||||
KIT_VERSION="1.2.1.8"
|
||||
|
||||
cat >&2 <<'MSG'
|
||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||
|
||||
+87
-3
@@ -73,6 +73,9 @@ kit_local_help() {
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening сводка по защите сервера и что ещё можно включить
|
||||
kit local access доступ к панели, сертификат, ссылки и заглушка — одним экраном
|
||||
kit local links подписка, QR-код и ссылки по протоколам (как в меню kit)
|
||||
kit local ports открытые порты: что нужно, что лишнее и как закрыть
|
||||
kit local uninstall то же, что kit uninstall: снести всё, включая сам kit (SSH не трогает)
|
||||
|
||||
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
|
||||
@@ -420,9 +423,8 @@ Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
EOF
|
||||
systemctl enable --now unattended-upgrades >/dev/null 2>&1 || kl_warn "служба не включилась — проверьте: systemctl status unattended-upgrades"
|
||||
kl_ok "включено: обновления безопасности ставятся сами"
|
||||
kl_ok "панель и ядро это не затрагивает — они ставятся не из apt, а из своих релизов"
|
||||
kl_warn "обновление ядра применится только после перезагрузки, сам сервер не перезагружается"
|
||||
# Одна строка: подробности нужны редко, а в выводе установки они превращались в шум.
|
||||
kl_ok "автообновления безопасности включены (панель и ядро это не затрагивает, сервер сам не перезагружается)"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -900,6 +902,9 @@ kit_local_cmd() {
|
||||
upgrades | auto) shift; kit_local_upgrades "$@" ;;
|
||||
hardening | hard) shift; kit_local_hardening "$@" ;;
|
||||
uninstall | remove) shift; kit_local_uninstall "$@" ;;
|
||||
links | link) shift; kit_local_links "$@" ;;
|
||||
access) shift; kit_local_access "$@" ;;
|
||||
ports) shift; kit_local_ports "$@" ;;
|
||||
site | stub | stub-site) shift; kit_local_site "$@" ;;
|
||||
*)
|
||||
printf 'Неизвестная команда форка: %s\n' "$1" >&2
|
||||
@@ -922,6 +927,82 @@ kit_local_cmd() {
|
||||
#
|
||||
# kit local uninstall спросит подтверждение словом «удалить»
|
||||
# kit local uninstall --dry-run только покажет, что будет удалено
|
||||
kit_local_ports() { # [--close-extra] — что открыто в ufw, что реально нужно и как закрыть лишнее
|
||||
local close=no a
|
||||
for a in "$@"; do
|
||||
case $a in
|
||||
--close-extra) close=yes ;;
|
||||
--help | -h) printf 'kit local ports [--close-extra] — открытые порты, нужные и лишние\n'; return 0 ;;
|
||||
*) kl_die "kit local ports [--close-extra]"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
command -v ufw >/dev/null 2>&1 || { kl_warn "ufw не установлен — порты смотреть нечем"; return 0; }
|
||||
local -a opened=() needed=() extra=()
|
||||
mapfile -t opened < <(ufw status 2>/dev/null | awk '$2 == "ALLOW" { print $1 }' | sort -u)
|
||||
# Нужные порты: те, что слушают наши подключения (x-ui и ядро Xray), плюс SSH.
|
||||
mapfile -t needed < <({ ss -tlnp 2>/dev/null; ss -ulnp 2>/dev/null; } \
|
||||
| awk '/x-ui|xray/ { print $4 }' | sed 's/.*://' | sort -un; ss -tlnp 2>/dev/null | awk '/sshd/ { print $4 }' | sed 's/.*://' | sort -un)
|
||||
local p q keep
|
||||
for p in "${opened[@]}"; do
|
||||
keep=no
|
||||
case $p in 80/tcp | 443/tcp) keep=yes ;; esac # 443 — работа, 80 — продление сертификата
|
||||
for q in "${needed[@]}"; do [[ $p == "$q/tcp" || $p == "$q/udp" || $p == "$q" ]] && keep=yes; done
|
||||
[[ $keep == yes ]] && continue
|
||||
extra+=("$p")
|
||||
done
|
||||
printf ' открыто в ufw: %s\n' "$(printf '%s ' "${opened[@]}")"
|
||||
if ((${#needed[@]})); then printf ' нужны: %s\n' "$(printf '%s/tcp %s/udp ' "${needed[@]}" "${needed[@]}")"
|
||||
else printf ' нужны: (не определил — смотрите kit check)\n'; fi
|
||||
if ((${#extra[@]})); then
|
||||
printf ' лишние: %s\n' "$(printf '%s ' "${extra[@]}")"
|
||||
printf ' закрыть лишние: kit local ports --close-extra\n'
|
||||
if [[ $close == yes ]]; then
|
||||
for p in "${extra[@]}"; do ufw --force delete allow "$p" >/dev/null 2>&1 && printf ' закрыл %s\n' "$p"; done
|
||||
fi
|
||||
else
|
||||
printf ' лишних нет\n'
|
||||
fi
|
||||
}
|
||||
kit_local_access() { # доступ к панели, ссылки на подключения, заглушка и сертификат — одним экраном
|
||||
local u=${1:-} till2
|
||||
printf '%s\n' "${B:-}Доступ к панели 3X-UI${N:-} (файл /root/3x-ui.txt, виден только root)"
|
||||
if grep -aqE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt 2>/dev/null; then
|
||||
grep -aE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt | sed 's/^[[:space:]]*/ /'
|
||||
else
|
||||
kl_warn "данных панели в /root/3x-ui.txt нет"
|
||||
fi
|
||||
printf '\n%s\n' "${B:-}Сертификат панели${N:-}"
|
||||
if [[ -s /root/cert/ip/fullchain.pem ]] && openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
|
||||
printf ' Let'"'"'s Encrypt на IP — доверенный, браузер не ругается\n'
|
||||
elif [[ -s /root/cert/ip/fullchain.pem ]]; then
|
||||
printf ' самоподписанный — браузер предупредит, подписка в приложениях может не открыться\n'
|
||||
else
|
||||
printf ' сертификата на IP нет: панель открывается только через SSH-туннель\n'
|
||||
fi
|
||||
till2=$(grep -rhoE 'retry after [0-9-]+ [0-9:]+ UTC' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed 's/^retry after //')
|
||||
[[ -n $till2 ]] && printf ' лимит Let'"'"'s Encrypt: повтор выпуска возможен после %s (затем запустите установщик снова)\n' "$till2"
|
||||
printf '\n%s\n' "${B:-}Ссылки на подключения и QR-код${N:-}"
|
||||
if [[ -z $u ]]; then u=$(kit user list 2>/dev/null | awk 'NR > 1 && NF { print $1; exit }'); u=${u:-admin}; fi
|
||||
local now; now=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
if [[ $(wc -w <<<"$now") -gt 1 ]]; then kit user link "$u" --all; else kit user link "$u"; fi
|
||||
printf '\n%s\n' "${B:-}Заглушка (что видит посторонний, открыв адрес сервера)${N:-}"
|
||||
kit local site --show
|
||||
printf ' посмотреть снаружи: откройте https://<IP сервера>/ в браузере или curl -k https://<IP>/
|
||||
'
|
||||
}
|
||||
|
||||
kit_local_links() { # [имя] — подписка, QR-код и ссылки по протоколам (то же, что «kit → Пользователи → Показать ссылки»)
|
||||
local u=${1:-} now
|
||||
if [[ -z $u ]]; then
|
||||
u=$(kit user list 2>/dev/null | awk 'NR > 1 && NF { print $1; exit }')
|
||||
u=${u:-admin}
|
||||
fi
|
||||
printf ' пользователь: %s\n' "$u"
|
||||
now=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
# Ссылки на каждый протокол — только если протоколов больше одного; при одном достаточно подписки.
|
||||
if [[ $(wc -w <<<"$now") -gt 1 ]]; then kit user link "$u" --all; else kit user link "$u"; fi
|
||||
}
|
||||
|
||||
kit_local_uninstall() {
|
||||
# Наш шов: логику удаления отдаём авторской команде «kit uninstall». Она снимает и сам kit, чтобы на
|
||||
# сервере не осталось следов, поэтому своя реализация здесь больше не нужна — команда оставлена
|
||||
@@ -987,6 +1068,9 @@ ${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то ж
|
||||
kit local site [--list | N | --show] сайт-заглушка: случайный ИТ-сайт, список, конкретный, какой стоит
|
||||
|
||||
Проверка и починка
|
||||
kit local access [имя] доступ к панели, сертификат, ссылки и заглушка одним экраном
|
||||
kit local links [имя] подписка, QR-код и ссылки по протоколам (по умолчанию — первый пользователь)
|
||||
kit local ports [--close-extra] открытые порты: нужные, лишние; --close-extra закрывает лишние
|
||||
kit local uninstall то же, что kit uninstall: снести установку целиком и сам kit; SSH не трогает
|
||||
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
|
||||
kit fix [--dry-run] безопасные починки: время, службы, конфиги
|
||||
|
||||
+7
-5
@@ -12,7 +12,7 @@
|
||||
set -Eeuo pipefail
|
||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||
|
||||
KIT_VERSION="1.2.1.1"
|
||||
KIT_VERSION="1.2.1.8"
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
||||
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
||||
@@ -3194,12 +3194,12 @@ menu_main() {
|
||||
echo
|
||||
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \
|
||||
"7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "10. Удалить KIT с сервера" "0. Выход"
|
||||
"7. Резервная копия" "8. Версии" "" "9. Доступ к панели, ссылки и заглушка" "10. Панель x-ui" "11. Удалить KIT с сервера" "0. Выход"
|
||||
echo
|
||||
menu_status
|
||||
update_hint
|
||||
echo
|
||||
ask_num "Выбор [0-10]: "; c=$REPLY
|
||||
ask_num "Выбор [0-11]: "; c=$REPLY
|
||||
case $c in
|
||||
1) menu_users ;;
|
||||
2) menu_net ;;
|
||||
@@ -3209,8 +3209,10 @@ menu_main() {
|
||||
6) run_action menu_auto; pause ;;
|
||||
7) run_action cmd_backup; pause ;;
|
||||
8) run_action cmd_version; pause ;;
|
||||
9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||
10) run_action cmd_uninstall; pause ;;
|
||||
# Наш шов: пункт 9 — доступ к панели, ссылки на подключения и состояние заглушки в одном месте.
|
||||
9) kit local access; pause ;;
|
||||
10) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||
11) run_action cmd_uninstall; pause ;;
|
||||
0 | "") return 0 ;;
|
||||
*) echo "Нет такого пункта." ;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Проверка: подсказка «Выбор [0-N]» в каждом меню kit должна совпадать с числом пунктов.
|
||||
|
||||
Пункты считаются по строкам box (с учётом переносов через «\\»). Смысл проверки: мы добавляем в меню
|
||||
свои пункты — однажды подсказка осталась «0-10» при 11 пунктах, и человек не мог выбрать последний.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import pathlib
|
||||
|
||||
path = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "scripts/kit.sh")
|
||||
text = path.read_text(encoding="utf-8").replace("\\\n", "")
|
||||
lines = text.splitlines()
|
||||
bad = 0
|
||||
for i, line in enumerate(lines):
|
||||
m = re.search(r'\bbox\s+"([^"]+)"(.*)$', line)
|
||||
if not m:
|
||||
continue
|
||||
items = re.findall(r'"(\d+)\.\s', m.group(2))
|
||||
maxn = max((int(x) for x in items), default=0)
|
||||
prompt = None
|
||||
for j in range(i + 1, min(i + 120, len(lines))):
|
||||
pm = re.search(r'ask_num\s+"Выбор \[0-(\d+)\]', lines[j])
|
||||
if pm:
|
||||
prompt = int(pm.group(1))
|
||||
break
|
||||
if prompt != maxn:
|
||||
print(f"! меню «{m.group(1)}»: пунктов {maxn}, а подсказка «Выбор [0-{prompt}]»", file=sys.stderr)
|
||||
bad += 1
|
||||
print(f"меню проверены: {len(lines) and sum(1 for l in lines if 'box \"' in l)} · рассинхронов: {bad}")
|
||||
sys.exit(1 if bad else 0)
|
||||
+37
-2
@@ -212,6 +212,27 @@ def main():
|
||||
' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "10. Удалить KIT с сервера" "0. Выход"\n',
|
||||
False,
|
||||
' "7. Резервная копия" "8. Версии" "" "9. Доступ к панели, ссылки и заглушка" "10. Панель x-ui" "11. Удалить KIT с сервера" "0. Выход"\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' ask_num "Выбор [0-10]: "; c=$REPLY\n',
|
||||
False,
|
||||
' ask_num "Выбор [0-11]: "; c=$REPLY\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 10) run_action cmd_uninstall; pause ;;\n',
|
||||
False,
|
||||
' # Наш шов: пункт 9 — доступ к панели, ссылки на подключения и состояние заглушки в одном месте.\n 9) kit local access; pause ;;\n 10) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 11) run_action cmd_uninstall; pause ;;\n',
|
||||
'авто-шов: replace, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' echo "${B}Полное удаление 3X-UI KIT${N}"\n',
|
||||
@@ -289,6 +310,20 @@ def main():
|
||||
' [[ $rc == 0 || $rc == 2 ]] || {\n # Наш шов: лимит Let\'s Encrypt — частая причина, и она не про сервер. Говорим прямо и с датой повтора.\n if grep -qE \'rateLimited|too many certificates\' "$log"; then\n local till\n till=$(grep -oE \'retry after [0-9-]+ [0-9:]+ UTC\' "$log" | tail -1 | sed \'s/^retry after //\')\n warn "Лимит Let\'s Encrypt: на имя ${B}$DOMAIN${N} за 7 суток уже выпущено 5 сертификатов${till:+ — повтор возможен после $till}. С вашим сервером всё в порядке: DNS, порты и ufw не при чём. Подождите до этой даты либо возьмите другое имя (например node3.…): лимит считается на точный набор имён."\n else\n warn "Let\'s Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"\n fi\n return 1\n }\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' local san="IP:$HOST"\n [[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"\n',
|
||||
False,
|
||||
' local san="IP:$HOST"\n [[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"\n # Наш шов: если причина — лимит Let\'s Encrypt, говорим это прямо и с датой повтора, как для домена.\n # Иначе человек читает «частые причины» и ищет проблему у хостера, которой нет.\n local till\n till=$(grep -rhoE \'retry after [0-9-]+ [0-9:]+ UTC\' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed \'s/^retry after //\')\n if grep -rqsE \'rateLimited|too many certificates\' /var/log/3x-ui-install.log 2>/dev/null; then\n warn "Причина: лимит Let\'s Encrypt — на этот IP за 7 суток уже выпущено 5 сертификатов${till:+; повтор возможен после $till}. Это ограничение Let\'s Encrypt, а не вашего сервера: порт 80 и файрвол тут не при чём."\n else\n',
|
||||
'авто-шов: insert, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' echo " – сбой у самого Let\'s Encrypt (лог: /var/log/3x-ui-install.log)."\n',
|
||||
False,
|
||||
' echo " – сбой у самого Let\'s Encrypt (лог: /var/log/3x-ui-install.log)."\n } >&2\n fi\n {\n',
|
||||
'авто-шов: insert, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
|
||||
@@ -307,7 +342,7 @@ def main():
|
||||
'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',
|
||||
' 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 # Наш шов: последним пунктом — автовыбор из этого списка (раньше выбор был только по номеру).\n printf \' %d - автовыбор из списка\\n\' "$((${#known[@]} + 1))" >&2\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 if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans == ${#known[@]} + 1)); then\n local autopick="${known[RANDOM % ${#known[@]}]}"\n printf \' автовыбор: %s\\n\' "$autopick" >&2\n printf \'sni:%s\' "$autopick"; 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',
|
||||
),
|
||||
(
|
||||
@@ -356,7 +391,7 @@ def main():
|
||||
'scripts/3x-ui.sh',
|
||||
' issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя. Исправьте причину и запустите скрипт снова."\n',
|
||||
False,
|
||||
' # Наш шов: провал сертификата не обрывает установку. Человек может повторить попытку (DNS доехал,\n # лимит Let\'s Encrypt кончился) или ввести другое имя — установка продолжится с него же.\n while ! issue_domain_cert; do\n echo\n echo " ${B}Что делать дальше?${N}"\n echo " ${B}1${N} повторить попытку если ждали обновления A-записи или лимита Let\'s Encrypt"\n echo " ${B}2${N} ввести другой домен проверим, что он указывает на этот сервер, и повторим"\n echo " ${B}3${N} остановиться поставить без своего домена: перезапустите и выберите соседа по подсети"\n ask_tty " Выбор [1]: " || die "Сертификат для $DOMAIN не выпущен. Подробности: /var/log/kit-domain-cert.log"\n case ${REPLY//[[:space:]]/} in\n 2) ask_tty " адрес сайта (например vpn.example.com): " || continue\n local d=${REPLY//[[:space:]]/}\n [[ -n $d ]] || continue\n if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN\n else warn "оставляю прежний домен $DOMAIN — новый не указывает на этот сервер"; fi ;;\n 3) die "Установка остановлена по вашему выбору. Чтобы поставить без домена — перезапустите и выберите соседа по подсети или общедоступный сайт." ;;\n *) sleep 5 ;;\n esac\n done\n',
|
||||
' # Наш шов: провал сертификата не обрывает установку. Человек может повторить попытку (DNS доехал,\n # лимит Let\'s Encrypt кончился) или ввести другое имя — установка продолжится с него же.\n while ! issue_domain_cert; do\n echo\n echo " ${B}Что делать дальше?${N}"\n echo " ${B}1${N} повторить попытку если ждали обновления A-записи или лимита Let\'s Encrypt"\n echo " ${B}2${N} ввести другой домен проверим, что он указывает на этот сервер, и повторим"\n echo " ${B}3${N} остановиться поставить без своего домена: перезапустите и выберите соседа по подсети"\n ask_tty " Выбор [1]: " || die "Сертификат для $DOMAIN не выпущен. Подробности: /var/log/kit-domain-cert.log"\n case ${REPLY//[[:space:]]/} in\n 2) ask_tty " адрес сайта (например vpn.example.com): " || continue\n local d=${REPLY//[[:space:]]/}\n [[ -n $d ]] || continue\n if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN\n else warn "оставляю прежний домен $DOMAIN — новый не указывает на этот сервер"; fi ;;\n 3) # Наш шов: продолжаем без своего домена прямо здесь — маскировка соседом по подсети\n # (или известным сайтом), панель и подписка по IP. Перезапускать установку не нужно.\n local nb2\n nb2=$(nearby_sites 2>/dev/null | shuf | head -1)\n [[ -n $nb2 ]] || nb2=dl.google.com\n DOMAIN=""; PANEL_ON=ip; LINK_HOST=$HOST\n SNI=$nb2; SNI2=${SNI2:-$nb2}\n ok "продолжаю без своего домена: маскировка ${B}$SNI${N}, панель и подписка — по IP"\n break ;;\n *) sleep 5 ;;\n esac\n done\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
|
||||
@@ -59,6 +59,11 @@ for fn in ask_yes ask_ssh_port ssh_ports_now ask_protocols proto_parse proto_men
|
||||
[[ -z $c || $c -gt $d ]] || die "scripts/3x-ui-local.sh: $fn вызывается (строка $c) раньше, чем определена ($d)"
|
||||
done
|
||||
ok "Все наши функции определены раньше своих вызовов"
|
||||
# python3 есть не везде (на Windows обычно только python) — берём тот, что есть.
|
||||
py=$(command -v python3 || command -v python || true)
|
||||
[[ -n $py ]] || die "нет python — им проверяются меню (tools/check-menus.py)"
|
||||
"$py" tools/check-menus.py >/dev/null || die "подсказка «Выбор [0-N]» не совпадает с числом пунктов меню (tools/check-menus.py)"
|
||||
ok "Подсказки меню совпадают с числом пунктов"
|
||||
|
||||
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user