Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edabfd9a83 | ||
|
|
741a70c9bf | ||
|
|
0d2af45bdb | ||
|
|
a02650311e | ||
|
|
2b57e6f6d7 | ||
|
|
df8a99bb78 | ||
|
|
47328b33b3 | ||
|
|
776b7a8c5e | ||
|
|
2d63136853 | ||
|
|
1b85a4bb23 | ||
|
|
b80c44077c | ||
|
|
f894b48cd2 | ||
|
|
bddef5b8d3 | ||
|
|
53cb694206 | ||
|
|
5c22cbf4fe | ||
|
|
a7b4586bc3 | ||
|
|
cec5246e47 | ||
|
|
151d510495 | ||
|
|
f9940026dc | ||
|
|
6d7ebf234e | ||
|
|
41092d4cd1 | ||
|
|
78cf3c05dc | ||
|
|
83d9b11c5c | ||
|
|
1fbf27e07c | ||
|
|
9571e93d46 | ||
|
|
9dfb4e7957 | ||
|
|
51c35dd2c1 | ||
|
|
3f3db5b04c | ||
|
|
8fd5572450 | ||
|
|
01ab468e24 | ||
|
|
b20fd6af77 | ||
|
|
bc18fd354e | ||
|
|
25e25232b2 | ||
|
|
6a9a6ed07e | ||
|
|
d127a92785 | ||
|
|
f38aeefcbc | ||
|
|
d0bfb2dc38 | ||
|
|
a0df115616 | ||
|
|
f4cf157293 | ||
|
|
73754dbc01 | ||
|
|
c261d27b80 | ||
|
|
fa9e7225ea | ||
|
|
4a9e452e3e | ||
|
|
d034e207da | ||
|
|
22b9925b0c | ||
|
|
9aa93fad37 | ||
|
|
5558314849 | ||
|
|
b778ce4428 | ||
|
|
143b41c84e | ||
|
|
01662ff039 | ||
|
|
04a69f3ae3 | ||
|
|
0daaf65b5c | ||
|
|
64796e0f53 | ||
|
|
b7834d5f5c | ||
|
|
77f98da99a |
+296
-12
@@ -1,15 +1,299 @@
|
||||
## v1.2.1.6: доступы к панели в конце установки и рабочая заглушка
|
||||
|
||||
* **Исправлено: доступы к панели не выводились.** Автор пишет строки в `/root/3x-ui.txt` без ведущих
|
||||
пробелов, а наш фильтр требовал два пробела — отсюда «данных панели нет». Теперь адрес панели, логин и
|
||||
пароль печатаются в конце установки (и в пункте меню «9. Доступ к панели, ссылки и заглушка»).
|
||||
Рядом указывается, что подписка и ссылки по протоколам лежат в том же файле.
|
||||
* **Исправлено: наша заглушка не готовилась до установщика.** Обёртка скачивала из релиза только три файла,
|
||||
а `scripts/kit-local.sh` (там функция страницы) не качала — падало с «kl_site_html: command not found».
|
||||
Теперь качаются все файлы релиза, и каждый сверяется с подписанным списком (проверка целостности стала
|
||||
полной, а не только для установщика).
|
||||
|
||||
## v1.2.1.5: доступ к панели, ссылки и заглушка — одним пунктом меню
|
||||
|
||||
* **В меню kit пункт «9. Доступ к панели, ссылки и заглушка»** — открывает одним экраном: адрес панели,
|
||||
логин и пароль (из /root/3x-ui.txt), состояние сертификата панели (доверенный на IP или
|
||||
самоподписанный, с датой повтора при лимите Let's Encrypt), ссылки на подключения с QR-кодом и что
|
||||
сейчас стоит заглушкой. Раньше доступ к панели в меню не показывался вообще, а про заглушку было
|
||||
непонятно, как её посмотреть.
|
||||
* Меню перенумеровано: «10. Панель x-ui», «11. Удалить KIT с сервера» (логика удаления — авторская).
|
||||
* То же самое доступно из консоли: `kit local access [имя]`.
|
||||
|
||||
## v1.2.1.4: короткий итог установки, автовыбор в общедоступных сайтах, команда про порты
|
||||
|
||||
* **Итог установки вместо простыни текста** — компактный блок: сборка, сервер, протоколы, состояние
|
||||
сертификата панели, одна строка проверки. Дальше только то, что нужно человеку: доступ к панели (адрес,
|
||||
логин, пароль — по просьбе из ранних правок), указатель на меню kit для ссылок и QR, открытые порты и
|
||||
команда для закрытия лишних. Длинные отчёты (состояние защиты, полный вывод kit check) убраны из
|
||||
установки — они остались доступны командами, в том числе `kit local hardening`.
|
||||
* **Ссылки на подключения больше не попадают в вывод установки даже косвенно**: раньше печатался весь
|
||||
файл /root/3x-ui.txt, а в нём лежат и ссылки vless://. Теперь из него берутся только строки панели —
|
||||
адрес, логин, пароль.
|
||||
* **В меню общедоступных сайтов последним пунктом добавлен автовыбор** (раньше можно было только выбрать
|
||||
номером или вернуться).
|
||||
* **Новая команда `kit local ports`** — что открыто в ufw, что реально нужно (порты подключений и SSH),
|
||||
что лишнее; `kit local ports --close-extra` закрывает лишнее.
|
||||
* Ошибка подготовки нашей заглушки теперь печатает причину, а не просто «не смог».
|
||||
|
||||
## v1.2.1.3: ссылки и QR — в меню kit; сертификат панели видно в итоге
|
||||
|
||||
* **Ссылки для подключения и QR-код больше не печатаются в выводе установки.** Их смотрят позже, когда
|
||||
настраивают телефон, и они всегда доступны заново: `kit → Пользователи → Показать ссылки`. В итоговом
|
||||
блоке вместо них — короткий путь к этому пункту и команды `kit local links` (подписка и QR) и
|
||||
`kit user link <имя> --all` (все протоколы). Доступы к панели (адрес, логин, пароль) по-прежнему
|
||||
в конце установки: их вы просили показывать один раз и сразу.
|
||||
* **Добавлена команда `kit local links`** — подписка, QR-код и ссылки по протоколам одной командой;
|
||||
пользователя можно указать, иначе берётся первый.
|
||||
* **В итоговом блоке видно состояние сертификата панели.** Панель и подписка висят на IP, поэтому
|
||||
печатается: «Let's Encrypt на IP — доверенный, браузер не ругается» либо «самоподписанный» с датой,
|
||||
после которой Let's Encrypt выпустит сертификат на IP (тот же лимит, что и для домена).
|
||||
|
||||
## v1.2.1.2: сертификат на IP — с датой повтора; «без домена» продолжает установку
|
||||
|
||||
* **Отказ Let's Encrypt на IP теперь объясняется честно.** Как и для домена, при лимите в выводе дата,
|
||||
после которой выпуск возможен, и прямо сказано, что порт 80 и файрвол тут не при чём. Раньше печатались
|
||||
«частые причины» и человек шёл проверять хостера.
|
||||
* **Пункт «без своего домена» больше не останавливает установку.** При отказе с сертификатом домена выбор
|
||||
«3» продолжает работу сразу: маскировка берётся из соседей по подсети (или известный сайт, если соседей
|
||||
нет), панель и подписка переключаются на IP, дальше установка идёт своим ходом. Перезапускать скрипт
|
||||
и заново отвечать на вопросы не нужно.
|
||||
|
||||
## v1.2.1.1: сборка на новой базе автора (3X-UI KIT 1.2.1)
|
||||
|
||||
**Правило нумерации:** берём версию автора и через точку добавляем свою, начиная с 1. У автора 1.2.1 —
|
||||
значит наша сборка 1.2.1.1; когда выйдет его 1.2.2, наша будет 1.2.2.1.
|
||||
|
||||
**Взяли от автора (его код теперь работает вместо наших заплаток):**
|
||||
* проверка A-записи домена сверяется с **публичным DNS**, а не с кэшем самого сервера — та самая причина,
|
||||
когда запись уже есть, а сервер её «не видит»;
|
||||
* пропал наш шов, открывавший порт 80: автор делает это сам перед выпуском сертификата **и закрывает обратно**;
|
||||
* пропал наш шов переиспользования сертификата: автор тоже берёт прошлый;
|
||||
* повторный запуск после неудачной установки: автор заново спрашивает протоколы и сайт маскировки и не
|
||||
оставляет сервер на полпути — прежнее наше «доустанавливаю» больше не нужно;
|
||||
* полное удаление: наш `kit local uninstall` теперь **делегирует авторской команде `kit uninstall`**
|
||||
(она снимает и сам kit) и остаётся синонимом;
|
||||
* меню `kit` — авторское (в том числе «9. Панель 3X-UI (меню x-ui)», «10. Удалить KIT с сервера»);
|
||||
* попутно приехали его починки: Hiddify 4.x снова принимает подписку, ссылка MTProto ведёт на 443, смена
|
||||
сайта маскировки не обрывает клиентов (прежнее имя живёт неделю), `nginx -t` в проверке сервера,
|
||||
устойчивость к битому apt-репозиторию, длинные имена сайта в nginx, проверка копии (в т.ч. U+202E),
|
||||
честная глубокая проверка, предупреждение «обновлять панель и ядро только через kit».
|
||||
|
||||
**Сохранили своё:**
|
||||
* наш файл `scripts/kit-local.sh` и наша обёртка установщика — автор их не трогает;
|
||||
* наш канал обновлений (GitHub → наш Gitea) и **только наш ключ подписи** в `KIT_SIGNERS`: сервер обязан
|
||||
принимать обновления лишь из нашего канала;
|
||||
* наш экран выбора протоколов (13 пунктов, дефолт REALITY + AmneziaWG-3.1) — теперь номера вводятся
|
||||
**через пробел** («1 12»), как вы просили; запятые по-прежнему принимаются;
|
||||
* наши меню маскировки REALITY и XHTTP/MTProto, вопрос «панель по IP или по домену», возврат «ввести
|
||||
другой домен» без обрыва установки;
|
||||
* наша заглушка ставится сразу, авторская не устанавливается;
|
||||
* финальный блок: доступы, подписка и QR печатает наш установщик один раз, в самом конце (авторский дубль
|
||||
«Готово! Сервер работает» убран, предупреждения остаются).
|
||||
|
||||
**Технически:** из 77 прежних швов 66 легли без изменений, 8 больше не нужны (их работу делает автор),
|
||||
3 перенастроены на его новый код (ключ подписи, защита наших меню, финальный блок).
|
||||
|
||||
## v1.2.27: ошибка с доменом больше не обрывает установку
|
||||
|
||||
* **Свой домен вводится заново, пока не заработает.** Раньше при промахе (A-запись ещё не разошлась
|
||||
по DNS, опечатка в имени) установка молча маскировалась под чужой сайт, а при домене из флага —
|
||||
обрывалась с «исправьте причину и запустите скрипт снова». Теперь сразу после ввода домена идёт
|
||||
проверка и при промахе — выбор: «проверить снова» (DNS расходится от минут до часов), «ввести другой
|
||||
домен», «без своего домена — маскировка соседом по подсети».
|
||||
* **Провал сертификата на шаге подключений не прерывает установку.** Предлагается повторить попытку
|
||||
(пока ждали обновления DNS или лимит Let's Encrypt), ввести другое имя (с той же проверкой A-записи)
|
||||
или остановиться с понятной инструкцией, как поставить без домена. Причина и дата повтора при лимите
|
||||
печатаются в самом отказе, лог остаётся в /var/log/kit-domain-cert.log.
|
||||
|
||||
## v1.2.26: лимит Let's Encrypt виден прямо, готовый сертификат переиспользуется
|
||||
|
||||
* **Причина отказа теперь называется честно.** Let's Encrypt разрешает 5 сертификатов на точный набор имён
|
||||
за 7 суток; при повторных установках лимит выбирается, и acme.sh отвечает `rateLimited`, а установщик
|
||||
писал про «A-запись, порт 80 или Cloudflare» — то есть отправлял искать причину не там. Теперь в выводе
|
||||
дата, после которой повтор сработает, и подсказка взять другое имя (`node3.…` — лимит считается на набор имён).
|
||||
* **Готовый сертификат переиспользуется.** Если у acme.sh уже лежит годный сертификат на этот домен,
|
||||
новый не заказывается — лишний заказ и упирался в лимит. Заодно не тратится время на повторные попытки.
|
||||
* Итоговая ошибка установщика больше не повторяет догадки про порт и DNS: причина печатается выше,
|
||||
а лог остаётся в /var/log/kit-domain-cert.log.
|
||||
|
||||
## 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` собран заново **по коду установщика** и дополнен тем, чего
|
||||
@@ -22,7 +306,6 @@
|
||||
* Флаг `--reset-ssh`: снести и наш ssh-дроп-ин (порт возвращается к системному). По умолчанию SSH не трогаем.
|
||||
* У установщика появился флаг **`--clean`**: убрать прошлую установку и поставить заново. Если установщик
|
||||
всё-таки упал на «уже установлена», в подсказке прямо сказано запустить с `--clean`.
|
||||
|
||||
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
|
||||
|
||||
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
|
||||
@@ -34,7 +317,6 @@
|
||||
«удалить», есть `--dry-run`.
|
||||
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
|
||||
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
|
||||
|
||||
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
|
||||
|
||||
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
|
||||
@@ -82,7 +364,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` пишет суммы как
|
||||
@@ -93,14 +381,12 @@
|
||||
другой машине.
|
||||
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
|
||||
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
|
||||
|
||||
## v1.2.4: проверка релиза отдельной командой
|
||||
|
||||
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
|
||||
можно проверить канал на сервере, ничего не устанавливая.
|
||||
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
|
||||
изменений на сервере, чтобы чужой код не запускался раньше проверки.
|
||||
|
||||
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
|
||||
|
||||
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
|
||||
@@ -115,7 +401,6 @@
|
||||
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
|
||||
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
|
||||
ключа обновляется и здесь, и в `kit`.
|
||||
|
||||
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
|
||||
|
||||
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
|
||||
@@ -132,7 +417,6 @@
|
||||
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
|
||||
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
||||
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
||||
|
||||
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
|
||||
|
||||
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
||||
|
||||
@@ -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,22 @@ bash tools/fork-sync.sh # подтянуть автора, перен
|
||||
# при конфликте — разобрать вручную: инструмент сам ничего не публикует
|
||||
|
||||
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом
|
||||
|
||||
**Нумерация версий.** Основа — версия автора, наша надстройка добавляется через точку с единицы:
|
||||
у автора 1.2.1 → наша сборка 1.2.1.1; вышла его 1.2.2 → наша 1.2.2.1. Так на сервере всегда видно,
|
||||
на чьей базе собрана сборка.
|
||||
|
||||
**Обновление базы автора.** Когда автор выпускает новую версию: перенести его файлы
|
||||
(`git checkout upstream -- scripts/kit.sh scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit-sub.py`),
|
||||
прогнать `python tools/fork_apply.py` и посмотреть, какие якоря не нашли себя. Те, чью работу автор уже
|
||||
делает сам, — вычеркнуть; остальные перенастроить на его новый код и пересобрать таблицу швов
|
||||
`git branch -f upstream <коммит автора>`.
|
||||
|
||||
Скрипт сам приводит переводы строк к 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
|
||||
```
|
||||
|
||||
@@ -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` — показывает план и ничего не меняет. Копии изменённых файлов
|
||||
|
||||
+9
-9
@@ -1,9 +1,9 @@
|
||||
# 3X-UI KIT 1.2.9
|
||||
# 3X-UI_autoVPS 1.2.9
|
||||
03d89d6ba3595ed29175d2bef321cf5104360011d6c3d7b75a0a6b713c0b78ca scripts/3x-ui.sh
|
||||
5f0cd97f1d2c26f35db3cf707656a7c951e79b1c6ef70c62a93b2e73fa4e3c58 scripts/hysteria2.sh
|
||||
b65f7b40a8efbe8e44175c6a6d0d2193d17d14dd64c1e6cff1aefd171ed35f09 scripts/kit.sh
|
||||
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
|
||||
3aa34a3d796161b056d2e0bd15456851304bb4bc5717ed2df5522c53d6c71e36 scripts/kit-local.sh
|
||||
81137d4407aba121547647b1cc8c0b8e6d27fdef5abf067bcdf1c56d77ef6f49 scripts/3x-ui-local.sh
|
||||
c8d83c2672c5e397ce16a2ee275ac45917002b9b36765f77d99f31bdf81ee828 VERSION
|
||||
# 3X-UI KIT 1.2.1.6
|
||||
# 3X-UI_autoVPS 1.2.1.6
|
||||
f30fc23d1c667d6fc90742fdf12377ca84d9e119fc394d86822bf1daaf462795 scripts/3x-ui.sh
|
||||
4ef4c3f830196704648c15dd546e20195ce15e2d4ee6ca66e363aac47811c4ab scripts/hysteria2.sh
|
||||
b85e6ca11eef15eed8d45fba9e7b4a7eddf6141de4aeceaac1fe7f51d4c9fa78 scripts/kit.sh
|
||||
f214671464e1c2e174e5af9df42c95567a9b4a9141f31a39f7df2e971e89d240 scripts/kit-sub.py
|
||||
75cc7bc4e048c43989f66ca3b9e9addff8d7a434e651daba6db894e18109b45b scripts/kit-local.sh
|
||||
ba87dfba9abc3417092a2c2ec8da1b0332397e9952ead213e9367692f2849459 scripts/3x-ui-local.sh
|
||||
3acd832d329f916790a07153fcc6b7cbdbeb6958131e60751094c059e4fb0f76 VERSION
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABA0E1E05SsGMOSZAwROAfE/KJeG9tZVb3Fj7YS+4KFh07863
|
||||
1Z59OMaOqnsSwoF/TD98jH9ZyU/ovYOjajHpgDDw==
|
||||
AAALc3NoLWVkMjU1MTkAAABADBMIIRfAe53BhNokGqyMTekQwEmlBZ8HdhkKUXjRPv0ofv
|
||||
u4/0GW5p5d2QnwShb7+zS/Dr+LekQ2SMQEBvVbAA==
|
||||
-----END SSH SIGNATURE-----
|
||||
|
||||
+212
-111
@@ -35,16 +35,16 @@
|
||||
# --no-time не запускать kit fix
|
||||
# --no-check не запускать в конце kit check
|
||||
# --clean убрать прошлую установку и поставить заново (SSH не трогает)
|
||||
# --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети)
|
||||
# --verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
# --clean снести прошлую установку и поставить заново (SSH не трогает)
|
||||
--dry-run показать план и ничего не делать
|
||||
# --dry-run показать план и ничего не делать
|
||||
# -h | --help эта справка
|
||||
#
|
||||
# Наш файл: авторские релизы его не перезаписывают, а правки форка живут в scripts/kit-local.sh.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
KIT_VERSION="1.2.9" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
KIT_VERSION="1.2.1.6" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||
NS="3x-ui-kit-release"
|
||||
SIG_ID="releases@3x-ui-kit"
|
||||
@@ -78,11 +78,12 @@ usage() {
|
||||
--no-harden не трогать ping и автообновления
|
||||
--ssh-port N перевести SSH на порт N (прежний закроется после проверки входа)
|
||||
--yes ни о чём не спрашивать (для автоматизации)
|
||||
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
|
||||
не трогать авторскую страницу (то же: --no-site)
|
||||
--no-sub не показывать в конце подписку и QR-код
|
||||
--clean снести прошлую установку и поставить заново (если прошлый запуск оборвался)
|
||||
--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 показать план и ничего не делать
|
||||
@@ -96,6 +97,7 @@ INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no;
|
||||
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
|
||||
show_sub=yes # в самом конце показать подписку, QR-код и ссылки
|
||||
clean=no # --clean: убрать прошлую установку перед новой
|
||||
pick_sni=yes # показывать меню выбора сайта маскировки по результатам скана
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--harden) harden=yes ;;
|
||||
@@ -108,6 +110,7 @@ while (($#)); do
|
||||
--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 ;;
|
||||
@@ -140,6 +143,8 @@ if [[ $dry == yes ]]; then
|
||||
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 и автообновления"
|
||||
@@ -174,7 +179,7 @@ say "беру релиз v$KIT_VERSION и проверяю подпись"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
raw="$GITEA/raw/tag/v$KIT_VERSION"
|
||||
for f in SHA256SUMS SHA256SUMS.sig scripts/3x-ui.sh; do
|
||||
for f in SHA256SUMS SHA256SUMS.sig; do
|
||||
mkdir -p "$tmp/$(dirname "$f")"
|
||||
curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)"
|
||||
done
|
||||
@@ -185,7 +190,19 @@ sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp
|
||||
[[ $(printf '%s' "$sum" | wc -c) -eq 64 ]] || die "в SHA256SUMS нет установщика — релиз собран неверно"
|
||||
got=$(sha256sum "$tmp/scripts/3x-ui.sh" | awk '{print $1}')
|
||||
[[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю"
|
||||
# Качаем все остальные файлы релиза и сверяем каждый с подписанным списком. Раньше брали только
|
||||
# установщик, поэтому kit-local.sh (в нём наша страница-заглушка) отсутствовал, и подготовка
|
||||
# заглушки до установщика падала с «kl_site_html: command not found».
|
||||
while read -r sum2 name2; do
|
||||
case $sum2 in \#* | "") continue ;; esac
|
||||
[[ $name2 == scripts/3x-ui.sh ]] && continue
|
||||
mkdir -p "$tmp/$(dirname "$name2")"
|
||||
curl -fsSL --retry 3 -m 60 -o "$tmp/$name2" "$raw/$name2" || die "не скачался $name2 из $raw (Gitea доступна с сервера?)"
|
||||
got2=$(sha256sum "$tmp/$name2" | awk '{print $1}')
|
||||
[[ $got2 == "$sum2" ]] || die "SHA256 не совпал у $name2 — не запускаю"
|
||||
done <"$tmp/SHA256SUMS"
|
||||
bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса"
|
||||
[[ -r $tmp/scripts/kit-local.sh ]] || die "в релизе нет scripts/kit-local.sh — сборка неполная"
|
||||
printf ' ✓ подпись сошлась, SHA256 установщика совпал\n'
|
||||
|
||||
if [[ $verify_only == yes ]]; then
|
||||
@@ -271,11 +288,11 @@ ask_protocols() { # печатает итоговый список через з
|
||||
interactive || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
|
||||
proto_menu
|
||||
for i in 1 2 3; do
|
||||
printf '\n%sНомера через запятую (например 1,12) или «все»; Enter — REALITY + AmneziaWG-3.1: %s' "$B" "$N" >&2
|
||||
printf '\n%sНомера через пробел (например 1 12) или «все»; Enter — REALITY + AmneziaWG-3.1: %s' "$B" "$N" >&2
|
||||
ans=""
|
||||
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
|
||||
if picked=$(proto_parse "$ans"); then printf '%s' "$picked"; return 0; fi
|
||||
warn "не понял «$ans» — нужны номера из списка через запятую или «все»"
|
||||
warn "не понял «$ans» — нужны номера из списка через пробел или «все»"
|
||||
done
|
||||
warn "оставляю набор по умолчанию"
|
||||
printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"
|
||||
@@ -289,7 +306,8 @@ ask_protocols() { # печатает итоговый список через з
|
||||
clean_previous() {
|
||||
say "убираю прошлую установку (--clean)"
|
||||
local s p
|
||||
for s in x-ui kit-sub kit-update.timer kit-update.service; do
|
||||
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"
|
||||
@@ -297,7 +315,8 @@ clean_previous() {
|
||||
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-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 \
|
||||
@@ -323,6 +342,14 @@ clean_previous() {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -340,20 +367,9 @@ else
|
||||
fi
|
||||
say "протоколы: $protos — под остальные не создаётся ничего"
|
||||
|
||||
[[ $clean == yes ]] && clean_previous
|
||||
|
||||
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 — что-то пошло не так, смотрите вывод выше"
|
||||
|
||||
# ------------------------------------------------------------------ наши дополнения
|
||||
# ------------------------------------------------------------------ вопросы (всё спросим ДО установки)
|
||||
# После установщика на экране появляются доступы к панели: там вопросы уже неуместны. Поэтому
|
||||
# спрашиваем всё заранее, а действия выполняем после установки (см. «наши дополнения» ниже).
|
||||
|
||||
ask_yes() { # "вопрос" y|n — по умолчанию
|
||||
local q=$1 def=$2 ans=""
|
||||
@@ -365,43 +381,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() {
|
||||
@@ -438,66 +417,188 @@ 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>"$tmp/stub.err" \
|
||||
&& [[ -s /var/www/kit/index.html ]]; then
|
||||
stub_staged=yes
|
||||
rm -f "$tmp/var/www/kit/index.html"
|
||||
say "заглушка: наш сайт №$((stub_n + 1)) — ставится сразу, авторская страница не используется"
|
||||
else
|
||||
warn "не смог подготовить нашу заглушку — поставлю после установки (kit local site). Причина: $(head -c 200 "$tmp/stub.err" 2>/dev/null | tr '\n' ' ')"
|
||||
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"
|
||||
# ------------------------------------------------------------------ итог: коротко и по делу
|
||||
|
||||
if [[ $do_check == yes || $show_sub == yes ]]; then
|
||||
# kit check печатает много строк: человеку нужны только замечания и одна строка «всё ли в порядке».
|
||||
chk=$(kit check 2>&1 || true)
|
||||
problems=$(printf '%s\n' "$chk" | grep -cE '^❌|^⚠|^!' || true)
|
||||
[[ $problems == 0 ]] || printf '%s\n' "$chk" | grep -E '^❌|^⚠|^!' | sed 's/^/ /'
|
||||
ports_open=""
|
||||
command -v ufw >/dev/null 2>&1 && ports_open=$(ufw status 2>/dev/null | awk '$2 == "ALLOW" { printf "%s ", $1 }')
|
||||
ip_show=$(hostname -I 2>/dev/null | awk '{ print $1 }')
|
||||
os_show=$( . /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}" )
|
||||
cert_show="без сертификата: панель открывается через SSH-туннель"
|
||||
if [[ -s /root/cert/ip/fullchain.pem ]]; then
|
||||
if openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
|
||||
cert_show="${G}Let's Encrypt на IP — доверенный, браузер не ругается${N}"
|
||||
else
|
||||
cert_show="${Y}самоподписанный — браузер предупредит${N}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
protos_show=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
cat <<EOF
|
||||
|
||||
# ------------------------------------------------------------------ проверка
|
||||
${B}────────────── Установка завершена ──────────────${N}
|
||||
сборка: 3X-UI_autoVPS $KIT_VERSION (канал: наш Gitea)
|
||||
сервер: ${ip_show:-?}${os_show:+ · $os_show}
|
||||
протоколы: ${protos_show:-—}
|
||||
сертификат: $cert_show
|
||||
проверка: $([[ $problems == 0 ]] && echo 'замечаний нет' || echo "замечаний: $problems (подробности: kit check)")
|
||||
${B}───────────────────────────────────────────────────${N}
|
||||
|
||||
if [[ $do_check == yes ]]; then
|
||||
say "проверяю, что получилось"
|
||||
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix"
|
||||
kit local hardening
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea).
|
||||
|
||||
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||
${B}Доступ к панели${N} (файл /root/3x-ui.txt, виден только root)
|
||||
EOF
|
||||
|
||||
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
||||
|
||||
if [[ $show_sub == yes ]]; then
|
||||
say "панель 3X-UI: адрес, логин и пароль"
|
||||
if [[ -s /root/3x-ui.txt ]]; then
|
||||
printf ' файл: /root/3x-ui.txt (виден только root). Сохраните данные себе:\n'
|
||||
sed 's/^/ /' /root/3x-ui.txt
|
||||
# Автор пишет эти строки без ведущих пробелов (RESULT=/root/3x-ui.txt), поэтому ищем их как есть.
|
||||
if grep -aqE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt 2>/dev/null; then
|
||||
grep -aE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt | sed 's/^[[:space:]]*/ /'
|
||||
printf ' файл целиком (там же подписка и ссылки по протоколам): /root/3x-ui.txt\n'
|
||||
else
|
||||
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
|
||||
warn "данных панели в /root/3x-ui.txt нет — смотрите вывод установщика выше"
|
||||
fi
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
# ------------------------------------------------------------------ подписка, QR-код и доступы
|
||||
${B}Ссылки для подключения и QR-код${N} — в меню kit
|
||||
kit → Пользователи → Показать ссылки из консоли: kit local links
|
||||
EOF
|
||||
if [[ -n $ports_open ]]; then
|
||||
cat <<EOF
|
||||
|
||||
# Показываем в самом конце, после всех проверок: это то, что человек уносит с собой.
|
||||
# Печатает авторская команда (подписка + QR-код через qrencode + ссылки по протоколам),
|
||||
# поэтому вывод не расходится с тем, что показывает `kit` потом.
|
||||
if [[ $show_sub == yes ]]; then
|
||||
sub_user=admin
|
||||
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
|
||||
[[ ${INSTALLER_ARGS[_i]} == --user ]] && sub_user=${INSTALLER_ARGS[_i + 1]:-admin}
|
||||
done
|
||||
say "подписка и доступы — это то, что стоит сохранить"
|
||||
printf ' подписка — одна ссылка на все устройства; QR-код ниже наводите камерой в приложении\n'
|
||||
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
||||
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
||||
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
||||
if ! kit user link "$sub_user" --all; then
|
||||
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
||||
${B}Порты${N}
|
||||
открыто: $ports_open
|
||||
что нужно и что лишнее: kit local ports закрыть лишние: kit local ports --close-extra
|
||||
EOF
|
||||
fi
|
||||
else
|
||||
say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all"
|
||||
if [[ -n ${ssh_port:-} ]]; then
|
||||
cat <<EOF
|
||||
|
||||
${Y}SSH переведён на порт $ssh_port.${N} Прежние (${ssh_ports_before:-22}) пока открыты намеренно:
|
||||
1) проверьте вход во втором окне: ssh -p $ssh_port root@${ip_show:-<IP>}
|
||||
2) закройте прежние порты: kit local ssh --close-old $ssh_port
|
||||
EOF
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
EOF
|
||||
fi
|
||||
|
||||
+526
-125
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||
# останавливается и не заменяет команду hy2.
|
||||
|
||||
KIT_VERSION="1.2.9"
|
||||
KIT_VERSION="1.2.1.6"
|
||||
|
||||
cat >&2 <<'MSG'
|
||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||
|
||||
+109
-142
@@ -73,7 +73,10 @@ kit_local_help() {
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening сводка по защите сервера и что ещё можно включить
|
||||
kit local uninstall снести всё, что поставила установка (SSH не трогает)
|
||||
kit local access доступ к панели, сертификат, ссылки и заглушка — одним экраном
|
||||
kit local links подписка, QR-код и ссылки по протоколам (как в меню kit)
|
||||
kit local ports открытые порты: что нужно, что лишнее и как закрыть
|
||||
kit local uninstall то же, что kit uninstall: снести всё, включая сам kit (SSH не трогает)
|
||||
|
||||
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
|
||||
kit local site поставить случайный сайт из нашего набора (10 штук)
|
||||
@@ -443,12 +446,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 +879,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"
|
||||
@@ -896,6 +903,9 @@ kit_local_cmd() {
|
||||
upgrades | auto) shift; kit_local_upgrades "$@" ;;
|
||||
hardening | hard) shift; kit_local_hardening "$@" ;;
|
||||
uninstall | remove) shift; kit_local_uninstall "$@" ;;
|
||||
links | link) shift; kit_local_links "$@" ;;
|
||||
access) shift; kit_local_access "$@" ;;
|
||||
ports) shift; kit_local_ports "$@" ;;
|
||||
site | stub | stub-site) shift; kit_local_site "$@" ;;
|
||||
*)
|
||||
printf 'Неизвестная команда форка: %s\n' "$1" >&2
|
||||
@@ -918,145 +928,99 @@ kit_local_cmd() {
|
||||
#
|
||||
# kit local uninstall спросит подтверждение словом «удалить»
|
||||
# kit local uninstall --dry-run только покажет, что будет удалено
|
||||
kit_local_uninstall() {
|
||||
kl_args "$@"
|
||||
local arg=${KL_REST[0]:-} ans found
|
||||
local reset_ssh=no
|
||||
case $arg in
|
||||
"") ;;
|
||||
--dry-run) KL_DRY=yes ;;
|
||||
--reset-ssh) reset_ssh=yes ;;
|
||||
*) kl_die "kit local uninstall [--dry-run] [--reset-ssh]"; return 1 ;;
|
||||
esac
|
||||
|
||||
local -a svc=(x-ui kit-sub kit-update.timer kit-update.service)
|
||||
# Список собран по коду установщика (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/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 /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 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' "${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
|
||||
|
||||
if kl_dry; then
|
||||
printf '\n (dry-run) ничего не делаю. Запустить по-настоящему: kit local uninstall\n'
|
||||
return 0
|
||||
fi
|
||||
kl_need_root || return 1
|
||||
|
||||
if [[ -z ${KIT_LOCAL_UNINSTALL_CONFIRMED:-} ]]; then
|
||||
printf '\n%sЧтобы удалить всё перечисленное, напишите слово «удалить»: %s' "${B:-}" "${N:-}"
|
||||
read -r ans </dev/tty 2>/dev/null || ans=""
|
||||
[[ ${ans,,} == "удалить" ]] || { kl_warn "не подтверждено — ничего не удаляю"; return 1; }
|
||||
fi
|
||||
|
||||
kl_step "Останавливаю и отключаю службы"
|
||||
local s
|
||||
for s in "${svc[@]}"; do
|
||||
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"
|
||||
kit_local_ports() { # [--close-extra] — что открыто в ufw, что реально нужно и как закрыть лишнее
|
||||
local close=no a
|
||||
for a in "$@"; do
|
||||
case $a in
|
||||
--close-extra) close=yes ;;
|
||||
--help | -h) printf 'kit local ports [--close-extra] — открытые порты, нужные и лишние\n'; return 0 ;;
|
||||
*) kl_die "kit local ports [--close-extra]"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
command -v ufw >/dev/null 2>&1 || { kl_warn "ufw не установлен — порты смотреть нечем"; return 0; }
|
||||
local -a opened=() needed=() extra=()
|
||||
mapfile -t opened < <(ufw status 2>/dev/null | awk '$2 == "ALLOW" { print $1 }' | sort -u)
|
||||
# Нужные порты: те, что слушают наши подключения (x-ui и ядро Xray), плюс SSH.
|
||||
mapfile -t needed < <({ ss -tlnp 2>/dev/null; ss -ulnp 2>/dev/null; } \
|
||||
| awk '/x-ui|xray/ { print $4 }' | sed 's/.*://' | sort -un; ss -tlnp 2>/dev/null | awk '/sshd/ { print $4 }' | sed 's/.*://' | sort -un)
|
||||
local p q keep
|
||||
for p in "${opened[@]}"; do
|
||||
keep=no
|
||||
case $p in 80/tcp | 443/tcp) keep=yes ;; esac # 443 — работа, 80 — продление сертификата
|
||||
for q in "${needed[@]}"; do [[ $p == "$q/tcp" || $p == "$q/udp" || $p == "$q" ]] && keep=yes; done
|
||||
[[ $keep == yes ]] && continue
|
||||
extra+=("$p")
|
||||
done
|
||||
printf ' открыто в ufw: %s\n' "$(printf '%s ' "${opened[@]}")"
|
||||
if ((${#needed[@]})); then printf ' нужны: %s\n' "$(printf '%s/tcp %s/udp ' "${needed[@]}" "${needed[@]}")"
|
||||
else printf ' нужны: (не определил — смотрите kit check)\n'; fi
|
||||
if ((${#extra[@]})); then
|
||||
printf ' лишние: %s\n' "$(printf '%s ' "${extra[@]}")"
|
||||
printf ' закрыть лишние: kit local ports --close-extra\n'
|
||||
if [[ $close == yes ]]; then
|
||||
for p in "${extra[@]}"; do ufw --force delete allow "$p" >/dev/null 2>&1 && printf ' закрыл %s\n' "$p"; done
|
||||
fi
|
||||
done
|
||||
if kl_have nginx; then
|
||||
local others
|
||||
others=$(find /etc/nginx/conf.d /etc/nginx/sites-enabled /etc/nginx/streams-enabled -mindepth 1 2>/dev/null \
|
||||
| grep -vE 'kit\.conf$|(^|/)default$' | wc -l)
|
||||
if [[ $others -eq 0 ]]; then
|
||||
systemctl disable --now nginx >/dev/null 2>&1 && kl_ok "nginx остановлен (чужих сайтов в нём не было)"
|
||||
KL_NGINX_PURGE=yes
|
||||
else
|
||||
kl_ok "nginx оставляю: в нём есть другие сайты — уберу только наши строки и файлы"
|
||||
KL_NGINX_PURGE=no
|
||||
fi
|
||||
fi
|
||||
|
||||
kl_step "Убираю файлы"
|
||||
local f
|
||||
for f in "${paths[@]}"; do
|
||||
[[ -e $f ]] || continue
|
||||
rm -rf "$f" && kl_ok "удалено: $f" || kl_warn "не удалось удалить: $f"
|
||||
done
|
||||
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 "Возвращаю систему"
|
||||
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 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"
|
||||
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
|
||||
sysctl -q -w net.ipv4.icmp_echo_ignore_all=0 >/dev/null 2>&1 || true
|
||||
|
||||
kl_step "Проверка (теми же условиями, что и установщик)"
|
||||
local left=0
|
||||
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
|
||||
kl_warn "установщик всё ещё считает панель установленной"; left=1
|
||||
fi
|
||||
for s in "${svc[@]}"; do
|
||||
systemctl list-unit-files 2>/dev/null | grep -q "^$s" && { kl_warn "осталась служба: $s"; left=1; }
|
||||
done
|
||||
found=$(ls -d /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
|
||||
/root/3x-ui.txt /root/cert /etc/x-ui /usr/local/x-ui 2>/dev/null | 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; }
|
||||
if ((left)); then
|
||||
kl_warn "что-то осталось — пришлите этот вывод, поправим список в kit local uninstall"
|
||||
else
|
||||
kl_ok "чисто: от установки не осталось ни файлов, ни служб, порт 443 свободен"
|
||||
printf ' лишних нет\n'
|
||||
fi
|
||||
kl_ok "SSH не тронут (если не указан --reset-ssh)"
|
||||
kl_step "Готово: сервер в состоянии до установки, можно ставить заново"
|
||||
}
|
||||
kit_local_access() { # доступ к панели, ссылки на подключения, заглушка и сертификат — одним экраном
|
||||
local u=${1:-} till2
|
||||
printf '%s\n' "${B:-}Доступ к панели 3X-UI${N:-} (файл /root/3x-ui.txt, виден только root)"
|
||||
if grep -aqE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt 2>/dev/null; then
|
||||
grep -aE '^[[:space:]]*(Панель|Логин|Пароль):' /root/3x-ui.txt | sed 's/^[[:space:]]*/ /'
|
||||
else
|
||||
kl_warn "данных панели в /root/3x-ui.txt нет"
|
||||
fi
|
||||
printf '\n%s\n' "${B:-}Сертификат панели${N:-}"
|
||||
if [[ -s /root/cert/ip/fullchain.pem ]] && openssl x509 -in /root/cert/ip/fullchain.pem -noout -issuer 2>/dev/null | grep -q "Let's Encrypt"; then
|
||||
printf ' Let'"'"'s Encrypt на IP — доверенный, браузер не ругается\n'
|
||||
elif [[ -s /root/cert/ip/fullchain.pem ]]; then
|
||||
printf ' самоподписанный — браузер предупредит, подписка в приложениях может не открыться\n'
|
||||
else
|
||||
printf ' сертификата на IP нет: панель открывается только через SSH-туннель\n'
|
||||
fi
|
||||
till2=$(grep -rhoE 'retry after [0-9-]+ [0-9:]+ UTC' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed 's/^retry after //')
|
||||
[[ -n $till2 ]] && printf ' лимит Let'"'"'s Encrypt: повтор выпуска возможен после %s (затем запустите установщик снова)\n' "$till2"
|
||||
printf '\n%s\n' "${B:-}Ссылки на подключения и QR-код${N:-}"
|
||||
if [[ -z $u ]]; then u=$(kit user list 2>/dev/null | awk 'NR > 1 && NF { print $1; exit }'); u=${u:-admin}; fi
|
||||
local now; now=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
if [[ $(wc -w <<<"$now") -gt 1 ]]; then kit user link "$u" --all; else kit user link "$u"; fi
|
||||
printf '\n%s\n' "${B:-}Заглушка (что видит посторонний, открыв адрес сервера)${N:-}"
|
||||
kit local site --show
|
||||
printf ' посмотреть снаружи: откройте https://<IP сервера>/ в браузере или curl -k https://<IP>/
|
||||
'
|
||||
}
|
||||
|
||||
kit_local_links() { # [имя] — подписка, QR-код и ссылки по протоколам (то же, что «kit → Пользователи → Показать ссылки»)
|
||||
local u=${1:-} now
|
||||
if [[ -z $u ]]; then
|
||||
u=$(kit user list 2>/dev/null | awk 'NR > 1 && NF { print $1; exit }')
|
||||
u=${u:-admin}
|
||||
fi
|
||||
printf ' пользователь: %s\n' "$u"
|
||||
now=$(tr '\n' ' ' </etc/kit/kl-protos 2>/dev/null || true)
|
||||
# Ссылки на каждый протокол — только если протоколов больше одного; при одном достаточно подписки.
|
||||
if [[ $(wc -w <<<"$now") -gt 1 ]]; then kit user link "$u" --all; else kit user link "$u"; fi
|
||||
}
|
||||
|
||||
kit_local_uninstall() {
|
||||
# Наш шов: логику удаления отдаём авторской команде «kit uninstall». Она снимает и сам kit, чтобы на
|
||||
# сервере не осталось следов, поэтому своя реализация здесь больше не нужна — команда оставлена
|
||||
# синонимом, чтобы привычное «kit local uninstall» продолжало работать.
|
||||
local a=() x
|
||||
for x in "$@"; do
|
||||
case $x in
|
||||
--dry-run | --yes | --no-backup) a+=("$x") ;;
|
||||
--help | -h)
|
||||
printf 'kit local uninstall [--dry-run] [--yes] [--no-backup] — то же, что kit uninstall\n'
|
||||
return 0 ;;
|
||||
*) kl_die "kit local uninstall [--dry-run] [--yes] [--no-backup]"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
[[ -x /usr/local/bin/kit ]] || { kl_die "не нашёл /usr/local/bin/kit — удалить вручную: kit uninstall"; return 1; }
|
||||
printf ' удаляю по авторской логике: /usr/local/bin/kit uninstall %s\n' "${a[*]}"
|
||||
exec /usr/local/bin/kit uninstall "${a[@]}"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- полная справка `kit help`
|
||||
@@ -1105,7 +1069,10 @@ ${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то ж
|
||||
kit local site [--list | N | --show] сайт-заглушка: случайный ИТ-сайт, список, конкретный, какой стоит
|
||||
|
||||
Проверка и починка
|
||||
kit local uninstall снести установку целиком (панель, туннель, службы kit, сайт, сертификаты); SSH не трогает
|
||||
kit local access [имя] доступ к панели, сертификат, ссылки и заглушка одним экраном
|
||||
kit local links [имя] подписка, QR-код и ссылки по протоколам (по умолчанию — первый пользователь)
|
||||
kit local ports [--close-extra] открытые порты: нужные, лишние; --close-extra закрывает лишние
|
||||
kit local uninstall то же, что kit uninstall: снести установку целиком и сам kit; SSH не трогает
|
||||
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
|
||||
kit fix [--dry-run] безопасные починки: время, службы, конфиги
|
||||
kit local hardening сводка по защите и что ещё можно включить
|
||||
|
||||
+52
-10
@@ -39,6 +39,16 @@ CLASH_UA = re.compile(r"clash|mihomo|flclash|stash|nyanpasu|meta", re.I)
|
||||
# AmneziaWG добавляем только приложениям на ядре Mihomo. Karing, Hiddify и другие на sing-box
|
||||
# тоже могут просить формат Clash (Karing так и делает), но AmneziaWG не умеют.
|
||||
NO_AWG_UA = re.compile(r"karing|hiddify|nekobox|sing-?box|husi|stash|shadowrocket|v2box|streisand|happ|loon|surge|quantumult", re.I)
|
||||
# Hiddify 4.x в строке приложения называет себя и Clash, поэтому панель отдаёт ему Clash-YAML, а он обрезает в нём пробелы и
|
||||
# выдаёт «Unable to determine config format». Панели такой клиент представляем без слов про Clash: она отдаст обычный
|
||||
# список ссылок (base64), который Hiddify читает без проблем.
|
||||
HIDDIFY_UA = re.compile(r"hiddify", re.I)
|
||||
|
||||
|
||||
def upstream_ua(ua):
|
||||
return CLASH_UA.sub("", ua).strip() if HIDDIFY_UA.search(ua) else ua
|
||||
|
||||
|
||||
SUB_ID = re.compile(r"^[A-Za-z0-9_.@-]{1,64}$")
|
||||
# Страница подписки для браузера (React от 3X-UI) подгружает скрипты и стили из «<путь>/assets/…»: пропускаем только такие файлы.
|
||||
ASSET = re.compile(r"^assets/[A-Za-z0-9_.-]{1,128}\.(js|css|woff2?|svg|png|ico|map)$")
|
||||
@@ -65,6 +75,11 @@ DOH_DEFAULT = ["https://1.1.1.1/dns-query", "https://8.8.8.8/dns-query"]
|
||||
PRIVATE_NETS = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]
|
||||
# Дополнительные пути, которые панель показывает на странице подписки (Clash и JSON): обрабатываются так же, как основной.
|
||||
EXTRA = ["/" + x.strip("/") + "/" for x in CONF.get("extra_paths", []) if isinstance(x, str) and re.fullmatch(r"/?[A-Za-z0-9_-]+/?", x)]
|
||||
# После kit net site прежнее имя сайта REALITY ещё неделю принимается сервером (льготный срок), а панель берёт имя для ссылки
|
||||
# случайно из списка. Карта «прежнее имя → нынешнее» от kit: в подписку уходит только нынешнее. Нет файла – ничего не меняем.
|
||||
GRACE_FILE = os.environ.get("KIT_SUB_GRACE") or "/etc/kit-sub/sni-grace.json"
|
||||
SITE_NAME = re.compile(r"[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+")
|
||||
LINK_SNI = re.compile(r"([?&]sni=)([^&#\s]*)")
|
||||
|
||||
|
||||
def log(msg):
|
||||
@@ -94,10 +109,32 @@ def fix_userinfo(value):
|
||||
JSON_FIX_MAX = 2 * 1024 * 1024
|
||||
|
||||
|
||||
def fix_vless_encryption(body):
|
||||
def load_grace():
|
||||
"""Карта «прежнее имя сайта → нынешнее» (файл от kit). Только имена сайтов, остальное отбрасываем."""
|
||||
try:
|
||||
with open(GRACE_FILE, "rb") as f:
|
||||
raw = f.read(65537)
|
||||
data = json.loads(raw.decode("utf-8")) if len(raw) <= 65536 else {}
|
||||
except (OSError, ValueError, UnicodeError, RecursionError):
|
||||
return {}
|
||||
if not isinstance(data, dict):
|
||||
return {}
|
||||
return {k: v for k, v in list(data.items())[:100] if isinstance(k, str) and isinstance(v, str)
|
||||
and len(k) <= 253 and len(v) <= 253 and SITE_NAME.fullmatch(k) and SITE_NAME.fullmatch(v)}
|
||||
|
||||
|
||||
def grace_link(line, grace):
|
||||
"""Ссылка REALITY с прежним именем сайта → с нынешним; остальные ссылки как есть."""
|
||||
if not grace or not line.startswith("vless://") or "security=reality" not in line:
|
||||
return line
|
||||
return LINK_SNI.sub(lambda m: m.group(1) + grace.get(m.group(2), m.group(2)), line)
|
||||
|
||||
|
||||
def fix_vless_encryption(body, grace=None):
|
||||
"""JSON-подписка панели: у vless-пользователя без encryption (нет поля или пусто) ставим "none" – Xray иначе
|
||||
не принимает конфиг. Две формы: vnext[].users[] и плоская (settings.id/encryption). Непустое значение
|
||||
(например, mlkem) не трогаем. Не разобралось, слишком большое или менять нечего – тело как пришло."""
|
||||
(например, mlkem) не трогаем. Прежнее (льготное) имя сайта REALITY – на нынешнее (grace).
|
||||
Не разобралось, слишком большое или менять нечего – тело как пришло."""
|
||||
if len(body) > JSON_FIX_MAX:
|
||||
return body
|
||||
try:
|
||||
@@ -116,6 +153,11 @@ def fix_vless_encryption(body):
|
||||
for cfg in (data if isinstance(data, list) else [data]):
|
||||
outs = cfg.get("outbounds") if isinstance(cfg, dict) else None
|
||||
for o in outs if isinstance(outs, list) else []:
|
||||
ss = o.get("streamSettings") if isinstance(o, dict) else None
|
||||
rs = ss.get("realitySettings") if isinstance(ss, dict) else None
|
||||
if grace and isinstance(rs, dict) and rs.get("serverName") in grace:
|
||||
rs["serverName"] = grace[rs["serverName"]]
|
||||
changed = True
|
||||
st = o.get("settings") if isinstance(o, dict) and o.get("protocol") == "vless" else None
|
||||
if not isinstance(st, dict):
|
||||
continue
|
||||
@@ -127,9 +169,9 @@ def fix_vless_encryption(body):
|
||||
return json.dumps(data, ensure_ascii=False).encode() if changed else body
|
||||
|
||||
|
||||
def strip_links(body):
|
||||
def strip_links(body, grace=None):
|
||||
"""Список ссылок (base64 или текст) без vpn:// и tg:// – их не умеет ни одно VPN-приложение
|
||||
со ссылками: vpn:// – конфиг для AmneziaVPN, tg:// – прокси для Telegram."""
|
||||
со ссылками: vpn:// – конфиг для AmneziaVPN, tg:// – прокси для Telegram. Прежнее имя сайта REALITY – на нынешнее."""
|
||||
text = body.decode("utf-8", "replace").strip()
|
||||
encoded = "://" not in text
|
||||
if encoded:
|
||||
@@ -137,7 +179,7 @@ def strip_links(body):
|
||||
text = base64.b64decode(text + "=" * (-len(text) % 4)).decode("utf-8", "replace")
|
||||
except ValueError:
|
||||
return body
|
||||
lines = [l for l in text.splitlines() if l.strip() and not l.startswith(("vpn://", "tg://"))]
|
||||
lines = [grace_link(l, grace) for l in text.splitlines() if l.strip() and not l.startswith(("vpn://", "tg://"))]
|
||||
out = "\n".join(lines)
|
||||
return base64.b64encode(out.encode()).decode().encode() if encoded else out.encode()
|
||||
|
||||
@@ -416,7 +458,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
ua = self.headers.get("User-Agent", "")
|
||||
host = CONF.get("link_host") or self.headers.get("Host", CONF.get("host", ""))
|
||||
accept = self.headers.get("Accept", "")
|
||||
code, headers, body = upstream(sub_id, ua, host, accept, prefix)
|
||||
code, headers, body = upstream(sub_id, upstream_ua(ua), host, accept, prefix)
|
||||
if code is None:
|
||||
return self.send_html_error(502)
|
||||
|
||||
@@ -435,9 +477,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
elif code == 200 and ("json" in headers.get("content-type", "") or (
|
||||
"text/plain" in headers.get("content-type", "") and body.lstrip()[:1] in (b"[", b"{"))):
|
||||
# Панель 3.9 отдаёт JSON-подписку с Content-Type text/plain – определяем по телу.
|
||||
body = fix_vless_encryption(body)
|
||||
body = fix_vless_encryption(body, load_grace())
|
||||
elif code == 200 and "text/plain" in headers.get("content-type", ""):
|
||||
body = strip_links(body)
|
||||
body = strip_links(body, load_grace())
|
||||
if code == 200 and clash and CONF.get("auto", True):
|
||||
body = add_auto(body)
|
||||
if code == 200 and clash and CONF.get("dns", True):
|
||||
@@ -546,7 +588,7 @@ def main():
|
||||
cert, key = os.path.join(CREDS, "cert.pem"), os.path.join(CREDS, "key.pem")
|
||||
if not cert:
|
||||
srv = Server((CONF.get("listen", "127.0.0.1"), int(CONF["port"])), Handler)
|
||||
log(f"kit-sub слушает http://{CONF.get('listen', '127.0.0.1')}:{CONF['port']}{PATH} (TLS снимает nginx)")
|
||||
log(f"kit-sub слушает {CONF.get('listen', '127.0.0.1')}:{CONF['port']} (TLS снимает nginx)") # путь подписки в журнал не пишем: он секретный
|
||||
srv.serve_forever()
|
||||
return
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
@@ -571,7 +613,7 @@ def main():
|
||||
threading.Thread(target=reload_cert, daemon=True).start()
|
||||
srv = Server((CONF.get("listen", "0.0.0.0"), int(CONF["port"])), Handler)
|
||||
srv.ssl_ctx = ctx
|
||||
log(f"kit-sub слушает {CONF.get('listen', '0.0.0.0')}:{CONF['port']}{PATH}")
|
||||
log(f"kit-sub слушает {CONF.get('listen', '0.0.0.0')}:{CONF['port']}")
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
|
||||
+896
-115
File diff suppressed because it is too large
Load Diff
+521
-157
@@ -86,166 +86,530 @@ 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:bNjI10eGVGjdFeDJnAHvUFi73rlrXVy6vKFf8lmNyxU (itsnotkubrick, 2026-10-14; прежний ключ SHA256:VDuGgJ8d… утерян, снят)\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEWDFGq2Wno/ADRS2E6UKsgFbUSLvaiPIfRBvSEGJr81 3X-UI KIT releases"\n',
|
||||
False,
|
||||
' # Наш шов: здесь только ключ форка (hacker07). Авторские ключи сняты намеренно — сервер обязан\n # принимать обновления лишь из нашего канала (git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS).\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)" "10. Удалить KIT с сервера" "0. Выход"\n',
|
||||
False,
|
||||
' "7. Резервная копия" "8. Версии" "" "9. Доступ к панели, ссылки и заглушка" "10. Панель x-ui" "11. Удалить KIT с сервера" "0. Выход"\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 10) run_action cmd_uninstall; pause ;;\n',
|
||||
False,
|
||||
' # Наш шов: пункт 9 — доступ к панели, ссылки на подключения и состояние заглушки в одном месте.\n 9) kit local access; pause ;;\n 10) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 11) run_action cmd_uninstall; pause ;;\n',
|
||||
'авто-шов: replace, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' echo "${B}Полное удаление 3X-UI KIT${N}"\n',
|
||||
False,
|
||||
' echo "${B}Полное удаление 3X-UI_autoVPS${N}"\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'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',
|
||||
' [[ -x $acme ]] || { warn "Не нашёл acme.sh, которым установщик 3X-UI получает сертификаты: не могу выпустить сертификат для $DOMAIN."; return 1; }\n',
|
||||
False,
|
||||
' [[ -x $acme ]] || { warn "Не нашёл acme.sh, которым установщик 3X-UI получает сертификаты: не могу выпустить сертификат для $DOMAIN."; return 1; }\n # Наш шов: лимит Let\'s Encrypt считается на точный набор имён (5 сертификатов за 7 суток). Если у acme.sh\n # уже лежит годный сертификат на этот домен — берём его и не заказываем новый, лишний заказ упирается в лимит.\n local need_issue=yes\n if [[ -s /root/.acme.sh/${DOMAIN}_ecc/fullchain.cer ]] \\\n && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkhost "$DOMAIN" 2>/dev/null | grep -q \'does match\' \\\n && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkend 86400 >/dev/null 2>&1; then\n ok "у acme.sh уже есть годный сертификат для ${B}$DOMAIN${N} — беру его, новый не заказываю"\n need_issue=no\n fi\n',
|
||||
'авто-шов: insert, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' [[ $rc == 0 || $rc == 2 ]] || { warn "Let\'s Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"; return 1; }\n',
|
||||
False,
|
||||
' [[ $rc == 0 || $rc == 2 ]] || {\n # Наш шов: лимит Let\'s Encrypt — частая причина, и она не про сервер. Говорим прямо и с датой повтора.\n if grep -qE \'rateLimited|too many certificates\' "$log"; then\n local till\n till=$(grep -oE \'retry after [0-9-]+ [0-9:]+ UTC\' "$log" | tail -1 | sed \'s/^retry after //\')\n warn "Лимит Let\'s Encrypt: на имя ${B}$DOMAIN${N} за 7 суток уже выпущено 5 сертификатов${till:+ — повтор возможен после $till}. С вашим сервером всё в порядке: DNS, порты и ufw не при чём. Подождите до этой даты либо возьмите другое имя (например node3.…): лимит считается на точный набор имён."\n else\n warn "Let\'s Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"\n fi\n return 1\n }\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' local san="IP:$HOST"\n [[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"\n',
|
||||
False,
|
||||
' local san="IP:$HOST"\n [[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"\n # Наш шов: если причина — лимит Let\'s Encrypt, говорим это прямо и с датой повтора, как для домена.\n # Иначе человек читает «частые причины» и ищет проблему у хостера, которой нет.\n local till\n till=$(grep -rhoE \'retry after [0-9-]+ [0-9:]+ UTC\' /var/log/3x-ui-install.log "$HOME"/.acme.sh/*/ca 2>/dev/null | tail -1 | sed \'s/^retry after //\')\n if grep -rqsE \'rateLimited|too many certificates\' /var/log/3x-ui-install.log 2>/dev/null; then\n warn "Причина: лимит Let\'s Encrypt — на этот IP за 7 суток уже выпущено 5 сертификатов${till:+; повтор возможен после $till}. Это ограничение Let\'s Encrypt, а не вашего сервера: порт 80 и файрвол тут не при чём."\n else\n',
|
||||
'авто-шов: insert, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' echo " – сбой у самого Let\'s Encrypt (лог: /var/log/3x-ui-install.log)."\n',
|
||||
False,
|
||||
' echo " – сбой у самого Let\'s Encrypt (лог: /var/log/3x-ui-install.log)."\n } >&2\n fi\n {\n',
|
||||
'авто-шов: insert, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
|
||||
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 # Наш шов: последним пунктом — автовыбор из этого списка (раньше выбор был только по номеру).\n printf \' %d - автовыбор из списка\\n\' "$((${#known[@]} + 1))" >&2\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'sni:%s\' "${known[10#$ans - 1]}"; return 0\n fi\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans == ${#known[@]} + 1)); then\n local autopick="${known[RANDOM % ${#known[@]}]}"\n printf \' автовыбор: %s\\n\' "$autopick" >&2\n printf \'sni:%s\' "$autopick"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'own:%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n',
|
||||
'авто-шов: insert, строк 3',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && -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 && -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 # Наш шов: домен вводится заново, пока он не указывает на этот сервер. Раньше при промахе\n # установка молча маскировалась под чужой сайт, а при домене из флага — обрывалась.\n # A-запись у регистратора расходится не мгновенно, поэтому первым пунктом — «проверить снова».\n while :; do\n if domain_points_here "$DOMAIN"; then\n SNI=$DOMAIN\n ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let\'s Encrypt выпустит установщик)"\n # Наш шов: спрашиваем, где открывать панель и подписку, и при интерактивном вводе домена тоже.\n # У автора этот вопрос задавался только при домене из флага --domain, а при выборе «ввести свой\n # домен» в меню маскировки — нет, и панель молча оставалась по IP с самоподписанным сертификатом.\n panel_on_choice\n break\n fi\n echo " Домен ${B}$DOMAIN${N} пока не указывает на этот сервер." >&2\n echo " ${B}1${N} проверить снова A-запись расходится по DNS от нескольких минут до нескольких часов" >&2\n echo " ${B}2${N} ввести другой домен" >&2\n echo " ${B}3${N} без своего домена: маскировка соседом по подсети" >&2\n ask_tty " Выбор [1]: " || break\n case ${REPLY//[[:space:]]/} in\n 2) ask_tty " адрес сайта (например vpn.example.com): " || break\n local d=${REPLY//[[:space:]]/}\n [[ -n $d ]] && DOMAIN=$d ;;\n 3) own_sni=no; DOMAIN=""; break ;;\n *) sleep 5 ;;\n esac\n done\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',
|
||||
' issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя. Исправьте причину и запустите скрипт снова."\n',
|
||||
False,
|
||||
' # Наш шов: провал сертификата не обрывает установку. Человек может повторить попытку (DNS доехал,\n # лимит Let\'s Encrypt кончился) или ввести другое имя — установка продолжится с него же.\n while ! issue_domain_cert; do\n echo\n echo " ${B}Что делать дальше?${N}"\n echo " ${B}1${N} повторить попытку если ждали обновления A-записи или лимита Let\'s Encrypt"\n echo " ${B}2${N} ввести другой домен проверим, что он указывает на этот сервер, и повторим"\n echo " ${B}3${N} остановиться поставить без своего домена: перезапустите и выберите соседа по подсети"\n ask_tty " Выбор [1]: " || die "Сертификат для $DOMAIN не выпущен. Подробности: /var/log/kit-domain-cert.log"\n case ${REPLY//[[:space:]]/} in\n 2) ask_tty " адрес сайта (например vpn.example.com): " || continue\n local d=${REPLY//[[:space:]]/}\n [[ -n $d ]] || continue\n if domain_points_here "$d"; then DOMAIN=$d; SNI=$DOMAIN\n else warn "оставляю прежний домен $DOMAIN — новый не указывает на этот сервер"; fi ;;\n 3) # Наш шов: продолжаем без своего домена прямо здесь — маскировка соседом по подсети\n # (или известным сайтом), панель и подписка по IP. Перезапускать установку не нужно.\n local nb2\n nb2=$(nearby_sites 2>/dev/null | shuf | head -1)\n [[ -n $nb2 ]] || nb2=dl.google.com\n DOMAIN=""; PANEL_ON=ip; LINK_HOST=$HOST\n SNI=$nb2; SNI2=${SNI2:-$nb2}\n ok "продолжаю без своего домена: маскировка ${B}$SNI${N}, панель и подписка — по IP"\n break ;;\n *) sleep 5 ;;\n esac\n done\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'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',
|
||||
' } >"$RESULT"\n\n echo\n echo "${G}${B}Готово! Сервер работает.${N}"\n [[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let\'s Encrypt продлевается сам."\n [[ $SELF_IP_CERT == yes ]] && later "Сертификат на IP самоподписанный: пользуйтесь ссылками на отдельные подключения, подписка в приложениях может не открыться."\n',
|
||||
False,
|
||||
' } >"$RESULT"\n\n echo\n',
|
||||
'авто-шов: delete, строк 6',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' 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 else\n /usr/local/bin/kit __links "$NAME" "$SUBID" || true\n fi\n local -a tp=(\n "Управление сервером: введите команду ${B}kit${N} – откроется меню. Запоминать команды не нужно."\n "Ссылки на отдельные протоколы (REALITY, Hysteria2, Telegram и другие): kit → Пользователи → Показать ссылки."\n "Добавить пользователя: kit → Пользователи → Добавить."\n "Резервная копия: kit → Резервная копия. Храните её как пароль.")\n tips "Что дальше" "${tp[@]}"\n',
|
||||
False,
|
||||
' echo "${G}${B}Установка завершена.${N} Доступы к панели, подписка и QR-код — в самом конце вывода."\n',
|
||||
'авто-шов: replace, строк 25',
|
||||
),
|
||||
(
|
||||
'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',
|
||||
' [[ $(LC_ALL=C tr -d \'\\n -~\' <"$f" | wc -c) -eq 0 ]] \\\n || die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||
False,
|
||||
' [[ $(LC_ALL=C tr -d \'\\n -~\' <"$f" | wc -c) -eq 0 ]] \\\n || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||
'авто-шов: replace, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
' [[ $rc -eq 1 ]] \\\n || die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||
False,
|
||||
' [[ $rc -eq 1 ]] \\\n || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||
'авто-шов: replace, строк 2',
|
||||
),
|
||||
(
|
||||
'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 продолжать нельзя — причина выше, подробности в /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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user