Compare commits

..
42 Commits
Author SHA1 Message Date
hacker07 a7b4586bc3 подпись релиза 1.2.25
Синхронизация с автором / sync (push) Failing after 3s
2026-10-14 14:38:07 +03:00
hacker07 cec5246e47 release.sh: понятная подсказка при неверной парольной фразе ключа 2026-10-14 14:36:44 +03:00
hacker07 151d510495 v1.2.25: вопрос про панель (IP/домен) в интерактивном выборе домена; понятнее блок про мягкую защиту 2026-10-14 14:34:27 +03:00
hacker07 f9940026dc подпись релиза 1.2.24
Синхронизация с автором / sync (push) Successful in 1s
2026-10-14 14:08:14 +03:00
hacker07 6d7ebf234e FORK.md: как ведёт себя release.sh — проверки, проба GitHub, парольная фраза ключа 2026-10-14 13:59:31 +03:00
hacker07 41092d4cd1 release.sh: проба связи с GitHub (нет связи — проверки пропускаются сразу, без многосекундных зависаний), warn объявлен 2026-10-14 13:58:11 +03:00
hacker07 78cf3c05dc release.sh: исправлен continue вне цикла (обрывал прогон), connect-timeout 30с и 5 повторов на медленной сети 2026-10-14 13:46:19 +03:00
hacker07 83d9b11c5c release.sh: недоступный GitHub не блокирует подпись (предупреждение вместо стопа), реальное расхождение хешей по-прежнему стоп 2026-10-14 13:40:34 +03:00
hacker07 1fbf27e07c release.sh: повторы и понятная ошибка при сетевых сбоях до GitHub 2026-10-14 13:39:23 +03:00
hacker07 9571e93d46 v1.2.24: свой домен → панель и подписка по домену; предупреждение при панели по IP 2026-10-14 13:36:24 +03:00
hacker07 9dfb4e7957 швы: якоря теперь уникальны и в дереве (чистая проверка) 2026-10-14 13:27:36 +03:00
hacker07 51c35dd2c1 v1.2.23: убран дубль доступов, заглушка сразу наша, ссылки только для нескольких протоколов 2026-10-14 13:26:48 +03:00
hacker07 3f3db5b04c Релиз v1.2.22: подпись
Синхронизация с автором / sync (push) Successful in 3s
2026-10-14 12:51:11 +03:00
hacker07 8fd5572450 v1.2.22: 80 в ufw открывается перед выпуском сертификата; точная причина сбоя 2026-10-14 12:24:03 +03:00
hacker07 01ab468e24 Релиз v1.2.21: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-14 00:18:01 +03:00
hacker07 b20fd6af77 v1.2.21: проверка правил 80/443 в ufw после установки 2026-10-14 00:14:32 +03:00
hacker07 bc18fd354e v1.2.21: порты 80/443 и понятная ошибка сертификата; блок про SSH-порт в конце 2026-10-14 00:14:05 +03:00
hacker07 25e25232b2 Релиз v1.2.20: подпись
Синхронизация с автором / sync (push) Successful in 2s
2026-10-14 00:03:57 +03:00
hacker07 6a9a6ed07e v1.2.20: после сноса меню не показывается; update_hint терпит отсутствие /etc/kit 2026-10-14 00:02:26 +03:00
hacker07 d127a92785 v1.2.19: extra объявлена пустой (set -u валил установку) + правило в FORK.md 2026-10-13 23:59:29 +03:00
hacker07 f38aeefcbc Релиз v1.2.18: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-13 23:53:22 +03:00
hacker07 d0bfb2dc38 v1.2.18: свой домен из меню не подменяется запасным сайтом (не перезаписывать выбор, не проверять TLS своего домена) 2026-10-13 23:52:02 +03:00
hacker07 a0df115616 Релиз v1.2.17: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-13 23:45:13 +03:00
hacker07 f4cf157293 release.sh: гейт «определение раньше вызова» больше не обрывает релиз (set -e + пустой grep) 2026-10-13 23:44:16 +03:00
hacker07 73754dbc01 v1.2.17: меню маскировки первым (меню автора не перебивает) + исправление ask_yes 2026-10-13 23:39:50 +03:00
hacker07 c261d27b80 v1.2.16: возвращён ask_yes; гейт на «определение раньше вызова» 2026-10-13 23:34:02 +03:00
hacker07 fa9e7225ea Релиз v1.2.15: подпись
Синхронизация с автором / sync (push) Successful in 2s
2026-10-13 23:22:16 +03:00
hacker07 4a9e452e3e v1.2.15: меню 9/10/11, снос с добивкой, меню маскировки как просил, быстрый скан, швы пересобраны (63 шва, воспроизведение побайтово) 2026-10-13 23:20:18 +03:00
hacker07 d034e207da docs: README под новые возможности; журнал версий в порядке убывания
Синхронизация с автором / sync (push) Successful in 1s
2026-10-13 22:59:43 +03:00
hacker07 22b9925b0c Релиз v1.2.14: подпись
Синхронизация с автором / sync (push) Successful in 2s
2026-10-13 22:57:13 +03:00
hacker07 9aa93fad37 v1.2.14: меню выбора сайта маскировки по скану + снос не обрывается на set -e 2026-10-13 22:52:33 +03:00
hacker07 5558314849 Релиз v1.2.13: подпись
Синхронизация с автором / sync (push) Successful in 3s
2026-10-13 22:40:27 +03:00
hacker07 b778ce4428 v1.2.13: вернул заголовок раздела действий 2026-10-13 22:39:04 +03:00
hacker07 143b41c84e v1.2.13: вопросы до установки (доступы в самом конце) + гейты по выбранным протоколам 2026-10-13 22:38:23 +03:00
hacker07 01662ff039 v1.2.12: не сканировать соседей по подсети, если XHTTP и MTProto не выбраны 2026-10-13 22:33:23 +03:00
hacker07 04a69f3ae3 Релиз v1.2.11: подпись
Синхронизация с автором / sync (push) Successful in 2s
2026-10-12 22:57:16 +03:00
hacker07 0daaf65b5c v1.2.11: снос убирает kit-limit и находит свои службы по шаблонам 2026-10-12 22:55:04 +03:00
hacker07 64796e0f53 Релиз v1.2.10: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-12 22:47:40 +03:00
hacker07 b7834d5f5c FORK.md: правило — main двигается только с подписанным релизом 2026-10-12 22:41:02 +03:00
hacker07 77f98da99a v1.2.10: убран мусор в выводе установщика + гейт на строки-сироты 2026-10-12 22:40:05 +03:00
hacker07 6bb4f55f33 Релиз v1.2.9: подпись
Синхронизация с автором / sync (push) Successful in 1s
2026-10-12 22:38:17 +03:00
hacker07 6fb7d6c59c v1.2.9: снос убирает всё (список по коду установщика) + проверка после сноса + флаг --clean у установщика 2026-10-12 22:35:57 +03:00
13 changed files with 1294 additions and 374 deletions
+190 -11
View File
@@ -1,15 +1,193 @@
## v1.2.25: вопрос про панель возвращён, блок про защиту переписан понятнее
* **Снова спрашиваем, где открывать панель и подписку.** У автора этот вопрос задавался только когда
домен передан флагом `--domain`; при выборе «ввести свой домен» в меню маскировки его не было, и панель
молча оставалась по IP с самоподписанным сертификатом — то самое «панель на это ругается». Теперь:
выбрали свой домен → сразу вопрос «По IP (как раньше) или По домену» (`https://домен/…`, сертификат
Let's Encrypt, браузер не ругается).
* В отчёте защиты блок «чего здесь нет» больше не ссылается на сторонний проект без пояснений: сказано,
что именно сделано мягче (без полного обновления системы, без автоперезагрузки, без общесистемного
запрета ICMP) и почему — панель и ядро закреплённых версий, сервер не должен вставать сам.
## v1.2.24: сертификат идёт вслед за выбором «домен или IP»
* Выбрали в меню маскировки **свой домен** — панель и подписка тоже переезжают на домен:
`https://ваш.домен/...` и открываются тем же сертификатом Let's Encrypt, без предупреждений браузера.
Раньше сертификат выписывался только для маскировки, а панель оставалась по IP с самоподписанным —
отсюда ругань и в панели, и в подписке.
* Если выбрали соседа или общедоступный сайт (своего домена нет) — панель остаётся по IP, и в итоговом
блоке прямо сказано: сертификат самоподписанный, браузер предупредит; перейти на домен потом —
`kit net panel domain`.
* Домен, который не указывает на этот сервер, используется только как маска (и об этом сообщается),
панель в этом случае остаётся по IP.
## v1.2.23: чистый вывод — без дублей доступов, без авторской заглушки, без лишних ссылок
* **Убран дублирующий блок автора.** Он печатал «Готово! Сервер работает» с адресом панели, логином,
паролем, подпиской, QR-кодом и ссылками — а потом то же самое выводил наш финальный блок.
Теперь авторский блок заканчивается строкой «Установка завершена» и списком предупреждений;
доступы и подписка печатаются один раз, в самом конце.
* **Авторская заглушка больше не ставится.** Наш сайт кладётся до установщика, а его страница из
релиза убирается — иначе она перезаписывала нашу на шаге сборки сайта. Заодно пропадает лишний
шаг «поставить поверх» и его бэкап.
* **Ссылки по протоколам — только если протоколов больше одного.** При одном REALITY печатается
подписка и QR-код, без списка «отдельные ссылки на каждый протокол».
* Самопроверка в конце установки больше не перечисляет чужие протоколы («REALITY, XHTTP и
Hysteria2») — пишет те, что реально созданы. Предупреждение про XHTTP/MTProto показывается только
если эти протоколы выбраны.
## v1.2.22: 80 в ufw открывается прямо перед выпуском сертификата
* Настоящая причина сбоя сертификата (проверено на живом сервере): установщик открывает список
портов в ufw **позже**, чем пробует выпустить сертификат. На свежем сервере ufw в этот момент
разрешает только 22, поэтому проверка Let's Encrypt доходила до сервера и «проглатывалась» —
в логе acme `Timeout during connect (likely firewall problem)`. Провайдер тут ни при чём:
с настоящим слушателем 80 и 443 отвечают снаружи нормально (проверено в том числе с другого сервера).
* Теперь 80/tcp открывается в ufw непосредственно перед `issue_domain_cert`, а формулировка ошибки
в установщике исправлена: сначала проверяем ufw, потом хостера.
## v1.2.21: порты до выпуска сертификата и понятный блок про SSH-порт
* **Порты до сертификата.** Установщик теперь сам открывает 80 и 443 в ufw перед работой и прямо
предупреждает, что такие же правила нужны в файрволе хостинга: частая причина срыва — порт 80
закрыт именно у провайдера, а не в сервере. Если сертификат всё же не выпустился, в выводе
разбирается причина: при `Timeout during connect (likely firewall problem)` пишется, что делать
(открыть 80 в панели хостинга) и предлагается вариант без своего домена (тогда сертификат домена
не нужен, панель остаётся на самоподписанном). После установки 80 и 443 в ufw проверяются ещё раз.
* **Смена порта SSH — с понятным завершением.** Прежние порты запоминаются до смены, и в конце
печатается блок: новый порт, какие прежние ещё открыты, команда проверки входа во втором окне и
готовая команда `kit local ssh --close-old <новый>` для закрытия прежних. Строка про SSH-порт
добавлена и в итоговый блок с доступом к панели и подпиской.
## v1.2.20: после сноса меню не показывается, и нет ошибок про latest-version
* Удаление из меню (`kit`, пункт 9) теперь завершается сразу: печатается «Готово: сервер в состоянии
до установки» — и выход. Раньше меню перерисовывалось на уже удалённом сервере и сыпало
`touch: cannot touch '/etc/kit/latest-version'`.
* `update_hint` больше не пытается что-либо писать, если каталога `/etc/kit` уже нет.
* Входит исправление 1.2.19: `extra` объявлена пустой (иначе `set -u` валил установку на шаге
«[6/6] Проверка» сообщением `extra: unbound variable`).
## v1.2.19: исправлено «extra: unbound variable» при установке
* На середине установки (после проверки протоколов) скрипт падал: `extra: unbound variable`.
Причина — моя правка v1.2.13: присваивание ссылок legacy-подписок завёрнуто в `if`, а переменная
объявлялась как `local extra` без значения. Авторский скрипт работает с `set -u`, поэтому при
невыполненном условии (AWG и MTProto не выбраны) чтение `$extra` валило установку. Объявлена пустой.
* Правило записано в FORK.md: оборачивая авторское присваивание в `if`, объявляй переменную пустой —
`local extra=""`, иначе `set -u` обрывает установку на середине.
## v1.2.18: выбранный «свой домен» больше не подменяется соседом
* Что было: после выбора «ввести свой домен» установщик всё равно брал запасной сайт из подсети.
Две причины, обе в моём коде: (1) выбор перезаписывался сырым значением меню (`own:домен`)
вместо самого домена; (2) домен проверялся на «TLS 1.3 + HTTP/2» — а до установки на нём ещё нет
сертификата, поэтому проверка честно не проходила и срабатывал откат на соседа. Теперь для своего
домена проверка TLS не делается (он указывает на этот сервер — установщик сам выпустит
сертификат Let's Encrypt), а автоподбор и запасной сайт включаются только если вы НЕ выбрали
ничего (автовыбор).
## v1.2.17: меню маскировки идёт ПЕРВЫМ (меню автора больше не перебивает)
* Причина, по которой меню не было видно: **меню спрашивал сам установщик** («Свой домен / Сосед /
Свой сайт») — и до нашего скана подсети дело не доходило. Теперь, когда включено наше меню,
меню автора не показывается вообще: сразу скан, список найденных доменов и выбор — из найденных,
автовыбор, общедоступный или свой домен. Отключить и вернуть поведение автора: `--auto-sni`.
* Ответ меню помечается: если вы ввели домен, который указывает на этот сервер, маскировка идёт
«под свой домен» (сертификат Let's Encrypt выпустит установщик); если не указывает — маскируюсь
под него как под чужой сайт и прямо об этом сообщаю.
* Входит исправление 1.2.16: возвращён `ask_yes` (в 1.2.13–1.2.15 при установке печаталось
`ask_yes: command not found`, и значения брались по умолчанию) + гейт «определение раньше вызова».
## v1.2.16: возвращён ask_yes (вылезало «command not found» при установке)
* При переносе вопросов наверх (v1.2.13) потерялось определение функции `ask_yes` — установщик
писал `ask_yes: command not found` на двух строках и молча брал значения по умолчанию:
ping оставался открытым, автообновления выключались. Скрипт при этом не падал, поэтому
ошибку было видно, но установку она не срывала.
* Почему не поймали: гейт в релизе прогонял только `--dry-run`, а он выходит ДО вопросов.
Добавлена статическая проверка: каждая наша функция должна быть определена раньше первого
вызова (`ask_yes`, `ask_ssh_port`, `ask_protocols`, `interactive`, `clean_previous` и др.).
## v1.2.15: меню выбора сайта в вашем виде, быстрый скан, снос с добивкой
* Меню маскировки выглядит так, как и просили. Если соседи нашлись — их список по номерам,
затем `автовыбор из списка`, `выбрать общедоступный` (отдельным списком) и `ввести свой домен`.
Если в подсети ничего не нашлось — сразу «общедоступный» или «свой домен» (ничего лишнего).
Enter — автовыбор. Общедоступные сайты предлагаются и для REALITY, и для XHTTP/MTProto.
* **Скан подсети стал быстрым.** Было: адреса проверялись по 8 сразу, а найденные имена —
по очереди (до 40 имён × три проверки с сетевыми запросами ≈ минуты). Стало: 32 адреса сразу
и имена проверяются параллельно по 8; таймаут проверки адреса 3 с вместо 4.
* В выводе видно, сколько соседей нашлось (раньше было не понять, работает ли скан).
* После нашего меню автор больше не спрашивает «продолжить с запасным сайтом?» — выбор уже сделан.
* В меню `kit`: **9 — «Удалить всё установленное», 10 — «Выход»** (меню панели `x-ui` переехало
в пункт 11).
* **`kit local uninstall` доводит дело до конца:** после явного списка идёт добивка по шаблонам
(`/etc/kit*`, `/usr/local/lib/kit*`, юниты `kit-*`/`x-ui*`, `/var/www/kit`, сертификаты), повторная
проверка и внятный итог. Появился флаг `--yes` — снести без вопроса.
## v1.2.14: выбор сайта маскировки по результатам скана + снос, который не обрывается
* **Появилось меню маскировки.** Сначала один скан подсети (~250 обращений к 443 соседних адресов),
потом выбор: номер из найденного, `0` — ввести адрес вручную, Enter — автоподбор. Отдельно для
**REALITY** (найденное или свой) и отдельно для **XHTTP/MTProto** (найденное, свой или известный
сайт — список известных расширен Microsoft, AMD, Intel, NVIDIA). Если XHTTP/MTProto не выбраны,
второго меню не будет. Отключить меню: `--auto-sni`.
* **Исправлено: `kit local uninstall` обрывался на середине.** `kit` запускает наши команды
с `set -e`, и подсчёт чужих сайтов nginx (`find | grep | wc -l`, где grep не находит ничего и
возвращает 1) убивал скрипт сразу после остановки служб — файлы и `/usr/local/bin/kit` оставались,
поэтому `kit help` продолжал работать. Теперь снос выполняется с отключённым `set -e` и все
подобные конвейеры защищены; проверка после сноса это подтверждает списком, если что-то осталось.
## v1.2.13: вопросов после показа доступов нет, и никакой работы для невыбранных протоколов
* **Все вопросы — до установщика.** Раньше после установки (когда на экране уже были данные входа
в панель) скрипт спрашивал про ping, автообновления, порт SSH и пароль root — доступы терялись
в потоке вопросов. Теперь спрашиваем всё заранее, а действия выполняем после установки; последнее,
что видит человек, — доступ к панели, подписка и QR-код.
* Автор тянет подписки «-awg» и «-tg» (наследие установок kit < 1.1) всегда — теперь только если
AmneziaWG или MTProto выбраны.
* Наши команды помнят выбор: установщик пишет список протоколов в `/etc/kit/kl-protos`, и
`kit local site` не синхронизирует ответ Hysteria2, если Hysteria2 не ставился (раньше он это делал
и выдавал предупреждение про отсутствующий Hysteria2).
## v1.2.12: не сканируем соседей, когда они не нужны
* Автор подбирал сайты-соседи для XHTTP и MTProto **всегда**, когда маскировка доверенная («панель по IP»)
и всё висит на 443 — независимо от того, выбраны ли эти протоколы. При установке одного REALITY это
давало ~250 обращений к чужим адресам впустую. Теперь скан идёт только если XHTTP или MTProto выбраны;
иначе имена SNI2/SNI3 берутся из общего списка сайтов автора (как и раньше при неудаче скана).
# Что нового в сборке форка
Здесь записи о наших версиях. Файл автора (`CHANGELOG.md`) мы не трогаем — так обновления
автора не конфликтуют с нашими записями.
## v1.2.11: снос знает про kit-limit и находит свои службы сам
## v1.2.6: вопросы при установке
* В списке сноса не было `kit-limit.service` и `kit-limit.timer` (юнит «общий лимит трафика» из kit) —
его создаёт установка, а `kit local uninstall` его не убирал. Из-за этого после сноса оставался
работающий таймер, который раз в 5 минут дёргал уже удалённый `kit` и падал.
* Теперь снос берёт список служб **и из явного перечня, и по шаблонам `x-ui*`/`kit*`** из systemd —
новая служба, добавленная автором позже, не останется незамеченной. Проверка после сноса ищет
по тем же шаблонам и показывает всё найденное.
* Флаг `--clean` у установщика использует тот же список.
## v1.2.10: убран мусор в выводе установщика
* **README.md переписан целиком:** убрано авторское описание с его командой установки, добавлены
актуальная команда, флаги, таблица протоколов, список сайтов-заглушек, команды `kit` и `kit local`,
канал обновлений и оговорки. Две картинки — наши (`manuals/assets/fork-banner.svg` и
`manuals/assets/script-3x-ui-local.svg`, терминал собран из настоящего вывода скрипта).
* Шов в README больше не нужен (`tools/fork_apply.py`): файл наш целиком, как `kit-local.sh`.
* В шапке `scripts/3x-ui-local.sh` один из флагов потерял `#` и **при каждом запуске печаталось
`--dry-run: command not found`** (до `set -e` — потому и не роняло установку, но выглядело как ошибка).
Исправлено, заодно выровнены строки справки.
* В релизный гейт добавлена проверка, которая такое ловит: холостой прогон установщика, чей вывод в stderr
содержит «command not found / syntax error / bad substitution», релиз не собирается.
## v1.2.9: снос действительно убирает всё, повторная установка после сноса
* Список удаляемого в `kit local uninstall` собран заново **по коду установщика** и дополнен тем, чего
не хватало: `/usr/local/lib/kit-sub`, `/root/kit-backup-*` и `/root/x-ui-before-*` (копии kit),
`/var/log/kit-install-check.log`, `/usr/bin/x-ui.new`, строка `include kit-stream.conf` в `nginx.conf`,
симлинки в `sites-enabled`/`streams-enabled`, файл автообновлений безопасности, пользователь `kit-sub`.
Из-за пропущенных файлов установщик после сноса отказывался ставить заново («3X-UI уже установлена»).
* После сноса печатается проверка **теми же условиями, что у установщика**, и список того, что осталось
на диске — вместо бодрого «удалено» вслепую.
* Флаг `--reset-ssh`: снести и наш ssh-дроп-ин (порт возвращается к системному). По умолчанию SSH не трогаем.
* У установщика появился флаг **`--clean`**: убрать прошлую установку и поставить заново. Если установщик
всё-таки упал на «уже установлена», в подсказке прямо сказано запустить с `--clean`.
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
@@ -21,7 +199,6 @@
«удалить», есть `--dry-run`.
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
@@ -69,7 +246,13 @@
* Флаг `--harden` теперь действительно существует (раньше он уходил установщику автора неизвестным
параметром и ронял установку): закрыть ping и включить автообновления без вопросов.
* `--dry-run` показывает, что именно будет спрошено или сделано по умолчанию.
## v1.2.6: вопросы при установке
* **README.md переписан целиком:** убрано авторское описание с его командой установки, добавлены
актуальная команда, флаги, таблица протоколов, список сайтов-заглушек, команды `kit` и `kit local`,
канал обновлений и оговорки. Две картинки — наши (`manuals/assets/fork-banner.svg` и
`manuals/assets/script-3x-ui-local.svg`, терминал собран из настоящего вывода скрипта).
* Шов в README больше не нужен (`tools/fork_apply.py`): файл наш целиком, как `kit-local.sh`.
## v1.2.5: суммы релиза в формате, который понимает kit
* Исправлена ошибка нашего релизного конвейера: на Windows `sha256sum` пишет суммы как
@@ -80,14 +263,12 @@
другой машине.
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
## v1.2.4: проверка релиза отдельной командой
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
можно проверить канал на сервере, ничего не устанавливая.
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
изменений на сервере, чтобы чужой код не запускался раньше проверки.
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
@@ -102,7 +283,6 @@
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
ключа обновляется и здесь, и в `kit`.
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
@@ -119,7 +299,6 @@
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
+31 -1
View File
@@ -124,7 +124,31 @@ Hysteria2 (UDP 443) работают как работали; исходящий
если автор изменил нужную строку — инструмент останавливается и говорит об этом, а не делает
полу-патч.
## Ветки и релизы
## Ветки
`tools/release.sh` — **наш файл** (как README): авторский скрипт подписи мы переписали под себя
(проверка LF, гейт справки, шапка `SHA256SUMS` в двух строках, свой список файлов). Швов к нему нет;
при обновлении из авторского проекта файл конфликтует и решается в нашу пользу (как README).
Важно при правках швов: авторский скрипт работает с `set -u`, поэтому если оборачивать его присваивание
в `if`, переменную нужно объявить пустой (`local extra=""`, а не `local extra`) — иначе при невыполненном
условии любое чтение `$extra` валит установку на середине с «unbound variable».
Проверка корректности швов (делать после каждой правки и после синхронизации с автором):
накатать `tools/fork_apply.py` на чистую копию авторского кода и сверить с нашим деревом — расхождений
быть не должно, кроме наших собственных файлов. Это единственный способ увидеть правки, которых нет в швах.
**`main` двигается только вместе с подписанным релизом.** Серверы читают версию из `main` (`KIT_RAW`),
поэтому в `main` не должно попадать то, что ещё не подписано: иначе `kit update` и однострочник
установки упираются в отсутствующий тег или в неподписанные суммы. Порядок такой:
1. поднять версию и собрать изменения в `dev`;
2. пользователь подписывает (`tools/release.sh`);
3. коммит с `SHA256SUMS` и `SHA256SUMS.sig` → тег `v<версия>` → и только теперь `dev` вливается в `main`.
Проверка перед публикацией в `main`: `curl -fsSL .../raw/tag/v<версия>/VERSION` должен отдавать ту же
версию, а `bash <(curl ... raw/branch/main/scripts/3x-ui-local.sh) --verify-only` — писать, что подпись сошлась.
и релизы
- `upstream` — точная копия ветки автора (`main`), только вперёд.
- `dev` — наша сборка: `upstream` + швы + наши файлы; из неё делаются релизы.
@@ -166,6 +190,12 @@ bash tools/fork-sync.sh # подтянуть автора, перен
# при конфликте — разобрать вручную: инструмент сам ничего не публикует
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом
Скрипт сам приводит переводы строк к LF, проверяет справку и то, что установщик не ругается на пустые
строки, сверяет панель и ядро Xray с официальными хешами GitHub (если GitHub недоступен — предупреждает и
пропускает эти проверки за секунды, а не висит; расхождение хешей при живом GitHub по-прежнему останавливает
выпуск). Далее скрипт ждёт **парольную фразу ключа** — её вводит человек, поэтому запускать надо в окне, где
можно набрать пароль (в PowerShell — да, в фоне — нет).
git add -A && git commit -m "Релиз v$(cat VERSION): …" && git tag v$(cat VERSION)
git push origin dev v$(cat VERSION) upstream
```
+33 -12
View File
@@ -28,9 +28,15 @@
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
```
Без флагов скрипт **спрашивает** всё по ходу: какие протоколы ставить (Enter — REALITY + AmneziaWG-3.1),
какую маскировку и домен, закрывать ли ответ на ping, включать ли автообновления безопасности,
менять ли порт SSH (Enter — оставить 22), менять ли пароль root.
Без флагов скрипт **спрашивает всё заранее, до установки**: какие протоколы ставить (Enter — REALITY +
AmneziaWG-3.1), менять ли порт SSH (Enter — оставить 22), менять ли пароль root, закрывать ли ответ на
ping, включать ли автообновления безопасности. Затем сканирует подсеть и **предлагает выбрать сайт
маскировки** — отдельно для REALITY и, если выбраны XHTTP/MTProto, отдельно для них: номер из найденных
соседей, `0` — ввести адрес вручную, для XHTTP/MTProto ещё и известный сайт (Google, Microsoft, AMD…).
После установщика скрипт **ничего не спрашивает** — только выполняет выбранное, проверяет сервер и в
самом конце показывает то, что нужно унести: **доступ к панели 3X-UI** (адрес, логин, пароль) и
**подписку с QR-кодом** и ссылками по протоколам.
<img alt="Установка одной командой" src="manuals/assets/script-3x-ui-local.svg" width="860">
@@ -67,6 +73,9 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
| `--harden` / `--no-harden` | закрыть ping и включить автообновления безопасности / не трогать это |
| `--yes` | не задавать вопросы (для автоматизации) |
| `--host IP`, `--user имя`, `--port N`, `--restore файл` | адрес в ссылках, имя первого клиента, порт REALITY, восстановление из копии `kit backup` |
| `--clean` | убрать прошлую (оборвавшуюся) установку и поставить заново — SSH не трогает |
| `--auto-sni` | не показывать меню выбора сайта маскировки, подобрать самим (автоподбор автора) |
| `--no-sub` | не показывать в конце доступ к панели, подписку и QR-код |
| `--no-time`, `--no-check`, `--verify-only`, `--dry-run`, `-h` | служебные |
## Что происходит по шагам
@@ -74,15 +83,17 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
1. **Проверка сервера**: root, Debian/Ubuntu, память, место на диске, занятые порты 80/443.
2. **Проверка релиза**: файлы берутся из этого репозитория по тегу, проверяется **подпись `SHA256SUMS`**
нашим ключом и SHA256 самого установщика. Не сошлось — ничего не запускается.
3. **Выбор протоколов** и запуск установщика: панель 3X-UI, выбранные подключения, nginx на 443,
сертификат, сайт-заглушка, служба подписки, правила ufw.
4. **Сайт-заглушка**: ставится случайный правдоподобный ИТ-сайт из нашего набора (10 штук) и
синхронизируется в ответ Hysteria2.
5. **Наши вопросы и правки**: закрыть ответ на ping, включить обновления безопасности, сменить порт
SSH, сменить пароль root.
6. **`kit fix`** — синхронизация времени и безопасные починки.
7. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
`kit local hardening`. В конце — как забрать ссылки и подписку.
3. **Наши вопросы** (до установки): протоколы, порт SSH, пароль root, ping, автообновления.
4. **Установщик**: панель 3X-UI, только выбранные подключения, nginx на 443, сертификат, сайт-заглушка,
служба подписки, правила ufw. Маскировку спрашивает он: свой домен, сосед по подсети или свой сайт.
Если выбран сосед — сначала идёт **скан подсети**, затем меню выбора сайта (см. выше).
5. **Наши действия** (без вопросов): правдоподобный ИТ-сайт-заглушка вместо авторской страницы,
`kit fix` (время и безопасные починки), закрыть ответ на ping, включить обновления безопасности,
перевести SSH на новый порт, сменить пароль root.
6. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
`kit local hardening`.
7. **В самом конце** — **доступ к панели 3X-UI** (адрес, логин, пароль из `/root/3x-ui.txt`) и
**подписка с QR-кодом** и ссылками по протоколам. После них скрипт уже ничего не делает и не спрашивает.
## Протоколы
@@ -110,6 +121,15 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
дополнительных объяснений на клиенте. Маскировку и сайт можно менять потом: `kit net site`,
`kit local site`.
**Сайт маскировки выбираете вы.** После скана подсети установщик спрашивает отдельно для REALITY
(найденный сосед или свой адрес) и отдельно для XHTTP/MTProto (найденный, свой или известный сайт —
список известных расширен Google, Amazon, Samsung, Yahoo, Microsoft, AMD, Intel, NVIDIA). Выбранный
адрес проверяется на TLS 1.3 + HTTP/2. Если XHTTP и MTProto не выбраны, второго вопроса нет.
Для протоколов, которые **не** выбраны, не делается ничего: ни подключений, ни записей в nginx, ни
ссылок в подписке, ни лишних обращений к сети (например, обход соседей запускается только когда он
действительно нужен).
## Сайт-заглушка
На 443 при неизвестном имени отдаётся страница нашего набора: небольшие правдоподобные ИТ-сайты
@@ -163,6 +183,7 @@ kit local site --show # какой стоит сейчас
| `kit local ping off\|on\|status` | ответ на ICMP echo правилом ufw (видно в `ufw status`, отменяется одной командой) |
| `kit local passwd [юзер]`, `--generate` | смена пароля (спрашивает дважды, в логи не попадает) |
| `kit local upgrades on\|off\|status` | обновления безопасности |
| `kit local uninstall` | **снести установку целиком**: панель, службы и файлы kit, nginx и сайт, сертификаты, наши правки системы и правила ufw для 80/443. Спрашивает слово «удалить», есть `--dry-run`. **SSH не трогает** |
| `kit local hardening`, `masking`, `channel`, `seams`, `version` | сводка защиты, состояние маскировки, канал обновлений, какие места форк правит у автора |
У любой правки есть `--dry-run` — показывает план и ничего не меняет. Копии изменённых файлов
+8 -8
View File
@@ -1,9 +1,9 @@
# 3X-UI KIT 1.2.8
# 3X-UI_autoVPS 1.2.8
b0497a26637fd2ef2aae6e512a9afc49f4f6f64b639994c9f029fa4c0977a3a8 scripts/3x-ui.sh
03589cb294acf83be77aeb170eb8adcfc2f901437e437ebc6c1b56bb18579333 scripts/hysteria2.sh
7d328be5414ab544d0b29e89db3ef172a7d650f3ac0b80bade25ac7136709a47 scripts/kit.sh
# 3X-UI KIT 1.2.25
# 3X-UI_autoVPS 1.2.25
9c5f4fe42bff145834c50ac8603ba2119785b658f954251b24d679946f9794c6 scripts/3x-ui.sh
de4738467b80e1f27cf823793e7514b1d56cbdecf029875f9880459d616607bf scripts/hysteria2.sh
e33c8cad20d21572303a80b80ac86a8e8f5a375384cb1831a6877897263b0478 scripts/kit.sh
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
8216a98d82712dd320e6f158212b557c01c5aa94f8099c89ce33fe5fc39f9002 scripts/kit-local.sh
bf961b4e33bfb9e4d18fd9edcea0a71320d6de9a8304c17b6cbeb95aef04affc scripts/3x-ui-local.sh
d2d31534641da01801512a3ec793bc8d57132d4cefa19a5fb503e8995f39eb8f VERSION
2b93ecc5901f7ea2033749a7e395eb7c16ce1d2461a9f3690b7ee407f00056ba scripts/kit-local.sh
8f138044442732093972728c33e5b3fdccf53f4b9ab77f5bcd7cfc1f2c0671c2 scripts/3x-ui-local.sh
24282a40c1a6de0f90841072cc3fa92a1cfec3872672596f6d41ade44979b907 VERSION
+2 -2
View File
@@ -1,6 +1,6 @@
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
AAALc3NoLWVkMjU1MTkAAABAc/dTe+Zehd28Zfczj1mJXWHNZGSAx8Lbr+jqpF7oYcVNq9
ExRrCHUH3GS7IDR2KRJRV8zqVyEDwBDat6ZQhZBQ==
AAALc3NoLWVkMjU1MTkAAABAKvjXzfKjy4irRKB07KffvAMz/wvijprwHBobm9I3EqrCGE
Y+VwreS9CR/3/Mwe3sRMlH/X2iQfG/xOlDiliuBg==
-----END SSH SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
1.2.8
1.2.25
+219 -64
View File
@@ -34,6 +34,8 @@
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
# --no-time не запускать kit fix
# --no-check не запускать в конце kit check
# --clean убрать прошлую установку и поставить заново (SSH не трогает)
# --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети)
# --verify-only только проверить релиз (подпись и SHA256) и выйти
# --dry-run показать план и ничего не делать
# -h | --help эта справка
@@ -42,7 +44,7 @@
set -Eeuo pipefail
KIT_VERSION="1.2.8" # версия сборки форка; должна совпадать с VERSION релиза
KIT_VERSION="1.2.25" # версия сборки форка; должна совпадать с VERSION релиза
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
NS="3x-ui-kit-release"
SIG_ID="releases@3x-ui-kit"
@@ -76,10 +78,12 @@ usage() {
--no-harden не трогать ping и автообновления
--ssh-port N перевести SSH на порт N (прежний закроется после проверки входа)
--yes ни о чём не спрашивать (для автоматизации)
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
не трогать авторскую страницу (то же: --no-site)
--no-sub не показывать в конце подписку, QR-код и ссылки по протоколам
--no-time не запускать kit fix (время и безопасные починки)
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
не трогать авторскую страницу (то же: --no-site)
--no-sub не показывать в конце подписку, доступ к панели и QR-код
--clean снести прошлую установку и поставить заново (если прошлый запуск оборвался)
--auto-sni не предлагать выбор сайта маскировки (автоподбор автора)
--no-time не запускать kit fix (время и безопасные починки)
--no-check не запускать в конце kit check
--verify-only только проверить релиз (подпись и SHA256) и выйти
--dry-run показать план и ничего не делать
@@ -92,6 +96,8 @@ EOF
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
show_sub=yes # в самом конце показать подписку, QR-код и ссылки
clean=no # --clean: убрать прошлую установку перед новой
pick_sni=yes # показывать меню выбора сайта маскировки по результатам скана
while (($#)); do
case $1 in
--harden) harden=yes ;;
@@ -103,6 +109,8 @@ while (($#)); do
--site=*) site=${1#*=} ;;
--no-site) site=off ;;
--no-sub) show_sub=no ;;
--clean) clean=yes ;;
--auto-sni) pick_sni=no ;;
--no-time) fix_time=no ;;
--no-check) do_check=no ;;
--verify-only) verify_only=yes ;;
@@ -134,6 +142,9 @@ if [[ $dry == yes ]]; then
say "план установки (--dry-run: ничего не делаю)"
pre "версия сборки: $KIT_VERSION"
pre "откуда: $GITEA (тег v$KIT_VERSION), подпись проверяется ключом форка"
[[ $clean == yes ]] && pre "прошлая установка: уберу её перед установкой (--clean), SSH не трону"
if [[ $pick_sni == yes ]]; then pre "маскировка: просканирую подсеть и предложу выбрать сайт (--auto-sni — автоподбор)"
else pre "маскировка: подберу сайт сам, без вопросов (--auto-sni)"; fi
if ((${#INSTALLER_ARGS[@]})); then pre "установщик автора: bash scripts/3x-ui.sh ${INSTALLER_ARGS[*]}"; else pre "установщик автора: bash scripts/3x-ui.sh — спросит про маскировку и домен"; fi
if [[ $harden == auto ]]; then
if interactive; then pre "защита: спрошу про ответ на ping и автообновления"
@@ -153,7 +164,7 @@ if [[ $dry == yes ]]; then
esac
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
[[ $show_sub == yes ]] && pre "в самом конце: подписка, QR-код и ссылки по протоколам (--no-sub — не надо)"
[[ $show_sub == yes ]] && pre "в самом конце: доступ к панели 3X-UI, подписка, QR-код и ссылки (--no-sub — не надо)"
echo
say "запуск без --dry-run выполнит всё это на сервере"
exit 0
@@ -275,6 +286,65 @@ ask_protocols() { # печатает итоговый список через з
printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"
}
# ------------------------------------------------------------------ снос прошлой установки (--clean)
# Нужен, когда прошлая установка оборвалась: авторский установщик в таком случае отказывается работать
# («3X-UI уже установлена этим скриптом»). Убираем то, что создаёт установка, и ставим заново.
# SSH не трогаем: ни порт, ни конфиги — иначе можно потерять доступ к серверу.
clean_previous() {
say "убираю прошлую установку (--clean)"
local s p
for s in x-ui kit-sub kit-update.timer kit-update.service kit-limit.timer kit-limit.service \
$(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' | grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
systemctl list-unit-files 2>/dev/null | grep -q "^$s" || continue
systemctl disable --now "$s" >/dev/null 2>&1 || true
printf ' остановлено: %s\n' "$s"
done
for p in /usr/local/x-ui /etc/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /etc/systemd/system/x-ui.service \
/usr/local/bin/kit /usr/local/lib/kit /usr/local/lib/kit-sub /etc/kit /etc/kit-sub \
/etc/systemd/system/kit-sub.service /etc/systemd/system/kit-update.service \
/etc/systemd/system/kit-update.timer /etc/systemd/system/kit-limit.service \
/etc/systemd/system/kit-limit.timer \
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert \
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf /var/www/kit \
/root/3x-ui.txt /root/cert /var/log/kit-install-check.log \
/root/kit-local-backup-* /root/kit-backup-* /root/x-ui-before-sni-* /root/x-ui-before-update-*; do
[[ -e $p ]] || continue
rm -rf $p && printf ' удалено: %s\n' "$p"
done
if [[ -f /etc/nginx/nginx.conf ]] && grep -q 'kit-stream\.conf' /etc/nginx/nginx.conf; then
sed -i '/kit-stream\.conf/d' /etc/nginx/nginx.conf
printf ' из nginx.conf убрана наша строка включения\n'
fi
if id -u kit-sub >/dev/null 2>&1; then
pkill -u kit-sub >/dev/null 2>&1 || true
userdel -r kit-sub >/dev/null 2>&1 || true
printf ' удалён пользователь kit-sub\n'
fi
systemctl daemon-reload
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
warn "часть файлов прошлой установки осталась — проверьте: ls -la /usr/local/x-ui /root/3x-ui.txt"
else
printf ' готово: место под установку свободно\n'
fi
}
installer_failed() {
if grep -q "likely firewall problem" /var/log/kit-domain-cert.log 2>/dev/null \
|| grep -q "Timeout during connect" /var/log/3x-ui-install.log 2>/dev/null; then
printf '\n%s!%s %s\n' "$Y" "$N" \
"Let's Encrypt не смог достучаться до порта 80 (в логе acme «Timeout during connect»)." >&2
printf ' Проверьте по порядку: открыт ли 80 в ufw (скрипт делает это сам) и не режет ли его хостер.\n' >&2
printf ' Либо ставьте без своего домена: в меню маскировки выберите «общедоступный сайт» —\n' >&2
printf ' сертификат для домена тогда не нужен, панель останется на самоподписанном.\n' >&2
fi
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
printf '\n%s!%s %s\n' "$Y" "$N" \
"На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2
fi
die "установщик остановился с ошибкой — смотрите его вывод выше"
}
# ------------------------------------------------------------------ установка
if [[ -z $proto_flag ]]; then
@@ -285,18 +355,9 @@ else
fi
say "протоколы: $protos — под остальные не создаётся ничего"
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
if [[ -r /dev/tty ]]; then
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || die "установщик остановился с ошибкой — смотрите его вывод выше"
else
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || die "установщик остановился с ошибкой (нет терминала — вопросы задать некому)"
fi
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
# ------------------------------------------------------------------ наши дополнения
# ------------------------------------------------------------------ вопросы (всё спросим ДО установки)
# После установщика на экране появляются доступы к панели: там вопросы уже неуместны. Поэтому
# спрашиваем всё заранее, а действия выполняем после установки (см. «наши дополнения» ниже).
ask_yes() { # "вопрос" y|n — по умолчанию
local q=$1 def=$2 ans=""
@@ -308,43 +369,6 @@ ask_yes() { # "вопрос" y|n — по умолчанию
[[ $ans =~ ^(y|yes|д|да)$ ]]
}
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
case $site in
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
random)
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
;;
*)
say "ставлю сайт-заглушку №$site"
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
;;
esac
if [[ $fix_time == yes ]]; then
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
fi
# ping и автообновления: --harden/--no-harden решают без вопросов, иначе спрашиваем
want_ping=$harden; want_update=$harden
[[ $want_ping == auto ]] && want_ping=yes
[[ $want_update == auto ]] && want_update=yes
if [[ $harden == auto ]] && interactive; then
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
fi
if [[ $want_ping == yes ]]; then
say "закрываю ответ на ping (правилом ufw)"
kit local ping off || warn "не удалось — повторите: kit local ping off"
fi
if [[ $want_update == yes ]]; then
say "включаю автообновления безопасности"
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
fi
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
ssh_ports_now() {
@@ -381,19 +405,126 @@ ask_ssh_port() { # пусто — оставить как есть; иначе
warn "оставляю прежний порт ($cur)"
}
want_ping=$harden; want_update=$harden
[[ $want_ping == auto ]] && want_ping=yes
[[ $want_update == auto ]] && want_update=yes
if [[ $harden == auto ]] && interactive; then
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
fi
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
ssh_ports_before=$(ssh_ports_now) # для подсказки в конце: какие порты закрывать
want_passwd=no
if interactive; then
if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi
fi
[[ $clean == yes ]] && clean_previous
# Порты для сертификата и маскировки: открываем правила ufw ДО установщика и говорим, где смотреть,
# если сертификат не выпустится (частая причина — свой файрвол у хостера, а не ufw).
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then
for _p in 80/tcp 443/tcp; do ufw allow "$_p" >/dev/null 2>&1 || true; done
say "порты 80 и 443 открыты в ufw (нужны для сертификата и работы маскировки)"
fi
printf ' %sесли у хостера свой файрвол — 80 и 443 должны быть разрешены и там.%s\n' "$D" "$N"
printf ' %sпризнак закрытого 80: сертификат не выпускается, в логе acme «Timeout during connect (likely firewall problem)»%s\n' "$D" "$N"
# Меню выбора сайта маскировки: сканируем подсеть один раз и предлагаем выбрать сайт отдельно для
# REALITY и (если выбраны) для XHTTP/MTProto. Отключается флагом --auto-sni (тогда автоподбор автора).
if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
if [[ -r /dev/tty ]]; then
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || installer_failed
else
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || installer_failed
fi
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
# Ставим нашу заглушку СРАЗУ (а не поверх авторской): страницу кладём до установщика, а его собственную
# страницу из релиза убираем — иначе она перезапишет нашу на шаге сборки сайта.
stub_staged=no
if [[ $site != off ]]; then
stub_n=""
case $site in
random) stub_n=$(( RANDOM % 10 )) ;;
*) [[ $site =~ ^[0-9]+$ ]] && stub_n=$((10#$site - 1)) ;;
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 \
&& [[ -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)"
fi
fi
fi
# Запоминаем, какие протоколы выбраны: наши команды (kit local site и др.) смотрят сюда и не
# делают ничего для протоколов, которых в этой установке нет.
printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos 2>/dev/null || true
# ------------------------------------------------------------------ наши дополнения (только действия)
# Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем.
# 443 нужен для маскировки и VPN, 80 — для выпуска сертификата: проверяем правила ufw ещё раз.
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then
for _p in 80/tcp 443/tcp; do ufw allow "$_p" >/dev/null 2>&1 || true; done
fi
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
case ${stub_staged:-no} in
yes) say "заглушка уже стоит (поставлена при установке, Hysteria2 синхронизирован тем же файлом)" ;;
*) case $site in
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
random)
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
;;
*)
say "ставлю сайт-заглушку №$site"
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
;;
esac ;;
esac
if [[ $fix_time == yes ]]; then
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
fi
# Действия по ответам, которые были заданы ДО установщика (см. блок вопросов выше).
if [[ $want_ping == yes ]]; then
say "закрываю ответ на ping (правилом ufw)"
kit local ping off || warn "не удалось — повторите: kit local ping off"
fi
if [[ $want_update == yes ]]; then
say "включаю автообновления безопасности"
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
fi
if [[ $want_passwd == yes ]]; then
say "меняю пароль root"
kit local passwd || warn "не получилось — повторите: kit local passwd"
fi
# порт SSH: новый порт добавляем, прежний остаётся рабочим до вашей проверки входа.
if [[ -n $ssh_port ]]; then
say "перевожу SSH на порт $ssh_port: новый добавляю, прежний пока остаётся рабочим"
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
fi
# пароль root: в флагах его нет намеренно — значение в командной строке видно в списке процессов
if interactive; then
if ask_yes "Сменить пароль root прямо сейчас?" n; then
kit local passwd || warn "не получилось — повторите: kit local passwd"
fi
fi
# ------------------------------------------------------------------ проверка
if [[ $do_check == yes ]]; then
@@ -411,6 +542,19 @@ ${G}${B}Готово.${N} Сервер поставлен из нашей сбо
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
EOF
# ------------------------------------------------------------------ SSH: как закрыть прежние порты
if [[ -n ${ssh_port:-} ]]; then
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, терминалы, мониторинг
EOF
fi
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
if [[ $show_sub == yes ]]; then
@@ -421,6 +565,10 @@ if [[ $show_sub == yes ]]; then
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-код и доступы
@@ -438,8 +586,15 @@ if [[ $show_sub == yes ]]; then
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
if ! kit user link "$sub_user" --all; then
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
[[ -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"
+150 -57
View File
@@ -30,7 +30,7 @@ wait_apt_idle() {
XUI_VERSION="v3.9.0"
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
KIT_VERSION="1.2.8"
KIT_VERSION="1.2.25"
# 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,
@@ -50,7 +50,8 @@ RESULT=/root/3x-ui.txt
XUI_ENV=/etc/x-ui/install-result.env
# Сайты для маскировки REALITY: нужны TLS 1.3 и HTTP/2. Берём первый доступный.
# Apple, iCloud, Microsoft и домены .ru сам Xray не советует – их тут нет.
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)
# Наш шов: добавлены известные сайты (Microsoft, AMD, Intel, NVIDIA) — их можно выбрать вручную.
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com www.microsoft.com www.amd.com www.intel.com www.nvidia.com)
ALL_PROTOS=(reality reality2 hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
# Обычный WireGuard легко распознаётся сетевым оборудованием и работает нестабильно
@@ -163,26 +164,32 @@ same_net() { # имя
# Имена из сертификатов соседних адресов /24: заходим на 443 без имени и читаем subjectAltName.
nearby_scan() { # основа "a.b.c"
local me; me=$(host_ip)
seq 1 254 | xargs -P 8 -I{} bash -c '
# Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.
seq 1 254 | xargs -P 32 -I{} bash -c '
ip=$1; [ "$ip" = "$2" ] && exit 0
echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true
echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true
}
# Подходящие сайты: сначала в /24 сервера, если пусто – во втором /24 того же /23. До 8 имён.
nearby_sites() {
local me a b c base n names found=0
local me a b c base n names out
set +e; trap - ERR # выполняется в подпроцессе: сбой одной проверки не должен обрывать поиск
me=$(host_ip)
[[ $me =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 0
IFS=. read -r a b c _ <<<"$me"
# Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная
# причина «долго сканирует». Функции передаём в подпроцессы через export -f.
export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip
export HOST
for base in "$a.$b.$c" "$a.$b.$((c ^ 1))"; do
names=$(nearby_scan "$base")
for n in $names; do
[[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
if same_net "$n" && sni_alive "$n" && sni_trusted "$n" && sni_quality "$n"; then echo "$n"; found=$((found + 1)); ((found >= 8)) && return 0; fi
done
((found > 0)) && return 0
names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)
[[ -n $names ]] || continue
out=$(while IFS= read -r n; do
[[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
printf '%s\n' "$n"
done <<<"$names" | xargs -P 8 -I{} bash -c 'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\n" "$1"' _ {} 2>/dev/null | head -8)
[[ -n $out ]] && { printf '%s\n' "$out"; return 0; }
done
return 0
}
@@ -537,28 +544,132 @@ main() {
later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."
fi
# ---------------------------------------------------------------------------
# Наш шов: выбор сайта маскировки по результатам скана подсети.
# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).
# role — подпись для человека: REALITY или XHTTP/MTProto.
kl_sni_menu() {
local role=$1; shift
local -a found=("$@")
local -a known=("${SNI_CANDIDATES[@]}")
local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try
n=${#found[@]}
printf '\n%s%s%s\n' "$B" "$role" "$N" >&2
if ((n)); then
printf 'Найдено в подсети:\n\n' >&2
for i in "${!found[@]}"; do printf ' %d - %s\n' "$((i + 1))" "${found[i]}" >&2; done
auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))
printf ' %d - автовыбор из списка\n' "$auto_idx" >&2
printf ' %d - выбрать общедоступный (например %s, %s)\n' "$known_idx" "${known[0]}" "${known[1]}" >&2
printf ' %d - ввести свой домен\n\n' "$own_idx" >&2
else
auto_idx=0; known_idx=1; own_idx=2
printf 'В подсети доменов не найдено, выберите:\n\n' >&2
printf ' 1 - общедоступный (например %s, %s)\n' "${known[0]}" "${known[1]}" >&2
printf ' 2 - ввести свой домен\n\n' >&2
fi
printf 'Ваш выбор (Enter — автовыбор): ' >&2
for try in 1 2 3; do
ans=""
read -r -t 300 ans <"$src" 2>/dev/null || true
ans=${ans//[[:space:]]/}
[[ -z $ans ]] && return 0
[[ $ans =~ ^[0-9]+$ ]] || { printf ' введите номер из списка\n' >&2; continue; }
i=$((10#$ans))
if ((n)) && ((i >= 1 && i <= n)); then printf 'sni:%s' "${found[i - 1]}"; return 0; fi
((i == auto_idx)) && return 0
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 ' 0 - назад\nВаш выбор: ' >&2
ans=""
read -r -t 300 ans <"$src" 2>/dev/null || true
ans=${ans//[[:space:]]/}
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then
printf 'sni:%s' "${known[10#$ans - 1]}"; return 0
fi
printf ' ничего не выбрано — оставляю автовыбор\n' >&2
return 0
fi
if ((i == own_idx)); then
printf ' адрес сайта (например vpn.example.com): ' >&2
ans=""
read -r -t 300 ans <"$src" 2>/dev/null || true
ans=${ans//[[:space:]]/}
[[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] && { printf 'own:%s' "$ans"; return 0; }
printf ' не похоже на адрес сайта — оставляю автовыбор\n' >&2
return 0
fi
printf ' такого номера нет\n' >&2
done
return 0
}
# Маскировка: из флагов, по вопросу пользователю или автоматически (сосед по подсети).
if [[ -n $DOMAIN ]]; then
[[ $PANEL_SSL == ip ]] || die "Свой домен работает с сертификатом панели Let's Encrypt на IP: освободите порт 80 и не указывайте --cert, --key и --panel-ssl none (сертификат для домена установщик получит сам)."
domain_points_here "$DOMAIN" || die "Исправьте A-запись домена и запустите скрипт снова (DNS обновляется от нескольких минут до нескольких часов)."
elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
# Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе
# человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.
elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : </dev/tty; } 2>/dev/null; then
choose_masking
fi
if [[ -n $DOMAIN ]]; then
SNI=$DOMAIN
elif [[ -z $SNI ]]; then
echo
echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"
echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."
echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."
local -a nb=()
mapfile -t nb < <(nearby_sites | shuf)
NB_ALL=("${nb[@]}")
if ((${#nb[@]})); then
SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}
# Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.
if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then
local pick pick2 own_sni=no
pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})
case $pick in
own:*)
own_sni=yes
DOMAIN=${pick#own:}
if domain_points_here "$DOMAIN" 2>/dev/null; then
SNI=$DOMAIN
ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let's Encrypt выпустит установщик)"
# Наш шов: спрашиваем, где открывать панель и подписку, и при интерактивном вводе домена тоже.
# У автора этот вопрос задавался только при домене из флага --domain, а при выборе «ввести свой
# домен» в меню маскировки — нет, и панель молча оставалась по IP с самоподписанным сертификатом.
panel_on_choice
else
SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт, панель и подписка остаются по IP"
fi
;;
sni:*) SNI=${pick#sni:} ;;
esac
if [[ -n $SNI ]]; then
if [[ $own_sni == no ]] && ! sni_ok "$SNI"; then
warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"
SNI="${nb[0]:-$SNI}"
fi
ok "маскировка REALITY: ${B}$SNI${N}"
fi
if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})
[[ $pick2 == sni:* ]] && pick2=${pick2#sni:}
[[ $pick2 == own:* ]] && pick2=${pick2#own:}
[[ -n $pick2 ]] && SNI2=$pick2
[[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"
fi
fi
if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"
else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi
if [[ -z $SNI ]] && ((${#nb[@]})); then
SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}
ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"
else
elif [[ -z $SNI ]]; then
warn "В подсети подходящего сайта не нашёл."
echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com"
if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then
: # сайт уже выбран в нашем меню — вопрос автора не нужен
elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
ask_tty " Продолжить с запасным сайтом из списка? [y/N] " || true
[[ $REPLY =~ ^[yYдД]$ ]] || die "Остановился по вашей просьбе. Ставить можно снова в любой момент."
else
@@ -578,7 +689,10 @@ main() {
LINK_HOST=${DOMAIN:-$HOST}
[[ $PANEL_ON == ip || -n $DOMAIN ]] || { PANEL_ON=ip; later "--panel-on domain нужен со своим доменом: панель и подписка открываются по IP."; }
# Режим «всё на 443»: XHTTP и MTProto различаются по имени сайта, берём и им соседей по подсети.
if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then
# Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей
# по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.
if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \
&& [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
local -a nb2=()
echo
if ((${#NB_ALL[@]})); then
@@ -602,7 +716,9 @@ main() {
done
SNI2=${SNI2:-$SNI}; SNI3=${SNI3:-www.cloudflare.com}
if [[ $TRUSTED == yes && $multi == no && ( $SNI2 =~ $SNI_BRAND_RE || $SNI3 =~ $SNI_BRAND_RE ) ]]; then
later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"
# Наш шов: про XHTTP и MTProto говорим только если они выбраны — иначе это шум.
[[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]] && \
later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"
fi
step "Пакеты"
@@ -732,7 +848,7 @@ main() {
chk=$(/usr/local/bin/kit check --deep 2>&1) && { bad=no; break; }
done
if [[ $bad == no ]]; then
ok "подключения работают: REALITY, XHTTP и Hysteria2 проверены клиентом с самого сервера"
ok "подключения работают (проверено клиентом с самого сервера): ${CREATED[*]:-нет}"
else
install -m 600 /dev/null /var/log/kit-install-check.log
printf '%s\n' "$chk" >/var/log/kit-install-check.log
@@ -753,8 +869,12 @@ main() {
fi
links=$(sub_links "$SUBID")
# У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg».
local extra
extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)
local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны
# Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,
# если эти протоколы не выбраны (никаких лишних обращений к панели).
if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)
fi
[[ -n $extra ]] && links+=$'\n'"$extra"
AWG_LINKS=$(grep '^vpn://' <<<"$links" || true)
# MTProto слушает localhost, а клиенты приходят через nginx на 443.
@@ -773,33 +893,7 @@ main() {
} >"$RESULT"
echo
echo "${G}${B}Готово! Сервер работает.${N}"
[[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let's Encrypt продлевается сам."
[[ $SELF_IP_CERT == yes ]] && later "Сертификат на IP самоподписанный: пользуйтесь ссылками на отдельные подключения, подписка в приложениях может не открыться."
echo
echo "${B}ПАНЕЛЬ${N}"
if [[ $TRUSTED == yes ]]; then
echo " $panel_url"
else
echo " Только через SSH-туннель. На вашем компьютере:"
echo " ssh -L $XUI_PANEL_PORT:127.0.0.1:$XUI_PANEL_PORT root@$HOST"
echo " Затем в браузере: http://127.0.0.1:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH"
fi
echo " Логин: ${B}$XUI_USERNAME${N} Пароль: ${B}$XUI_PASSWORD${N}"
echo " ${D}(сохранено в $RESULT, файл виден только root)${N}"
echo
if [[ $TRUSTED == yes ]]; then
echo "${B}ПОДПИСКА${N} – одна ссылка для всех протоколов"
echo " ${B}$SUB_URL${N}"
echo " Вставьте в Happ, Hiddify, Karing, v2rayN, Clash Verge или FlClash."
qrencode -t ANSIUTF8 -m 1 "$SUB_URL" || true
echo
KIT_NO_QR=1 /usr/local/bin/kit __links "$NAME" "$SUBID" || true
else
/usr/local/bin/kit __links "$NAME" "$SUBID" || true
fi
echo
echo "${D}Порты, выключение и включение протоколов: ${N}${B}kit net${N}"
echo "${G}${B}Установка завершена.${N} Доступы к панели, подписка и QR-код — в самом конце вывода."
if ((${#WARNINGS[@]})); then
echo
echo "${B}ВНИМАНИЕ${N}"
@@ -810,12 +904,7 @@ main() {
done
((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"
fi
echo
echo "${B}ДАЛЬШЕ${N}"
echo " ${B}kit${N} меню управления"
echo " ${B}kit user add имя --gb 50 --days 30${N} добавить пользователя"
echo " ${B}kit check --deep${N} проверить, что всё работает"
echo " ${B}kit backup${N} копия сервера (держите в надёжном месте)"
}
# Официальный установщик 3X-UI закреплённой версии, сверенный по SHA256.
@@ -2047,7 +2136,11 @@ PY
install -d -m 755 /var/www/kit
[[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html
if [[ $SINGLE == yes ]]; then
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }
# Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов
# позже (после сборки nginx), поэтому на свежем сервере проверка Let's Encrypt доходила до сервера и
# «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».
command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }
setup_nginx
fi
+1 -1
View File
@@ -8,7 +8,7 @@
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
# останавливается и не заменяет команду hy2.
KIT_VERSION="1.2.8"
KIT_VERSION="1.2.25"
cat >&2 <<'MSG'
Отдельный установщик Hysteria2 больше не поддерживается.
+117 -47
View File
@@ -443,12 +443,14 @@ kit_local_hardening() {
cat <<'EOF'
Чего здесь НЕТ и почему (в отличие от vps-setup):
Что сделано намеренно МЯГЧЕ, чем в популярных скриптах первичной настройки:
* apt full-upgrade — тянет систему вперёд и может сломать закреплённые панель и ядро;
вместо него включены обновления безопасности;
* автоматическая перезагрузка после правок — сервер не должен вставать без вашего решения;
* общесистемный icmp_echo_ignore_all — правило ufw видно в `ufw status` и отменяется одной
командой, не глушит диагностику внутренних интерфейсов.
* общесистемный запрет ICMP для всех интерфейсов — здесь он выключен на уровне ufw: правило видно
в `ufw status` и отменяется одной командой, а внутренняя диагностика не глушится.
Причина одна: перечисленное может увести систему вперёд или выключить сервер, а панель и ядро у нас
закреплённых версий.
Что включить при желании:
kit local ssh 2222 добавить порт SSH
@@ -874,7 +876,9 @@ kit_local_site() {
# Порт 443 отдаёт файл через nginx (перезагрузка не нужна). UDP-порт Hysteria2 держит копию
# страницы внутри подключения — её надо обновить, иначе ответы разойдутся.
if kl_have kit; then
if kit net masq on >/dev/null 2>&1; then
if [[ -f /etc/kit/kl-protos ]] && ! grep -qE '(^|[ ,])hy2([ ,]|$)' /etc/kit/kl-protos; then
kl_ok "Hysteria2 в этой установке не ставился — синхронизировать нечего"
elif kit net masq on >/dev/null 2>&1; then
kl_ok "Hysteria2 отвечает той же страницей"
else
kl_warn "Hysteria2 не синхронизирован (может, его нет в этой установке) — проверьте: kit net masq on"
@@ -920,40 +924,55 @@ kit_local_cmd() {
# kit local uninstall --dry-run только покажет, что будет удалено
kit_local_uninstall() {
kl_args "$@"
local arg=${KL_REST[0]:-} ans answer
local arg=${KL_REST[0]:-} ans found
local reset_ssh=no i=0
case $arg in
"") ;;
--dry-run) KL_DRY=yes ;;
*) kl_die "kit local uninstall [--dry-run]"; return 1 ;;
--yes) KIT_LOCAL_UNINSTALL_CONFIRMED=yes ;;
--reset-ssh) reset_ssh=yes ;;
*) kl_die "kit local uninstall [--dry-run] [--yes] [--reset-ssh]"; return 1 ;;
esac
local -a svc=(x-ui kit-sub kit-update.timer kit-update.service)
local -a files=(
/usr/local/x-ui /etc/x-ui /usr/bin/x-ui /etc/systemd/system/x-ui.service
/usr/local/bin/kit /usr/local/lib/kit /etc/kit /etc/kit-sub
# Службы: и явный список, и всё, что найдётся по нашим шаблонам (чтобы новые юниты kit не оставались).
# kit запускает наши команды с `set -e`: при уборке это недопустимо — любая «не найденная» служба
# или пустой grep обрывали бы снос на середине (так и было: файлы оставались). Убираем errexit.
set +e
local -a svc=(x-ui.service kit-sub.service kit-update.timer kit-update.service kit-limit.timer kit-limit.service)
local u
for u in $(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' \
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
[[ " ${svc[*]} " == *" $u "* ]] || svc+=("$u")
done
# Список собран по коду установщика (scripts/3x-ui.sh) и нашего слоя: убираем всё, что они создают.
local -a paths=(
/usr/local/x-ui /etc/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /etc/systemd/system/x-ui.service
/usr/local/bin/kit /usr/local/lib/kit /usr/local/lib/kit-sub /etc/kit /etc/kit-sub
/etc/systemd/system/kit-sub.service /etc/systemd/system/kit-update.service
/etc/systemd/system/kit-update.timer
/etc/systemd/system/kit-update.timer /etc/systemd/system/kit-limit.service
/etc/systemd/system/kit-limit.timer
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf
/var/www/kit /root/3x-ui.txt /root/cert /root/.acme.sh /etc/letsencrypt
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf /var/www/kit
/root/3x-ui.txt /root/cert /root/.acme.sh /etc/letsencrypt /var/log/kit-install-check.log
/etc/sysctl.d/99-kit-local-icmp.conf /etc/apt/apt.conf.d/52-kit-local-upgrades
/etc/apt/apt.conf.d/20auto-upgrades
)
local -a backups=()
local b
for b in /root/kit-local-backup-*; do [[ -e $b ]] && backups+=("$b"); done
local -a masks=(/root/kit-local-backup-* /root/kit-backup-* /root/x-ui-before-sni-* /root/x-ui-before-update-*)
local -a extra=()
local m
for m in "${masks[@]}"; do for f in $m; do [[ -e $f ]] && extra+=("$f"); done; done
kl_step "Будет удалено"
printf ' службы: %s\n' "${svc[*]}"
printf ' файлы и каталоги: %s\n' "${files[*]}"
[[ ${#backups[@]} -eq 0 ]] || printf ' наши копии изменённых файлов: %s\n' "${backups[*]}"
printf ' правила ufw для 80/tcp и 443/tcp (правило для SSH не трогаем)\n'
printf ' ответ на ping вернём (снимем наше правило)\n'
if kl_ufw_active; then
if [[ $(grep -c '^# -A ufw-before-input -p icmp --icmp-type echo-request' /etc/ufw/before.rules 2>/dev/null || echo 0) -gt 0 ]]; then
printf ' %s\n' "правило ICMP наше (строка закомментирована) — вернём как было"
fi
printf ' файлы и каталоги: %s\n' "${paths[*]}"
[[ ${#extra[@]} -eq 0 ]] || printf ' копии и журналы: %s\n' "${extra[*]}"
printf ' пользователь kit-sub (если создавался)\n'
printf ' правила ufw для 80/tcp, 443/tcp и 443/udp; ответ на ping возвращается\n'
kl_ok "SSH не трогаем: ни порт, ни конфиги, ни правила — иначе можно потерять доступ"
if [[ $reset_ssh == yes ]]; then
kl_warn "--reset-ssh: снесу и наш ssh-дроп-ин (порт вернётся к системному). Убедитесь, что вход по нему работает!"
fi
kl_ok "перезагрузка не потребуется, SSH и его порт не затрагиваются"
if kl_dry; then
printf '\n (dry-run) ничего не делаю. Запустить по-настоящему: kit local uninstall\n'
@@ -970,51 +989,102 @@ kit_local_uninstall() {
kl_step "Останавливаю и отключаю службы"
local s
for s in "${svc[@]}"; do
systemctl disable --now "$s" >/dev/null 2>&1 && kl_ok "остановлено и отключено: $s" || kl_warn "служба $s не найдена (уже удалена?)"
if systemctl list-unit-files 2>/dev/null | grep -q "^$s"; then
systemctl disable --now "$s" >/dev/null 2>&1 && kl_ok "остановлено и отключено: $s" || kl_warn "не смог остановить: $s"
fi
done
if kl_have nginx; then
local others
others=$(find /etc/nginx/conf.d /etc/nginx/sites-enabled -type f -o -type l 2>/dev/null | grep -vE 'kit\.conf$|default$' | wc -l)
others=$( { find /etc/nginx/conf.d /etc/nginx/sites-enabled /etc/nginx/streams-enabled -mindepth 1 2>/dev/null || true; } \
| { grep -vE 'kit\.conf$|(^|/)default$' || true; } | wc -l)
if [[ $others -eq 0 ]]; then
kl_run systemctl disable --now nginx >/dev/null 2>&1 && kl_ok "nginx остановлен (своих сайтов, кроме нашего, не было)"
systemctl disable --now nginx >/dev/null 2>&1 && kl_ok "nginx остановлен (чужих сайтов в нём не было)"
KL_NGINX_PURGE=yes
else
kl_ok "nginx оставляю: в нём есть другие сайты ($others) — уберу только наши конфиги"
kl_ok "nginx оставляю: в нём есть другие сайты — уберу только наши строки и файлы"
KL_NGINX_PURGE=no
fi
fi
kl_step "Убираю файлы"
for f in "${files[@]}"; do
local f
for f in "${paths[@]}"; do
[[ -e $f ]] || continue
kl_run rm -rf "$f" && kl_ok "удалено: $f"
rm -rf "$f" && kl_ok "удалено: $f" || kl_warn "не удалось удалить: $f"
done
for f in "${backups[@]}"; do kl_run rm -rf "$f" && kl_ok "удалено: $f"; done
[[ ${KL_NGINX_PURGE:-no} == yes ]] && { kl_run apt-get remove -y --purge nginx nginx-common >/dev/null 2>&1 && kl_ok "пакет nginx удалён"; }
for f in ${extra[@]+"${extra[@]}"}; do rm -rf "$f" && kl_ok "удалено: $f"; done
# Наши строки в чужом nginx: include stream-конфига и симлинки, если были.
if [[ -f /etc/nginx/nginx.conf ]] && grep -q 'kit-stream.conf' /etc/nginx/nginx.conf; then
kl_backup /etc/nginx/nginx.conf
sed -i '/kit-stream\.conf/d' /etc/nginx/nginx.conf && kl_ok "из nginx.conf убрана наша строка включения"
fi
local lnk
for lnk in /etc/nginx/sites-enabled/*kit* /etc/nginx/streams-enabled/*kit*; do
[[ -e $lnk || -L $lnk ]] && rm -f "$lnk" && kl_ok "удалена ссылка: $lnk"
done
if [[ ${KL_NGINX_PURGE:-no} == yes ]]; then
apt-get remove -y --purge nginx nginx-common >/dev/null 2>&1 && kl_ok "пакет nginx удалён"
fi
# Пользователь подписки (работает не от root).
if id -u kit-sub >/dev/null 2>&1; then
pkill -u kit-sub >/dev/null 2>&1 || true
userdel -r kit-sub >/dev/null 2>&1 && kl_ok "пользователь kit-sub удалён" || kl_warn "не смог удалить пользователя kit-sub"
fi
kl_step "Возвращаю систему"
kl_run systemctl daemon-reload
if kl_ufw_active; then
systemctl daemon-reload
if [[ $reset_ssh == yes && -f /etc/systemd/system/ssh.socket.d/99-kit-local-port.conf ]]; then
rm -f /etc/systemd/system/ssh.socket.d/99-kit-local-port.conf
rm -f /etc/ssh/sshd_config.d/99-kit-local-port.conf
systemctl daemon-reload
kl_run systemctl restart ssh.socket 2>/dev/null || kl_run systemctl restart ssh 2>/dev/null || true
kl_warn "ssh-дроп-ин удалён: порт вернулся к системному. Проверьте вход в НОВОМ окне!"
fi
if kl_have ufw && kl_ufw_active; then
local p
for p in 80/tcp 443/tcp 443/udp; do kl_run ufw delete allow "$p" >/dev/null 2>&1 && kl_ok "правило ufw снято: $p"; done
for p in 80/tcp 443/tcp 443/udp; do ufw delete allow "$p" >/dev/null 2>&1 && kl_ok "правило ufw снято: $p"; done
if [[ -f $KL_UFW_RULES ]]; then
kl_backup "$KL_UFW_RULES"; kl_backup "$KL_UFW6_RULES"
kl_run sed -i 's/^# -A ufw-before-input -p icmp --icmp-type echo-request/-A ufw-before-input -p icmp --icmp-type echo-request/' "$KL_UFW_RULES"
[[ -f $KL_UFW6_RULES ]] && kl_run sed -i 's/^# -A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/' "$KL_UFW6_RULES"
kl_run ufw reload >/dev/null 2>&1
kl_ok "ответ на ping возвращён"
sed -i 's/^# -A ufw-before-input -p icmp --icmp-type echo-request/-A ufw-before-input -p icmp --icmp-type echo-request/' "$KL_UFW_RULES"
[[ -f $KL_UFW6_RULES ]] && sed -i 's/^# -A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/' "$KL_UFW6_RULES"
ufw reload >/dev/null 2>&1 && kl_ok "ответ на ping возвращён"
fi
fi
kl_run sysctl -q -w net.ipv4.icmp_echo_ignore_all=0 >/dev/null 2>&1 || true
sysctl -q -w net.ipv4.icmp_echo_ignore_all=0 >/dev/null 2>&1 || true
kl_step "Проверка"
kl_step "Проверка (теми же условиями, что и установщик)"
local left=0
for s in "${svc[@]}"; do systemctl list-unit-files 2>/dev/null | grep -q "^$s" && { kl_warn "осталась служба: $s"; left=1; }; done
command -v kit >/dev/null && { kl_warn "команда kit ещё на месте (запустите команду ещё раз или удалите /usr/local/bin/kit)"; left=1; }
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
kl_warn "установщик всё ещё считает панель установленной"; left=1
fi
for s in $(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' \
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
kl_warn "осталась наша служба: $s"; left=1
done
# Добивка: убираем всё, что осталось и подходит под наши шаблоны (вдруг автор добавил новый файл
# или что-то не удалилось с первого раза). Затем проверяем ещё раз.
for f in /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
/etc/systemd/system/kit-*.service /etc/systemd/system/kit-*.timer \
/etc/systemd/system/x-ui*.service /usr/lib/systemd/system/kit-*.service \
/etc/x-ui /usr/local/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /root/3x-ui.txt /root/cert; do
[[ -e $f ]] || continue
rm -rf "$f" && { kl_ok "добито: $f"; i=1; } || kl_warn "не удалось удалить: $f"
done
((i)) && systemctl daemon-reload
found=$( { ls -d /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
/etc/systemd/system/*kit* /etc/x-ui /usr/local/x-ui /root/3x-ui.txt /root/cert 2>/dev/null || true; } | head -20)
[[ -z $found ]] || { kl_warn "осталось на диске:"; printf ' %s\n' $found; left=1; }
ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE '[:.]443$' && { kl_warn "порт 443 всё ещё кем-то слушается"; left=1; }
(( left )) || kl_ok "от установки ничего не осталось, порт 443 свободен"
kl_ok "SSH не тронут: порт 22 (или ваш текущий) и правила для него на месте"
kl_step "Готово. Сервер вернулся к состоянию до установки."
if ((left)); then
kl_warn "осталось перечисленное выше — пришлите этот вывод, поправим список в kit local uninstall"
else
kl_ok "чисто: от установки не осталось ни файлов, ни служб, порт 443 свободен"
kl_ok "проверка установщика пройдена: он снова поставит сервер с нуля"
fi
kl_ok "SSH не тронут (если не указан --reset-ssh)"
kl_step "Готово: сервер в состоянии до установки, можно ставить заново"
}
# ---------------------------------------------------------------- полная справка `kit help`
+9 -8
View File
@@ -12,7 +12,7 @@
set -Eeuo pipefail
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
KIT_VERSION="1.2.8"
KIT_VERSION="1.2.25"
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"; }
@@ -478,7 +478,8 @@ newer() { [[ $1 != "$2" && $(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1) ==
auto_enabled() { systemctl is-enabled -q kit-update.timer 2>/dev/null; }
# Раз в сутки узнаём последнюю версию (не дольше 3 секунд) и подсказываем обновиться.
update_hint() {
update_hint() { # подсказка про новую версию
[[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)
local latest=""
if [[ ! -f $KIT_LATEST ]] || (($(date +%s) - $(stat -c %Y "$KIT_LATEST") > 86400)); then
latest=$(remote_version 3) || true
@@ -2553,13 +2554,13 @@ 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)" "0. Выход"
"10. Удалить всё установленное (наш пункт)"
"7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \
"11. Панель 3X-UI (меню x-ui)"
echo
menu_status
update_hint
echo
ask_num "Выбор [0-9, 10]: "; c=$REPLY
ask_num "Выбор [1-11]: "; c=$REPLY
case $c in
1) menu_users ;;
2) menu_net ;;
@@ -2569,9 +2570,9 @@ 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) kit_local_uninstall ;;
0 | "") return 0 ;;
9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем
10 | 0 | "") return 0 ;;
11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
*) echo "Нет такого пункта." ;;
esac
done
+493 -157
View File
@@ -86,166 +86,502 @@ def main():
version_re = r'^KIT_VERSION="[0-9.]+"$'
edits = [
# --- scripts/kit.sh -------------------------------------------------
("scripts/kit.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
(
"scripts/kit.sh",
'scripts/kit.sh',
'# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.\n',
False,
'# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }\n',
False,
'kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }\n\n# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---\n# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.\n# Все команды форка — под «kit local …» (см. kit local help).\nKIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"\nif [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi\n\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)\n',
False,
'Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут\n',
False,
'Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' # SHA256:VDuGgJ8dOeCNXB4nBZf3+kRlWthw3+vh8rfMxzMSRIM (itsnotkubrick, 2026-09-30)\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrTGCDhhnm8XO1ekpPJuSWRVCJiFiupEspfQxcbEBmz 3X-UI KIT releases"\n',
False,
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
'update_hint() {\n',
False,
'update_hint() { # подсказка про новую версию\n [[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' echo "3X-UI KIT $KIT_VERSION"\n',
False,
' echo "3X-UI_autoVPS $KIT_VERSION"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'\n',
False,
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py; do\n',
False,
' grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
False,
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: автообновление kit и kit-sub (только подписанные релизы)\n',
False,
'Description=3X-UI_autoVPS: автообновление kit и kit-sub (только подписанные релизы)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'Description=3X-UI KIT: проверка обновлений раз в сутки\n',
False,
'Description=3X-UI_autoVPS: проверка обновлений раз в сутки\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI KIT $KIT_VERSION → $latest"; fi\n',
False,
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI_autoVPS $KIT_VERSION → $latest"; fi\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n',
False,
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n install -d -m 755 /usr/local/lib/kit\n install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n',
'авто-шов: insert, строк 1',
),
(
'scripts/kit.sh',
' echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
False,
' echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"\n',
False,
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI_autoVPS"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' grep -q \'"Name":"3X-UI KIT"\' <<<"$v"\n',
False,
' grep -q \'"Name":"3X-UI_autoVPS"\' <<<"$v"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' box "3X-UI KIT $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
False,
' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"\n',
False,
' "7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \\\n "11. Панель 3X-UI (меню x-ui)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' ask_num "Выбор [0-9]: "; c=$REPLY\n',
False,
' ask_num "Выбор [1-11]: "; c=$REPLY\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit.sh',
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 0 | "") return 0 ;;\n',
False,
' 9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем\n 10 | 0 | "") return 0 ;;\n 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n',
'авто-шов: replace, строк 2',
),
(
'scripts/kit.sh',
'${B}3X-UI KIT $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
False,
'${B}3X-UI_autoVPS $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"\n',
False,
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)\n',
False,
'# Наш шов: добавлены известные сайты (Microsoft, AMD, Intel, NVIDIA) — их можно выбрать вручную.\nSNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com www.microsoft.com www.amd.com www.intel.com www.nvidia.com)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
" seq 1 254 | xargs -P 8 -I{} bash -c '\n",
False,
" # Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.\n seq 1 254 | xargs -P 32 -I{} bash -c '\n",
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true\n',
False,
' echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' local me a b c base n names found=0\n',
False,
' local me a b c base n names out\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n',
False,
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n # Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная\n # причина «долго сканирует». Функции передаём в подпроцессы через export -f.\n export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip\n export HOST\n',
'авто-шов: insert, строк 2',
),
(
'scripts/3x-ui.sh',
' names=$(nearby_scan "$base")\n for n in $names; do\n [[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n if same_net "$n" && sni_alive "$n" && sni_trusted "$n" && sni_quality "$n"; then echo "$n"; found=$((found + 1)); ((found >= 8)) && return 0; fi\n done\n ((found > 0)) && return 0\n',
False,
' names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)\n [[ -n $names ]] || continue\n out=$(while IFS= read -r n; do\n [[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n printf \'%s\\n\' "$n"\n done <<<"$names" | xargs -P 8 -I{} bash -c \'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\\n" "$1"\' _ {} 2>/dev/null | head -8)\n [[ -n $out ]] && { printf \'%s\\n\' "$out"; return 0; }\n',
'авто-шов: replace, строк 6',
),
(
'scripts/3x-ui.sh',
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
False,
" _ _ _ ___ _____\n | | | | __ _ ___| | _____ _ __ / _ \\___ |\n | |_| |/ _` |/ __| |/ / _ \\ '__| | | | | / /\n | _ | (_| | (__| < __/ | | |_| |/ /\n |_| |_|\\__,_|\\___|_|\\_\\___|_| \\___//_/\n",
'авто-шов: replace, строк 5',
),
(
'scripts/3x-ui.sh',
' echo "${B}3X-UI KIT $KIT_VERSION${N} · it\'s not Kubrick. it\'s just a VPN."\n',
False,
' echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n',
False,
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# role — подпись для человека: REALITY или XHTTP/MTProto.\nkl_sni_menu() {\n local role=$1; shift\n local -a found=("$@")\n local -a known=("${SNI_CANDIDATES[@]}")\n local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try\n n=${#found[@]}\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((n)); then\n printf \'Найдено в подсети:\\n\\n\' >&2\n for i in "${!found[@]}"; do printf \' %d - %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))\n printf \' %d - автовыбор из списка\\n\' "$auto_idx" >&2\n printf \' %d - выбрать общедоступный (например %s, %s)\\n\' "$known_idx" "${known[0]}" "${known[1]}" >&2\n printf \' %d - ввести свой домен\\n\\n\' "$own_idx" >&2\n else\n auto_idx=0; known_idx=1; own_idx=2\n printf \'В подсети доменов не найдено, выберите:\\n\\n\' >&2\n printf \' 1 - общедоступный (например %s, %s)\\n\' "${known[0]}" "${known[1]}" >&2\n printf \' 2 - ввести свой домен\\n\\n\' >&2\n fi\n printf \'Ваш выбор (Enter — автовыбор): \' >&2\n for try in 1 2 3; do\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ -z $ans ]] && return 0\n [[ $ans =~ ^[0-9]+$ ]] || { printf \' введите номер из списка\\n\' >&2; continue; }\n i=$((10#$ans))\n if ((n)) && ((i >= 1 && i <= n)); then printf \'sni:%s\' "${found[i - 1]}"; return 0; fi\n ((i == auto_idx)) && return 0\n if ((i == known_idx)); then\n printf \'\\nОбщедоступные сайты:\\n\' >&2\n for k in "${!known[@]}"; do printf \' %d - %s\\n\' "$((k + 1))" "${known[k]}" >&2; done\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'sni:%s\' "${known[10#$ans - 1]}"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'own:%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n',
'авто-шов: insert, строк 3',
),
(
'scripts/3x-ui.sh',
' elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
False,
' # Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе\n # человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.\n elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"\n',
False,
' echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."\n echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n',
False,
' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then\n local pick pick2 own_sni=no\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n case $pick in\n own:*)\n own_sni=yes\n DOMAIN=${pick#own:}\n if domain_points_here "$DOMAIN" 2>/dev/null; then\n SNI=$DOMAIN\n ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let\'s Encrypt выпустит установщик)"\n # Наш шов: спрашиваем, где открывать панель и подписку, и при интерактивном вводе домена тоже.\n # У автора этот вопрос задавался только при домене из флага --domain, а при выборе «ввести свой\n # домен» в меню маскировки — нет, и панель молча оставалась по IP с самоподписанным сертификатом.\n panel_on_choice\n else\n SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт, панель и подписка остаются по IP"\n fi\n ;;\n sni:*) SNI=${pick#sni:} ;;\n esac\n if [[ -n $SNI ]]; then\n if [[ $own_sni == no ]] && ! sni_ok "$SNI"; then\n warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"\n SNI="${nb[0]:-$SNI}"\n fi\n ok "маскировка REALITY: ${B}$SNI${N}"\n fi\n if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})\n [[ $pick2 == sni:* ]] && pick2=${pick2#sni:}\n [[ $pick2 == own:* ]] && pick2=${pick2#own:}\n [[ -n $pick2 ]] && SNI2=$pick2\n [[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"\n fi\n fi\n if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"\n else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi\n if [[ -z $SNI ]] && ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n else\n',
False,
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n elif [[ -z $SNI ]]; then\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
False,
' if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then\n : # сайт уже выбран в нашем меню — вопрос автора не нужен\n elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then\n',
False,
' # Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей\n # по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.\n if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \\\n && [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"\n',
False,
' # Наш шов: про XHTTP и MTProto говорим только если они выбраны — иначе это шум.\n [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]] && \\\n later "XHTTP и MTProto используют известные сайты из общего списка (достаточно соседей по подсети не нашлось). Заменить: kit net site"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' ok "подключения работают: REALITY, XHTTP и Hysteria2 проверены клиентом с самого сервера"\n',
False,
' ok "подключения работают (проверено клиентом с самого сервера): ${CREATED[*]:-нет}"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' local extra\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n',
False,
' local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны\n # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,\n # если эти протоколы не выбраны (никаких лишних обращений к панели).\n if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n fi\n',
'авто-шов: replace, строк 2',
),
(
'scripts/3x-ui.sh',
' echo "3X-UI KIT (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
False,
' echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo "${G}${B}Готово! Сервер работает.${N}"\n [[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let\'s Encrypt продлевается сам."\n [[ $SELF_IP_CERT == yes ]] && later "Сертификат на IP самоподписанный: пользуйтесь ссылками на отдельные подключения, подписка в приложениях может не открыться."\n echo\n echo "${B}ПАНЕЛЬ${N}"\n if [[ $TRUSTED == yes ]]; then\n echo " $panel_url"\n else\n echo " Только через SSH-туннель. На вашем компьютере:"\n echo " ssh -L $XUI_PANEL_PORT:127.0.0.1:$XUI_PANEL_PORT root@$HOST"\n echo " Затем в браузере: http://127.0.0.1:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH"\n fi\n echo " Логин: ${B}$XUI_USERNAME${N} Пароль: ${B}$XUI_PASSWORD${N}"\n echo " ${D}(сохранено в $RESULT, файл виден только root)${N}"\n echo\n if [[ $TRUSTED == yes ]]; then\n echo "${B}ПОДПИСКА${N} – одна ссылка для всех протоколов"\n echo " ${B}$SUB_URL${N}"\n echo " Вставьте в Happ, Hiddify, Karing, v2rayN, Clash Verge или FlClash."\n qrencode -t ANSIUTF8 -m 1 "$SUB_URL" || true\n echo\n KIT_NO_QR=1 /usr/local/bin/kit __links "$NAME" "$SUBID" || true\n else\n /usr/local/bin/kit __links "$NAME" "$SUBID" || true\n fi\n echo\n echo "${D}Порты, выключение и включение протоколов: ${N}${B}kit net${N}"\n',
False,
' echo "${G}${B}Установка завершена.${N} Доступы к панели, подписка и QR-код — в самом конце вывода."\n',
'авто-шов: replace, строк 27',
),
(
'scripts/3x-ui.sh',
' ((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"\n fi\n echo\n echo "${B}ДАЛЬШЕ${N}"\n echo " ${B}kit${N} меню управления"\n echo " ${B}kit user add имя --gb 50 --days 30${N} добавить пользователя"\n echo " ${B}kit check --deep${N} проверить, что всё работает"\n echo " ${B}kit backup${N} копия сервера (держите в надёжном месте)"\n',
False,
' ((${#WARNINGS[@]} > 3)) && echo " … и ещё $((${#WARNINGS[@]} - 3)): kit check"\n fi\n\n',
'авто-шов: replace, строк 8',
),
(
'scripts/3x-ui.sh',
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\n',
False,
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"\n',
'авто-шов: insert, строк 1',
),
(
'scripts/3x-ui.sh',
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n',
False,
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n install -d -m 755 /usr/local/lib/kit\n if [[ -n $src && -f $d/kit-local.sh ]]; then\n install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n else\n curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n fi\n',
'авто-шов: insert, строк 1',
),
(
'scripts/3x-ui.sh',
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"\n',
False,
'KIT_INSTALL_CMD="bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui.sh)"\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' echo "# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске."\n',
False,
' echo "# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске.\n',
False,
'# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске.\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI KIT" --arg uri "$uri" \'\n',
False,
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI_autoVPS" --arg uri "$uri" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI KIT" \'\n',
False,
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI_autoVPS" \'\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
False,
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' && die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
False,
' && die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
False,
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' *) die "Это не резервная копия 3X-UI KIT или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
False,
' *) die "Это не резервная копия 3X-UI_autoVPS или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."\n',
False,
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена. Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (порт панели). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (порт панели). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."\n',
False,
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (адрес сервера). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' || die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."\n',
False,
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."\n',
'авто-шов: replace, строк 1',
),
(
'scripts/3x-ui.sh',
' [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
False,
' # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов\n # позже (после сборки nginx), поэтому на свежем сервере проверка Let\'s Encrypt доходила до сервера и\n # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».\n command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true\n [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
'авто-шов: replace, строк 1',
),
(
'scripts/hysteria2.sh',
'# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
False,
'# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
'авто-шов: replace, строк 1',
),
(
'scripts/hysteria2.sh',
'Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).\n',
False,
'Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
False,
' data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
False,
' data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
'авто-шов: replace, строк 1',
),
(
'scripts/kit-sub.py',
' main()\n',
False,
' main()\n\n',
'авто-шов: insert, строк 1',
),
(
'scripts/kit.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/3x-ui.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/hysteria2.sh',
version_re,
True,
f'KIT_VERSION="{version}"',
'версия сборки форка',
),
(
'scripts/kit.sh',
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
False,
'KIT_RAW="%s/raw/branch/main"' % GITEA,
"источник VERSION и подсказки об обновлении",
),
(
"scripts/kit.sh",
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }',
False,
'kit_ref_raw() { echo "%s/raw/tag/v$1"; }' % GITEA + "\n" + HOOK,
"файлы релиза из нашего тега + подключение kit-local.sh",
),
(
"scripts/kit.sh",
r' # SHA256:[^\n]*\n "ssh-ed25519 [^\n]*"\n',
True,
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n'
' "%s"\n' % pub,
"ключ проверки подписи релизов",
),
(
"scripts/kit.sh",
"for f in scripts/kit.sh scripts/kit-sub.py; do",
False,
"for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do",
"наш файл в списке проверяемых и скачиваемых",
),
(
"scripts/kit.sh",
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit',
False,
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n'
" # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n"
" install -d -m 755 /usr/local/lib/kit\n"
' install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh',
"установка kit-local.sh при обновлении",
),
# --- scripts/3x-ui.sh -----------------------------------------------
("scripts/3x-ui.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
(
"scripts/3x-ui.sh",
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"',
False,
'KIT_RAW="%s/raw/tag/v$KIT_VERSION"' % GITEA,
"запасная загрузка kit из нашего релиза",
),
(
"scripts/3x-ui.sh",
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"',
False,
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"',
"адрес нашего файла для установщика",
),
(
"scripts/3x-ui.sh",
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"',
False,
'KIT_INSTALL_CMD="bash <(curl -fsSL %s/raw/branch/main/scripts/3x-ui.sh)"' % GITEA,
"команда установки, которую показывает панель",
),
(
"scripts/3x-ui.sh",
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
False,
" # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n"
" install -d -m 755 /usr/local/lib/kit\n"
' if [[ -n $src && -f $d/kit-local.sh ]]; then\n'
' install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n'
" else\n"
' curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n'
' || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n'
" fi\n"
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
"установка kit-local.sh установщиком",
),
# --- scripts/hysteria2.sh -------------------------------------------
("scripts/hysteria2.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
# --- tools/release.sh -----------------------------------------------
(
"tools/release.sh",
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py VERSION)",
False,
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)",
"наш файл в подписанном списке релиза",
),
(
"tools/release.sh",
'{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}"; } >SHA256SUMS',
False,
"{ echo \"# 3X-UI KIT $v\"; echo \"# 3X-UI_autoVPS $v\"; sha \"${FILES[@]}\" | awk '{ sub(/^\\*/, \"\", $2); print $1\" \"$2 }'; } >SHA256SUMS",
"суммы в POSIX-виде без звёздочки (на Windows sha256sum её ставит, и kit её не понимает)",
),
# --- свои записи о версии (наш файл, авторский CHANGELOG не трогаем) ----
(
"tools/release.sh",
'grep -q "^## v$v" CHANGELOG.md || die "В CHANGELOG.md нет раздела ## v$v"',
False,
'(grep -q "^## v$v" CHANGELOG.md || grep -q "^## v$v" FORK-CHANGELOG.md)'
' || die "Нет раздела ## v$v ни в CHANGELOG.md, ни в FORK-CHANGELOG.md (форк ведёт свои записи в FORK-CHANGELOG.md)"',
"раздел версии форка — в нашем файле",
),
(
"tools/release.sh",
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md) ]]',
False,
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]]',
"наш файл записей тоже должен быть закоммичен",
),
(
"scripts/kit.sh",
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'',
False,
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'',
"«что нового» ищет и в наших записях",
),
(
"scripts/3x-ui.sh",
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\",
False,
" _ _ _ ___ _____\n | | | | __ _ ___| | _____ _ __ / _ \\___ |\n | |_| |/ _` |/ __| |/ / _ \\ '__| | | | | / /\n | _ | (_| | (__| < __/ | | |_| |/ /\n |_| |_|\\__,_|\\___|_|\\_\\___|_| \\___//_/",
"баннер сервера: наша надпись вместо авторской",
),
(
"scripts/3x-ui.sh",
' echo "${B}3X-UI KIT $KIT_VERSION${N} · it\'s not Kubrick. it\'s just a VPN."',
False,
' echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"',
"подпись под баннером: наше имя сборки",
),
(
"scripts/kit.sh",
' grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS"',
False,
' grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS"',
"проверка шапки подписанного релиза принимает и старое, и новое имя",
),
(
'scripts/kit.sh',
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"',
False,
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"\n "10. Удалить всё установленное (наш пункт)"',
'пункт меню: снос установки',
),
(
'scripts/kit.sh',
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;',
False,
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 10) kit_local_uninstall ;;',
'пункт меню: снос установки (обработчик)',
),
(
"scripts/kit.sh",
'ask_num "Выбор [0-9]: "',
False,
'ask_num "Выбор [0-9, 10]: "',
"подсказка выбора в меню учитывает новый пункт",
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"',
'канал обновлений: наш Gitea вместо GitHub',
),
]
+40 -5
View File
@@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
die() { printf '✗ %s\n' "$*" >&2; exit 1; }
ok() { printf '✓ %s\n' "$*"; }
warn() { printf '! %s\n' "$*" >&2; } # сеть до GitHub отвалилась: проверку пропускаем, релиз не блокируем
sha() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
key=${1:-$HOME/.ssh/3xui_kit_release}
@@ -39,12 +40,40 @@ ok "Переводы строк во всех файлах релиза — LF"
export PY
bash tools/check-help.sh || die "справка kit help отстала от набора команд"
# «Строки-сироты»: строка с флагом без команды (например потерянный "#" в комментарии) не ловится
# ни bash -n, ни справкой, но при каждом запуске печатает «command not found». Проверяем холостым прогоном.
err=$(bash scripts/3x-ui-local.sh --dry-run 2>&1 >/dev/null || true)
if grep -qE "command not found|syntax error|bad substitution" <<<"$err"; then
printf '%s\n' "$err" | grep -E "command not found|syntax error|bad substitution" | head -3
die "установщик ругается на строку без команды — поправьте файл (см. выше)"
fi
ok "Установщик не ругается на пустые строки и опечатки"
# Наши функции в установщике обязаны быть определены РАНЬШЕ первого вызова: `bash -n` и холостой
# прогон этого не видят, а человек видит «command not found» посреди установки (так и было 13.10).
for fn in ask_yes ask_ssh_port ssh_ports_now ask_protocols proto_parse proto_menu interactive clean_previous installer_failed; do
# || true обязателен: без него grep, не найдя совпадений, возвращает 1 и set -e молча обрывает релиз.
d=$(grep -n "^$fn()" scripts/3x-ui-local.sh | head -1 | cut -d: -f1 || true)
c=$(grep -n "^[^#]*[^a-z_]$fn[ ;\"]" scripts/3x-ui-local.sh | grep -v "^${d:-0}:" | head -1 | cut -d: -f1 || true)
[[ -n $d ]] || die "scripts/3x-ui-local.sh: функция $fn не определена, а вызывается"
[[ -z $c || $c -gt $d ]] || die "scripts/3x-ui-local.sh: $fn вызывается (строка $c) раньше, чем определена ($d)"
done
ok "Все наши функции определены раньше своих вызовов"
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
# Часть проверок сверяется с GitHub, а он из домашней сети отвечает через раз. Сначала короткая проба:
# нет связи — сразу предупреждаем и пропускаем внешние проверки (подписи это не мешает), а не висим минутами.
gh_ok=yes
curl -fsI --connect-timeout 8 --max-time 12 https://github.com >/dev/null 2>&1 || gh_ok=no
[[ $gh_ok == yes ]] || warn "GitHub недоступен из вашей сети — установщик, суммы ядра и панели не сверяю. Подписи это не мешает."
gh_curl() { [[ ${gh_ok:-no} == yes ]] || return 1; curl -fsSL --retry 2 --retry-delay 2 --connect-timeout 15 --max-time 90 "$@"; }
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
xv=$(sed -n 's/^XUI_VERSION="\(.*\)"/\1/p' scripts/3x-ui.sh)
xs=$(sed -n 's/^XUI_INSTALL_SHA256="\(.*\)"/\1/p' scripts/3x-ui.sh)
got=$(curl -fsSL "https://raw.githubusercontent.com/MHSanaei/3x-ui/$xv/install.sh" | sha | awk '{print $1}')
got=$(gh_curl "https://raw.githubusercontent.com/MHSanaei/3x-ui/$xv/install.sh" | sha | awk '{print $1}') \
|| { warn "GitHub недоступен — проверку установщика 3X-UI пропускаю (релиз не блокирую)"; got=$xs; }
[[ $got == "$xs" ]] || die "Установщик 3X-UI $xv изменился: $got вместо $xs. Проверьте, что поменялось, прежде чем выпускать."
ok "Установщик 3X-UI $xv совпадает с закреплённым SHA256"
@@ -52,7 +81,8 @@ ok "Установщик 3X-UI $xv совпадает с закреплённы
xc=$(sed -n 's/^XRAY_CORE="\(.*\)"/\1/p' scripts/3x-ui.sh)
for a in 64 arm64-v8a; do
pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/3x-ui.sh)
off=$(curl -fsSL "https://github.com/XTLS/Xray-core/releases/download/$xc/Xray-linux-$a.zip.dgst" | awk '/^SHA2-256=/ {print $2}')
off=$(gh_curl "https://github.com/XTLS/Xray-core/releases/download/$xc/Xray-linux-$a.zip.dgst" | awk '/^SHA2-256=/ {print $2}' || true)
[[ -n $off ]] || { warn "GitHub недоступен — суммы Xray для $a пропускаю"; continue; }
[[ -n $pin && $pin == "$off" ]] || die "Сумма ядра Xray $xc ($a) в 3x-ui.sh не совпадает с официальной: «$pin» вместо «$off»."
done
ok "Суммы архивов ядра Xray $xc совпадают с официальными"
@@ -65,11 +95,13 @@ kx=$(sed -n 's/^XRAY_PIN="\(.*\)"/\1/p' scripts/kit.sh)
[[ $kx == "$xc" ]] || die "XRAY_PIN в kit.sh ($kx) не совпадает с XRAY_CORE в 3x-ui.sh ($xc)."
for a in amd64 arm64; do
pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/kit.sh)
off=$(curl -fsSL "https://github.com/MHSanaei/3x-ui/releases/download/$kp/x-ui-linux-$a.tar.gz.sha256" | awk 'NR == 1 {print $1}')
off=$(gh_curl "https://github.com/MHSanaei/3x-ui/releases/download/$kp/x-ui-linux-$a.tar.gz.sha256" | awk 'NR == 1 {print $1}' || true)
[[ -n $off ]] || { warn "GitHub недоступен — сумму панели для $a пропускаю"; continue; }
[[ -n $pin && $pin == "$off" ]] || die "Сумма архива панели $kp ($a) в kit.sh не совпадает с официальной: «$pin» вместо «$off»."
done
shs=$(sed -n 's/^XUI_SH_SHA256=\(.*\)/\1/p' scripts/kit.sh)
got=$(curl -fsSL "https://raw.githubusercontent.com/MHSanaei/3x-ui/$kp/x-ui.sh" | sha | awk '{print $1}')
got=$(gh_curl "https://raw.githubusercontent.com/MHSanaei/3x-ui/$kp/x-ui.sh" | sha | awk '{print $1}' || true)
[[ -n $got ]] || { warn "GitHub недоступен — проверку x-ui.sh $kp пропускаю"; got=$shs; }
[[ $shs == "$got" ]] || die "Сумма x-ui.sh $kp в kit.sh не совпадает с официальной: «$shs» вместо «$got»."
ok "Панель $kp и ядро $kx в kit.sh совпадают с установщиком и официальными суммами"
@@ -82,7 +114,10 @@ ok "Ключ подписи записан в kit"
{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
rm -f SHA256SUMS.sig
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null
# Ключ защищён парольной фразой: её вводит человек. Понятная подсказка на случай опечатки.
echo "Подписываю ключом $key — если он защищён парольной фразой, введите её сейчас."
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null \
|| die "Ключ не подписал. Чаще всего это неверная парольная фраза («incorrect passphrase») — запустите команду ещё раз и введите её внимательно. Файл SHA256SUMS при этом не портится."
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
signers scripts/kit.sh | while read -r k; do printf '%s namespaces="%s" %s\n' "$ID" "$NS" "$k"; done >"$tmp"