Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13c86723be | ||
|
|
96823d74f1 | ||
|
|
ca5a57db55 | ||
|
|
628130992d | ||
|
|
445c066791 | ||
|
|
1fdc4945f1 | ||
|
|
cc17b11bd1 | ||
|
|
4ed9c5ceb5 | ||
|
|
0f31fc1739 | ||
|
|
619b2120ce | ||
|
|
b61a3225d8 | ||
|
|
70e2a499c7 | ||
|
|
a70dd14542 | ||
|
|
934077c56f | ||
|
|
ada50ae24e | ||
|
|
2e875aaa2f | ||
|
|
88af9444c0 | ||
|
|
e0ddf8fe25 | ||
|
|
c83fde40e4 | ||
|
|
211053a36b | ||
|
|
80258f1d45 | ||
|
|
7b30cd9b23 | ||
|
|
45b8d1c783 |
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,100 @@
|
||||
# Что нового в сборке форка
|
||||
|
||||
Здесь записи о наших версиях. Файл автора (`CHANGELOG.md`) мы не трогаем — так обновления
|
||||
автора не конфликтуют с нашими записями.
|
||||
|
||||
## v1.2.6: вопросы при установке
|
||||
|
||||
* **README.md переписан целиком:** убрано авторское описание с его командой установки, добавлены
|
||||
актуальная команда, флаги, таблица протоколов, список сайтов-заглушек, команды `kit` и `kit local`,
|
||||
канал обновлений и оговорки. Две картинки — наши (`manuals/assets/fork-banner.svg` и
|
||||
`manuals/assets/script-3x-ui-local.svg`, терминал собран из настоящего вывода скрипта).
|
||||
* Шов в README больше не нужен (`tools/fork_apply.py`): файл наш целиком, как `kit-local.sh`.
|
||||
* Набор по умолчанию — **REALITY + AmneziaWG-3.1** (самый совместимый минимум); ответ «все»
|
||||
ставит полный набор автора. Умолчание задаётся в одном месте (`DEFAULT_PROTOS`).
|
||||
* **Сайт-заглушка переделан на ИТ-тематику:** десять правдоподобных сайтов (дата-аналитика, DevOps,
|
||||
мониторинг, разработка, аудит, ИТ-поддержка, колокейшн, заметки про Linux, бэкапы, документация)
|
||||
вместо авторских «кофеен и пекарен». Ставится случайный, конкретный — `kit local site 7`,
|
||||
список — `kit local site --list`. Команда обновляет и файл для nginx, и копию страницы внутри
|
||||
подключения Hysteria2 (`kit net masq on`): если менять файл вручную, 443/tcp и UDP-порт начнут
|
||||
отвечать разными страницами — это заметно снаружи.
|
||||
* Обёртка спрашивает, **какие протоколы ставить** (нумерованный список, `1,3,11` или «все»,
|
||||
Enter — набор автора): невыбранные протоколы не создаются вообще — ни подключения в панели,
|
||||
ни записей в nginx, ни ссылок в подписке. Список и умолчание взяты из самого установщика
|
||||
(`ALL_PROTOS`/`DEFAULT_PROTOS`), передаётся его же флагом `--protocols`, так что поведение
|
||||
не расходится с авторским. Свой `--protocols` можно задать флагом — тогда вопроса не будет.
|
||||
* Обёртка `scripts/3x-ui-local.sh` теперь спрашивает, а не решает за вас: закрывать ли ответ на ping,
|
||||
включать ли автообновления безопасности, **менять ли порт SSH** (Enter — оставить текущий,
|
||||
число — перевести на него; мусор и занятый порт переспрашивает), менять ли пароль root.
|
||||
Маскировку и домен, как и раньше, спрашивает установщик автора.
|
||||
* Смена порта SSH никогда не закрывает прежний порт сама: сначала добавляется новый, затем скрипт
|
||||
печатает две команды — проверить вход во втором окне и закрыть прежние (`kit local ssh --close-old`).
|
||||
* Запуск без флагов = «одна команда, дальше вопросы». Флаг `--yes` — наоборот, ничего не спрашивать
|
||||
(для автоматизации): ping закрывается, автообновления включаются, порт SSH и пароль не трогаются.
|
||||
* Флаг `--harden` теперь действительно существует (раньше он уходил установщику автора неизвестным
|
||||
параметром и ронял установку): закрыть ping и включить автообновления без вопросов.
|
||||
* `--dry-run` показывает, что именно будет спрошено или сделано по умолчанию.
|
||||
|
||||
## v1.2.5: суммы релиза в формате, который понимает kit
|
||||
|
||||
* Исправлена ошибка нашего релизного конвейера: на Windows `sha256sum` пишет суммы как
|
||||
`hash *файл`, а `kit` на сервере сверяет имя строго (`$2 == "файл"`) — то есть `kit update`
|
||||
отклонил бы релизы v1.2.1–v1.2.4 как «в подписанном списке нет такого файла». Теперь
|
||||
`tools/release.sh` приводит список к POSIX-виду (`hash файл`) **до** подписи.
|
||||
* `scripts/3x-ui-local.sh` принимает оба формата, чтобы не спотыкаться о суммы, собранные на
|
||||
другой машине.
|
||||
* Проверка теперь повторяет логику самого `kit`: подпись `SHA256SUMS` + строгая сверка имени
|
||||
каждого файла из подписанного списка (а не только `sha256sum -c`, который звёздочку прощает).
|
||||
|
||||
## v1.2.4: проверка релиза отдельной командой
|
||||
|
||||
* Флаг `--verify-only`: скачать релиз, проверить подпись `SHA256SUMS` и SHA256 установщика и выйти —
|
||||
можно проверить канал на сервере, ничего не устанавливая.
|
||||
* Порядок шагов изменён: проверка подписи теперь идёт до предполётных проверок и до любых
|
||||
изменений на сервере, чтобы чужой код не запускался раньше проверки.
|
||||
|
||||
## v1.2.3: установка одной командой (`scripts/3x-ui-local.sh`)
|
||||
|
||||
* Один скрипт на чистом VPS: проверка сервера → скачивание нашего релиза → проверка подписи
|
||||
`SHA256SUMS` и SHA256 самого установщика → установщик автора (панель 3X-UI, протоколы,
|
||||
сайт-заглушка, маскировка, сертификат, подписка, ufw) → наши дополнения → проверка.
|
||||
* По умолчанию досыпает: закрыть ответ на ping (правилом ufw), включить обновления безопасности,
|
||||
`kit fix` (время и безопасные починки), в конце `kit check` и сводку `kit local hardening`.
|
||||
Отключается флагами `--no-harden`, `--no-time`, `--no-check`.
|
||||
* Второй порт SSH — только по явному флагу `--ssh-port N`: прежний порт продолжает работать,
|
||||
закрытие — отдельным шагом `kit local ssh --close-old N`. Автоматически доступ не запираем.
|
||||
* Все флаги установщика автора (`--domain`, `--sni`, `--panel-on`, `--host`, `--restore` …)
|
||||
передаются как есть; есть `--dry-run` (план без изменений) и `--help`.
|
||||
* Публичный ключ форка вшит в обёртку — подпись проверяется до запуска чужого кода; при смене
|
||||
ключа обновляется и здесь, и в `kit`.
|
||||
|
||||
## v1.2.2: команды защиты сервера (поведение из StressOzz/vps-setup)
|
||||
|
||||
* Новые команды `kit local`: `ssh <порт>` (добавить порт SSH), `ssh --close-old` (закрыть прежние
|
||||
порты), `ping off|on` (не отвечать на ICMP echo), `passwd [--generate]` (сменить пароль),
|
||||
`upgrades on|off|status` (автообновления безопасности), `hardening` (сводка по защите сервера).
|
||||
* Сделано безопаснее, чем в источнике: конфиг sshd проверяется через `sshd -t` **до** перезапуска,
|
||||
новый порт сначала открывается в ufw, прежний порт продолжает работать до отдельного шага
|
||||
`--close-old`, у правок есть `--dry-run`, а копии изменённых файлов остаются в
|
||||
`/root/kit-local-backup-<дата>/` (не в `/tmp`).
|
||||
* `apt full-upgrade` из источника не выполняется: вместо него включаются обновления безопасности
|
||||
(только security, без самовольной перезагрузки) — это не затрагивает закреплённые версии панели
|
||||
и ядра, которые ставятся не из apt.
|
||||
* Учтён Ubuntu 24.04: если SSH слушает systemd (`ssh.socket`), порт добавляется socket-drop-in'ом,
|
||||
а закрытие старых портов не делается автоматически — там ошибка запирает доступ к серверу.
|
||||
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
||||
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
||||
|
||||
## v1.2.1: сборка форка поверх 3X-UI KIT 1.2
|
||||
|
||||
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
||||
оттуда, а подпись релиза проверяется ключом форка. Авторский ночной апдейт приходит в наш
|
||||
канал, поэтому наши доработки не стираются ночью.
|
||||
* Дополнения форка живут отдельным файлом `/usr/local/lib/kit/kit-local.sh` и подключаются одной
|
||||
строкой: `kit local help | version | channel | seams | masking`.
|
||||
* Наш файл едет в том же подписанном релизе, что и код автора: он входит в `SHA256SUMS`,
|
||||
проверяется по подписи и обновляется вместе с `kit`.
|
||||
* Швы в коде автора описаны в `tools/fork_apply.py` (источник правды), синхронизация с автором и
|
||||
проверки — `tools/fork-sync.sh`.
|
||||
|
||||
Основа — 3X-UI KIT 1.2 (панель 3X-UI v3.9.0, ядро Xray v26.6.27). Что нового у автора — в `CHANGELOG.md`.
|
||||
@@ -0,0 +1,202 @@
|
||||
# Форк 3X-UI KIT (hacker07) — своя сборка и свой канал обновлений
|
||||
|
||||
Коротко: это [3X-UI KIT](https://github.com/itsnotkubrick/3X-UI_KIT) со своими доработками. Наш код
|
||||
живёт рядом с кодом автора и не ломается, когда автор выпускает новую версию, а серверы ставятся и
|
||||
обновляются **из этого репозитория** — релизы подписываются нашим ключом.
|
||||
|
||||
## С чего начать: новый VPS
|
||||
|
||||
1. Если нужен свой домен для маскировки — заранее направьте его A-запись на IP сервера и освободите
|
||||
порт 80 (иначе Let's Encrypt не выпустит сертификат). Без домена этот шаг пропускается.
|
||||
2. Одна команда на сервере (от root) — **дальше скрипт спросит сам**: какие протоколы ставить
|
||||
(Enter — REALITY + AmneziaWG-3.1, «все» — набор автора), какую маскировку и домен,
|
||||
закрывать ли ответ на ping, включать ли автообновления, менять ли порт SSH (Enter — оставить
|
||||
текущий, число — перевести на него), менять ли пароль root:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
||||
```
|
||||
|
||||
Те же ответы можно задать флагами — тогда вопросов не будет:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --protocols reality,hy2,awg,mtproto --domain vpn.example.com
|
||||
```
|
||||
|
||||
3. Скрипт делает всё сам: проверяет сервер → скачивает наш релиз и **проверяет подпись `SHA256SUMS`
|
||||
и SHA256 установщика** → запускает установщик автора (панель 3X-UI, **только выбранные протоколы**,
|
||||
сайт-заглушка, маскировка, сертификат, подписка, ufw) → наши вопросы и правки (ping,
|
||||
автообновления, второй порт SSH, пароль root) → `kit fix` → в конце показывает `kit check` и сводку
|
||||
`kit local hardening`.
|
||||
4. Забрать ссылки и подписку: `kit user link admin` (или адрес подписки из вывода установки).
|
||||
|
||||
Сомневаетесь в канале — сначала ничего не меняющие режимы:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --verify-only # только проверка подписи релиза
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run --domain vpn.example.com # план установки
|
||||
```
|
||||
|
||||
### Флаги установки
|
||||
|
||||
| Флаг | Что делает |
|
||||
|---|---|
|
||||
| `--protocols список` | какие протоколы ставить: `all`, `minimal` или через запятую — `reality,reality2,hy2,xhttp,ws,trojan,vmess,ss,tuic,wg,awg,awg3,mtproto`. Невыбранные не создаются вообще. Без флага — вопрос, по умолчанию предлагаются **REALITY + AmneziaWG-3.1** (ответ «все» — набор автора) |
|
||||
| `--site N\|random\|off` | сайт-заглушка: конкретный из нашего набора (10 ИТ-сайтов), случайный (по умолчанию) или не трогать авторскую страницу (то же — `--no-site`) |
|
||||
| `--yes` | не задавать вопросы (ping закрывается, автообновления включаются, порт SSH и пароль не трогаются) |
|
||||
| `--harden` | закрыть ответ на ping и включить автообновления без вопросов |
|
||||
| `--domain домен` | свой домен для маскировки: сайт и REALITY маскируются под него (A-запись на сервер, свободный 80) |
|
||||
| `--sni сайт.com` | маскировка под чужой сайт вместо домена |
|
||||
| `--panel-on domain\|ip` | где открывать панель и подписку |
|
||||
| `--ssh-port N` | перевести SSH на порт N (число 1024–65535). Прежний порт сам не закрывается: сначала проверьте вход во втором окне, потом `kit local ssh --close-old N` |
|
||||
| `--no-harden` | не закрывать ответ на ping и не включать автообновления безопасности |
|
||||
| `--no-time` / `--no-check` | не запускать `kit fix` / `kit check` |
|
||||
| `--verify-only` / `--dry-run` | проверить релиз / показать план и ничего не менять |
|
||||
| остальные | передаются установщику автора как есть: `--host`, `--user`, `--port`, `--protocols`, `--restore`, `--multi-port`, `--cert`/`--key`, `--panel-ssl` |
|
||||
|
||||
Чего скрипт **не** делает (сознательно): `apt full-upgrade` (тянет систему вперёд и может сломать
|
||||
закреплённые панель 3X-UI 3.9.0 и ядро Xray 26.6.27 — вместо него обновления безопасности),
|
||||
автоматическую перезагрузку и смену пароля root (в однострочник пароль не подставляем, он попал бы
|
||||
в список процессов — для этого есть `kit local passwd`).
|
||||
|
||||
### Как проверить, что установка удалась
|
||||
|
||||
```bash
|
||||
kit check # без ❌: службы, порт 443 (nginx), подписка, маскировка, сертификат
|
||||
kit local hardening # сводка: порты SSH, ответ на ping: deny, ufw активен, автообновления: включены
|
||||
kit local channel # источник — этот репозиторий, ключей в KIT_SIGNERS: 1 (наш)
|
||||
kit local version # версия сборки: 1.2.5 или новее
|
||||
ping <IP сервера> # с домашней машины — таймаут: ответ на ping закрыт, это ожидаемо
|
||||
kit user link admin # ссылка подписки; откройте её в браузере
|
||||
```
|
||||
|
||||
Если ставили с доменом, в `kit check` должны быть строки «свой домен … указывает на этот сервер» и
|
||||
«сайт маскировки … отвечает по TLS 1.3 и HTTP/2»; предупреждения про известный сайт на чужом IP
|
||||
быть не должно (это касалось бы вспомогательного XHTTP, у REALITY должен быть ваш домен).
|
||||
|
||||
## Команды форка (`kit local`)
|
||||
|
||||
```bash
|
||||
kit local help # справка по всем командам форка
|
||||
kit local version # версия, источник обновлений, дата сборки нашего файла
|
||||
kit local channel # откуда берутся обновления и чем проверяется подпись
|
||||
kit local seams # какие швы форка сейчас в установленном /usr/local/bin/kit
|
||||
kit local masking # состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening # сводка по защите сервера
|
||||
```
|
||||
|
||||
Защита сервера (поведение взято из проекта `StressOzz/vps-setup`, реализация своя и безопаснее):
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `kit local site` | поставить случайный сайт-заглушку из нашего набора (10 ИТ-сайтов) и синхронизировать ответ Hysteria2 |
|
||||
| `kit local site --list` / `kit local site 7` / `--show` | список сайтов с номерами / поставить конкретный / какой стоит сейчас |
|
||||
| `kit local ssh 2222` | добавить порт SSH (прежние продолжают работать; порт сначала открывается в ufw, конфиг проверяется через `sshd -t`) |
|
||||
| `kit local ssh --status` | какие порты SSH слушаются сейчас |
|
||||
| `kit local ssh --close-old 2222` | закрыть прежние порты после того, как новый проверен из второго окна |
|
||||
| `kit local ping off\|on\|status` | ответ на ICMP echo: закрыть/вернуть/показать (правилом ufw, видно в `ufw status`) |
|
||||
| `kit local passwd [юзер]` | сменить пароль (спросит дважды, в историю команд не попадёт) |
|
||||
| `kit local passwd --generate` | сгенерировать пароль и показать один раз |
|
||||
| `kit local upgrades on\|off\|status` | автообновления безопасности (только security, без перезагрузки) |
|
||||
|
||||
К любой правке можно добавить `--dry-run` — покажет план и ничего не изменит. Копии изменённых
|
||||
файлов складываются в `/root/kit-local-backup-<дата>/` (не в `/tmp`).
|
||||
|
||||
Что `ping off` затрагивает: только ICMP echo. Подписка (HTTPS), панель, REALITY/XHTTP (TCP 443),
|
||||
Hysteria2 (UDP 443) работают как работали; исходящий ping с самого сервера тоже остаётся. Если
|
||||
сервер есть в вашем мониторинге — пробу до него делайте по HTTPS/TCP, а не по ICMP.
|
||||
|
||||
## Что где лежит
|
||||
|
||||
| Что | Где | Правит |
|
||||
|---|---|---|
|
||||
| Наши доработки (команды, хуки) | `scripts/kit-local.sh` | только мы |
|
||||
| Установка одной командой (обёртка) | `scripts/3x-ui-local.sh` | только мы |
|
||||
| Описание «швов» в коде автора | `tools/fork_apply.py` | только мы |
|
||||
| Синхронизация с автором и проверки | `tools/fork-sync.sh` | только мы |
|
||||
| Записи о наших версиях | `FORK-CHANGELOG.md` | только мы |
|
||||
| `README.md` | целиком, включая картинки в `manuals/assets/` | только мы |
|
||||
| Файлы автора (`scripts/kit.sh`, `scripts/3x-ui.sh`, …) | как есть | только автор |
|
||||
|
||||
Ключевая идея: в файлах автора мы правим только «швы» — несколько констант, одну строку подключения
|
||||
и нормализацию сумм в релизном скрипте. Всё, что мы дописываем по существу, лежит в наших файлах.
|
||||
Швы описаны в одном месте (`tools/fork_apply.py`) и накатываются только на чистое дерево автора;
|
||||
если автор изменил нужную строку — инструмент останавливается и говорит об этом, а не делает
|
||||
полу-патч.
|
||||
|
||||
## Ветки и релизы
|
||||
|
||||
- `upstream` — точная копия ветки автора (`main`), только вперёд.
|
||||
- `dev` — наша сборка: `upstream` + швы + наши файлы; из неё делаются релизы.
|
||||
- `main` — то же состояние плюс документация после релиза (по нему считается «последняя версия»).
|
||||
- релизы — теги `v<номера>`; номер = `<версия автора>.<наш уровень>` (автор 1.2 → наш 1.2.1; после
|
||||
релиза автора 1.3 → 1.3.1). Номер обязан строго расти: `kit update` не принимает откат назад.
|
||||
|
||||
## Обновление сервера и канал
|
||||
|
||||
```bash
|
||||
kit update --force && kit check # тянет наш релиз из этого репозитория, проверяет нашу подпись
|
||||
```
|
||||
|
||||
Источник файлов — `KIT_RAW` / `kit_ref_raw()` в `scripts/kit.sh` (наш Gitea и наш тег), подпись
|
||||
проверяется ключом из `KIT_SIGNERS` (там только ключ форка). Ночной `kit-update.timer` (03:00) делает
|
||||
то же автоматически — поэтому сервер и должен смотреть в этот репозиторий: иначе наши доработки
|
||||
исчезнут в первую же ночь.
|
||||
|
||||
Вернуться на канал автора: скачать `scripts/kit.sh` из релиза автора с GitHub в `/usr/local/bin/kit`,
|
||||
затем `kit update --force`.
|
||||
|
||||
## Обновление от автора (с рабочей станции)
|
||||
|
||||
```bash
|
||||
cd H:/My_Proj/Fork_3X-UI_KIT
|
||||
bash tools/fork-sync.sh # подтянуть автора, перенести наши правки, прогнать проверки
|
||||
# при конфликте — разобрать вручную: инструмент сам ничего не публикует
|
||||
|
||||
bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key # SHA256SUMS + подпись нашим ключом
|
||||
git add -A && git commit -m "Релиз v$(cat VERSION): …" && git tag v$(cat VERSION)
|
||||
git push origin dev v$(cat VERSION) upstream
|
||||
```
|
||||
|
||||
## Подпись релизов
|
||||
|
||||
- Открытая часть ключа — `tools/fork-signer.pub` и в `KIT_SIGNERS` (в `scripts/kit.sh`); она же
|
||||
вшита в `scripts/3x-ui-local.sh`, чтобы проверять подпись до запуска установщика.
|
||||
- Закрытая часть — только на рабочей станции владельца (`~/.ssh/3x-ui-kit-fork/release_key`).
|
||||
В репозиторий, CI и на серверы не попадает: кто получит ключ — получит root на всех серверах,
|
||||
которые обновляются из этого репозитория. Пароль на ключ пока не установлен
|
||||
(`ssh-keygen -p -f ~/.ssh/3x-ui-kit-fork/release_key`).
|
||||
- Смена ключа: новый релиз подписывается **старым** ключом, иначе серверы его не примут.
|
||||
|
||||
## Грабли, о которых стоит помнить
|
||||
|
||||
1. **Ночное автообновление** (`kit-update.timer`, 03:00) перезаписывает `/usr/local/bin/kit` тем, что
|
||||
лежит в настроенном репозитории. Канал и наши правки — один шаг, а не два.
|
||||
2. **Переводы строк.** Релиз подписывается по суммам рабочей копии, а сервер сверяет их с файлами из
|
||||
Gitea: при `core.autocrlf=true` суммы не сойдутся (CRLF против LF). В клоне должно быть
|
||||
`git config core.autocrlf false` (и `.gitattributes`), проверка есть в `tools/fork-sync.sh`.
|
||||
3. **Наш файл обязан быть в двух списках** — `FILES=(…)` в `tools/release.sh` и список загрузки
|
||||
`fetch_release()` в `scripts/kit.sh`, иначе после обновления `kit-local.sh` останется старым.
|
||||
4. **Панель 3X-UI ставится с GitHub** по закреплённой версии и SHA256 — это шаг автора; для установки
|
||||
с нуля серверу нужен доступ к GitHub.
|
||||
5. **Формат контрольных сумм.** `kit` сверяет имя файла в `SHA256SUMS` строго (`$2 == "путь"`).
|
||||
На Windows `sha256sum` пишет `hash *путь`, и такие релизы `kit update` отклоняет («в подписанном
|
||||
списке нет такого файла»), хотя `sha256sum -c` их принимает. Поэтому `tools/release.sh` приводит
|
||||
список к POSIX-виду **до** подписи, а релизы **v1.2.1–v1.2.4 ставить нельзя** — только v1.2.5+.
|
||||
Проверять релиз надо логикой самого `kit`, а не одной `sha256sum -c`.
|
||||
6. **Сборка релиза обращается к GitHub** (сверка закреплённых версий панели и ядра). Из домашней сети
|
||||
TLS до GitHub иногда рвётся (`schannel: failed to receive handshake`) — просто повторить запуск.
|
||||
7. **`README.md` — наш файл целиком.** Авторское описание убрано: иначе человек, открывший
|
||||
репозиторий, читает команду установки **авторской** сборки, ставит по ней и оказывается на
|
||||
авторском канале обновлений — ночное автообновление вернёт его сборку. При обновлении из
|
||||
авторского проекта README конфликтует: берём нашу версию (`git checkout --ours README.md`),
|
||||
авторские правки в нём нас не интересуют.
|
||||
|
||||
## Что проверено на сегодня
|
||||
|
||||
- Релиз `v1.2.5`: подпись сходится с ключом из опубликованного `kit.sh`, строгая сверка сумм
|
||||
`$2 == путь` для `kit.sh`, `kit-sub.py`, `kit-local.sh` — как это делает `kit` на сервере.
|
||||
- Публичный однострочник в режиме `--verify-only` отвечает «подпись сошлась, SHA256 установщика
|
||||
совпал» — проверено снаружи, из домашней сети.
|
||||
- Установка «в бою» на живом сервере пока не прогонялась: сначала её стоит выполнить на новом VPS,
|
||||
посмотрев `--verify-only` и `--dry-run`.
|
||||
@@ -1,311 +1,196 @@
|
||||
<div align="center">
|
||||
|
||||
<img alt="3X-UI KIT" src="manuals/assets/banner.svg" width="820">
|
||||
<img alt="3X-UI autoVPS" src="manuals/assets/fork-banner.svg" width="860">
|
||||
|
||||
**Установка 3X-UI одной командой: 11 протоколов, один порт 443, одна подписка на всё**
|
||||
**Сервер одной командой: панель 3X-UI, VPN-протоколы за одним портом 443, одна подписка на все устройства**
|
||||
|
||||
[](https://github.com/itsnotkubrick/3X-UI_KIT/releases/latest)
|
||||
[](manuals/3x-ui.md#протоколы)
|
||||
[](https://github.com/MHSanaei/3x-ui)
|
||||
[](https://github.com/itsnotkubrick/3X-UI_KIT/stargazers)
|
||||
[](https://github.com/itsnotkubrick/3X-UI_KIT/commits)
|
||||
Сборка со **своим каналом обновлений**: сервер ставится и обновляется из этого репозитория, релизы
|
||||
подписаны, ночное автообновление не вернёт чужую версию. В основе — открытый проект 3X-UI KIT.
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
## Что ставится
|
||||
|
||||
> [!WARNING]
|
||||
> **Проект – набор скриптов с открытым кодом.** Он не оказывает услуг связи и не предоставляет доступ
|
||||
> к серверам. Вы ставите его на собственный сервер и сами отвечаете за соблюдение законодательства
|
||||
> своей страны. Проект создан в образовательных целях.
|
||||
|
||||
> [!TIP]
|
||||
> **Вышла версия 1.2:** панель 3X-UI v3.9.0, меню `kit`, раздельная маршрутизация и `--restore`.
|
||||
> [Что нового](https://github.com/itsnotkubrick/3X-UI_KIT/releases/tag/v1.2) ·
|
||||
> сервер на 1.1.x обновится сам этой ночью, а на 1.0 одной командой:
|
||||
> ```bash
|
||||
> curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v1.2/scripts/kit.sh -o /usr/local/bin/kit && chmod 755 /usr/local/bin/kit && kit update
|
||||
> ```
|
||||
|
||||
**3X-UI KIT** превращает чистый VPS в готовый VPN-сервер за несколько минут. Скрипт ставит
|
||||
официальную панель [3X-UI](https://github.com/MHSanaei/3x-ui), настраивает все популярные
|
||||
протоколы, сертификат и файрвол и выдаёт одну ссылку-подписку. Её можно вставить в любое
|
||||
приложение – оно само получит подходящие ему протоколы. Домен не нужен.
|
||||
|
||||
<div align="center">
|
||||
<img alt="Конец установки 3X-UI KIT" src="manuals/assets/script-3x-ui.svg" width="760">
|
||||
</div>
|
||||
|
||||
## Возможности
|
||||
|
||||
- 🧩 **11 протоколов на одном порту 443.** Панель, подписка и все TCP-протоколы спрятаны за одним портом,
|
||||
а на случайный заход сервер показывает обычный сайт. Протоколы: VLESS REALITY, VLESS XHTTP + REALITY,
|
||||
VLESS WebSocket + TLS, Trojan gRPC + TLS, VMess WebSocket + TLS, Shadowsocks 2022, Hysteria2, TUIC v5,
|
||||
AmneziaWG, AmneziaWG 3.1, MTProto (прокси для Telegram).
|
||||
- 🔗 **Одна подписка на все приложения.** Hiddify, Happ, v2rayN, Karing, Clash Verge и FlClash
|
||||
получают свой формат и только те протоколы, которые распознают.
|
||||
- 🧭 **Раздельная маршрутизация.** Через VPN идут только зарубежные сервисы, остальное – напрямую.
|
||||
Правила приезжают вместе с подпиской, DNS не течёт.
|
||||
- 🥷 **Маскировка.** Со своим доменом на сервере стоит ваш сайт, без него сайт подбирается среди соседей
|
||||
по подсети. REALITY работает с `xtls-rprx-vision`.
|
||||
- 👥 **Пользователи во все протоколы сразу.** Один пользователь – одна подписка, общий лимит трафика,
|
||||
срок и число устройств. Меню `kit` работает как у x-ui: выбрали цифру – готово.
|
||||
- 💾 **Резервная копия и переезд на новый сервер одной командой.** Копия сохраняет подключения, ключи
|
||||
и пользователей, а на чистом VPS (даже с другим IP) сервер поднимается из неё за несколько минут.
|
||||
- 🔄 **Обновляется само, но только подписанными релизами.**
|
||||
- 🩺 **Проверка и починка сервера** одной командой.
|
||||
|
||||
## Что понадобится
|
||||
|
||||
- VPS с **Ubuntu 22.04/24.04** или **Debian 12/13** и доступом root по SSH
|
||||
- Свободные порты **443** и **80** – на свежем сервере они свободны
|
||||
|
||||
Нужен VPS? Я сам использую **[IS Hosting](https://ishosting.io/affiliate/NTg4MiM4)** и рекомендую его
|
||||
(реферальная ссылка).
|
||||
| Что | Зачем |
|
||||
|---|---|
|
||||
| Панель **3X-UI** + ядро **Xray** | управление подключениями и пользователями |
|
||||
| **nginx** на порту 443 | разводит подключения по именам и путям, отдаёт сайт-заглушку |
|
||||
| VPN-подключения | REALITY, Hysteria2, AmneziaWG, XHTTP, VLESS-WS, Trojan-gRPC, VMess-WS, Shadowsocks, TUIC, MTProto, WireGuard — какие выберете |
|
||||
| **Подписка** (`kit-sub`) | одна ссылка на все устройства: Happ, Hiddify, v2rayN, Clash/Mihomo |
|
||||
| **Сайт-заглушка** | на 443 и в ответе Hysteria2 — правдоподобный ИТ-сайт из нашего набора |
|
||||
| **ufw**, обновления безопасности, синхронизация времени | защита сервера и корректная работа REALITY |
|
||||
|
||||
## Установка
|
||||
|
||||
Подключитесь к серверу по SSH и выполните одну команду на чистом VPS.
|
||||
|
||||
Панель 3X-UI, все протоколы на порту 443 и одна подписка на всё:
|
||||
От root, на чистом сервере (Debian 11+ / Ubuntu 22.04+, 64-бит):
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)
|
||||
bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
||||
```
|
||||
|
||||
Через пару минут скрипт покажет адрес панели, логин, пароль и подписку с QR-кодом.
|
||||
Подробно – подключение приложений, дополнительные пользователи и параметры – в **[инструкции](manuals/3x-ui.md)**.
|
||||
Без флагов скрипт **спрашивает** всё по ходу: какие протоколы ставить (Enter — REALITY + AmneziaWG-3.1),
|
||||
какую маскировку и домен, закрывать ли ответ на ping, включать ли автообновления безопасности,
|
||||
менять ли порт SSH (Enter — оставить 22), менять ли пароль root.
|
||||
|
||||
## Команды KIT
|
||||
<img alt="Установка одной командой" src="manuals/assets/script-3x-ui-local.svg" width="860">
|
||||
|
||||
После установки на сервере появляется команда `kit`. Запустите её **без аргументов** – откроется меню, как у `x-ui`: выберите цифру, остальное спросит само. Те же действия одной строкой – в группах ниже (нажмите, чтобы раскрыть). Подробности – в [инструкции](manuals/3x-ui.md).
|
||||
|
||||
<details>
|
||||
<summary><b>Пользователи</b> – <code>kit user</code></summary>
|
||||
Если вопросы не нужны — те же ответы задаются флагами:
|
||||
|
||||
```bash
|
||||
kit user add имя [--gb 50] [--days 30] [--devices 3] # добавить во все протоколы, показать подписку
|
||||
kit user list # кто сколько израсходовал, до какого числа
|
||||
kit user link имя [--all | --amnezia | --telegram] # подписка и ссылки «открыть в приложении»
|
||||
kit user limit имя [--gb N] [--days N] [--devices N] # изменить лимиты (0 – без ограничений)
|
||||
kit user off имя / kit user on имя # выключить и включить
|
||||
kit user del имя # удалить
|
||||
```
|
||||
</details>
|
||||
# минимум: свой домен и два протокола, без вопросов
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --protocols reality,awg3 --domain vpn.example.com
|
||||
|
||||
<details>
|
||||
<summary><b>Сеть и маскировка</b> – <code>kit net</code></summary>
|
||||
# всё, кроме WireGuard, и второй порт SSH
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --protocols все --ssh-port 2222
|
||||
|
||||
# ничего не спрашивать: ping закрыть, автообновления включить, порт и пароль не трогать
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --yes
|
||||
```
|
||||
|
||||
Проверить канал, ничего не меняя:
|
||||
|
||||
```bash
|
||||
kit net # протоколы, порты, сайт маскировки, отпечаток – одним экраном
|
||||
kit net site [сайт.com] # сменить сайт маскировки (без аргумента – среди соседей по подсети)
|
||||
kit net port имя порт # сменить порт протокола
|
||||
kit net off имя / kit net on имя # выключить и включить протокол (kit net on reality2 – второй REALITY)
|
||||
kit net fp firefox # сменить отпечаток клиента у всех подключений
|
||||
kit net masq on|off # Hysteria2 отвечает на чужой запрос страницей сайта
|
||||
kit net dns on|off # DNS в подписке по DoH через прокси
|
||||
kit net panel domain | ip # панель и подписка по своему домену или по IP
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Раздельная маршрутизация</b> – <code>kit net split</code></summary>
|
||||
|
||||
Через VPN идёт только список зарубежных сервисов, остальное – напрямую. Подробнее – [в инструкции](manuals/3x-ui.md#раздельная-маршрутизация-kit-net-split).
|
||||
|
||||
```bash
|
||||
kit net split on # создать список и включить
|
||||
kit net split check # проверить список
|
||||
kit net split apply # после правки списка обновить профили Happ и Xray
|
||||
kit net split off # выключить
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Обслуживание сервера</b> – проверка, обновление, копия</summary>
|
||||
|
||||
```bash
|
||||
kit check [--deep] [--fix] # проверить сервер; --deep – подключиться клиентом, --fix – исправить безопасное
|
||||
kit update [--panel] # обновить сейчас; --panel – панель 3X-UI до проверенной версии
|
||||
kit update --auto # ночное автообновление включить (--manual – выключить)
|
||||
kit backup # копия сервера: подключения, ключи, пользователи
|
||||
kit version # версии KIT, панели и ядра
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --verify-only # подпись релиза и сумма установщика
|
||||
bash <(curl -fsSL …/scripts/3x-ui-local.sh) --dry-run # что именно будет сделано
|
||||
```
|
||||
|
||||
Копию из `kit backup` на новом чистом сервере поднимает `bash 3x-ui.sh --restore файл`. Если подписка выдавалась
|
||||
на домене, а не на IP, клиентам достаточно сменить адрес в DNS.
|
||||
</details>
|
||||
### Флаги установки
|
||||
|
||||
<details>
|
||||
<summary><b>Если не подключается</b> – диагностика</summary>
|
||||
|
||||
```bash
|
||||
kit check --deep # проверить сервер и подключиться к нему клиентом: видно, виноват сервер или сеть
|
||||
kit net fp firefox # другой отпечаток клиента (вернуть: kit net fp chrome)
|
||||
kit net site # другой сайт для маскировки
|
||||
kit net on reality2 # дополнительный REALITY на другом порту
|
||||
kit backup # не помогло – копия, затем перенос на другой адрес: на новом сервере bash 3x-ui.sh --restore файл
|
||||
```
|
||||
|
||||
Один протокол не подключается – попробуйте соседний: у каждого свои сильные стороны (Hysteria2 работает по UDP,
|
||||
остальные по TCP). В подписке Clash «Авто» выбирает сама, в остальных приложениях – вручную.
|
||||
</details>
|
||||
|
||||
## VPN на роутере
|
||||
|
||||
Через прокси идут только нужные сервисы и выбранные устройства, остальное работает напрямую.
|
||||
Порядок: **1.** установить программу на роутер → **2.** собрать конфиг в [генераторе](#генератор-конфигов-для-роутера) →
|
||||
**3.** вставить команду из генератора.
|
||||
|
||||
> [!NOTE]
|
||||
> На роутере работает [XKeen](https://github.com/jameszeroX/XKeen) автора jameszeroX, спасибо ему.
|
||||
|
||||
<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 на роутер – в разделе [VPN на роутере](#vpn-на-роутере).
|
||||
|
||||
| Роутер | Ядро | Что получится | Генератор |
|
||||
|---|---|---|---|
|
||||
| Keenetic (XKeen) | Xray | `04_outbounds.json` и `05_routing.json`: серверы, выбор сервисов, реклама, свои сайты | [Открыть →](https://itsnotkubrick.github.io/3X-UI_KIT/tools/?core=xray) |
|
||||
| Keenetic (XKeen) | Mihomo | `config.yaml` с автовыбором сервера, подпиской, Hysteria2, AmneziaWG и веб-панелью | [Открыть →](https://itsnotkubrick.github.io/3X-UI_KIT/tools/?core=mihomo) |
|
||||
| OpenWrt (Nikki), бета | Mihomo | профиль для Nikki и команда, которая его включит | [Открыть →](https://itsnotkubrick.github.io/3X-UI_KIT/tools/?router=openwrt) |
|
||||
|
||||
## Полезное
|
||||
|
||||
- [XKeen](https://github.com/jameszeroX/XKeen) и его [вики](https://github.com/jameszeroX/XKeen/wiki) – документация по маршрутизации на Keenetic
|
||||
- [XKeen UI](https://github.com/zxc-rv/XKeen-UI) – веб-интерфейс для XKeen
|
||||
- [IP-адреса для AmneziaWG](https://github.com/RockBlack-VPN/ip-address) – актуальные списки от RockBlack
|
||||
|
||||
## Поддержать проект
|
||||
|
||||
Скрипты и инструкции бесплатные. Донат добровольный – он помогает оплачивать
|
||||
тестовые серверы и держать скрипты в актуальном состоянии. Спасибо! 💜
|
||||
|
||||
| Способ | |
|
||||
| Флаг | Что делает |
|
||||
|---|---|
|
||||
| Российской картой, СБП, Tinkoff Pay | [Tribute](https://t.me/tribute/app?startapp=dRsY) |
|
||||
| Зарубежной картой, Apple Pay, Google Pay | [Buy Me a Coffee](https://buymeacoffee.com/relo.cate) |
|
||||
| USDT (TRC-20) | `TS83ViXrdezUpp1eFadqj1rBhGLZaba1c1` |
|
||||
| TON | `UQBchO4XFPwF9MMa_tjXpwqTo8IL2FhUDyllhYuFo8WM-Qbf` |
|
||||
| Ethereum (ERC-20) | `0xC06F6B3A029d7Ea00705B7028490744e2BC16799` |
|
||||
| `--protocols all\|minimal\|список` | какие протоколы ставить: `all`, `minimal` (только REALITY) или через запятую. Невыбранные не создаются вообще |
|
||||
| `--domain ваш.домен` | свой домен для маскировки (A-запись на сервер, свободный порт 80 — сертификат Let's Encrypt выпустит установщик) |
|
||||
| `--sni сайт.ком` | маскировка под чужой сайт вместо своего домена |
|
||||
| `--panel-on ip\|domain` | где открывать панель и подписку |
|
||||
| `--site N\|random\|off` | сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или не трогать |
|
||||
| `--ssh-port N` | перевести SSH на порт N (прежний закроется только после вашей проверки) |
|
||||
| `--harden` / `--no-harden` | закрыть ping и включить автообновления безопасности / не трогать это |
|
||||
| `--yes` | не задавать вопросы (для автоматизации) |
|
||||
| `--host IP`, `--user имя`, `--port N`, `--restore файл` | адрес в ссылках, имя первого клиента, порт REALITY, восстановление из копии `kit backup` |
|
||||
| `--no-time`, `--no-check`, `--verify-only`, `--dry-run`, `-h` | служебные |
|
||||
|
||||
## Благодарности
|
||||
## Что происходит по шагам
|
||||
|
||||
3X-UI KIT построен на работе авторов этих проектов:
|
||||
[3X-UI](https://github.com/MHSanaei/3x-ui) ·
|
||||
[Xray-core](https://github.com/XTLS/Xray-core) ·
|
||||
[Mihomo](https://github.com/MetaCubeX/mihomo) ·
|
||||
[Hysteria](https://github.com/apernet/hysteria) ·
|
||||
[AmneziaWG](https://github.com/amnezia-vpn) ·
|
||||
[XKeen](https://github.com/jameszeroX/XKeen)
|
||||
1. **Проверка сервера**: root, Debian/Ubuntu, память, место на диске, занятые порты 80/443.
|
||||
2. **Проверка релиза**: файлы берутся из этого репозитория по тегу, проверяется **подпись `SHA256SUMS`**
|
||||
нашим ключом и SHA256 самого установщика. Не сошлось — ничего не запускается.
|
||||
3. **Выбор протоколов** и запуск установщика: панель 3X-UI, выбранные подключения, nginx на 443,
|
||||
сертификат, сайт-заглушка, служба подписки, правила ufw.
|
||||
4. **Сайт-заглушка**: ставится случайный правдоподобный ИТ-сайт из нашего набора (10 штук) и
|
||||
синхронизируется в ответ Hysteria2.
|
||||
5. **Наши вопросы и правки**: закрыть ответ на ping, включить обновления безопасности, сменить порт
|
||||
SSH, сменить пароль root.
|
||||
6. **`kit fix`** — синхронизация времени и безопасные починки.
|
||||
7. **Проверка**: `kit check` (порты, службы, подписка, сертификат, маскировка) и сводка
|
||||
`kit local hardening`. В конце — как забрать ссылки и подписку.
|
||||
|
||||
## Протоколы
|
||||
|
||||
Один порт 443 на всё: nginx разводит подключения по именам и путям, поэтому снаружи видно только
|
||||
443/tcp (и Hysteria2 на 443/udp). Список и умолчание — как у проекта: невыбранные протоколы не
|
||||
создаются вообще, ни подключения в панели, ни записей в nginx, ни ссылок в подписке.
|
||||
|
||||
| № | Протокол | Что это |
|
||||
|---|---|---|
|
||||
| 1 | **REALITY** | VLESS + Reality, основной вход. **По умолчанию** |
|
||||
| 2 | REALITY-2 | второй REALITY на случайном высоком порту |
|
||||
| 3 | Hysteria2 | быстрее на плохой сети (UDP 443) |
|
||||
| 4 | XHTTP | VLESS + Reality под другим именем маскировки |
|
||||
| 5 | VLESS-WS | VLESS через WebSocket |
|
||||
| 6 | Trojan-gRPC | Trojan через gRPC |
|
||||
| 7 | VMess-WS | VMess через WebSocket |
|
||||
| 8 | Shadowsocks | Shadowsocks 2022 |
|
||||
| 9 | TUIC | быстрый UDP-протокол |
|
||||
| 10 | WireGuard | обычный WireGuard: легко распознаётся, поэтому по умолчанию не ставится |
|
||||
| 11 | AmneziaWG | AmneziaWG: Mihomo, роутеры Keenetic |
|
||||
| 12 | **AmneziaWG-3.1** | AmneziaWG с защитой заголовков: приложения AmneziaVPN. **По умолчанию** |
|
||||
| 13 | MTProto | прокси для Telegram |
|
||||
|
||||
Наш набор по умолчанию — **REALITY + AmneziaWG-3.1**: работает почти везде и не требует
|
||||
дополнительных объяснений на клиенте. Маскировку и сайт можно менять потом: `kit net site`,
|
||||
`kit local site`.
|
||||
|
||||
## Сайт-заглушка
|
||||
|
||||
На 443 при неизвестном имени отдаётся страница нашего набора: небольшие правдоподобные ИТ-сайты
|
||||
вместо стандартных «страница в разработке».
|
||||
|
||||
| № | Сайт | Тема |
|
||||
|---|---|---|
|
||||
| 1 | Radian Analytics | дата-аналитика: хранилище, отчёты, дашборды |
|
||||
| 2 | Circuit Nine | DevOps-консалтинг: CI/CD, инфраструктура, наблюдаемость |
|
||||
| 3 | Cortex Monitor | мониторинг серверов: проверки, алерты, сертификаты |
|
||||
| 4 | Bluefin Software | разработка ПО на заказ |
|
||||
| 5 | Vantage Security | аудит и пентест |
|
||||
| 6 | Keystone IT | ИТ-поддержка офисов |
|
||||
| 7 | Meridian Networks | колокейшн и транзит |
|
||||
| 8 | Lumen Notes | заметки про Linux, nginx и сети |
|
||||
| 9 | Evercore Backup | бэкапы и восстановление |
|
||||
| 10 | Halyard Docs | документация проекта |
|
||||
|
||||
```bash
|
||||
kit local site # случайный сайт из набора
|
||||
kit local site --list # список с номерами
|
||||
kit local site 7 # поставить конкретный
|
||||
kit local site --show # какой стоит сейчас
|
||||
```
|
||||
|
||||
Менять страницу нужно **этой командой**: `/var/www/kit/index.html` отдаёт nginx, а Hysteria2 держит
|
||||
свою копию страницы внутри подключения. Команда обновляет обе — иначе 443/tcp и UDP начнут отвечать
|
||||
разными страницами, и это заметно снаружи.
|
||||
|
||||
## Команды
|
||||
|
||||
На сервере после установки доступна команда `kit` (без аргументов — меню).
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `kit user …` | `add`, `list`, `link`, `limit`, `on`, `off`, `del` — пользователи, ссылки, лимиты трафика и сроков |
|
||||
| `kit net` | состояние подключений; `on`/`off` включить-выключить, `site` — сайт маскировки, `masq` — ответ Hysteria2 страницей, `dns` — DoH в подписках |
|
||||
| `kit port` / `kit port set <имя> <порт>` | какие порты слушают подключения / сменить порт подключения |
|
||||
| `kit reality add [порт]` | добавить второй REALITY |
|
||||
| `kit check [--deep\|--fix]`, `kit fix` | проверка сервера (в том числе с самого сервера, как клиент) и безопасные починки |
|
||||
| `kit update [--panel]`, `--auto`, `--manual` | обновление сборки и панели, режим автообновления |
|
||||
| `kit backup` | копия сервера (панель, сертификаты, настройки) |
|
||||
| `kit version` | версии сборки, панели и ядра |
|
||||
|
||||
Наш слой — `kit local` (справка: `kit local help`):
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `kit local site` | сайт-заглушка: случайный, `--list`, номер, `--show` |
|
||||
| `kit local ssh 2222` / `--status` / `--close-old 2222` | добавить порт SSH / какие слушают / закрыть прежние после проверки |
|
||||
| `kit local ping off\|on\|status` | ответ на ICMP echo правилом ufw (видно в `ufw status`, отменяется одной командой) |
|
||||
| `kit local passwd [юзер]`, `--generate` | смена пароля (спрашивает дважды, в логи не попадает) |
|
||||
| `kit local upgrades on\|off\|status` | обновления безопасности |
|
||||
| `kit local hardening`, `masking`, `channel`, `seams`, `version` | сводка защиты, состояние маскировки, канал обновлений, какие места форк правит у автора |
|
||||
|
||||
У любой правки есть `--dry-run` — показывает план и ничего не меняет. Копии изменённых файлов
|
||||
складываются в `/root/kit-local-backup-<дата>/`.
|
||||
|
||||
## Обновление и канал
|
||||
|
||||
- Сервер обновляется из **этого репозитория**: `kit update` и ночной таймер берут релиз с подписью
|
||||
нашего ключа. Чужой релиз сервер не примет.
|
||||
- Посмотреть, откуда берутся обновления и чем проверяется подпись: `kit local channel`.
|
||||
- Управление автообновлением: `kit update --auto` (включить) и `kit update --manual` (выключить),
|
||||
`kit update` — обновиться сейчас, `kit update --panel` — обновить саму панель 3X-UI.
|
||||
- Проверить канал, ничего не устанавливая: установщик с `--verify-only` или `kit local channel`.
|
||||
|
||||
## Оговорки
|
||||
|
||||
- **Пароль root** никогда не передаётся флагом: значение в командной строке видно другим процессам.
|
||||
Спрашивается в ходе установки или меняется `kit local passwd`.
|
||||
- **Смена порта SSH** делается в два шага: новый порт добавляется, прежний продолжает работать, пока
|
||||
вы не проверите вход во втором окне. Закрыть прежние: `kit local ssh --close-old 2222`.
|
||||
- **`apt full-upgrade` не выполняется** — только обновления безопасности: панель и ядро Xray
|
||||
закреплены на проверенных версиях.
|
||||
- Версии панели и ядра в сборке сверяются с официальными хешами при выпуске релиза.
|
||||
|
||||
## Документация форка
|
||||
|
||||
| Файл | О чём |
|
||||
|---|---|
|
||||
| [FORK.md](FORK.md) | как устроен форк: где наши правки (швы), регламент обновления из авторского проекта, канал, подпись, грабли |
|
||||
| [FORK-CHANGELOG.md](FORK-CHANGELOG.md) | что менялось в наших версиях |
|
||||
| `kit local help` | команды форка прямо на сервере |
|
||||
|
||||
+8
-6
@@ -1,6 +1,8 @@
|
||||
# 3X-UI KIT 1.2
|
||||
2d7a0f0dcc26ea4f8bab3d60e1900e7046a9673a88fef85c03630502120961f0 scripts/3x-ui.sh
|
||||
ec3d62ecee39d14ea98ccc367ebd34febbb2e57a28a4e2405f70bcc7b26e45ab scripts/hysteria2.sh
|
||||
ce4889af8e9d1ce70508166f96986d33a5f4e3829b8223f8035e380f45ca7760 scripts/kit.sh
|
||||
eac3e47491ce86487f05160952ff5b4ad8a3821954f082eee3516e40575fb574 scripts/kit-sub.py
|
||||
44804414f85bef9588f60086587fd6e8871b39123c831ec129624f4d81a95fea VERSION
|
||||
# 3X-UI KIT 1.2.6
|
||||
db677f17e1c8f59e0c5f329faff6f6178454b75b266966b80c225b8923318831 scripts/3x-ui.sh
|
||||
b8bc9cb115e427b601d2d9ea0b3e1c759b4fc0f9f36b267e364e47658422e37d scripts/hysteria2.sh
|
||||
1215b3435a5f476dee26fd732640c6d33a7445585f14108a289ae4eafb05b439 scripts/kit.sh
|
||||
d393dc04a69bad2ce9f022b2f51e0d349b6655c5c92fc64cf67402c5a63e2f3a scripts/kit-sub.py
|
||||
ae81cd252affc1bbfdc59421dfa30f85eb6bbbd3e178faceb7e6f708f36b6730 scripts/kit-local.sh
|
||||
eaf72eb353c14de8bc9cd567b396efc40eb1493c3839009465f0f8b35f0db557 scripts/3x-ui-local.sh
|
||||
5844ffcc346f89c07b13ba7596bfb3788ed73f4755e541182d7822d43b7c7a24 VERSION
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgatMYIOGGebxc7V6Sk8m5JZFUIm
|
||||
IWK6kSyl9DFxsQGbMAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABAdFL7PZOiEBqM7mNXcIUk96bj1YnqSR6BijHEd07ij1IG05
|
||||
Vjb8nVmTjlvGMbhxZw0lNAbdeQLSDpBgBS2JFGBQ==
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABAHRB6TB0NGpmJ1w5Ua1PEGUrmlQUuM99cll5nnhO9EJvMRz
|
||||
IoDmvnCMGC4Xgh22IeRR88vtLkKz3hgtMoMYRLAg==
|
||||
-----END SSH SIGNATURE-----
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="860" height="190" viewBox="0 0 860 190" role="img" aria-label="3X-UI autoVPS — сборка форка">
|
||||
<style>.t{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}
|
||||
.m{font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace}</style>
|
||||
<rect x="0.5" y="0.5" width="859" height="189" rx="16" fill="#15110f" stroke="#43372e"/>
|
||||
<circle cx="748" cy="46" r="58" fill="#93e06f" opacity="0.07"/>
|
||||
<circle cx="808" cy="140" r="34" fill="#93e06f" opacity="0.05"/>
|
||||
<text x="44" y="86" class="t" font-size="46" font-weight="700" fill="#fbf6ec">3X-UI <tspan fill="#93e06f">autoVPS</tspan></text>
|
||||
<text x="44" y="118" class="t" font-size="16" fill="#cbbfa9">сервер одной командой: панель 3X-UI, VPN-протоколы, один порт 443, одна подписка</text>
|
||||
<text x="44" y="152" class="m" font-size="13" fill="#8a8073">свой канал обновлений · подписанные релизы · 13 протоколов · 10 сайтов-заглушек</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,413 @@
|
||||
#!/usr/bin/env bash
|
||||
# 3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
#
|
||||
# Что делает (по шагам):
|
||||
# 1) проверяет сервер и наличие curl, ssh-keygen, openssl;
|
||||
# 2) берёт из нашего Gitea релиз своей же версии, проверяет ПОДПИСЬ SHA256SUMS и SHA256 установщика;
|
||||
# 3) запускает установщик автора (scripts/3x-ui.sh) — панель 3X-UI, протоколы, сайт-заглушка,
|
||||
# маскировка, сертификат, подписка, ufw. Флаги установщика можно передавать как есть,
|
||||
# а можно ничего не передавать — тогда он задаст вопросы сам (в том числе про домен);
|
||||
# 4) спрашивает, какие протоколы ставить: невыбранные не создаются вообще — ни подключения в панели,
|
||||
# ни записей в nginx, ни ссылок в подписке (за это отвечает флаг автора --protocols);
|
||||
# 5) ставит сайт-заглушку: случайный правдоподобный ИТ-сайт из нашего набора (10 штук;
|
||||
# --site N — конкретный, --no-site — не трогать авторскую страницу);
|
||||
# 6) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
|
||||
# безопасности, перевести SSH на другой порт, сменить пароль root (поведение из StressOzz/vps-setup,
|
||||
# но безопаснее);
|
||||
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
|
||||
#
|
||||
# Запуск:
|
||||
# # одна команда, дальше — вопросы:
|
||||
# bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
|
||||
# # или сразу с ответами, без вопросов:
|
||||
# ... 3x-ui-local.sh --domain vpn.example.com
|
||||
# ... 3x-ui-local.sh --sni dl.google.com --no-harden --ssh-port 2222
|
||||
# # без вопросов, всё по умолчанию (для автоматизации):
|
||||
# ... 3x-ui-local.sh --yes
|
||||
#
|
||||
# Флаги этого скрипта (остальные уходят установщику автора):
|
||||
# --domain ДОМЕН, --sni САЙТ, --panel-on ip|domain, --host IP, --user ИМЯ, --port N, --restore ФАЙЛ
|
||||
# --harden закрыть ping и включить автообновления, без вопросов
|
||||
# --no-harden не трогать ping и автообновления
|
||||
# --ssh-port N добавить порт SSH N (прежний порт продолжает работать)
|
||||
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
|
||||
# --no-time не запускать kit fix
|
||||
# --no-check не запускать в конце kit check
|
||||
# --verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
# --dry-run показать план и ничего не делать
|
||||
# -h | --help эта справка
|
||||
#
|
||||
# Наш файл: авторские релизы его не перезаписывают, а правки форка живут в scripts/kit-local.sh.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
KIT_VERSION="1.2.6" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||
NS="3x-ui-kit-release"
|
||||
SIG_ID="releases@3x-ui-kit"
|
||||
# Публичный ключ форка. Меняется только вместе с ключом подписи (тогда обновляется и здесь, и в kit).
|
||||
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)'
|
||||
|
||||
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
|
||||
[[ -t 1 ]] || { G=; Y=; R=; B=; D=; N=; }
|
||||
say() { printf '%s\n' "${G}==>${N} $*"; }
|
||||
warn() { printf '%s\n' "${Y}!${N} $*" >&2; }
|
||||
die() { printf '%s\n' "${R}✗${N} $*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
|
||||
bash <(curl -fsSL <наш Gitea>/raw/branch/main/scripts/3x-ui-local.sh) [флаги…]
|
||||
|
||||
Без флагов скрипт спрашивает: какие протоколы ставить (невыбранные не создаются вообще),
|
||||
маскировку и домен — это вопросы установщика автора; затем про ответ на ping, автообновления,
|
||||
второй порт SSH и пароль root — это наши вопросы.
|
||||
|
||||
Флаги, которые скрипт передаёт установщику автора как есть:
|
||||
--domain vpn.example.com свой домен для маскировки (A-запись на этот сервер, свободный 80)
|
||||
--sni сайт.com маскировка под чужой сайт
|
||||
--panel-on domain | ip где открывать панель и подписку
|
||||
--host IP, --user имя, --port N, --protocols …, --restore файл
|
||||
|
||||
Флаги этого скрипта:
|
||||
--harden закрыть ping и включить автообновления, без вопросов
|
||||
--no-harden не трогать ping и автообновления
|
||||
--ssh-port N перевести SSH на порт N (прежний закроется после проверки входа)
|
||||
--yes ни о чём не спрашивать (для автоматизации)
|
||||
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
|
||||
не трогать авторскую страницу (то же: --no-site)
|
||||
--no-time не запускать kit fix (время и безопасные починки)
|
||||
--no-check не запускать в конце kit check
|
||||
--verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
--dry-run показать план и ничего не делать
|
||||
-h | --help эта справка
|
||||
EOF
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ разбор флагов
|
||||
|
||||
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
|
||||
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--harden) harden=yes ;;
|
||||
--no-harden) harden=no ;;
|
||||
--ssh-port) ssh_port=${2:-}; shift || die "--ssh-port: нужен номер порта" ;;
|
||||
--ssh-port=*) ssh_port=${1#*=} ;;
|
||||
--yes | -y) assume_yes=yes ;;
|
||||
--site) site=${2:-random}; shift || die "--site: нужен номер сайта (1..10), random или off" ;;
|
||||
--site=*) site=${1#*=} ;;
|
||||
--no-site) site=off ;;
|
||||
--no-time) fix_time=no ;;
|
||||
--no-check) do_check=no ;;
|
||||
--verify-only) verify_only=yes ;;
|
||||
--dry-run) dry=yes ;;
|
||||
-h | --help) usage; exit 0 ;;
|
||||
*) INSTALLER_ARGS+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
[[ -z $ssh_port ]] || { [[ $ssh_port =~ ^[0-9]{1,5}$ ]] && ((10#$ssh_port >= 1024 && 10#$ssh_port <= 65535)); } \
|
||||
|| die "--ssh-port: нужно число 1024–65535, например 2222"
|
||||
if [[ $site != off && $site != random ]]; then
|
||||
# 10 — число сайтов в наборе (scripts/kit-local.sh, KL_SITE_TITLES); добавите сайт — поднимите границу
|
||||
[[ $site =~ ^[0-9]{1,2}$ ]] && ((10#$site >= 1 && 10#$site <= 10)) \
|
||||
|| die "--site: random, off или номер 1..10 (список: kit local site --list)"
|
||||
fi
|
||||
|
||||
# Если --protocols задан флагом — не спрашиваем и не дублируем его.
|
||||
proto_flag=""
|
||||
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
|
||||
[[ ${INSTALLER_ARGS[_i]} == --protocols ]] && proto_flag=${INSTALLER_ARGS[_i + 1]:-all}
|
||||
done
|
||||
|
||||
pre() { printf ' %s\n' "$*"; }
|
||||
ASK_SRC=${KIT_ASK_SRC:-/dev/tty} # откуда читать ответы (в тестах подменяется файлом)
|
||||
interactive() { [[ $assume_yes == no && -r $ASK_SRC ]]; }
|
||||
|
||||
if [[ $dry == yes ]]; then
|
||||
say "план установки (--dry-run: ничего не делаю)"
|
||||
pre "версия сборки: $KIT_VERSION"
|
||||
pre "откуда: $GITEA (тег v$KIT_VERSION), подпись проверяется ключом форка"
|
||||
if ((${#INSTALLER_ARGS[@]})); then pre "установщик автора: bash scripts/3x-ui.sh ${INSTALLER_ARGS[*]}"; else pre "установщик автора: bash scripts/3x-ui.sh — спросит про маскировку и домен"; fi
|
||||
if [[ $harden == auto ]]; then
|
||||
if interactive; then pre "защита: спрошу про ответ на ping и автообновления"
|
||||
else pre "защита: без терминала — закрыть ping и включить автообновления"; fi
|
||||
elif [[ $harden == yes ]]; then pre "защита: закрыть ping и включить автообновления (--harden)"
|
||||
else pre "защита: пропускаю (--no-harden)"; fi
|
||||
if [[ -n $proto_flag ]]; then pre "протоколы: ${proto_flag} (задано флагом)"
|
||||
elif interactive; then pre "протоколы: спрошу (Enter — REALITY + AmneziaWG-3.1, «все» — набор автора)"
|
||||
else pre "протоколы: REALITY + AmneziaWG-3.1 (без терминала вопроса нет)"; fi
|
||||
if [[ -n $ssh_port ]]; then pre "порт SSH: перевожу на $ssh_port (прежний — после проверки входа)"
|
||||
elif interactive; then pre "порт SSH: спрошу (Enter — оставить текущий)"; fi
|
||||
if interactive; then pre "пароль root: спрошу (по умолчанию — не менять)"; fi
|
||||
case $site in
|
||||
off) pre "заглушка: оставляю авторскую страницу (--no-site)" ;;
|
||||
random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;;
|
||||
*) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;;
|
||||
esac
|
||||
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
|
||||
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
|
||||
echo
|
||||
say "запуск без --dry-run выполнит всё это на сервере"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ релиз: скачать и проверить подпись
|
||||
|
||||
# Подпись проверяем ДО запуска чужого кода, поэтому здесь нужны только curl/ssh-keygen/openssl.
|
||||
for c in curl ssh-keygen openssl; do command -v "$c" >/dev/null || die "нет $c — поставьте: apt-get update && apt-get install -y $c"; done
|
||||
|
||||
say "беру релиз v$KIT_VERSION и проверяю подпись"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
raw="$GITEA/raw/tag/v$KIT_VERSION"
|
||||
for f in SHA256SUMS SHA256SUMS.sig scripts/3x-ui.sh; do
|
||||
mkdir -p "$tmp/$(dirname "$f")"
|
||||
curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)"
|
||||
done
|
||||
printf '%s namespaces="%s" %s\n' "$SIG_ID" "$NS" "$SIGNER_PUB" >"$tmp/allowed_signers"
|
||||
ssh-keygen -Y verify -f "$tmp/allowed_signers" -I "$SIG_ID" -n "$NS" -s "$tmp/SHA256SUMS.sig" <"$tmp/SHA256SUMS" >/dev/null 2>&1 \
|
||||
|| die "подпись релиза v$KIT_VERSION не сошлась с ключом форка — это не наш релиз, ничего не запускаю"
|
||||
sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp/SHA256SUMS")
|
||||
[[ $(printf '%s' "$sum" | wc -c) -eq 64 ]] || die "в SHA256SUMS нет установщика — релиз собран неверно"
|
||||
got=$(sha256sum "$tmp/scripts/3x-ui.sh" | awk '{print $1}')
|
||||
[[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю"
|
||||
bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса"
|
||||
printf ' ✓ подпись сошлась, SHA256 установщика совпал\n'
|
||||
|
||||
if [[ $verify_only == yes ]]; then
|
||||
say "релиз v$KIT_VERSION проверен: подпись и SHA256 сошлись, установщик не запускался"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ предполётные проверки
|
||||
|
||||
say "проверяю сервер"
|
||||
[[ ${EUID:-0} -eq 0 ]] || die "нужен root: запустите от root (на чистом VPS обычно так и есть)"
|
||||
[[ -r /etc/os-release ]] || die "не вижу /etc/os-release — это не Debian/Ubuntu?"
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
case ${ID:-} in debian | ubuntu | raspbian) ;; *) warn "система ${PRETTY_NAME:-?}: поддерживаются Debian и Ubuntu" ;; esac
|
||||
printf ' %s\n' "${PRETTY_NAME:-Linux}, ядро $(uname -r), памяти $(awk '/MemTotal/ {printf "%.1f ГБ", $2 / 1048576}' /proc/meminfo 2>/dev/null || echo '?')"
|
||||
df -P / | awk 'NR==2 {gsub("%","",$5); if ($5 >= 95) exit 1}' || warn "диск занят почти полностью — установка может не пройти"
|
||||
for p in 443 80; do ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$p\$" && warn "порт $p уже кем-то занят"; done
|
||||
|
||||
# ------------------------------------------------------------------ какие протоколы ставить
|
||||
|
||||
# Список, порядок и набор по умолчанию — как у установщика автора (scripts/3x-ui.sh:
|
||||
# ALL_PROTOS / DEFAULT_PROTOS). Невыбранные протоколы не создаются вообще: под них не будет
|
||||
# ни подключения в панели, ни записей в nginx, ни ссылок в подписке.
|
||||
ALL_PROTOS=(reality reality2 hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
|
||||
PROTO_HINT=(
|
||||
"REALITY основной: VLESS + Reality, порт 443"
|
||||
"REALITY-2 второй REALITY на случайном высоком порту"
|
||||
"Hysteria2 быстрее на плохой сети (UDP 443)"
|
||||
"XHTTP VLESS + Reality под другим именем маскировки"
|
||||
"VLESS-WS VLESS через WebSocket"
|
||||
"Trojan-gRPC Trojan через gRPC"
|
||||
"VMess-WS VMess через WebSocket"
|
||||
"Shadowsocks Shadowsocks 2022"
|
||||
"TUIC быстрый UDP-протокол"
|
||||
"WireGuard обычный WireGuard: легко распознаётся, обычно не ставят"
|
||||
"AmneziaWG AmneziaWG: Mihomo, роутеры Keenetic"
|
||||
"AmneziaWG-3.1 AmneziaWG с защитой заголовков: AmneziaVPN"
|
||||
"MTProto прокси для Telegram"
|
||||
)
|
||||
# Наш набор по умолчанию: VLESS REALITY и AmneziaWG-3.1 — самый совместимый минимум.
|
||||
# Набор автора (флаг --protocols all) доступен ответом «все».
|
||||
AUTHOR_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
|
||||
DEFAULT_PROTOS=(reality awg3)
|
||||
|
||||
proto_menu() {
|
||||
local i n mark
|
||||
say "какие протоколы поставить?" >&2
|
||||
for i in "${!ALL_PROTOS[@]}"; do
|
||||
n=$((i + 1)) mark=" "
|
||||
[[ " ${DEFAULT_PROTOS[*]} " == *" ${ALL_PROTOS[i]} "* ]] && mark="*"
|
||||
printf ' %s %2d. %s\n' "$mark" "$n" "${PROTO_HINT[i]}" >&2
|
||||
done
|
||||
printf ' %s\n' "${D}[* — по умолчанию: REALITY + AmneziaWG-3.1. «все» — набор автора (всё, кроме WireGuard).${N}" >&2
|
||||
printf ' %s\n' "${D}[Невыбранные протоколы не создаются вообще: ни подключения, ни записей в nginx, ни ссылок в подписке.]${N}" >&2
|
||||
}
|
||||
|
||||
proto_parse() { # ответ → выбранные имена через запятую; код 1, если не разобрал
|
||||
local ans=${1,,} tok i all=" ${ALL_PROTOS[*]} " picked=""
|
||||
ans=${ans//[[:space:]]/,} # «1 3 11» и «1,3,11» — одно и то же
|
||||
[[ -n $ans ]] || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
|
||||
case $ans in
|
||||
all | все | всё) printf '%s' "$(IFS=,; echo "${AUTHOR_PROTOS[*]}")"; return 0 ;;
|
||||
minimal | минимум) printf '%s' "reality"; return 0 ;;
|
||||
esac
|
||||
local IFS=','
|
||||
for tok in $ans; do
|
||||
[[ -n $tok ]] || continue # двойные запятые и пробелы — не ошибка
|
||||
if [[ $tok =~ ^[0-9]{1,2}$ ]]; then
|
||||
i=$((10#$tok - 1))
|
||||
((i >= 0 && i < ${#ALL_PROTOS[@]})) || return 1
|
||||
tok=${ALL_PROTOS[i]}
|
||||
fi
|
||||
[[ $all == *" $tok "* ]] || return 1 # только имена из списка автора
|
||||
[[ ,$picked, == *",$tok,"* ]] || picked+="${picked:+,}$tok"
|
||||
done
|
||||
[[ -n $picked ]] || return 1
|
||||
printf '%s' "$picked"
|
||||
}
|
||||
|
||||
ask_protocols() { # печатает итоговый список через запятую
|
||||
local i ans picked
|
||||
interactive || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
|
||||
proto_menu
|
||||
for i in 1 2 3; do
|
||||
printf '\n%sНомера через запятую (например 1,12) или «все»; Enter — REALITY + AmneziaWG-3.1: %s' "$B" "$N" >&2
|
||||
ans=""
|
||||
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
|
||||
if picked=$(proto_parse "$ans"); then printf '%s' "$picked"; return 0; fi
|
||||
warn "не понял «$ans» — нужны номера из списка через запятую или «все»"
|
||||
done
|
||||
warn "оставляю набор по умолчанию"
|
||||
printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ установка
|
||||
|
||||
if [[ -z $proto_flag ]]; then
|
||||
protos=$(ask_protocols)
|
||||
INSTALLER_ARGS+=(--protocols "$protos")
|
||||
else
|
||||
protos=$proto_flag
|
||||
fi
|
||||
say "протоколы: $protos — под остальные не создаётся ничего"
|
||||
|
||||
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
|
||||
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
|
||||
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
|
||||
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
|
||||
if [[ -r /dev/tty ]]; then
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || die "установщик остановился с ошибкой — смотрите его вывод выше"
|
||||
else
|
||||
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || die "установщик остановился с ошибкой (нет терминала — вопросы задать некому)"
|
||||
fi
|
||||
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
|
||||
|
||||
# ------------------------------------------------------------------ наши дополнения
|
||||
|
||||
ask_yes() { # "вопрос" y|n — по умолчанию
|
||||
local q=$1 def=$2 ans=""
|
||||
interactive || { [[ $def == y ]]; return; }
|
||||
printf '\n%s [%s]: ' "$q" "$([[ $def == y ]] && echo 'Y/n' || echo 'y/N'])"
|
||||
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
|
||||
ans=${ans,,}; ans=${ans//[[:space:]]/}
|
||||
[[ -z $ans && $def == y ]] && return 0
|
||||
[[ $ans =~ ^(y|yes|д|да)$ ]]
|
||||
}
|
||||
|
||||
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
|
||||
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
|
||||
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
|
||||
case $site in
|
||||
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
|
||||
random)
|
||||
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
|
||||
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
|
||||
;;
|
||||
*)
|
||||
say "ставлю сайт-заглушку №$site"
|
||||
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $fix_time == yes ]]; then
|
||||
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
|
||||
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
|
||||
fi
|
||||
|
||||
# ping и автообновления: --harden/--no-harden решают без вопросов, иначе спрашиваем
|
||||
want_ping=$harden; want_update=$harden
|
||||
[[ $want_ping == auto ]] && want_ping=yes
|
||||
[[ $want_update == auto ]] && want_update=yes
|
||||
if [[ $harden == auto ]] && interactive; then
|
||||
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
|
||||
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
|
||||
fi
|
||||
if [[ $want_ping == yes ]]; then
|
||||
say "закрываю ответ на ping (правилом ufw)"
|
||||
kit local ping off || warn "не удалось — повторите: kit local ping off"
|
||||
fi
|
||||
if [[ $want_update == yes ]]; then
|
||||
say "включаю автообновления безопасности"
|
||||
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
|
||||
fi
|
||||
|
||||
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
|
||||
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
|
||||
ssh_ports_now() {
|
||||
local p
|
||||
p=$(kit local ssh --status 2>/dev/null | sed -n 's/^порты SSH сейчас: //p')
|
||||
[[ -n $p ]] || p=$(sshd -T 2>/dev/null | awk '$1 == "port" {print $2}' | paste -sd, -)
|
||||
[[ -n $p ]] || p=$(grep -rhiE '^[[:space:]]*Port[[:space:]]+[0-9]+' /etc/ssh/sshd_config /etc/ssh/sshd_config.d 2>/dev/null | awk '{print $2}' | paste -sd, -)
|
||||
printf '%s' "${p:-22}"
|
||||
}
|
||||
|
||||
ask_ssh_port() { # пусто — оставить как есть; иначе номер нового порта
|
||||
local cur ans try
|
||||
cur=$(ssh_ports_now)
|
||||
interactive || return 0
|
||||
for try in 1 2 3; do
|
||||
printf '\n%sСменить порт SSH? Сейчас %s. Введите новый номер, Enter — оставить как есть: %s' "$B" "$cur" "$N" >&2
|
||||
ans=""
|
||||
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
|
||||
ans=${ans//[[:space:]]/}
|
||||
[[ -n $ans ]] || { say "порт SSH оставляю как есть ($cur)" >&2; return 0; }
|
||||
if ! [[ $ans =~ ^[0-9]{1,5}$ ]] || ((10#$ans < 1024 || 10#$ans > 65535)); then
|
||||
warn "«$ans» не похоже на порт (нужно число 1024–65535)"
|
||||
continue
|
||||
fi
|
||||
if [[ ,$cur, == *",$ans,"* ]]; then
|
||||
say "порт $ans уже слушает SSH — менять нечего" >&2; return 0
|
||||
fi
|
||||
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$ans\$"; then
|
||||
warn "порт $ans/tcp занят другой программой"
|
||||
continue
|
||||
fi
|
||||
printf '%s' "$ans"; return 0
|
||||
done
|
||||
warn "оставляю прежний порт ($cur)"
|
||||
}
|
||||
|
||||
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
|
||||
if [[ -n $ssh_port ]]; then
|
||||
say "перевожу SSH на порт $ssh_port: новый добавляю, прежний пока остаётся рабочим"
|
||||
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
|
||||
cat <<EOF
|
||||
|
||||
${Y}Дальше обязательно (иначе можно потерять доступ к серверу):${N}
|
||||
1) откройте ВТОРОЕ окно и проверьте вход: ssh -p $ssh_port root@<IP сервера>
|
||||
2) вход работает — закройте прежние порты: kit local ssh --close-old $ssh_port
|
||||
EOF
|
||||
fi
|
||||
|
||||
# пароль root: в флагах его нет намеренно — значение в командной строке видно в списке процессов
|
||||
if interactive; then
|
||||
if ask_yes "Сменить пароль root прямо сейчас?" n; then
|
||||
kit local passwd || warn "не получилось — повторите: kit local passwd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ проверка
|
||||
|
||||
if [[ $do_check == yes ]]; then
|
||||
say "проверяю, что получилось"
|
||||
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix"
|
||||
kit local hardening
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI KIT $KIT_VERSION, канал: наш Gitea).
|
||||
|
||||
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||
EOF
|
||||
+12
-3
@@ -30,9 +30,9 @@ wait_apt_idle() {
|
||||
XUI_VERSION="v3.9.0"
|
||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
||||
KIT_VERSION="1.2"
|
||||
KIT_VERSION="1.2.6"
|
||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||
# Clash Verge, Mihomo в XKeen) не проходят REALITY – проверено 2026-09-25.
|
||||
# 26.6.27 – последняя версия, с которой работают все клиенты и которую принимает 3X-UI.
|
||||
@@ -1270,6 +1270,7 @@ awg_attach() { # имя subId [лимит-байт] [срок-мс] [устро
|
||||
}
|
||||
|
||||
KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"
|
||||
KIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"
|
||||
|
||||
install_kit_cli() {
|
||||
install -d -m 700 /etc/kit
|
||||
@@ -1293,10 +1294,18 @@ install_kit_file() {
|
||||
[[ -f $d/kit.sh && ${BASH_SOURCE[0]} != /dev/fd/* ]] && src=$d/kit.sh
|
||||
if [[ -n $src ]]; then install -m 755 "$src" /usr/local/bin/kit
|
||||
else curl -fsSL --retry 3 -o /usr/local/bin/kit "$KIT_CLI_URL" && chmod 755 /usr/local/bin/kit; fi
|
||||
# Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).
|
||||
install -d -m 755 /usr/local/lib/kit
|
||||
if [[ -n $src && -f $d/kit-local.sh ]]; then
|
||||
install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh
|
||||
else
|
||||
curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \
|
||||
|| warn "Локальные дополнения kit не скачались: kit local не будет работать"
|
||||
fi
|
||||
bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"
|
||||
}
|
||||
|
||||
KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"
|
||||
KIT_INSTALL_CMD="bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui.sh)"
|
||||
|
||||
# После «x-ui → Uninstall» меню подсказывает команду официального установщика –
|
||||
# меняем её на нашу. Только в echo: вызов установщика в «Update» не трогаем.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||
# останавливается и не заменяет команду hy2.
|
||||
|
||||
KIT_VERSION="1.2"
|
||||
KIT_VERSION="1.2.6"
|
||||
|
||||
cat >&2 <<'MSG'
|
||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||
|
||||
@@ -0,0 +1,888 @@
|
||||
#!/usr/bin/env bash
|
||||
# 3X-UI_KIT (форк hacker07) — локальные дополнения.
|
||||
#
|
||||
# Этот файл наш: автор его не знает и не правит, поэтому обновления автора его не ломают.
|
||||
# kit подключает его одной строкой из kit.sh (шов в tools/fork_apply.py) и передаёт сюда
|
||||
# аргументы командной строки. Всё, что мы дописываем к kit, живёт ЗДЕСЬ.
|
||||
#
|
||||
# Команды: kit local <команда> (справка: kit local help)
|
||||
#
|
||||
# Часть команд повторяет поведение проекта StressOzz/vps-setup (добавить порт SSH, отключить
|
||||
# ответ на ping, сменить пароль root). Там нет файла лицензии, поэтому код свой, а поведение
|
||||
# сделано безопаснее, чем в источнике:
|
||||
# * конфиг sshd проверяется (`sshd -t`) ДО перезапуска, новый порт сначала открывается в ufw;
|
||||
# * прежний порт продолжает работать, пока новый не проверен (`--close-old` — отдельный шаг);
|
||||
# * у каждой правки есть --dry-run и копия затронутого файла в /root/kit-local-backup-<дата>/;
|
||||
# * apt full-upgrade из источника НЕ выполняется (он тянет систему вперёд и может сломать
|
||||
# закреплённые версии панели и ядра) — вместо него автообновления безопасности.
|
||||
#
|
||||
# Правила работы с файлом: только функции, никакого кода на верхнем уровне (он выполняется при
|
||||
# каждом запуске kit), и никаких зависимостей от функций автора: наш вызов идёт в самом начале
|
||||
# kit.sh, когда авторские say/warn ещё не объявлены.
|
||||
|
||||
KL_BACKUP_DIR="/root/kit-local-backup-$(date +%Y%m%d)"
|
||||
KL_SSHD_CONF="/etc/ssh/sshd_config"
|
||||
KL_SSHD_DROPIN="/etc/ssh/sshd_config.d/99-kit-local-port.conf"
|
||||
KL_SOCKET_DROPIN="/etc/systemd/system/ssh.socket.d/99-kit-local-port.conf"
|
||||
KL_UFW_RULES=/etc/ufw/before.rules
|
||||
KL_UFW6_RULES=/etc/ufw/before6.rules
|
||||
KL_SYSCTL_DROPIN=/etc/sysctl.d/99-kit-local-icmp.conf
|
||||
|
||||
kl_say() { printf '\033[1m==>\033[0m %s\n' "$*"; }
|
||||
kl_ok() { printf ' ✓ %s\n' "$*"; }
|
||||
kl_warn() { printf ' ! %s\n' "$*" >&2; }
|
||||
kl_step() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
||||
kl_die() { printf '✗ %s\n' "$*" >&2; return 1; }
|
||||
kl_have() { command -v "$1" >/dev/null 2>&1; }
|
||||
kl_dry() { [[ ${KL_DRY:-no} == yes ]]; }
|
||||
kl_run() { if kl_dry; then printf ' (dry-run) %s\n' "$*"; return 0; fi; "$@"; }
|
||||
|
||||
# Разбор аргументов: --dry-run поднимаем в KL_DRY, остальное складываем в KL_REST.
|
||||
kl_args() {
|
||||
KL_DRY=no; KL_REST=()
|
||||
local a
|
||||
for a in "$@"; do
|
||||
case $a in
|
||||
--dry-run) KL_DRY=yes ;;
|
||||
*) KL_REST+=("$a") ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
kl_backup() { # файл → копия рядом с релизом, не в /tmp (там её переживёт только до перезагрузки)
|
||||
[[ -e $1 ]] || return 0
|
||||
local dst="$KL_BACKUP_DIR/$(printf '%s' "$1" | tr '/' '_')"
|
||||
if kl_dry; then printf ' (dry-run) сохранил бы %s → %s\n' "$1" "$dst"; return 0; fi
|
||||
install -d -m 700 "$KL_BACKUP_DIR"
|
||||
cp -p "$1" "$dst" && printf ' ✓ копия: %s\n' "$dst"
|
||||
return 0
|
||||
}
|
||||
|
||||
kl_need_root() { [[ ${EUID:-0} -eq 0 ]] || kl_die "нужен root"; }
|
||||
|
||||
# ------------------------------------------------------------------ справка
|
||||
|
||||
kit_local_help() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT — дополнения форка (hacker07):
|
||||
|
||||
Состояние
|
||||
kit local эта справка
|
||||
kit local version версия kit, источник обновлений, дата сборки нашего файла
|
||||
kit local channel откуда берутся обновления и чем проверяется подпись
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening сводка по защите сервера и что ещё можно включить
|
||||
|
||||
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
|
||||
kit local site поставить случайный сайт из нашего набора (10 штук)
|
||||
kit local site --list список сайтов с номерами
|
||||
kit local site 7 поставить конкретный
|
||||
kit local site --show какой сайт стоит сейчас
|
||||
|
||||
Защита сервера (поведение взято из StressOzz/vps-setup, сделано безопаснее)
|
||||
kit local ssh 2222 добавить порт SSH (прежние продолжают работать)
|
||||
kit local ssh --status какие порты SSH слушаются сейчас
|
||||
kit local ssh --close-old 2222 закрыть прежние порты после проверки нового
|
||||
kit local ping off|on отключить/вернуть ответ на ICMP echo (ping) через ufw
|
||||
kit local ping status состояние ответа на ping
|
||||
kit local passwd [юзер] сменить пароль (спросит дважды, в логи не попадает)
|
||||
kit local passwd --generate сгенерировать пароль и показать один раз
|
||||
kit local upgrades on|off|status автообновления безопасности
|
||||
|
||||
У любой правки можно добавить --dry-run: покажет план и ничего не изменит.
|
||||
Копии изменённых файлов — в /root/kit-local-backup-<дата>/.
|
||||
|
||||
Оговорки:
|
||||
* прежний порт SSH сам не закрывается: сначала проверьте новый из отдельного окна;
|
||||
* ping отключаем правилом ufw (видно в `ufw status`), а не общесистемным sysctl: отменяется
|
||||
одной командой и не глушит диагностику для внутренних интерфейсов;
|
||||
* apt full-upgrade из источника не делаем: вместо него — обновления безопасности.
|
||||
EOF
|
||||
}
|
||||
|
||||
kit_local_version() {
|
||||
printf 'kit: %s (форк hacker07)\n' "${KIT_VERSION:-?}"
|
||||
printf 'источник: %s\n' "${KIT_RAW:-?}"
|
||||
local lib=${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}
|
||||
printf 'наш файл: %s\n' "$lib"
|
||||
[[ -r $lib ]] && printf 'собран: %s\n' "$(date -r "$lib" '+%Y-%m-%d %H:%M' 2>/dev/null || echo '?')"
|
||||
local latest
|
||||
latest=$(cat /etc/kit/latest-version 2>/dev/null || true)
|
||||
[[ -n $latest ]] && printf 'последняя: %s\n' "$latest"
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_channel() {
|
||||
printf 'файлы релиза: %s\n' "${KIT_RAW:-?}"
|
||||
printf 'подпись: id=%s namespace=%s\n' "${KIT_SIG_ID:-?}" "${KIT_SIG_NS:-?}"
|
||||
local n=0 k
|
||||
for k in "${KIT_SIGNERS[@]:-}"; do [[ -n $k ]] && n=$((n + 1)); done
|
||||
printf 'ключей в KIT_SIGNERS: %d (должен быть только ключ форка)\n' "$n"
|
||||
if [[ $n -eq 0 ]]; then
|
||||
printf 'ВНИМАНИЕ: список ключей пуст — обновление не пройдёт проверку подписи.\n'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_seams() {
|
||||
local bin=/usr/local/bin/kit
|
||||
[[ -r $bin ]] || { printf 'нет %s\n' "$bin"; return 1; }
|
||||
grep -n '3X-UI_KIT (форк hacker07)' "$bin" 2>/dev/null \
|
||||
|| printf 'метки форка в %s не найдены — сборка не наша?\n' "$bin"
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_masking() {
|
||||
kl_have kit || { printf 'нет команды kit\n'; return 1; }
|
||||
kit net site 2>/dev/null || printf 'посмотреть: kit net site\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ SSH: порты
|
||||
|
||||
kl_ssh_ports() {
|
||||
{ sshd -T 2>/dev/null | awk '$1 == "port" {print $2}'
|
||||
kl_have ss && ss -H -ltnp 2>/dev/null | awk '/sshd/ {n = split($4, a, ":"); print a[n]}'
|
||||
[[ -n ${SSH_CONNECTION:-} ]] && awk '{print $4}' <<<"$SSH_CONNECTION"
|
||||
[[ -n ${SSH_CLIENT:-} ]] && awk '{print $2}' <<<"$SSH_CLIENT"
|
||||
} 2>/dev/null | grep -E '^[0-9]+$' | sort -un | tr '\n' ' '
|
||||
}
|
||||
|
||||
kl_ssh_socket_active() { kl_have systemctl && systemctl is-active --quiet ssh.socket 2>/dev/null; }
|
||||
|
||||
kl_ufw_active() { kl_have ufw && ufw status 2>/dev/null | grep -q '^Status: active'; }
|
||||
|
||||
kl_ssh_status() {
|
||||
local ports; ports=$(kl_ssh_ports)
|
||||
printf 'порты SSH сейчас: %s\n' "${ports:-не удалось определить}"
|
||||
if kl_ssh_socket_active; then
|
||||
printf 'слушает: systemd (ssh.socket — так в Ubuntu 24.04)\n'
|
||||
[[ -f $KL_SOCKET_DROPIN ]] && grep -H 'ListenStream' "$KL_SOCKET_DROPIN"
|
||||
else
|
||||
printf 'слушает: sshd по %s\n' "$KL_SSHD_CONF"
|
||||
[[ -f $KL_SSHD_DROPIN ]] && grep -H '^Port' "$KL_SSHD_DROPIN"
|
||||
fi
|
||||
kl_ufw_active && printf 'ufw: %s\n' "$(ufw status 2>/dev/null | awk '$2 == "ALLOW" && $1 ~ /\/tcp$/ {printf "%s ", $1}')"
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_ssh() {
|
||||
kl_args "$@"
|
||||
local arg=${KL_REST[0]:-} second=${KL_REST[1]:-} close_old=no new
|
||||
[[ $arg == --close-old ]] && { close_old=yes; new=$second; }
|
||||
[[ $second == --close-old ]] && close_old=yes
|
||||
|
||||
if [[ -z $arg || $arg == --status ]]; then kl_ssh_status; return 0; fi
|
||||
kl_need_root || return 1
|
||||
[[ $close_old == no ]] && new=$arg
|
||||
[[ $new =~ ^[0-9]+$ ]] && ((new >= 1 && new <= 65535)) \
|
||||
|| { kl_die "порт — число 1..65535, например: kit local ssh 2222"; return 1; }
|
||||
|
||||
local cur; cur=$(kl_ssh_ports)
|
||||
|
||||
if [[ $close_old == yes ]]; then
|
||||
kl_say "закрываю прежние порты SSH, оставляю только $new"
|
||||
printf ' было: %s\n' "${cur:-?}"
|
||||
if [[ -z $cur ]]; then
|
||||
kl_warn "не удалось определить текущие порты SSH — закройте прежний порт вручную (файл sshd или ssh.socket)"
|
||||
return 1
|
||||
fi
|
||||
if kl_ssh_socket_active; then
|
||||
cat <<EOF
|
||||
! SSH слушает systemd (ssh.socket): закрытие старых портов делается правкой
|
||||
$KL_SOCKET_DROPIN — первой строкой там должен идти «ListenStream=» (сброс списка),
|
||||
затем «ListenStream=$new», после чего: systemctl daemon-reload && systemctl restart ssh.socket.
|
||||
Автоматически не делаю: ошибка здесь запирает доступ к серверу.
|
||||
EOF
|
||||
return 0
|
||||
fi
|
||||
local p
|
||||
for p in $cur; do
|
||||
[[ $p == "$new" ]] && continue
|
||||
kl_backup "$KL_SSHD_CONF"
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) закрыл бы порт %s в %s и снял правило ufw %s/tcp\n' "$p" "$KL_SSHD_CONF" "$p"
|
||||
continue
|
||||
fi
|
||||
sed -i -E "s|^[[:space:]]*Port[[:space:]]+$p[[:space:]]*$|# Port $p (закрыт kit local $(date +%F))|" "$KL_SSHD_CONF"
|
||||
kl_ufw_active && ufw delete allow "$p/tcp" >/dev/null 2>&1
|
||||
kl_ok "порт $p закрыт"
|
||||
done
|
||||
kl_run sshd -t || { kl_warn "sshd -t не принял конфиг — верните файл из $KL_BACKUP_DIR"; return 1; }
|
||||
kl_run systemctl restart ssh
|
||||
kl_say "остался только порт $new. Проверьте вход в НОВОМ окне, это не закрывая."
|
||||
return 0
|
||||
fi
|
||||
|
||||
kl_say "добавляю порт SSH $new (прежние: ${cur:-?})"
|
||||
if [[ " $cur " == *" $new "* ]]; then kl_warn "порт $new уже слушается — ничего не меняю"; return 0; fi
|
||||
if kl_have ss && ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$new\$"; then
|
||||
kl_die "порт $new занят другой службой — выберите другой"; return 1
|
||||
fi
|
||||
|
||||
if kl_ufw_active; then
|
||||
kl_run ufw allow "$new/tcp" && kl_ok "ufw: открыт $new/tcp — сделано до перезапуска, чтобы не запереть себя"
|
||||
else
|
||||
kl_warn "ufw не активен: проверьте, что порт $new открыт у хостера"
|
||||
fi
|
||||
|
||||
if kl_ssh_socket_active; then
|
||||
kl_backup "$KL_SOCKET_DROPIN"
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) записал бы в %s: [Socket] + ListenStream=%s (старый порт остаётся)\n' "$KL_SOCKET_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 "$(dirname "$KL_SOCKET_DROPIN")"
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\n[Socket]\nListenStream=%s\n' "$(date +%F)" "$new" >"$KL_SOCKET_DROPIN"
|
||||
kl_ok "добавлен $KL_SOCKET_DROPIN"
|
||||
fi
|
||||
kl_run systemctl daemon-reload
|
||||
kl_run systemctl restart ssh.socket
|
||||
else
|
||||
kl_backup "$KL_SSHD_DROPIN"
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) записал бы в %s: Port %s (старый порт остаётся)\n' "$KL_SSHD_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 /etc/ssh/sshd_config.d
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\nPort %s\n' "$(date +%F)" "$new" >"$KL_SSHD_DROPIN"
|
||||
kl_ok "добавлен $KL_SSHD_DROPIN"
|
||||
fi
|
||||
kl_run sshd -t || { kl_warn "sshd -t не принял конфиг — убираю наш файл, ничего не перезапускаю"; kl_dry || rm -f "$KL_SSHD_DROPIN"; return 1; }
|
||||
kl_run systemctl restart ssh || kl_run systemctl restart sshd
|
||||
fi
|
||||
|
||||
if ! kl_dry; then
|
||||
sleep 2
|
||||
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$new\$"; then kl_ok "sshd слушает $new"
|
||||
else kl_warn "порт $new пока не слушается — смотрите: journalctl -u ssh -n 30"; fi
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
Дальше обязательно:
|
||||
1) во ВТОРОМ окне проверьте вход: ssh -p $new root@<IP сервера>
|
||||
2) заработало — закройте прежние порты: kit local ssh --close-old $new
|
||||
3) поменяйте порт у себя в ~/.ssh/config и в мониторинге, если он там есть
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ ping (ICMP echo)
|
||||
|
||||
kl_ping_state() {
|
||||
if kl_have ufw && [[ -f $KL_UFW_RULES ]]; then
|
||||
grep -qE '^-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT' "$KL_UFW_RULES" && echo allow || echo deny
|
||||
elif [[ -f $KL_SYSCTL_DROPIN ]]; then
|
||||
grep -q 'icmp_echo_ignore_all = 1' "$KL_SYSCTL_DROPIN" && echo deny || echo allow
|
||||
else
|
||||
echo "?"
|
||||
fi
|
||||
}
|
||||
|
||||
kit_local_ping() {
|
||||
kl_args "$@"
|
||||
local want=${KL_REST[0]:-status}
|
||||
case $want in
|
||||
status | "")
|
||||
printf 'ответ на ping: %s\n' "$(kl_ping_state)"
|
||||
if kl_have ufw && [[ -f $KL_UFW_RULES ]]; then
|
||||
printf ' ufw before.rules: echo-request %s\n' "$(grep -qE '^# -A ufw-before-input -p icmp --icmp-type echo-request' "$KL_UFW_RULES" && echo 'закрыт (строка закомментирована)' || echo 'открыт')"
|
||||
[[ -f $KL_UFW6_RULES ]] && printf ' ufw before6.rules: echo-request %s\n' "$(grep -qE '^# -A ufw6-before-input' "$KL_UFW6_RULES" && echo 'закрыт' || echo 'открыт')"
|
||||
fi
|
||||
printf ' sysctl icmp_echo_ignore_all=%s\n' "$(sysctl -n net.ipv4.icmp_echo_ignore_all 2>/dev/null || echo '?')"
|
||||
return 0 ;;
|
||||
off | on) ;;
|
||||
*) kl_die "kit local ping off|on|status"; return 1 ;;
|
||||
esac
|
||||
kl_need_root || return 1
|
||||
|
||||
if kl_ufw_active; then
|
||||
kl_backup "$KL_UFW_RULES"; kl_backup "$KL_UFW6_RULES"
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) %s в %s и before6.rules правило «icmp echo-request», затем ufw reload\n' \
|
||||
"$([[ $want == off ]] && echo 'закомментировал бы' || echo 'вернул бы')" "$KL_UFW_RULES"
|
||||
return 0
|
||||
fi
|
||||
local f
|
||||
for f in "$KL_UFW_RULES" "$KL_UFW6_RULES"; do
|
||||
[[ -f $f ]] || continue
|
||||
if [[ $want == off ]]; then
|
||||
sed -i -E 's|^(-A ufw6?-before-input -p icmp6? --icmp6?-type echo-request -j ACCEPT)|# \1 # kit local: закрыт|' "$f"
|
||||
else
|
||||
sed -i -E 's|^# (-A ufw6?-before-input -p icmp6? --icmp6?-type echo-request -j ACCEPT).*$|\1|' "$f"
|
||||
fi
|
||||
done
|
||||
ufw reload >/dev/null 2>&1 || kl_warn "ufw reload не прошёл — проверьте: ufw status"
|
||||
rm -f "$KL_SYSCTL_DROPIN"
|
||||
if [[ $want == off ]]; then
|
||||
kl_ok "сервер больше не отвечает на ping"
|
||||
kl_warn "ICMP-мониторинг этого сервера из дома работать перестанет (проба «ping» даст сбой)"
|
||||
kl_warn "вернуть: kit local ping on"
|
||||
else
|
||||
kl_ok "сервер снова отвечает на ping"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
kl_warn "ufw не активен — использую общесистемный запрет ICMP echo (это шире: глушит ping и для внутренних интерфейсов)"
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) записал бы %s с icmp_echo_ignore_all=%s\n' "$KL_SYSCTL_DROPIN" "$([[ $want == off ]] && echo 1 || echo 0)"
|
||||
return 0
|
||||
fi
|
||||
printf 'net.ipv4.icmp_echo_ignore_all = %s\n' "$([[ $want == off ]] && echo 1 || echo 0)" >"$KL_SYSCTL_DROPIN"
|
||||
sysctl -p "$KL_SYSCTL_DROPIN" >/dev/null 2>&1
|
||||
kl_ok "готово (файл $KL_SYSCTL_DROPIN); вернуть: kit local ping on"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ пароль
|
||||
|
||||
kit_local_passwd() {
|
||||
kl_args "$@"
|
||||
local user=${KL_REST[0]:-root} gen=no
|
||||
[[ $user == --generate ]] && { user=root; gen=yes; }
|
||||
[[ ${KL_REST[1]:-} == --generate ]] && gen=yes
|
||||
kl_need_root || return 1
|
||||
id "$user" >/dev/null 2>&1 || { kl_die "нет пользователя $user"; return 1; }
|
||||
|
||||
if [[ $gen == yes ]]; then
|
||||
if kl_dry; then printf ' (dry-run) сгенерировал бы пароль пользователю %s и показал бы один раз\n' "$user"; return 0; fi
|
||||
local pw; pw=$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-20)
|
||||
printf '%s:%s\n' "$user" "$pw" | chpasswd || { kl_die "chpasswd не сработал"; return 1; }
|
||||
cat <<EOF
|
||||
|
||||
Пароль пользователя $user сменён. Показываю ОДИН раз:
|
||||
|
||||
$pw
|
||||
|
||||
Перенесите в CREDENTIALS.md и очистите прокрутку терминала (clear).
|
||||
EOF
|
||||
else
|
||||
kl_say "меняю пароль $user — спросит дважды, в историю команд не попадёт"
|
||||
if kl_dry; then printf ' (dry-run) запустил бы passwd %s\n' "$user"; return 0; fi
|
||||
passwd "$user" || return 1
|
||||
fi
|
||||
kl_dry || passwd -S "$user" 2>/dev/null
|
||||
sshd -T 2>/dev/null | grep -qi '^passwordauthentication no' \
|
||||
&& kl_warn "в sshd вход по паролю выключен — пароль пригодится только для консоли хостера"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ автообновления безопасности
|
||||
|
||||
kit_local_upgrades_status() {
|
||||
printf 'автообновления безопасности: %s\n' "$([[ -f /etc/apt/apt.conf.d/20auto-upgrades ]] && echo включены || echo выключены)"
|
||||
kl_have systemctl && printf ' служба unattended-upgrades: %s\n' "$(systemctl is-active unattended-upgrades 2>/dev/null || echo 'нет')"
|
||||
[[ -f /etc/apt/apt.conf.d/20auto-upgrades ]] && sed -n '1,4p' /etc/apt/apt.conf.d/20auto-upgrades | sed 's/^/ /'
|
||||
if [[ -f /var/log/unattended-upgrades/unattended-upgrades.log ]]; then
|
||||
printf ' последние запуски:\n'
|
||||
tail -n 3 /var/log/unattended-upgrades/unattended-upgrades.log | cut -c1-110 | sed 's/^/ /'
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_upgrades() {
|
||||
kl_args "$@"
|
||||
local want=${KL_REST[0]:-status}
|
||||
case $want in
|
||||
status | "") kit_local_upgrades_status; return 0 ;;
|
||||
on | off) ;;
|
||||
*) kl_die "kit local upgrades on|off|status"; return 1 ;;
|
||||
esac
|
||||
kl_need_root || return 1
|
||||
|
||||
if [[ $want == off ]]; then
|
||||
kl_say "выключаю автообновления безопасности"
|
||||
kl_run rm -f /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/52-kit-local-upgrades
|
||||
kl_run systemctl disable --now unattended-upgrades
|
||||
kl_ok "готово (пакет оставлен)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
kl_say "включаю автообновления безопасности (только security, без перезагрузки)"
|
||||
if ! kl_dry && ! dpkg -s unattended-upgrades >/dev/null 2>&1; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq unattended-upgrades \
|
||||
|| { kl_die "пакет не поставился"; return 1; }
|
||||
fi
|
||||
if kl_dry; then
|
||||
printf ' (dry-run) записал бы /etc/apt/apt.conf.d/20auto-upgrades и 52-kit-local-upgrades, включил бы службу\n'
|
||||
return 0
|
||||
fi
|
||||
cat >/etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
EOF
|
||||
cat >/etc/apt/apt.conf.d/52-kit-local-upgrades <<'EOF'
|
||||
// 3X-UI KIT (форк): только обновления безопасности, никакой самовольной перезагрузки.
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
EOF
|
||||
systemctl enable --now unattended-upgrades >/dev/null 2>&1 || kl_warn "служба не включилась — проверьте: systemctl status unattended-upgrades"
|
||||
kl_ok "включено: обновления безопасности ставятся сами"
|
||||
kl_ok "панель и ядро это не затрагивает — они ставятся не из apt, а из своих релизов"
|
||||
kl_warn "обновление ядра применится только после перезагрузки, сам сервер не перезагружается"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ сводка
|
||||
|
||||
kit_local_hardening() {
|
||||
kl_args "$@"
|
||||
local ports; ports=$(kl_ssh_ports)
|
||||
kl_step "Защита сервера — состояние"
|
||||
printf 'порты SSH: %s\n' "${ports:-?}$(kl_ssh_socket_active && echo ' — через systemd ssh.socket')"
|
||||
printf 'ответ на ping: %s\n' "$(kl_ping_state)"
|
||||
printf 'ufw: %s\n' "$(kl_ufw_active && echo 'активен' || echo 'не активен')"
|
||||
kl_ufw_active && printf 'открытые порты: %s\n' "$(ufw status 2>/dev/null | awk '$2 == "ALLOW" {printf "%s ", $1}')"
|
||||
printf 'автообновления: %s\n' "$([[ -f /etc/apt/apt.conf.d/20auto-upgrades ]] && echo включены || echo выключены)"
|
||||
printf 'вход по паролю в sshd: %s\n' "$(sshd -T 2>/dev/null | awk '/^passwordauthentication/ {print $2}' || echo '?')"
|
||||
printf 'смена пароля root: %s\n' "$(chage -l root 2>/dev/null | awk -F': ' '/Last password change/ {print $2}')"
|
||||
printf 'продление сертификата: %s\n' "$(crontab -l 2>/dev/null | grep -c acme) задач acme в cron"
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Чего здесь НЕТ и почему (в отличие от vps-setup):
|
||||
* apt full-upgrade — тянет систему вперёд и может сломать закреплённые панель и ядро;
|
||||
вместо него включены обновления безопасности;
|
||||
* автоматическая перезагрузка после правок — сервер не должен вставать без вашего решения;
|
||||
* общесистемный icmp_echo_ignore_all — правило ufw видно в `ufw status` и отменяется одной
|
||||
командой, не глушит диагностику внутренних интерфейсов.
|
||||
|
||||
Что включить при желании:
|
||||
kit local ssh 2222 добавить порт SSH
|
||||
kit local ping off не отвечать на ping
|
||||
kit local upgrades on обновления безопасности
|
||||
kit local passwd --generate сменить пароль root
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ заглушка: ИТ-сайты (наши)
|
||||
# Десять правдоподобных ИТ-сайтов: хостинг, DevOps, мониторинг, разработка, безопасность,
|
||||
# поддержка, сети и колокейшн, заметки про Linux, бэкапы, документация проекта.
|
||||
# Ставится случайный (kit local site), конкретный — по номеру (kit local site 7).
|
||||
# Страница отдаётся nginx на 443 при неизвестном SNI и синхронизируется в ответ Hysteria2
|
||||
# (kit net masq on) — поэтому менять её нужно этой командой, иначе 443/tcp и UDP-порт начнут
|
||||
# отвечать разными страницами (это заметно снаружи).
|
||||
|
||||
KL_SITE_FILE=/var/www/kit/index.html
|
||||
KL_SITE_TITLES=(
|
||||
"Radian Analytics — data platforms and BI"
|
||||
"Circuit Nine — DevOps consulting"
|
||||
"Cortex Monitor — server monitoring"
|
||||
"Bluefin Software — custom development"
|
||||
"Vantage Security — audits and penetration tests"
|
||||
"Keystone IT — managed systems support"
|
||||
"Meridian Networks — colocation and transit"
|
||||
"Lumen Notes — Linux and networking"
|
||||
"Evercore Backup — backup and recovery"
|
||||
"Halyard Docs — project documentation"
|
||||
)
|
||||
KL_SITE_TOPICS=(
|
||||
"дата-аналитика: хранилище, отчёты, дашборды"
|
||||
"DevOps-консалтинг: CI/CD, Kubernetes, наблюдаемость"
|
||||
"мониторинг серверов: метрики, алерты, статус-страница"
|
||||
"разработка ПО на заказ: портфолио, стек, процесс"
|
||||
"аудит и пентест: этапы, отчёт, повторная проверка"
|
||||
"ИТ-поддержка: серверы, сети, рабочие места"
|
||||
"колокейшн и транзит: стойки, каналы, BGP"
|
||||
"заметки про Linux, nginx и сети"
|
||||
"бэкапы и восстановление: политика, проверки, тесты"
|
||||
"документация проекта: установка, конфигурация, справка"
|
||||
)
|
||||
|
||||
KL_SITE_CSS=':root{--fg:#1b1f24;--mu:#5b6670;--bd:#e3e6ea;--ac:#1f6feb;--bg:#fff}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--fg);background:var(--bg)}
|
||||
.wrap{max-width:980px;margin:0 auto;padding:0 20px}
|
||||
header{border-bottom:1px solid var(--bd)}
|
||||
header .wrap{display:flex;align-items:center;justify-content:space-between;height:64px}
|
||||
.logo{font-weight:700;text-decoration:none;color:var(--fg)}
|
||||
nav a{color:var(--mu);text-decoration:none;margin-left:18px;font-size:15px}
|
||||
.hero{padding:72px 0 40px}
|
||||
.hero h1{font-size:40px;line-height:1.15;margin:0 0 16px;letter-spacing:-.02em}
|
||||
.hero p{font-size:18px;color:var(--mu);max-width:640px;margin:0 0 28px}
|
||||
.btn{display:inline-block;background:var(--ac);color:#fff;text-decoration:none;padding:12px 22px;border-radius:6px;font-weight:600}
|
||||
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px;padding:8px 0 44px}
|
||||
.card{border:1px solid var(--bd);border-radius:8px;padding:22px}
|
||||
.card h3{margin:0 0 8px;font-size:18px}
|
||||
.card p{margin:0;color:var(--mu)}
|
||||
.prose{padding:0 0 56px;max-width:780px}
|
||||
.prose h2{font-size:22px;margin:34px 0 10px}
|
||||
.prose p,.prose li{color:var(--mu)}
|
||||
code{background:#f4f6f8;padding:2px 5px;border-radius:4px;font-size:14px}
|
||||
footer{border-top:1px solid var(--bd);padding:26px 0;color:var(--mu);font-size:14px}
|
||||
table{border-collapse:collapse;width:100%}
|
||||
th,td{border-bottom:1px solid var(--bd);text-align:left;padding:10px 8px;font-size:15px}
|
||||
ul{padding-left:20px}
|
||||
@media(max-width:600px){.hero{padding:44px 0 28px}.hero h1{font-size:29px}}'
|
||||
|
||||
kl_site_open() { # заголовок описание бренд «навигация как HTML»
|
||||
cat <<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>$1</title>
|
||||
<meta name="description" content="$2">
|
||||
<meta name="generator" content="static">
|
||||
<style>$KL_SITE_CSS</style>
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="wrap"><a class="logo" href="/">$3</a><nav>$4</nav></div></header>
|
||||
HTML
|
||||
}
|
||||
|
||||
kl_site_close() { # бренд
|
||||
cat <<HTML
|
||||
<footer><div class="wrap"><p>© $(date +%Y) $1 · <a href="#">Imprint</a> · <a href="#">Privacy</a> · <a href="#">Status</a></p></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
}
|
||||
|
||||
kl_site_html() { # номер (с нуля) → страница на stdout
|
||||
case $1 in
|
||||
0) # дата-аналитика
|
||||
kl_site_open "Radian Analytics — data platforms and BI" "We collect data from accounting, warehouses and websites into one place and build the few reports the business actually reads." "Radian Analytics" '<a href="#services">Services</a><a href="#approach">Approach</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Reports that arrive before the meeting</h1>
|
||||
<p>We pull data out of your accounting system, warehouse and website, load it into one warehouse, and build the handful of dashboards people actually open.</p>
|
||||
<a class="btn" href="#services">What we build</a>
|
||||
</div></section>
|
||||
<section id="services" class="wrap cards">
|
||||
<div class="card"><h3>Data pipelines</h3><p>Scheduled loads from databases, APIs and spreadsheets, with checks that catch a broken source early.</p></div>
|
||||
<div class="card"><h3>Warehouse</h3><p>PostgreSQL or ClickHouse, modelled so that a new question does not need a new table every time.</p></div>
|
||||
<div class="card"><h3>Dashboards</h3><p>The five numbers that matter on one page, with the detail one click behind each of them.</p></div>
|
||||
</section>
|
||||
<section id="approach" class="wrap prose">
|
||||
<h2>How it usually goes</h2>
|
||||
<p>Two weeks to collect the sources and agree on definitions — most arguments turn out to be about definitions, not about software. Then the pipeline runs nightly and the first reports go out a week later.</p>
|
||||
<h2>One warning</h2>
|
||||
<p>A dashboard nobody opens is expensive decoration. One report that somebody uses beats ten that nobody does.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Nordhost" ;;
|
||||
1) # DevOps-консалтинг
|
||||
kl_site_open "Circuit Nine — DevOps consulting" "We help small teams ship safely: CI/CD, Kubernetes, infrastructure as code and observability." "Circuit Nine" '<a href="#work">Work</a><a href="#approach">Approach</a><a href="#about">About</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Ship on Fridays without holding your breath</h1>
|
||||
<p>We set up pipelines, infrastructure and monitoring so that releasing is a boring, repeatable event — and so that on-call does not ruin anyone’s week.</p>
|
||||
<a class="btn" href="#work">What we do</a>
|
||||
</div></section>
|
||||
<section id="work" class="wrap cards">
|
||||
<div class="card"><h3>CI/CD</h3><p>Build pipelines, artefact registries, staging environments and one-command rollbacks.</p></div>
|
||||
<div class="card"><h3>Infrastructure</h3><p>Terraform and Ansible for servers and cloud, with reviews and tests in the pipeline.</p></div>
|
||||
<div class="card"><h3>Observability</h3><p>Metrics, logs and traces wired together, with alerts that people actually trust.</p></div>
|
||||
</section>
|
||||
<section id="approach" class="wrap prose">
|
||||
<h2>How we work</h2>
|
||||
<p>A two-week discovery, then small changes with a review after each one. Everything lands in your repository and your accounts — no private tooling, no lock-in.</p>
|
||||
<table>
|
||||
<tr><th>Week</th><th>What happens</th></tr>
|
||||
<tr><td>1–2</td><td>Audit of the current setup, a written report with priorities</td></tr>
|
||||
<tr><td>3–6</td><td>Pipelines and infrastructure, pairing with your engineers</td></tr>
|
||||
<tr><td>7–8</td><td>Monitoring, runbooks and a handover session</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Circuit Nine" ;;
|
||||
2) # мониторинг
|
||||
kl_site_open "Cortex Monitor — server monitoring" "Uptime, resource and certificate monitoring with alerting to Telegram, e-mail and webhooks." "Cortex Monitor" '<a href="#checks">Checks</a><a href="#alerts">Alerts</a><a href="#pricing">Pricing</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Know about the outage before your users do</h1>
|
||||
<p>HTTP, TCP, ICMP and TLS checks from six locations, host metrics through a lightweight agent, and alerts that reach you in seconds.</p>
|
||||
<a class="btn" href="#checks">See checks</a>
|
||||
</div></section>
|
||||
<section id="checks" class="wrap cards">
|
||||
<div class="card"><h3>Availability</h3><p>HTTP and TCP checks with keyword matching, redirects and status codes.</p></div>
|
||||
<div class="card"><h3>Resources</h3><p>CPU, memory, disk and network of every host on one page.</p></div>
|
||||
<div class="card"><h3>Certificates</h3><p>Expiry warnings 30, 14 and 3 days ahead, with a list of what needs renewing.</p></div>
|
||||
</section>
|
||||
<section id="alerts" class="wrap prose">
|
||||
<h2>Alerting</h2>
|
||||
<p>Alerts are grouped so that a single broken router does not produce forty messages. Quiet hours, maintenance windows and escalation to a second contact are configurable per check.</p>
|
||||
<h2>Maybe you do not need us yet</h2>
|
||||
<p>If you run one server, an uptime check and a cron job with certificate renewal will do. Come back when there are more of them than you can remember.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Cortex Monitor" ;;
|
||||
3) # разработка ПО
|
||||
kl_site_open "Bluefin Software — custom development" "A small team building web services, integrations and internal tools for companies that grew out of spreadsheets." "Bluefin Software" '<a href="#services">Services</a><a href="#stack">Stack</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Software for the part of the business nobody sells</h1>
|
||||
<p>Integrations, back-office tools and web services. We take a process that lives in three spreadsheets and one person’s head, and give it a proper home.</p>
|
||||
<a class="btn" href="#services">Services</a>
|
||||
</div></section>
|
||||
<section id="services" class="wrap cards">
|
||||
<div class="card"><h3>Web services</h3><p>APIs and admin panels in Python or Go, deployed to your infrastructure.</p></div>
|
||||
<div class="card"><h3>Integrations</h3><p>Accounting, warehouses, CRM and everything that has an API or a database.</p></div>
|
||||
<div class="card"><h3>Rescue work</h3><p>Taking over an unfinished project: audit, plan, tidy up, finish.</p></div>
|
||||
</section>
|
||||
<section id="stack" class="wrap prose">
|
||||
<h2>Stack</h2>
|
||||
<p>Python (Django, FastAPI), PostgreSQL, Redis, Docker, and whatever the customer already runs. We keep deployments simple enough that a new colleague can understand them in a day.</p>
|
||||
<h2>Team</h2>
|
||||
<p>Four engineers and a part-time designer. We take on three projects at a time so that nobody is queued behind somebody else’s deadline.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Bluefin Software" ;;
|
||||
4) # безопасность
|
||||
kl_site_open "Vantage Security — audits and penetration tests" "External and internal penetration tests, configuration reviews and help with fixing what we find." "Vantage Security" '<a href="#services">Services</a><a href="#process">Process</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Find the problems before somebody else does</h1>
|
||||
<p>Penetration tests and configuration reviews for web services, networks and cloud accounts — with a report your engineers can act on and a retest when the fixes are in.</p>
|
||||
<a class="btn" href="#services">Services</a>
|
||||
</div></section>
|
||||
<section id="services" class="wrap cards">
|
||||
<div class="card"><h3>External test</h3><p>Everything reachable from the internet: services, TLS, DNS, mail.</p></div>
|
||||
<div class="card"><h3>Internal test</h3><p>What an attacker does after getting a foothold in an office or a VPN.</p></div>
|
||||
<div class="card"><h3>Review</h3><p>Settings, backups, access rights and monitoring — usually the cheapest findings.</p></div>
|
||||
</section>
|
||||
<section id="process" class="wrap prose">
|
||||
<h2>Process</h2>
|
||||
<p>Scope, rules of engagement and a test window are agreed in writing first. We work against a staging copy where possible, and we never touch production data beyond what the scope allows.</p>
|
||||
<h2>Report</h2>
|
||||
<p>Findings are ranked by what they let an attacker do, with reproduction steps and concrete fixes. A retest of the fixed issues is included in the price.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Vantage Security" ;;
|
||||
5) # ИТ-поддержка
|
||||
kl_site_open "Keystone IT — managed systems support" "We look after servers, networks, backups and workstations for offices of ten to two hundred people." "Keystone IT" '<a href="#services">Services</a><a href="#support">Support</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>An IT department, without hiring one</h1>
|
||||
<p>Monitoring, updates, backups and a real person on the phone. Contracts start at a fixed monthly fee, and you can leave with thirty days’ notice.</p>
|
||||
<a class="btn" href="#services">What is included</a>
|
||||
</div></section>
|
||||
<section id="services" class="wrap cards">
|
||||
<div class="card"><h3>Servers</h3><p>Updates, monitoring, disk space and certificate renewal handled for you.</p></div>
|
||||
<div class="card"><h3>Backups</h3><p>Nightly copies, offsite storage and a restore test twice a year.</p></div>
|
||||
<div class="card"><h3>Workplaces</h3><p>Accounts, laptops, printers and the printer driver that never works.</p></div>
|
||||
</section>
|
||||
<section id="support" class="wrap prose">
|
||||
<h2>Support</h2>
|
||||
<p>Weekdays from 8 to 19, with an on-call line for outages. Response time for a full outage is one hour, for ordinary requests one working day.</p>
|
||||
<p>We keep a written inventory of your systems, so that a new engineer — ours or yours — can take over without a two-week interview with the person who built it.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Keystone IT" ;;
|
||||
6) # сети и колокейшн
|
||||
kl_site_open "Meridian Networks — colocation and transit" "Rack space, cross-connects and IP transit in two carrier-neutral facilities." "Meridian Networks" '<a href="#colo">Colocation</a><a href="#transit">Transit</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Racks, cross-connects and transit</h1>
|
||||
<p>Quarter, half and full racks in Frankfurt and Amsterdam, with remote hands, redundant power and a choice of transit providers.</p>
|
||||
<a class="btn" href="#colo">Colocation</a>
|
||||
</div></section>
|
||||
<section id="colo" class="wrap cards">
|
||||
<div class="card"><h3>Quarter rack</h3><p>10 U, 2 kW, 1 Gbps port and one IPv4 prefix.</p></div>
|
||||
<div class="card"><h3>Half rack</h3><p>22 U, 4 kW and a redundant 10 Gbps pair.</p></div>
|
||||
<div class="card"><h3>Private cage</h3><p>From eight racks, with your own access control and monitoring.</p></div>
|
||||
</section>
|
||||
<section id="transit" class="wrap prose">
|
||||
<h2>Transit and peering</h2>
|
||||
<p>We announce your prefix over two upstreams and the local internet exchange. BGP communities for traffic engineering, RPKI filtering and a looking glass are available to customers.</p>
|
||||
<table>
|
||||
<tr><th>Port</th><th>Committed rate</th></tr>
|
||||
<tr><td>1 Gbps</td><td>from 50 Mbps</td></tr>
|
||||
<tr><td>10 Gbps</td><td>from 1 Gbps</td></tr>
|
||||
<tr><td>2×10 Gbps</td><td>from 2 Gbps, redundant</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Meridian Networks" ;;
|
||||
7) # блог
|
||||
kl_site_open "Lumen Notes — Linux and networking" "Short notes about servers, nginx, storage and networks — written while working, mostly for myself." "Lumen Notes" '<a href="#posts">Posts</a><a href="#about">About</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Notes from a server room</h1>
|
||||
<p>Things I worked out once and do not want to work out again: nginx, systemd, ZFS, routing and the odd shell trick.</p>
|
||||
<a class="btn" href="#posts">Read the posts</a>
|
||||
</div></section>
|
||||
<section id="posts" class="wrap prose">
|
||||
<h2>Replacing a certificate without dropping connections</h2>
|
||||
<p>Reload, not restart. <code>systemctl reload nginx</code> keeps the listeners open and swaps the configuration, so long downloads and websockets survive. A restart does not.</p>
|
||||
<h2>Finding what filled the disk</h2>
|
||||
<p><code>du -xh --max-depth=1 / | sort -h</code> first, then follow the biggest branch. Journals under <code>/var/log/journal</code> are a common answer; <code>journalctl --vacuum-size=200M</code> trims them.</p>
|
||||
<h2>Checking that a port is really listening</h2>
|
||||
<p><code>ss -ltnp</code> shows TCP listeners and the process behind each one. If something answers on 443 but is not in the list, look for a proxy in front of it.</p>
|
||||
<h2>Backup is not a copy</h2>
|
||||
<p>A copy on the same disk protects against nothing but a fat finger. The test that matters is a restore, done on purpose, on a machine that is not the original.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Lumen Notes" ;;
|
||||
8) # бэкапы
|
||||
kl_site_open "Evercore Backup — backup and recovery" "Offsite backups with a documented restore procedure and a restore test every quarter." "Evercore Backup" '<a href="#policy">Policy</a><a href="#restore">Restore</a><a href="#contact">Contact</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>A backup you have never restored is a rumour</h1>
|
||||
<p>We copy your servers and databases to a separate location, keep versions, and prove once a quarter that a restore actually works.</p>
|
||||
<a class="btn" href="#policy">How it works</a>
|
||||
</div></section>
|
||||
<section id="policy" class="wrap cards">
|
||||
<div class="card"><h3>Nightly copies</h3><p>Files and databases, encrypted before they leave the server.</p></div>
|
||||
<div class="card"><h3>Versions</h3><p>Hourly for a day, daily for a month, monthly for a year.</p></div>
|
||||
<div class="card"><h3>Restore test</h3><p>A quarterly restore into a clean machine, with a short report.</p></div>
|
||||
</section>
|
||||
<section id="restore" class="wrap prose">
|
||||
<h2>Recovery</h2>
|
||||
<p>A lost file takes minutes: we restore the version you pick. A lost server takes an hour or two: a prepared image plus your latest copy, booted and checked before it goes back into service.</p>
|
||||
<p>Encryption keys are kept by you as well as by us, so that neither side alone can read the copies.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Evercore Backup" ;;
|
||||
9) # документация проекта
|
||||
kl_site_open "Halyard Docs — project documentation" "Installation, configuration and reference for a self-hosted service: everything in one place." "Halyard Docs" '<a href="#install">Install</a><a href="#config">Config</a><a href="#reference">Reference</a>'
|
||||
cat <<'HTML'
|
||||
<main>
|
||||
<section class="hero"><div class="wrap">
|
||||
<h1>Documentation</h1>
|
||||
<p>Installation, configuration and the reference pages. Written for the current release; older versions keep their own section.</p>
|
||||
<a class="btn" href="#install">Start here</a>
|
||||
</div></section>
|
||||
<section id="install" class="wrap prose">
|
||||
<h2>Installation</h2>
|
||||
<p>Download the archive, unpack it into <code>/opt/halyard</code> and run the setup script. The script asks three questions and writes a configuration file next to the binary.</p>
|
||||
<h2>Configuration</h2>
|
||||
<p>Settings live in <code>/etc/halyard/config.toml</code>. Every option has a default, so a minimal file is often enough. Changes are applied with <code>halyard reload</code> — no restart, no dropped connections.</p>
|
||||
</section>
|
||||
<section id="reference" class="wrap prose">
|
||||
<h2>Reference</h2>
|
||||
<table>
|
||||
<tr><th>Command</th><th>What it does</th></tr>
|
||||
<tr><td><code>halyard status</code></td><td>Shows the running version and the state of each component</td></tr>
|
||||
<tr><td><code>halyard check</code></td><td>Validates the configuration and the listening ports</td></tr>
|
||||
<tr><td><code>halyard logs</code></td><td>Follows the log with readable timestamps</td></tr>
|
||||
</table>
|
||||
<p>Questions and patches are welcome through the issue tracker; the address is on the contact page.</p>
|
||||
</section>
|
||||
</main>
|
||||
HTML
|
||||
kl_site_close "Halyard Docs" ;;
|
||||
*) kl_die "нет такого сайта: $1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
kl_site_current() { # какой из наших сайтов стоит сейчас (пусто — не наш)
|
||||
local i t
|
||||
[[ -s $KL_SITE_FILE ]] || return 0
|
||||
for i in "${!KL_SITE_TITLES[@]}"; do
|
||||
t=${KL_SITE_TITLES[i]}
|
||||
if grep -qF "<title>$t</title>" "$KL_SITE_FILE" 2>/dev/null; then
|
||||
printf '%s' "$((i + 1)). $t"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
kit_local_site() {
|
||||
kl_args "$@"
|
||||
local arg=${KL_REST[0]:-} n cur
|
||||
case $arg in
|
||||
--list | list)
|
||||
kl_say "сайты-заглушки в наборе (ставится случайный)"
|
||||
local i
|
||||
for i in "${!KL_SITE_TITLES[@]}"; do
|
||||
printf ' %2d. %-22s %s\n' "$((i + 1))" "${KL_SITE_TITLES[i]%% — *}" "${KL_SITE_TOPICS[i]}"
|
||||
done
|
||||
printf ' %s\n' "поставить конкретный: kit local site 7"
|
||||
return 0 ;;
|
||||
--show | show | status)
|
||||
cur=$(kl_site_current)
|
||||
if [[ -n $cur ]]; then kl_ok "сейчас стоит наш сайт: $cur"
|
||||
elif [[ -s $KL_SITE_FILE ]]; then kl_ok "сейчас стоит страница автора (случайная из его наборов)"
|
||||
else kl_warn "файла $KL_SITE_FILE нет"; fi
|
||||
return 0 ;;
|
||||
"" | random | --random) n=$((RANDOM % ${#KL_SITE_TITLES[@]})) ;;
|
||||
*)
|
||||
[[ $arg =~ ^[0-9]{1,2}$ ]] && ((10#$arg >= 1 && 10#$arg <= ${#KL_SITE_TITLES[@]})) \
|
||||
|| { kl_die "kit local site [номер 1..${#KL_SITE_TITLES[@]}|--list|--show]"; return 1; }
|
||||
n=$((10#$arg - 1)) ;;
|
||||
esac
|
||||
if kl_dry; then
|
||||
kl_say "заглушка: ${KL_SITE_TITLES[n]} (${KL_SITE_TOPICS[n]})"
|
||||
printf ' (dry-run) записал бы %s и синхронизировал бы ответ Hysteria2 (kit net masq on)\n' "$KL_SITE_FILE"
|
||||
return 0
|
||||
fi
|
||||
kl_need_root || return 1
|
||||
kl_say "заглушка: ${KL_SITE_TITLES[n]} (${KL_SITE_TOPICS[n]})"
|
||||
install -d -m 755 /var/www/kit
|
||||
[[ -f $KL_SITE_FILE ]] && kl_backup "$KL_SITE_FILE"
|
||||
if ! kl_site_html "$n" >"$KL_SITE_FILE.new"; then
|
||||
rm -f "$KL_SITE_FILE.new"; kl_die "не удалось собрать страницу"; return 1
|
||||
fi
|
||||
chmod 644 "$KL_SITE_FILE.new" && mv "$KL_SITE_FILE.new" "$KL_SITE_FILE" \
|
||||
|| { rm -f "$KL_SITE_FILE.new"; kl_die "не удалось заменить $KL_SITE_FILE"; return 1; }
|
||||
kl_ok "страница обновлена: $KL_SITE_FILE ($(wc -c <"$KL_SITE_FILE") байт)"
|
||||
# Порт 443 отдаёт файл через nginx (перезагрузка не нужна). UDP-порт Hysteria2 держит копию
|
||||
# страницы внутри подключения — её надо обновить, иначе ответы разойдутся.
|
||||
if kl_have kit; then
|
||||
if kit net masq on >/dev/null 2>&1; then
|
||||
kl_ok "Hysteria2 отвечает той же страницей"
|
||||
else
|
||||
kl_warn "Hysteria2 не синхронизирован (может, его нет в этой установке) — проверьте: kit net masq on"
|
||||
fi
|
||||
fi
|
||||
kl_ok "посмотреть снаружи: curl -k https://<IP-или-домен>/ ; состояние: kit local site --show, kit check"
|
||||
}
|
||||
|
||||
kit_local_cmd() {
|
||||
case ${1:-help} in
|
||||
help | -h | --help) kit_local_help ;;
|
||||
version | -v) kit_local_version ;;
|
||||
channel) kit_local_channel ;;
|
||||
seams) kit_local_seams ;;
|
||||
masking | mask) kit_local_masking ;;
|
||||
ssh | ssh-port) shift; kit_local_ssh "$@" ;;
|
||||
ping | icmp) shift; kit_local_ping "$@" ;;
|
||||
passwd | pass) shift; kit_local_passwd "$@" ;;
|
||||
upgrades | auto) shift; kit_local_upgrades "$@" ;;
|
||||
hardening | hard) shift; kit_local_hardening "$@" ;;
|
||||
site | stub | stub-site) shift; kit_local_site "$@" ;;
|
||||
*)
|
||||
printf 'Неизвестная команда форка: %s\n' "$1" >&2
|
||||
kit_local_help >&2
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Точка входа: вызывается из kit.sh с аргументами командной строки.
|
||||
# Если команда не наша — возвращаем 0, и kit работает ровно как раньше.
|
||||
kit_local_dispatch() {
|
||||
case ${1:-} in
|
||||
local)
|
||||
shift
|
||||
kit_local_cmd "$@"
|
||||
exit $?
|
||||
;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
@@ -577,3 +577,4 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
+17
-7
@@ -12,10 +12,17 @@
|
||||
set -Eeuo pipefail
|
||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||
|
||||
KIT_VERSION="1.2"
|
||||
KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
||||
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||
kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }
|
||||
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
||||
|
||||
# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---
|
||||
# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.
|
||||
# Все команды форка — под «kit local …» (см. kit local help).
|
||||
KIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"
|
||||
if [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi
|
||||
|
||||
|
||||
XUI_ENV=/etc/x-ui/install-result.env
|
||||
KIT_ENV=/etc/kit/kit.env
|
||||
@@ -454,8 +461,8 @@ cmd_del() {
|
||||
# есть только у автора, поэтому подменить обновление, взломав один GitHub, не выйдет.
|
||||
# Новый ключ приходит только в релизе, подписанном старым.
|
||||
KIT_SIGNERS=(
|
||||
# SHA256:VDuGgJ8dOeCNXB4nBZf3+kRlWthw3+vh8rfMxzMSRIM (itsnotkubrick, 2026-09-30)
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrTGCDhhnm8XO1ekpPJuSWRVCJiFiupEspfQxcbEBmz 3X-UI KIT releases"
|
||||
# Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)"
|
||||
)
|
||||
KIT_SIG_NS="3x-ui-kit-release"
|
||||
KIT_SIG_ID="releases@3x-ui-kit"
|
||||
@@ -498,7 +505,7 @@ cmd_version() {
|
||||
|
||||
# Что нового в версии $1 – из CHANGELOG.md, без разметки.
|
||||
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; curl -fsS -m 5 "$KIT_RAW/FORK-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 -E 's/ ?Спасибо .*$//; s/\[([^]]*)\]\([^)]*\)/\1/g; s/\*\*//g; s/`//g' | grep -v '^[[:space:]]*$' | head -40 || true
|
||||
}
|
||||
|
||||
@@ -519,7 +526,7 @@ fetch_release() { # версия каталог
|
||||
fi
|
||||
# Версия записана внутри подписанного файла: старый подписанный релиз под видом нового не пройдёт.
|
||||
grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }
|
||||
for f in scripts/kit.sh scripts/kit-sub.py; do
|
||||
for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do
|
||||
sum=$(awk -v f="$f" '$2 == f {print $1}' "$d/SHA256SUMS")
|
||||
[[ $sum =~ ^[0-9a-f]{64}$ ]] || { warn "В подписанном списке нет $f."; return 1; }
|
||||
curl -fsSL --retry 3 -o "$d/${f##*/}" "$raw/$f" || { warn "Не удалось скачать $f."; return 1; }
|
||||
@@ -715,6 +722,9 @@ cmd_update() {
|
||||
|
||||
# Через rename: bash дочитывает текущий kit по ходу работы, его файл трогать нельзя.
|
||||
install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit
|
||||
# Форк: ставим наши локальные дополнения из того же подписанного релиза.
|
||||
install -d -m 755 /usr/local/lib/kit
|
||||
install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh
|
||||
# Новые пути подписки (Clash и JSON) подключает уже новая версия kit.
|
||||
/usr/local/bin/kit __subextra >/dev/null 2>&1 || warn "Дополнительные пути подписки не подключились: kit update --force"
|
||||
echo
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# 3X-UI_KIT (форк hacker07): подтянуть обновление автора в ветку dev и прогнать проверки.
|
||||
#
|
||||
# Ничего не публикует и ничего не обновляет на серверах: после проверок печатает, что делать
|
||||
# дальше (версия, подпись, тег, push). Запускать с рабочей станции, по шагам.
|
||||
#
|
||||
# bash tools/fork-sync.sh # подтянуть автора и проверить
|
||||
# bash tools/fork-sync.sh --apply # ещё и вписать VERSION/KIT_VERSION (номер версии)
|
||||
#
|
||||
# Логика версии: у автора 1.3 → наш номер 1.3.1; если база та же, а мы что-то доработали —
|
||||
# 1.3.2. Номер обязан строго расти: kit у серверов не принимает откат назад.
|
||||
set -Eeuo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
PY=${PY:-$(command -v python3 || command -v python || true)}
|
||||
[[ -n $PY ]] || { echo "! нужен python3" >&2; exit 1; }
|
||||
|
||||
apply=no
|
||||
case ${1:-} in --apply) apply=yes ;; "") ;; *) echo "Использование: bash tools/fork-sync.sh [--apply]" >&2; exit 1 ;; esac
|
||||
|
||||
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
||||
die() { printf '! %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
[[ -z $(git status --porcelain) ]] || die "в рабочей копии есть незакоммиченные правки — сначала закоммить или убери их"
|
||||
|
||||
say "Забираю ветку автора"
|
||||
git fetch --prune upstream 'refs/heads/main:refs/heads/upstream'
|
||||
author_version=$(git show upstream:VERSION | tr -d '[:space:]')
|
||||
echo "у автора: $author_version"
|
||||
|
||||
say "Переношу наши правки на свежую ветку автора"
|
||||
git checkout -q dev
|
||||
if ! git rebase upstream; then
|
||||
echo
|
||||
echo "Конфликт при переносе. Это ожидаемо, если автор переписал те же строки."
|
||||
echo " 1) разбери конфликт: git status, правь файлы, git add, git rebase --continue"
|
||||
echo " 2) если проще собрать швы заново: git rebase --abort, затем"
|
||||
echo " git checkout -B dev upstream && $PY tools/fork_apply.py && git commit -am 'fork: швы'"
|
||||
die "остановился, ничего не публикую"
|
||||
fi
|
||||
|
||||
say "Проверяю, что наши швы ещё на месте"
|
||||
"$PY" tools/fork_apply.py --check
|
||||
|
||||
say "Проверки кода (как на стороне сервера и в CI автора)"
|
||||
for f in scripts/*.sh tools/*.sh; do bash -n "$f" || die "$f: синтаксис"; done
|
||||
echo " ✓ bash -n"
|
||||
"$PY" -m py_compile scripts/kit-sub.py && echo " ✓ python-скрипты собираются"
|
||||
if grep -qU $'\r' scripts/*.sh tools/*.sh VERSION 2>/dev/null; then
|
||||
die "в файлах есть CRLF — сервер сверяет SHA256, и его сумма не сойдётся. Проверь core.autocrlf (нужен false)"
|
||||
fi
|
||||
echo " ✓ переводы строк LF"
|
||||
if command -v shellcheck >/dev/null; then
|
||||
shellcheck -S warning -e SC1090,SC2034,SC2211 scripts/*.sh tools/release.sh tools/fork-sync.sh && echo " ✓ shellcheck"
|
||||
else
|
||||
echo " – shellcheck не установлен, пропускаю"
|
||||
fi
|
||||
if command -v node >/dev/null; then
|
||||
node --test tools/test/*.test.js >/dev/null && echo " ✓ node --test"
|
||||
else
|
||||
echo " – node нет, пропускаю тесты генераторов"
|
||||
fi
|
||||
"$PY" tools/test/restore_env_test.py >/dev/null && "$PY" tools/test/restore_archive_test.py >/dev/null \
|
||||
&& echo " ✓ тесты копии (--restore)"
|
||||
|
||||
say "Готово: наши правки лежат на свежем коде автора"
|
||||
git --no-pager log --oneline upstream..dev
|
||||
echo
|
||||
echo "Версия автора: $author_version · наша сейчас: $(tr -d '[:space:]' <VERSION)"
|
||||
|
||||
if [[ $apply == yes ]]; then
|
||||
base=${author_version%%.*}
|
||||
cur=$(tr -d '[:space:]' <VERSION)
|
||||
if [[ ${cur%%.*} == "$base" ]]; then
|
||||
new="$base.$(( ${cur##*.} + 1 ))"
|
||||
else
|
||||
new="$author_version.1"
|
||||
fi
|
||||
printf '%s\n' "$new" >VERSION
|
||||
"$PY" tools/fork_apply.py >/dev/null
|
||||
echo "Версия сборки → $new (VERSION и KIT_VERSION в трёх скриптах)"
|
||||
fi
|
||||
|
||||
cat <<'NEXT'
|
||||
|
||||
Дальше вручную, по шагам:
|
||||
1) подпись и релиз: bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key
|
||||
2) коммит и тег: git add -A && git commit -m "Релиз v$(cat VERSION): швы форка + подпись"
|
||||
git tag v$(cat VERSION)
|
||||
3) публикация: git push origin dev v$(cat VERSION) upstream
|
||||
4) на сервере: kit update --force && kit check
|
||||
NEXT
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""3X-UI_KIT (форк hacker07): накатывает наши «швы» на чистый код автора.
|
||||
|
||||
Это источник правды для форка: единственное место, где описано, ЧТО именно мы правим в
|
||||
файлах автора. Всё, что мы дописываем по существу, живёт в scripts/kit-local.sh — файле,
|
||||
который автор не трогает, поэтому обновления автора его не ломают.
|
||||
|
||||
Правила:
|
||||
* каждый якорь обязан найтись РОВНО один раз, иначе — стоп (автор изменил код, правку
|
||||
надо пересмотреть руками);
|
||||
* при любой ошибке ничего не пишется наполовину: сначала прогоняй --check;
|
||||
* открытый ключ подписи берётся из tools/fork-signer.pub (закрытая часть — только на
|
||||
рабочей станции владельца, в репозиторий не попадает).
|
||||
|
||||
Использование:
|
||||
python3 tools/fork_apply.py --check [корень-репозитория]
|
||||
python3 tools/fork_apply.py [корень-репозитория]
|
||||
"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
GITEA = os.environ.get("FORK_GITEA_BASE", "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS")
|
||||
SIGNER_FILE = os.environ.get("FORK_SIGNER_PUB", "tools/fork-signer.pub")
|
||||
|
||||
# README.md форка — НАШ файл целиком: авторское описание из него убрано, чтобы никто не
|
||||
# ставил сервер авторской командой и не оказался на авторском канале обновлений. Шва здесь нет:
|
||||
# при обновлении из авторского проекта README конфликтует и решается в нашу пользу (см. FORK.md).
|
||||
HOOK = """
|
||||
# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---
|
||||
# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.
|
||||
# Все команды форка — под «kit local …» (см. kit local help).
|
||||
KIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"
|
||||
if [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi
|
||||
"""
|
||||
|
||||
|
||||
def die(msg):
|
||||
print("! " + msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def ok(msg):
|
||||
print("==> " + msg)
|
||||
|
||||
|
||||
def count(text, anchor, regex):
|
||||
if regex:
|
||||
return len(re.findall(anchor, text, re.MULTILINE))
|
||||
return text.count(anchor)
|
||||
|
||||
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:]]
|
||||
check = False
|
||||
if "--check" in args:
|
||||
check = True
|
||||
args.remove("--check")
|
||||
root = pathlib.Path(args[0] if args else ".").resolve()
|
||||
if not (root / "VERSION").exists():
|
||||
die("%s не похож на репозиторий 3X-UI KIT (нет VERSION)" % root)
|
||||
os.chdir(root)
|
||||
|
||||
version = (root / "VERSION").read_text(encoding="utf-8").strip()
|
||||
if not re.fullmatch(r"[0-9]+(\.[0-9]+)+", version):
|
||||
die("VERSION=%r: нужен числовой номер вида 1.2.1" % version)
|
||||
|
||||
signer_path = pathlib.Path(SIGNER_FILE)
|
||||
if not signer_path.exists():
|
||||
die(
|
||||
"нет %s — сначала создай ключ подписи:\n"
|
||||
" ssh-keygen -t ed25519 -C \"3X-UI KIT fork releases\" -f ~/.ssh/3x-ui-kit-fork/release_key\n"
|
||||
" mkdir -p tools && ssh-keygen -y -f ~/.ssh/3x-ui-kit-fork/release_key > tools/fork-signer.pub"
|
||||
% SIGNER_FILE
|
||||
)
|
||||
pub = ""
|
||||
for line in signer_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith(("ssh-ed25519 ", "ssh-rsa ")):
|
||||
pub = line
|
||||
break
|
||||
if not pub:
|
||||
die("%s: не нашёл строку открытого ключа (ssh-ed25519 …)" % signer_path)
|
||||
|
||||
version_re = r'^KIT_VERSION="[0-9.]+"$'
|
||||
edits = [
|
||||
# --- scripts/kit.sh -------------------------------------------------
|
||||
("scripts/kit.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
|
||||
False,
|
||||
'KIT_RAW="%s/raw/branch/main"' % GITEA,
|
||||
"источник VERSION и подсказки об обновлении",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }',
|
||||
False,
|
||||
'kit_ref_raw() { echo "%s/raw/tag/v$1"; }' % GITEA + "\n" + HOOK,
|
||||
"файлы релиза из нашего тега + подключение kit-local.sh",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
r' # SHA256:[^\n]*\n "ssh-ed25519 [^\n]*"\n',
|
||||
True,
|
||||
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n'
|
||||
' "%s"\n' % pub,
|
||||
"ключ проверки подписи релизов",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
"for f in scripts/kit.sh scripts/kit-sub.py; do",
|
||||
False,
|
||||
"for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do",
|
||||
"наш файл в списке проверяемых и скачиваемых",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit',
|
||||
False,
|
||||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n'
|
||||
" # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n"
|
||||
" install -d -m 755 /usr/local/lib/kit\n"
|
||||
' install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh',
|
||||
"установка kit-local.sh при обновлении",
|
||||
),
|
||||
# --- scripts/3x-ui.sh -----------------------------------------------
|
||||
("scripts/3x-ui.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"',
|
||||
False,
|
||||
'KIT_RAW="%s/raw/tag/v$KIT_VERSION"' % GITEA,
|
||||
"запасная загрузка kit из нашего релиза",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"',
|
||||
False,
|
||||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"',
|
||||
"адрес нашего файла для установщика",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"',
|
||||
False,
|
||||
'KIT_INSTALL_CMD="bash <(curl -fsSL %s/raw/branch/main/scripts/3x-ui.sh)"' % GITEA,
|
||||
"команда установки, которую показывает панель",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||||
False,
|
||||
" # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n"
|
||||
" install -d -m 755 /usr/local/lib/kit\n"
|
||||
' if [[ -n $src && -f $d/kit-local.sh ]]; then\n'
|
||||
' install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n'
|
||||
" else\n"
|
||||
' curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n'
|
||||
' || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n'
|
||||
" fi\n"
|
||||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||||
"установка kit-local.sh установщиком",
|
||||
),
|
||||
# --- scripts/hysteria2.sh -------------------------------------------
|
||||
("scripts/hysteria2.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||||
# --- tools/release.sh -----------------------------------------------
|
||||
(
|
||||
"tools/release.sh",
|
||||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py VERSION)",
|
||||
False,
|
||||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)",
|
||||
"наш файл в подписанном списке релиза",
|
||||
),
|
||||
(
|
||||
"tools/release.sh",
|
||||
'{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}"; } >SHA256SUMS',
|
||||
False,
|
||||
"{ echo \"# 3X-UI KIT $v\"; sha \"${FILES[@]}\" | awk '{ sub(/^\\*/, \"\", $2); print $1\" \"$2 }'; } >SHA256SUMS",
|
||||
"суммы в POSIX-виде без звёздочки (на Windows sha256sum её ставит, и kit её не понимает)",
|
||||
),
|
||||
# --- свои записи о версии (наш файл, авторский CHANGELOG не трогаем) ----
|
||||
(
|
||||
"tools/release.sh",
|
||||
'grep -q "^## v$v" CHANGELOG.md || die "В CHANGELOG.md нет раздела ## v$v"',
|
||||
False,
|
||||
'(grep -q "^## v$v" CHANGELOG.md || grep -q "^## v$v" FORK-CHANGELOG.md)'
|
||||
' || die "Нет раздела ## v$v ни в CHANGELOG.md, ни в FORK-CHANGELOG.md (форк ведёт свои записи в FORK-CHANGELOG.md)"',
|
||||
"раздел версии форка — в нашем файле",
|
||||
),
|
||||
(
|
||||
"tools/release.sh",
|
||||
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md) ]]',
|
||||
False,
|
||||
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]]',
|
||||
"наш файл записей тоже должен быть закоммичен",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'',
|
||||
False,
|
||||
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'',
|
||||
"«что нового» ищет и в наших записях",
|
||||
),
|
||||
]
|
||||
|
||||
bad = 0
|
||||
for path, anchor, is_re, new, why in edits:
|
||||
p = pathlib.Path(path)
|
||||
if not p.exists():
|
||||
print("✗ %s: файла нет" % path)
|
||||
bad += 1
|
||||
continue
|
||||
text = p.read_text(encoding="utf-8", newline="")
|
||||
if check:
|
||||
# --check смотрит на готовое дерево dev: наш шов обязан быть на месте и ровно один раз
|
||||
n = text.count(new)
|
||||
what, shown = "наш шов", new
|
||||
else:
|
||||
# применение — только на чистое дерево автора: якорь обязан найтись ровно один раз
|
||||
n = count(text, anchor, is_re)
|
||||
what, shown = "якорь автора", anchor
|
||||
short = shown if len(shown) <= 54 else shown[:51] + "…"
|
||||
short = short.replace("\n", "⏎")
|
||||
if n != 1:
|
||||
print("✗ %-20s %-56s %s найден %d раз (%s)" % (path, short, what, n, why))
|
||||
bad += 1
|
||||
continue
|
||||
if check:
|
||||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||||
continue
|
||||
if is_re:
|
||||
text = re.sub(anchor, new, text, count=1, flags=re.MULTILINE)
|
||||
else:
|
||||
text = text.replace(anchor, new, 1)
|
||||
p.write_text(text, encoding="utf-8", newline="")
|
||||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||||
|
||||
if bad:
|
||||
if check:
|
||||
die("якорей не найдено: %d — автор изменил код, пересмотри правки в tools/fork_apply.py" % bad)
|
||||
die("якорей не найдено: %d — правки не применены" % bad)
|
||||
if check:
|
||||
ok("все якоря на месте (версия сборки %s)" % version)
|
||||
else:
|
||||
ok("швы накатаны, версия сборки %s" % version)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+11
-4
@@ -19,17 +19,24 @@ key=${1:-$HOME/.ssh/3xui_kit_release}
|
||||
[[ -f $key ]] || die "Нет закрытого ключа $key. Создать: ssh-keygen -t ed25519 -C \"3X-UI KIT releases\" -f $key"
|
||||
NS="3x-ui-kit-release"
|
||||
ID="releases@3x-ui-kit"
|
||||
FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py VERSION)
|
||||
FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)
|
||||
|
||||
v=$(tr -d '[:space:]' <VERSION)
|
||||
[[ $v =~ ^[0-9]+(\.[0-9]+)+$ ]] || die "VERSION: $v"
|
||||
for f in scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh; do
|
||||
grep -qx "KIT_VERSION=\"$v\"" "$f" || die "$f: KIT_VERSION не $v"
|
||||
done
|
||||
grep -q "^## v$v" CHANGELOG.md || die "В CHANGELOG.md нет раздела ## v$v"
|
||||
(grep -q "^## v$v" CHANGELOG.md || grep -q "^## v$v" FORK-CHANGELOG.md) || die "Нет раздела ## v$v ни в CHANGELOG.md, ни в FORK-CHANGELOG.md (форк ведёт свои записи в FORK-CHANGELOG.md)"
|
||||
ok "Версия $v во всех скриптах и в CHANGELOG"
|
||||
|
||||
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||
# Переводы строк. Сервер получает файл из git (там LF), а суммы считаются по рабочей копии:
|
||||
# если в рабочей копии окажутся CR, суммы не сойдутся и kit update откажется ставить релиз.
|
||||
for f in "${FILES[@]}"; do
|
||||
[[ -z $(tr -cd "\r" <"$f") ]] || die "$f: переводы строк Windows (CR). Уберите: tr -d '\r' < \"$f\" > \"$f.lf\" && mv \"$f.lf\" \"$f\""
|
||||
done
|
||||
ok "Переводы строк во всех файлах релиза — LF"
|
||||
|
||||
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||
|
||||
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
|
||||
xv=$(sed -n 's/^XUI_VERSION="\(.*\)"/\1/p' scripts/3x-ui.sh)
|
||||
@@ -70,7 +77,7 @@ pub=$(ssh-keygen -y -f "$key" | awk '{print $1, $2}')
|
||||
signers scripts/kit.sh | awk '{print $1, $2}' | grep -qxF "$pub" || die "Ключа $key нет в KIT_SIGNERS – серверы не примут такую подпись."
|
||||
ok "Ключ подписи записан в kit"
|
||||
|
||||
{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}"; } >SHA256SUMS
|
||||
{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
|
||||
rm -f SHA256SUMS.sig
|
||||
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null
|
||||
tmp=$(mktemp)
|
||||
|
||||
Reference in New Issue
Block a user