Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa9e7225ea | ||
|
|
4a9e452e3e | ||
|
|
d034e207da | ||
|
|
22b9925b0c | ||
|
|
9aa93fad37 | ||
|
|
5558314849 | ||
|
|
b778ce4428 | ||
|
|
143b41c84e | ||
|
|
01662ff039 | ||
|
|
04a69f3ae3 | ||
|
|
0daaf65b5c | ||
|
|
64796e0f53 | ||
|
|
b7834d5f5c | ||
|
|
77f98da99a | ||
|
|
6bb4f55f33 | ||
|
|
6fb7d6c59c | ||
|
|
ad7b66dbf8 | ||
|
|
6323c28533 | ||
|
|
6da8650d1d | ||
|
|
8d470f3eb1 | ||
|
|
c270b09891 | ||
|
|
a1ce2f1c54 | ||
|
|
65f29782fe | ||
|
|
db549348e5 | ||
|
|
7cc7e684d5 | ||
|
|
5e316fb1be | ||
|
|
6b497b047d |
@@ -0,0 +1,32 @@
|
||||
name: Синхронизация с автором
|
||||
|
||||
# Раз в сутки проверяем обновления у автора проекта и, если они есть, переносим наши правки
|
||||
# на свежий код и публикуем в ветку dev. Релиз и подпись здесь НЕ делаются: закрытый ключ
|
||||
# подписи лежит только на рабочей станции владельца.
|
||||
#
|
||||
# Ручной запуск: в Gitea → Actions → «Синхронизация с автором» → Run workflow.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 3 * * *" # каждую ночь в 03:30 по времени сервера Gitea
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
paths:
|
||||
- ".gitea/workflows/fork-sync.yml"
|
||||
|
||||
concurrency:
|
||||
group: fork-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Подтянуть обновления автора, проверить и опубликовать
|
||||
env:
|
||||
GITEA_URL: http://10.10.10.54:3000
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSL "$GITEA_URL/hacker07/3X-UI_autoVPS/raw/branch/main/tools/fork-sync-bot.sh" -o /tmp/fork-sync-bot.sh
|
||||
bash /tmp/fork-sync-bot.sh
|
||||
+115
-12
@@ -1,15 +1,116 @@
|
||||
## v1.2.15: меню выбора сайта в вашем виде, быстрый скан, снос с добивкой
|
||||
|
||||
* Меню маскировки выглядит так, как и просили. Если соседи нашлись — их список по номерам,
|
||||
затем `автовыбор из списка`, `выбрать общедоступный` (отдельным списком) и `ввести свой домен`.
|
||||
Если в подсети ничего не нашлось — сразу «общедоступный» или «свой домен» (ничего лишнего).
|
||||
Enter — автовыбор. Общедоступные сайты предлагаются и для REALITY, и для XHTTP/MTProto.
|
||||
* **Скан подсети стал быстрым.** Было: адреса проверялись по 8 сразу, а найденные имена —
|
||||
по очереди (до 40 имён × три проверки с сетевыми запросами ≈ минуты). Стало: 32 адреса сразу
|
||||
и имена проверяются параллельно по 8; таймаут проверки адреса 3 с вместо 4.
|
||||
* В выводе видно, сколько соседей нашлось (раньше было не понять, работает ли скан).
|
||||
* После нашего меню автор больше не спрашивает «продолжить с запасным сайтом?» — выбор уже сделан.
|
||||
* В меню `kit`: **9 — «Удалить всё установленное», 10 — «Выход»** (меню панели `x-ui` переехало
|
||||
в пункт 11).
|
||||
* **`kit local uninstall` доводит дело до конца:** после явного списка идёт добивка по шаблонам
|
||||
(`/etc/kit*`, `/usr/local/lib/kit*`, юниты `kit-*`/`x-ui*`, `/var/www/kit`, сертификаты), повторная
|
||||
проверка и внятный итог. Появился флаг `--yes` — снести без вопроса.
|
||||
|
||||
## v1.2.14: выбор сайта маскировки по результатам скана + снос, который не обрывается
|
||||
|
||||
* **Появилось меню маскировки.** Сначала один скан подсети (~250 обращений к 443 соседних адресов),
|
||||
потом выбор: номер из найденного, `0` — ввести адрес вручную, Enter — автоподбор. Отдельно для
|
||||
**REALITY** (найденное или свой) и отдельно для **XHTTP/MTProto** (найденное, свой или известный
|
||||
сайт — список известных расширен Microsoft, AMD, Intel, NVIDIA). Если XHTTP/MTProto не выбраны,
|
||||
второго меню не будет. Отключить меню: `--auto-sni`.
|
||||
* **Исправлено: `kit local uninstall` обрывался на середине.** `kit` запускает наши команды
|
||||
с `set -e`, и подсчёт чужих сайтов nginx (`find | grep | wc -l`, где grep не находит ничего и
|
||||
возвращает 1) убивал скрипт сразу после остановки служб — файлы и `/usr/local/bin/kit` оставались,
|
||||
поэтому `kit help` продолжал работать. Теперь снос выполняется с отключённым `set -e` и все
|
||||
подобные конвейеры защищены; проверка после сноса это подтверждает списком, если что-то осталось.
|
||||
## v1.2.13: вопросов после показа доступов нет, и никакой работы для невыбранных протоколов
|
||||
|
||||
* **Все вопросы — до установщика.** Раньше после установки (когда на экране уже были данные входа
|
||||
в панель) скрипт спрашивал про ping, автообновления, порт SSH и пароль root — доступы терялись
|
||||
в потоке вопросов. Теперь спрашиваем всё заранее, а действия выполняем после установки; последнее,
|
||||
что видит человек, — доступ к панели, подписка и QR-код.
|
||||
* Автор тянет подписки «-awg» и «-tg» (наследие установок kit < 1.1) всегда — теперь только если
|
||||
AmneziaWG или MTProto выбраны.
|
||||
* Наши команды помнят выбор: установщик пишет список протоколов в `/etc/kit/kl-protos`, и
|
||||
`kit local site` не синхронизирует ответ Hysteria2, если Hysteria2 не ставился (раньше он это делал
|
||||
и выдавал предупреждение про отсутствующий Hysteria2).
|
||||
## v1.2.12: не сканируем соседей, когда они не нужны
|
||||
|
||||
* Автор подбирал сайты-соседи для XHTTP и MTProto **всегда**, когда маскировка доверенная («панель по IP»)
|
||||
и всё висит на 443 — независимо от того, выбраны ли эти протоколы. При установке одного REALITY это
|
||||
давало ~250 обращений к чужим адресам впустую. Теперь скан идёт только если XHTTP или MTProto выбраны;
|
||||
иначе имена SNI2/SNI3 берутся из общего списка сайтов автора (как и раньше при неудаче скана).
|
||||
|
||||
# Что нового в сборке форка
|
||||
|
||||
Здесь записи о наших версиях. Файл автора (`CHANGELOG.md`) мы не трогаем — так обновления
|
||||
автора не конфликтуют с нашими записями.
|
||||
## v1.2.11: снос знает про kit-limit и находит свои службы сам
|
||||
|
||||
## v1.2.6: вопросы при установке
|
||||
* В списке сноса не было `kit-limit.service` и `kit-limit.timer` (юнит «общий лимит трафика» из kit) —
|
||||
его создаёт установка, а `kit local uninstall` его не убирал. Из-за этого после сноса оставался
|
||||
работающий таймер, который раз в 5 минут дёргал уже удалённый `kit` и падал.
|
||||
* Теперь снос берёт список служб **и из явного перечня, и по шаблонам `x-ui*`/`kit*`** из systemd —
|
||||
новая служба, добавленная автором позже, не останется незамеченной. Проверка после сноса ищет
|
||||
по тем же шаблонам и показывает всё найденное.
|
||||
* Флаг `--clean` у установщика использует тот же список.
|
||||
## v1.2.10: убран мусор в выводе установщика
|
||||
|
||||
* В шапке `scripts/3x-ui-local.sh` один из флагов потерял `#` и **при каждом запуске печаталось
|
||||
`--dry-run: command not found`** (до `set -e` — потому и не роняло установку, но выглядело как ошибка).
|
||||
Исправлено, заодно выровнены строки справки.
|
||||
* В релизный гейт добавлена проверка, которая такое ловит: холостой прогон установщика, чей вывод в stderr
|
||||
содержит «command not found / syntax error / bad substitution», релиз не собирается.
|
||||
## v1.2.9: снос действительно убирает всё, повторная установка после сноса
|
||||
|
||||
* Список удаляемого в `kit local uninstall` собран заново **по коду установщика** и дополнен тем, чего
|
||||
не хватало: `/usr/local/lib/kit-sub`, `/root/kit-backup-*` и `/root/x-ui-before-*` (копии kit),
|
||||
`/var/log/kit-install-check.log`, `/usr/bin/x-ui.new`, строка `include kit-stream.conf` в `nginx.conf`,
|
||||
симлинки в `sites-enabled`/`streams-enabled`, файл автообновлений безопасности, пользователь `kit-sub`.
|
||||
Из-за пропущенных файлов установщик после сноса отказывался ставить заново («3X-UI уже установлена»).
|
||||
* После сноса печатается проверка **теми же условиями, что у установщика**, и список того, что осталось
|
||||
на диске — вместо бодрого «удалено» вслепую.
|
||||
* Флаг `--reset-ssh`: снести и наш ssh-дроп-ин (порт возвращается к системному). По умолчанию SSH не трогаем.
|
||||
* У установщика появился флаг **`--clean`**: убрать прошлую установку и поставить заново. Если установщик
|
||||
всё-таки упал на «уже установлена», в подсказке прямо сказано запустить с `--clean`.
|
||||
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
|
||||
|
||||
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
|
||||
файла `/root/3x-ui.txt` — раньше это было видно только в общем потоке вывода установщика.
|
||||
* **`kit local uninstall`** — снос установки целиком: панель и служба `x-ui`, ядро Xray, службы и файлы
|
||||
kit (`kit-sub`, автообновление, cron), конфиги nginx и сайт-заглушка, сертификаты (acme.sh),
|
||||
наши правки системы (правило ICMP, обновления безопасности), наши бэкапы и файл с данными панели.
|
||||
Правила ufw для 80/443 снимаются, ответ на ping возвращается. Спрашивает подтверждение словом
|
||||
«удалить», есть `--dry-run`.
|
||||
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
|
||||
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
|
||||
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
|
||||
|
||||
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
|
||||
стиле, новое имя в справке, версии, описаниях служб и в имени профиля в клиенте. В файлах автора
|
||||
это сделано швами (`tools/fork_apply.py`), поэтому обновление автора правки не затирает.
|
||||
* **Совместимость шапки релиза.** В подписанном `SHA256SUMS` этого релиза шапка пишется **двумя
|
||||
строками** (старая и новая), а проверка на сервере принимает обе. Иначе уже установленные серверы
|
||||
отказались бы ставить обновление: они сверяют точный вид строки. Со следующего релиза останется
|
||||
только новое имя.
|
||||
* Убрано дублирование подсказки «Дальше обязательно» при смене порта SSH: она печаталась и
|
||||
установщиком, и командой `kit local ssh`.
|
||||
* **`kit help` — полный справочник по категориям**: пользователи, подключения и порты, маскировка и
|
||||
сайт, проверка и починка, обслуживание и обновление, защита сервера (наш слой). Раньше `kit help`
|
||||
показывал короткий список автора и не упоминал наши команды. Чтобы справка не отставала молча,
|
||||
добавлена проверка `tools/check-help.sh`: она сверяет справку с диспетчером команд и встроена в
|
||||
сборку релиза (`tools/release.sh`).
|
||||
* У сайтов-заглушек все ссылки и кнопки **живые**: нажатие открывает окно «раздел в разработке,
|
||||
приносим извинения» (закрыть — кнопкой, кликом мимо окна или Esc). Никаких внешних запросов:
|
||||
скрипт и стили внутри страницы.
|
||||
* В самом конце установки (после всех проверок) печатается то, что нужно сохранить: **подписка
|
||||
с QR-кодом** и ссылки по протоколам — авторской командой `kit user link <имя> --all`, поэтому
|
||||
вывод совпадает с тем, что показывает `kit` потом. Отключается флагом `--no-sub`.
|
||||
|
||||
* **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`.
|
||||
* Набор по умолчанию — **REALITY + AmneziaWG-3.1** (самый совместимый минимум); ответ «все»
|
||||
ставит полный набор автора. Умолчание задаётся в одном месте (`DEFAULT_PROTOS`).
|
||||
* **Сайт-заглушка переделан на ИТ-тематику:** десять правдоподобных сайтов (дата-аналитика, DevOps,
|
||||
@@ -34,7 +135,13 @@
|
||||
* Флаг `--harden` теперь действительно существует (раньше он уходил установщику автора неизвестным
|
||||
параметром и ронял установку): закрыть ping и включить автообновления без вопросов.
|
||||
* `--dry-run` показывает, что именно будет спрошено или сделано по умолчанию.
|
||||
## v1.2.6: вопросы при установке
|
||||
|
||||
* **README.md переписан целиком:** убрано авторское описание с его командой установки, добавлены
|
||||
актуальная команда, флаги, таблица протоколов, список сайтов-заглушек, команды `kit` и `kit local`,
|
||||
канал обновлений и оговорки. Две картинки — наши (`manuals/assets/fork-banner.svg` и
|
||||
`manuals/assets/script-3x-ui-local.svg`, терминал собран из настоящего вывода скрипта).
|
||||
* Шов в README больше не нужен (`tools/fork_apply.py`): файл наш целиком, как `kit-local.sh`.
|
||||
## v1.2.5: суммы релиза в формате, который понимает kit
|
||||
|
||||
* Исправлена ошибка нашего релизного конвейера: на Windows `sha256sum` пишет суммы как
|
||||
@@ -45,14 +152,12 @@
|
||||
другой машине.
|
||||
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
|
||||
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
|
||||
|
||||
## v1.2.4: проверка релиза отдельной командой
|
||||
|
||||
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
|
||||
можно проверить канал на сервере, ничего не устанавливая.
|
||||
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
|
||||
изменений на сервере, чтобы чужой код не запускался раньше проверки.
|
||||
|
||||
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
|
||||
|
||||
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
|
||||
@@ -67,7 +172,6 @@
|
||||
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
|
||||
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
|
||||
ключа обновляется и здесь, и в `kit`.
|
||||
|
||||
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
|
||||
|
||||
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
|
||||
@@ -84,8 +188,7 @@
|
||||
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
|
||||
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
||||
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
||||
|
||||
## v1.2.1: сборка форка поверх 3X-UI KIT 1.2
|
||||
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
|
||||
|
||||
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
||||
оттуда, а подпись релиза проверяется ключом форка. Авторский ночной апдейт приходит в наш
|
||||
@@ -97,4 +200,4 @@
|
||||
* Швы в коде автора описаны в `tools/fork_apply.py` (источник правды), синхронизация с автором и
|
||||
проверки — `tools/fork-sync.sh`.
|
||||
|
||||
Основа — 3X-UI KIT 1.2 (панель 3X-UI v3.9.0, ядро Xray v26.6.27). Что нового у автора — в `CHANGELOG.md`.
|
||||
Основа — 3X-UI_autoVPS 1.2 (панель 3X-UI v3.9.0, ядро Xray v26.6.27). Что нового у автора — в `CHANGELOG.md`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Форк 3X-UI KIT (hacker07) — своя сборка и свой канал обновлений
|
||||
# Форк 3X-UI_autoVPS (hacker07) — своя сборка и свой канал обновлений
|
||||
|
||||
Коротко: это [3X-UI KIT](https://github.com/itsnotkubrick/3X-UI_KIT) со своими доработками. Наш код
|
||||
Коротко: это [3X-UI_autoVPS](https://github.com/itsnotkubrick/3X-UI_KIT) со своими доработками. Наш код
|
||||
живёт рядом с кодом автора и не ломается, когда автор выпускает новую версию, а серверы ставятся и
|
||||
обновляются **из этого репозитория** — релизы подписываются нашим ключом.
|
||||
|
||||
@@ -124,7 +124,27 @@ Hysteria2 (UDP 443) работают как работали; исходящий
|
||||
если автор изменил нужную строку — инструмент останавливается и говорит об этом, а не делает
|
||||
полу-патч.
|
||||
|
||||
## Ветки и релизы
|
||||
## Ветки
|
||||
|
||||
`tools/release.sh` — **наш файл** (как README): авторский скрипт подписи мы переписали под себя
|
||||
(проверка LF, гейт справки, шапка `SHA256SUMS` в двух строках, свой список файлов). Швов к нему нет;
|
||||
при обновлении из авторского проекта файл конфликтует и решается в нашу пользу (как README).
|
||||
|
||||
Проверка корректности швов (делать после каждой правки и после синхронизации с автором):
|
||||
накатать `tools/fork_apply.py` на чистую копию авторского кода и сверить с нашим деревом — расхождений
|
||||
быть не должно, кроме наших собственных файлов. Это единственный способ увидеть правки, которых нет в швах.
|
||||
|
||||
**`main` двигается только вместе с подписанным релизом.** Серверы читают версию из `main` (`KIT_RAW`),
|
||||
поэтому в `main` не должно попадать то, что ещё не подписано: иначе `kit update` и однострочник
|
||||
установки упираются в отсутствующий тег или в неподписанные суммы. Порядок такой:
|
||||
|
||||
1. поднять версию и собрать изменения в `dev`;
|
||||
2. пользователь подписывает (`tools/release.sh`);
|
||||
3. коммит с `SHA256SUMS` и `SHA256SUMS.sig` → тег `v<версия>` → и только теперь `dev` вливается в `main`.
|
||||
|
||||
Проверка перед публикацией в `main`: `curl -fsSL .../raw/tag/v<версия>/VERSION` должен отдавать ту же
|
||||
версию, а `bash <(curl ... raw/branch/main/scripts/3x-ui-local.sh) --verify-only` — писать, что подпись сошлась.
|
||||
и релизы
|
||||
|
||||
- `upstream` — точная копия ветки автора (`main`), только вперёд.
|
||||
- `dev` — наша сборка: `upstream` + швы + наши файлы; из неё делаются релизы.
|
||||
@@ -148,6 +168,18 @@ kit update --force && kit check # тянет наш релиз из это
|
||||
|
||||
## Обновление от автора (с рабочей станции)
|
||||
|
||||
### Автоматически: робот на раннере (CT 114)
|
||||
|
||||
Раз в сутки (03:30) Gitea сама запускает `.gitea/workflows/fork-sync.yml` на раннере в CT 114:
|
||||
робот `tools/fork-sync-bot.sh` забирает ветку автора, переносит на неё наши правки, прогоняет те же
|
||||
проверки, что и `tools/fork-sync.sh`, дописывает раздел в `FORK-CHANGELOG.md` и публикует `dev`
|
||||
(`--force-with-lease`). Не сошлось (конфликт в местах наших правок) — **ничего не публикуется**,
|
||||
причина видна в Gitea → Actions и в `/opt/kit-fork-sync/last-run.log` на контейнере.
|
||||
|
||||
Робот **не** выпускает и не подписывает релиз: закрытый ключ подписи остаётся на рабочей станции.
|
||||
После автоматической синхронизации — как обычно: `git fetch origin && git checkout dev && git reset --hard origin/dev`
|
||||
(робот переписывает `dev`), описать изменения в `FORK-CHANGELOG.md`, затем `tools/release.sh` и тег.
|
||||
|
||||
```bash
|
||||
cd H:/My_Proj/Fork_3X-UI_KIT
|
||||
bash tools/fork-sync.sh # подтянуть автора, перенести наши правки, прогнать проверки
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**Сервер одной командой: панель 3X-UI, VPN-протоколы за одним портом 443, одна подписка на все устройства**
|
||||
|
||||
Сборка со **своим каналом обновлений**: сервер ставится и обновляется из этого репозитория, релизы
|
||||
подписаны, ночное автообновление не вернёт чужую версию. В основе — открытый проект 3X-UI KIT.
|
||||
подписаны, ночное автообновление не вернёт чужую версию. В основе — открытый проект 3X-UI_autoVPS.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,9 +28,15 @@
|
||||
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
||||
```
|
||||
|
||||
Без флагов скрипт **спрашивает** всё по ходу: какие протоколы ставить (Enter — REALITY + AmneziaWG-3.1),
|
||||
какую маскировку и домен, закрывать ли ответ на ping, включать ли автообновления безопасности,
|
||||
менять ли порт SSH (Enter — оставить 22), менять ли пароль root.
|
||||
Без флагов скрипт **спрашивает всё заранее, до установки**: какие протоколы ставить (Enter — REALITY +
|
||||
AmneziaWG-3.1), менять ли порт SSH (Enter — оставить 22), менять ли пароль root, закрывать ли ответ на
|
||||
ping, включать ли автообновления безопасности. Затем сканирует подсеть и **предлагает выбрать сайт
|
||||
маскировки** — отдельно для REALITY и, если выбраны XHTTP/MTProto, отдельно для них: номер из найденных
|
||||
соседей, `0` — ввести адрес вручную, для XHTTP/MTProto ещё и известный сайт (Google, Microsoft, AMD…).
|
||||
|
||||
После установщика скрипт **ничего не спрашивает** — только выполняет выбранное, проверяет сервер и в
|
||||
самом конце показывает то, что нужно унести: **доступ к панели 3X-UI** (адрес, логин, пароль) и
|
||||
**подписку с QR-кодом** и ссылками по протоколам.
|
||||
|
||||
<img alt="Установка одной командой" src="manuals/assets/script-3x-ui-local.svg" width="860">
|
||||
|
||||
@@ -67,6 +73,9 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
|
||||
| `--harden` / `--no-harden` | закрыть ping и включить автообновления безопасности / не трогать это |
|
||||
| `--yes` | не задавать вопросы (для автоматизации) |
|
||||
| `--host IP`, `--user имя`, `--port N`, `--restore файл` | адрес в ссылках, имя первого клиента, порт REALITY, восстановление из копии `kit backup` |
|
||||
| `--clean` | убрать прошлую (оборвавшуюся) установку и поставить заново — SSH не трогает |
|
||||
| `--auto-sni` | не показывать меню выбора сайта маскировки, подобрать самим (автоподбор автора) |
|
||||
| `--no-sub` | не показывать в конце доступ к панели, подписку и QR-код |
|
||||
| `--no-time`, `--no-check`, `--verify-only`, `--dry-run`, `-h` | служебные |
|
||||
|
||||
## Что происходит по шагам
|
||||
@@ -74,15 +83,17 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
|
||||
1. **Проверка сервера**: root, Debian/Ubuntu, память, место на диске, занятые порты 80/443.
|
||||
2. **Проверка релиза**: файлы берутся из этого репозитория по тегу, проверяется **подпись `SHA256SUMS`**
|
||||
нашим ключом и SHA256 самого установщика. Не сошлось — ничего не запускается.
|
||||
3. **Выбор протоколов** и запуск установщика: панель 3X-UI, выбранные подключения, nginx на 443,
|
||||
сертификат, сайт-заглушка, служба подписки, правила ufw.
|
||||
4. **Сайт-заглушка**: ставится случайный правдоподобный ИТ-сайт из нашего набора (10 штук) и
|
||||
синхронизируется в ответ Hysteria2.
|
||||
5. **Наши вопросы и правки**: закрыть ответ на ping, включить обновления безопасности, сменить порт
|
||||
SSH, сменить пароль root.
|
||||
6. **`kit fix`** — синхронизация времени и безопасные починки.
|
||||
7. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
|
||||
`kit local hardening`. В конце — как забрать ссылки и подписку.
|
||||
3. **Наши вопросы** (до установки): протоколы, порт SSH, пароль root, ping, автообновления.
|
||||
4. **Установщик**: панель 3X-UI, только выбранные подключения, nginx на 443, сертификат, сайт-заглушка,
|
||||
служба подписки, правила ufw. Маскировку спрашивает он: свой домен, сосед по подсети или свой сайт.
|
||||
Если выбран сосед — сначала идёт **скан подсети**, затем меню выбора сайта (см. выше).
|
||||
5. **Наши действия** (без вопросов): правдоподобный ИТ-сайт-заглушка вместо авторской страницы,
|
||||
`kit fix` (время и безопасные починки), закрыть ответ на ping, включить обновления безопасности,
|
||||
перевести SSH на новый порт, сменить пароль root.
|
||||
6. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
|
||||
`kit local hardening`.
|
||||
7. **В самом конце** — **доступ к панели 3X-UI** (адрес, логин, пароль из `/root/3x-ui.txt`) и
|
||||
**подписка с QR-кодом** и ссылками по протоколам. После них скрипт уже ничего не делает и не спрашивает.
|
||||
|
||||
## Протоколы
|
||||
|
||||
@@ -110,6 +121,15 @@ bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что имен
|
||||
дополнительных объяснений на клиенте. Маскировку и сайт можно менять потом: `kit net site`,
|
||||
`kit local site`.
|
||||
|
||||
**Сайт маскировки выбираете вы.** После скана подсети установщик спрашивает отдельно для REALITY
|
||||
(найденный сосед или свой адрес) и отдельно для XHTTP/MTProto (найденный, свой или известный сайт —
|
||||
список известных расширен Google, Amazon, Samsung, Yahoo, Microsoft, AMD, Intel, NVIDIA). Выбранный
|
||||
адрес проверяется на TLS 1.3 + HTTP/2. Если XHTTP и MTProto не выбраны, второго вопроса нет.
|
||||
|
||||
Для протоколов, которые **не** выбраны, не делается ничего: ни подключений, ни записей в nginx, ни
|
||||
ссылок в подписке, ни лишних обращений к сети (например, обход соседей запускается только когда он
|
||||
действительно нужен).
|
||||
|
||||
## Сайт-заглушка
|
||||
|
||||
На 443 при неизвестном имени отдаётся страница нашего набора: небольшие правдоподобные ИТ-сайты
|
||||
@@ -163,6 +183,7 @@ kit local site --show # какой стоит сейчас
|
||||
| `kit local ping off\|on\|status` | ответ на ICMP echo правилом ufw (видно в `ufw status`, отменяется одной командой) |
|
||||
| `kit local passwd [юзер]`, `--generate` | смена пароля (спрашивает дважды, в логи не попадает) |
|
||||
| `kit local upgrades on\|off\|status` | обновления безопасности |
|
||||
| `kit local uninstall` | **снести установку целиком**: панель, службы и файлы kit, nginx и сайт, сертификаты, наши правки системы и правила ufw для 80/443. Спрашивает слово «удалить», есть `--dry-run`. **SSH не трогает** |
|
||||
| `kit local hardening`, `masking`, `channel`, `seams`, `version` | сводка защиты, состояние маскировки, канал обновлений, какие места форк правит у автора |
|
||||
|
||||
У любой правки есть `--dry-run` — показывает план и ничего не меняет. Копии изменённых файлов
|
||||
|
||||
+9
-8
@@ -1,8 +1,9 @@
|
||||
# 3X-UI KIT 1.2.6
|
||||
db677f17e1c8f59e0c5f329faff6f6178454b75b266966b80c225b8923318831 scripts/3x-ui.sh
|
||||
b8bc9cb115e427b601d2d9ea0b3e1c759b4fc0f9f36b267e364e47658422e37d scripts/hysteria2.sh
|
||||
1215b3435a5f476dee26fd732640c6d33a7445585f14108a289ae4eafb05b439 scripts/kit.sh
|
||||
d393dc04a69bad2ce9f022b2f51e0d349b6655c5c92fc64cf67402c5a63e2f3a scripts/kit-sub.py
|
||||
ae81cd252affc1bbfdc59421dfa30f85eb6bbbd3e178faceb7e6f708f36b6730 scripts/kit-local.sh
|
||||
eaf72eb353c14de8bc9cd567b396efc40eb1493c3839009465f0f8b35f0db557 scripts/3x-ui-local.sh
|
||||
5844ffcc346f89c07b13ba7596bfb3788ed73f4755e541182d7822d43b7c7a24 VERSION
|
||||
# 3X-UI KIT 1.2.15
|
||||
# 3X-UI_autoVPS 1.2.15
|
||||
6f92f568cd4c12090a3a57c4c17382b9b1045982109cff0e2fdfc88f11a8f594 scripts/3x-ui.sh
|
||||
61c17a801d2c77c66242c30480c33e15bc9d70dae84afa5116a116ffff75ae9d scripts/hysteria2.sh
|
||||
980c8f54a5cdb3c524a03ddb5750f1451dbd409cf14cb588d7aec415d01976d4 scripts/kit.sh
|
||||
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
|
||||
c38e5fe05bec66e747bb3feee2af4444d3a53d66e74ef1acb59861fbafa65e4c scripts/kit-local.sh
|
||||
fb0393138347800635c62481ad63a7884fc341c6e982d4c5371af040d1a3c1ae scripts/3x-ui-local.sh
|
||||
091d55b64de2013341ba60beea22ca1392e1c258d68ff2aa2e8f901cf9723d63 VERSION
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABAHRB6TB0NGpmJ1w5Ua1PEGUrmlQUuM99cll5nnhO9EJvMRz
|
||||
IoDmvnCMGC4Xgh22IeRR88vtLkKz3hgtMoMYRLAg==
|
||||
AAALc3NoLWVkMjU1MTkAAABAGOHytoKQ2p0+qkeebGkp+s0UwEDY6JFdBOCwsnzSTcpYIW
|
||||
/78qQjt6WV+B4MSh8mEi9Chr/fpCG8plW8nFJvAQ==
|
||||
-----END SSH SIGNATURE-----
|
||||
|
||||
+184
-78
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
# 3X-UI_autoVPS (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
#
|
||||
# Что делает (по шагам):
|
||||
# 1) проверяет сервер и наличие curl, ssh-keygen, openssl;
|
||||
@@ -14,7 +14,8 @@
|
||||
# 6) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
|
||||
# безопасности, перевести SSH на другой порт, сменить пароль root (поведение из StressOzz/vps-setup,
|
||||
# но безопаснее);
|
||||
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
|
||||
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening;
|
||||
# 8) последним — подписка, QR-код и ссылки по протоколам: то, что нужно сохранить.
|
||||
#
|
||||
# Запуск:
|
||||
# # одна команда, дальше — вопросы:
|
||||
@@ -33,6 +34,8 @@
|
||||
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
|
||||
# --no-time не запускать kit fix
|
||||
# --no-check не запускать в конце kit check
|
||||
# --clean убрать прошлую установку и поставить заново (SSH не трогает)
|
||||
# --auto-sni не предлагать выбор сайта маскировки (иначе покажем меню по скану подсети)
|
||||
# --verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
# --dry-run показать план и ничего не делать
|
||||
# -h | --help эта справка
|
||||
@@ -41,12 +44,12 @@
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
KIT_VERSION="1.2.6" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
KIT_VERSION="1.2.15" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||
NS="3x-ui-kit-release"
|
||||
SIG_ID="releases@3x-ui-kit"
|
||||
# Публичный ключ форка. Меняется только вместе с ключом подписи (тогда обновляется и здесь, и в kit).
|
||||
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)'
|
||||
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)'
|
||||
|
||||
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
|
||||
[[ -t 1 ]] || { G=; Y=; R=; B=; D=; N=; }
|
||||
@@ -56,7 +59,7 @@ die() { printf '%s\n' "${R}✗${N} $*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
3X-UI_autoVPS (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
|
||||
bash <(curl -fsSL <наш Gitea>/raw/branch/main/scripts/3x-ui-local.sh) [флаги…]
|
||||
|
||||
@@ -77,6 +80,9 @@ usage() {
|
||||
--yes ни о чём не спрашивать (для автоматизации)
|
||||
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
|
||||
не трогать авторскую страницу (то же: --no-site)
|
||||
--no-sub не показывать в конце подписку, доступ к панели и QR-код
|
||||
--clean снести прошлую установку и поставить заново (если прошлый запуск оборвался)
|
||||
--auto-sni не предлагать выбор сайта маскировки (автоподбор автора)
|
||||
--no-time не запускать kit fix (время и безопасные починки)
|
||||
--no-check не запускать в конце kit check
|
||||
--verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
@@ -89,6 +95,9 @@ EOF
|
||||
|
||||
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
|
||||
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
|
||||
show_sub=yes # в самом конце показать подписку, QR-код и ссылки
|
||||
clean=no # --clean: убрать прошлую установку перед новой
|
||||
pick_sni=yes # показывать меню выбора сайта маскировки по результатам скана
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--harden) harden=yes ;;
|
||||
@@ -99,6 +108,9 @@ while (($#)); do
|
||||
--site) site=${2:-random}; shift || die "--site: нужен номер сайта (1..10), random или off" ;;
|
||||
--site=*) site=${1#*=} ;;
|
||||
--no-site) site=off ;;
|
||||
--no-sub) show_sub=no ;;
|
||||
--clean) clean=yes ;;
|
||||
--auto-sni) pick_sni=no ;;
|
||||
--no-time) fix_time=no ;;
|
||||
--no-check) do_check=no ;;
|
||||
--verify-only) verify_only=yes ;;
|
||||
@@ -130,6 +142,9 @@ if [[ $dry == yes ]]; then
|
||||
say "план установки (--dry-run: ничего не делаю)"
|
||||
pre "версия сборки: $KIT_VERSION"
|
||||
pre "откуда: $GITEA (тег v$KIT_VERSION), подпись проверяется ключом форка"
|
||||
[[ $clean == yes ]] && pre "прошлая установка: уберу её перед установкой (--clean), SSH не трону"
|
||||
if [[ $pick_sni == yes ]]; then pre "маскировка: просканирую подсеть и предложу выбрать сайт (--auto-sni — автоподбор)"
|
||||
else pre "маскировка: подберу сайт сам, без вопросов (--auto-sni)"; fi
|
||||
if ((${#INSTALLER_ARGS[@]})); then pre "установщик автора: bash scripts/3x-ui.sh ${INSTALLER_ARGS[*]}"; else pre "установщик автора: bash scripts/3x-ui.sh — спросит про маскировку и домен"; fi
|
||||
if [[ $harden == auto ]]; then
|
||||
if interactive; then pre "защита: спрошу про ответ на ping и автообновления"
|
||||
@@ -149,6 +164,7 @@ if [[ $dry == yes ]]; then
|
||||
esac
|
||||
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
|
||||
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
|
||||
[[ $show_sub == yes ]] && pre "в самом конце: доступ к панели 3X-UI, подписка, QR-код и ссылки (--no-sub — не надо)"
|
||||
echo
|
||||
say "запуск без --dry-run выполнит всё это на сервере"
|
||||
exit 0
|
||||
@@ -270,6 +286,57 @@ ask_protocols() { # печатает итоговый список через з
|
||||
printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ снос прошлой установки (--clean)
|
||||
|
||||
# Нужен, когда прошлая установка оборвалась: авторский установщик в таком случае отказывается работать
|
||||
# («3X-UI уже установлена этим скриптом»). Убираем то, что создаёт установка, и ставим заново.
|
||||
# SSH не трогаем: ни порт, ни конфиги — иначе можно потерять доступ к серверу.
|
||||
clean_previous() {
|
||||
say "убираю прошлую установку (--clean)"
|
||||
local s p
|
||||
for s in x-ui kit-sub kit-update.timer kit-update.service kit-limit.timer kit-limit.service \
|
||||
$(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' | grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
|
||||
systemctl list-unit-files 2>/dev/null | grep -q "^$s" || continue
|
||||
systemctl disable --now "$s" >/dev/null 2>&1 || true
|
||||
printf ' остановлено: %s\n' "$s"
|
||||
done
|
||||
for p in /usr/local/x-ui /etc/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /etc/systemd/system/x-ui.service \
|
||||
/usr/local/bin/kit /usr/local/lib/kit /usr/local/lib/kit-sub /etc/kit /etc/kit-sub \
|
||||
/etc/systemd/system/kit-sub.service /etc/systemd/system/kit-update.service \
|
||||
/etc/systemd/system/kit-update.timer /etc/systemd/system/kit-limit.service \
|
||||
/etc/systemd/system/kit-limit.timer \
|
||||
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert \
|
||||
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf /var/www/kit \
|
||||
/root/3x-ui.txt /root/cert /var/log/kit-install-check.log \
|
||||
/root/kit-local-backup-* /root/kit-backup-* /root/x-ui-before-sni-* /root/x-ui-before-update-*; do
|
||||
[[ -e $p ]] || continue
|
||||
rm -rf $p && printf ' удалено: %s\n' "$p"
|
||||
done
|
||||
if [[ -f /etc/nginx/nginx.conf ]] && grep -q 'kit-stream\.conf' /etc/nginx/nginx.conf; then
|
||||
sed -i '/kit-stream\.conf/d' /etc/nginx/nginx.conf
|
||||
printf ' из nginx.conf убрана наша строка включения\n'
|
||||
fi
|
||||
if id -u kit-sub >/dev/null 2>&1; then
|
||||
pkill -u kit-sub >/dev/null 2>&1 || true
|
||||
userdel -r kit-sub >/dev/null 2>&1 || true
|
||||
printf ' удалён пользователь kit-sub\n'
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
|
||||
warn "часть файлов прошлой установки осталась — проверьте: ls -la /usr/local/x-ui /root/3x-ui.txt"
|
||||
else
|
||||
printf ' готово: место под установку свободно\n'
|
||||
fi
|
||||
}
|
||||
|
||||
installer_failed() {
|
||||
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
|
||||
printf '\n%s!%s %s\n' "$Y" "$N" \
|
||||
"На сервере осталась прошлая установка (установщик так и пишет). Запустите ещё раз с флагом --clean — он уберёт старое и поставит заново." >&2
|
||||
fi
|
||||
die "установщик остановился с ошибкой — смотрите его вывод выше"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ установка
|
||||
|
||||
if [[ -z $proto_flag ]]; then
|
||||
@@ -280,65 +347,9 @@ else
|
||||
fi
|
||||
say "протоколы: $protos — под остальные не создаётся ничего"
|
||||
|
||||
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
|
||||
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
|
||||
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
|
||||
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
|
||||
if [[ -r /dev/tty ]]; then
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || die "установщик остановился с ошибкой — смотрите его вывод выше"
|
||||
else
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || die "установщик остановился с ошибкой (нет терминала — вопросы задать некому)"
|
||||
fi
|
||||
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
|
||||
|
||||
# ------------------------------------------------------------------ наши дополнения
|
||||
|
||||
ask_yes() { # "вопрос" y|n — по умолчанию
|
||||
local q=$1 def=$2 ans=""
|
||||
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|д|да)$ ]]
|
||||
}
|
||||
|
||||
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
|
||||
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
|
||||
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
|
||||
case $site in
|
||||
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
|
||||
random)
|
||||
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
|
||||
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
|
||||
;;
|
||||
*)
|
||||
say "ставлю сайт-заглушку №$site"
|
||||
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $fix_time == yes ]]; then
|
||||
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
|
||||
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
|
||||
fi
|
||||
|
||||
# ping и автообновления: --harden/--no-harden решают без вопросов, иначе спрашиваем
|
||||
want_ping=$harden; want_update=$harden
|
||||
[[ $want_ping == auto ]] && want_ping=yes
|
||||
[[ $want_update == auto ]] && want_update=yes
|
||||
if [[ $harden == auto ]] && interactive; then
|
||||
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
|
||||
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
|
||||
fi
|
||||
if [[ $want_ping == yes ]]; then
|
||||
say "закрываю ответ на ping (правилом ufw)"
|
||||
kit local ping off || warn "не удалось — повторите: kit local ping off"
|
||||
fi
|
||||
if [[ $want_update == yes ]]; then
|
||||
say "включаю автообновления безопасности"
|
||||
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
|
||||
fi
|
||||
# ------------------------------------------------------------------ вопросы (всё спросим ДО установки)
|
||||
# После установщика на экране появляются доступы к панели: там вопросы уже неуместны. Поэтому
|
||||
# спрашиваем всё заранее, а действия выполняем после установки (см. «наши дополнения» ниже).
|
||||
|
||||
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
|
||||
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
|
||||
@@ -376,23 +387,84 @@ ask_ssh_port() { # пусто — оставить как есть; иначе
|
||||
warn "оставляю прежний порт ($cur)"
|
||||
}
|
||||
|
||||
want_ping=$harden; want_update=$harden
|
||||
[[ $want_ping == auto ]] && want_ping=yes
|
||||
[[ $want_update == auto ]] && want_update=yes
|
||||
if [[ $harden == auto ]] && interactive; then
|
||||
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
|
||||
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
|
||||
fi
|
||||
|
||||
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
|
||||
want_passwd=no
|
||||
if interactive; then
|
||||
if ask_yes "Сменить пароль root прямо сейчас? Новый пароль введёте в следующем окне." n; then want_passwd=yes; fi
|
||||
fi
|
||||
|
||||
[[ $clean == yes ]] && clean_previous
|
||||
|
||||
# Меню выбора сайта маскировки: сканируем подсеть один раз и предлагаем выбрать сайт отдельно для
|
||||
# REALITY и (если выбраны) для XHTTP/MTProto. Отключается флагом --auto-sni (тогда автоподбор автора).
|
||||
if [[ $pick_sni == yes ]]; then export KL_PICK_SNI=1; else export KL_PICK_SNI=0; fi
|
||||
|
||||
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
|
||||
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
|
||||
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
|
||||
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
|
||||
if [[ -r /dev/tty ]]; then
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || installer_failed
|
||||
else
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || installer_failed
|
||||
fi
|
||||
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
|
||||
|
||||
# Запоминаем, какие протоколы выбраны: наши команды (kit local site и др.) смотрят сюда и не
|
||||
# делают ничего для протоколов, которых в этой установке нет.
|
||||
printf '%s\n' "${protos//,/ }" | tr ' ' '\n' | sed '/^$/d' > /etc/kit/kl-protos 2>/dev/null || true
|
||||
|
||||
# ------------------------------------------------------------------ наши дополнения (только действия)
|
||||
|
||||
# Ничего не спрашиваем: все ответы собраны до установщика. Здесь только выполняем.
|
||||
|
||||
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
|
||||
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
|
||||
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
|
||||
case $site in
|
||||
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
|
||||
random)
|
||||
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
|
||||
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
|
||||
;;
|
||||
*)
|
||||
say "ставлю сайт-заглушку №$site"
|
||||
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $fix_time == yes ]]; then
|
||||
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
|
||||
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
|
||||
fi
|
||||
|
||||
# Действия по ответам, которые были заданы ДО установщика (см. блок вопросов выше).
|
||||
if [[ $want_ping == yes ]]; then
|
||||
say "закрываю ответ на ping (правилом ufw)"
|
||||
kit local ping off || warn "не удалось — повторите: kit local ping off"
|
||||
fi
|
||||
if [[ $want_update == yes ]]; then
|
||||
say "включаю автообновления безопасности"
|
||||
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
|
||||
fi
|
||||
|
||||
if [[ $want_passwd == yes ]]; then
|
||||
say "меняю пароль root"
|
||||
kit local passwd || warn "не получилось — повторите: kit local passwd"
|
||||
fi
|
||||
|
||||
# порт SSH: новый порт добавляем, прежний остаётся рабочим до вашей проверки входа.
|
||||
if [[ -n $ssh_port ]]; then
|
||||
say "перевожу SSH на порт $ssh_port: новый добавляю, прежний пока остаётся рабочим"
|
||||
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
|
||||
cat <<EOF
|
||||
|
||||
${Y}Дальше обязательно (иначе можно потерять доступ к серверу):${N}
|
||||
1) откройте ВТОРОЕ окно и проверьте вход: ssh -p $ssh_port root@<IP сервера>
|
||||
2) вход работает — закройте прежние порты: kit local ssh --close-old $ssh_port
|
||||
EOF
|
||||
fi
|
||||
|
||||
# пароль root: в флагах его нет намеренно — значение в командной строке видно в списке процессов
|
||||
if interactive; then
|
||||
if ask_yes "Сменить пароль root прямо сейчас?" n; then
|
||||
kit local passwd || warn "не получилось — повторите: kit local passwd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ проверка
|
||||
@@ -405,9 +477,43 @@ fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI KIT $KIT_VERSION, канал: наш Gitea).
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea).
|
||||
|
||||
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||
EOF
|
||||
|
||||
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
||||
|
||||
if [[ $show_sub == yes ]]; then
|
||||
say "панель 3X-UI: адрес, логин и пароль"
|
||||
if [[ -s /root/3x-ui.txt ]]; then
|
||||
printf ' файл: /root/3x-ui.txt (виден только root). Сохраните данные себе:\n'
|
||||
sed 's/^/ /' /root/3x-ui.txt
|
||||
else
|
||||
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ подписка, QR-код и доступы
|
||||
|
||||
# Показываем в самом конце, после всех проверок: это то, что человек уносит с собой.
|
||||
# Печатает авторская команда (подписка + QR-код через qrencode + ссылки по протоколам),
|
||||
# поэтому вывод не расходится с тем, что показывает `kit` потом.
|
||||
if [[ $show_sub == yes ]]; then
|
||||
sub_user=admin
|
||||
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
|
||||
[[ ${INSTALLER_ARGS[_i]} == --user ]] && sub_user=${INSTALLER_ARGS[_i + 1]:-admin}
|
||||
done
|
||||
say "подписка и доступы — это то, что стоит сохранить"
|
||||
printf ' подписка — одна ссылка на все устройства; QR-код ниже наводите камерой в приложении\n'
|
||||
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
||||
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
||||
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
||||
if ! kit user link "$sub_user" --all; then
|
||||
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
||||
fi
|
||||
else
|
||||
say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all"
|
||||
fi
|
||||
|
||||
+139
-37
@@ -30,7 +30,7 @@ wait_apt_idle() {
|
||||
XUI_VERSION="v3.9.0"
|
||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.15"
|
||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||
@@ -50,7 +50,8 @@ RESULT=/root/3x-ui.txt
|
||||
XUI_ENV=/etc/x-ui/install-result.env
|
||||
# Сайты для маскировки REALITY: нужны TLS 1.3 и HTTP/2. Берём первый доступный.
|
||||
# Apple, iCloud, Microsoft и домены .ru сам Xray не советует – их тут нет.
|
||||
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)
|
||||
# Наш шов: добавлены известные сайты (Microsoft, AMD, Intel, NVIDIA) — их можно выбрать вручную.
|
||||
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com www.microsoft.com www.amd.com www.intel.com www.nvidia.com)
|
||||
|
||||
ALL_PROTOS=(reality reality2 hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
|
||||
# Обычный WireGuard легко распознаётся сетевым оборудованием и работает нестабильно
|
||||
@@ -163,26 +164,32 @@ same_net() { # имя
|
||||
# Имена из сертификатов соседних адресов /24: заходим на 443 без имени и читаем subjectAltName.
|
||||
nearby_scan() { # основа "a.b.c"
|
||||
local me; me=$(host_ip)
|
||||
seq 1 254 | xargs -P 8 -I{} bash -c '
|
||||
# Наш шов: 32 потока вместо 8 и таймаут 3 с — скан подсети занимает секунды, а не минуту.
|
||||
seq 1 254 | xargs -P 32 -I{} bash -c '
|
||||
ip=$1; [ "$ip" = "$2" ] && exit 0
|
||||
echo | timeout 4 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
|
||||
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -40 || true
|
||||
echo | timeout 3 openssl s_client -connect "$ip:443" -tls1_3 -alpn h2 2>/dev/null | openssl x509 -noout -ext subjectAltName 2>/dev/null \
|
||||
| tr "," "\n" | sed -n "s/^ *DNS://p"' _ "$1.{}" "$me" 2>/dev/null | sort -u | grep -E "$SNI_RE" | head -60 || true
|
||||
}
|
||||
|
||||
# Подходящие сайты: сначала в /24 сервера, если пусто – во втором /24 того же /23. До 8 имён.
|
||||
nearby_sites() {
|
||||
local me a b c base n names found=0
|
||||
local me a b c base n names out
|
||||
set +e; trap - ERR # выполняется в подпроцессе: сбой одной проверки не должен обрывать поиск
|
||||
me=$(host_ip)
|
||||
[[ $me =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 0
|
||||
IFS=. read -r a b c _ <<<"$me"
|
||||
# Наш шов: имена проверяются параллельно (8 сразу) — раньше по очереди, и это была основная
|
||||
# причина «долго сканирует». Функции передаём в подпроцессы через export -f.
|
||||
export -f same_net sni_alive sni_trusted sni_quality sni_ok host_ip
|
||||
export HOST
|
||||
for base in "$a.$b.$c" "$a.$b.$((c ^ 1))"; do
|
||||
names=$(nearby_scan "$base")
|
||||
for n in $names; do
|
||||
[[ $n == "$me" || $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
|
||||
if same_net "$n" && sni_alive "$n" && sni_trusted "$n" && sni_quality "$n"; then echo "$n"; found=$((found + 1)); ((found >= 8)) && return 0; fi
|
||||
done
|
||||
((found > 0)) && return 0
|
||||
names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)
|
||||
[[ -n $names ]] || continue
|
||||
out=$(while IFS= read -r n; do
|
||||
[[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue
|
||||
printf '%s\n' "$n"
|
||||
done <<<"$names" | xargs -P 8 -I{} bash -c 'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\n" "$1"' _ {} 2>/dev/null | head -8)
|
||||
[[ -n $out ]] && { printf '%s\n' "$out"; return 0; }
|
||||
done
|
||||
return 0
|
||||
}
|
||||
@@ -372,15 +379,15 @@ kit_banner() {
|
||||
echo
|
||||
printf '%s' "$G"
|
||||
cat <<'ART'
|
||||
_ _ _ _ _ _ _
|
||||
(_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __
|
||||
| | __/ __| '_ \ / _ \| __| |/ / | | | '_ \| '__| |/ __| |/ /
|
||||
| | |_\__ \ | | | (_) | |_| <| |_| | |_) | | | | (__| <
|
||||
|_|\__|___/_| |_|\___/ \__|_|\_\\__,_|_.__/|_| |_|\___|_|\_\
|
||||
_ _ _ ___ _____
|
||||
| | | | __ _ ___| | _____ _ __ / _ \___ |
|
||||
| |_| |/ _` |/ __| |/ / _ \ '__| | | | | / /
|
||||
| _ | (_| | (__| < __/ | | |_| |/ /
|
||||
|_| |_|\__,_|\___|_|\_\___|_| \___//_/
|
||||
ART
|
||||
printf '%s' "$N"
|
||||
echo
|
||||
echo "${B}3X-UI KIT $KIT_VERSION${N} · it's not Kubrick. it's just a VPN."
|
||||
echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"
|
||||
echo "${D}на основе панели 3X-UI (MHSanaei/3x-ui) и ядра Xray · github.com/itsnotkubrick/3X-UI_KIT${N}"
|
||||
}
|
||||
|
||||
@@ -537,6 +544,67 @@ main() {
|
||||
later "Адрес $HOST не найден на сетевых интерфейсах сервера (бывает за NAT). Если ссылки не работают – запустите с --host и верным IP."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Наш шов: выбор сайта маскировки по результатам скана подсети.
|
||||
# Печатает выбранное имя в stdout (пусто — оставляем автоподбор автора).
|
||||
# role — подпись для человека: REALITY или XHTTP/MTProto.
|
||||
kl_sni_menu() {
|
||||
local role=$1; shift
|
||||
local -a found=("$@")
|
||||
local -a known=("${SNI_CANDIDATES[@]}")
|
||||
local src=${KL_ASK_SRC:-/dev/tty} ans i k n auto_idx known_idx own_idx try
|
||||
n=${#found[@]}
|
||||
printf '\n%s%s%s\n' "$B" "$role" "$N" >&2
|
||||
if ((n)); then
|
||||
printf 'Найдено в подсети:\n\n' >&2
|
||||
for i in "${!found[@]}"; do printf ' %d - %s\n' "$((i + 1))" "${found[i]}" >&2; done
|
||||
auto_idx=$((n + 1)); known_idx=$((n + 2)); own_idx=$((n + 3))
|
||||
printf ' %d - автовыбор из списка\n' "$auto_idx" >&2
|
||||
printf ' %d - выбрать общедоступный (например %s, %s)\n' "$known_idx" "${known[0]}" "${known[1]}" >&2
|
||||
printf ' %d - ввести свой домен\n\n' "$own_idx" >&2
|
||||
else
|
||||
auto_idx=0; known_idx=1; own_idx=2
|
||||
printf 'В подсети доменов не найдено, выберите:\n\n' >&2
|
||||
printf ' 1 - общедоступный (например %s, %s)\n' "${known[0]}" "${known[1]}" >&2
|
||||
printf ' 2 - ввести свой домен\n\n' >&2
|
||||
fi
|
||||
printf 'Ваш выбор (Enter — автовыбор): ' >&2
|
||||
for try in 1 2 3; do
|
||||
ans=""
|
||||
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||
ans=${ans//[[:space:]]/}
|
||||
[[ -z $ans ]] && return 0
|
||||
[[ $ans =~ ^[0-9]+$ ]] || { printf ' введите номер из списка\n' >&2; continue; }
|
||||
i=$((10#$ans))
|
||||
if ((n)) && ((i >= 1 && i <= n)); then printf '%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 '%s' "${known[10#$ans - 1]}"; return 0
|
||||
fi
|
||||
printf ' ничего не выбрано — оставляю автовыбор\n' >&2
|
||||
return 0
|
||||
fi
|
||||
if ((i == own_idx)); then
|
||||
printf ' адрес сайта (например vpn.example.com): ' >&2
|
||||
ans=""
|
||||
read -r -t 300 ans <"$src" 2>/dev/null || true
|
||||
ans=${ans//[[:space:]]/}
|
||||
[[ $ans =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] && { printf '%s' "$ans"; return 0; }
|
||||
printf ' не похоже на адрес сайта — оставляю автовыбор\n' >&2
|
||||
return 0
|
||||
fi
|
||||
printf ' такого номера нет\n' >&2
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Маскировка: из флагов, по вопросу пользователю или автоматически (сосед по подсети).
|
||||
if [[ -n $DOMAIN ]]; then
|
||||
[[ $PANEL_SSL == ip ]] || die "Свой домен работает с сертификатом панели Let's Encrypt на IP: освободите порт 80 и не указывайте --cert, --key и --panel-ssl none (сертификат для домена установщик получит сам)."
|
||||
@@ -548,17 +616,44 @@ main() {
|
||||
SNI=$DOMAIN
|
||||
elif [[ -z $SNI ]]; then
|
||||
echo
|
||||
echo "Подбираю сайт для маскировки среди соседей по подсети… (около 250 коротких обращений к порту 443 соседних адресов, около минуты; не нужно – запустите с --sni или --domain)"
|
||||
echo "Сканирую подсеть: около 250 коротких обращений к порту 443 соседних адресов (около минуты)."
|
||||
echo "По результатам предложу сайты для маскировки. Не нужно – запустите с --sni или --domain."
|
||||
local -a nb=()
|
||||
mapfile -t nb < <(nearby_sites | shuf)
|
||||
NB_ALL=("${nb[@]}")
|
||||
# Наш шов: показываем найденное и даём выбрать — отдельно для REALITY и для XHTTP/MTProto.
|
||||
if [[ ${KL_PICK_SNI:-} == 1 && $yes == no ]] && { : </dev/tty; } 2>/dev/null; then
|
||||
local pick pick2
|
||||
pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})
|
||||
if ((${#nb[@]})); then
|
||||
SNI=${nb[0]}; SNI2=${nb[1]:-}; SNI3=${nb[2]:-}
|
||||
[[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}
|
||||
elif [[ -n $pick ]]; then
|
||||
SNI=$pick
|
||||
fi
|
||||
if [[ -n $SNI ]]; then
|
||||
if ! sni_ok "$SNI"; then
|
||||
warn "$SNI не отвечает по TLS 1.3 + HTTP/2 — беру найденное в подсети"
|
||||
SNI="${nb[0]:-$SNI}"
|
||||
fi
|
||||
ok "маскировка REALITY: ${B}$SNI${N}"
|
||||
fi
|
||||
if [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
||||
pick2=$(kl_sni_menu "XHTTP и MTProto (различаются по имени сайта) — какой сайт взять" ${nb[@]+"${nb[@]}"})
|
||||
[[ -n $pick2 ]] && SNI2=$pick2
|
||||
[[ -n $SNI2 ]] && ok "маскировка XHTTP/MTProto: ${B}$SNI2${N}"
|
||||
fi
|
||||
fi
|
||||
if ((${#nb[@]})); then ok "подходящих соседей найдено: ${#nb[@]}"
|
||||
else warn "в подсети подходящих сайтов не нашлось (адреса проверены, откликов нет)"; fi
|
||||
if [[ -z $SNI ]] && ((${#nb[@]})); then
|
||||
SNI=${nb[0]}; SNI2=${SNI2:-${nb[1]:-}}; SNI3=${nb[2]:-}
|
||||
ok "найден: ${B}$SNI${N} · та же подсеть · TLS 1.3, h2, сертификат верный"
|
||||
else
|
||||
warn "В подсети подходящего сайта не нашёл."
|
||||
echo " Лучший выход – свой домен: запустите снова с --domain vpn.example.com"
|
||||
if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
||||
if [[ -n ${KL_PICK_SNI:-} && ${KL_PICK_SNI} == 1 && -z $SNI ]]; then
|
||||
: # сайт уже выбран в нашем меню — вопрос автора не нужен
|
||||
elif [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
||||
ask_tty " Продолжить с запасным сайтом из списка? [y/N] " || true
|
||||
[[ $REPLY =~ ^[yYдД]$ ]] || die "Остановился по вашей просьбе. Ставить можно снова в любой момент."
|
||||
else
|
||||
@@ -578,7 +673,10 @@ main() {
|
||||
LINK_HOST=${DOMAIN:-$HOST}
|
||||
[[ $PANEL_ON == ip || -n $DOMAIN ]] || { PANEL_ON=ip; later "--panel-on domain нужен со своим доменом: панель и подписка открываются по IP."; }
|
||||
# Режим «всё на 443»: XHTTP и MTProto различаются по имени сайта, берём и им соседей по подсети.
|
||||
if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]]; then
|
||||
# Наш шов: если XHTTP и MTProto не выбраны, имена для них сейчас не нужны — не сканируем соседей
|
||||
# по подсети впустую (около 250 обращений к чужим 443). Имена SNI2/SNI3 возьмутся из общего списка ниже.
|
||||
if [[ $TRUSTED == yes && $multi == no && ( -z $SNI2 || -z $SNI3 ) ]] \
|
||||
&& [[ " ${PROTOS[*]} " == *" xhttp "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
||||
local -a nb2=()
|
||||
echo
|
||||
if ((${#NB_ALL[@]})); then
|
||||
@@ -754,14 +852,18 @@ main() {
|
||||
links=$(sub_links "$SUBID")
|
||||
# У установок до kit 1.1 AmneziaWG и MTProto лежат в подписках «-awg» и «-tg».
|
||||
local extra
|
||||
# Наш шов: подписки «-awg»/«-tg» есть только у установок с AmneziaWG и MTProto — не тянем их,
|
||||
# если эти протоколы не выбраны (никаких лишних обращений к панели).
|
||||
if [[ " ${PROTOS[*]} " == *" awg "* || " ${PROTOS[*]} " == *" awg3 "* || " ${PROTOS[*]} " == *" mtproto "* ]]; then
|
||||
extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)
|
||||
fi
|
||||
[[ -n $extra ]] && links+=$'\n'"$extra"
|
||||
AWG_LINKS=$(grep '^vpn://' <<<"$links" || true)
|
||||
# MTProto слушает localhost, а клиенты приходят через nginx на 443.
|
||||
[[ $SINGLE == yes ]] && links=$(sed "s/^\(tg:\/\/proxy?\)\(.*\)port=${INNER[mtproto]}/\1\2port=443/" <<<"$links")
|
||||
umask 077
|
||||
{
|
||||
echo "3X-UI KIT (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"
|
||||
echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"
|
||||
echo
|
||||
echo "Панель: $panel_url"
|
||||
echo "Логин: $XUI_USERNAME"
|
||||
@@ -1480,7 +1582,7 @@ setup_nginx() {
|
||||
local panel_path=/${XUI_WEB_BASE_PATH#/}
|
||||
panel_path=${panel_path%/}/
|
||||
{
|
||||
echo "# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске."
|
||||
echo "# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске."
|
||||
echo "stream {"
|
||||
echo " map \$ssl_preread_server_name \$kit_upstream {"
|
||||
[[ -n $reality_sni ]] && echo " $reality_sni 127.0.0.1:${INNER[reality]};"
|
||||
@@ -1500,7 +1602,7 @@ setup_nginx() {
|
||||
echo "}"
|
||||
} >/etc/nginx/kit-stream.conf
|
||||
cat >/etc/nginx/conf.d/kit.conf <<NGX
|
||||
# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске.
|
||||
# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске.
|
||||
server {
|
||||
listen 127.0.0.1:${INNER[web]} ssl http2 proxy_protocol;
|
||||
server_name _;
|
||||
@@ -1588,14 +1690,14 @@ setup_subscription() {
|
||||
# Без subURI панель показывает ссылку на внутренний порт 2097, до которого снаружи не достучаться.
|
||||
local uri="https://$HOST:$SUB_PORT$SUB_PATH"
|
||||
[[ $SINGLE == yes ]] && uri="https://$HOST$SUB_PATH"
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI KIT" --arg uri "$uri" '
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI_autoVPS" --arg uri "$uri" '
|
||||
.subEnable = true | .subPath = $path | .subTitle = $title | .subListen = "127.0.0.1" | .subPort = $ip
|
||||
| .subURI = $uri
|
||||
| .subCertFile = "" | .subKeyFile = ""
|
||||
| .subClashEnable = true | .subClashAutoDetect = true | .subJsonEnable = true | .subJsonAutoDetect = true' <<<"$all")
|
||||
else
|
||||
SUB_PORT=$(jq -r '.subPort // 2096' <<<"$all")
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI KIT" '
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI_autoVPS" '
|
||||
.subEnable = true | .subPath = $path | .subTitle = $title
|
||||
| .subClashEnable = true | .subClashAutoDetect = true | .subJsonEnable = true | .subJsonAutoDetect = true' <<<"$all")
|
||||
fi
|
||||
@@ -1616,7 +1718,7 @@ setup_subscription() {
|
||||
kit_sub_unit() { # путь-к-сертификату путь-к-ключу (пусто – за nginx)
|
||||
cat <<UNIT
|
||||
[Unit]
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)
|
||||
After=network-online.target x-ui.service
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -1718,11 +1820,11 @@ safe_env() { # файл имя...
|
||||
local f=$1 n re; shift
|
||||
[[ -r $f ]] || die "В копии нет файла ${f##*/}."
|
||||
[[ $(LC_ALL=C tr -d '\n -~' <"$f" | wc -c) -eq 0 ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
LC_ALL=C grep -aqvE "^([A-Z][A-Z0-9_]*=([A-Za-z0-9._:/@%+,=_-]*|''))?$" "$f" \
|
||||
&& die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
&& die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
while IFS= read -r n; do
|
||||
[[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."
|
||||
[[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."
|
||||
case $n in
|
||||
BACKUP_KIT_VERSION) re='^[0-9]{1,3}(\.[0-9]{1,3}){1,3}$' ;;
|
||||
BACKUP_HOST | HOST) re='^[A-Za-z0-9.:-]{1,253}$' ;;
|
||||
@@ -1740,10 +1842,10 @@ safe_env() { # файл имя...
|
||||
SUB_PATH) re='^/[A-Za-z0-9_-]{1,64}/$' ;;
|
||||
SINGLE) re='^(yes|no)?$' ;;
|
||||
MTPROTO_INNER) re='^[0-9]{0,5}$' ;;
|
||||
*) die "Это не резервная копия 3X-UI KIT или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;
|
||||
*) die "Это не резервная копия 3X-UI_autoVPS или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;
|
||||
esac
|
||||
[[ $(env_get "$f" "$n") =~ $re ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (значение $n в ${f##*/}). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (значение $n в ${f##*/}). Ничего не менял."
|
||||
done < <(LC_ALL=C grep -aoE '^[A-Z][A-Z0-9_]*' "$f")
|
||||
return 0
|
||||
}
|
||||
@@ -1874,7 +1976,7 @@ restore_main() { # файл
|
||||
tmp=$(mktemp -d)
|
||||
# shellcheck disable=SC2064 # путь подставляем сразу: при выходе локальной переменной уже нет
|
||||
trap "rm -rf -- '$tmp'" EXIT
|
||||
python3 - "$file" "$tmp" <<'PY' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."
|
||||
python3 - "$file" "$tmp" <<'PY' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена. Ничего не менял."
|
||||
import os, sys, tarfile
|
||||
allowed = ("etc/x-ui/x-ui.db", "etc/x-ui/install-result.env", "etc/kit/kit.env", "etc/kit-sub/config.json", "etc/kit-sub/rules.yaml",
|
||||
"etc/nginx/kit-stream.conf", "etc/nginx/conf.d/kit.conf", "var/www/kit", "root/cert/self",
|
||||
@@ -1935,7 +2037,7 @@ PY
|
||||
safe_env "$tmp/kit-backup.env" BACKUP_KIT_VERSION BACKUP_HOST BACKUP_SSL BACKUP_DATE
|
||||
safe_env "$tmp/etc/x-ui/install-result.env" XUI_USERNAME XUI_PASSWORD XUI_PANEL_PORT XUI_WEB_BASE_PATH XUI_ACCESS_URL XUI_API_TOKEN XUI_DB_TYPE
|
||||
[[ $(env_get "$tmp/etc/x-ui/install-result.env" XUI_PANEL_PORT) =~ ^[0-9]{1,5}$ ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (порт панели). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (порт панели). Ничего не менял."
|
||||
if [[ -f $tmp/etc/kit/kit.env ]]; then
|
||||
safe_env "$tmp/etc/kit/kit.env" HOST LINK_HOST PANEL_ON SUB_BASE SUB_PATH SUB_INTERNAL SINGLE MTPROTO_INNER
|
||||
fi
|
||||
@@ -1947,10 +2049,10 @@ PY
|
||||
BACKUP_SSL=$(env_get "$tmp/kit-backup.env" BACKUP_SSL)
|
||||
BACKUP_DATE=$(env_get "$tmp/kit-backup.env" BACKUP_DATE)
|
||||
BACKUP_KIT_VERSION=$(env_get "$tmp/kit-backup.env" BACKUP_KIT_VERSION)
|
||||
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."
|
||||
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (адрес сервера). Ничего не менял."
|
||||
[[ $BACKUP_SSL =~ ^(ip|custom|none)$ ]] || die "В копии нет данных о сертификате."
|
||||
[[ $BACKUP_SSL != custom || -f $tmp/root/cert/custom/fullchain.pem ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."
|
||||
PANEL_SSL=$BACKUP_SSL
|
||||
# Домен переезжает вместе с сервером (поменяйте A-запись), IP – нет.
|
||||
if [[ $BACKUP_HOST =~ ^[0-9.]+$ ]]; then
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
# Hysteria2 – https://github.com/itsnotkubrick/3X-UI_KIT
|
||||
#
|
||||
# Отдельный установщик Hysteria2 больше не поддерживается: Hysteria2 входит в основную
|
||||
# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`
|
||||
# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`
|
||||
# получила из подписанного релиза понятное сообщение. Ничего не ставит, не удаляет и не
|
||||
# останавливает: сервер Hysteria2, установленный раньше, продолжает работать как был.
|
||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||
# останавливается и не заменяет команду hy2.
|
||||
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.15"
|
||||
|
||||
cat >&2 <<'MSG'
|
||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||
Сервер Hysteria2 продолжает работать как раньше, но не обновляется этим скриптом.
|
||||
Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).
|
||||
Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).
|
||||
Если нужен только Hysteria2 без панели – официальный установщик:
|
||||
bash <(curl -fsSL https://get.hy2.sh/)
|
||||
MSG
|
||||
|
||||
+290
-5
@@ -64,7 +64,7 @@ kl_need_root() { [[ ${EUID:-0} -eq 0 ]] || kl_die "нужен root"; }
|
||||
|
||||
kit_local_help() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT — дополнения форка (hacker07):
|
||||
3X-UI_autoVPS — дополнения форка (hacker07):
|
||||
|
||||
Состояние
|
||||
kit local эта справка
|
||||
@@ -73,6 +73,7 @@ kit_local_help() {
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening сводка по защите сервера и что ещё можно включить
|
||||
kit local uninstall снести всё, что поставила установка (SSH не трогает)
|
||||
|
||||
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
|
||||
kit local site поставить случайный сайт из нашего набора (10 штук)
|
||||
@@ -233,7 +234,7 @@ EOF
|
||||
printf ' (dry-run) записал бы в %s: [Socket] + ListenStream=%s (старый порт остаётся)\n' "$KL_SOCKET_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 "$(dirname "$KL_SOCKET_DROPIN")"
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\n[Socket]\nListenStream=%s\n' "$(date +%F)" "$new" >"$KL_SOCKET_DROPIN"
|
||||
printf '# 3X-UI_autoVPS (форк): дополнительный порт SSH, добавлен %s\n[Socket]\nListenStream=%s\n' "$(date +%F)" "$new" >"$KL_SOCKET_DROPIN"
|
||||
kl_ok "добавлен $KL_SOCKET_DROPIN"
|
||||
fi
|
||||
kl_run systemctl daemon-reload
|
||||
@@ -244,7 +245,7 @@ EOF
|
||||
printf ' (dry-run) записал бы в %s: Port %s (старый порт остаётся)\n' "$KL_SSHD_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 /etc/ssh/sshd_config.d
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\nPort %s\n' "$(date +%F)" "$new" >"$KL_SSHD_DROPIN"
|
||||
printf '# 3X-UI_autoVPS (форк): дополнительный порт SSH, добавлен %s\nPort %s\n' "$(date +%F)" "$new" >"$KL_SSHD_DROPIN"
|
||||
kl_ok "добавлен $KL_SSHD_DROPIN"
|
||||
fi
|
||||
kl_run sshd -t || { kl_warn "sshd -t не принял конфиг — убираю наш файл, ничего не перезапускаю"; kl_dry || rm -f "$KL_SSHD_DROPIN"; return 1; }
|
||||
@@ -414,7 +415,7 @@ APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
EOF
|
||||
cat >/etc/apt/apt.conf.d/52-kit-local-upgrades <<'EOF'
|
||||
// 3X-UI KIT (форк): только обновления безопасности, никакой самовольной перезагрузки.
|
||||
// 3X-UI_autoVPS (форк): только обновления безопасности, никакой самовольной перезагрузки.
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
EOF
|
||||
@@ -516,6 +517,12 @@ footer{border-top:1px solid var(--bd);padding:26px 0;color:var(--mu);font-size:1
|
||||
table{border-collapse:collapse;width:100%}
|
||||
th,td{border-bottom:1px solid var(--bd);text-align:left;padding:10px 8px;font-size:15px}
|
||||
ul{padding-left:20px}
|
||||
.modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(11,13,17,.55);padding:20px;z-index:50}
|
||||
.modal.on{display:flex}
|
||||
.modal .box{background:#fff;border-radius:10px;max-width:430px;padding:26px 26px 20px;box-shadow:0 18px 50px rgba(0,0,0,.28)}
|
||||
.modal h4{margin:0 0 8px;font-size:19px}
|
||||
.modal p{margin:0 0 18px;color:var(--mu)}
|
||||
.modal button{background:var(--ac);color:#fff;border:0;border-radius:6px;padding:10px 18px;font:inherit;font-weight:600;cursor:pointer}
|
||||
@media(max-width:600px){.hero{padding:44px 0 28px}.hero h1{font-size:29px}}'
|
||||
|
||||
kl_site_open() { # заголовок описание бренд «навигация как HTML»
|
||||
@@ -538,6 +545,29 @@ HTML
|
||||
kl_site_close() { # бренд
|
||||
cat <<HTML
|
||||
<footer><div class="wrap"><p>© $(date +%Y) $1 · <a href="#">Imprint</a> · <a href="#">Privacy</a> · <a href="#">Status</a></p></div></footer>
|
||||
|
||||
<!-- Ссылки и кнопки живые: открывают окно «раздел в разработке». Никаких внешних запросов. -->
|
||||
<div class="modal" id="dev" role="dialog" aria-modal="true" aria-labelledby="devtitle">
|
||||
<div class="box">
|
||||
<h4 id="devtitle">This section is under construction</h4>
|
||||
<p>We are still building this part of the site. We are sorry for the inconvenience — it will be
|
||||
available shortly.</p>
|
||||
<button type="button" id="devok">Got it</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var m = document.getElementById('dev');
|
||||
if (!m) return;
|
||||
function show(e) { if (e) e.preventDefault(); m.classList.add('on'); document.body.style.overflow = 'hidden'; }
|
||||
function hide() { m.classList.remove('on'); document.body.style.overflow = ''; }
|
||||
var links = document.querySelectorAll('a[href^="#"], a.btn, nav a, footer a');
|
||||
for (var i = 0; i < links.length; i++) { links[i].addEventListener('click', show); }
|
||||
m.addEventListener('click', function (e) { if (e.target === m) hide(); });
|
||||
document.getElementById('devok').addEventListener('click', hide);
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') hide(); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
@@ -844,7 +874,9 @@ kit_local_site() {
|
||||
# Порт 443 отдаёт файл через nginx (перезагрузка не нужна). UDP-порт Hysteria2 держит копию
|
||||
# страницы внутри подключения — её надо обновить, иначе ответы разойдутся.
|
||||
if kl_have kit; then
|
||||
if kit net masq on >/dev/null 2>&1; then
|
||||
if [[ -f /etc/kit/kl-protos ]] && ! grep -qE '(^|[ ,])hy2([ ,]|$)' /etc/kit/kl-protos; then
|
||||
kl_ok "Hysteria2 в этой установке не ставился — синхронизировать нечего"
|
||||
elif kit net masq on >/dev/null 2>&1; then
|
||||
kl_ok "Hysteria2 отвечает той же страницей"
|
||||
else
|
||||
kl_warn "Hysteria2 не синхронизирован (может, его нет в этой установке) — проверьте: kit net masq on"
|
||||
@@ -865,6 +897,7 @@ kit_local_cmd() {
|
||||
passwd | pass) shift; kit_local_passwd "$@" ;;
|
||||
upgrades | auto) shift; kit_local_upgrades "$@" ;;
|
||||
hardening | hard) shift; kit_local_hardening "$@" ;;
|
||||
uninstall | remove) shift; kit_local_uninstall "$@" ;;
|
||||
site | stub | stub-site) shift; kit_local_site "$@" ;;
|
||||
*)
|
||||
printf 'Неизвестная команда форка: %s\n' "$1" >&2
|
||||
@@ -876,6 +909,253 @@ kit_local_cmd() {
|
||||
|
||||
# Точка входа: вызывается из kit.sh с аргументами командной строки.
|
||||
# Если команда не наша — возвращаем 0, и kit работает ровно как раньше.
|
||||
# ---------------------------------------------------------------- снос установки целиком
|
||||
# Убираем всё, что появляется после установки: панель 3X-UI и службу x-ui, ядро Xray, службы и файлы
|
||||
# kit (kit-sub, автообновление, cron), конфиги nginx и сайт-заглушку, сертификаты, правила ufw для 80/443,
|
||||
# наши правки системы (правило ICMP, обновления безопасности), наши бэкапы и файл с данными панели.
|
||||
#
|
||||
# ЧЕГО НЕ ТРОГАЕМ: SSH — ни порт, ни конфиги, ни правила ufw для него, ни наш ssh-дроп-ин. Иначе легко
|
||||
# остаться без доступа к серверу. Если SSH-порт когда-то менялся, верните его сами: kit local ssh 22
|
||||
# (и закрыть лишние: kit local ssh --close-old 22).
|
||||
#
|
||||
# kit local uninstall спросит подтверждение словом «удалить»
|
||||
# kit local uninstall --dry-run только покажет, что будет удалено
|
||||
kit_local_uninstall() {
|
||||
kl_args "$@"
|
||||
local arg=${KL_REST[0]:-} ans found
|
||||
local reset_ssh=no i=0
|
||||
case $arg in
|
||||
"") ;;
|
||||
--dry-run) KL_DRY=yes ;;
|
||||
--yes) KIT_LOCAL_UNINSTALL_CONFIRMED=yes ;;
|
||||
--reset-ssh) reset_ssh=yes ;;
|
||||
*) kl_die "kit local uninstall [--dry-run] [--yes] [--reset-ssh]"; return 1 ;;
|
||||
esac
|
||||
|
||||
# Службы: и явный список, и всё, что найдётся по нашим шаблонам (чтобы новые юниты kit не оставались).
|
||||
# kit запускает наши команды с `set -e`: при уборке это недопустимо — любая «не найденная» служба
|
||||
# или пустой grep обрывали бы снос на середине (так и было: файлы оставались). Убираем errexit.
|
||||
set +e
|
||||
|
||||
local -a svc=(x-ui.service kit-sub.service kit-update.timer kit-update.service kit-limit.timer kit-limit.service)
|
||||
local u
|
||||
for u in $(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' \
|
||||
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
|
||||
[[ " ${svc[*]} " == *" $u "* ]] || svc+=("$u")
|
||||
done
|
||||
# Список собран по коду установщика (scripts/3x-ui.sh) и нашего слоя: убираем всё, что они создают.
|
||||
local -a paths=(
|
||||
/usr/local/x-ui /etc/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /etc/systemd/system/x-ui.service
|
||||
/usr/local/bin/kit /usr/local/lib/kit /usr/local/lib/kit-sub /etc/kit /etc/kit-sub
|
||||
/etc/systemd/system/kit-sub.service /etc/systemd/system/kit-update.service
|
||||
/etc/systemd/system/kit-update.timer /etc/systemd/system/kit-limit.service
|
||||
/etc/systemd/system/kit-limit.timer
|
||||
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert
|
||||
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf /var/www/kit
|
||||
/root/3x-ui.txt /root/cert /root/.acme.sh /etc/letsencrypt /var/log/kit-install-check.log
|
||||
/etc/sysctl.d/99-kit-local-icmp.conf /etc/apt/apt.conf.d/52-kit-local-upgrades
|
||||
/etc/apt/apt.conf.d/20auto-upgrades
|
||||
)
|
||||
local -a masks=(/root/kit-local-backup-* /root/kit-backup-* /root/x-ui-before-sni-* /root/x-ui-before-update-*)
|
||||
local -a extra=()
|
||||
local m
|
||||
for m in "${masks[@]}"; do for f in $m; do [[ -e $f ]] && extra+=("$f"); done; done
|
||||
|
||||
kl_step "Будет удалено"
|
||||
printf ' службы: %s\n' "${svc[*]}"
|
||||
printf ' файлы и каталоги: %s\n' "${paths[*]}"
|
||||
[[ ${#extra[@]} -eq 0 ]] || printf ' копии и журналы: %s\n' "${extra[*]}"
|
||||
printf ' пользователь kit-sub (если создавался)\n'
|
||||
printf ' правила ufw для 80/tcp, 443/tcp и 443/udp; ответ на ping возвращается\n'
|
||||
kl_ok "SSH не трогаем: ни порт, ни конфиги, ни правила — иначе можно потерять доступ"
|
||||
if [[ $reset_ssh == yes ]]; then
|
||||
kl_warn "--reset-ssh: снесу и наш ssh-дроп-ин (порт вернётся к системному). Убедитесь, что вход по нему работает!"
|
||||
fi
|
||||
|
||||
if kl_dry; then
|
||||
printf '\n (dry-run) ничего не делаю. Запустить по-настоящему: kit local uninstall\n'
|
||||
return 0
|
||||
fi
|
||||
kl_need_root || return 1
|
||||
|
||||
if [[ -z ${KIT_LOCAL_UNINSTALL_CONFIRMED:-} ]]; then
|
||||
printf '\n%sЧтобы удалить всё перечисленное, напишите слово «удалить»: %s' "${B:-}" "${N:-}"
|
||||
read -r ans </dev/tty 2>/dev/null || ans=""
|
||||
[[ ${ans,,} == "удалить" ]] || { kl_warn "не подтверждено — ничего не удаляю"; return 1; }
|
||||
fi
|
||||
|
||||
kl_step "Останавливаю и отключаю службы"
|
||||
local s
|
||||
for s in "${svc[@]}"; do
|
||||
if systemctl list-unit-files 2>/dev/null | grep -q "^$s"; then
|
||||
systemctl disable --now "$s" >/dev/null 2>&1 && kl_ok "остановлено и отключено: $s" || kl_warn "не смог остановить: $s"
|
||||
fi
|
||||
done
|
||||
if kl_have nginx; then
|
||||
local others
|
||||
others=$( { find /etc/nginx/conf.d /etc/nginx/sites-enabled /etc/nginx/streams-enabled -mindepth 1 2>/dev/null || true; } \
|
||||
| { grep -vE 'kit\.conf$|(^|/)default$' || true; } | wc -l)
|
||||
if [[ $others -eq 0 ]]; then
|
||||
systemctl disable --now nginx >/dev/null 2>&1 && kl_ok "nginx остановлен (чужих сайтов в нём не было)"
|
||||
KL_NGINX_PURGE=yes
|
||||
else
|
||||
kl_ok "nginx оставляю: в нём есть другие сайты — уберу только наши строки и файлы"
|
||||
KL_NGINX_PURGE=no
|
||||
fi
|
||||
fi
|
||||
|
||||
kl_step "Убираю файлы"
|
||||
local f
|
||||
for f in "${paths[@]}"; do
|
||||
[[ -e $f ]] || continue
|
||||
rm -rf "$f" && kl_ok "удалено: $f" || kl_warn "не удалось удалить: $f"
|
||||
done
|
||||
for f in ${extra[@]+"${extra[@]}"}; do rm -rf "$f" && kl_ok "удалено: $f"; done
|
||||
|
||||
# Наши строки в чужом nginx: include stream-конфига и симлинки, если были.
|
||||
if [[ -f /etc/nginx/nginx.conf ]] && grep -q 'kit-stream.conf' /etc/nginx/nginx.conf; then
|
||||
kl_backup /etc/nginx/nginx.conf
|
||||
sed -i '/kit-stream\.conf/d' /etc/nginx/nginx.conf && kl_ok "из nginx.conf убрана наша строка включения"
|
||||
fi
|
||||
local lnk
|
||||
for lnk in /etc/nginx/sites-enabled/*kit* /etc/nginx/streams-enabled/*kit*; do
|
||||
[[ -e $lnk || -L $lnk ]] && rm -f "$lnk" && kl_ok "удалена ссылка: $lnk"
|
||||
done
|
||||
if [[ ${KL_NGINX_PURGE:-no} == yes ]]; then
|
||||
apt-get remove -y --purge nginx nginx-common >/dev/null 2>&1 && kl_ok "пакет nginx удалён"
|
||||
fi
|
||||
|
||||
# Пользователь подписки (работает не от root).
|
||||
if id -u kit-sub >/dev/null 2>&1; then
|
||||
pkill -u kit-sub >/dev/null 2>&1 || true
|
||||
userdel -r kit-sub >/dev/null 2>&1 && kl_ok "пользователь kit-sub удалён" || kl_warn "не смог удалить пользователя kit-sub"
|
||||
fi
|
||||
|
||||
kl_step "Возвращаю систему"
|
||||
systemctl daemon-reload
|
||||
if [[ $reset_ssh == yes && -f /etc/systemd/system/ssh.socket.d/99-kit-local-port.conf ]]; then
|
||||
rm -f /etc/systemd/system/ssh.socket.d/99-kit-local-port.conf
|
||||
rm -f /etc/ssh/sshd_config.d/99-kit-local-port.conf
|
||||
systemctl daemon-reload
|
||||
kl_run systemctl restart ssh.socket 2>/dev/null || kl_run systemctl restart ssh 2>/dev/null || true
|
||||
kl_warn "ssh-дроп-ин удалён: порт вернулся к системному. Проверьте вход в НОВОМ окне!"
|
||||
fi
|
||||
if kl_have ufw && kl_ufw_active; then
|
||||
local p
|
||||
for p in 80/tcp 443/tcp 443/udp; do ufw delete allow "$p" >/dev/null 2>&1 && kl_ok "правило ufw снято: $p"; done
|
||||
if [[ -f $KL_UFW_RULES ]]; then
|
||||
kl_backup "$KL_UFW_RULES"; kl_backup "$KL_UFW6_RULES"
|
||||
sed -i 's/^# -A ufw-before-input -p icmp --icmp-type echo-request/-A ufw-before-input -p icmp --icmp-type echo-request/' "$KL_UFW_RULES"
|
||||
[[ -f $KL_UFW6_RULES ]] && sed -i 's/^# -A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/' "$KL_UFW6_RULES"
|
||||
ufw reload >/dev/null 2>&1 && kl_ok "ответ на ping возвращён"
|
||||
fi
|
||||
fi
|
||||
sysctl -q -w net.ipv4.icmp_echo_ignore_all=0 >/dev/null 2>&1 || true
|
||||
|
||||
kl_step "Проверка (теми же условиями, что и установщик)"
|
||||
local left=0
|
||||
if [[ -f /root/3x-ui.txt && -x /usr/local/x-ui/x-ui ]]; then
|
||||
kl_warn "установщик всё ещё считает панель установленной"; left=1
|
||||
fi
|
||||
for s in $(systemctl list-unit-files --no-legend 2>/dev/null | awk '{print $1}' \
|
||||
| grep -E '^(x-ui|kit-|kit\.)' | grep -E '\.(service|timer)$'); do
|
||||
kl_warn "осталась наша служба: $s"; left=1
|
||||
done
|
||||
# Добивка: убираем всё, что осталось и подходит под наши шаблоны (вдруг автор добавил новый файл
|
||||
# или что-то не удалилось с первого раза). Затем проверяем ещё раз.
|
||||
for f in /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
|
||||
/etc/systemd/system/kit-*.service /etc/systemd/system/kit-*.timer \
|
||||
/etc/systemd/system/x-ui*.service /usr/lib/systemd/system/kit-*.service \
|
||||
/etc/x-ui /usr/local/x-ui /usr/bin/x-ui /usr/bin/x-ui.new /root/3x-ui.txt /root/cert; do
|
||||
[[ -e $f ]] || continue
|
||||
rm -rf "$f" && { kl_ok "добито: $f"; i=1; } || kl_warn "не удалось удалить: $f"
|
||||
done
|
||||
((i)) && systemctl daemon-reload
|
||||
found=$( { ls -d /etc/kit* /usr/local/lib/kit* /usr/local/bin/kit /etc/cron.d/kit-* /var/www/kit \
|
||||
/etc/systemd/system/*kit* /etc/x-ui /usr/local/x-ui /root/3x-ui.txt /root/cert 2>/dev/null || true; } | head -20)
|
||||
[[ -z $found ]] || { kl_warn "осталось на диске:"; printf ' %s\n' $found; left=1; }
|
||||
ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE '[:.]443$' && { kl_warn "порт 443 всё ещё кем-то слушается"; left=1; }
|
||||
if ((left)); then
|
||||
kl_warn "осталось перечисленное выше — пришлите этот вывод, поправим список в kit local uninstall"
|
||||
else
|
||||
kl_ok "чисто: от установки не осталось ни файлов, ни служб, порт 443 свободен"
|
||||
kl_ok "проверка установщика пройдена: он снова поставит сервер с нуля"
|
||||
fi
|
||||
kl_ok "SSH не тронут (если не указан --reset-ssh)"
|
||||
kl_step "Готово: сервер в состоянии до установки, можно ставить заново"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- полная справка `kit help`
|
||||
# Категории и все команды: сначала авторские, затем наши. Если автор добавит команду, а справка
|
||||
# про неё не знает — сборку релиза остановит проверка tools/check-help.sh.
|
||||
kit_help_all() {
|
||||
cat <<EOF
|
||||
${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то же самое доступно в меню: kit без аргументов)
|
||||
|
||||
Состояние и версии
|
||||
kit меню
|
||||
kit version версии: сборка, панель 3X-UI, ядро Xray
|
||||
kit local version наша сборка, источник обновлений, дата файла
|
||||
kit local channel откуда берутся обновления и чем проверяется подпись
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit help эта справка
|
||||
|
||||
Пользователи
|
||||
kit user add <имя> [--gb N] [--days N] [--devices N] создать (пусто/0 — без ограничения)
|
||||
kit user list список пользователей
|
||||
kit user link <имя> [--all | --amnezia | --telegram] подписка с QR-кодом и ссылки
|
||||
kit user limit <имя> --gb N --days N --devices N лимиты трафика, срока, устройств
|
||||
kit user on <имя> включить пользователя
|
||||
kit user off <имя> выключить пользователя
|
||||
kit user del <имя> удалить
|
||||
kit user enforce применить лимиты сейчас
|
||||
kit user vision <имя> [on|off] поток xtls-rprx-vision у REALITY
|
||||
|
||||
Подключения и порты
|
||||
kit net состояние подключений
|
||||
kit net on <имя> включить подключение
|
||||
kit net off <имя> выключить подключение
|
||||
kit net port <имя> <порт> перевести подключение на другой порт
|
||||
kit net vision on|off Vision у всех REALITY сразу
|
||||
kit net split on|off|check|apply разделение трафика по правилам (split)
|
||||
kit port какие порты слушают подключения
|
||||
kit port set <имя> <порт> сменить порт подключения
|
||||
kit reality add [порт] добавить второй REALITY
|
||||
|
||||
Маскировка и сайт
|
||||
kit net site [сайт] сменить сайт маскировки (без аргумента — подобрать соседа по подсети)
|
||||
kit net fp <отпечаток> отпечаток TLS (chrome, firefox, safari, …)
|
||||
kit sni какие сайты маскировки у подключений
|
||||
kit sni rotate сменить сайты маскировки у всех подключений
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local site [--list | N | --show] сайт-заглушка: случайный ИТ-сайт, список, конкретный, какой стоит
|
||||
|
||||
Проверка и починка
|
||||
kit local uninstall снести установку целиком (панель, туннель, службы kit, сайт, сертификаты); SSH не трогает
|
||||
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
|
||||
kit fix [--dry-run] безопасные починки: время, службы, конфиги
|
||||
kit local hardening сводка по защите и что ещё можно включить
|
||||
|
||||
Обслуживание и обновление
|
||||
kit update [--panel | --force | --auto | --manual] обновить сборку, панель, автообновление
|
||||
kit panel update обновить панель 3X-UI
|
||||
kit backup копия сервера: панель, сертификаты, настройки
|
||||
kit net masq on|off ответ Hysteria2 страницей сайта
|
||||
kit net dns on|off DoH в подписках
|
||||
|
||||
Защита сервера (наши команды)
|
||||
kit local ssh <порт> | --status | --close-old <порт> добавить порт SSH, какие слушают, закрыть прежние
|
||||
kit local ping off | on | status ответ на ping (правилом ufw)
|
||||
kit local passwd [--generate] [пользователь] сменить пароль (в логи не попадает)
|
||||
kit local upgrades on | off | status обновления безопасности
|
||||
|
||||
Общие правила
|
||||
* у наших команд есть --dry-run: показывает план и ничего не меняет
|
||||
* подробная справка по нашему слою: kit local help
|
||||
* копии изменённых файлов: /root/kit-local-backup-<дата>/
|
||||
EOF
|
||||
}
|
||||
|
||||
kit_local_dispatch() {
|
||||
case ${1:-} in
|
||||
local)
|
||||
@@ -883,6 +1163,11 @@ kit_local_dispatch() {
|
||||
kit_local_cmd "$@"
|
||||
exit $?
|
||||
;;
|
||||
help | -h | --help)
|
||||
# Полная справка: авторские команды + наши, по категориям.
|
||||
kit_help_all
|
||||
exit 0
|
||||
;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
+2
-2
@@ -494,11 +494,11 @@ def routing_payload(kind, loaded):
|
||||
else:
|
||||
ips.append(v)
|
||||
if kind == "happ":
|
||||
data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,
|
||||
data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,
|
||||
"ProxyIp": ips, "DirectIp": ["geoip:private"]}
|
||||
else:
|
||||
# localhost – системный DNS клиента для прямых сайтов (как в подписке Mihomo); список direct_dns его заменяет.
|
||||
data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",
|
||||
data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",
|
||||
"ProxySites": sites, "ProxyIp": ["1.1.1.1", "8.8.8.8"] + ips, "DirectSites": ["regexp:.*"],
|
||||
"DirectIp": ["geoip:private", "0.0.0.0/0", "::/0"], "DomesticDNSDomain": direct_dns[0] if direct_dns else "localhost"}
|
||||
text = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
+22
-20
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.
|
||||
# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.
|
||||
# https://github.com/itsnotkubrick/3X-UI_KIT
|
||||
#
|
||||
# kit user add имя [--gb 50] [--days 30] [--devices 3]
|
||||
@@ -12,7 +12,7 @@
|
||||
set -Eeuo pipefail
|
||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.15"
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
||||
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
||||
@@ -387,7 +387,7 @@ cmd_enforce() {
|
||||
limit_timer_on() {
|
||||
cat >/etc/systemd/system/kit-limit.service <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)
|
||||
Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)
|
||||
After=x-ui.service
|
||||
|
||||
[Service]
|
||||
@@ -397,7 +397,7 @@ TimeoutStartSec=120
|
||||
UNIT
|
||||
cat >/etc/systemd/system/kit-limit.timer <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут
|
||||
Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
@@ -462,7 +462,7 @@ cmd_del() {
|
||||
# Новый ключ приходит только в релизе, подписанном старым.
|
||||
KIT_SIGNERS=(
|
||||
# Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"
|
||||
)
|
||||
KIT_SIG_NS="3x-ui-kit-release"
|
||||
KIT_SIG_ID="releases@3x-ui-kit"
|
||||
@@ -496,7 +496,7 @@ update_hint() {
|
||||
}
|
||||
|
||||
cmd_version() {
|
||||
echo "3X-UI KIT $KIT_VERSION"
|
||||
echo "3X-UI_autoVPS $KIT_VERSION"
|
||||
echo "${D}панель 3X-UI $(/usr/local/x-ui/x-ui -v 2>/dev/null | head -1 || echo '?'), ядро Xray $(xray_version)${N}"
|
||||
if auto_enabled; then echo "${D}автообновление: включено (только подписанные релизы), журнал $KIT_UPDATE_LOG${N}"
|
||||
else echo "${D}автообновление: выключено, включить: kit update --auto${N}"; fi
|
||||
@@ -525,7 +525,7 @@ fetch_release() { # версия каталог
|
||||
return 1
|
||||
fi
|
||||
# Версия записана внутри подписанного файла: старый подписанный релиз под видом нового не пройдёт.
|
||||
grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }
|
||||
grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }
|
||||
for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do
|
||||
sum=$(awk -v f="$f" '$2 == f {print $1}' "$d/SHA256SUMS")
|
||||
[[ $sum =~ ^[0-9a-f]{64}$ ]] || { warn "В подписанном списке нет $f."; return 1; }
|
||||
@@ -540,7 +540,7 @@ fetch_release() { # версия каталог
|
||||
kit_sub_unit() { # путь-к-сертификату путь-к-ключу (пусто – за nginx)
|
||||
cat <<UNIT
|
||||
[Unit]
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)
|
||||
After=network-online.target x-ui.service
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -604,7 +604,7 @@ needs_migration() {
|
||||
auto_on() {
|
||||
cat >/etc/systemd/system/kit-update.service <<UNIT
|
||||
[Unit]
|
||||
Description=3X-UI KIT: автообновление kit и kit-sub (только подписанные релизы)
|
||||
Description=3X-UI_autoVPS: автообновление kit и kit-sub (только подписанные релизы)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -616,7 +616,7 @@ StandardError=append:$KIT_UPDATE_LOG
|
||||
UNIT
|
||||
cat >/etc/systemd/system/kit-update.timer <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: проверка обновлений раз в сутки
|
||||
Description=3X-UI_autoVPS: проверка обновлений раз в сутки
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:00:00
|
||||
@@ -666,7 +666,7 @@ cmd_update() {
|
||||
[[ $unattended == yes ]] || echo "${D}Переустановить файлы kit той же версии: kit update --force${N}"
|
||||
return
|
||||
fi
|
||||
if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI KIT $KIT_VERSION → $latest"; fi
|
||||
if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI_autoVPS $KIT_VERSION → $latest"; fi
|
||||
tmp=$(mktemp -d)
|
||||
# shellcheck disable=SC2064 # путь подставляем сразу: при выходе локальной переменной уже нет
|
||||
trap "rm -rf -- '$tmp'" EXIT
|
||||
@@ -728,7 +728,7 @@ cmd_update() {
|
||||
# Новые пути подписки (Clash и JSON) подключает уже новая версия kit.
|
||||
/usr/local/bin/kit __subextra >/dev/null 2>&1 || warn "Дополнительные пути подписки не подключились: kit update --force"
|
||||
echo
|
||||
echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."
|
||||
echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."
|
||||
[[ $unattended == yes ]] && return
|
||||
local news
|
||||
news=$(changelog_of "$latest")
|
||||
@@ -1914,10 +1914,10 @@ YAML
|
||||
|
||||
# Приложениям на Xray правила раздаёт сама панель (3X-UI 3.9+): заголовок Routing для Happ и правила JSON-подписки.
|
||||
# Собираем их из того же rules.yaml. Чужой профиль, уже стоящий в панели, не трогаем.
|
||||
split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"
|
||||
split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI_autoVPS"
|
||||
local v=$1
|
||||
[[ $v == happ://routing/onadd/* ]] && v=$(printf '%s' "${v#happ://routing/onadd/}" | base64 -d 2>/dev/null || true)
|
||||
grep -q '"Name":"3X-UI KIT"' <<<"$v"
|
||||
grep -q '"Name":"3X-UI_autoVPS"' <<<"$v"
|
||||
}
|
||||
|
||||
split_panel_sync() { # on|off
|
||||
@@ -2551,14 +2551,15 @@ menu_main() {
|
||||
local c
|
||||
while :; do
|
||||
echo
|
||||
box "3X-UI KIT $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \
|
||||
"7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"
|
||||
"7. Резервная копия" "8. Версии" "" "9. Удалить всё установленное" "10. Выход" \
|
||||
"11. Панель 3X-UI (меню x-ui)"
|
||||
echo
|
||||
menu_status
|
||||
update_hint
|
||||
echo
|
||||
ask_num "Выбор [0-9]: "; c=$REPLY
|
||||
ask_num "Выбор [1-11]: "; c=$REPLY
|
||||
case $c in
|
||||
1) menu_users ;;
|
||||
2) menu_net ;;
|
||||
@@ -2568,8 +2569,9 @@ menu_main() {
|
||||
6) run_action menu_auto; pause ;;
|
||||
7) run_action cmd_backup; pause ;;
|
||||
8) run_action cmd_version; pause ;;
|
||||
9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||
0 | "") return 0 ;;
|
||||
9) kit_local_uninstall ;;
|
||||
10 | 0 | "") return 0 ;;
|
||||
11) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||
*) echo "Нет такого пункта." ;;
|
||||
esac
|
||||
done
|
||||
@@ -2577,7 +2579,7 @@ menu_main() {
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${B}3X-UI KIT $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})
|
||||
${B}3X-UI_autoVPS $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})
|
||||
|
||||
kit меню
|
||||
kit user … add, list, link, limit, on, off, del
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Справка `kit help` обязана перечислять все команды: авторские (их берём прямо из диспетчера
|
||||
# scripts/kit.sh) и наши. Если автор добавит команду, а мы забудем про справку — проверка упадёт,
|
||||
# и сборка релиза остановится: так справка не отстанет молча.
|
||||
set -Eeuo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
help=$(awk '/^kit_help_all\(\) \{/,/^\}/' scripts/kit-local.sh)
|
||||
[[ -n $help ]] || { echo "✗ не нашёл kit_help_all в scripts/kit-local.sh" >&2; exit 1; }
|
||||
|
||||
bad=0
|
||||
# 1) авторские команды: первое слово каждой ветки диспетчера kit.sh
|
||||
while read -r word; do
|
||||
[[ -n $word ]] || continue
|
||||
case $word in __* | local | help | -h | --help | -v | --version) continue ;; esac
|
||||
# «user off» в справке может стоять как «user on|off» — поэтому пробел допускаем и как разделитель вариантов
|
||||
pat=${word// /[ |]}
|
||||
grep -qE " kit ${pat}" <<<"$help" || { echo " ✗ в справке нет авторской команды: kit $word" >&2; bad=1; }
|
||||
done < <(grep -oE '^ +"[a-z][a-z0-9 -]*"\)|^ +"[a-z][a-z0-9 -]*" \*\)' scripts/kit.sh | sed 's/)$//' | tr -d '"' | sed 's/^ *//' | sort -u)
|
||||
|
||||
# 2) наши команды из ветки kit_local_cmd
|
||||
while read -r word; do
|
||||
[[ -n $word ]] || continue
|
||||
grep -qF " kit local $word" <<<"$help" || { echo " ✗ в справке нет нашей команды: kit local $word" >&2; bad=1; }
|
||||
done < <(awk '/^kit_local_cmd\(\) \{/,/^\}/' scripts/kit-local.sh | grep -oE '^ [a-z-]+' | tr -d ' ' | sort -u)
|
||||
|
||||
if (( bad )); then
|
||||
echo "✗ справка kit help отстала от набора команд — поправьте kit_help_all в scripts/kit-local.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ справка kit help содержит все команды (авторские и наши)"
|
||||
@@ -1 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
# Робот ежесуточной синхронизации: проверяет обновления у автора проекта и, если они есть,
|
||||
# переносит их в нашу ветку dev — но только когда всё сошлось.
|
||||
#
|
||||
# Где живёт: запускается из .gitea/workflows/fork-sync.yml на раннере (CT 114 «runner»).
|
||||
# Что НЕ делает: не выпускает релиз и не подписывает его — закрытый ключ подписи остаётся
|
||||
# только на рабочей станции владельца, поэтому версия автора публикуется вручную.
|
||||
#
|
||||
# Переменные окружения (все имеют значения по умолчанию, кроме TOKEN):
|
||||
# TOKEN токен Gitea с правом записи в репозиторий (берётся из секретов workflow)
|
||||
# GITEA_URL внутренний адрес Gitea (снаружи контейнер её не видит: имя резолвится в публичный IP)
|
||||
# REPO владелец/репозиторий
|
||||
# UPSTREAM адрес авторского репозитория
|
||||
# WORK рабочая копия на раннере
|
||||
# BRANCH наша ветка для синхронизации
|
||||
# LOG файл отчёта последнего запуска (его читают из контейнера руками)
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
GITEA_URL=${GITEA_URL:-http://10.10.10.54:3000}
|
||||
REPO=${REPO:-hacker07/3X-UI_autoVPS}
|
||||
UPSTREAM=${UPSTREAM:-https://github.com/itsnotkubrick/3X-UI_KIT}
|
||||
WORK=${WORK:-/opt/kit-fork-sync/repo}
|
||||
BRANCH=${BRANCH:-dev}
|
||||
LOG=${LOG:-/opt/kit-fork-sync/last-run.log}
|
||||
TOKEN=${TOKEN:-}
|
||||
|
||||
say() { printf '\n==> %s\n' "$*"; }
|
||||
ok() { printf ' ✓ %s\n' "$*"; }
|
||||
die() { printf '✗ %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
printf '\n===== запуск %s =====\n' "$(date '+%F %T %Z')"
|
||||
|
||||
[[ -n $TOKEN ]] || die "нет TOKEN: workflow не передал секрет GITEA_TOKEN"
|
||||
command -v git >/dev/null || die "нет git"
|
||||
PY=${PY:-$(command -v python3 || command -v python || true)}
|
||||
[[ -n $PY ]] || die "нет python3 — без него не применить швы"
|
||||
|
||||
auth_url="${GITEA_URL}/${REPO}.git"
|
||||
auth_url="${auth_url/http:\/\//http://x-access-token:${TOKEN}@}"
|
||||
|
||||
# ---------------------------------------------------------------- рабочая копия
|
||||
|
||||
if [[ ! -d $WORK/.git ]]; then
|
||||
say "Первая синхронизация: клонирую репозиторий"
|
||||
git clone -q "$auth_url" "$WORK" || die "не смог склонировать $REPO"
|
||||
git -C "$WORK" remote set-url origin "$auth_url"
|
||||
git -C "$WORK" remote add upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote set-url upstream "$UPSTREAM"
|
||||
else
|
||||
git -C "$WORK" remote set-url origin "$auth_url"
|
||||
git -C "$WORK" remote set-url upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote add upstream "$UPSTREAM"
|
||||
fi
|
||||
cd "$WORK"
|
||||
|
||||
say "Забираю ветки: наша $BRANCH и авторскую main"
|
||||
git fetch -q origin --prune
|
||||
git fetch -q upstream 'refs/heads/main:refs/heads/upstream' --prune
|
||||
git checkout -q "$BRANCH"
|
||||
git reset -q --hard "origin/$BRANCH"
|
||||
|
||||
author_version=$(git show upstream:VERSION 2>/dev/null | tr -d '[:space:]' || true)
|
||||
[[ -n $author_version ]] || die "у автора не читается VERSION — проверьте ветку upstream"
|
||||
ours=$(tr -d '[:space:]' <VERSION)
|
||||
echo " у автора: $author_version · у нас: $ours"
|
||||
|
||||
new_commits=$(git rev-list --count "$BRANCH..upstream")
|
||||
if [[ $new_commits == 0 ]]; then
|
||||
say "У автора нет новых коммитов — публиковать нечего"
|
||||
ok "проверка выполнена, ветка $BRANCH не изменялась"
|
||||
exit 0
|
||||
fi
|
||||
say "У автора $new_commits новых коммитов — переношу наши правки на свежий код"
|
||||
git --no-pager log --oneline "$BRANCH..upstream" | sed 's/^/ /'
|
||||
|
||||
# ---------------------------------------------------------------- перенос и проверки
|
||||
|
||||
# Скрипт форка делает перенос (rebase на ветку автора) и все проверки: швы, синтаксис, LF, тесты.
|
||||
# Без --apply: швы в нашей ветке уже накатаны, повторный накат на такое дерево падает (якоря автора
|
||||
# заменены нашими строками). Номер версии поднимаем сами — по той же логике, что и в fork-sync.sh.
|
||||
if ! bash tools/fork-sync.sh; then
|
||||
die "перенос не прошёл (конфликт в местах наших правок). Ветка $BRANCH не изменена. Разберите вручную на рабочей станции"
|
||||
fi
|
||||
|
||||
# Номер версии: у автора 1.3 → наш 1.3.1; если база та же, а мы дорабатываем — 1.3.2. Обязан расти.
|
||||
base=${author_version%%.*}
|
||||
cur=$(tr -d '[:space:]' <VERSION)
|
||||
if [[ ${cur%%.*} == "$base" ]]; then
|
||||
new_version="$base.$(( ${cur##*.} + 1 ))"
|
||||
else
|
||||
new_version="${author_version}.1"
|
||||
fi
|
||||
printf '%s\n' "$new_version" >VERSION
|
||||
for f in scripts/kit.sh scripts/3x-ui.sh scripts/hysteria2.sh scripts/3x-ui-local.sh; do
|
||||
sed -i "s/^KIT_VERSION=\"[0-9.]*\"/KIT_VERSION=\"$new_version\"/" "$f"
|
||||
done
|
||||
"$PY" tools/fork_apply.py >/dev/null 2>&1 || true # на дереве со швами это ожидаемо: якорей уже нет
|
||||
echo " версия сборки: $cur → $new_version"
|
||||
|
||||
# Заглушка записи в нашем журнале версий: без раздела «## v<версия>» сборщик релиза откажется
|
||||
# подписывать. Текст — на владельца, он правит его перед выпуском.
|
||||
if ! grep -q "^## v$new_version" FORK-CHANGELOG.md; then
|
||||
say "Добавляю запись в FORK-CHANGELOG для версии $new_version"
|
||||
"$PY" - "$new_version" "$author_version" <<'PY'
|
||||
import sys, pathlib
|
||||
new, author = sys.argv[1], sys.argv[2]
|
||||
p = pathlib.Path("FORK-CHANGELOG.md")
|
||||
text = p.read_text(encoding="utf-8")
|
||||
block = (f"## v{new}: обновление от автора {author}\n\n"
|
||||
f"* Автоматическая синхронизация подтянула код автора {author} в нашу сборку: наши правки (швы)\n"
|
||||
f" перенесены на свежий код, проверки прошли. Описать, что изменилось у автора, до выпуска релиза.\n\n")
|
||||
marker = "## v"
|
||||
i = text.find(marker)
|
||||
p.write_text(text[:i] + block + text[i:], encoding="utf-8", newline="\n")
|
||||
print(f" ✓ раздел «## v{new}» добавлен")
|
||||
PY
|
||||
fi
|
||||
|
||||
# Финальный контроль: швы на месте, синтаксис и LF — как того требует сборщик релиза.
|
||||
"$PY" tools/fork_apply.py --check >/dev/null || die "после переноса швы не проверяются — ничего не публикую"
|
||||
for f in scripts/*.sh tools/*.sh; do bash -n "$f" || die "$f: синтаксис"; done
|
||||
if grep -qU $'\r' scripts/*.sh tools/*.sh VERSION 2>/dev/null; then
|
||||
die "в файлах CRLF — сервер сверяет SHA256, сумма не сойдётся. Ничего не публикую"
|
||||
fi
|
||||
ok "швы на месте, синтаксис и переводы строк в порядке"
|
||||
|
||||
# ---------------------------------------------------------------- публикация
|
||||
|
||||
if git diff --quiet origin/"$BRANCH" -- VERSION scripts tools .gitea 2>/dev/null && [[ $new_version == "$ours" ]]; then
|
||||
say "Содержимое не изменилось — публиковать нечего"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "Публикую в $BRANCH"
|
||||
git add -A
|
||||
git -c user.name="fork-sync-bot" -c user.email="fork-sync@rukavishnikoff.ru" \
|
||||
commit -q -m "Синхронизация с автором ${author_version}: швы и версия ${new_version}" \
|
||||
-m "Автоматика CT 114. Проверки: швы на месте, bash -n, LF. Релиз и подпись — вручную с рабочей станции." \
|
||||
|| die "не смог закоммитить"
|
||||
curl -fsS -o /dev/null -H "Authorization: token ${TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/branches/${BRANCH}" || die "Gitea недоступна перед отправкой"
|
||||
# Перенос идёт rebase'ом: наша ветка переписывается на свежий код автора, поэтому отправляем
|
||||
# с --force-with-lease — если за это время в ветку что-то попало с рабочей станции, отправка
|
||||
# откажется, а не затрёт чужой коммит.
|
||||
git push -q --force-with-lease=refs/heads/"$BRANCH" origin "$BRANCH" \
|
||||
|| die "не смог отправить ветку $BRANCH (возможно, в ней есть ваши свежие коммиты — тогда синхронизируйте вручную)"
|
||||
ok "ветка $BRANCH обновлена: $(git --no-pager log --oneline -1)"
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Готово. Дальше вручную, с рабочей станции:
|
||||
1) git fetch origin && git checkout $BRANCH && git reset --hard origin/$BRANCH
|
||||
2) описать изменения в FORK-CHANGELOG.md (раздел уже добавлен)
|
||||
3) bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key — подпись и релиз
|
||||
4) git tag v$new_version && git push origin $BRANCH v$new_version
|
||||
EOF
|
||||
+488
-116
@@ -60,7 +60,7 @@ def main():
|
||||
args.remove("--check")
|
||||
root = pathlib.Path(args[0] if args else ".").resolve()
|
||||
if not (root / "VERSION").exists():
|
||||
die("%s не похож на репозиторий 3X-UI KIT (нет VERSION)" % root)
|
||||
die("%s не похож на репозиторий 3X-UI_autoVPS (нет VERSION)" % root)
|
||||
os.chdir(root)
|
||||
|
||||
version = (root / "VERSION").read_text(encoding="utf-8").strip()
|
||||
@@ -86,124 +86,446 @@ def main():
|
||||
|
||||
version_re = r'^KIT_VERSION="[0-9.]+"$'
|
||||
edits = [
|
||||
# --- scripts/kit.sh -------------------------------------------------
|
||||
("scripts/kit.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
'scripts/kit.sh',
|
||||
'# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.\n',
|
||||
False,
|
||||
'# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }\n',
|
||||
False,
|
||||
'kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }\n\n# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---\n# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.\n# Все команды форка — под «kit local …» (см. kit local help).\nKIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"\nif [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi\n\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
'Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)\n',
|
||||
False,
|
||||
'Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
'Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут\n',
|
||||
False,
|
||||
'Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' # SHA256:VDuGgJ8dOeCNXB4nBZf3+kRlWthw3+vh8rfMxzMSRIM (itsnotkubrick, 2026-09-30)\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrTGCDhhnm8XO1ekpPJuSWRVCJiFiupEspfQxcbEBmz 3X-UI KIT releases"\n',
|
||||
False,
|
||||
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"\n',
|
||||
'авто-шов: replace, строк 2',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' 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 ;;\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 \'%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 \'%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 \'%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',
|
||||
' 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\n pick=$(kl_sni_menu "REALITY (TCP 443) — под какой сайт маскировать" ${nb[@]+"${nb[@]}"})\n if ((${#nb[@]})); then\n [[ -n $pick ]] && SNI=$pick || SNI=${nb[0]}\n elif [[ -n $pick ]]; then\n SNI=$pick\n fi\n if [[ -n $SNI ]]; then\n if ! 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 [[ -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',
|
||||
' 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',
|
||||
' extra=$(sub_links "$SUBID-awg" 1; sub_links "$SUBID-tg" 1)\n',
|
||||
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\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'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/hysteria2.sh',
|
||||
'# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
|
||||
False,
|
||||
'# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/hysteria2.sh',
|
||||
'Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).\n',
|
||||
False,
|
||||
'Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit-sub.py',
|
||||
' data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
|
||||
False,
|
||||
' data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit-sub.py',
|
||||
' data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
|
||||
False,
|
||||
' data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",\n',
|
||||
'авто-шов: replace, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit-sub.py',
|
||||
' main()\n',
|
||||
False,
|
||||
' main()\n\n',
|
||||
'авто-шов: insert, строк 1',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
version_re,
|
||||
True,
|
||||
f'KIT_VERSION="{version}"',
|
||||
'версия сборки форка',
|
||||
),
|
||||
(
|
||||
'scripts/3x-ui.sh',
|
||||
version_re,
|
||||
True,
|
||||
f'KIT_VERSION="{version}"',
|
||||
'версия сборки форка',
|
||||
),
|
||||
(
|
||||
'scripts/hysteria2.sh',
|
||||
version_re,
|
||||
True,
|
||||
f'KIT_VERSION="{version}"',
|
||||
'версия сборки форка',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
|
||||
False,
|
||||
'KIT_RAW="%s/raw/branch/main"' % GITEA,
|
||||
"источник VERSION и подсказки об обновлении",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }',
|
||||
False,
|
||||
'kit_ref_raw() { echo "%s/raw/tag/v$1"; }' % GITEA + "\n" + HOOK,
|
||||
"файлы релиза из нашего тега + подключение kit-local.sh",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
r' # SHA256:[^\n]*\n "ssh-ed25519 [^\n]*"\n',
|
||||
True,
|
||||
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n'
|
||||
' "%s"\n' % pub,
|
||||
"ключ проверки подписи релизов",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
"for f in scripts/kit.sh scripts/kit-sub.py; do",
|
||||
False,
|
||||
"for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do",
|
||||
"наш файл в списке проверяемых и скачиваемых",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit',
|
||||
False,
|
||||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n'
|
||||
" # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n"
|
||||
" install -d -m 755 /usr/local/lib/kit\n"
|
||||
' install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh',
|
||||
"установка kit-local.sh при обновлении",
|
||||
),
|
||||
# --- scripts/3x-ui.sh -----------------------------------------------
|
||||
("scripts/3x-ui.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"',
|
||||
False,
|
||||
'KIT_RAW="%s/raw/tag/v$KIT_VERSION"' % GITEA,
|
||||
"запасная загрузка kit из нашего релиза",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"',
|
||||
False,
|
||||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"',
|
||||
"адрес нашего файла для установщика",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"',
|
||||
False,
|
||||
'KIT_INSTALL_CMD="bash <(curl -fsSL %s/raw/branch/main/scripts/3x-ui.sh)"' % GITEA,
|
||||
"команда установки, которую показывает панель",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||||
False,
|
||||
" # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n"
|
||||
" install -d -m 755 /usr/local/lib/kit\n"
|
||||
' if [[ -n $src && -f $d/kit-local.sh ]]; then\n'
|
||||
' install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n'
|
||||
" else\n"
|
||||
' curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n'
|
||||
' || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n'
|
||||
" fi\n"
|
||||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||||
"установка kit-local.sh установщиком",
|
||||
),
|
||||
# --- scripts/hysteria2.sh -------------------------------------------
|
||||
("scripts/hysteria2.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
# --- tools/release.sh -----------------------------------------------
|
||||
(
|
||||
"tools/release.sh",
|
||||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py VERSION)",
|
||||
False,
|
||||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)",
|
||||
"наш файл в подписанном списке релиза",
|
||||
),
|
||||
(
|
||||
"tools/release.sh",
|
||||
'{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}"; } >SHA256SUMS',
|
||||
False,
|
||||
"{ echo \"# 3X-UI KIT $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" \'',
|
||||
"«что нового» ищет и в наших записях",
|
||||
'KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"',
|
||||
'канал обновлений: наш Gitea вместо GitHub',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -239,6 +561,56 @@ def main():
|
||||
p.write_text(text, encoding="utf-8", newline="")
|
||||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||||
|
||||
|
||||
# Массовая замена бренда в файлах автора: строки, которые видит человек (справка, версия,
|
||||
# описания служб, баннер). Строку проверки шапки SHA256SUMS не трогаем — её правит отдельный шов,
|
||||
# а сама шапка ещё один релиз пишется в старом виде (иначе уже установленные серверы откажутся
|
||||
# принимать обновление: они сверяют точный вид строки).
|
||||
globals_ = [
|
||||
("scripts/kit.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/3x-ui.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/hysteria2.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/kit-sub.py", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
]
|
||||
for path, old, new in globals_:
|
||||
p = pathlib.Path(path)
|
||||
if not p.exists():
|
||||
print("✗ %s: файла нет" % path)
|
||||
bad += 1
|
||||
continue
|
||||
text = p.read_text(encoding="utf-8", newline="")
|
||||
# строки, где старое имя — часть служебной сверки, не переименовываем
|
||||
keep = lambda line: "grep -qx" in line
|
||||
if check:
|
||||
if new not in text:
|
||||
print("✗ %-20s нет нашего имени «%s»" % (path, new))
|
||||
bad += 1
|
||||
continue
|
||||
left = [l for l in text.splitlines() if old in l and not keep(l)]
|
||||
if left:
|
||||
print("✗ %-20s осталось старое имя в %d строк(ах)" % (path, len(left)))
|
||||
bad += 1
|
||||
continue
|
||||
print("✓ %-20s бренд: %s" % (path, new))
|
||||
continue
|
||||
out = []
|
||||
changed = 0
|
||||
for line in text.splitlines(keepends=True):
|
||||
if old in line and not keep(line):
|
||||
out.append(line.replace(old, new))
|
||||
changed += line.count(old)
|
||||
else:
|
||||
out.append(line)
|
||||
if not changed:
|
||||
if new in text:
|
||||
print("– %-20s уже переименовано" % path)
|
||||
continue
|
||||
print("✗ %-20s не нашёл «%s»" % (path, old))
|
||||
bad += 1
|
||||
continue
|
||||
p.write_text("".join(out), encoding="utf-8", newline="")
|
||||
print("✓ %-20s переименовано вхождений: %d" % (path, changed))
|
||||
|
||||
if bad:
|
||||
if check:
|
||||
die("якорей не найдено: %d — автор изменил код, пересмотри правки в tools/fork_apply.py" % bad)
|
||||
|
||||
+16
-4
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Подпись релиза 3X-UI KIT. Запускает только автор, на своём компьютере, перед тегом:
|
||||
# Подпись релиза 3X-UI_autoVPS. Запускает только автор, на своём компьютере, перед тегом:
|
||||
#
|
||||
# tools/release.sh [~/.ssh/3xui_kit_release]
|
||||
#
|
||||
# Проверяет, что версии сходятся и установщик 3X-UI не подменили, пишет SHA256SUMS,
|
||||
# подписывает его закрытым ключом (ssh-keygen спросит пароль ключа) и сразу проверяет
|
||||
# подпись открытым ключом, записанным в kit. Сервер 3X-UI KIT ставит обновление, только
|
||||
# подпись открытым ключом, записанным в kit. Сервер 3X-UI_autoVPS ставит обновление, только
|
||||
# если эта проверка у него тоже сошлась. Закрытый ключ никуда не отправляется.
|
||||
|
||||
set -euo pipefail
|
||||
@@ -16,7 +16,7 @@ ok() { printf '✓ %s\n' "$*"; }
|
||||
sha() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
|
||||
|
||||
key=${1:-$HOME/.ssh/3xui_kit_release}
|
||||
[[ -f $key ]] || die "Нет закрытого ключа $key. Создать: ssh-keygen -t ed25519 -C \"3X-UI KIT releases\" -f $key"
|
||||
[[ -f $key ]] || die "Нет закрытого ключа $key. Создать: ssh-keygen -t ed25519 -C \"3X-UI_autoVPS releases\" -f $key"
|
||||
NS="3x-ui-kit-release"
|
||||
ID="releases@3x-ui-kit"
|
||||
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)
|
||||
@@ -36,6 +36,18 @@ for f in "${FILES[@]}"; do
|
||||
done
|
||||
ok "Переводы строк во всех файлах релиза — LF"
|
||||
|
||||
export PY
|
||||
bash tools/check-help.sh || die "справка kit help отстала от набора команд"
|
||||
|
||||
# «Строки-сироты»: строка с флагом без команды (например потерянный "#" в комментарии) не ловится
|
||||
# ни bash -n, ни справкой, но при каждом запуске печатает «command not found». Проверяем холостым прогоном.
|
||||
err=$(bash scripts/3x-ui-local.sh --dry-run 2>&1 >/dev/null || true)
|
||||
if grep -qE "command not found|syntax error|bad substitution" <<<"$err"; then
|
||||
printf '%s\n' "$err" | grep -E "command not found|syntax error|bad substitution" | head -3
|
||||
die "установщик ругается на строку без команды — поправьте файл (см. выше)"
|
||||
fi
|
||||
ok "Установщик не ругается на пустые строки и опечатки"
|
||||
|
||||
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||
|
||||
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
|
||||
@@ -77,7 +89,7 @@ pub=$(ssh-keygen -y -f "$key" | awk '{print $1, $2}')
|
||||
signers scripts/kit.sh | awk '{print $1, $2}' | grep -qxF "$pub" || die "Ключа $key нет в KIT_SIGNERS – серверы не примут такую подпись."
|
||||
ok "Ключ подписи записан в kit"
|
||||
|
||||
{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
|
||||
{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
|
||||
rm -f SHA256SUMS.sig
|
||||
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null
|
||||
tmp=$(mktemp)
|
||||
|
||||
@@ -113,7 +113,7 @@ class RestoreEnv(unittest.TestCase):
|
||||
def bad(self, over):
|
||||
r = restore_env(over)
|
||||
self.assertNotIn("ENV-OK", r.stdout)
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", r.stdout + r.stderr)
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", r.stdout + r.stderr)
|
||||
|
||||
def test_mtproto_inner_sed(self):
|
||||
for v in (b"MTPROTO_INNER='[0-9]*.*/touch \\/tmp\\/pwn-test/e;#'", b"MTPROTO_INNER='1/x/;1e touch /tmp/pwn-test #'", b"MTPROTO_INNER=1/x/e"):
|
||||
@@ -148,7 +148,7 @@ class RestoreEnv(unittest.TestCase):
|
||||
self.assertIn("байты " + name, check(p).stderr)
|
||||
|
||||
def test_3xui_txt(self):
|
||||
good = "3X-UI KIT – данные для входа\n\nПанель: https://1.2.3.4/x/\n\tvless://a@1.2.3.4:443?x=1#Мой 🙂\n".encode()
|
||||
good = "3X-UI_autoVPS – данные для входа\n\nПанель: https://1.2.3.4/x/\n\tvless://a@1.2.3.4:443?x=1#Мой 🙂\n".encode()
|
||||
r = check(archive([(k, v) for k, v in ENVS.items()] + [("root/3x-ui.txt", good)]))
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
for bad in (b"\0", b"\x1b]0;x\x07", b"\x1b[2J", b"\r", b"\xff", b"\xc2\x9b", b"\x7f"):
|
||||
|
||||
@@ -102,7 +102,7 @@ class SafeEnv(unittest.TestCase):
|
||||
def rejected(self, name, text):
|
||||
code, out = run(name, text, 'echo "RESULT=$RESULT"\n')
|
||||
self.assertNotEqual(code, 0, out)
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", out)
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", out)
|
||||
self.assertNotIn("\nOK\n", "\n" + out)
|
||||
|
||||
def test_result_in_backup_env(self):
|
||||
@@ -148,7 +148,7 @@ class SafeEnv(unittest.TestCase):
|
||||
r = subprocess.run(["bash", "-c", script, "t", p], capture_output=True)
|
||||
out = (r.stdout + r.stderr).decode("utf-8", "replace")
|
||||
self.assertNotEqual(r.returncode, 0, "%s %r: %s" % (name, data, out))
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", out, "%s %r" % (name, data))
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", out, "%s %r" % (name, data))
|
||||
self.assertNotIn("\nOK\n", "\n" + out)
|
||||
|
||||
def test_substitutions(self):
|
||||
|
||||
@@ -128,7 +128,7 @@ class Rules(unittest.TestCase):
|
||||
link = ks.routing_payload("happ", loaded)
|
||||
self.assertTrue(link.startswith("happ://routing/onadd/"))
|
||||
h = json.loads(base64.b64decode(link.split("/onadd/")[1]))
|
||||
self.assertEqual(h["Name"], "3X-UI KIT")
|
||||
self.assertEqual(h["Name"], "3X-UI_autoVPS")
|
||||
self.assertEqual(h["GlobalProxy"], "false")
|
||||
self.assertEqual(h["ProxySites"], ["geosite:youtube", "domain:example.org", "full:api.example.com"])
|
||||
self.assertEqual(h["ProxyIp"], ["geoip:telegram", "203.0.113.0/24"])
|
||||
|
||||
Reference in New Issue
Block a user