1.1.1: kit check и kit fix, заголовки Happ, безопасная загрузка ядра, инструкция XKeen
- kit check и kit fix: проверка служб, сертификата, подписки, сайта маскировки, прав на файлы; безопасная починка - kit-sub: пропускает заголовки Happ (Routing и др.) до клиентов - ядро Xray: запасная загрузка через зеркало только с проверкой SHA256, tools/release.sh сверяет суммы с официальными - установка со своим доменом: при отказе Let's Encrypt предлагается самоподписанный сертификат - README: инструкция XKeen, команды kit, новый баннер; правки инструкций и changelog Co-authored-by: xlok1e <124461726+xlok1e@users.noreply.github.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
xlok1e
Claude Sonnet 5.5
parent
49b27f380a
commit
099117c2a7
@@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
+22
-6
@@ -1,20 +1,36 @@
|
|||||||
# Что нового
|
# Что нового
|
||||||
|
|
||||||
|
## v1.1.1: kit check и kit fix, исправления для Happ и скачивания ядра
|
||||||
|
|
||||||
|
**Новое**
|
||||||
|
|
||||||
|
* `kit check` и `kit fix`. Завёл для сервера врача. `kit check` осматривает службы, сертификат, подписку, права на файлы и сайт маскировки и честно говорит, что болит, а `kit fix` лечит только то, что безопасно лечить: перезапустит упавшее, вернёт права, включит автообновление. Никаких операций без наркоза. Заодно он спасает подписку, которая молча умирала после `x-ui cert`: она вела себя как кот, который делает вид, что вас не знает.
|
||||||
|
* Инструкция для XKeen прямо в README. Восемь шагов от компонентов роутера до проверки, со скриншотами, чтобы не собирать её по десяти вкладкам. Автору XKeen огромное спасибо: всю тяжёлую работу сделал он, я только собрал шпаргалку.
|
||||||
|
|
||||||
|
**Исправлено**
|
||||||
|
|
||||||
|
* Маршрутизация Happ доходит до клиентов. `kit-sub` пропускал только свой короткий список заголовков, поэтому профиль в Happ просто не появлялся. Спасибо [xlok1e](https://github.com/xlok1e): он нашёл причину и прислал исправление, мне осталось только вежливо принять его и проверить.
|
||||||
|
* Ядро Xray ставится, даже когда GitHub тормозит или молчит. Запасной путь идёт через зеркало, но только с проверкой SHA256: чужой бинарник не пройдёт, зеркалу я не верю ни на слово.
|
||||||
|
* Установка со своим доменом больше не обрывается, если Let's Encrypt отказал в сертификате на IP (лимит выдачи, закрытый порт 80, сбой). Я объясню причины, спрошу «продолжить с самоподписанным сертификатом? [Y/n]» и честно предупрежу: ссылки на подключения работают, а подписка в приложениях может не открыться. Без драмы.
|
||||||
|
* Подтянул мелочи по итогам проверки: права на ключ запасного сертификата, ожидание замка `dpkg` при установке `unzip` и защиту заголовков подписки от лишних символов.
|
||||||
|
|
||||||
|
Если у вас 1.1, сервер обновится сам этой ночью. Хотите сразу: `kit update`.
|
||||||
|
|
||||||
## v1.1: безопасность, автообновление и новый генератор для роутера
|
## v1.1: безопасность, автообновление и новый генератор для роутера
|
||||||
|
|
||||||
**Новое**
|
**Новое**
|
||||||
|
|
||||||
* Обновления приходят сами. Раз в сутки ночью сервер проверяет подпись автора и обновляет `kit`, а пользователи и ссылки остаются прежними. «Мам, удали старый VPN, вот новый» больше не нужно. Подпись не сошлась или подписка замолчала – откат. Не любите сюрпризы: `kit update --manual`.
|
* Обновления приходят сами. Раз в сутки ночью сервер проверяет мою подпись и обновляет `kit`, а пользователи и ссылки остаются прежними. «Мам, удали старый VPN, вот новый» больше не нужно. Подпись не сошлась или подписка замолчала: откат. Не любите сюрпризы? `kit update --manual`.
|
||||||
* Копия сервера одной командой. `kit backup` складывает подключения, ключи и пользователей в один файл. Восстановление из него придёт в 1.2: решили выпускать только то, что успели проверить на настоящем сервере, а не то, что «вроде работает».
|
* Копия сервера одной командой. `kit backup` складывает подключения, ключи и пользователей в один файл.
|
||||||
* Маскировка на выбор. Установщик спрашивает: стандартный чужой сайт (работает сразу) или свой домен (надёжнее: настоящая страница и сертификат Let's Encrypt). Без вопросов: `--domain vpn.example.com`. Теперь «под кого косим» решаете вы, а не скрипт за вас.
|
* Маскировка на выбор. Установщик спрашивает: стандартный чужой сайт (работает сразу) или свой домен (надёжнее: настоящая страница и сертификат Let's Encrypt). Без вопросов: `--domain vpn.example.com`. Под какой сайт будет выглядеть сервер, теперь решаете вы, а не скрипт за вас.
|
||||||
* Заглушка с характером. На случайный заход сервер показывает кофейню, фотографа или блог про Linux. Тысячи серверов 3X-UI KIT больше не выглядят как близнецы.
|
* Заглушка с характером. На случайный заход сервер показывает кофейню, фотографа или блог про Linux. Тысячи серверов 3X-UI KIT больше не выглядят как близнецы.
|
||||||
* Один генератор для роутера вместо двух. Выбираете Keenetic или OpenWrt, ядро Xray или Mihomo, вставляете ссылки и получаете конфиг плюс команду, которая сама положит его на роутер. Меньше вкладок, меньше «а я точно там нажал?». Старые адреса генераторов продолжают работать.
|
* Один генератор для роутера вместо двух. Выбираете Keenetic или OpenWrt, ядро Xray или Mihomo, вставляете ссылки и получаете конфиг плюс команду, которая сама положит его на роутер. Меньше вкладок, меньше «а я точно там нажал?». Старые адреса генераторов продолжают работать.
|
||||||
* OpenWrt через Nikki (бета). Генератор собирает профиль Mihomo и команду, которая включит его и перезапустит Nikki. Xray на OpenWrt не предлагаем: Nikki работает только на Mihomo, и мы не даём нажать то, что не заработает.
|
* OpenWrt через Nikki (бета). Генератор собирает профиль Mihomo и команду, которая включит его и перезапустит Nikki. Xray на OpenWrt я не предлагаю: Nikki работает только на Mihomo, а кнопку, которая не заработает, лучше не рисовать.
|
||||||
|
|
||||||
**Исправлено**
|
**Исправлено**
|
||||||
|
|
||||||
* Установщик 3X-UI сверяется по SHA256. Если его кто-то подменит, скрипт посмотрит на него как на незнакомца в чате с «выгодным предложением» и запускать не станет. Файлы теперь берём из тега версии, а не из `main`.
|
* Установщик 3X-UI сверяется по SHA256. Если его кто-то подменит, скрипт посмотрит на него как на незнакомца в чате с «выгодным предложением» и запускать не станет. Файлы я теперь беру из тега версии, а не из `main`.
|
||||||
* Hysteria больше не проверяется по справке, которую сама же принесла. Контрольная сумма записана в скрипте. Проверять человека по его же рекомендательному письму – так себе идея.
|
* Hysteria больше не проверяется по справке, которую сама же принесла. Контрольная сумма записана прямо в скрипте. Проверять человека по его же рекомендательному письму – так себе идея.
|
||||||
* Hysteria2 перестала исполнять почту. Особо изобретательный адрес раньше мог выполниться как команда. Теперь `me@gmail.com;rm -rf` получит вежливый отказ.
|
* Hysteria2 перестала исполнять почту. Особо изобретательный адрес раньше мог выполниться как команда. Теперь `me@gmail.com;rm -rf` получит вежливый отказ.
|
||||||
* Подписка больше не root. Даже если `kit-sub` взломают, ключей от сервера у него нет. Пусть попробует открыть хоть одну дверь.
|
* Подписка больше не root. Даже если `kit-sub` взломают, ключей от сервера у него нет. Пусть попробует открыть хоть одну дверь.
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,31 @@
|
|||||||
|
|
||||||
<img alt="3X-UI KIT" src="manuals/assets/banner.svg" width="820">
|
<img alt="3X-UI KIT" src="manuals/assets/banner.svg" width="820">
|
||||||
|
|
||||||
**Свой VPN-сервер одной командой: 11 протоколов, один порт 443, одна подписка на всё**
|
**Установка 3X-UI одной командой: 11 протоколов, один порт 443, одна подписка на всё**
|
||||||
|
|
||||||
[](manuals/3x-ui.md#протоколы)
|
[](manuals/3x-ui.md#протоколы)
|
||||||
[](https://github.com/MHSanaei/3x-ui)
|
[](https://github.com/MHSanaei/3x-ui)
|
||||||
[](https://github.com/itsnotkubrick/3X-UI_KIT/commits)
|
[](https://github.com/itsnotkubrick/3X-UI_KIT/commits)
|
||||||
|
|
||||||
[Возможности](#возможности) · [Установка](#установка) · [Генератор](#генератор-конфигов-для-роутера) · [Полезное](#полезное) · [Поддержать](#поддержать-проект)
|
[Возможности](#возможности) · [Установка](#установка) · [Команды](#команды-kit) · [Роутер](#vpn-на-роутере) · [Генератор](#генератор-конфигов-для-роутера) · [Полезное](#полезное) · [Поддержать](#поддержать-проект)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **Вышла версия 1.1:** исправления по аудиту безопасности, автообновление только подписанных
|
> **Вышла версия 1.1.1:** `kit check` и `kit fix`, исправления для Happ и скачивания ядра, инструкции для XKeen.
|
||||||
> релизов, выбор маскировки (свой домен). [Что нового](https://github.com/itsnotkubrick/3X-UI_KIT/releases/tag/v1.1) ·
|
> [Что нового](https://github.com/itsnotkubrick/3X-UI_KIT/releases/tag/v1.1.1) ·
|
||||||
> сервер на 1.0 обновляется одной командой:
|
> сервер на 1.1 обновится сам этой ночью, а на 1.0 одной командой:
|
||||||
> ```bash
|
> ```bash
|
||||||
> curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1/scripts/kit.sh -o /usr/local/bin/kit && chmod 755 /usr/local/bin/kit && kit update
|
> curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1.1/scripts/kit.sh -o /usr/local/bin/kit && chmod 755 /usr/local/bin/kit && kit update
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Проект – набор скриптов с открытым кодом.** Он не оказывает услуг связи и не предоставляет доступ
|
||||||
|
> к серверам. Вы ставите его на собственный сервер и сами отвечаете за соблюдение законодательства
|
||||||
|
> своей страны.
|
||||||
|
|
||||||
**3X-UI KIT** превращает чистый VPS в готовый VPN-сервер за несколько минут. Скрипт ставит
|
**3X-UI KIT** превращает чистый VPS в готовый VPN-сервер за несколько минут. Скрипт ставит
|
||||||
официальную панель [3X-UI](https://github.com/MHSanaei/3x-ui), настраивает все популярные
|
официальную панель [3X-UI](https://github.com/MHSanaei/3x-ui), настраивает все популярные
|
||||||
протоколы, сертификат и файрвол и выдаёт одну ссылку-подписку. Её можно вставить в любое
|
протоколы, сертификат и файрвол и выдаёт одну ссылку-подписку. Её можно вставить в любое
|
||||||
@@ -46,13 +51,16 @@
|
|||||||
- 🔒 **Сертификат Let's Encrypt на IP** выпускается и продлевается сам, панель скрыта на
|
- 🔒 **Сертификат Let's Encrypt на IP** выпускается и продлевается сам, панель скрыта на
|
||||||
случайном пути со случайными логином и паролем.
|
случайном пути со случайными логином и паролем.
|
||||||
- ✅ **Проверено настоящими клиентами** – каждый протокол на ядрах Xray, Mihomo и sing-box,
|
- ✅ **Проверено настоящими клиентами** – каждый протокол на ядрах Xray, Mihomo и sing-box,
|
||||||
в том числе с сервером в России: [tests/matrix](tests/matrix/).
|
в том числе через интернет к удалённому серверу: [tests/matrix](tests/matrix/).
|
||||||
|
|
||||||
## Что понадобится
|
## Что понадобится
|
||||||
|
|
||||||
- VPS с **Ubuntu 22.04/24.04** или **Debian 12/13** и доступом root по SSH
|
- VPS с **Ubuntu 22.04/24.04** или **Debian 12/13** и доступом root по SSH
|
||||||
- Свободные порты **443** и **80** – на свежем сервере они свободны
|
- Свободные порты **443** и **80** – на свежем сервере они свободны
|
||||||
|
|
||||||
|
Нужен VPS? Я сам использую **[IS Hosting](https://ishosting.io/affiliate/NTg4MiM4)** и рекомендую его
|
||||||
|
(реферальная ссылка).
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
Подключитесь к серверу по SSH и выполните:
|
Подключитесь к серверу по SSH и выполните:
|
||||||
@@ -71,12 +79,158 @@ bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main
|
|||||||
> Проект создан в образовательных целях. Убедитесь, что ваши действия
|
> Проект создан в образовательных целях. Убедитесь, что ваши действия
|
||||||
> соответствуют законодательству вашей страны.
|
> соответствуют законодательству вашей страны.
|
||||||
|
|
||||||
|
## Команды kit
|
||||||
|
|
||||||
|
После установки на сервере появляется команда `kit`. Подробности – в [инструкции](manuals/3x-ui.md).
|
||||||
|
|
||||||
|
| Команда | Что делает |
|
||||||
|
|---|---|
|
||||||
|
| `kit user add имя [--gb 50] [--days 30] [--devices 3]` | добавить пользователя во все протоколы и показать его подписку с QR-кодом |
|
||||||
|
| `kit user list` | кто сколько израсходовал, до какого числа и когда был в сети |
|
||||||
|
| `kit user link имя [--all]` | показать подписку ещё раз; `--all` добавляет ссылки `vpn://` и `tg://` |
|
||||||
|
| `kit user limit имя [--gb N] [--days N] [--devices N]` | изменить лимиты (`0` – без ограничений) |
|
||||||
|
| `kit user off имя` / `kit user on имя` | временно выключить и включить пользователя |
|
||||||
|
| `kit user del имя` | удалить пользователя |
|
||||||
|
| `kit check` | проверить сервер: службы, сертификат, подписку, сайт маскировки, права на файлы (ничего не меняет) |
|
||||||
|
| `kit fix [--dry-run]` | исправить безопасное: перезапустить службы, вернуть права, включить автообновление |
|
||||||
|
| `kit update [--manual \| --auto]` | обновить сейчас; `--manual` выключает автообновление, `--auto` включает |
|
||||||
|
| `kit backup` | сохранить копию сервера (подключения, ключи, пользователи) |
|
||||||
|
| `kit version` | версии `kit`, панели и ядра, состояние автообновления |
|
||||||
|
|
||||||
|
Для отдельного сервера Hysteria2 есть команда `hy2` (`hy2 add`, `hy2 del`, `hy2 list`, `hy2 link`, `hy2 status`,
|
||||||
|
`hy2 restart`, `hy2 update`, `hy2 uninstall`), подробнее – в [инструкции по Hysteria2](manuals/hysteria2.md).
|
||||||
|
Панелью 3X-UI управляет команда `x-ui`.
|
||||||
|
|
||||||
|
## VPN на роутере
|
||||||
|
|
||||||
|
Через прокси идут только нужные сервисы и выбранные устройства, остальное работает напрямую.
|
||||||
|
Порядок: **1.** установить программу на роутер → **2.** собрать конфиг в [генераторе](#генератор-конфигов-для-роутера) →
|
||||||
|
**3.** вставить команду из генератора.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **🙏 Спасибо автору XKeen.** На роутере работает не моя программа: я только собрал инструкцию
|
||||||
|
> и генератор конфигов, а всю сложную работу сделал **jameszeroX**.
|
||||||
|
>
|
||||||
|
> **[XKeen](https://github.com/jameszeroX/XKeen)** – прокси на Keenetic (Xray и Mihomo, политики для устройств).
|
||||||
|
> [Репозиторий](https://github.com/jameszeroX/XKeen) · [Вики](https://github.com/jameszeroX/XKeen/wiki) · ⭐ [Поставить звезду](https://github.com/jameszeroX/XKeen)
|
||||||
|
>
|
||||||
|
> Вопросы по самой программе задавайте в её репозитории: так автор узнаёт об ошибках. Если что-то не так
|
||||||
|
> с моей инструкцией или генератором, пишите мне.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>📶 Keenetic: XKeen (jameszeroX) · 8 шагов, 20–30 минут</b></summary>
|
||||||
|
|
||||||
|
Нужны роутер **Keenetic** или **Netcraze** с USB-портом, USB-флешка в формате **ext4** и свой сервер с VLESS
|
||||||
|
(например, из [этой инструкции](manuals/3x-ui.md)).
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>1. Компоненты KeeneticOS</b></summary>
|
||||||
|
|
||||||
|
В веб-интерфейсе: **Управление → Общие настройки → Изменить набор компонентов**. Отметьте компоненты
|
||||||
|
с галочками на картинке и установите. Роутер обновится и перезагрузится.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>2. Entware на флешку</b></summary>
|
||||||
|
|
||||||
|
1. Отформатируйте флешку в **ext4** и вставьте её в роутер.
|
||||||
|
2. Откройте её по сети (`\\192.168.x.x\`), создайте папку `install` и положите туда установщик под процессор роутера:
|
||||||
|
[mipsel](https://bin.entware.net/mipselsf-k3.4/installer/mipsel-installer.tar.gz),
|
||||||
|
[mips](https://bin.entware.net/mipssf-k3.4/installer/mips-installer.tar.gz) или
|
||||||
|
[aarch64](https://bin.entware.net/aarch64-k3.10/installer/aarch64-installer.tar.gz).
|
||||||
|
Не знаете, какой нужен? Модель роутера – в характеристиках на сайте Keenetic.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. **Управление → OPKG**: выберите флешку и сохраните. Установка займёт несколько минут.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>3. Подключение по SSH</b></summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@192.168.x.x -p 222
|
||||||
|
```
|
||||||
|
|
||||||
|
Пароль по умолчанию `keenetic`. **Сразу смените его** командой `passwd`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>4. Шифрованный DNS</b></summary>
|
||||||
|
|
||||||
|
Пропишите шифрованные DNS-серверы (DoT или DoH) по
|
||||||
|
[инструкции Keenetic](https://support.keenetic.ru/ultra/kn-1811/ru/31543-dot-and-doh-proxy-servers-for-dns-requests-encryption.html):
|
||||||
|
без этого XKeen работает неправильно.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>5. Токен для KeeneticOS 5.2 и новее</b></summary>
|
||||||
|
|
||||||
|
XKeen нужен токен доступа к роутеру. Создайте его в разделе **Пользователи и доступ**, вставьте в
|
||||||
|
[шаблон xkeen.json](https://github.com/jameszeroX/XKeen/releases/download/2.0.1_Beta/xkeen.json) и положите файл на роутер
|
||||||
|
по пути `/opt/etc/xkeen/xkeen.json`. Подробнее – в [вики XKeen](https://github.com/jameszeroX/XKeen/wiki/Порядок-установки).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>6. Установка XKeen</b></summary>
|
||||||
|
|
||||||
|
Подключитесь по SSH и выполните:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opkg update && opkg upgrade && opkg install curl tar && cd /tmp
|
||||||
|
sh -c "$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Если GitHub недоступен, замените адрес на `https://cdn.jsdelivr.net/gh/jameszeroX/XKeen@main/install.sh`.
|
||||||
|
Установщик спросит ядро (Xray или Mihomo), геобазы и автозагрузку.
|
||||||
|
|
||||||
|
Затем соберите конфиг в [генераторе](#генератор-конфигов-для-роутера) (роутер Keenetic, нужное ядро), вставьте команду в SSH-консоль
|
||||||
|
и нажмите Enter: файлы запишутся, XKeen перезапустится.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>7. Какие устройства через прокси</b></summary>
|
||||||
|
|
||||||
|
В веб-интерфейсе откройте **Приоритеты подключений → Политики доступа в Интернет**, создайте политику
|
||||||
|
с именем **`xkeen`** и перенесите в неё нужные устройства.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Без политики `xkeen` через прокси пойдёт трафик **всех** устройств в сети.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>8. Проверка</b></summary>
|
||||||
|
|
||||||
|
- На роутере: `xkeen -status` – XKeen должен быть запущен.
|
||||||
|
- На устройстве из политики `xkeen` откройте сайт, показывающий IP-адрес: он должен совпасть с адресом вашего сервера,
|
||||||
|
а на остальных устройствах остаться прежним.
|
||||||
|
- Удобнее управлять настройками из браузера через [XKeen UI](https://github.com/zxc-rv/XKeen-UI).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Подробная версия с теми же шагами: [инструкция для Keenetic](manuals/xkeen-keenetic.md).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Генератор конфигов для роутера
|
## Генератор конфигов для роутера
|
||||||
|
|
||||||
Вставьте ссылку на сервер или подписку, выберите роутер и ядро, отметьте нужные сервисы –
|
Вставьте ссылку на сервер или подписку, выберите роутер и ядро, отметьте нужные сервисы –
|
||||||
и получите готовый конфиг и одну команду, которая сама положит его на роутер.
|
и получите готовый конфиг и одну команду, которая сама положит его на роутер.
|
||||||
Всё считается в браузере, ссылки никуда не отправляются.
|
Всё считается в браузере, ссылки никуда не отправляются.
|
||||||
Как поставить XKeen на роутер – в [инструкции для Keenetic](manuals/xkeen-keenetic.md).
|
Как поставить XKeen на роутер – в разделе [VPN на роутере](#vpn-на-роутере).
|
||||||
|
|
||||||
| Роутер | Ядро | Что получится | Генератор |
|
| Роутер | Ядро | Что получится | Генератор |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
|||||||
+29
-11
@@ -30,7 +30,7 @@
|
|||||||
| VLESS WebSocket + TLS | 443/tcp | всех приложениях |
|
| VLESS WebSocket + TLS | 443/tcp | всех приложениях |
|
||||||
| Trojan gRPC + TLS | 443/tcp | всех приложениях |
|
| Trojan gRPC + TLS | 443/tcp | всех приложениях |
|
||||||
| VMess WebSocket + TLS | 443/tcp | всех приложениях |
|
| VMess WebSocket + TLS | 443/tcp | всех приложениях |
|
||||||
| Shadowsocks 2022 | 8388/tcp+udp | всех приложениях (мобильный МТС его блокирует, по Wi-Fi работает) |
|
| Shadowsocks 2022 | 8388/tcp+udp | всех приложениях (некоторые мобильные операторы его не пропускают, по Wi-Fi работает) |
|
||||||
| TUIC v5 | 8444/udp | Hiddify, Clash Verge, FlClash, NekoBox |
|
| TUIC v5 | 8444/udp | Hiddify, Clash Verge, FlClash, NekoBox |
|
||||||
| AmneziaWG | 51821/udp | AmneziaVPN, AmneziaWG, Clash Verge, FlClash |
|
| AmneziaWG | 51821/udp | AmneziaVPN, AmneziaWG, Clash Verge, FlClash |
|
||||||
| AmneziaWG 3.1 | 51822/udp | AmneziaVPN, AmneziaWG, Clash Verge, FlClash |
|
| AmneziaWG 3.1 | 51822/udp | AmneziaVPN, AmneziaWG, Clash Verge, FlClash |
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
доходят до панели, поэтому лимит устройств работает.
|
доходят до панели, поэтому лимит устройств работает.
|
||||||
|
|
||||||
Каждый протокол проверен настоящим подключением на ядрах Xray, Mihomo и sing-box,
|
Каждый протокол проверен настоящим подключением на ядрах Xray, Mihomo и sing-box,
|
||||||
в том числе через интернет к серверу в России – стенд лежит в [tests/matrix](../tests/matrix/).
|
в том числе через интернет к удалённому серверу – стенд лежит в [tests/matrix](../tests/matrix/).
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Обычный WireGuard по умолчанию не ставится: в России его рукопожатие режет DPI,
|
> Обычный WireGuard по умолчанию не ставится: его рукопожатие легко распознаётся сетевым
|
||||||
> а попытки подключения могут привести к блокировке IP сервера. AmneziaWG – тот же WireGuard
|
> оборудованием, из-за чего соединение нестабильно. AmneziaWG – тот же WireGuard с маскировкой –
|
||||||
> с маскировкой – проходит. Если сервер и пользователи за границей, добавьте `wg` в `--protocols`.
|
> работает лучше. Если сервер и пользователи за границей, добавьте `wg` в `--protocols`.
|
||||||
|
|
||||||
## Что понадобится
|
## Что понадобится
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ kit version # версии kit, панели и ядра, включе
|
|||||||
Сервер ставили версией 1.0? В ней команды `kit update` ещё нет, поэтому один раз:
|
Сервер ставили версией 1.0? В ней команды `kit update` ещё нет, поэтому один раз:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1/scripts/kit.sh -o /usr/local/bin/kit && chmod 755 /usr/local/bin/kit && kit update
|
curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1.1/scripts/kit.sh -o /usr/local/bin/kit && chmod 755 /usr/local/bin/kit && kit update
|
||||||
```
|
```
|
||||||
|
|
||||||
Дальше обновления будут приходить сами.
|
Дальше обновления будут приходить сами.
|
||||||
@@ -143,22 +143,40 @@ curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.1/script
|
|||||||
Панель 3X-UI обновляется отдельно, через `x-ui`. Ядро Xray в панели не обновляйте до последней
|
Панель 3X-UI обновляется отдельно, через `x-ui`. Ядро Xray в панели не обновляйте до последней
|
||||||
версии: с Xray 26.7 и новее Hiddify, FlClash и Clash Verge не подключаются к REALITY.
|
версии: с Xray 26.7 и новее Hiddify, FlClash и Clash Verge не подключаются к REALITY.
|
||||||
|
|
||||||
|
## Проверка и починка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kit check # проверить сервер, ничего не меняет
|
||||||
|
kit fix --dry-run # показать, что будет исправлено
|
||||||
|
kit fix # исправить безопасное
|
||||||
|
```
|
||||||
|
|
||||||
|
`kit check` проверяет службы, версии, срок сертификата, что панель не торчит наружу, права на файлы с
|
||||||
|
паролями и ключами, ответ подписки для Happ, Hiddify, v2rayN и Clash, а также что сайт маскировки отвечает по
|
||||||
|
TLS 1.3 и HTTP/2. Вывод можно просто прислать в чат, если нужна помощь.
|
||||||
|
|
||||||
|
`kit fix` лечит только безопасное: перезапускает упавшие службы, возвращает права `600`, включает
|
||||||
|
автообновление, перечитывает сертификат и снимает TLS у встроенной подписки 3X-UI (его включает команда
|
||||||
|
`x-ui cert`, и подписка перестаёт отвечать). Ничего необратимого он не делает.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Проверка сайта маскировки идёт с самого сервера: доступность из вашей сети она не покажет.
|
||||||
|
|
||||||
## Резервная копия
|
## Резервная копия
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kit backup
|
kit backup
|
||||||
```
|
```
|
||||||
|
|
||||||
Сохранит в `/root/kit-backup-ДАТА.tar.gz` всё, что нужно, чтобы поднять тот же сервер в другом
|
Сохранит в `/root/kit-backup-ДАТА.tar.gz` копию сервера: подключения, ключи, пользователей с их
|
||||||
месте: подключения, ключи, пользователей с их лимитами, настройки подписки и nginx. Файл виден
|
лимитами, настройки подписки и nginx. Файл виден только root, и в нём ключи от сервера, поэтому
|
||||||
только root, и в нём ключи от сервера, поэтому храните его как пароль. Скачать к себе:
|
храните его как пароль. Скачать к себе:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scp root@IP:/root/kit-backup-ДАТА.tar.gz .
|
scp root@IP:/root/kit-backup-ДАТА.tar.gz .
|
||||||
```
|
```
|
||||||
|
|
||||||
Восстановление из копии на новом VPS пока в разработке. Сейчас копия нужна как страховка:
|
Храните копию отдельно от сервера.
|
||||||
храните её отдельно от сервера.
|
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
|
|||||||
+20
-22
@@ -1,26 +1,24 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="300" viewBox="0 0 1200 300" role="img" aria-label="3X-UI KIT: свой VPN-сервер, все протоколы, один порт, одна подписка"><title>3X-UI KIT: свой VPN-сервер, все протоколы, один порт, одна подписка</title>
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="300" viewBox="0 0 1200 300" role="img" aria-label="3X-UI KIT: установка сервера одной командой"><title>3X-UI KIT: установка сервера одной командой</title>
|
||||||
<defs><clipPath id="r"><rect width="1200" height="300" rx="20"/></clipPath></defs>
|
<defs><clipPath id="r"><rect width="1200" height="300" rx="20"/></clipPath></defs>
|
||||||
<g clip-path="url(#r)"><rect width="1200" height="300" fill="#1a1411"/><path d="M60 0V300" stroke="#1d1713" stroke-width="2"/><path d="M156 0V300" stroke="#1d1713" stroke-width="2"/><path d="M252 0V300" stroke="#1d1713" stroke-width="2"/><path d="M348 0V300" stroke="#1d1713" stroke-width="2"/><path d="M444 0V300" stroke="#1d1713" stroke-width="2"/><path d="M540 0V300" stroke="#1d1713" stroke-width="2"/><path d="M636 0V300" stroke="#1d1713" stroke-width="2"/><path d="M732 0V300" stroke="#1d1713" stroke-width="2"/><path d="M828 0V300" stroke="#1d1713" stroke-width="2"/><path d="M924 0V300" stroke="#1d1713" stroke-width="2"/><path d="M1020 0V300" stroke="#1d1713" stroke-width="2"/><path d="M1116 0V300" stroke="#1d1713" stroke-width="2"/>
|
<g clip-path="url(#r)"><rect width="1200" height="300" fill="#1a1411"/><path d="M60 0V300" stroke="#1d1713" stroke-width="2"/><path d="M156 0V300" stroke="#1d1713" stroke-width="2"/><path d="M252 0V300" stroke="#1d1713" stroke-width="2"/><path d="M348 0V300" stroke="#1d1713" stroke-width="2"/><path d="M444 0V300" stroke="#1d1713" stroke-width="2"/><path d="M540 0V300" stroke="#1d1713" stroke-width="2"/><path d="M636 0V300" stroke="#1d1713" stroke-width="2"/><path d="M732 0V300" stroke="#1d1713" stroke-width="2"/><path d="M828 0V300" stroke="#1d1713" stroke-width="2"/><path d="M924 0V300" stroke="#1d1713" stroke-width="2"/><path d="M1020 0V300" stroke="#1d1713" stroke-width="2"/><path d="M1116 0V300" stroke="#1d1713" stroke-width="2"/><rect y="268" width="1200" height="32" fill="#120e0c"/></g>
|
||||||
<rect y="262" width="1200" height="38" fill="#120e0c"/></g>
|
|
||||||
<rect x="1.5" y="1.5" width="1197" height="297" rx="19" fill="none" stroke="#15110F" stroke-width="3"/>
|
<rect x="1.5" y="1.5" width="1197" height="297" rx="19" fill="none" stroke="#15110F" stroke-width="3"/>
|
||||||
<g font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="66" letter-spacing="1">
|
<g font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="66" letter-spacing="1">
|
||||||
<text x="80" y="108" fill="#15110F" stroke="#15110F" stroke-width="14" stroke-linejoin="round">3X-UI KIT</text>
|
<text x="80" y="104" fill="#15110F" stroke="#15110F" stroke-width="14" stroke-linejoin="round">3X-UI KIT</text>
|
||||||
<text x="80" y="108" fill="#FBF6EC" style="paint-order:stroke">3X-UI KIT</text></g>
|
<text x="80" y="104" fill="#FBF6EC" style="paint-order:stroke">3X-UI KIT</text></g>
|
||||||
<g font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="42" letter-spacing="1">
|
<g font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="35" letter-spacing="1">
|
||||||
<text x="82" y="164" fill="#8A66C4" stroke="#15110F" stroke-width="10" stroke-linejoin="round" transform="translate(3 4)">СВОЙ VPN ЗА МИНУТЫ</text>
|
<text x="82" y="156" fill="#8A66C4" stroke="#15110F" stroke-width="10" stroke-linejoin="round" transform="translate(3 4)">УСТАНОВКА ОДНОЙ КОМАНДОЙ</text>
|
||||||
<text x="82" y="164" fill="#93E06F" stroke="#15110F" stroke-width="8" stroke-linejoin="round" style="paint-order:stroke">СВОЙ VPN ЗА МИНУТЫ</text></g>
|
<text x="82" y="156" fill="#93E06F" stroke="#15110F" stroke-width="8" stroke-linejoin="round" style="paint-order:stroke">УСТАНОВКА ОДНОЙ КОМАНДОЙ</text></g>
|
||||||
<text x="82" y="204" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-size="19" font-weight="500" fill="#CBBFA9">11 протоколов, один порт 443, одна подписка на все приложения</text>
|
<g><rect x="77" y="190" width="130" height="38" rx="10" fill="#15110F"/><rect x="74" y="186" width="130" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="91" cy="205" r="4.5" fill="#93E06F"/><text x="104" y="211" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">Порт 443</text></g><g><rect x="219" y="190" width="183" height="38" rx="10" fill="#15110F"/><rect x="216" y="186" width="183" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="233" cy="205" r="4.5" fill="#93E06F"/><text x="246" y="211" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">Одна подписка</text></g><g><rect x="414" y="190" width="205" height="38" rx="10" fill="#15110F"/><rect x="411" y="186" width="205" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="428" cy="205" r="4.5" fill="#93E06F"/><text x="441" y="211" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">С доменом и без</text></g><g><rect x="77" y="238" width="141" height="38" rx="10" fill="#15110F"/><rect x="74" y="234" width="141" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="91" cy="253" r="4.5" fill="#93E06F"/><text x="104" y="259" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">kit check</text></g><g><rect x="230" y="238" width="194" height="38" rx="10" fill="#15110F"/><rect x="227" y="234" width="194" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="244" cy="253" r="4.5" fill="#93E06F"/><text x="257" y="259" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">Автообновление</text></g><g><rect x="436" y="238" width="236" height="38" rx="10" fill="#15110F"/><rect x="433" y="234" width="236" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="450" cy="253" r="4.5" fill="#93E06F"/><text x="463" y="259" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="16" fill="#FBF6EC">Keenetic и OpenWrt</text></g>
|
||||||
<g><rect x="77" y="228" width="115" height="38" rx="10" fill="#15110F"/><rect x="74" y="225" width="115" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="90" cy="244" r="4.5" fill="#93E06F"/><text x="102" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">REALITY</text></g><g><rect x="204" y="228" width="135" height="38" rx="10" fill="#15110F"/><rect x="201" y="225" width="135" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="217" cy="244" r="4.5" fill="#93E06F"/><text x="229" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">Hysteria2</text></g><g><rect x="351" y="228" width="135" height="38" rx="10" fill="#15110F"/><rect x="348" y="225" width="135" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="364" cy="244" r="4.5" fill="#93E06F"/><text x="376" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">AmneziaWG</text></g><g><rect x="498" y="228" width="95" height="38" rx="10" fill="#15110F"/><rect x="495" y="225" width="95" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="511" cy="244" r="4.5" fill="#93E06F"/><text x="523" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">XHTTP</text></g><g><rect x="605" y="228" width="84" height="38" rx="10" fill="#15110F"/><rect x="602" y="225" width="84" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="618" cy="244" r="4.5" fill="#93E06F"/><text x="630" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">TUIC</text></g><g><rect x="701" y="228" width="84" height="38" rx="10" fill="#15110F"/><rect x="698" y="225" width="84" height="38" rx="10" fill="#2C231D" stroke="#15110F" stroke-width="3"/><circle cx="714" cy="244" r="4.5" fill="#93E06F"/><text x="726" y="250" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="800" font-size="15" fill="#FBF6EC">:443</text></g>
|
<g transform="rotate(2 940 110)"><rect x="783" y="40" width="360" height="140" rx="14" fill="#15110F"/>
|
||||||
<g transform="rotate(2 940 130)"><rect x="763" y="54" width="360" height="156" rx="14" fill="#15110F"/>
|
<rect x="778" y="34" width="360" height="140" rx="14" fill="#221B17" stroke="#15110F" stroke-width="4"/>
|
||||||
<rect x="758" y="48" width="360" height="156" rx="14" fill="#221B17" stroke="#15110F" stroke-width="4"/>
|
<circle cx="800" cy="54" r="6" fill="#ff5f57"/><circle cx="820" cy="54" r="6" fill="#febc2e"/><circle cx="840" cy="54" r="6" fill="#28c840"/>
|
||||||
<circle cx="780" cy="68" r="6" fill="#ff5f57"/><circle cx="800" cy="68" r="6" fill="#febc2e"/><circle cx="820" cy="68" r="6" fill="#28c840"/>
|
<text x="798" y="92" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" font-size="14" fill="#CBBFA9">$ bash 3x-ui.sh</text>
|
||||||
<text x="778" y="108" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" font-size="14" fill="#CBBFA9">$ bash 3x-ui.sh</text>
|
<text x="798" y="116" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" font-size="13" fill="#CBBFA9">==> Ставлю 3X-UI…</text>
|
||||||
<text x="778" y="136" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="800" font-size="14" fill="#93E06F">Готово! 3X-UI работает:</text>
|
<text x="798" y="142" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="800" font-size="14" fill="#93E06F">Готово! 3X-UI работает.</text>
|
||||||
<text x="778" y="158" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="800" font-size="14" fill="#93E06F">11 протоколов.</text>
|
<text x="798" y="164" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" font-size="13" fill="#CBBFA9">https://IP/sub/…</text></g>
|
||||||
<text x="778" y="186" font-family="'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace" font-weight="700" font-size="13" fill="#CBBFA9">https://IP/sub/…</text></g>
|
<g transform="rotate(-4 1000 222)"><rect x="868" y="206" width="262" height="52" rx="6" fill="#15110F" transform="translate(4 5)"/>
|
||||||
<g transform="rotate(-5 1000 236)"><rect x="880" y="222" width="236" height="46" rx="6" fill="#15110F" transform="translate(4 5)"/>
|
<rect x="868" y="206" width="262" height="52" rx="6" fill="#F2E8D2" stroke="#15110F" stroke-width="3"/>
|
||||||
<rect x="880" y="222" width="236" height="46" rx="6" fill="#F2E8D2" stroke="#15110F" stroke-width="3"/>
|
<circle cx="881" cy="219" r="3.2" fill="#15110F"/><circle cx="1117" cy="219" r="3.2" fill="#15110F"/>
|
||||||
<circle cx="893" cy="235" r="3.2" fill="#15110F"/><circle cx="1103" cy="235" r="3.2" fill="#15110F"/>
|
<text x="999" y="241" text-anchor="middle" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="26" fill="#15110F" letter-spacing=".5">11 ПРОТОКОЛОВ</text></g>
|
||||||
<text x="998" y="253" text-anchor="middle" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-weight="900" font-size="20" fill="#15110F" letter-spacing=".5">БЕЗ ДОМЕНА</text></g>
|
<text x="1176" y="290" text-anchor="end" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-size="13" font-weight="500" fill="#CBBFA9" font-style="italic">it's not Kubrick. it's just a VPN.</text>
|
||||||
<text x="1176" y="288" text-anchor="end" font-family="Rubik,'Arial Black','Helvetica Neue',Arial,system-ui,sans-serif" font-size="13" font-weight="500" fill="#CBBFA9" font-style="italic">it's not Kubrick. it's just a VPN.</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.3 KiB |
+99
-235
@@ -28,13 +28,21 @@ wait_apt_idle() {
|
|||||||
XUI_VERSION="v3.8.5"
|
XUI_VERSION="v3.8.5"
|
||||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||||
XUI_INSTALL_SHA256="4e3fe7fe00ef8e904ce6a0e9c36fd8a0c7179fe5e786f23e31801aee84c6347d"
|
XUI_INSTALL_SHA256="4e3fe7fe00ef8e904ce6a0e9c36fd8a0c7179fe5e786f23e31801aee84c6347d"
|
||||||
KIT_VERSION="1.1"
|
KIT_VERSION="1.1.1"
|
||||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||||
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"
|
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"
|
||||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||||
# Clash Verge, Mihomo в XKeen) не проходят REALITY – проверено 2026-09-25.
|
# Clash Verge, Mihomo в XKeen) не проходят REALITY – проверено 2026-09-25.
|
||||||
# 26.6.27 – последняя версия, с которой работают все клиенты и которую принимает 3X-UI.
|
# 26.6.27 – последняя версия, с которой работают все клиенты и которую принимает 3X-UI.
|
||||||
XRAY_CORE="v26.6.27"
|
XRAY_CORE="v26.6.27"
|
||||||
|
# SHA256 архивов этой версии ядра (из официальных файлов .dgst релиза Xray-core). Запасной путь
|
||||||
|
# через зеркало ставит архив, только если сумма совпала: зеркалу верить не нужно.
|
||||||
|
# При смене XRAY_CORE обновите суммы: tools/release.sh сверяет их с официальными.
|
||||||
|
declare -A XRAY_ZIP_SHA256=(
|
||||||
|
[64]=b3e5902d06d6282fe53cfa2fc426058b9aeaa429b2c812e20887cd47f26d08bf
|
||||||
|
[arm64-v8a]=13a251379bea366c2cf10363ad71e75734193d401f26f518bf0c25e5c8f8c931
|
||||||
|
)
|
||||||
|
XRAY_MIRRORS=("https://github.com" "https://ghfast.top/https://github.com")
|
||||||
XUI_REPO="MHSanaei/3x-ui"
|
XUI_REPO="MHSanaei/3x-ui"
|
||||||
RESULT=/root/3x-ui.txt
|
RESULT=/root/3x-ui.txt
|
||||||
XUI_ENV=/etc/x-ui/install-result.env
|
XUI_ENV=/etc/x-ui/install-result.env
|
||||||
@@ -43,8 +51,8 @@ XUI_ENV=/etc/x-ui/install-result.env
|
|||||||
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)
|
SNI_CANDIDATES=(dl.google.com www.amazon.com www.samsung.com www.yahoo.com)
|
||||||
|
|
||||||
ALL_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
|
ALL_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
|
||||||
# Обычный WireGuard в России режет DPI (проверено 2026-09-27: рукопожатие доходит до сервера,
|
# Обычный WireGuard легко распознаётся сетевым оборудованием и работает нестабильно
|
||||||
# ответ – нет), а его попытки могут привлечь блокировку IP. По умолчанию не ставим.
|
# (проверено 2026-09-27: рукопожатие доходит до сервера, ответ – нет). По умолчанию не ставим.
|
||||||
DEFAULT_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
|
DEFAULT_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
|
||||||
declare -A PORTS=([xhttp]=8443 [ws]=2053 [trojan]=2083 [vmess]=2087 [ss]=8388 [tuic]=8444 [wg]=51820 [awg]=51821 [awg3]=51822 [mtproto]=8445)
|
declare -A PORTS=([xhttp]=8443 [ws]=2053 [trojan]=2083 [vmess]=2087 [ss]=8388 [tuic]=8444 [wg]=51820 [awg]=51821 [awg3]=51822 [mtproto]=8445)
|
||||||
PROTOS=(); CREATED=(); OPEN=()
|
PROTOS=(); CREATED=(); OPEN=()
|
||||||
@@ -55,6 +63,7 @@ SNI2=""; SNI3=""
|
|||||||
# Свой домен (self-steal): REALITY маскируется под сайт на этом же сервере, а не под чужой.
|
# Свой домен (self-steal): REALITY маскируется под сайт на этом же сервере, а не под чужой.
|
||||||
DOMAIN=""
|
DOMAIN=""
|
||||||
DOMAIN_CERT_DIR=/root/cert/domain
|
DOMAIN_CERT_DIR=/root/cert/domain
|
||||||
|
SELF_IP_CERT=no # yes – сертификат на IP самоподписанный (Let's Encrypt отказал, пользователь согласился)
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
|
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
|
||||||
@@ -183,17 +192,38 @@ issue_domain_cert() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Восстановление из копии не должно застревать на сертификате: сервер поднимаем с временным
|
# Let's Encrypt не выдал сертификат на IP, а режим со своим доменом без него не работает: предлагаем
|
||||||
# самоподписанным, а настоящий выпускаем отдельной командой (она же перезагрузит nginx).
|
# самоподписанный сертификат на IP. Ссылки на подключения продолжат работать (отпечаток уходит в
|
||||||
domain_cert_fallback() {
|
# ссылки), а подписка в приложениях может не открыться: им такой сертификат не нравится.
|
||||||
local acme=/root/.acme.sh/acme.sh
|
ip_cert_self_signed_fallback() {
|
||||||
install -d -m 700 "$DOMAIN_CERT_DIR"
|
local san="IP:$HOST"
|
||||||
openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout "$DOMAIN_CERT_DIR/privkey.pem" \
|
[[ $HOST =~ ^[0-9.]+$ ]] || san="DNS:$HOST"
|
||||||
-out "$DOMAIN_CERT_DIR/fullchain.pem" -subj "/CN=$DOMAIN" -addext "subjectAltName=DNS:$DOMAIN" -days 30 2>/dev/null
|
warn "Let's Encrypt не выдал сертификат на IP $HOST. Частые причины:"
|
||||||
chmod 600 "$DOMAIN_CERT_DIR/privkey.pem"
|
{
|
||||||
warn "Сервер поднимается с временным сертификатом для $DOMAIN: по домену заглушка откроется с предупреждением, клиентов это не заденет."
|
echo " – лимит Let's Encrypt: 5 сертификатов в неделю на один IP;"
|
||||||
echo " Когда причина устранена, выпустите настоящий сертификат:"
|
echo " – входящий порт 80 закрыт (у хостера или в файрволе);"
|
||||||
echo " $acme --issue -d $DOMAIN --standalone --server letsencrypt --keylength ec-256 && $acme --install-cert -d $DOMAIN --ecc --fullchain-file $DOMAIN_CERT_DIR/fullchain.pem --key-file $DOMAIN_CERT_DIR/privkey.pem --reloadcmd 'systemctl reload nginx'"
|
echo " – сбой у самого Let's Encrypt (лог: /var/log/3x-ui-install.log)."
|
||||||
|
echo " Можно продолжить с самоподписанным сертификатом: ссылки на подключения работают, браузер покажет"
|
||||||
|
echo " предупреждение, а подписка в приложениях может не открыться."
|
||||||
|
} >&2
|
||||||
|
if [[ $yes == no && -t 1 ]] && { : </dev/tty; } 2>/dev/null; then
|
||||||
|
ask_tty "Продолжить с самоподписанным сертификатом? [Y/n] " || REPLY=""
|
||||||
|
[[ ${REPLY,,} == n* ]] && return 1
|
||||||
|
else
|
||||||
|
say "Продолжаю с самоподписанным сертификатом (без вопросов)."
|
||||||
|
fi
|
||||||
|
install -d -m 700 /root/cert/ip
|
||||||
|
(umask 077; openssl req -x509 -nodes -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout /root/cert/ip/privkey.pem \
|
||||||
|
-out /root/cert/ip/fullchain.pem -subj "/CN=$HOST" -addext "subjectAltName=$san" -days 3650 2>/dev/null)
|
||||||
|
chmod 644 /root/cert/ip/fullchain.pem
|
||||||
|
chmod 600 /root/cert/ip/privkey.pem
|
||||||
|
say "Подключаю самоподписанный сертификат к панели"
|
||||||
|
/usr/local/x-ui/x-ui cert -webCert /root/cert/ip/fullchain.pem -webCertKey /root/cert/ip/privkey.pem >/dev/null 2>&1
|
||||||
|
systemctl restart x-ui
|
||||||
|
API="https://127.0.0.1:$XUI_PANEL_PORT/$XUI_WEB_BASE_PATH/panel/api"
|
||||||
|
wait_panel
|
||||||
|
SELF_IP_CERT=yes
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Куда REALITY отправляет чужих гостей: для своего домена – на nginx этого сервера.
|
# Куда REALITY отправляет чужих гостей: для своего домена – на nginx этого сервера.
|
||||||
@@ -270,7 +300,7 @@ main() {
|
|||||||
die "3X-UI уже установлена другим способом – не трогаю её. Удалите её (x-ui uninstall) или добавьте REALITY в панели вручную."
|
die "3X-UI уже установлена другим способом – не трогаю её. Удалите её (x-ui uninstall) или добавьте REALITY в панели вручную."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local PORT=443 SNI="" PANEL_SSL=auto HOST="" UFW=yes NAME="admin" yes=no protos=all ucert="" ukey="" multi=no restore=""
|
local PORT=443 SNI="" PANEL_SSL=auto HOST="" UFW=yes NAME="admin" yes=no protos=all ucert="" ukey="" multi=no
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--port) PORT=$2; shift 2 ;;
|
--port) PORT=$2; shift 2 ;;
|
||||||
@@ -284,15 +314,12 @@ main() {
|
|||||||
--multi-port) multi=yes; shift ;;
|
--multi-port) multi=yes; shift ;;
|
||||||
--key) ukey=$2; shift 2 ;;
|
--key) ukey=$2; shift 2 ;;
|
||||||
--no-ufw) UFW=no; shift ;;
|
--no-ufw) UFW=no; shift ;;
|
||||||
--restore) restore=$2; shift 2 ;;
|
|
||||||
-y|--yes) yes=yes; shift ;;
|
-y|--yes) yes=yes; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) die "Неизвестный параметр: $1 (см. --help)" ;;
|
*) die "Неизвестный параметр: $1 (см. --help)" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
[[ $PORT =~ ^[0-9]+$ ]] && ((PORT > 0 && PORT < 65536)) || die "Неверный порт: $PORT"
|
||||||
# Восстановление из копии (restore_main) отключено до версии 1.2: его ещё не проверили на настоящем сервере.
|
|
||||||
[[ -z $restore ]] || die "Восстановление из копии появится в версии 1.2. Пока доступна только резервная копия: kit backup."
|
|
||||||
local re_host='^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$'
|
local re_host='^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$'
|
||||||
[[ -z $SNI || $SNI =~ $re_host ]] || die "--sni: нужно имя сайта, например dl.google.com"
|
[[ -z $SNI || $SNI =~ $re_host ]] || die "--sni: нужно имя сайта, например dl.google.com"
|
||||||
[[ -z $DOMAIN || $DOMAIN =~ $re_host ]] || die "--domain: нужно имя вашего домена, например vpn.example.com"
|
[[ -z $DOMAIN || $DOMAIN =~ $re_host ]] || die "--domain: нужно имя вашего домена, например vpn.example.com"
|
||||||
@@ -339,7 +366,7 @@ main() {
|
|||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
wait_apt_idle
|
wait_apt_idle
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
apt-get install -y -qq curl jq openssl qrencode ca-certificates iproute2 ufw socat cron >/dev/null
|
apt-get install -y -qq curl jq openssl qrencode ca-certificates iproute2 ufw socat cron unzip >/dev/null
|
||||||
|
|
||||||
HOST=${HOST:-$(public_ip)}
|
HOST=${HOST:-$(public_ip)}
|
||||||
[[ -n $HOST ]] || die "Не удалось узнать внешний IP. Укажите его: --host 1.2.3.4"
|
[[ -n $HOST ]] || die "Не удалось узнать внешний IP. Укажите его: --host 1.2.3.4"
|
||||||
@@ -399,10 +426,13 @@ main() {
|
|||||||
# Let's Encrypt, сбой). Без него панель осталась бы без TLS, а nginx проксирует её по https:
|
# Let's Encrypt, сбой). Без него панель осталась бы без TLS, а nginx проксирует её по https:
|
||||||
# получился бы сервер, который говорит «Готово», а панель не открывается.
|
# получился бы сервер, который говорит «Готово», а панель не открывается.
|
||||||
if [[ $PANEL_SSL == ip && ! -s /root/cert/ip/fullchain.pem ]]; then
|
if [[ $PANEL_SSL == ip && ! -s /root/cert/ip/fullchain.pem ]]; then
|
||||||
[[ -z $DOMAIN ]] || die "Let's Encrypt не выдал сертификат на IP $HOST, а свой домен без него не работает. Частые причины: порт 80 закрыт у хостера, лимит Let's Encrypt (5 выпусков на один IP за неделю), сбой у Let's Encrypt. Лог: /var/log/3x-ui-install.log"
|
if [[ -n $DOMAIN ]]; then
|
||||||
warn "Let's Encrypt не выдал сертификат на IP $HOST (порт 80 закрыт у хостера, лимит выпусков или сбой). Ставлю без него: панель будет доступна только через SSH-туннель."
|
ip_cert_self_signed_fallback || die "Остановил установку по вашему выбору. Запустите скрипт снова без --domain (панель будет доступна через SSH-туннель) или позже, когда сертификат на IP снова можно будет получить."
|
||||||
PANEL_SSL=none
|
else
|
||||||
TRUSTED=no
|
warn "Let's Encrypt не выдал сертификат на IP $HOST (порт 80 закрыт у хостера, лимит выпусков или сбой). Ставлю без него: панель будет доступна только через SSH-туннель."
|
||||||
|
PANEL_SSL=none
|
||||||
|
TRUSTED=no
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Без сертификата панель и подписки не должны торчать наружу по HTTP.
|
# Без сертификата панель и подписки не должны торчать наружу по HTTP.
|
||||||
@@ -494,6 +524,7 @@ main() {
|
|||||||
echo "${G}${B}Готово! 3X-UI работает: ${#CREATED[@]} протоколов.${N}"
|
echo "${G}${B}Готово! 3X-UI работает: ${#CREATED[@]} протоколов.${N}"
|
||||||
echo "${D}${CREATED[*]}${N}"
|
echo "${D}${CREATED[*]}${N}"
|
||||||
[[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let's Encrypt продлевается сам."
|
[[ -n $DOMAIN ]] && echo "Маскировка: свой домен ${B}$DOMAIN${N}, сертификат Let's Encrypt продлевается сам."
|
||||||
|
[[ $SELF_IP_CERT == yes ]] && echo "${Y}Сертификат на IP самоподписанный:${N} используйте ссылки на отдельные подключения из /root/3x-ui.txt, подписка в приложениях может не открыться."
|
||||||
echo
|
echo
|
||||||
echo "Панель: ${B}$panel_url${N}"
|
echo "Панель: ${B}$panel_url${N}"
|
||||||
echo "Логин: ${B}$XUI_USERNAME${N}"
|
echo "Логин: ${B}$XUI_USERNAME${N}"
|
||||||
@@ -557,6 +588,43 @@ connect_panel() {
|
|||||||
wait_panel
|
wait_panel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Архив ядра: скачиваем по очереди с GitHub и с зеркала и принимаем только тот, чья SHA256 совпала.
|
||||||
|
xray_zip_arch() {
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64 | amd64) echo 64 ;;
|
||||||
|
aarch64 | arm64) echo arm64-v8a ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
xray_fetch_verified() { # каталог; кладёт xray.zip, 0 – сумма совпала
|
||||||
|
local d=$1 arch want got base
|
||||||
|
arch=$(xray_zip_arch) || return 1
|
||||||
|
want=${XRAY_ZIP_SHA256[$arch]}
|
||||||
|
for base in "${XRAY_MIRRORS[@]}"; do
|
||||||
|
curl -fsSL --connect-timeout 10 --max-time 180 --retry 1 -o "$d/xray.zip" \
|
||||||
|
"$base/XTLS/Xray-core/releases/download/$XRAY_CORE/Xray-linux-$arch.zip" 2>/dev/null || continue
|
||||||
|
got=$(sha256sum "$d/xray.zip" | awk '{print $1}')
|
||||||
|
[[ $got == "$want" ]] && return 0
|
||||||
|
warn "Архив ядра Xray с ${base#https://} не прошёл проверку SHA256 – отбрасываю."
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
xray_install_fallback() {
|
||||||
|
local d bin
|
||||||
|
d=$(mktemp -d)
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
wait_apt_idle
|
||||||
|
apt-get install -y -qq unzip >/dev/null || { warn "Не удалось поставить unzip – запасной путь загрузки ядра недоступен."; rm -rf "$d"; return 0; }
|
||||||
|
fi
|
||||||
|
bin=$(ls /usr/local/x-ui/bin/xray-linux-* 2>/dev/null | head -n 1) || true
|
||||||
|
if [[ -n $bin ]] && xray_fetch_verified "$d" && unzip -q -o "$d/xray.zip" xray -d "$d"; then
|
||||||
|
install -m 755 "$d/xray" "$bin.new" && mv -f "$bin.new" "$bin"
|
||||||
|
systemctl restart x-ui
|
||||||
|
wait_panel
|
||||||
|
fi
|
||||||
|
rm -rf "$d"
|
||||||
|
}
|
||||||
|
|
||||||
set_xray_core() {
|
set_xray_core() {
|
||||||
local cur_core
|
local cur_core
|
||||||
cur_core=$(/usr/local/x-ui/bin/xray-linux-* version 2>/dev/null | awk 'NR==1 {print "v" $2}')
|
cur_core=$(/usr/local/x-ui/bin/xray-linux-* version 2>/dev/null | awk 'NR==1 {print "v" $2}')
|
||||||
@@ -568,6 +636,11 @@ set_xray_core() {
|
|||||||
[[ $cur_core == "$XRAY_CORE" ]] && break
|
[[ $cur_core == "$XRAY_CORE" ]] && break
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
if [[ $cur_core != "$XRAY_CORE" ]]; then
|
||||||
|
warn "Панель не смогла скачать ядро с GitHub – пробую запасной путь: зеркало с проверкой SHA256."
|
||||||
|
xray_install_fallback
|
||||||
|
cur_core=$(/usr/local/x-ui/bin/xray-linux-* version 2>/dev/null | awk 'NR==1 {print "v" $2}')
|
||||||
|
fi
|
||||||
[[ $cur_core == "$XRAY_CORE" ]] || warn "Не удалось сменить ядро Xray (сейчас $cur_core). Клиенты на Mihomo и sing-box могут не подключиться."
|
[[ $cur_core == "$XRAY_CORE" ]] || warn "Не удалось сменить ядро Xray (сейчас $cur_core). Клиенты на Mihomo и sing-box могут не подключиться."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -589,6 +662,8 @@ setup_tls_cert() {
|
|||||||
CERT=/root/cert/custom/fullchain.pem; KEY=/root/cert/custom/privkey.pem
|
CERT=/root/cert/custom/fullchain.pem; KEY=/root/cert/custom/privkey.pem
|
||||||
elif [[ $PANEL_SSL == ip && -s /root/cert/ip/fullchain.pem ]]; then
|
elif [[ $PANEL_SSL == ip && -s /root/cert/ip/fullchain.pem ]]; then
|
||||||
CERT=/root/cert/ip/fullchain.pem; KEY=/root/cert/ip/privkey.pem
|
CERT=/root/cert/ip/fullchain.pem; KEY=/root/cert/ip/privkey.pem
|
||||||
|
# Самоподписанный сертификат на IP: отпечаток уходит в ссылки, чтобы клиенты доверяли именно ему.
|
||||||
|
[[ $SELF_IP_CERT == yes ]] && PIN=$(openssl x509 -in "$CERT" -noout -fingerprint -sha256 | cut -d= -f2 | tr -d ':' | tr 'A-F' 'a-f')
|
||||||
else
|
else
|
||||||
# Без Let's Encrypt – свой сертификат, а его отпечаток уходит в ссылки (pcs),
|
# Без Let's Encrypt – свой сертификат, а его отпечаток уходит в ссылки (pcs),
|
||||||
# чтобы клиенты доверяли именно ему.
|
# чтобы клиенты доверяли именно ему.
|
||||||
@@ -1263,224 +1338,13 @@ sub_links() {
|
|||||||
if grep -q '://' <<<"$raw"; then echo "$raw"; else base64 -d <<<"$raw" 2>/dev/null || true; fi
|
if grep -q '://' <<<"$raw"; then echo "$raw"; else base64 -d <<<"$raw" 2>/dev/null || true; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- восстановление из резервной копии (kit backup) ----------
|
|
||||||
|
|
||||||
# Файл настроек из копии можно подключать, только если в нём нет ничего, кроме
|
|
||||||
# ИМЯ=значение без подстановок и команд.
|
|
||||||
safe_env() {
|
|
||||||
[[ -r $1 ]] || die "В копии нет файла ${1##*/}."
|
|
||||||
grep -qvE "^([A-Z][A-Z0-9_]*=([A-Za-z0-9._:/@%+,=-]*|'[^']*'))?$" "$1" && die "В копии подозрительный файл ${1##*/} – не восстанавливаю."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Меняет старый IP на новый в базе панели и файлах kit (только целиком, 1.2.3.4 не заденет 11.2.3.45).
|
|
||||||
replace_host() { # старый новый файлы...
|
|
||||||
python3 - "$@" <<'PY'
|
|
||||||
import re, sqlite3, sys
|
|
||||||
old, new, files = sys.argv[1], sys.argv[2], sys.argv[3:]
|
|
||||||
rx = re.compile(r"(?<![0-9.])" + re.escape(old) + r"(?![0-9])")
|
|
||||||
sub = lambda v: rx.sub(new, v) if isinstance(v, str) else v
|
|
||||||
db = sqlite3.connect("/etc/x-ui/x-ui.db")
|
|
||||||
db.create_function("kit_sub", 1, sub)
|
|
||||||
for table, cols in (("inbounds", ("settings", "stream_settings")), ("settings", ("value",))):
|
|
||||||
for c in cols:
|
|
||||||
db.execute("UPDATE %s SET %s = kit_sub(%s)" % (table, c, c))
|
|
||||||
db.commit(); db.close()
|
|
||||||
for f in files:
|
|
||||||
s = open(f).read()
|
|
||||||
open(f, "w").write(rx.sub(new, s))
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_main() { # файл
|
|
||||||
local file=$1 tmp old_ip=""
|
|
||||||
[[ -f $file ]] || die "Нет файла $file. Сначала скопируйте копию на этот сервер: scp kit-backup-….tar.gz root@IP:"
|
|
||||||
[[ -d /usr/local/x-ui ]] && die "Восстанавливать можно только на чистый сервер, а здесь уже стоит 3X-UI."
|
|
||||||
port_busy 443 tcp && die "Порт 443/tcp занят. Восстанавливать нужно на чистый VPS."
|
|
||||||
|
|
||||||
say "Ставлю пакеты: curl, jq, openssl, qrencode, ufw, python3"
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
wait_apt_idle
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -y -qq curl jq openssl qrencode ca-certificates iproute2 ufw socat cron python3 python3-yaml >/dev/null
|
|
||||||
|
|
||||||
# Сначала проверяем архив целиком: только наши пути, только файлы и папки, без ссылок
|
|
||||||
# и «../». Чужой архив не должен ничего записать мимо.
|
|
||||||
tmp=$(mktemp -d)
|
|
||||||
# shellcheck disable=SC2064 # путь подставляем сразу: при выходе локальной переменной уже нет
|
|
||||||
trap "rm -rf -- '$tmp'" EXIT
|
|
||||||
python3 - "$file" "$tmp" <<'PY' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."
|
|
||||||
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/nginx/kit-stream.conf", "etc/nginx/conf.d/kit.conf", "var/www/kit", "root/cert/self",
|
|
||||||
"root/cert/custom", "root/3x-ui.txt", "kit-backup.env")
|
|
||||||
parents = {"etc", "etc/x-ui", "etc/kit", "etc/kit-sub", "etc/nginx", "etc/nginx/conf.d", "var", "var/www", "root", "root/cert"}
|
|
||||||
with tarfile.open(sys.argv[1], "r:gz") as t:
|
|
||||||
ms = []
|
|
||||||
for m in t.getmembers():
|
|
||||||
n = os.path.normpath(m.name)
|
|
||||||
if n == ".":
|
|
||||||
continue
|
|
||||||
if n.startswith("/") or ".." in n.split("/"):
|
|
||||||
sys.exit("путь " + m.name)
|
|
||||||
if not (m.isfile() or m.isdir()):
|
|
||||||
sys.exit("не файл " + m.name)
|
|
||||||
if not (n in parents and m.isdir()) and not any(n == a or n.startswith(a + "/") for a in allowed):
|
|
||||||
sys.exit("лишний " + m.name)
|
|
||||||
m.name = n
|
|
||||||
m.uid = m.gid = 0
|
|
||||||
m.uname = m.gname = "root"
|
|
||||||
m.mode = 0o700 if m.isdir() else 0o600
|
|
||||||
ms.append(m)
|
|
||||||
names = {m.name for m in ms}
|
|
||||||
for need in ("etc/x-ui/x-ui.db", "etc/x-ui/install-result.env", "kit-backup.env"):
|
|
||||||
if need not in names:
|
|
||||||
sys.exit("нет " + need)
|
|
||||||
if hasattr(tarfile, "data_filter"):
|
|
||||||
t.extractall(sys.argv[2], members=ms, filter="data")
|
|
||||||
else:
|
|
||||||
t.extractall(sys.argv[2], members=ms)
|
|
||||||
PY
|
|
||||||
local f
|
|
||||||
for f in "$tmp/kit-backup.env" "$tmp/etc/x-ui/install-result.env" "$tmp/etc/kit/kit.env"; do if [[ -f $f ]]; then safe_env "$f"; fi; done
|
|
||||||
python3 -c "import sqlite3,sys; c=sqlite3.connect(sys.argv[1]); assert c.execute('PRAGMA integrity_check').fetchone()[0]=='ok'; c.execute('SELECT count(*) FROM inbounds')" \
|
|
||||||
"$tmp/etc/x-ui/x-ui.db" 2>/dev/null || die "База панели в копии повреждена. Ничего не менял."
|
|
||||||
|
|
||||||
local BACKUP_HOST="" BACKUP_SSL="" BACKUP_DATE="" BACKUP_KIT_VERSION=""
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "$tmp/kit-backup.env"
|
|
||||||
[[ $BACKUP_SSL =~ ^(ip|custom|none)$ ]] || die "В копии нет данных о сертификате."
|
|
||||||
PANEL_SSL=$BACKUP_SSL
|
|
||||||
# Домен переезжает вместе с сервером (поменяйте A-запись), IP – нет.
|
|
||||||
if [[ $BACKUP_HOST =~ ^[0-9.]+$ ]]; then
|
|
||||||
HOST=${HOST:-$(public_ip)}
|
|
||||||
[[ -n $HOST ]] || die "Не удалось узнать внешний IP. Укажите его: --host 1.2.3.4"
|
|
||||||
[[ $HOST != "$BACKUP_HOST" ]] && old_ip=$BACKUP_HOST
|
|
||||||
else
|
|
||||||
HOST=$BACKUP_HOST
|
|
||||||
fi
|
|
||||||
# Свой домен (self-steal): берём из подключения REALITY в базе. Сертификат в копию не кладём,
|
|
||||||
# на новом сервере он выпускается заново, а для этого домен уже должен вести на новый IP.
|
|
||||||
DOMAIN=$(python3 - "$tmp/etc/x-ui/x-ui.db" 2>/dev/null <<'PY' || true
|
|
||||||
import json, sqlite3, sys
|
|
||||||
db = sqlite3.connect(sys.argv[1])
|
|
||||||
for (s,) in db.execute("SELECT stream_settings FROM inbounds WHERE remark = 'REALITY'"):
|
|
||||||
try:
|
|
||||||
r = json.loads(s)["realitySettings"]
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
if r.get("target") == "127.0.0.1:10447" and r.get("serverNames"):
|
|
||||||
print(r["serverNames"][0])
|
|
||||||
break
|
|
||||||
PY
|
|
||||||
)
|
|
||||||
[[ $PANEL_SSL == ip || -n $DOMAIN ]] && port_busy 80 tcp && die "Для сертификата нужен свободный порт 80/tcp."
|
|
||||||
if [[ -n $DOMAIN ]]; then
|
|
||||||
[[ $DOMAIN =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$ ]] || die "В копии странное имя домена – не восстанавливаю."
|
|
||||||
domain_points_here "$DOMAIN" || die "Сервер в копии маскируется под домен $DOMAIN. Сначала направьте его A-запись на этот сервер, потом запустите восстановление снова. Ничего не менял."
|
|
||||||
fi
|
|
||||||
# Конфиги nginx и kit-sub из архива не берём, а собираем заново: из копии – только
|
|
||||||
# проверенные значения (путь подписки, порты, режим).
|
|
||||||
SINGLE=no; SUB_PATH=""; SUB_INTERNAL=""; SUB_PORT=""
|
|
||||||
if [[ -f $tmp/etc/kit/kit.env ]]; then
|
|
||||||
SINGLE=$(. "$tmp/etc/kit/kit.env"; echo "${SINGLE:-no}")
|
|
||||||
SUB_PATH=$(. "$tmp/etc/kit/kit.env"; echo "${SUB_PATH:-}")
|
|
||||||
SUB_INTERNAL=$(. "$tmp/etc/kit/kit.env"; echo "${SUB_INTERNAL:-}")
|
|
||||||
[[ $SINGLE =~ ^(yes|no)$ && $SUB_PATH =~ ^/[A-Za-z0-9_-]+/$ && $SUB_INTERNAL =~ ^[0-9]{1,5}$ ]] \
|
|
||||||
|| die "В копии странные настройки kit (kit.env) – не восстанавливаю."
|
|
||||||
fi
|
|
||||||
if [[ -f $tmp/etc/kit-sub/config.json ]]; then
|
|
||||||
SUB_PORT=$(jq -r '.port' "$tmp/etc/kit-sub/config.json" 2>/dev/null || true)
|
|
||||||
[[ $SUB_PORT =~ ^[0-9]{1,5}$ && -n $SUB_PATH ]] || die "В копии странные настройки подписки – не восстанавливаю."
|
|
||||||
fi
|
|
||||||
say "Копия от ${BACKUP_DATE:-?} (kit ${BACKUP_KIT_VERSION:-?}), сервер ${B}$HOST${N}"
|
|
||||||
|
|
||||||
# 3X-UI той же закреплённой версии, потом подменяем её базу на базу из копии:
|
|
||||||
# подключения, ключи REALITY, пользователи и их подписки остаются прежними.
|
|
||||||
install_xui "$(free_port)" "$(rand_str 18)" "$(rand_str 10)" "$(rand_str 20)"
|
|
||||||
systemctl stop x-ui
|
|
||||||
install -m 600 "$tmp/etc/x-ui/x-ui.db" /etc/x-ui/x-ui.db
|
|
||||||
install -m 600 "$tmp/etc/x-ui/install-result.env" "$XUI_ENV"
|
|
||||||
local c
|
|
||||||
for c in self custom; do
|
|
||||||
[[ -d $tmp/root/cert/$c ]] || continue
|
|
||||||
install -d -m 700 "/root/cert/$c"
|
|
||||||
install -m 644 "$tmp/root/cert/$c/fullchain.pem" "/root/cert/$c/fullchain.pem"
|
|
||||||
install -m 600 "$tmp/root/cert/$c/privkey.pem" "/root/cert/$c/privkey.pem"
|
|
||||||
done
|
|
||||||
install -d -m 700 /etc/kit /etc/kit-sub
|
|
||||||
[[ -f $tmp/etc/kit/kit.env ]] && install -m 600 "$tmp/etc/kit/kit.env" /etc/kit/kit.env
|
|
||||||
[[ -f $tmp/root/3x-ui.txt ]] && install -m 600 "$tmp/root/3x-ui.txt" "$RESULT"
|
|
||||||
if [[ -n $old_ip ]]; then
|
|
||||||
say "Меняю адрес сервера в подключениях: $old_ip → $HOST"
|
|
||||||
local files=()
|
|
||||||
for f in "$XUI_ENV" /etc/kit/kit.env "$RESULT"; do if [[ -f $f ]]; then files+=("$f"); fi; done
|
|
||||||
replace_host "$old_ip" "$HOST" "${files[@]}"
|
|
||||||
fi
|
|
||||||
systemctl start x-ui
|
|
||||||
connect_panel
|
|
||||||
set_xray_core
|
|
||||||
|
|
||||||
# Сертификат: Let's Encrypt на новый IP выпустил установщик, свой и самоподписанный – из копии.
|
|
||||||
setup_tls_cert
|
|
||||||
TRUSTED=no
|
|
||||||
[[ $PANEL_SSL == ip || $PANEL_SSL == custom ]] && TRUSTED=yes
|
|
||||||
if [[ -n $SUB_PORT ]]; then
|
|
||||||
install_kit_sub
|
|
||||||
[[ $SINGLE == yes ]] || OPEN+=("$SUB_PORT/tcp")
|
|
||||||
fi
|
|
||||||
# Всё на 443: nginx строит маршруты по подключениям из базы, заглушка – из копии.
|
|
||||||
if [[ $SINGLE == yes ]]; then
|
|
||||||
[[ -n $DOMAIN ]] && { issue_domain_cert || domain_cert_fallback; }
|
|
||||||
install -d -m 755 /var/www/kit
|
|
||||||
[[ -f $tmp/var/www/kit/index.html ]] && install -m 644 "$tmp/var/www/kit/index.html" /var/www/kit/index.html
|
|
||||||
setup_nginx
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f /etc/kit/kit.env ]]; then
|
|
||||||
install_kit_file
|
|
||||||
brand_xui_menu
|
|
||||||
/usr/local/bin/kit update --auto >/dev/null 2>&1 || warn "Автообновление не включилось – включите позже: kit update --auto"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Порты – по подключениям из копии: что слушает не только localhost, то и открываем.
|
|
||||||
if [[ $UFW == yes ]]; then
|
|
||||||
local p
|
|
||||||
while read -r p; do OPEN+=("$p"); done < <(api GET inbounds/list | jq -r '.[] | select(.listen != "127.0.0.1")
|
|
||||||
| if (.protocol | test("^(hysteria|hysteria2|tuic|wireguard|amneziawg)$")) then "\(.port)/udp"
|
|
||||||
elif .protocol == "shadowsocks" then "\(.port)/tcp", "\(.port)/udp" else "\(.port)/tcp" end')
|
|
||||||
[[ $TRUSTED == yes && $SINGLE == no ]] && OPEN+=("$XUI_PANEL_PORT/tcp")
|
|
||||||
[[ $PANEL_SSL == ip || -n $DOMAIN ]] && OPEN+=("80/tcp")
|
|
||||||
setup_ufw
|
|
||||||
fi
|
|
||||||
|
|
||||||
local n_in n_cl
|
|
||||||
n_in=$(api GET inbounds/list | jq length)
|
|
||||||
n_cl=$(api GET inbounds/list | jq '[.[] | (.settings | if type == "string" then fromjson else . end).clients // [] | .[].email] | unique | length')
|
|
||||||
kit_banner
|
|
||||||
echo
|
|
||||||
echo "${G}${B}Готово! Сервер восстановлен из копии: $n_in подключений, $n_cl клиентских записей.${N}"
|
|
||||||
echo "Логин и пароль панели прежние, адрес панели и подписки: ${B}cat $RESULT${N}"
|
|
||||||
echo
|
|
||||||
if [[ -n $old_ip ]]; then
|
|
||||||
warn "IP сервера сменился: $old_ip → $HOST."
|
|
||||||
echo "Старые подписки указывают на старый IP, поэтому клиентам нужно один раз добавить"
|
|
||||||
echo "подписку заново: ${B}kit user list${N}, затем ${B}kit user link имя${N}."
|
|
||||||
echo "${D}Чтобы в следующий раз переезд прошёл незаметно для клиентов, ставьте сервер на домен (--cert, --key, --host).${N}"
|
|
||||||
else
|
|
||||||
echo "Клиентам ничего менять не нужно: ключи, ссылки и подписки те же."
|
|
||||||
fi
|
|
||||||
[[ $PANEL_SSL == custom ]] && echo "Направьте A-запись домена ${B}$HOST${N} на IP этого сервера, если ещё не сделали."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
3X-UI со всеми протоколами одной командой
|
3X-UI со всеми протоколами одной командой
|
||||||
|
|
||||||
--protocols all all (по умолчанию – всё, кроме WireGuard), minimal (только REALITY)
|
--protocols all all (по умолчанию – всё, кроме WireGuard), minimal (только REALITY)
|
||||||
или список через запятую: reality,hy2,xhttp,ws,trojan,vmess,ss,tuic,wg,awg,awg3,mtproto
|
или список через запятую: reality,hy2,xhttp,ws,trojan,vmess,ss,tuic,wg,awg,awg3,mtproto
|
||||||
(обычный WireGuard в России блокируется – включайте его, только если сервер и
|
(обычный WireGuard работает нестабильно – включайте его, только если сервер и
|
||||||
пользователи за границей)
|
пользователи за границей)
|
||||||
--port 443 порт REALITY (TCP) и Hysteria2 (UDP), по умолчанию 443
|
--port 443 порт REALITY (TCP) и Hysteria2 (UDP), по умолчанию 443
|
||||||
--sni сайт чужой сайт для маскировки (по умолчанию подбирается сам)
|
--sni сайт чужой сайт для маскировки (по умолчанию подбирается сам)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ declare -A HY_SHA256=(
|
|||||||
[amd64]=8c7a68a906998b747a0db87586e364f995fbfddb95693ae6e2fdb68a6e920d3e
|
[amd64]=8c7a68a906998b747a0db87586e364f995fbfddb95693ae6e2fdb68a6e920d3e
|
||||||
[arm64]=c8dc653c3ba0a28d29a26b8fa52d2086f27c0927afddce95c09965e7174e78b0
|
[arm64]=c8dc653c3ba0a28d29a26b8fa52d2086f27c0927afddce95c09965e7174e78b0
|
||||||
)
|
)
|
||||||
KIT_VERSION="1.1"
|
KIT_VERSION="1.1.1"
|
||||||
KIT_REPO_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT"
|
KIT_REPO_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT"
|
||||||
# Скрипт берём из тега релиза, а не из меняющейся ветки main.
|
# Скрипт берём из тега релиза, а не из меняющейся ветки main.
|
||||||
SELF_URL="$KIT_REPO_RAW/v$KIT_VERSION/scripts/hysteria2.sh"
|
SELF_URL="$KIT_REPO_RAW/v$KIT_VERSION/scripts/hysteria2.sh"
|
||||||
|
|||||||
+9
-2
@@ -37,8 +37,15 @@ CLASH_UA = re.compile(r"clash|mihomo|flclash|stash|nyanpasu|meta", re.I)
|
|||||||
# тоже могут просить формат Clash (Karing так и делает), но AmneziaWG не умеют.
|
# тоже могут просить формат Clash (Karing так и делает), но AmneziaWG не умеют.
|
||||||
NO_AWG_UA = re.compile(r"karing|hiddify|nekobox|sing-?box|husi|stash|shadowrocket|v2box|streisand|happ|loon|surge|quantumult", re.I)
|
NO_AWG_UA = re.compile(r"karing|hiddify|nekobox|sing-?box|husi|stash|shadowrocket|v2box|streisand|happ|loon|surge|quantumult", re.I)
|
||||||
SUB_ID = re.compile(r"^[A-Za-z0-9_.@-]{1,64}$")
|
SUB_ID = re.compile(r"^[A-Za-z0-9_.@-]{1,64}$")
|
||||||
|
# Заголовки Happ, которые 3X-UI отдаёт в подписке (маршрутизация, баннеры, настройки клиента).
|
||||||
|
HAPP_HEADERS = ("routing", "routing-enable", "announce", "providerid", "new-url", "fallback-url",
|
||||||
|
"hide-settings", "no-limit-enabled", "ping-type", "color-profile", "tun-mode", "tun-type",
|
||||||
|
"exclude-routes", "exclude-apns-enable", "per-app-proxy-mode", "per-app-proxy-list",
|
||||||
|
"notification-subs-expire", "sub-expire", "sub-expire-button-link", "sub-info-text",
|
||||||
|
"sub-info-color", "sub-info-button-text", "sub-info-button-link",
|
||||||
|
"subscription-autoconnect", "subscription-autoconnect-type", "subscription-always-hwid-enable")
|
||||||
PASS_HEADERS = ("content-type", "content-disposition", "profile-title", "profile-update-interval",
|
PASS_HEADERS = ("content-type", "content-disposition", "profile-title", "profile-update-interval",
|
||||||
"profile-web-page-url", "subscription-userinfo", "support-url", "cache-control")
|
"profile-web-page-url", "subscription-userinfo", "support-url", "cache-control") + HAPP_HEADERS
|
||||||
|
|
||||||
with open(CONFIG, encoding="utf-8") as f:
|
with open(CONFIG, encoding="utf-8") as f:
|
||||||
CONF = json.load(f)
|
CONF = json.load(f)
|
||||||
@@ -196,7 +203,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
for k in PASS_HEADERS:
|
for k in PASS_HEADERS:
|
||||||
if k in headers:
|
if k in headers:
|
||||||
v = fix_userinfo(headers[k]) if k == "subscription-userinfo" else headers[k]
|
v = fix_userinfo(headers[k]) if k == "subscription-userinfo" else headers[k]
|
||||||
if v:
|
if v and "\r" not in v and "\n" not in v:
|
||||||
self.send_header(k.title(), v)
|
self.send_header(k.title(), v)
|
||||||
self.send_header("Content-Length", str(len(body)))
|
self.send_header("Content-Length", str(len(body)))
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|||||||
+234
-7
@@ -4,14 +4,14 @@
|
|||||||
#
|
#
|
||||||
# kit user add имя [--gb 50] [--days 30] [--devices 3]
|
# kit user add имя [--gb 50] [--days 30] [--devices 3]
|
||||||
# kit user list | link имя | limit имя [--gb N] [--days N] | off имя | on имя | del имя
|
# kit user list | link имя | limit имя [--gb N] [--days N] | off имя | on имя | del имя
|
||||||
# kit update [--auto | --manual] | kit backup | kit version
|
# kit update [--auto | --manual] | kit backup | kit check | kit fix | kit version
|
||||||
|
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||||
|
|
||||||
KIT_VERSION="1.1"
|
KIT_VERSION="1.1.1"
|
||||||
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"
|
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"
|
||||||
# Файлы новой версии берём из её тега (v1.2 и т.д.), а не из меняющейся ветки main.
|
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||||
kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }
|
kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }
|
||||||
|
|
||||||
XUI_ENV=/etc/x-ui/install-result.env
|
XUI_ENV=/etc/x-ui/install-result.env
|
||||||
@@ -271,7 +271,7 @@ cmd_version() {
|
|||||||
# Что нового в версии $1 – из CHANGELOG.md, без разметки.
|
# Что нового в версии $1 – из CHANGELOG.md, без разметки.
|
||||||
changelog_of() {
|
changelog_of() {
|
||||||
curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" '
|
curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" '
|
||||||
index($0, v) == 1 { on = 1; t = substr($0, length(v) + 1); sub(/^[: ]+/, "", t); if (t != "") print t; next } on && /^## / { exit } on' | sed 's/\*\*//g; s/`//g' | grep -v '^[[:space:]]*$' | head -40 || true
|
index($0, v) == 1 { on = 1; t = substr($0, length(v) + 1); sub(/^[: ]+/, "", t); if (t != "") print t; next } on && /^## / { exit } on' | sed -E 's/ ?Спасибо \[[^]]*\]\([^)]*\)[^.]*\.//g; s/\[([^]]*)\]\([^)]*\)/\1/g; s/\*\*//g; s/`//g' | grep -v '^[[:space:]]*$' | head -40 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Скачивает релиз $1 в каталог $2 и проверяет: подпись SHA256SUMS нашим ключом, версию
|
# Скачивает релиз $1 в каталог $2 и проверяет: подпись SHA256SUMS нашим ключом, версию
|
||||||
@@ -483,9 +483,233 @@ cmd_update() {
|
|||||||
if [[ -n $news ]]; then echo; echo "${B}Что нового в $latest${N}"; echo "$news"; fi
|
if [[ -n $news ]]; then echo; echo "${B}Что нового в $latest${N}"; echo "$news"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------- проверка и починка ----------
|
||||||
|
|
||||||
|
# kit check только читает и показывает, что с сервером. kit fix чинит безопасное: перезапускает
|
||||||
|
# упавшие службы, возвращает права на файлы, включает автообновление, перечитывает сертификат.
|
||||||
|
CHECK_BAD=0; CHECK_WARN=0; CHECK_FIX=(); CHECK_QUIET=no
|
||||||
|
c_ok() { [[ $CHECK_QUIET == yes ]] || printf '%s\n' "${G}✅${N} $*"; }
|
||||||
|
c_info() { [[ $CHECK_QUIET == yes ]] || printf '%s\n' "${D}ℹ $*${N}"; }
|
||||||
|
c_warn() { printf '%s\n' "${Y}⚠${N} $*"; CHECK_WARN=$((CHECK_WARN + 1)); }
|
||||||
|
c_bad() { printf '%s\n' "${R}❌${N} ${*:2}"; CHECK_BAD=$((CHECK_BAD + 1)); [[ -z $1 ]] || CHECK_FIX+=("$1"); } # код-починки сообщение
|
||||||
|
|
||||||
|
# Сайт маскировки отвечает по TLS 1.3 и HTTP/2 (как требует REALITY)? Проверка с самого сервера.
|
||||||
|
sni_alive() { echo | timeout 8 openssl s_client -connect "$1:443" -servername "$1" -tls1_3 -alpn h2 2>/dev/null | grep -q 'ALPN protocol: h2'; }
|
||||||
|
|
||||||
|
check_services() {
|
||||||
|
local pid u
|
||||||
|
if systemctl is-active -q x-ui; then c_ok "служба x-ui работает"; else c_bad svc:x-ui "служба x-ui не работает (journalctl -u x-ui -n 50)"; fi
|
||||||
|
if [[ ${SINGLE:-no} == yes ]]; then
|
||||||
|
if systemctl is-active -q nginx; then c_ok "nginx работает"; else c_bad svc:nginx "nginx не работает (journalctl -u nginx -n 50)"; fi
|
||||||
|
fi
|
||||||
|
if [[ -f /usr/local/lib/kit-sub/kit_sub.py ]]; then
|
||||||
|
if systemctl is-active -q kit-sub; then
|
||||||
|
c_ok "служба kit-sub работает"
|
||||||
|
pid=$(systemctl show -p MainPID --value kit-sub 2>/dev/null || true)
|
||||||
|
u=$(ps -o user= -p "$pid" 2>/dev/null | tr -d ' ' || true)
|
||||||
|
if [[ -n $u && $u != root ]]; then c_ok "kit-sub работает не от root (пользователь $u)"; else c_bad "" "kit-sub работает от root – это небезопасно, обновите kit: kit update --force"; fi
|
||||||
|
else
|
||||||
|
c_bad svc:kit-sub "служба kit-sub не работает (journalctl -u kit-sub -n 50)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if auto_enabled; then c_ok "автообновление включено"
|
||||||
|
elif [[ -f $KIT_MANUAL ]]; then c_info "автообновление выключено вами (включить: kit update --auto)"
|
||||||
|
else c_bad timer "автообновление не включено"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_versions() {
|
||||||
|
local xv; xv=$(xray_version || true)
|
||||||
|
c_info "kit $KIT_VERSION, панель 3X-UI $(/usr/local/x-ui/x-ui -v 2>/dev/null | head -1 || echo '?'), ядро Xray ${xv:-?}"
|
||||||
|
if [[ -n $xv ]] && newer "$xv" v26.6.27; then
|
||||||
|
c_warn "ядро Xray $xv новее проверенного (26.6.27): клиенты на Mihomo и sing-box могут не подключаться к REALITY"
|
||||||
|
fi
|
||||||
|
if [[ -s $KIT_LATEST ]] && newer "$(cat "$KIT_LATEST")" "$KIT_VERSION"; then
|
||||||
|
c_info "вышла версия $(cat "$KIT_LATEST"): kit update"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_cert() {
|
||||||
|
local cert end left
|
||||||
|
cert=$(awk '$1 == "ssl_certificate" {sub(/;$/, "", $2); print $2; exit}' /etc/nginx/conf.d/kit.conf 2>/dev/null || true)
|
||||||
|
[[ -n $cert ]] || cert=$(jq -r '.cert // empty' /etc/kit-sub/config.json 2>/dev/null || true)
|
||||||
|
[[ -n $cert && -f $cert ]] || { c_info "сертификат для проверки не найден (режим без TLS)"; return 0; }
|
||||||
|
if ! openssl x509 -in "$cert" -noout -checkend 0 >/dev/null 2>&1; then
|
||||||
|
c_bad cert "сертификат $cert просрочен"
|
||||||
|
else
|
||||||
|
end=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||||
|
left=$((($(date -d "$end" +%s) - $(date +%s)) / 86400))
|
||||||
|
if openssl x509 -in "$cert" -noout -checkend 86400 >/dev/null 2>&1; then c_ok "сертификат действителен ещё ~$left дн."
|
||||||
|
else c_warn "сертификат истекает меньше чем через сутки: проверьте продление (acme.sh --cron)"; fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_exposure() {
|
||||||
|
local bind f m bad=0
|
||||||
|
# Панель наружу не торчит: за nginx или в режиме «только SSH-туннель».
|
||||||
|
if [[ ${SINGLE:-no} == yes || ! -f /usr/local/lib/kit-sub/kit_sub.py ]]; then
|
||||||
|
bind=$(ss -ltnH "sport = :$XUI_PANEL_PORT" 2>/dev/null | awk '{print $4}' || true)
|
||||||
|
if [[ -z $bind ]]; then c_bad "" "панель не слушает порт $XUI_PANEL_PORT"
|
||||||
|
elif grep -qv '^127\.0\.0\.1:' <<<"$bind"; then c_bad "" "панель слушает наружу ($(tr '\n' ' ' <<<"$bind")): в настройках 3X-UI укажите адрес 127.0.0.1"
|
||||||
|
else c_ok "панель слушает только localhost"; fi
|
||||||
|
fi
|
||||||
|
if [[ ${SINGLE:-no} == yes ]]; then
|
||||||
|
if ss -ltnH 'sport = :443' 2>/dev/null | grep -q .; then c_ok "порт 443 слушается"; else c_bad svc:nginx "порт 443 никто не слушает"; fi
|
||||||
|
if command -v ufw >/dev/null && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||||||
|
if ufw status | grep -Eq '^443/tcp +ALLOW'; then c_ok "ufw пропускает 443/tcp"; else c_bad "" "ufw не пропускает 443/tcp: ufw allow 443/tcp"; fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
for f in /etc/x-ui/install-result.env /etc/kit/kit.env /etc/kit-sub/config.json /root/3x-ui.txt /root/cert/*/privkey.pem; do
|
||||||
|
[[ -f $f ]] || continue
|
||||||
|
m=$(stat -c %a "$f")
|
||||||
|
[[ $m == 600 ]] || { c_bad perms "права на $f: $m (нужно 600)"; bad=1; }
|
||||||
|
done
|
||||||
|
((bad)) || c_ok "файлы с паролями и ключами доступны только root"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_subscription() {
|
||||||
|
local cfg=/etc/kit-sub/config.json port path scheme=http sid ua code size body up sub_cert
|
||||||
|
[[ -f $cfg ]] || { c_info "подписка не установлена (режим без сертификата: используйте отдельные ссылки из /root/3x-ui.txt)"; return 0; }
|
||||||
|
port=$(jq -r '.port' "$cfg"); path=$(jq -r '.path' "$cfg")
|
||||||
|
# Частая поломка: команда «x-ui cert» или меню панели включает TLS у встроенной подписки 3X-UI,
|
||||||
|
# а kit-sub ходит к ней по http – подписка перестаёт отвечать.
|
||||||
|
up=$(jq -r '.upstream // empty' "$cfg")
|
||||||
|
sub_cert=$(api POST setting/all '{}' 2>/dev/null | jq -r '.subCertFile // empty' 2>/dev/null || true)
|
||||||
|
if [[ $up == http://* && -n $sub_cert ]]; then
|
||||||
|
c_bad subtls "у встроенной подписки 3X-UI включён TLS (сертификат $sub_cert), а kit-sub ходит к ней по http – подписка не отвечает"
|
||||||
|
fi
|
||||||
|
body=$(mktemp)
|
||||||
|
[[ -n $(jq -r '.cert // empty' "$cfg") ]] && scheme=https
|
||||||
|
sid=$(clients 2>/dev/null | jq -r '[.[] | .subId // empty | select(. != "")][0] // empty' 2>/dev/null || true)
|
||||||
|
[[ -n $sid ]] || { c_info "пользователей нет, подписку проверить нечем"; return 0; }
|
||||||
|
for ua in "Happ/1.0" "HiddifyNext/2.0" "v2rayN/7.0" "clash-verge/v2"; do
|
||||||
|
: >"$body"
|
||||||
|
code=$(curl -sgk -m 10 -A "$ua" -o "$body" -w '%{http_code}' "$scheme://127.0.0.1:$port$path$sid" 2>/dev/null || true)
|
||||||
|
size=$(wc -c <"$body" | tr -d ' ')
|
||||||
|
if [[ $code == 200 && ${size:-0} -gt 0 ]]; then c_ok "подписка отвечает для $ua ($size байт)"; else c_bad svc:kit-sub "подписка для $ua: HTTP ${code:-нет ответа}"; fi
|
||||||
|
done
|
||||||
|
rm -f "$body"
|
||||||
|
if [[ ${SINGLE:-no} == yes ]]; then
|
||||||
|
code=$(curl -sk -m 10 -A "Happ/1.0" -o /dev/null -w '%{http_code}' "https://127.0.0.1$path$sid" 2>/dev/null || true)
|
||||||
|
if [[ $code == 200 ]]; then c_ok "подписка отвечает и через nginx (443)"; else c_bad svc:nginx "подписка через nginx: HTTP ${code:-нет ответа} (ищите причину выше)"; fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_masking() {
|
||||||
|
local list line sni target host ips
|
||||||
|
list=$(api GET inbounds/list 2>/dev/null) || { c_bad "" "не удалось получить подключения из панели (панель не отвечает?)"; return 0; }
|
||||||
|
while IFS=$'\t' read -r name sni target; do
|
||||||
|
[[ -n $sni ]] || continue
|
||||||
|
if [[ $target == 127.0.0.1:* ]]; then
|
||||||
|
ips=$(getent ahostsv4 "$sni" 2>/dev/null | awk '{print $1}' | sort -u || true); host=${HOST:-}
|
||||||
|
if [[ -n $ips ]] && grep -qx "$host" <<<"$ips"; then c_ok "$name: свой домен $sni указывает на этот сервер"
|
||||||
|
else c_bad "" "$name: свой домен $sni не указывает на этот сервер (A-запись: ${ips:-нет}, нужен $host)"; fi
|
||||||
|
elif sni_alive "$sni"; then
|
||||||
|
c_ok "$name: сайт маскировки $sni отвечает по TLS 1.3 и HTTP/2"
|
||||||
|
else
|
||||||
|
c_bad "" "$name: сайт маскировки $sni не отвечает по TLS 1.3 и HTTP/2 – смените его в панели 3X-UI"
|
||||||
|
fi
|
||||||
|
done < <(jq -r '.[] | select(.protocol == "vless") | (.streamSettings | if type == "string" then fromjson else . end) as $s
|
||||||
|
| select($s.security == "reality") | [.remark, ($s.realitySettings.serverNames[0] // ""), ($s.realitySettings.target // "")] | @tsv' <<<"$list" 2>/dev/null || true)
|
||||||
|
c_info "Проверка идёт с самого сервера: доступность из вашей сети она не покажет."
|
||||||
|
}
|
||||||
|
|
||||||
|
check_system() {
|
||||||
|
local used
|
||||||
|
if command -v timedatectl >/dev/null; then
|
||||||
|
if [[ $(timedatectl show -p NTPSynchronized --value 2>/dev/null || true) == yes ]]; then c_ok "время синхронизировано"
|
||||||
|
else c_warn "время не синхронизировано: при сильном сдвиге TLS и REALITY не работают (kit fix включит синхронизацию)"; CHECK_FIX+=(ntp); fi
|
||||||
|
fi
|
||||||
|
used=$(df -P / | awk 'NR==2 {gsub("%", "", $5); print $5}')
|
||||||
|
if ((${used:-0} >= 95)); then c_warn "диск заполнен на ${used}%"; else c_ok "место на диске: занято ${used:-?}%"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_checks() {
|
||||||
|
CHECK_BAD=0; CHECK_WARN=0; CHECK_FIX=()
|
||||||
|
check_services; check_versions; check_cert; check_exposure; check_subscription; check_masking; check_system
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_check() {
|
||||||
|
echo "${B}kit check${N} – проверка сервера (ничего не меняет)"
|
||||||
|
echo
|
||||||
|
run_checks
|
||||||
|
echo
|
||||||
|
if ((CHECK_BAD == 0)); then
|
||||||
|
echo "${G}${B}Всё в порядке.${N}$( ((CHECK_WARN)) && echo " Предупреждений: $CHECK_WARN." || true)"
|
||||||
|
else
|
||||||
|
echo "${R}${B}Проблем: $CHECK_BAD.${N} Безопасные исправления: ${B}kit fix${N} (сначала можно посмотреть: kit fix --dry-run)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_action() { # код
|
||||||
|
case $1 in
|
||||||
|
svc:x-ui) say "Перезапускаю x-ui"; systemctl restart x-ui; sleep 3 ;;
|
||||||
|
svc:nginx)
|
||||||
|
if nginx -t >/dev/null 2>&1; then say "Перезапускаю nginx"; systemctl restart nginx
|
||||||
|
else warn "Конфиг nginx не проходит проверку (nginx -t) – не трогаю, чтобы не сломать сервер."; fi ;;
|
||||||
|
svc:kit-sub) say "Перезапускаю kit-sub"; systemctl restart kit-sub; sleep 2 ;;
|
||||||
|
subtls)
|
||||||
|
say "Убираю сертификат у встроенной подписки 3X-UI (TLS снимает nginx, kit-sub ходит по http)"
|
||||||
|
local all upd
|
||||||
|
all=$(api POST setting/all '{}')
|
||||||
|
upd=$(jq -c '.subCertFile = "" | .subKeyFile = ""' <<<"$all")
|
||||||
|
api POST setting/update "$upd" >/dev/null
|
||||||
|
systemctl restart x-ui; sleep 4 ;;
|
||||||
|
timer) say "Включаю автообновление"; auto_on ;;
|
||||||
|
perms)
|
||||||
|
say "Возвращаю права 600 на файлы с паролями и ключами"
|
||||||
|
local f
|
||||||
|
for f in /etc/x-ui/install-result.env /etc/kit/kit.env /etc/kit-sub/config.json /root/3x-ui.txt /root/cert/*/privkey.pem; do
|
||||||
|
[[ -f $f ]] && chmod 600 "$f"
|
||||||
|
done ;;
|
||||||
|
cert)
|
||||||
|
if nginx -t >/dev/null 2>&1; then say "Перечитываю сертификат в nginx"; systemctl reload nginx
|
||||||
|
else warn "Конфиг nginx не проходит проверку – сертификат не перечитываю."; fi
|
||||||
|
warn "Если сертификат всё ещё просрочен, запустите продление: ~/.acme.sh/acme.sh --cron" ;;
|
||||||
|
ntp) say "Включаю синхронизацию времени"; timedatectl set-ntp true ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Что чинить сейчас: если найдена причина (TLS у подписки), перезапуски kit-sub и nginx – лишь
|
||||||
|
# следствия: сначала лечим причину, потом проверяем заново.
|
||||||
|
fix_plan() {
|
||||||
|
local c
|
||||||
|
while IFS= read -r c; do
|
||||||
|
if [[ $c == svc:kit-sub || $c == svc:nginx ]] && printf '%s\n' "${CHECK_FIX[@]}" | grep -qx subtls; then continue; fi
|
||||||
|
echo "$c"
|
||||||
|
done < <(printf '%s\n' "${CHECK_FIX[@]}" | sort -u)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_fix() {
|
||||||
|
local dry=no c pass plan
|
||||||
|
case ${1:-} in --dry-run) dry=yes ;; "") ;; *) die "kit fix [--dry-run]" ;; esac
|
||||||
|
echo "${B}kit fix${N} – безопасные исправления (службы, права, автообновление, сертификат, время)"
|
||||||
|
echo
|
||||||
|
CHECK_QUIET=yes; run_checks; CHECK_QUIET=no
|
||||||
|
if ((${#CHECK_FIX[@]} == 0)); then
|
||||||
|
if ((CHECK_BAD == 0)); then echo "${G}Чинить нечего: всё в порядке.${N}"; return 0; fi
|
||||||
|
echo; echo "${Y}Автоматически эти проблемы не исправить, они описаны выше. Подробности: kit check${N}"; return 1
|
||||||
|
fi
|
||||||
|
if [[ $dry == yes ]]; then
|
||||||
|
while IFS= read -r c; do echo " будет сделано: $c"; done < <(fix_plan)
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for pass in 1 2; do
|
||||||
|
plan=$(fix_plan)
|
||||||
|
[[ -n $plan ]] || break
|
||||||
|
while IFS= read -r c; do fix_action "$c"; done <<<"$plan"
|
||||||
|
CHECK_QUIET=yes; run_checks; CHECK_QUIET=no
|
||||||
|
((${#CHECK_FIX[@]})) || break
|
||||||
|
done
|
||||||
|
echo; echo "${B}Повторная проверка${N}"; echo
|
||||||
|
run_checks
|
||||||
|
echo
|
||||||
|
if ((CHECK_BAD == 0)); then echo "${G}${B}Готово: всё в порядке.${N}"; else echo "${R}Осталось проблем: $CHECK_BAD.${N} Часть из них нужно решать вручную (см. выше)."; return 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
# ---------- резервная копия ----------
|
# ---------- резервная копия ----------
|
||||||
|
|
||||||
# Всё, что нужно, чтобы поднять тот же сервер в другом месте: база панели (пользователи,
|
# Что входит в копию: база панели (пользователи,
|
||||||
# ключи, подключения), настройки kit и kit-sub, nginx, сайт-заглушка, свои сертификаты.
|
# ключи, подключения), настройки kit и kit-sub, nginx, сайт-заглушка, свои сертификаты.
|
||||||
# Сертификаты Let's Encrypt (на IP и на свой домен) не берём: на новом сервере он выпускается заново.
|
# Сертификаты Let's Encrypt (на IP и на свой домен) не берём: на новом сервере он выпускается заново.
|
||||||
BACKUP_PATHS=(/etc/x-ui/install-result.env /etc/kit/kit.env /etc/kit-sub/config.json
|
BACKUP_PATHS=(/etc/x-ui/install-result.env /etc/kit/kit.env /etc/kit-sub/config.json
|
||||||
@@ -527,7 +751,6 @@ PY
|
|||||||
echo "В ней ключи и пароли от сервера, храните её как пароль. Скачать к себе (на компьютере):"
|
echo "В ней ключи и пароли от сервера, храните её как пароль. Скачать к себе (на компьютере):"
|
||||||
echo " ${B}scp root@$HOST:$out .${N}"
|
echo " ${B}scp root@$HOST:$out .${N}"
|
||||||
echo
|
echo
|
||||||
echo "${D}Восстановление из копии на новом VPS появится в версии 1.2.${N}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@@ -545,7 +768,9 @@ ${B}kit${N} $KIT_VERSION – управление сервером 3X-UI KIT
|
|||||||
Сервер:
|
Сервер:
|
||||||
kit update обновить kit и подписку kit-sub сейчас (пользователи и ссылки не меняются)
|
kit update обновить kit и подписку kit-sub сейчас (пользователи и ссылки не меняются)
|
||||||
kit update --manual выключить автообновление (--auto – включить обратно)
|
kit update --manual выключить автообновление (--auto – включить обратно)
|
||||||
kit backup резервная копия сервера (восстановление из неё – в версии 1.2)
|
kit backup резервная копия сервера (подключения, ключи, пользователи)
|
||||||
|
kit check проверить сервер: службы, сертификат, подписка, сайт маскировки, права
|
||||||
|
kit fix [--dry-run] исправить безопасное: перезапустить службы, права, автообновление, сертификат
|
||||||
kit version версия kit, панели и ядра
|
kit version версия kit, панели и ядра
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -560,6 +785,8 @@ case "${1:-} ${2:-}" in
|
|||||||
"user del") shift 2; cmd_del "$@" ;;
|
"user del") shift 2; cmd_del "$@" ;;
|
||||||
"update "*) shift; cmd_update "$@" ;;
|
"update "*) shift; cmd_update "$@" ;;
|
||||||
"backup "*) cmd_backup ;;
|
"backup "*) cmd_backup ;;
|
||||||
|
"check "*) cmd_check ;;
|
||||||
|
"fix "*) shift; cmd_fix "$@" ;;
|
||||||
"version "*|"--version "*|"-v "*) cmd_version ;;
|
"version "*|"--version "*|"-v "*) cmd_version ;;
|
||||||
*) usage; update_hint ;;
|
*) usage; update_hint ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Xray, Mihomo, sing-box и официальным клиентом AmneziaWG. П
|
|||||||
| VMess WS + TLS | ✓ | ✓ | ✓ |
|
| VMess WS + TLS | ✓ | ✓ | ✓ |
|
||||||
| Shadowsocks 2022 | ✓ | ✓ | ✓ |
|
| Shadowsocks 2022 | ✓ | ✓ | ✓ |
|
||||||
| TUIC v5 | – | ✓ | ✓ |
|
| TUIC v5 | – | ✓ | ✓ |
|
||||||
| WireGuard | ✓ | ✓ | ✓ | (локально; через DPI в России – ✗ у всех клиентов) |
|
| WireGuard | ✓ | ✓ | ✓ | (локально; через интернет к удалённому серверу – ✗ у всех клиентов) |
|
||||||
| AmneziaWG (классика) | – | ✓ | – |
|
| AmneziaWG (классика) | – | ✓ | – |
|
||||||
| AmneziaWG 3.1 | – | ✓ | – |
|
| AmneziaWG 3.1 | – | ✓ | – |
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ MTProto: `mtg doctor` – все дата-центры Telegram доступны
|
|||||||
Со своим (самоподписанным) сертификатом подписка Clash от 3X-UI не передаёт Mihomo отпечаток –
|
Со своим (самоподписанным) сертификатом подписка Clash от 3X-UI не передаёт Mihomo отпечаток –
|
||||||
TLS-протоколы в ней не работают; ссылки с `pcs` работают.
|
TLS-протоколы в ней не работают; ссылки с `pcs` работают.
|
||||||
|
|
||||||
## Через интернет к серверу в России, 2026-09-27
|
## Через интернет к удалённому серверу, 2026-09-27
|
||||||
|
|
||||||
Сервер Ubuntu 24.04 в России, `scripts/3x-ui.sh` с настоящим сертификатом Let's Encrypt на IP,
|
Удалённый сервер Ubuntu 24.04, `scripts/3x-ui.sh` с настоящим сертификатом Let's Encrypt на IP,
|
||||||
клиенты – на Mac через интернет. Подписка-ссылки и подписка Clash: все протоколы ✓, кроме:
|
клиенты – на Mac через интернет. Подписка-ссылки и подписка Clash: все протоколы ✓, кроме:
|
||||||
|
|
||||||
- **WireGuard** – ✗ у Xray, Mihomo и sing-box: рукопожатие доходит до сервера, ответ режет DPI.
|
- **WireGuard** – ✗ у Xray, Mihomo и sing-box: рукопожатие доходит до сервера, ответ не возвращается.
|
||||||
Поэтому из набора по умолчанию убран.
|
Поэтому из набора по умолчанию убран.
|
||||||
- **MTProto** – сервер хостера не достаёт до Telegram, прокси бесполезен; установщик теперь
|
- **MTProto** – сервер хостера не достаёт до Telegram, прокси бесполезен; установщик теперь
|
||||||
проверяет это и пропускает MTProto.
|
проверяет это и пропускает MTProto.
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// Сервисы: какие наборы правил отправлять через прокси.
|
// Сервисы: какие наборы правил отправлять через прокси.
|
||||||
const SERVICES = [
|
const SERVICES = [
|
||||||
{ id: 'blocked', group: 'Заблокированное', label: 'Заблокированное в России', hint: 'список re:filter – основные заблокированные сайты', on: true,
|
{ id: 'blocked', group: 'Re:filter', label: 'Сайты из списка re:filter', hint: 'список re:filter – основные сайты', on: true,
|
||||||
sets: [['refilter@domain', REFILTER + 'domain-rule.mrs', 'domain'], ['refilter@ipcidr', REFILTER + 'ip-rule.mrs', 'ipcidr']] },
|
sets: [['refilter@domain', REFILTER + 'domain-rule.mrs', 'domain'], ['refilter@ipcidr', REFILTER + 'ip-rule.mrs', 'ipcidr']] },
|
||||||
{ id: 'youtube', label: 'YouTube', on: true, sets: [['youtube@domain', META + 'geosite/youtube.mrs', 'domain']] },
|
{ id: 'youtube', label: 'YouTube', on: true, sets: [['youtube@domain', META + 'geosite/youtube.mrs', 'domain']] },
|
||||||
{ id: 'telegram', label: 'Telegram', on: true,
|
{ id: 'telegram', label: 'Telegram', on: true,
|
||||||
|
|||||||
+2
-2
@@ -83,12 +83,12 @@
|
|||||||
const BASES = {
|
const BASES = {
|
||||||
zkeen: { label: 'ZKeen', hint: 'ставится XKeen по умолчанию, рекомендуется', site: 'zkeen.dat', ip: 'zkeenip.dat' },
|
zkeen: { label: 'ZKeen', hint: 'ставится XKeen по умолчанию, рекомендуется', site: 'zkeen.dat', ip: 'zkeenip.dat' },
|
||||||
v2fly: { label: 'V2Fly', hint: 'нужна для отдельных сервисов и блокировки рекламы', site: 'geosite_v2fly.dat', ip: 'geoip_v2fly.dat' },
|
v2fly: { label: 'V2Fly', hint: 'нужна для отдельных сервисов и блокировки рекламы', site: 'geosite_v2fly.dat', ip: 'geoip_v2fly.dat' },
|
||||||
refilter: { label: 'Re:filter', hint: 'большой список заблокированного', site: 'geosite_refilter.dat', ip: 'geoip_refilter.dat' },
|
refilter: { label: 'Re:filter', hint: 'большой список сайтов', site: 'geosite_refilter.dat', ip: 'geoip_refilter.dat' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Сервис → правила по базам. d: домены, i: IP. Берутся все доступные базы.
|
// Сервис → правила по базам. d: домены, i: IP. Берутся все доступные базы.
|
||||||
const SERVICES = [
|
const SERVICES = [
|
||||||
{ id: 'blocked', label: 'Заблокированное в России', on: true,
|
{ id: 'blocked', label: 'Сайты из списка re:filter', on: true,
|
||||||
zkeen: { d: ['domains', 'other', 'politic'] }, refilter: { d: ['refilter'], i: ['refilter'] } },
|
zkeen: { d: ['domains', 'other', 'politic'] }, refilter: { d: ['refilter'], i: ['refilter'] } },
|
||||||
{ id: 'youtube', label: 'YouTube', on: true, zkeen: { d: ['youtube'], i: ['youtube'] }, v2fly: { d: ['youtube'] } },
|
{ id: 'youtube', label: 'YouTube', on: true, zkeen: { d: ['youtube'], i: ['youtube'] }, v2fly: { d: ['youtube'] } },
|
||||||
{ id: 'telegram', label: 'Telegram', on: true, zkeen: { i: ['telegram'] }, v2fly: { d: ['telegram'] } },
|
{ id: 'telegram', label: 'Telegram', on: true, zkeen: { i: ['telegram'] }, v2fly: { d: ['telegram'] } },
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ got=$(curl -fsSL "https://raw.githubusercontent.com/MHSanaei/3x-ui/$xv/install.s
|
|||||||
[[ $got == "$xs" ]] || die "Установщик 3X-UI $xv изменился: $got вместо $xs. Проверьте, что поменялось, прежде чем выпускать."
|
[[ $got == "$xs" ]] || die "Установщик 3X-UI $xv изменился: $got вместо $xs. Проверьте, что поменялось, прежде чем выпускать."
|
||||||
ok "Установщик 3X-UI $xv совпадает с закреплённым SHA256"
|
ok "Установщик 3X-UI $xv совпадает с закреплённым SHA256"
|
||||||
|
|
||||||
|
# Суммы архивов ядра Xray в скрипте должны совпадать с официальными (.dgst релиза Xray-core).
|
||||||
|
xc=$(sed -n 's/^XRAY_CORE="\(.*\)"/\1/p' scripts/3x-ui.sh)
|
||||||
|
for a in 64 arm64-v8a; do
|
||||||
|
pin=$(sed -n "s/^ \[$a\]=\([0-9a-f]\{64\}\)\$/\1/p" scripts/3x-ui.sh)
|
||||||
|
off=$(curl -fsSL "https://github.com/XTLS/Xray-core/releases/download/$xc/Xray-linux-$a.zip.dgst" | awk '/^SHA2-256=/ {print $2}')
|
||||||
|
[[ -n $pin && $pin == "$off" ]] || die "Сумма ядра Xray $xc ($a) в 3x-ui.sh не совпадает с официальной: «$pin» вместо «$off»."
|
||||||
|
done
|
||||||
|
ok "Суммы архивов ядра Xray $xc совпадают с официальными"
|
||||||
|
|
||||||
# Открытые ключи из kit и hy2 – одинаковые, и среди них есть ключ, которым подписываем.
|
# Открытые ключи из kit и hy2 – одинаковые, и среди них есть ключ, которым подписываем.
|
||||||
signers() { awk '/^KIT_SIGNERS=\(/ {on = 1; next} on && /^\)/ {exit} on && /^ *"/ {gsub(/^ *"|"$/, ""); print}' "$1"; }
|
signers() { awk '/^KIT_SIGNERS=\(/ {on = 1; next} on && /^\)/ {exit} on && /^ *"/ {gsub(/^ *"|"$/, ""); print}' "$1"; }
|
||||||
[[ -n $(signers scripts/kit.sh) ]] || die "В scripts/kit.sh пустой KIT_SIGNERS: впишите туда открытую часть ключа ($key.pub)."
|
[[ -n $(signers scripts/kit.sh) ]] || die "В scripts/kit.sh пустой KIT_SIGNERS: впишите туда открытую часть ключа ($key.pub)."
|
||||||
|
|||||||
Reference in New Issue
Block a user