Compare commits

...
16 Commits
Author SHA1 Message Date
hacker07 c4f7c4f710 v1.2.1.9: kit fix снова выполняется при установке; убрано только описание команды 2026-10-14 18:57:00 +03:00
hacker07 19eb77be68 подпись релиза 1.2.1.8
Синхронизация с автором / sync (push) Failing after 2s
2026-10-14 18:30:20 +03:00
hacker07 333b8edc9a release.sh: проверка меню через python из PATH (python3 есть не везде) 2026-10-14 18:27:05 +03:00
hacker07 13dfb44f3f v1.2.1.8: подсказка меню 0-11, тест меню в гейте, без kit fix и лишних строк в установке 2026-10-14 18:25:33 +03:00
hacker07 3a8518651c подпись релиза 1.2.1.7
Синхронизация с автором / sync (push) Failing after 1s
2026-10-14 18:05:59 +03:00
hacker07 915dd35f42 v1.2.1.7: переставлен порядок — сначала качаем все файлы релиза, потом проверяем установщик 2026-10-14 18:03:57 +03:00
hacker07 edabfd9a83 подпись релиза 1.2.1.6
Синхронизация с автором / sync (push) Failing after 1s
2026-10-14 17:55:19 +03:00
hacker07 741a70c9bf v1.2.1.6: доступы к панели в конце установки; все файлы релиза скачиваются и сверяются (рабочая заглушка) 2026-10-14 17:53:23 +03:00
hacker07 0d2af45bdb подпись релиза 1.2.1.5
Синхронизация с автором / sync (push) Failing after 1s
2026-10-14 17:34:04 +03:00
hacker07 a02650311e v1.2.1.5: пункт меню «Доступ к панели, ссылки и заглушка» + kit local access 2026-10-14 17:31:58 +03:00
hacker07 2b57e6f6d7 подпись релиза 1.2.1.4
Синхронизация с автором / sync (push) Failing after 1s
2026-10-14 17:16:10 +03:00
hacker07 df8a99bb78 v1.2.1.4: короткий итог установки, автовыбор в общедоступных сайтах, kit local ports, без дампа ссылок 2026-10-14 17:06:23 +03:00
hacker07 47328b33b3 подпись релиза 1.2.1.3
Синхронизация с автором / sync (push) Failing after 2s
2026-10-14 16:51:06 +03:00
hacker07 776b7a8c5e v1.2.1.3: ссылки и QR — в меню kit (и kit local links), в итоге видно сертификат панели 2026-10-14 16:49:45 +03:00
hacker07 2d63136853 подпись релиза 1.2.1.2
Синхронизация с автором / sync (push) Failing after 2s
2026-10-14 16:32:58 +03:00
hacker07 1b85a4bb23 v1.2.1.2: отказ сертификата на IP — с датой повтора; «без домена» продолжает установку вместо остановки 2026-10-14 16:31:54 +03:00
13 changed files with 389 additions and 103 deletions
+91
View File
@@ -1,3 +1,94 @@
## v1.2.1.9: kit fix снова выполняется при установке
* В 1.2.1.8 я понял просьбу слишком широко и **выключил сам `kit fix`** — вместе с ним пропала и
синхронизация времени. Возвращено: `kit fix` при установке выполняется как раньше (время, службы, права),
ключ `--no-time` его пропускает.
* Убрана только та строка, о которой шла речь: описание команды «kit fix – безопасные исправления
(службы, права, автообновление, сертификат, время)» больше не печатается — результат («Чинить нечего»
или список найденного) остаётся.
* Наша строка шага сокращена: «kit fix: безопасные починки (время, службы, права)».
## 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) ## v1.2.1.1: сборка на новой базе автора (3X-UI KIT 1.2.1)
**Правило нумерации:** берём версию автора и через точку добавляем свою, начиная с 1. У автора 1.2.1 — **Правило нумерации:** берём версию автора и через точку добавляем свою, начиная с 1. У автора 1.2.1 —
+4
View File
@@ -191,6 +191,10 @@ bash tools/fork-sync.sh # подтянуть автора, перен
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом 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. Так на сервере всегда видно, у автора 1.2.1 → наша сборка 1.2.1.1; вышла его 1.2.2 → наша 1.2.2.1. Так на сервере всегда видно,
на чьей базе собрана сборка. на чьей базе собрана сборка.
+8 -8
View File
@@ -1,9 +1,9 @@
# 3X-UI KIT 1.2.1.1 # 3X-UI KIT 1.2.1.8
# 3X-UI_autoVPS 1.2.1.1 # 3X-UI_autoVPS 1.2.1.8
f2c191e06de726a68f69453b33a4c042b09f50e4feb0285459a8ad8b696a9b18 scripts/3x-ui.sh 9776e75232d9e65614130cd350e5d7f0dd97ffaa5640729479cc1ced927869c8 scripts/3x-ui.sh
6f8187aef75658bbd77adf1ad560aaebd103ad6f13654851f7e6ddbcce95d05b scripts/hysteria2.sh 81ae4f2c3d512f352e64596149174a9141f8a29e2fbd44f0e16db5387f8d31f0 scripts/hysteria2.sh
1e5338461ef9cec1445335812cf73659329f16a0ac92a5b622e9788b74fdc773 scripts/kit.sh 17d118d5aa7ba22d4541e7d4874d31b5fb1950b3b7cc09b2fc97885eb537ec8e scripts/kit.sh
f214671464e1c2e174e5af9df42c95567a9b4a9141f31a39f7df2e971e89d240 scripts/kit-sub.py f214671464e1c2e174e5af9df42c95567a9b4a9141f31a39f7df2e971e89d240 scripts/kit-sub.py
79e53e4c4ffcfdbcf1209196901ac48c4c76c32ae677ef5800d3658f51ca1a55 scripts/kit-local.sh 5d07c87452057e1c73e0f3af008074f94f5be6175817b2d6f94a93f69b0533b7 scripts/kit-local.sh
7df814ffab37d8e1c48b895677b63311a0e993a275c2095fbb97c5d359ba1bed scripts/3x-ui-local.sh 483355bf1c0e9b75f15cf783a730ea59f5fc17038b9e212c952f792bcc57d0d8 scripts/3x-ui-local.sh
91c47310dd65fd1f86ad29aad31e72c5d5661532039230a52d5a90f630ef21ff VERSION a9762073937784114e8467c57045aa83784f14ab0c0e62906ec5c4c164baf4ef VERSION
+2 -2
View File
@@ -1,6 +1,6 @@
-----BEGIN SSH SIGNATURE----- -----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
AAALc3NoLWVkMjU1MTkAAABAXrsYVkh26UyCTiwNfxwKgBQzrKOSAubhberognGoaeNfTp AAALc3NoLWVkMjU1MTkAAABAKCus5+Q0q6U/kWCLOJxk/TURjxPuy+TLbudsY80iVGf4sR
YRJzjFe7Y03DQBilCb39W2RKLLOlZyz2ncRCWGDQ== +Nohbd9U5s7OlYRoHWVN60bRDhesIqsYfxzWirDA==
-----END SSH SIGNATURE----- -----END SSH SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
1.2.1.1 1.2.1.9
+79 -77
View File
@@ -32,7 +32,6 @@
# --no-harden не трогать ping и автообновления # --no-harden не трогать ping и автообновления
# --ssh-port N добавить порт SSH N (прежний порт продолжает работать) # --ssh-port N добавить порт SSH N (прежний порт продолжает работать)
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет) # --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
# --no-time не запускать kit fix
# --no-check не запускать в конце kit check # --no-check не запускать в конце kit check
# --clean убрать прошлую установку и поставить заново (SSH не трогает) # --clean убрать прошлую установку и поставить заново (SSH не трогает)
# --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети) # --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети)
@@ -44,7 +43,7 @@
set -Eeuo pipefail set -Eeuo pipefail
KIT_VERSION="1.2.1.1" # версия сборки форка; должна совпадать с VERSION релиза KIT_VERSION="1.2.1.9" # версия сборки форка; должна совпадать с VERSION релиза
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}" GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
NS="3x-ui-kit-release" NS="3x-ui-kit-release"
SIG_ID="releases@3x-ui-kit" SIG_ID="releases@3x-ui-kit"
@@ -83,7 +82,7 @@ usage() {
--no-sub не показывать в конце подписку, доступ к панели и QR-код --no-sub не показывать в конце подписку, доступ к панели и QR-код
--clean снести прошлую установку и поставить заново (если прошлый запуск оборвался) --clean снести прошлую установку и поставить заново (если прошлый запуск оборвался)
--auto-sni не предлагать выбор сайта маскировки (автоподбор автора) --auto-sni не предлагать выбор сайта маскировки (автоподбор автора)
--no-time не запускать kit fix (время и безопасные починки) --no-time не запускать kit fix при установке (время, службы, права)
--no-check не запускать в конце kit check --no-check не запускать в конце kit check
--verify-only только проверить релиз (подпись и SHA256) и выйти --verify-only только проверить релиз (подпись и SHA256) и выйти
--dry-run показать план и ничего не делать --dry-run показать план и ничего не делать
@@ -162,7 +161,7 @@ if [[ $dry == yes ]]; then
random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;; random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;;
*) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;; *) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;;
esac esac
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)" [[ $fix_time == yes ]] && pre "после: kit fix (время, службы, права) — тихо, без описания команды"
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening" [[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
[[ $show_sub == yes ]] && pre "в самом конце: доступ к панели 3X-UI, подписка, QR-код и ссылки (--no-sub — не надо)" [[ $show_sub == yes ]] && pre "в самом конце: доступ к панели 3X-UI, подписка, QR-код и ссылки (--no-sub — не надо)"
echo echo
@@ -179,7 +178,7 @@ say "беру релиз v$KIT_VERSION и проверяю подпись"
tmp=$(mktemp -d) tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT trap 'rm -rf "$tmp"' EXIT
raw="$GITEA/raw/tag/v$KIT_VERSION" 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")" mkdir -p "$tmp/$(dirname "$f")"
curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)" curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)"
done done
@@ -188,9 +187,21 @@ ssh-keygen -Y verify -f "$tmp/allowed_signers" -I "$SIG_ID" -n "$NS" -s "$tmp/SH
|| die "подпись релиза v$KIT_VERSION не сошлась с ключом форка — это не наш релиз, ничего не запускаю" || die "подпись релиза v$KIT_VERSION не сошлась с ключом форка — это не наш релиз, ничего не запускаю"
sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp/SHA256SUMS") sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp/SHA256SUMS")
[[ $(printf '%s' "$sum" | wc -c) -eq 64 ]] || die "в 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=$(sha256sum "$tmp/scripts/3x-ui.sh" | awk '{print $1}')
[[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю" [[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю"
bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса" bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса"
[[ -r $tmp/scripts/kit-local.sh ]] || die "в релизе нет scripts/kit-local.sh — сборка неполная"
printf ' ✓ подпись сошлась, SHA256 установщика совпал\n' printf ' ✓ подпись сошлась, SHA256 установщика совпал\n'
if [[ $verify_only == yes ]]; then if [[ $verify_only == yes ]]; then
@@ -457,13 +468,13 @@ if [[ $site != off ]]; then
esac esac
if [[ -n $stub_n ]]; then if [[ -n $stub_n ]]; then
install -d -m 755 /var/www/kit 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 && [[ -s /var/www/kit/index.html ]]; then
stub_staged=yes stub_staged=yes
rm -f "$tmp/var/www/kit/index.html" rm -f "$tmp/var/www/kit/index.html"
say "заглушка: наш сайт №$((stub_n + 1)) — ставится сразу, авторская страница не используется" say "заглушка: наш сайт №$((stub_n + 1)) — ставится сразу, авторская страница не используется"
else else
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site)" warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site). Причина: $(head -c 200 "$tmp/stub.err" 2>/dev/null | tr '\n' ' ')"
fi fi
fi fi
fi fi
@@ -500,7 +511,7 @@ case ${stub_staged:-no} in
esac esac
if [[ $fix_time == yes ]]; then if [[ $fix_time == yes ]]; then
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)" say "kit fix: безопасные починки (время, службы, права)"
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод" kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
fi fi
@@ -525,77 +536,68 @@ if [[ -n $ssh_port ]]; then
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port" kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
fi fi
# ------------------------------------------------------------------ проверка # ------------------------------------------------------------------ итог: коротко и по делу
if [[ $do_check == yes ]]; then if [[ $do_check == yes || $show_sub == yes ]]; then
say "проверяю, что получилось" # kit check печатает много строк: человеку нужны только замечания и одна строка «всё ли в порядке».
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix" chk=$(kit check 2>&1 || true)
kit local hardening problems=$(printf '%s\n' "$chk" | grep -cE '^❌|^⚠|^!' || true)
fi [[ $problems == 0 ]] || printf '%s\n' "$chk" | grep -E '^❌|^⚠|^!' | sed 's/^/ /'
ports_open=""
cat <<EOF 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 }')
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea). os_show=$( . /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}" )
cert_show="без сертификата: панель открывается через SSH-туннель"
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика) if [[ -s /root/cert/ip/fullchain.pem ]]; then
если что-то не так: kit check --deep, затем kit fix if openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force cert_show="${G}Let's Encrypt на IP — доверенный, браузер не ругается${N}"
EOF else
cert_show="${Y}самоподписанный — браузер предупредит${N}"
# ------------------------------------------------------------------ SSH: как закрыть прежние порты fi
fi
if [[ -n ${ssh_port:-} ]]; then protos_show=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
cat <<EOF cat <<EOF
${Y}${B}SSH переведён на порт $ssh_port.${N} Прежние порты (${ssh_ports_before:-22}) пока работают — это ${B}────────────── Установка завершена ──────────────${N}
сделано специально, чтобы вы не потеряли доступ. Порядок обязательный: сборка: 3X-UI_autoVPS $KIT_VERSION (канал: наш Gitea)
1) во ВТОРОМ окне проверьте вход: ssh -p $ssh_port root@${HOST:-<IP сервера>} сервер: ${ip_show:-?}${os_show:+ · $os_show}
2) вход работает — закройте прежние порты: ${B}kit local ssh --close-old $ssh_port${N} протоколы: ${protos_show:-—}
3) поправьте порт у себя: ~/.ssh/config, терминалы, мониторинг сертификат: $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 EOF
fi 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
View File
@@ -39,7 +39,7 @@ wait_apt_idle() {
XUI_VERSION="v3.9.0" XUI_VERSION="v3.9.0"
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30). # SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6" XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
KIT_VERSION="1.2.1.1" KIT_VERSION="1.2.1.9"
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main. # kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash, # Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
@@ -451,11 +451,21 @@ issue_domain_cert() {
ip_cert_self_signed_fallback() { ip_cert_self_signed_fallback() {
local san="IP:$HOST" local san="IP:$HOST"
[[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$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. Частые причины:" warn "Let's Encrypt не выдал сертификат на IP $HOST. Частые причины:"
{ {
echo " – лимит Let's Encrypt: 5 сертификатов в неделю на один IP;" echo " – лимит Let's Encrypt: 5 сертификатов в неделю на один IP;"
echo " – входящий порт 80 закрыт (у хостера или в файрволе);" echo " – входящий порт 80 закрыт (у хостера или в файрволе);"
echo " – сбой у самого Let's Encrypt (лог: /var/log/3x-ui-install.log)." echo " – сбой у самого Let's Encrypt (лог: /var/log/3x-ui-install.log)."
} >&2
fi
{
echo " Можно продолжить с самоподписанным сертификатом: ссылки на подключения работают, браузер покажет" echo " Можно продолжить с самоподписанным сертификатом: ссылки на подключения работают, браузер покажет"
echo " предупреждение, а подписка в приложениях может не открыться." echo " предупреждение, а подписка в приложениях может не открыться."
} >&2 } >&2
@@ -746,6 +756,8 @@ kl_sni_menu() {
if ((i == known_idx)); then if ((i == known_idx)); then
printf '\nОбщедоступные сайты:\n' >&2 printf '\nОбщедоступные сайты:\n' >&2
for k in "${!known[@]}"; do printf ' %d - %s\n' "$((k + 1))" "${known[k]}" >&2; done 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 printf ' 0 - назад\nВаш выбор: ' >&2
ans="" ans=""
read -r -t 300 ans <"$src" 2>/dev/null || true 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 if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then
printf 'sni:%s' "${known[10#$ans - 1]}"; return 0 printf 'sni:%s' "${known[10#$ans - 1]}"; return 0
fi 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 printf ' ничего не выбрано — оставляю автовыбор\n' >&2
return 0 return 0
fi fi
@@ -1022,7 +1039,15 @@ kl_sni_menu() {
[[ -n $d ]] || continue [[ -n $d ]] || continue
if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN
else warn "оставляю прежний домен $DOMAIN — новый не указывает на этот сервер"; fi ;; 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 ;; *) sleep 5 ;;
esac esac
done done
+1 -1
View File
@@ -8,7 +8,7 @@
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём # Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
# останавливается и не заменяет команду hy2. # останавливается и не заменяет команду hy2.
KIT_VERSION="1.2.1.1" KIT_VERSION="1.2.1.9"
cat >&2 <<'MSG' cat >&2 <<'MSG'
Отдельный установщик Hysteria2 больше не поддерживается. Отдельный установщик Hysteria2 больше не поддерживается.
+87 -3
View File
@@ -73,6 +73,9 @@ kit_local_help() {
kit local seams какие места форк правит в файлах автора kit local seams какие места форк правит в файлах автора
kit local masking состояние маскировки (у REALITY должен быть свой домен) kit local masking состояние маскировки (у REALITY должен быть свой домен)
kit local hardening сводка по защите сервера и что ещё можно включить kit local hardening сводка по защите сервера и что ещё можно включить
kit local access доступ к панели, сертификат, ссылки и заглушка — одним экраном
kit local links подписка, QR-код и ссылки по протоколам (как в меню kit)
kit local ports открытые порты: что нужно, что лишнее и как закрыть
kit local uninstall то же, что kit uninstall: снести всё, включая сам kit (SSH не трогает) kit local uninstall то же, что kit uninstall: снести всё, включая сам kit (SSH не трогает)
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница) Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
@@ -420,9 +423,8 @@ Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
EOF EOF
systemctl enable --now unattended-upgrades >/dev/null 2>&1 || kl_warn "служба не включилась — проверьте: systemctl status unattended-upgrades" systemctl enable --now unattended-upgrades >/dev/null 2>&1 || kl_warn "служба не включилась — проверьте: systemctl status unattended-upgrades"
kl_ok "включено: обновления безопасности ставятся сами" # Одна строка: подробности нужны редко, а в выводе установки они превращались в шум.
kl_ok "панель и ядро это не затрагивает — они ставятся не из apt, а из своих релизов" kl_ok "автообновления безопасности включены (панель и ядро это не затрагивает, сервер сам не перезагружается)"
kl_warn "обновление ядра применится только после перезагрузки, сам сервер не перезагружается"
return 0 return 0
} }
@@ -900,6 +902,9 @@ kit_local_cmd() {
upgrades | auto) shift; kit_local_upgrades "$@" ;; upgrades | auto) shift; kit_local_upgrades "$@" ;;
hardening | hard) shift; kit_local_hardening "$@" ;; hardening | hard) shift; kit_local_hardening "$@" ;;
uninstall | remove) shift; kit_local_uninstall "$@" ;; 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 "$@" ;; site | stub | stub-site) shift; kit_local_site "$@" ;;
*) *)
printf 'Неизвестная команда форка: %s\n' "$1" >&2 printf 'Неизвестная команда форка: %s\n' "$1" >&2
@@ -922,6 +927,82 @@ kit_local_cmd() {
# #
# kit local uninstall спросит подтверждение словом «удалить» # kit local uninstall спросит подтверждение словом «удалить»
# kit local uninstall --dry-run только покажет, что будет удалено # 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_local_uninstall() {
# Наш шов: логику удаления отдаём авторской команде «kit uninstall». Она снимает и сам kit, чтобы на # Наш шов: логику удаления отдаём авторской команде «kit uninstall». Она снимает и сам kit, чтобы на
# сервере не осталось следов, поэтому своя реализация здесь больше не нужна — команда оставлена # сервере не осталось следов, поэтому своя реализация здесь больше не нужна — команда оставлена
@@ -987,6 +1068,9 @@ ${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то ж
kit local site [--list | N | --show] сайт-заглушка: случайный ИТ-сайт, список, конкретный, какой стоит 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 local uninstall то же, что kit uninstall: снести установку целиком и сам kit; SSH не трогает
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент) kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
kit fix [--dry-run] безопасные починки: время, службы, конфиги kit fix [--dry-run] безопасные починки: время, службы, конфиги
+9 -7
View File
@@ -12,7 +12,7 @@
set -Eeuo pipefail set -Eeuo pipefail
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
KIT_VERSION="1.2.1.1" KIT_VERSION="1.2.1.9"
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
# Файлы новой версии берём из её тега, а не из меняющейся ветки main. # Файлы новой версии берём из её тега, а не из меняющейся ветки main.
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; } kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
@@ -1342,8 +1342,8 @@ fix_plan() {
cmd_fix() { cmd_fix() {
local dry=no c pass plan local dry=no c pass plan
case ${1:-} in --dry-run) dry=yes ;; "") ;; *) die "kit fix [--dry-run]" ;; esac case ${1:-} in --dry-run) dry=yes ;; "") ;; *) die "kit fix [--dry-run]" ;; esac
echo "${B}kit fix${N} – безопасные исправления (службы, права, автообновление, сертификат, время)" # Наш шов: строку-описание команды не печатаем — при установке это лишний шум,
echo # а смысл виден по результату (подробности — в kit help).
CHECK_QUIET=yes; run_checks; CHECK_QUIET=no CHECK_QUIET=yes; run_checks; CHECK_QUIET=no
if ((${#CHECK_FIX[@]} == 0)); then if ((${#CHECK_FIX[@]} == 0)); then
if ((CHECK_BAD == 0)); then echo "${G}Чинить нечего: всё в порядке.${N}"; return 0; fi if ((CHECK_BAD == 0)); then echo "${G}Чинить нечего: всё в порядке.${N}"; return 0; fi
@@ -3194,12 +3194,12 @@ menu_main() {
echo echo
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \ box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \ "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 echo
menu_status menu_status
update_hint update_hint
echo echo
ask_num "Выбор [0-10]: "; c=$REPLY ask_num "Выбор [0-11]: "; c=$REPLY
case $c in case $c in
1) menu_users ;; 1) menu_users ;;
2) menu_net ;; 2) menu_net ;;
@@ -3209,8 +3209,10 @@ menu_main() {
6) run_action menu_auto; pause ;; 6) run_action menu_auto; pause ;;
7) run_action cmd_backup; pause ;; 7) run_action cmd_backup; pause ;;
8) run_action cmd_version; pause ;; 8) run_action cmd_version; pause ;;
9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;; # Наш шов: пункт 9 — доступ к панели, ссылки на подключения и состояние заглушки в одном месте.
10) run_action cmd_uninstall; pause ;; 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 ;; 0 | "") return 0 ;;
*) echo "Нет такого пункта." ;; *) echo "Нет такого пункта." ;;
esac esac
+31
View File
@@ -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)
+44 -2
View File
@@ -191,6 +191,13 @@ def main():
' echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."\n', ' echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
'авто-шов: replace, строк 1', 'авто-шов: replace, строк 1',
), ),
(
'scripts/kit.sh',
' echo "${B}kit fix${N} – безопасные исправления (службы, права, автообновление, сертификат, время)"\n echo\n',
False,
' # Наш шов: строку-описание команды не печатаем — при установке это лишний шум,\n # а смысл виден по результату (подробности — в kit help).\n',
'авто-шов: replace, строк 2',
),
( (
'scripts/kit.sh', 'scripts/kit.sh',
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"\n', 'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"\n',
@@ -212,6 +219,27 @@ def main():
' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n', ' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
'авто-шов: replace, строк 1', 'авто-шов: 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', 'scripts/kit.sh',
' echo "${B}Полное удаление 3X-UI KIT${N}"\n', ' echo "${B}Полное удаление 3X-UI KIT${N}"\n',
@@ -289,6 +317,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', ' [[ $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', 'авто-шов: 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', 'scripts/3x-ui.sh',
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n", " _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
@@ -307,7 +349,7 @@ def main():
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n', ' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n',
False, 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', 'авто-шов: insert, строк 3',
), ),
( (
@@ -356,7 +398,7 @@ def main():
'scripts/3x-ui.sh', 'scripts/3x-ui.sh',
' issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя. Исправьте причину и запустите скрипт снова."\n', ' issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя. Исправьте причину и запустите скрипт снова."\n',
False, 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', 'авто-шов: replace, строк 1',
), ),
( (
+5
View File
@@ -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)" [[ -z $c || $c -gt $d ]] || die "scripts/3x-ui-local.sh: $fn вызывается (строка $c) раньше, чем определена ($d)"
done done
ok "Все наши функции определены раньше своих вызовов" 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 "Есть незакоммиченные изменения в файлах релиза – сначала коммит." [[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."