Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f3db5b04c | ||
|
|
8fd5572450 | ||
|
|
01ab468e24 | ||
|
|
b20fd6af77 | ||
|
|
bc18fd354e | ||
|
|
25e25232b2 | ||
|
|
6a9a6ed07e | ||
|
|
d127a92785 | ||
|
|
f38aeefcbc | ||
|
|
d0bfb2dc38 | ||
|
|
a0df115616 | ||
|
|
f4cf157293 | ||
|
|
73754dbc01 | ||
|
|
c261d27b80 | ||
|
|
fa9e7225ea | ||
|
|
4a9e452e3e | ||
|
|
d034e207da |
+96
-18
@@ -1,3 +1,93 @@
|
|||||||
|
## 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: выбор сайта маскировки по результатам скана + снос, который не обрывается
|
## v1.2.14: выбор сайта маскировки по результатам скана + снос, который не обрывается
|
||||||
|
|
||||||
* **Появилось меню маскировки.** Сначала один скан подсети (~250 обращений к 443 соседних адресов),
|
* **Появилось меню маскировки.** Сначала один скан подсети (~250 обращений к 443 соседних адресов),
|
||||||
@@ -10,7 +100,6 @@
|
|||||||
возвращает 1) убивал скрипт сразу после остановки служб — файлы и `/usr/local/bin/kit` оставались,
|
возвращает 1) убивал скрипт сразу после остановки служб — файлы и `/usr/local/bin/kit` оставались,
|
||||||
поэтому `kit help` продолжал работать. Теперь снос выполняется с отключённым `set -e` и все
|
поэтому `kit help` продолжал работать. Теперь снос выполняется с отключённым `set -e` и все
|
||||||
подобные конвейеры защищены; проверка после сноса это подтверждает списком, если что-то осталось.
|
подобные конвейеры защищены; проверка после сноса это подтверждает списком, если что-то осталось.
|
||||||
|
|
||||||
## v1.2.13: вопросов после показа доступов нет, и никакой работы для невыбранных протоколов
|
## v1.2.13: вопросов после показа доступов нет, и никакой работы для невыбранных протоколов
|
||||||
|
|
||||||
* **Все вопросы — до установщика.** Раньше после установки (когда на экране уже были данные входа
|
* **Все вопросы — до установщика.** Раньше после установки (когда на экране уже были данные входа
|
||||||
@@ -22,7 +111,6 @@
|
|||||||
* Наши команды помнят выбор: установщик пишет список протоколов в `/etc/kit/kl-protos`, и
|
* Наши команды помнят выбор: установщик пишет список протоколов в `/etc/kit/kl-protos`, и
|
||||||
`kit local site` не синхронизирует ответ Hysteria2, если Hysteria2 не ставился (раньше он это делал
|
`kit local site` не синхронизирует ответ Hysteria2, если Hysteria2 не ставился (раньше он это делал
|
||||||
и выдавал предупреждение про отсутствующий Hysteria2).
|
и выдавал предупреждение про отсутствующий Hysteria2).
|
||||||
|
|
||||||
## v1.2.12: не сканируем соседей, когда они не нужны
|
## v1.2.12: не сканируем соседей, когда они не нужны
|
||||||
|
|
||||||
* Автор подбирал сайты-соседи для XHTTP и MTProto **всегда**, когда маскировка доверенная («панель по IP»)
|
* Автор подбирал сайты-соседи для XHTTP и MTProto **всегда**, когда маскировка доверенная («панель по IP»)
|
||||||
@@ -34,14 +122,6 @@
|
|||||||
|
|
||||||
Здесь записи о наших версиях. Файл автора (`CHANGELOG.md`) мы не трогаем — так обновления
|
Здесь записи о наших версиях. Файл автора (`CHANGELOG.md`) мы не трогаем — так обновления
|
||||||
автора не конфликтуют с нашими записями.
|
автора не конфликтуют с нашими записями.
|
||||||
|
|
||||||
## 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.11: снос знает про kit-limit и находит свои службы сам
|
## v1.2.11: снос знает про kit-limit и находит свои службы сам
|
||||||
|
|
||||||
* В списке сноса не было `kit-limit.service` и `kit-limit.timer` (юнит «общий лимит трафика» из kit) —
|
* В списке сноса не было `kit-limit.service` и `kit-limit.timer` (юнит «общий лимит трафика» из kit) —
|
||||||
@@ -51,7 +131,6 @@
|
|||||||
новая служба, добавленная автором позже, не останется незамеченной. Проверка после сноса ищет
|
новая служба, добавленная автором позже, не останется незамеченной. Проверка после сноса ищет
|
||||||
по тем же шаблонам и показывает всё найденное.
|
по тем же шаблонам и показывает всё найденное.
|
||||||
* Флаг `--clean` у установщика использует тот же список.
|
* Флаг `--clean` у установщика использует тот же список.
|
||||||
|
|
||||||
## v1.2.10: убран мусор в выводе установщика
|
## v1.2.10: убран мусор в выводе установщика
|
||||||
|
|
||||||
* В шапке `scripts/3x-ui-local.sh` один из флагов потерял `#` и **при каждом запуске печаталось
|
* В шапке `scripts/3x-ui-local.sh` один из флагов потерял `#` и **при каждом запуске печаталось
|
||||||
@@ -59,7 +138,6 @@
|
|||||||
Исправлено, заодно выровнены строки справки.
|
Исправлено, заодно выровнены строки справки.
|
||||||
* В релизный гейт добавлена проверка, которая такое ловит: холостой прогон установщика, чей вывод в stderr
|
* В релизный гейт добавлена проверка, которая такое ловит: холостой прогон установщика, чей вывод в stderr
|
||||||
содержит «command not found / syntax error / bad substitution», релиз не собирается.
|
содержит «command not found / syntax error / bad substitution», релиз не собирается.
|
||||||
|
|
||||||
## v1.2.9: снос действительно убирает всё, повторная установка после сноса
|
## v1.2.9: снос действительно убирает всё, повторная установка после сноса
|
||||||
|
|
||||||
* Список удаляемого в `kit local uninstall` собран заново **по коду установщика** и дополнен тем, чего
|
* Список удаляемого в `kit local uninstall` собран заново **по коду установщика** и дополнен тем, чего
|
||||||
@@ -72,7 +150,6 @@
|
|||||||
* Флаг `--reset-ssh`: снести и наш ssh-дроп-ин (порт возвращается к системному). По умолчанию SSH не трогаем.
|
* Флаг `--reset-ssh`: снести и наш ssh-дроп-ин (порт возвращается к системному). По умолчанию SSH не трогаем.
|
||||||
* У установщика появился флаг **`--clean`**: убрать прошлую установку и поставить заново. Если установщик
|
* У установщика появился флаг **`--clean`**: убрать прошлую установку и поставить заново. Если установщик
|
||||||
всё-таки упал на «уже установлена», в подсказке прямо сказано запустить с `--clean`.
|
всё-таки упал на «уже установлена», в подсказке прямо сказано запустить с `--clean`.
|
||||||
|
|
||||||
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
|
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
|
||||||
|
|
||||||
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
|
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
|
||||||
@@ -84,7 +161,6 @@
|
|||||||
«удалить», есть `--dry-run`.
|
«удалить», есть `--dry-run`.
|
||||||
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
|
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
|
||||||
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
|
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
|
||||||
|
|
||||||
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
|
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
|
||||||
|
|
||||||
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
|
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
|
||||||
@@ -132,7 +208,13 @@
|
|||||||
* Флаг `--harden` теперь действительно существует (раньше он уходил установщику автора неизвестным
|
* Флаг `--harden` теперь действительно существует (раньше он уходил установщику автора неизвестным
|
||||||
параметром и ронял установку): закрыть ping и включить автообновления без вопросов.
|
параметром и ронял установку): закрыть ping и включить автообновления без вопросов.
|
||||||
* `--dry-run` показывает, что именно будет спрошено или сделано по умолчанию.
|
* `--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
|
## v1.2.5: суммы релиза в формате, который понимает kit
|
||||||
|
|
||||||
* Исправлена ошибка нашего релизного конвейера: на Windows `sha256sum` пишет суммы как
|
* Исправлена ошибка нашего релизного конвейера: на Windows `sha256sum` пишет суммы как
|
||||||
@@ -143,14 +225,12 @@
|
|||||||
другой машине.
|
другой машине.
|
||||||
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
|
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
|
||||||
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
|
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
|
||||||
|
|
||||||
## v1.2.4: проверка релиза отдельной командой
|
## v1.2.4: проверка релиза отдельной командой
|
||||||
|
|
||||||
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
|
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
|
||||||
можно проверить канал на сервере, ничего не устанавливая.
|
можно проверить канал на сервере, ничего не устанавливая.
|
||||||
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
|
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
|
||||||
изменений на сервере, чтобы чужой код не запускался раньше проверки.
|
изменений на сервере, чтобы чужой код не запускался раньше проверки.
|
||||||
|
|
||||||
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
|
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
|
||||||
|
|
||||||
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
|
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
|
||||||
@@ -165,7 +245,6 @@
|
|||||||
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
|
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
|
||||||
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
|
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
|
||||||
ключа обновляется и здесь, и в `kit`.
|
ключа обновляется и здесь, и в `kit`.
|
||||||
|
|
||||||
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
|
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
|
||||||
|
|
||||||
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
|
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
|
||||||
@@ -182,7 +261,6 @@
|
|||||||
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
|
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
|
||||||
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
||||||
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
||||||
|
|
||||||
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
|
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
|
||||||
|
|
||||||
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
||||||
|
|||||||
@@ -126,6 +126,18 @@ 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` двигается только вместе с подписанным релизом.** Серверы читают версию из `main` (`KIT_RAW`),
|
||||||
поэтому в `main` не должно попадать то, что ещё не подписано: иначе `kit update` и однострочник
|
поэтому в `main` не должно попадать то, что ещё не подписано: иначе `kit update` и однострочник
|
||||||
установки упираются в отсутствующий тег или в неподписанные суммы. Порядок такой:
|
установки упираются в отсутствующий тег или в неподписанные суммы. Порядок такой:
|
||||||
|
|||||||
@@ -28,9 +28,15 @@
|
|||||||
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
Без флагов скрипт **спрашивает** всё по ходу: какие протоколы ставить (Enter — REALITY + AmneziaWG-3.1),
|
Без флагов скрипт **спрашивает всё заранее, до установки**: какие протоколы ставить (Enter — REALITY +
|
||||||
какую маскировку и домен, закрывать ли ответ на ping, включать ли автообновления безопасности,
|
AmneziaWG-3.1), менять ли порт SSH (Enter — оставить 22), менять ли пароль root, закрывать ли ответ на
|
||||||
менять ли порт 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">
|
<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 и включить автообновления безопасности / не трогать это |
|
| `--harden` / `--no-harden` | закрыть ping и включить автообновления безопасности / не трогать это |
|
||||||
| `--yes` | не задавать вопросы (для автоматизации) |
|
| `--yes` | не задавать вопросы (для автоматизации) |
|
||||||
| `--host IP`, `--user имя`, `--port N`, `--restore файл` | адрес в ссылках, имя первого клиента, порт REALITY, восстановление из копии `kit backup` |
|
| `--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` | служебные |
|
| `--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.
|
1. **Проверка сервера**: root, Debian/Ubuntu, память, место на диске, занятые порты 80/443.
|
||||||
2. **Проверка релиза**: файлы берутся из этого репозитория по тегу, проверяется **подпись `SHA256SUMS`**
|
2. **Проверка релиза**: файлы берутся из этого репозитория по тегу, проверяется **подпись `SHA256SUMS`**
|
||||||
нашим ключом и SHA256 самого установщика. Не сошлось — ничего не запускается.
|
нашим ключом и SHA256 самого установщика. Не сошлось — ничего не запускается.
|
||||||
3. **Выбор протоколов** и запуск установщика: панель 3X-UI, выбранные подключения, nginx на 443,
|
3. **Наши вопросы** (до установки): протоколы, порт SSH, пароль root, ping, автообновления.
|
||||||
сертификат, сайт-заглушка, служба подписки, правила ufw.
|
4. **Установщик**: панель 3X-UI, только выбранные подключения, nginx на 443, сертификат, сайт-заглушка,
|
||||||
4. **Сайт-заглушка**: ставится случайный правдоподобный ИТ-сайт из нашего набора (10 штук) и
|
служба подписки, правила ufw. Маскировку спрашивает он: свой домен, сосед по подсети или свой сайт.
|
||||||
синхронизируется в ответ Hysteria2.
|
Если выбран сосед — сначала идёт **скан подсети**, затем меню выбора сайта (см. выше).
|
||||||
5. **Наши вопросы и правки**: закрыть ответ на ping, включить обновления безопасности, сменить порт
|
5. **Наши действия** (без вопросов): правдоподобный ИТ-сайт-заглушка вместо авторской страницы,
|
||||||
SSH, сменить пароль root.
|
`kit fix` (время и безопасные починки), закрыть ответ на ping, включить обновления безопасности,
|
||||||
6. **`kit fix`** — синхронизация времени и безопасные починки.
|
перевести SSH на новый порт, сменить пароль root.
|
||||||
7. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
|
6. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
|
||||||
`kit local hardening`. В конце — как забрать ссылки и подписку.
|
`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 net site`,
|
||||||
`kit local site`.
|
`kit local site`.
|
||||||
|
|
||||||
|
**Сайт маскировки выбираете вы.** После скана подсети установщик спрашивает отдельно для REALITY
|
||||||
|
(найденный сосед или свой адрес) и отдельно для XHTTP/MTProto (найденный, свой или известный сайт —
|
||||||
|
список известных расширен Google, Amazon, Samsung, Yahoo, Microsoft, AMD, Intel, NVIDIA). Выбранный
|
||||||
|
адрес проверяется на TLS 1.3 + HTTP/2. Если XHTTP и MTProto не выбраны, второго вопроса нет.
|
||||||
|
|
||||||
|
Для протоколов, которые **не** выбраны, не делается ничего: ни подключений, ни записей в nginx, ни
|
||||||
|
ссылок в подписке, ни лишних обращений к сети (например, обход соседей запускается только когда он
|
||||||
|
действительно нужен).
|
||||||
|
|
||||||
## Сайт-заглушка
|
## Сайт-заглушка
|
||||||
|
|
||||||
На 443 при неизвестном имени отдаётся страница нашего набора: небольшие правдоподобные ИТ-сайты
|
На 443 при неизвестном имени отдаётся страница нашего набора: небольшие правдоподобные ИТ-сайты
|
||||||
@@ -163,6 +183,7 @@ kit local site --show # какой стоит сейчас
|
|||||||
| `kit local ping off\|on\|status` | ответ на ICMP echo правилом ufw (видно в `ufw status`, отменяется одной командой) |
|
| `kit local ping off\|on\|status` | ответ на ICMP echo правилом ufw (видно в `ufw status`, отменяется одной командой) |
|
||||||
| `kit local passwd [юзер]`, `--generate` | смена пароля (спрашивает дважды, в логи не попадает) |
|
| `kit local passwd [юзер]`, `--generate` | смена пароля (спрашивает дважды, в логи не попадает) |
|
||||||
| `kit local upgrades on\|off\|status` | обновления безопасности |
|
| `kit local upgrades on\|off\|status` | обновления безопасности |
|
||||||
|
| `kit local uninstall` | **снести установку целиком**: панель, службы и файлы kit, nginx и сайт, сертификаты, наши правки системы и правила ufw для 80/443. Спрашивает слово «удалить», есть `--dry-run`. **SSH не трогает** |
|
||||||
| `kit local hardening`, `masking`, `channel`, `seams`, `version` | сводка защиты, состояние маскировки, канал обновлений, какие места форк правит у автора |
|
| `kit local hardening`, `masking`, `channel`, `seams`, `version` | сводка защиты, состояние маскировки, канал обновлений, какие места форк правит у автора |
|
||||||
|
|
||||||
У любой правки есть `--dry-run` — показывает план и ничего не меняет. Копии изменённых файлов
|
У любой правки есть `--dry-run` — показывает план и ничего не меняет. Копии изменённых файлов
|
||||||
|
|||||||
+8
-8
@@ -1,9 +1,9 @@
|
|||||||
# 3X-UI KIT 1.2.14
|
# 3X-UI KIT 1.2.22
|
||||||
# 3X-UI_autoVPS 1.2.14
|
# 3X-UI_autoVPS 1.2.22
|
||||||
666af017a32dd8b5b4c82488af7d47c17ebd4d7b700ddf056ee3e50c089b13d1 scripts/3x-ui.sh
|
8d090a042d1d243203b6c0f726ee2f81b00aa1c553924ae7fe239e6d324e420d scripts/3x-ui.sh
|
||||||
d224550fc777ac15c9bccafb17a82a36b42bcdc6376572c7c0d1bb9781a200dc scripts/hysteria2.sh
|
4d046772692ee7007bc47d202317623e2524fb04a0b4037aae10bfaa2fffc72c scripts/hysteria2.sh
|
||||||
d362a890e8e70a835021dadd8996409069e644db000f9685415bf857112fcf05 scripts/kit.sh
|
04aed8fd115159269fe45e79b31614778a6b23b53d45b5ab6db17e729ffabf24 scripts/kit.sh
|
||||||
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
|
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
|
||||||
d248182063d75028f20f83c2669b020515dd80ea91bafba100ac75a7c3480941 scripts/kit-local.sh
|
c38e5fe05bec66e747bb3feee2af4444d3a53d66e74ef1acb59861fbafa65e4c scripts/kit-local.sh
|
||||||
a48508498b4fcd88d55eb011e1c60a9503fdfa28ceae22053d4c05a10c4cf6a7 scripts/3x-ui-local.sh
|
c219de310f9b0af6d83995fbe708f0f04b9884f924aa7d0095b7a7555c3b7bf7 scripts/3x-ui-local.sh
|
||||||
22e988bfb2127d8584cc829f76868c2c85e138af32fe0989b29f7580b88a00a5 VERSION
|
552d1b0bcdbecc5cc39f8b9d98564f34edfaefeda6478e91eb54176935bde53a VERSION
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
-----BEGIN SSH SIGNATURE-----
|
-----BEGIN SSH SIGNATURE-----
|
||||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||||
AAALc3NoLWVkMjU1MTkAAABAgzLMUmGdBD24AF1SiVATfnfXNJK0/mVfusR3Wm+pEBIunn
|
AAALc3NoLWVkMjU1MTkAAABAhKwW7tjjJQf7d0Dr+17/JrDW93JXMDNPAn4+88utnpBq5a
|
||||||
18+cyVZfVPHaFlB7yXmYhZ1/xPtRKMVnVQoSJFAQ==
|
lHiMvRPEFnp6UOXR98Ee+TDIyVT3NExbdyOAhqDg==
|
||||||
-----END SSH SIGNATURE-----
|
-----END SSH SIGNATURE-----
|
||||||
|
|||||||
+48
-1
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
KIT_VERSION="1.2.14" # версия сборки форка; должна совпадать с VERSION релиза
|
KIT_VERSION="1.2.22" # версия сборки форка; должна совпадать с VERSION релиза
|
||||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||||
NS="3x-ui-kit-release"
|
NS="3x-ui-kit-release"
|
||||||
SIG_ID="releases@3x-ui-kit"
|
SIG_ID="releases@3x-ui-kit"
|
||||||
@@ -330,6 +330,14 @@ clean_previous() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
installer_failed() {
|
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
|
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
|
||||||
printf '\n%s!%s %s\n' "$Y" "$N" \
|
printf '\n%s!%s %s\n' "$Y" "$N" \
|
||||||
"На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2
|
"На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2
|
||||||
@@ -351,6 +359,16 @@ say "протоколы: $protos — под остальные не создаё
|
|||||||
# После установщика на экране появляются доступы к панели: там вопросы уже неуместны. Поэтому
|
# После установщика на экране появляются доступы к панели: там вопросы уже неуместны. Поэтому
|
||||||
# спрашиваем всё заранее, а действия выполняем после установки (см. «наши дополнения» ниже).
|
# спрашиваем всё заранее, а действия выполняем после установки (см. «наши дополнения» ниже).
|
||||||
|
|
||||||
|
ask_yes() { # "вопрос" y|n — по умолчанию
|
||||||
|
local q=$1 def=$2 ans=""
|
||||||
|
interactive || { [[ $def == y ]]; return; }
|
||||||
|
printf '\n%s [%s]: ' "$q" "$([[ $def == y ]] && echo 'Y/n' || echo 'y/N'])"
|
||||||
|
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
|
||||||
|
ans=${ans,,}; ans=${ans//[[:space:]]/}
|
||||||
|
[[ -z $ans && $def == y ]] && return 0
|
||||||
|
[[ $ans =~ ^(y|yes|д|да)$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
|
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
|
||||||
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
|
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
|
||||||
ssh_ports_now() {
|
ssh_ports_now() {
|
||||||
@@ -396,6 +414,7 @@ if [[ $harden == auto ]] && interactive; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
|
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
|
||||||
|
ssh_ports_before=$(ssh_ports_now) # для подсказки в конце: какие порты закрывать
|
||||||
want_passwd=no
|
want_passwd=no
|
||||||
if interactive; then
|
if interactive; then
|
||||||
if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi
|
if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi
|
||||||
@@ -403,6 +422,15 @@ fi
|
|||||||
|
|
||||||
[[ $clean == yes ]] && clean_previous
|
[[ $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 (тогда автоподбор автора).
|
# REALITY и (если выбраны) для XHTTP/MTProto. Отключается флагом --auto-sni (тогда автоподбор автора).
|
||||||
if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi
|
if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi
|
||||||
@@ -426,6 +454,11 @@ printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos
|
|||||||
|
|
||||||
# Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем.
|
# Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем.
|
||||||
|
|
||||||
|
# 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,
|
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
|
||||||
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
|
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
|
||||||
@@ -484,6 +517,19 @@ ${G}${B}Готово.${N} Сервер поставлен из нашей сбо
|
|||||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ SSH: как закрыть прежние порты
|
||||||
|
|
||||||
|
if [[ -n ${ssh_port:-} ]]; then
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
${Y}${B}SSH переведён на порт $ssh_port.${N} Прежние порты (${ssh_ports_before:-22}) пока работают — это
|
||||||
|
сделано специально, чтобы вы не потеряли доступ. Порядок обязательный:
|
||||||
|
1) во ВТОРОМ окне проверьте вход: ssh -p $ssh_port root@${HOST:-<IP сервера>}
|
||||||
|
2) вход работает — закройте прежние порты: ${B}kit local ssh --close-old $ssh_port${N}
|
||||||
|
3) поправьте порт у себя: ~/.ssh/config, терминалы, мониторинг
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
||||||
|
|
||||||
if [[ $show_sub == yes ]]; then
|
if [[ $show_sub == yes ]]; then
|
||||||
@@ -511,6 +557,7 @@ if [[ $show_sub == yes ]]; then
|
|||||||
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
||||||
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
||||||
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
||||||
|
[[ -n ${ssh_port:-} ]] && printf ' SSH: новый порт %s, прежние (%s) ещё открыты — закройте их после проверки входа: kit local ssh --close-old %s\n' "$ssh_port" "${ssh_ports_before:-22}" "$ssh_port"
|
||||||
if ! kit user link "$sub_user" --all; then
|
if ! kit user link "$sub_user" --all; then
|
||||||
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+85
-50
@@ -30,7 +30,7 @@ wait_apt_idle() {
|
|||||||
XUI_VERSION="v3.9.0"
|
XUI_VERSION="v3.9.0"
|
||||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||||
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
||||||
KIT_VERSION="1.2.14"
|
KIT_VERSION="1.2.22"
|
||||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
||||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||||
@@ -164,26 +164,32 @@ same_net() { # имя
|
|||||||
# Имена из сертификатов соседних адресов /24: заходим на 443 без имени и читаем subjectAltName.
|
# Имена из сертификатов соседних адресов /24: заходим на 443 без имени и читаем subjectAltName.
|
||||||
nearby_scan() { # основа "a.b.c"
|
nearby_scan() { # основа "a.b.c"
|
||||||
local me; me=$(host_ip)
|
local me; me=$(host_ip)
|
||||||
seq 1 254 | xargs -P 8 -I{} bash -c '
|
# Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.
|
||||||
|
seq 1 254 | xargs -P 32 -I{} bash -c '
|
||||||
ip=$1; [ "$ip" = "$2" ] && exit 0
|
ip=$1; [ "$ip" = "$2" ] && exit 0
|
||||||
echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
|
echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
|
||||||
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true
|
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Подходящие сайты: сначала в /24 сервера, если пусто – во втором /24 того же /23. До 8 имён.
|
# Подходящие сайты: сначала в /24 сервера, если пусто – во втором /24 того же /23. До 8 имён.
|
||||||
nearby_sites() {
|
nearby_sites() {
|
||||||
local me a b c base n names found=0
|
local me a b c base n names out
|
||||||
set +e; trap - ERR # выполняется в подпроцессе: сбой одной проверки не должен обрывать поиск
|
set +e; trap - ERR # выполняется в подпроцессе: сбой одной проверки не должен обрывать поиск
|
||||||
me=$(host_ip)
|
me=$(host_ip)
|
||||||
[[ $me =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 0
|
[[ $me =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 0
|
||||||
IFS=. read -r a b c _ <<<"$me"
|
IFS=. read -r a b c _ <<<"$me"
|
||||||
|
# Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная
|
||||||
|
# причина «долго сканирует». Функции передаём в подпроцессы через export -f.
|
||||||
|
export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip
|
||||||
|
export HOST
|
||||||
for base in "$a.$b.$c" "$a.$b.$((c ^ 1))"; do
|
for base in "$a.$b.$c" "$a.$b.$((c ^ 1))"; do
|
||||||
names=$(nearby_scan "$base")
|
names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)
|
||||||
for n in $names; do
|
[[ -n $names ]] || continue
|
||||||
[[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
|
out=$(while IFS= read -r n; do
|
||||||
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 =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
|
||||||
done
|
printf '%s\n' "$n"
|
||||||
((found > 0)) && return 0
|
done <<<"$names" | xargs -P 8 -I{} bash -c 'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\n" "$1"' _ {} 2>/dev/null | head -8)
|
||||||
|
[[ -n $out ]] && { printf '%s\n' "$out"; return 0; }
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -541,50 +547,60 @@ main() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Наш шов: выбор сайта маскировки по результатам скана подсети.
|
# Наш шов: выбор сайта маскировки по результатам скана подсети.
|
||||||
# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).
|
# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).
|
||||||
# Роль: reality (только найденные и ручной ввод) или xhttp (плюс известные сайты).
|
# role — подпись для человека: REALITY или XHTTP/MTProto.
|
||||||
kl_sni_menu() {
|
kl_sni_menu() {
|
||||||
local role=$1; shift
|
local role=$1; shift
|
||||||
local -a found=("$@")
|
local -a found=("$@")
|
||||||
local -a known=("${SNI_CANDIDATES[@]}")
|
local -a known=("${SNI_CANDIDATES[@]}")
|
||||||
local src=${KL_ASK_SRC:-/dev/tty} ans i n try
|
local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try
|
||||||
[[ $role == xhttp ]] || known=() # REALITY маскируем только под соседа или введённый вручную
|
n=${#found[@]}
|
||||||
printf '\n%s%s%s\n' "$B" "$role" "$N" >&2
|
printf '\n%s%s%s\n' "$B" "$role" "$N" >&2
|
||||||
if ((${#found[@]})); then
|
if ((n)); then
|
||||||
printf ' найдено в вашей подсети (TLS 1.3, h2, сертификат верный):\n' >&2
|
printf 'Найдено в подсети:\n\n' >&2
|
||||||
for i in "${!found[@]}"; do printf ' %2d) %s\n' "$((i + 1))" "${found[i]}" >&2; done
|
for i in "${!found[@]}"; do printf ' %d - %s\n' "$((i + 1))" "${found[i]}" >&2; done
|
||||||
|
auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))
|
||||||
|
printf ' %d - автовыбор из списка\n' "$auto_idx" >&2
|
||||||
|
printf ' %d - выбрать общедоступный (например %s, %s)\n' "$known_idx" "${known[0]}" "${known[1]}" >&2
|
||||||
|
printf ' %d - ввести свой домен\n\n' "$own_idx" >&2
|
||||||
else
|
else
|
||||||
printf ' в подсети подходящих сайтов не нашлось\n' >&2
|
auto_idx=0; known_idx=1; own_idx=2
|
||||||
|
printf 'В подсети доменов не найдено, выберите:\n\n' >&2
|
||||||
|
printf ' 1 - общедоступный (например %s, %s)\n' "${known[0]}" "${known[1]}" >&2
|
||||||
|
printf ' 2 - ввести свой домен\n\n' >&2
|
||||||
fi
|
fi
|
||||||
if [[ $role == xhttp ]]; then
|
printf 'Ваш выбор (Enter — автовыбор): ' >&2
|
||||||
n=${#found[@]}
|
|
||||||
printf ' известные сайты (заметнее, чем соседи по подсети):\n' >&2
|
|
||||||
for i in "${!known[@]}"; do printf ' %2d) %s\n' "$((i + 1 + n))" "${known[i]}" >&2; done
|
|
||||||
fi
|
|
||||||
printf ' 0) ввести адрес сайта вручную\n' >&2
|
|
||||||
printf ' Enter — оставить автоподбор. Выбор: ' >&2
|
|
||||||
for try in 1 2 3; do
|
for try in 1 2 3; do
|
||||||
ans=""
|
ans=""
|
||||||
read -r -t 300 ans <"$src" 2>/dev/null || true
|
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||||
ans=${ans//[[:space:]]/}
|
ans=${ans//[[:space:]]/}
|
||||||
[[ -z $ans ]] && return 0
|
[[ -z $ans ]] && return 0
|
||||||
if [[ $ans == 0 ]]; then
|
[[ $ans =~ ^[0-9]+$ ]] || { printf ' введите номер из списка\n' >&2; continue; }
|
||||||
|
i=$((10#$ans))
|
||||||
|
if ((n)) && ((i >= 1 && i <= n)); then printf 'sni:%s' "${found[i - 1]}"; return 0; fi
|
||||||
|
((i == auto_idx)) && return 0
|
||||||
|
if ((i == known_idx)); then
|
||||||
|
printf '\nОбщедоступные сайты:\n' >&2
|
||||||
|
for k in "${!known[@]}"; do printf ' %d - %s\n' "$((k + 1))" "${known[k]}" >&2; done
|
||||||
|
printf ' 0 - назад\nВаш выбор: ' >&2
|
||||||
|
ans=""
|
||||||
|
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||||
|
ans=${ans//[[:space:]]/}
|
||||||
|
if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then
|
||||||
|
printf 'sni:%s' "${known[10#$ans - 1]}"; return 0
|
||||||
|
fi
|
||||||
|
printf ' ничего не выбрано — оставляю автовыбор\n' >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ((i == own_idx)); then
|
||||||
printf ' адрес сайта (например vpn.example.com): ' >&2
|
printf ' адрес сайта (например vpn.example.com): ' >&2
|
||||||
ans=""
|
ans=""
|
||||||
read -r -t 300 ans <"$src" 2>/dev/null || true
|
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||||
ans=${ans//[[:space:]]/}
|
ans=${ans//[[:space:]]/}
|
||||||
if [[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then printf '%s' "$ans"; return 0; fi
|
[[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] && { printf 'own:%s' "$ans"; return 0; }
|
||||||
printf ' не похоже на адрес сайта\n' >&2
|
printf ' не похоже на адрес сайта — оставляю автовыбор\n' >&2
|
||||||
elif [[ $ans =~ ^[0-9]+$ ]]; then
|
return 0
|
||||||
local total=${#found[@]}
|
|
||||||
((total)) && known=() # для reality известные не предлагаются
|
|
||||||
local idx=$((10#$ans))
|
|
||||||
if ((idx >= 1 && idx <= total)); then printf '%s' "${found[idx - 1]}"; return 0; fi
|
|
||||||
if ((idx > total && idx <= total + ${#known[@]})); then printf '%s' "${known[idx - total - 1]}"; return 0; fi
|
|
||||||
printf ' такого номера нет\n' >&2
|
|
||||||
else
|
|
||||||
printf ' не понял «%s» — введите номер или 0\n' "$ans" >&2
|
|
||||||
fi
|
fi
|
||||||
printf ' ещё раз (Enter — автоподбор): ' >&2
|
printf ' такого номера нет\n' >&2
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -593,7 +609,9 @@ kl_sni_menu() {
|
|||||||
if [[ -n $DOMAIN ]]; then
|
if [[ -n $DOMAIN ]]; then
|
||||||
[[ $PANEL_SSL == ip ]] || die "Свой домен работает с сертификатом панели Let's Encrypt на IP: освободите порт 80 и не указывайте --cert, --key и --panel-ssl none (сертификат для домена установщик получит сам)."
|
[[ $PANEL_SSL == ip ]] || die "Свой домен работает с сертификатом панели Let's Encrypt на IP: освободите порт 80 и не указывайте --cert, --key и --panel-ssl none (сертификат для домена установщик получит сам)."
|
||||||
domain_points_here "$DOMAIN" || die "Исправьте A-запись домена и запустите скрипт снова (DNS обновляется от нескольких минут до нескольких часов)."
|
domain_points_here "$DOMAIN" || die "Исправьте A-запись домена и запустите скрипт снова (DNS обновляется от нескольких минут до нескольких часов)."
|
||||||
elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
# Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе
|
||||||
|
# человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.
|
||||||
|
elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
||||||
choose_masking
|
choose_masking
|
||||||
fi
|
fi
|
||||||
if [[ -n $DOMAIN ]]; then
|
if [[ -n $DOMAIN ]]; then
|
||||||
@@ -607,15 +625,22 @@ kl_sni_menu() {
|
|||||||
NB_ALL=("${nb[@]}")
|
NB_ALL=("${nb[@]}")
|
||||||
# Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.
|
# Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.
|
||||||
if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then
|
if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then
|
||||||
local pick pick2
|
local pick pick2 own_sni=no
|
||||||
pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})
|
pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})
|
||||||
if ((${#nb[@]})); then
|
case $pick in
|
||||||
[[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}
|
own:*)
|
||||||
elif [[ -n $pick ]]; then
|
own_sni=yes
|
||||||
SNI=$pick
|
DOMAIN=${pick#own:}
|
||||||
fi
|
if domain_points_here "$DOMAIN" 2>/dev/null; then
|
||||||
|
SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let's Encrypt выпустит установщик)"
|
||||||
|
else
|
||||||
|
SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
sni:*) SNI=${pick#sni:} ;;
|
||||||
|
esac
|
||||||
if [[ -n $SNI ]]; then
|
if [[ -n $SNI ]]; then
|
||||||
if ! sni_ok "$SNI"; then
|
if [[ $own_sni == no ]] && ! sni_ok "$SNI"; then
|
||||||
warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"
|
warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"
|
||||||
SNI="${nb[0]:-$SNI}"
|
SNI="${nb[0]:-$SNI}"
|
||||||
fi
|
fi
|
||||||
@@ -623,17 +648,23 @@ kl_sni_menu() {
|
|||||||
fi
|
fi
|
||||||
if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
||||||
pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})
|
pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})
|
||||||
|
[[ $pick2 == sni:* ]] && pick2=${pick2#sni:}
|
||||||
|
[[ $pick2 == own:* ]] && pick2=${pick2#own:}
|
||||||
[[ -n $pick2 ]] && SNI2=$pick2
|
[[ -n $pick2 ]] && SNI2=$pick2
|
||||||
[[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"
|
[[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"
|
||||||
|
else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi
|
||||||
if [[ -z $SNI ]] && ((${#nb[@]})); then
|
if [[ -z $SNI ]] && ((${#nb[@]})); then
|
||||||
SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}
|
SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}
|
||||||
ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"
|
ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"
|
||||||
else
|
elif [[ -z $SNI ]]; then
|
||||||
warn "В подсети подходящего сайта не нашёл."
|
warn "В подсети подходящего сайта не нашёл."
|
||||||
echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com"
|
echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com"
|
||||||
if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then
|
||||||
|
: # сайт уже выбран в нашем меню — вопрос автора не нужен
|
||||||
|
elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
||||||
ask_tty " Продолжить с запасным сайтом из списка? [y/N] " || true
|
ask_tty " Продолжить с запасным сайтом из списка? [y/N] " || true
|
||||||
[[ $REPLY =~ ^[yYдД]$ ]] || die "Остановился по вашей просьбе. Ставить можно снова в любой момент."
|
[[ $REPLY =~ ^[yYдД]$ ]] || die "Остановился по вашей просьбе. Ставить можно снова в любой момент."
|
||||||
else
|
else
|
||||||
@@ -831,7 +862,7 @@ kl_sni_menu() {
|
|||||||
fi
|
fi
|
||||||
links=$(sub_links "$SUBID")
|
links=$(sub_links "$SUBID")
|
||||||
# У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg».
|
# У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg».
|
||||||
local extra
|
local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны
|
||||||
# Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,
|
# Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,
|
||||||
# если эти протоколы не выбраны (никаких лишних обращений к панели).
|
# если эти протоколы не выбраны (никаких лишних обращений к панели).
|
||||||
if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
||||||
@@ -2129,7 +2160,11 @@ PY
|
|||||||
install -d -m 755 /var/www/kit
|
install -d -m 755 /var/www/kit
|
||||||
[[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html
|
[[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html
|
||||||
if [[ $SINGLE == yes ]]; then
|
if [[ $SINGLE == yes ]]; then
|
||||||
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }
|
# Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов
|
||||||
|
# позже (после сборки nginx), поэтому на свежем сервере проверка Let's Encrypt доходила до сервера и
|
||||||
|
# «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».
|
||||||
|
command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true
|
||||||
|
[[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }
|
||||||
setup_nginx
|
setup_nginx
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||||
# останавливается и не заменяет команду hy2.
|
# останавливается и не заменяет команду hy2.
|
||||||
|
|
||||||
KIT_VERSION="1.2.14"
|
KIT_VERSION="1.2.22"
|
||||||
|
|
||||||
cat >&2 <<'MSG'
|
cat >&2 <<'MSG'
|
||||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||||
|
|||||||
+16
-4
@@ -923,12 +923,13 @@ kit_local_cmd() {
|
|||||||
kit_local_uninstall() {
|
kit_local_uninstall() {
|
||||||
kl_args "$@"
|
kl_args "$@"
|
||||||
local arg=${KL_REST[0]:-} ans found
|
local arg=${KL_REST[0]:-} ans found
|
||||||
local reset_ssh=no
|
local reset_ssh=no i=0
|
||||||
case $arg in
|
case $arg in
|
||||||
"") ;;
|
"") ;;
|
||||||
--dry-run) KL_DRY=yes ;;
|
--dry-run) KL_DRY=yes ;;
|
||||||
|
--yes) KIT_LOCAL_UNINSTALL_CONFIRMED=yes ;;
|
||||||
--reset-ssh) reset_ssh=yes ;;
|
--reset-ssh) reset_ssh=yes ;;
|
||||||
*) kl_die "kit local uninstall [--dry-run] [--reset-ssh]"; return 1 ;;
|
*) kl_die "kit local uninstall [--dry-run] [--yes] [--reset-ssh]"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Службы: и явный список, и всё, что найдётся по нашим шаблонам (чтобы новые юниты kit не оставались).
|
# Службы: и явный список, и всё, что найдётся по нашим шаблонам (чтобы новые юниты kit не оставались).
|
||||||
@@ -1060,14 +1061,25 @@ kit_local_uninstall() {
|
|||||||
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
|
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
|
||||||
kl_warn "осталась наша служба: $s"; left=1
|
kl_warn "осталась наша служба: $s"; left=1
|
||||||
done
|
done
|
||||||
|
# Добивка: убираем всё, что осталось и подходит под наши шаблоны (вдруг автор добавил новый файл
|
||||||
|
# или что-то не удалилось с первого раза). Затем проверяем ещё раз.
|
||||||
|
for f in /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
|
||||||
|
/etc/systemd/system/kit-*.service /etc/systemd/system/kit-*.timer \
|
||||||
|
/etc/systemd/system/x-ui*.service /usr/lib/systemd/system/kit-*.service \
|
||||||
|
/etc/x-ui /usr/local/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /root/3x-ui.txt /root/cert; do
|
||||||
|
[[ -e $f ]] || continue
|
||||||
|
rm -rf "$f" && { kl_ok "добито: $f"; i=1; } || kl_warn "не удалось удалить: $f"
|
||||||
|
done
|
||||||
|
((i)) && systemctl daemon-reload
|
||||||
found=$( { ls -d /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
|
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 || true; } | head -20)
|
/etc/systemd/system/*kit* /etc/x-ui /usr/local/x-ui /root/3x-ui.txt /root/cert 2>/dev/null || true; } | head -20)
|
||||||
[[ -z $found ]] || { kl_warn "осталось на диске:"; printf ' %s\n' $found; left=1; }
|
[[ -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; }
|
ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE '[:.]443$' && { kl_warn "порт 443 всё ещё кем-то слушается"; left=1; }
|
||||||
if ((left)); then
|
if ((left)); then
|
||||||
kl_warn "что-то осталось — пришлите этот вывод, поправим список в kit local uninstall"
|
kl_warn "осталось перечисленное выше — пришлите этот вывод, поправим список в kit local uninstall"
|
||||||
else
|
else
|
||||||
kl_ok "чисто: от установки не осталось ни файлов, ни служб, порт 443 свободен"
|
kl_ok "чисто: от установки не осталось ни файлов, ни служб, порт 443 свободен"
|
||||||
|
kl_ok "проверка установщика пройдена: он снова поставит сервер с нуля"
|
||||||
fi
|
fi
|
||||||
kl_ok "SSH не тронут (если не указан --reset-ssh)"
|
kl_ok "SSH не тронут (если не указан --reset-ssh)"
|
||||||
kl_step "Готово: сервер в состоянии до установки, можно ставить заново"
|
kl_step "Готово: сервер в состоянии до установки, можно ставить заново"
|
||||||
|
|||||||
+9
-8
@@ -12,7 +12,7 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||||
|
|
||||||
KIT_VERSION="1.2.14"
|
KIT_VERSION="1.2.22"
|
||||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
||||||
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||||
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
||||||
@@ -478,7 +478,8 @@ newer() { [[ $1 != "$2" && $(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1) ==
|
|||||||
auto_enabled() { systemctl is-enabled -q kit-update.timer 2>/dev/null; }
|
auto_enabled() { systemctl is-enabled -q kit-update.timer 2>/dev/null; }
|
||||||
|
|
||||||
# Раз в сутки узнаём последнюю версию (не дольше 3 секунд) и подсказываем обновиться.
|
# Раз в сутки узнаём последнюю версию (не дольше 3 секунд) и подсказываем обновиться.
|
||||||
update_hint() {
|
update_hint() { # подсказка про новую версию
|
||||||
|
[[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)
|
||||||
local latest=""
|
local latest=""
|
||||||
if [[ ! -f $KIT_LATEST ]] || (($(date +%s) - $(stat -c %Y "$KIT_LATEST") > 86400)); then
|
if [[ ! -f $KIT_LATEST ]] || (($(date +%s) - $(stat -c %Y "$KIT_LATEST") > 86400)); then
|
||||||
latest=$(remote_version 3) || true
|
latest=$(remote_version 3) || true
|
||||||
@@ -2553,13 +2554,13 @@ menu_main() {
|
|||||||
echo
|
echo
|
||||||
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||||
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \
|
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \
|
||||||
"7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"
|
"7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \
|
||||||
"10. Удалить всё установленное (наш пункт)"
|
"11. Панель 3X-UI (меню x-ui)"
|
||||||
echo
|
echo
|
||||||
menu_status
|
menu_status
|
||||||
update_hint
|
update_hint
|
||||||
echo
|
echo
|
||||||
ask_num "Выбор [0-9, 10]: "; c=$REPLY
|
ask_num "Выбор [1-11]: "; c=$REPLY
|
||||||
case $c in
|
case $c in
|
||||||
1) menu_users ;;
|
1) menu_users ;;
|
||||||
2) menu_net ;;
|
2) menu_net ;;
|
||||||
@@ -2569,9 +2570,9 @@ menu_main() {
|
|||||||
6) run_action menu_auto; pause ;;
|
6) run_action menu_auto; pause ;;
|
||||||
7) run_action cmd_backup; pause ;;
|
7) run_action cmd_backup; pause ;;
|
||||||
8) run_action cmd_version; pause ;;
|
8) run_action cmd_version; pause ;;
|
||||||
9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем
|
||||||
10) kit_local_uninstall ;;
|
10 | 0 | "") return 0 ;;
|
||||||
0 | "") return 0 ;;
|
11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||||
*) echo "Нет такого пункта." ;;
|
*) echo "Нет такого пункта." ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
+465
-199
@@ -86,208 +86,474 @@ def main():
|
|||||||
|
|
||||||
version_re = r'^KIT_VERSION="[0-9.]+"$'
|
version_re = r'^KIT_VERSION="[0-9.]+"$'
|
||||||
edits = [
|
edits = [
|
||||||
# --- scripts/kit.sh -------------------------------------------------
|
|
||||||
("scripts/kit.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
|
||||||
(
|
(
|
||||||
"scripts/kit.sh",
|
'scripts/kit.sh',
|
||||||
|
'# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.\n',
|
||||||
|
False,
|
||||||
|
'# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }\n',
|
||||||
|
False,
|
||||||
|
'kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }\n\n# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---\n# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.\n# Все команды форка — под «kit local …» (см. kit local help).\nKIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"\nif [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi\n\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)\n',
|
||||||
|
False,
|
||||||
|
'Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут\n',
|
||||||
|
False,
|
||||||
|
'Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' # SHA256:VDuGgJ8dOeCNXB4nBZf3+kRlWthw3+vh8rfMxzMSRIM (itsnotkubrick, 2026-09-30)\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrTGCDhhnm8XO1ekpPJuSWRVCJiFiupEspfQxcbEBmz 3X-UI KIT releases"\n',
|
||||||
|
False,
|
||||||
|
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'update_hint() {\n',
|
||||||
|
False,
|
||||||
|
'update_hint() { # подсказка про новую версию\n [[ -d /etc/kit ]] || return 0 # kit удалён — подсказывать нечего (раньше тут сыпались ошибки про latest-version)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' echo "3X-UI KIT $KIT_VERSION"\n',
|
||||||
|
False,
|
||||||
|
' echo "3X-UI_autoVPS $KIT_VERSION"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'\n',
|
||||||
|
False,
|
||||||
|
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py; do\n',
|
||||||
|
False,
|
||||||
|
' grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }\n for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
|
||||||
|
False,
|
||||||
|
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'Description=3X-UI KIT: автообновление kit и kit-sub (только подписанные релизы)\n',
|
||||||
|
False,
|
||||||
|
'Description=3X-UI_autoVPS: автообновление kit и kit-sub (только подписанные релизы)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'Description=3X-UI KIT: проверка обновлений раз в сутки\n',
|
||||||
|
False,
|
||||||
|
'Description=3X-UI_autoVPS: проверка обновлений раз в сутки\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI KIT $KIT_VERSION → $latest"; fi\n',
|
||||||
|
False,
|
||||||
|
' if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI_autoVPS $KIT_VERSION → $latest"; fi\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n',
|
||||||
|
False,
|
||||||
|
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n install -d -m 755 /usr/local/lib/kit\n install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n',
|
||||||
|
'авто-шов: insert, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
|
||||||
|
False,
|
||||||
|
' echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"\n',
|
||||||
|
False,
|
||||||
|
'split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI_autoVPS"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' grep -q \'"Name":"3X-UI KIT"\' <<<"$v"\n',
|
||||||
|
False,
|
||||||
|
' grep -q \'"Name":"3X-UI_autoVPS"\' <<<"$v"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' box "3X-UI KIT $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
|
||||||
|
False,
|
||||||
|
' box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \\\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"\n',
|
||||||
|
False,
|
||||||
|
' "7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \\\n "11. Панель 3X-UI (меню x-ui)"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' ask_num "Выбор [0-9]: "; c=$REPLY\n',
|
||||||
|
False,
|
||||||
|
' ask_num "Выбор [1-11]: "; c=$REPLY\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 0 | "") return 0 ;;\n',
|
||||||
|
False,
|
||||||
|
' 9) kit_local_uninstall && return 0 ;; # снос — и сразу выход: показывать меню после удаления незачем\n 10 | 0 | "") return 0 ;;\n 11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
'${B}3X-UI KIT $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
|
||||||
|
False,
|
||||||
|
'${B}3X-UI_autoVPS $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"\n',
|
||||||
|
False,
|
||||||
|
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)\n',
|
||||||
|
False,
|
||||||
|
'# Наш шов: добавлены известные сайты (Microsoft, AMD, Intel, NVIDIA) — их можно выбрать вручную.\nSNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com www.microsoft.com www.amd.com www.intel.com www.nvidia.com)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
" seq 1 254 | xargs -P 8 -I{} bash -c '\n",
|
||||||
|
False,
|
||||||
|
" # Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.\n seq 1 254 | xargs -P 32 -I{} bash -c '\n",
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true\n',
|
||||||
|
False,
|
||||||
|
' echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \\\n | tr "," "\\n" | sed -n "s/^ *DNS://p"\' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' local me a b c base n names found=0\n',
|
||||||
|
False,
|
||||||
|
' local me a b c base n names out\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n',
|
||||||
|
False,
|
||||||
|
' [[ $me =~ ^[0-9]{1,3}(\\.[0-9]{1,3}){3}$ ]] || return 0\n IFS=. read -r a b c _ <<<"$me"\n # Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная\n # причина «долго сканирует». Функции передаём в подпроцессы через export -f.\n export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip\n export HOST\n',
|
||||||
|
'авто-шов: insert, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' names=$(nearby_scan "$base")\n for n in $names; do\n [[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n if same_net "$n" && sni_alive "$n" && sni_trusted "$n" && sni_quality "$n"; then echo "$n"; found=$((found + 1)); ((found >= 8)) && return 0; fi\n done\n ((found > 0)) && return 0\n',
|
||||||
|
False,
|
||||||
|
' names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)\n [[ -n $names ]] || continue\n out=$(while IFS= read -r n; do\n [[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n printf \'%s\\n\' "$n"\n done <<<"$names" | xargs -P 8 -I{} bash -c \'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\\n" "$1"\' _ {} 2>/dev/null | head -8)\n [[ -n $out ]] && { printf \'%s\\n\' "$out"; return 0; }\n',
|
||||||
|
'авто-шов: replace, строк 6',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n",
|
||||||
|
False,
|
||||||
|
" _ _ _ ___ _____\n | | | | __ _ ___| | _____ _ __ / _ \\___ |\n | |_| |/ _` |/ __| |/ / _ \\ '__| | | | | / /\n | _ | (_| | (__| < __/ | | |_| |/ /\n |_| |_|\\__,_|\\___|_|\\_\\___|_| \\___//_/\n",
|
||||||
|
'авто-шов: replace, строк 5',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' echo "${B}3X-UI KIT $KIT_VERSION${N} · it\'s not Kubrick. it\'s just a VPN."\n',
|
||||||
|
False,
|
||||||
|
' echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n',
|
||||||
|
False,
|
||||||
|
' later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."\n fi\n\n# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# role — подпись для человека: REALITY или XHTTP/MTProto.\nkl_sni_menu() {\n local role=$1; shift\n local -a found=("$@")\n local -a known=("${SNI_CANDIDATES[@]}")\n local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try\n n=${#found[@]}\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((n)); then\n printf \'Найдено в подсети:\\n\\n\' >&2\n for i in "${!found[@]}"; do printf \' %d - %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))\n printf \' %d - автовыбор из списка\\n\' "$auto_idx" >&2\n printf \' %d - выбрать общедоступный (например %s, %s)\\n\' "$known_idx" "${known[0]}" "${known[1]}" >&2\n printf \' %d - ввести свой домен\\n\\n\' "$own_idx" >&2\n else\n auto_idx=0; known_idx=1; own_idx=2\n printf \'В подсети доменов не найдено, выберите:\\n\\n\' >&2\n printf \' 1 - общедоступный (например %s, %s)\\n\' "${known[0]}" "${known[1]}" >&2\n printf \' 2 - ввести свой домен\\n\\n\' >&2\n fi\n printf \'Ваш выбор (Enter — автовыбор): \' >&2\n for try in 1 2 3; do\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ -z $ans ]] && return 0\n [[ $ans =~ ^[0-9]+$ ]] || { printf \' введите номер из списка\\n\' >&2; continue; }\n i=$((10#$ans))\n if ((n)) && ((i >= 1 && i <= n)); then printf \'sni:%s\' "${found[i - 1]}"; return 0; fi\n ((i == auto_idx)) && return 0\n if ((i == known_idx)); then\n printf \'\\nОбщедоступные сайты:\\n\' >&2\n for k in "${!known[@]}"; do printf \' %d - %s\\n\' "$((k + 1))" "${known[k]}" >&2; done\n printf \' 0 - назад\\nВаш выбор: \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n if [[ $ans =~ ^[0-9]+$ ]] && ((10#$ans >= 1 && 10#$ans <= ${#known[@]})); then\n printf \'sni:%s\' "${known[10#$ans - 1]}"; return 0\n fi\n printf \' ничего не выбрано — оставляю автовыбор\\n\' >&2\n return 0\n fi\n if ((i == own_idx)); then\n printf \' адрес сайта (например vpn.example.com): \' >&2\n ans=""\n read -r -t 300 ans <"$src" 2>/dev/null || true\n ans=${ans//[[:space:]]/}\n [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]] && { printf \'own:%s\' "$ans"; return 0; }\n printf \' не похоже на адрес сайта — оставляю автовыбор\\n\' >&2\n return 0\n fi\n printf \' такого номера нет\\n\' >&2\n done\n return 0\n}\n\n',
|
||||||
|
'авто-шов: insert, строк 3',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
|
||||||
|
False,
|
||||||
|
' # Наш шов: когда выбор сайта делает наше меню (KL_PICK_SNI=1), меню автора не показываем — иначе\n # человек отвечает на «Свой домен / Сосед / Свой сайт», и до скана подсети дело не доходит.\n elif [[ -z $SNI && $yes == no && $PANEL_SSL == ip && $multi == no && ! -f $XUI_ENV && -t 1 && ${KL_PICK_SNI:-0} != 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"\n',
|
||||||
|
False,
|
||||||
|
' echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."\n echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' if ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n',
|
||||||
|
False,
|
||||||
|
' # Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.\n if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then\n local pick pick2 own_sni=no\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n case $pick in\n own:*)\n own_sni=yes\n DOMAIN=${pick#own:}\n if domain_points_here "$DOMAIN" 2>/dev/null; then\n SNI=$DOMAIN; ok "маскировка под свой домен: ${B}$DOMAIN${N} (сертификат Let\'s Encrypt выпустит установщик)"\n else\n SNI=$DOMAIN; later "домен $DOMAIN не указывает на этот сервер — маскируюсь под него как под чужой сайт"\n fi\n ;;\n sni:*) SNI=${pick#sni:} ;;\n esac\n if [[ -n $SNI ]]; then\n if [[ $own_sni == no ]] && ! sni_ok "$SNI"; then\n warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"\n SNI="${nb[0]:-$SNI}"\n fi\n ok "маскировка REALITY: ${B}$SNI${N}"\n fi\n if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})\n [[ $pick2 == sni:* ]] && pick2=${pick2#sni:}\n [[ $pick2 == own:* ]] && pick2=${pick2#own:}\n [[ -n $pick2 ]] && SNI2=$pick2\n [[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"\n fi\n fi\n if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"\n else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi\n if [[ -z $SNI ]] && ((${#nb[@]})); then\n SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n else\n',
|
||||||
|
False,
|
||||||
|
' ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"\n elif [[ -z $SNI ]]; then\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
|
||||||
|
False,
|
||||||
|
' if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then\n : # сайт уже выбран в нашем меню — вопрос автора не нужен\n elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then\n',
|
||||||
|
False,
|
||||||
|
' # Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей\n # по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.\n if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \\\n && [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' local extra\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n',
|
||||||
|
False,
|
||||||
|
' local extra="" # пустая строка, иначе set -u валит скрипт, когда AWG/MTProto не выбраны\n # Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,\n # если эти протоколы не выбраны (никаких лишних обращений к панели).\n if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then\n extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n fi\n',
|
||||||
|
'авто-шов: replace, строк 2',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' echo "3X-UI KIT (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
|
||||||
|
False,
|
||||||
|
' echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\n',
|
||||||
|
False,
|
||||||
|
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"\n',
|
||||||
|
'авто-шов: insert, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n',
|
||||||
|
False,
|
||||||
|
' else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi\n # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n install -d -m 755 /usr/local/lib/kit\n if [[ -n $src && -f $d/kit-local.sh ]]; then\n install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n else\n curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n fi\n',
|
||||||
|
'авто-шов: insert, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"\n',
|
||||||
|
False,
|
||||||
|
'KIT_INSTALL_CMD="bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui.sh)"\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' echo "# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске."\n',
|
||||||
|
False,
|
||||||
|
' echo "# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске.\n',
|
||||||
|
False,
|
||||||
|
'# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске.\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI KIT" --arg uri "$uri" \'\n',
|
||||||
|
False,
|
||||||
|
' upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI_autoVPS" --arg uri "$uri" \'\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI KIT" \'\n',
|
||||||
|
False,
|
||||||
|
' upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI_autoVPS" \'\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
'Description=kit-sub: подписка с учётом приложения (3X-UI KIT)\n',
|
||||||
|
False,
|
||||||
|
'Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' || die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' && die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' && die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' [[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' *) die "Это не резервная копия 3X-UI KIT или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
|
||||||
|
False,
|
||||||
|
' *) die "Это не резервная копия 3X-UI_autoVPS или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' || die "Это не резервная копия 3X-UI KIT или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (значение $n в ${f##*/}). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' python3 - "$file" "$tmp" <<\'PY\' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена. Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' || die "Это не резервная копия 3X-UI KIT или она повреждена (порт панели). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (порт панели). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' [[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (адрес сервера). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' || die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."\n',
|
||||||
|
False,
|
||||||
|
' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
' [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
|
||||||
|
False,
|
||||||
|
' # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов\n # позже (после сборки nginx), поэтому на свежем сервере проверка Let\'s Encrypt доходила до сервера и\n # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».\n command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true\n [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/hysteria2.sh',
|
||||||
|
'# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
|
||||||
|
False,
|
||||||
|
'# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/hysteria2.sh',
|
||||||
|
'Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).\n',
|
||||||
|
False,
|
||||||
|
'Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit-sub.py',
|
||||||
|
' data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
|
||||||
|
False,
|
||||||
|
' data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit-sub.py',
|
||||||
|
' data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
|
||||||
|
False,
|
||||||
|
' data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
|
||||||
|
'авто-шов: replace, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit-sub.py',
|
||||||
|
' main()\n',
|
||||||
|
False,
|
||||||
|
' main()\n\n',
|
||||||
|
'авто-шов: insert, строк 1',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
|
version_re,
|
||||||
|
True,
|
||||||
|
f'KIT_VERSION="{version}"',
|
||||||
|
'версия сборки форка',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/3x-ui.sh',
|
||||||
|
version_re,
|
||||||
|
True,
|
||||||
|
f'KIT_VERSION="{version}"',
|
||||||
|
'версия сборки форка',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/hysteria2.sh',
|
||||||
|
version_re,
|
||||||
|
True,
|
||||||
|
f'KIT_VERSION="{version}"',
|
||||||
|
'версия сборки форка',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'scripts/kit.sh',
|
||||||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
|
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
|
||||||
False,
|
False,
|
||||||
'KIT_RAW="%s/raw/branch/main"' % GITEA,
|
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"',
|
||||||
"источник VERSION и подсказки об обновлении",
|
'канал обновлений: наш Gitea вместо GitHub',
|
||||||
),
|
|
||||||
(
|
|
||||||
"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]: "',
|
|
||||||
"подсказка выбора в меню учитывает новый пункт",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
' if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then',
|
|
||||||
False,
|
|
||||||
' # Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей\n # по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.\n if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \\\n && [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then',
|
|
||||||
'не сканировать соседей, если XHTTP и MTProto не выбраны',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
' extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)',
|
|
||||||
False,
|
|
||||||
' # Наш шов: подписки «-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',
|
|
||||||
'не тянуть legacy-подписки awg/tg, если протоколы не выбраны',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
'SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)',
|
|
||||||
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)',
|
|
||||||
'список известных сайтов расширен',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
' # Маскировка: из флагов, по вопросу пользователю или автоматически (сосед по подсети).',
|
|
||||||
False,
|
|
||||||
'# ---------------------------------------------------------------------------\n# Наш шов: выбор сайта маскировки по результатам скана подсети.\n# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).\n# Роль: reality (только найденные и ручной ввод) или xhttp (плюс известные сайты).\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 n try\n [[ $role == xhttp ]] || known=() # REALITY маскируем только под соседа или введённый вручную\n printf \'\\n%s%s%s\\n\' "$B" "$role" "$N" >&2\n if ((${#found[@]})); then\n printf \' найдено в вашей подсети (TLS 1.3, h2, сертификат верный):\\n\' >&2\n for i in "${!found[@]}"; do printf \' %2d) %s\\n\' "$((i + 1))" "${found[i]}" >&2; done\n else\n printf \' в подсети подходящих сайтов не нашлось\\n\' >&2\n fi\n if [[ $role == xhttp ]]; then\n n=${#found[@]}\n printf \' известные сайты (заметнее, чем соседи по подсети):\\n\' >&2\n for i in "${!known[@]}"; do printf \' %2d) %s\\n\' "$((i + 1 + n))" "${known[i]}" >&2; done\n fi\n printf \' 0) ввести адрес сайта вручную\\n\' >&2\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 if [[ $ans == 0 ]]; 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 if [[ $ans =~ ^[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]]; then printf \'%s\' "$ans"; return 0; fi\n printf \' не похоже на адрес сайта\\n\' >&2\n elif [[ $ans =~ ^[0-9]+$ ]]; then\n local total=${#found[@]}\n ((total)) && known=() # для reality известные не предлагаются\n local idx=$((10#$ans))\n if ((idx >= 1 && idx <= total)); then printf \'%s\' "${found[idx - 1]}"; return 0; fi\n if ((idx > total && idx <= total + ${#known[@]})); then printf \'%s\' "${known[idx - total - 1]}"; return 0; fi\n printf \' такого номера нет\\n\' >&2\n else\n printf \' не понял «%s» — введите номер или 0\\n\' "$ans" >&2\n fi\n printf \' ещё раз (Enter — автоподбор): \' >&2\n done\n return 0\n}\n\n # Маскировка: из флагов, по вопросу пользователю или автоматически (сосед по подсети).',
|
|
||||||
'меню выбора сайта маскировки (наш код)',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
' echo\n echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"',
|
|
||||||
False,
|
|
||||||
' echo\n echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."\n echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."\n local -a nb=()\n mapfile -t nb < <(nearby_sites | shuf)\n NB_ALL=("${nb[@]}")\n',
|
|
||||||
'скан + выбор сайта вместо автоподбора',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'scripts/3x-ui.sh',
|
|
||||||
' SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}\n ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"',
|
|
||||||
False,
|
|
||||||
' SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}\n ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"',
|
|
||||||
'не перетирать выбранный SNI2',
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ if grep -qE "command not found|syntax error|bad substitution" <<<"$err"; then
|
|||||||
fi
|
fi
|
||||||
ok "Установщик не ругается на пустые строки и опечатки"
|
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 "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||||
|
|
||||||
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
|
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
|
||||||
|
|||||||
Reference in New Issue
Block a user