Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad7b66dbf8 | ||
|
|
6323c28533 | ||
|
|
6da8650d1d | ||
|
|
8d470f3eb1 | ||
|
|
c270b09891 | ||
|
|
a1ce2f1c54 | ||
|
|
65f29782fe | ||
|
|
db549348e5 | ||
|
|
7cc7e684d5 | ||
|
|
5e316fb1be | ||
|
|
6b497b047d |
@@ -0,0 +1,32 @@
|
||||
name: Синхронизация с автором
|
||||
|
||||
# Раз в сутки проверяем обновления у автора проекта и, если они есть, переносим наши правки
|
||||
# на свежий код и публикуем в ветку dev. Релиз и подпись здесь НЕ делаются: закрытый ключ
|
||||
# подписи лежит только на рабочей станции владельца.
|
||||
#
|
||||
# Ручной запуск: в Gitea → Actions → «Синхронизация с автором» → Run workflow.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 3 * * *" # каждую ночь в 03:30 по времени сервера Gitea
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
paths:
|
||||
- ".gitea/workflows/fork-sync.yml"
|
||||
|
||||
concurrency:
|
||||
group: fork-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Подтянуть обновления автора, проверить и опубликовать
|
||||
env:
|
||||
GITEA_URL: http://10.10.10.54:3000
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSL "$GITEA_URL/hacker07/3X-UI_autoVPS/raw/branch/main/tools/fork-sync-bot.sh" -o /tmp/fork-sync-bot.sh
|
||||
bash /tmp/fork-sync-bot.sh
|
||||
+37
-2
@@ -10,6 +10,41 @@
|
||||
канал обновлений и оговорки. Две картинки — наши (`manuals/assets/fork-banner.svg` и
|
||||
`manuals/assets/script-3x-ui-local.svg`, терминал собран из настоящего вывода скрипта).
|
||||
* Шов в README больше не нужен (`tools/fork_apply.py`): файл наш целиком, как `kit-local.sh`.
|
||||
## v1.2.8: доступ к панели в конце установки и снос сервера из меню
|
||||
|
||||
* В конце установки (перед подпиской) печатается **доступ к панели 3X-UI**: адрес, логин и пароль из
|
||||
файла `/root/3x-ui.txt` — раньше это было видно только в общем потоке вывода установщика.
|
||||
* **`kit local uninstall`** — снос установки целиком: панель и служба `x-ui`, ядро Xray, службы и файлы
|
||||
kit (`kit-sub`, автообновление, cron), конфиги nginx и сайт-заглушка, сертификаты (acme.sh),
|
||||
наши правки системы (правило ICMP, обновления безопасности), наши бэкапы и файл с данными панели.
|
||||
Правила ufw для 80/443 снимаются, ответ на ping возвращается. Спрашивает подтверждение словом
|
||||
«удалить», есть `--dry-run`.
|
||||
**SSH не трогается** — ни порт, ни конфиги, ни правила для него: иначе легко потерять доступ к серверу.
|
||||
В главном меню `kit` появился пункт **10. Удалить всё установленное**.
|
||||
|
||||
## v1.2.7: имя 3X-UI_autoVPS, справка kit help, подписка с QR, живые кнопки на заглушках
|
||||
|
||||
* **Сборка называется `3X-UI_autoVPS`** (было `3X-UI KIT`): баннер сервера — `Hacker 07` в том же
|
||||
стиле, новое имя в справке, версии, описаниях служб и в имени профиля в клиенте. В файлах автора
|
||||
это сделано швами (`tools/fork_apply.py`), поэтому обновление автора правки не затирает.
|
||||
* **Совместимость шапки релиза.** В подписанном `SHA256SUMS` этого релиза шапка пишется **двумя
|
||||
строками** (старая и новая), а проверка на сервере принимает обе. Иначе уже установленные серверы
|
||||
отказались бы ставить обновление: они сверяют точный вид строки. Со следующего релиза останется
|
||||
только новое имя.
|
||||
* Убрано дублирование подсказки «Дальше обязательно» при смене порта SSH: она печаталась и
|
||||
установщиком, и командой `kit local ssh`.
|
||||
* **`kit help` — полный справочник по категориям**: пользователи, подключения и порты, маскировка и
|
||||
сайт, проверка и починка, обслуживание и обновление, защита сервера (наш слой). Раньше `kit help`
|
||||
показывал короткий список автора и не упоминал наши команды. Чтобы справка не отставала молча,
|
||||
добавлена проверка `tools/check-help.sh`: она сверяет справку с диспетчером команд и встроена в
|
||||
сборку релиза (`tools/release.sh`).
|
||||
* У сайтов-заглушек все ссылки и кнопки **живые**: нажатие открывает окно «раздел в разработке,
|
||||
приносим извинения» (закрыть — кнопкой, кликом мимо окна или Esc). Никаких внешних запросов:
|
||||
скрипт и стили внутри страницы.
|
||||
* В самом конце установки (после всех проверок) печатается то, что нужно сохранить: **подписка
|
||||
с QR-кодом** и ссылки по протоколам — авторской командой `kit user link <имя> --all`, поэтому
|
||||
вывод совпадает с тем, что показывает `kit` потом. Отключается флагом `--no-sub`.
|
||||
|
||||
* Набор по умолчанию — **REALITY + AmneziaWG-3.1** (самый совместимый минимум); ответ «все»
|
||||
ставит полный набор автора. Умолчание задаётся в одном месте (`DEFAULT_PROTOS`).
|
||||
* **Сайт-заглушка переделан на ИТ-тематику:** десять правдоподобных сайтов (дата-аналитика, DevOps,
|
||||
@@ -85,7 +120,7 @@
|
||||
* Ping отключается правилом ufw (`before.rules`), а не общесистемным `icmp_echo_ignore_all`:
|
||||
видно в `ufw status`, отменяется одной командой и не глушит ICMP для внутренних интерфейсов.
|
||||
|
||||
## v1.2.1: сборка форка поверх 3X-UI KIT 1.2
|
||||
## v1.2.1: сборка форка поверх 3X-UI_autoVPS 1.2
|
||||
|
||||
* Источник файлов и обновлений — свой репозиторий Gitea: установка и `kit update` берут релиз
|
||||
оттуда, а подпись релиза проверяется ключом форка. Авторский ночной апдейт приходит в наш
|
||||
@@ -97,4 +132,4 @@
|
||||
* Швы в коде автора описаны в `tools/fork_apply.py` (источник правды), синхронизация с автором и
|
||||
проверки — `tools/fork-sync.sh`.
|
||||
|
||||
Основа — 3X-UI KIT 1.2 (панель 3X-UI v3.9.0, ядро Xray v26.6.27). Что нового у автора — в `CHANGELOG.md`.
|
||||
Основа — 3X-UI_autoVPS 1.2 (панель 3X-UI v3.9.0, ядро Xray v26.6.27). Что нового у автора — в `CHANGELOG.md`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Форк 3X-UI KIT (hacker07) — своя сборка и свой канал обновлений
|
||||
# Форк 3X-UI_autoVPS (hacker07) — своя сборка и свой канал обновлений
|
||||
|
||||
Коротко: это [3X-UI KIT](https://github.com/itsnotkubrick/3X-UI_KIT) со своими доработками. Наш код
|
||||
Коротко: это [3X-UI_autoVPS](https://github.com/itsnotkubrick/3X-UI_KIT) со своими доработками. Наш код
|
||||
живёт рядом с кодом автора и не ломается, когда автор выпускает новую версию, а серверы ставятся и
|
||||
обновляются **из этого репозитория** — релизы подписываются нашим ключом.
|
||||
|
||||
@@ -148,6 +148,18 @@ kit update --force && kit check # тянет наш релиз из это
|
||||
|
||||
## Обновление от автора (с рабочей станции)
|
||||
|
||||
### Автоматически: робот на раннере (CT 114)
|
||||
|
||||
Раз в сутки (03:30) Gitea сама запускает `.gitea/workflows/fork-sync.yml` на раннере в CT 114:
|
||||
робот `tools/fork-sync-bot.sh` забирает ветку автора, переносит на неё наши правки, прогоняет те же
|
||||
проверки, что и `tools/fork-sync.sh`, дописывает раздел в `FORK-CHANGELOG.md` и публикует `dev`
|
||||
(`--force-with-lease`). Не сошлось (конфликт в местах наших правок) — **ничего не публикуется**,
|
||||
причина видна в Gitea → Actions и в `/opt/kit-fork-sync/last-run.log` на контейнере.
|
||||
|
||||
Робот **не** выпускает и не подписывает релиз: закрытый ключ подписи остаётся на рабочей станции.
|
||||
После автоматической синхронизации — как обычно: `git fetch origin && git checkout dev && git reset --hard origin/dev`
|
||||
(робот переписывает `dev`), описать изменения в `FORK-CHANGELOG.md`, затем `tools/release.sh` и тег.
|
||||
|
||||
```bash
|
||||
cd H:/My_Proj/Fork_3X-UI_KIT
|
||||
bash tools/fork-sync.sh # подтянуть автора, перенести наши правки, прогнать проверки
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**Сервер одной командой: панель 3X-UI, VPN-протоколы за одним портом 443, одна подписка на все устройства**
|
||||
|
||||
Сборка со **своим каналом обновлений**: сервер ставится и обновляется из этого репозитория, релизы
|
||||
подписаны, ночное автообновление не вернёт чужую версию. В основе — открытый проект 3X-UI KIT.
|
||||
подписаны, ночное автообновление не вернёт чужую версию. В основе — открытый проект 3X-UI_autoVPS.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
+9
-8
@@ -1,8 +1,9 @@
|
||||
# 3X-UI KIT 1.2.6
|
||||
db677f17e1c8f59e0c5f329faff6f6178454b75b266966b80c225b8923318831 scripts/3x-ui.sh
|
||||
b8bc9cb115e427b601d2d9ea0b3e1c759b4fc0f9f36b267e364e47658422e37d scripts/hysteria2.sh
|
||||
1215b3435a5f476dee26fd732640c6d33a7445585f14108a289ae4eafb05b439 scripts/kit.sh
|
||||
d393dc04a69bad2ce9f022b2f51e0d349b6655c5c92fc64cf67402c5a63e2f3a scripts/kit-sub.py
|
||||
ae81cd252affc1bbfdc59421dfa30f85eb6bbbd3e178faceb7e6f708f36b6730 scripts/kit-local.sh
|
||||
eaf72eb353c14de8bc9cd567b396efc40eb1493c3839009465f0f8b35f0db557 scripts/3x-ui-local.sh
|
||||
5844ffcc346f89c07b13ba7596bfb3788ed73f4755e541182d7822d43b7c7a24 VERSION
|
||||
# 3X-UI KIT 1.2.8
|
||||
# 3X-UI_autoVPS 1.2.8
|
||||
b0497a26637fd2ef2aae6e512a9afc49f4f6f64b639994c9f029fa4c0977a3a8 scripts/3x-ui.sh
|
||||
03589cb294acf83be77aeb170eb8adcfc2f901437e437ebc6c1b56bb18579333 scripts/hysteria2.sh
|
||||
7d328be5414ab544d0b29e89db3ef172a7d650f3ac0b80bade25ac7136709a47 scripts/kit.sh
|
||||
4027ea2c6dbc9fd176356acac3f676445419c2f021146ed25dfe5a304c3ac5fb scripts/kit-sub.py
|
||||
8216a98d82712dd320e6f158212b557c01c5aa94f8099c89ce33fe5fc39f9002 scripts/kit-local.sh
|
||||
bf961b4e33bfb9e4d18fd9edcea0a71320d6de9a8304c17b6cbeb95aef04affc scripts/3x-ui-local.sh
|
||||
d2d31534641da01801512a3ec793bc8d57132d4cefa19a5fb503e8995f39eb8f VERSION
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
-----BEGIN SSH SIGNATURE-----
|
||||
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgozRJ+f9RB6qO9S9j3BWCDtC0bV
|
||||
FHakRSSqtQPljLoQUAAAARM3gtdWkta2l0LXJlbGVhc2UAAAAAAAAABnNoYTUxMgAAAFMA
|
||||
AAALc3NoLWVkMjU1MTkAAABAHRB6TB0NGpmJ1w5Ua1PEGUrmlQUuM99cll5nnhO9EJvMRz
|
||||
IoDmvnCMGC4Xgh22IeRR88vtLkKz3hgtMoMYRLAg==
|
||||
AAALc3NoLWVkMjU1MTkAAABAc/dTe+Zehd28Zfczj1mJXWHNZGSAx8Lbr+jqpF7oYcVNq9
|
||||
ExRrCHUH3GS7IDR2KRJRV8zqVyEDwBDat6ZQhZBQ==
|
||||
-----END SSH SIGNATURE-----
|
||||
|
||||
+45
-12
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
# 3X-UI_autoVPS (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
#
|
||||
# Что делает (по шагам):
|
||||
# 1) проверяет сервер и наличие curl, ssh-keygen, openssl;
|
||||
@@ -14,7 +14,8 @@
|
||||
# 6) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
|
||||
# безопасности, перевести SSH на другой порт, сменить пароль root (поведение из StressOzz/vps-setup,
|
||||
# но безопаснее);
|
||||
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
|
||||
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening;
|
||||
# 8) последним — подписка, QR-код и ссылки по протоколам: то, что нужно сохранить.
|
||||
#
|
||||
# Запуск:
|
||||
# # одна команда, дальше — вопросы:
|
||||
@@ -41,12 +42,12 @@
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
KIT_VERSION="1.2.6" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
KIT_VERSION="1.2.8" # версия сборки форка; должна совпадать с VERSION релиза
|
||||
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
|
||||
NS="3x-ui-kit-release"
|
||||
SIG_ID="releases@3x-ui-kit"
|
||||
# Публичный ключ форка. Меняется только вместе с ключом подписи (тогда обновляется и здесь, и в kit).
|
||||
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)'
|
||||
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)'
|
||||
|
||||
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
|
||||
[[ -t 1 ]] || { G=; Y=; R=; B=; D=; N=; }
|
||||
@@ -56,7 +57,7 @@ die() { printf '%s\n' "${R}✗${N} $*" >&2; exit 1; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
3X-UI_autoVPS (форк hacker07) — установка сервера одной командой из нашего канала.
|
||||
|
||||
bash <(curl -fsSL <наш Gitea>/raw/branch/main/scripts/3x-ui-local.sh) [флаги…]
|
||||
|
||||
@@ -77,6 +78,7 @@ usage() {
|
||||
--yes ни о чём не спрашивать (для автоматизации)
|
||||
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
|
||||
не трогать авторскую страницу (то же: --no-site)
|
||||
--no-sub не показывать в конце подписку, QR-код и ссылки по протоколам
|
||||
--no-time не запускать kit fix (время и безопасные починки)
|
||||
--no-check не запускать в конце kit check
|
||||
--verify-only только проверить релиз (подпись и SHA256) и выйти
|
||||
@@ -89,6 +91,7 @@ EOF
|
||||
|
||||
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
|
||||
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
|
||||
show_sub=yes # в самом конце показать подписку, QR-код и ссылки
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
--harden) harden=yes ;;
|
||||
@@ -99,6 +102,7 @@ while (($#)); do
|
||||
--site) site=${2:-random}; shift || die "--site: нужен номер сайта (1..10), random или off" ;;
|
||||
--site=*) site=${1#*=} ;;
|
||||
--no-site) site=off ;;
|
||||
--no-sub) show_sub=no ;;
|
||||
--no-time) fix_time=no ;;
|
||||
--no-check) do_check=no ;;
|
||||
--verify-only) verify_only=yes ;;
|
||||
@@ -149,6 +153,7 @@ if [[ $dry == yes ]]; then
|
||||
esac
|
||||
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
|
||||
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
|
||||
[[ $show_sub == yes ]] && pre "в самом конце: подписка, QR-код и ссылки по протоколам (--no-sub — не надо)"
|
||||
echo
|
||||
say "запуск без --dry-run выполнит всё это на сервере"
|
||||
exit 0
|
||||
@@ -380,12 +385,6 @@ 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: в флагах его нет намеренно — значение в командной строке видно в списке процессов
|
||||
@@ -405,9 +404,43 @@ fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI KIT $KIT_VERSION, канал: наш Gitea).
|
||||
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI_autoVPS $KIT_VERSION, канал: наш Gitea).
|
||||
|
||||
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
|
||||
если что-то не так: kit check --deep, затем kit fix
|
||||
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
|
||||
EOF
|
||||
|
||||
# ------------------------------------------------------------------ доступ к панели (перед подпиской)
|
||||
|
||||
if [[ $show_sub == yes ]]; then
|
||||
say "панель 3X-UI: адрес, логин и пароль"
|
||||
if [[ -s /root/3x-ui.txt ]]; then
|
||||
printf ' файл: /root/3x-ui.txt (виден только root). Сохраните данные себе:\n'
|
||||
sed 's/^/ /' /root/3x-ui.txt
|
||||
else
|
||||
warn "файла /root/3x-ui.txt нет — адрес панели и логин были в выводе установщика выше"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ подписка, QR-код и доступы
|
||||
|
||||
# Показываем в самом конце, после всех проверок: это то, что человек уносит с собой.
|
||||
# Печатает авторская команда (подписка + QR-код через qrencode + ссылки по протоколам),
|
||||
# поэтому вывод не расходится с тем, что показывает `kit` потом.
|
||||
if [[ $show_sub == yes ]]; then
|
||||
sub_user=admin
|
||||
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
|
||||
[[ ${INSTALLER_ARGS[_i]} == --user ]] && sub_user=${INSTALLER_ARGS[_i + 1]:-admin}
|
||||
done
|
||||
say "подписка и доступы — это то, что стоит сохранить"
|
||||
printf ' подписка — одна ссылка на все устройства; QR-код ниже наводите камерой в приложении\n'
|
||||
printf ' приложения: Happ, Hiddify, Karing, v2rayN, Clash/Mihomo\n'
|
||||
printf ' отдельные ссылки по протоколам — ниже же (если приложение не умеет подписку)\n'
|
||||
printf ' %sпозже то же самое: kit user link %s --all (или меню: kit)%s\n' "$D" "$sub_user" "$N"
|
||||
if ! kit user link "$sub_user" --all; then
|
||||
warn "не смог показать ссылки для «$sub_user» — список пользователей: kit user list"
|
||||
fi
|
||||
else
|
||||
say "ссылки и QR-код не показываю (--no-sub). Посмотреть: kit user link admin --all"
|
||||
fi
|
||||
|
||||
+22
-22
@@ -30,7 +30,7 @@ wait_apt_idle() {
|
||||
XUI_VERSION="v3.9.0"
|
||||
# SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30).
|
||||
XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6"
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.8"
|
||||
# kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main.
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION"
|
||||
# Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash,
|
||||
@@ -372,15 +372,15 @@ kit_banner() {
|
||||
echo
|
||||
printf '%s' "$G"
|
||||
cat <<'ART'
|
||||
_ _ _ _ _ _ _
|
||||
(_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __
|
||||
| | __/ __| '_ \ / _ \| __| |/ / | | | '_ \| '__| |/ __| |/ /
|
||||
| | |_\__ \ | | | (_) | |_| <| |_| | |_) | | | | (__| <
|
||||
|_|\__|___/_| |_|\___/ \__|_|\_\\__,_|_.__/|_| |_|\___|_|\_\
|
||||
_ _ _ ___ _____
|
||||
| | | | __ _ ___| | _____ _ __ / _ \___ |
|
||||
| |_| |/ _` |/ __| |/ / _ \ '__| | | | | / /
|
||||
| _ | (_| | (__| < __/ | | |_| |/ /
|
||||
|_| |_|\__,_|\___|_|\_\___|_| \___//_/
|
||||
ART
|
||||
printf '%s' "$N"
|
||||
echo
|
||||
echo "${B}3X-UI KIT $KIT_VERSION${N} · it's not Kubrick. it's just a VPN."
|
||||
echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"
|
||||
echo "${D}на основе панели 3X-UI (MHSanaei/3x-ui) и ядра Xray · github.com/itsnotkubrick/3X-UI_KIT${N}"
|
||||
}
|
||||
|
||||
@@ -761,7 +761,7 @@ main() {
|
||||
[[ $SINGLE == yes ]] && links=$(sed "s/^\(tg:\/\/proxy?\)\(.*\)port=${INNER[mtproto]}/\1\2port=443/" <<<"$links")
|
||||
umask 077
|
||||
{
|
||||
echo "3X-UI KIT (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"
|
||||
echo "3X-UI_autoVPS (3X-UI $XUI_VERSION) – данные для входа (файл виден только root)"
|
||||
echo
|
||||
echo "Панель: $panel_url"
|
||||
echo "Логин: $XUI_USERNAME"
|
||||
@@ -1480,7 +1480,7 @@ setup_nginx() {
|
||||
local panel_path=/${XUI_WEB_BASE_PATH#/}
|
||||
panel_path=${panel_path%/}/
|
||||
{
|
||||
echo "# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске."
|
||||
echo "# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске."
|
||||
echo "stream {"
|
||||
echo " map \$ssl_preread_server_name \$kit_upstream {"
|
||||
[[ -n $reality_sni ]] && echo " $reality_sni 127.0.0.1:${INNER[reality]};"
|
||||
@@ -1500,7 +1500,7 @@ setup_nginx() {
|
||||
echo "}"
|
||||
} >/etc/nginx/kit-stream.conf
|
||||
cat >/etc/nginx/conf.d/kit.conf <<NGX
|
||||
# Сгенерировано 3x-ui.sh (3X-UI KIT) – перезаписывается при повторном запуске.
|
||||
# Сгенерировано 3x-ui.sh (3X-UI_autoVPS) – перезаписывается при повторном запуске.
|
||||
server {
|
||||
listen 127.0.0.1:${INNER[web]} ssl http2 proxy_protocol;
|
||||
server_name _;
|
||||
@@ -1588,14 +1588,14 @@ setup_subscription() {
|
||||
# Без subURI панель показывает ссылку на внутренний порт 2097, до которого снаружи не достучаться.
|
||||
local uri="https://$HOST:$SUB_PORT$SUB_PATH"
|
||||
[[ $SINGLE == yes ]] && uri="https://$HOST$SUB_PATH"
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI KIT" --arg uri "$uri" '
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --argjson ip "$SUB_INTERNAL" --arg title "3X-UI_autoVPS" --arg uri "$uri" '
|
||||
.subEnable = true | .subPath = $path | .subTitle = $title | .subListen = "127.0.0.1" | .subPort = $ip
|
||||
| .subURI = $uri
|
||||
| .subCertFile = "" | .subKeyFile = ""
|
||||
| .subClashEnable = true | .subClashAutoDetect = true | .subJsonEnable = true | .subJsonAutoDetect = true' <<<"$all")
|
||||
else
|
||||
SUB_PORT=$(jq -r '.subPort // 2096' <<<"$all")
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI KIT" '
|
||||
upd=$(jq -c --arg path "$SUB_PATH" --arg title "3X-UI_autoVPS" '
|
||||
.subEnable = true | .subPath = $path | .subTitle = $title
|
||||
| .subClashEnable = true | .subClashAutoDetect = true | .subJsonEnable = true | .subJsonAutoDetect = true' <<<"$all")
|
||||
fi
|
||||
@@ -1616,7 +1616,7 @@ setup_subscription() {
|
||||
kit_sub_unit() { # путь-к-сертификату путь-к-ключу (пусто – за nginx)
|
||||
cat <<UNIT
|
||||
[Unit]
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)
|
||||
After=network-online.target x-ui.service
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -1718,11 +1718,11 @@ safe_env() { # файл имя...
|
||||
local f=$1 n re; shift
|
||||
[[ -r $f ]] || die "В копии нет файла ${f##*/}."
|
||||
[[ $(LC_ALL=C tr -d '\n -~' <"$f" | wc -c) -eq 0 ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
LC_ALL=C grep -aqvE "^([A-Z][A-Z0-9_]*=([A-Za-z0-9._:/@%+,=_-]*|''))?$" "$f" \
|
||||
&& die "Это не резервная копия 3X-UI KIT или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
&& die "Это не резервная копия 3X-UI_autoVPS или она повреждена (файл ${f##*/}). Ничего не менял."
|
||||
while IFS= read -r n; do
|
||||
[[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."
|
||||
[[ " $* " == *" $n "* ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (лишнее имя $n в ${f##*/}). Ничего не менял."
|
||||
case $n in
|
||||
BACKUP_KIT_VERSION) re='^[0-9]{1,3}(\.[0-9]{1,3}){1,3}$' ;;
|
||||
BACKUP_HOST | HOST) re='^[A-Za-z0-9.:-]{1,253}$' ;;
|
||||
@@ -1740,10 +1740,10 @@ safe_env() { # файл имя...
|
||||
SUB_PATH) re='^/[A-Za-z0-9_-]{1,64}/$' ;;
|
||||
SINGLE) re='^(yes|no)?$' ;;
|
||||
MTPROTO_INNER) re='^[0-9]{0,5}$' ;;
|
||||
*) die "Это не резервная копия 3X-UI KIT или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;
|
||||
*) die "Это не резервная копия 3X-UI_autoVPS или она повреждена (имя $n в ${f##*/}). Ничего не менял." ;;
|
||||
esac
|
||||
[[ $(env_get "$f" "$n") =~ $re ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (значение $n в ${f##*/}). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (значение $n в ${f##*/}). Ничего не менял."
|
||||
done < <(LC_ALL=C grep -aoE '^[A-Z][A-Z0-9_]*' "$f")
|
||||
return 0
|
||||
}
|
||||
@@ -1874,7 +1874,7 @@ restore_main() { # файл
|
||||
tmp=$(mktemp -d)
|
||||
# shellcheck disable=SC2064 # путь подставляем сразу: при выходе локальной переменной уже нет
|
||||
trap "rm -rf -- '$tmp'" EXIT
|
||||
python3 - "$file" "$tmp" <<'PY' || die "Это не резервная копия 3X-UI KIT или она повреждена. Ничего не менял."
|
||||
python3 - "$file" "$tmp" <<'PY' || die "Это не резервная копия 3X-UI_autoVPS или она повреждена. Ничего не менял."
|
||||
import os, sys, tarfile
|
||||
allowed = ("etc/x-ui/x-ui.db", "etc/x-ui/install-result.env", "etc/kit/kit.env", "etc/kit-sub/config.json", "etc/kit-sub/rules.yaml",
|
||||
"etc/nginx/kit-stream.conf", "etc/nginx/conf.d/kit.conf", "var/www/kit", "root/cert/self",
|
||||
@@ -1935,7 +1935,7 @@ PY
|
||||
safe_env "$tmp/kit-backup.env" BACKUP_KIT_VERSION BACKUP_HOST BACKUP_SSL BACKUP_DATE
|
||||
safe_env "$tmp/etc/x-ui/install-result.env" XUI_USERNAME XUI_PASSWORD XUI_PANEL_PORT XUI_WEB_BASE_PATH XUI_ACCESS_URL XUI_API_TOKEN XUI_DB_TYPE
|
||||
[[ $(env_get "$tmp/etc/x-ui/install-result.env" XUI_PANEL_PORT) =~ ^[0-9]{1,5}$ ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (порт панели). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (порт панели). Ничего не менял."
|
||||
if [[ -f $tmp/etc/kit/kit.env ]]; then
|
||||
safe_env "$tmp/etc/kit/kit.env" HOST LINK_HOST PANEL_ON SUB_BASE SUB_PATH SUB_INTERNAL SINGLE MTPROTO_INNER
|
||||
fi
|
||||
@@ -1947,10 +1947,10 @@ PY
|
||||
BACKUP_SSL=$(env_get "$tmp/kit-backup.env" BACKUP_SSL)
|
||||
BACKUP_DATE=$(env_get "$tmp/kit-backup.env" BACKUP_DATE)
|
||||
BACKUP_KIT_VERSION=$(env_get "$tmp/kit-backup.env" BACKUP_KIT_VERSION)
|
||||
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."
|
||||
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI_autoVPS или она повреждена (адрес сервера). Ничего не менял."
|
||||
[[ $BACKUP_SSL =~ ^(ip|custom|none)$ ]] || die "В копии нет данных о сертификате."
|
||||
[[ $BACKUP_SSL != custom || -f $tmp/root/cert/custom/fullchain.pem ]] \
|
||||
|| die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."
|
||||
|| die "Это не резервная копия 3X-UI_autoVPS или она повреждена (нет своего сертификата). Ничего не менял."
|
||||
PANEL_SSL=$BACKUP_SSL
|
||||
# Домен переезжает вместе с сервером (поменяйте A-запись), IP – нет.
|
||||
if [[ $BACKUP_HOST =~ ^[0-9.]+$ ]]; then
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
# Hysteria2 – https://github.com/itsnotkubrick/3X-UI_KIT
|
||||
#
|
||||
# Отдельный установщик Hysteria2 больше не поддерживается: Hysteria2 входит в основную
|
||||
# установку 3X-UI KIT (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`
|
||||
# установку 3X-UI_autoVPS (scripts/3x-ui.sh). Файл оставлен, чтобы старая команда `hy2 update`
|
||||
# получила из подписанного релиза понятное сообщение. Ничего не ставит, не удаляет и не
|
||||
# останавливает: сервер Hysteria2, установленный раньше, продолжает работать как был.
|
||||
# Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём
|
||||
# останавливается и не заменяет команду hy2.
|
||||
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.8"
|
||||
|
||||
cat >&2 <<'MSG'
|
||||
Отдельный установщик Hysteria2 больше не поддерживается.
|
||||
Сервер Hysteria2 продолжает работать как раньше, но не обновляется этим скриптом.
|
||||
Hysteria2 входит в основную установку 3X-UI KIT (один из 11 протоколов).
|
||||
Hysteria2 входит в основную установку 3X-UI_autoVPS (один из 11 протоколов).
|
||||
Если нужен только Hysteria2 без панели – официальный установщик:
|
||||
bash <(curl -fsSL https://get.hy2.sh/)
|
||||
MSG
|
||||
|
||||
+221
-4
@@ -64,7 +64,7 @@ kl_need_root() { [[ ${EUID:-0} -eq 0 ]] || kl_die "нужен root"; }
|
||||
|
||||
kit_local_help() {
|
||||
cat <<'EOF'
|
||||
3X-UI KIT — дополнения форка (hacker07):
|
||||
3X-UI_autoVPS — дополнения форка (hacker07):
|
||||
|
||||
Состояние
|
||||
kit local эта справка
|
||||
@@ -73,6 +73,7 @@ kit_local_help() {
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local hardening сводка по защите сервера и что ещё можно включить
|
||||
kit local uninstall снести всё, что поставила установка (SSH не трогает)
|
||||
|
||||
Заглушка (сайт на 443 и ответ Hysteria2 — одна и та же страница)
|
||||
kit local site поставить случайный сайт из нашего набора (10 штук)
|
||||
@@ -233,7 +234,7 @@ EOF
|
||||
printf ' (dry-run) записал бы в %s: [Socket] + ListenStream=%s (старый порт остаётся)\n' "$KL_SOCKET_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 "$(dirname "$KL_SOCKET_DROPIN")"
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\n[Socket]\nListenStream=%s\n' "$(date +%F)" "$new" >"$KL_SOCKET_DROPIN"
|
||||
printf '# 3X-UI_autoVPS (форк): дополнительный порт SSH, добавлен %s\n[Socket]\nListenStream=%s\n' "$(date +%F)" "$new" >"$KL_SOCKET_DROPIN"
|
||||
kl_ok "добавлен $KL_SOCKET_DROPIN"
|
||||
fi
|
||||
kl_run systemctl daemon-reload
|
||||
@@ -244,7 +245,7 @@ EOF
|
||||
printf ' (dry-run) записал бы в %s: Port %s (старый порт остаётся)\n' "$KL_SSHD_DROPIN" "$new"
|
||||
else
|
||||
install -d -m 755 /etc/ssh/sshd_config.d
|
||||
printf '# 3X-UI KIT (форк): дополнительный порт SSH, добавлен %s\nPort %s\n' "$(date +%F)" "$new" >"$KL_SSHD_DROPIN"
|
||||
printf '# 3X-UI_autoVPS (форк): дополнительный порт SSH, добавлен %s\nPort %s\n' "$(date +%F)" "$new" >"$KL_SSHD_DROPIN"
|
||||
kl_ok "добавлен $KL_SSHD_DROPIN"
|
||||
fi
|
||||
kl_run sshd -t || { kl_warn "sshd -t не принял конфиг — убираю наш файл, ничего не перезапускаю"; kl_dry || rm -f "$KL_SSHD_DROPIN"; return 1; }
|
||||
@@ -414,7 +415,7 @@ APT::Periodic::Download-Upgradeable-Packages "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
EOF
|
||||
cat >/etc/apt/apt.conf.d/52-kit-local-upgrades <<'EOF'
|
||||
// 3X-UI KIT (форк): только обновления безопасности, никакой самовольной перезагрузки.
|
||||
// 3X-UI_autoVPS (форк): только обновления безопасности, никакой самовольной перезагрузки.
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
EOF
|
||||
@@ -516,6 +517,12 @@ footer{border-top:1px solid var(--bd);padding:26px 0;color:var(--mu);font-size:1
|
||||
table{border-collapse:collapse;width:100%}
|
||||
th,td{border-bottom:1px solid var(--bd);text-align:left;padding:10px 8px;font-size:15px}
|
||||
ul{padding-left:20px}
|
||||
.modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(11,13,17,.55);padding:20px;z-index:50}
|
||||
.modal.on{display:flex}
|
||||
.modal .box{background:#fff;border-radius:10px;max-width:430px;padding:26px 26px 20px;box-shadow:0 18px 50px rgba(0,0,0,.28)}
|
||||
.modal h4{margin:0 0 8px;font-size:19px}
|
||||
.modal p{margin:0 0 18px;color:var(--mu)}
|
||||
.modal button{background:var(--ac);color:#fff;border:0;border-radius:6px;padding:10px 18px;font:inherit;font-weight:600;cursor:pointer}
|
||||
@media(max-width:600px){.hero{padding:44px 0 28px}.hero h1{font-size:29px}}'
|
||||
|
||||
kl_site_open() { # заголовок описание бренд «навигация как HTML»
|
||||
@@ -538,6 +545,29 @@ HTML
|
||||
kl_site_close() { # бренд
|
||||
cat <<HTML
|
||||
<footer><div class="wrap"><p>© $(date +%Y) $1 · <a href="#">Imprint</a> · <a href="#">Privacy</a> · <a href="#">Status</a></p></div></footer>
|
||||
|
||||
<!-- Ссылки и кнопки живые: открывают окно «раздел в разработке». Никаких внешних запросов. -->
|
||||
<div class="modal" id="dev" role="dialog" aria-modal="true" aria-labelledby="devtitle">
|
||||
<div class="box">
|
||||
<h4 id="devtitle">This section is under construction</h4>
|
||||
<p>We are still building this part of the site. We are sorry for the inconvenience — it will be
|
||||
available shortly.</p>
|
||||
<button type="button" id="devok">Got it</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var m = document.getElementById('dev');
|
||||
if (!m) return;
|
||||
function show(e) { if (e) e.preventDefault(); m.classList.add('on'); document.body.style.overflow = 'hidden'; }
|
||||
function hide() { m.classList.remove('on'); document.body.style.overflow = ''; }
|
||||
var links = document.querySelectorAll('a[href^="#"], a.btn, nav a, footer a');
|
||||
for (var i = 0; i < links.length; i++) { links[i].addEventListener('click', show); }
|
||||
m.addEventListener('click', function (e) { if (e.target === m) hide(); });
|
||||
document.getElementById('devok').addEventListener('click', hide);
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') hide(); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
@@ -865,6 +895,7 @@ kit_local_cmd() {
|
||||
passwd | pass) shift; kit_local_passwd "$@" ;;
|
||||
upgrades | auto) shift; kit_local_upgrades "$@" ;;
|
||||
hardening | hard) shift; kit_local_hardening "$@" ;;
|
||||
uninstall | remove) shift; kit_local_uninstall "$@" ;;
|
||||
site | stub | stub-site) shift; kit_local_site "$@" ;;
|
||||
*)
|
||||
printf 'Неизвестная команда форка: %s\n' "$1" >&2
|
||||
@@ -876,6 +907,187 @@ kit_local_cmd() {
|
||||
|
||||
# Точка входа: вызывается из kit.sh с аргументами командной строки.
|
||||
# Если команда не наша — возвращаем 0, и kit работает ровно как раньше.
|
||||
# ---------------------------------------------------------------- снос установки целиком
|
||||
# Убираем всё, что появляется после установки: панель 3X-UI и службу x-ui, ядро Xray, службы и файлы
|
||||
# kit (kit-sub, автообновление, cron), конфиги nginx и сайт-заглушку, сертификаты, правила ufw для 80/443,
|
||||
# наши правки системы (правило ICMP, обновления безопасности), наши бэкапы и файл с данными панели.
|
||||
#
|
||||
# ЧЕГО НЕ ТРОГАЕМ: SSH — ни порт, ни конфиги, ни правила ufw для него, ни наш ssh-дроп-ин. Иначе легко
|
||||
# остаться без доступа к серверу. Если SSH-порт когда-то менялся, верните его сами: kit local ssh 22
|
||||
# (и закрыть лишние: kit local ssh --close-old 22).
|
||||
#
|
||||
# kit local uninstall спросит подтверждение словом «удалить»
|
||||
# kit local uninstall --dry-run только покажет, что будет удалено
|
||||
kit_local_uninstall() {
|
||||
kl_args "$@"
|
||||
local arg=${KL_REST[0]:-} ans answer
|
||||
case $arg in
|
||||
"") ;;
|
||||
--dry-run) KL_DRY=yes ;;
|
||||
*) kl_die "kit local uninstall [--dry-run]"; return 1 ;;
|
||||
esac
|
||||
|
||||
local -a svc=(x-ui kit-sub kit-update.timer kit-update.service)
|
||||
local -a files=(
|
||||
/usr/local/x-ui /etc/x-ui /usr/bin/x-ui /etc/systemd/system/x-ui.service
|
||||
/usr/local/bin/kit /usr/local/lib/kit /etc/kit /etc/kit-sub
|
||||
/etc/systemd/system/kit-sub.service /etc/systemd/system/kit-update.service
|
||||
/etc/systemd/system/kit-update.timer
|
||||
/etc/cron.d/kit-nginx-reload /etc/cron.d/kit-xui-menu /etc/cron.d/kit-sub-cert
|
||||
/etc/nginx/kit-stream.conf /etc/nginx/conf.d/kit.conf
|
||||
/var/www/kit /root/3x-ui.txt /root/cert /root/.acme.sh /etc/letsencrypt
|
||||
/etc/sysctl.d/99-kit-local-icmp.conf /etc/apt/apt.conf.d/52-kit-local-upgrades
|
||||
)
|
||||
local -a backups=()
|
||||
local b
|
||||
for b in /root/kit-local-backup-*; do [[ -e $b ]] && backups+=("$b"); done
|
||||
|
||||
kl_step "Будет удалено"
|
||||
printf ' службы: %s\n' "${svc[*]}"
|
||||
printf ' файлы и каталоги: %s\n' "${files[*]}"
|
||||
[[ ${#backups[@]} -eq 0 ]] || printf ' наши копии изменённых файлов: %s\n' "${backups[*]}"
|
||||
printf ' правила ufw для 80/tcp и 443/tcp (правило для SSH не трогаем)\n'
|
||||
printf ' ответ на ping вернём (снимем наше правило)\n'
|
||||
if kl_ufw_active; then
|
||||
if [[ $(grep -c '^# -A ufw-before-input -p icmp --icmp-type echo-request' /etc/ufw/before.rules 2>/dev/null || echo 0) -gt 0 ]]; then
|
||||
printf ' %s\n' "правило ICMP наше (строка закомментирована) — вернём как было"
|
||||
fi
|
||||
fi
|
||||
kl_ok "перезагрузка не потребуется, SSH и его порт не затрагиваются"
|
||||
|
||||
if kl_dry; then
|
||||
printf '\n (dry-run) ничего не делаю. Запустить по-настоящему: kit local uninstall\n'
|
||||
return 0
|
||||
fi
|
||||
kl_need_root || return 1
|
||||
|
||||
if [[ -z ${KIT_LOCAL_UNINSTALL_CONFIRMED:-} ]]; then
|
||||
printf '\n%sЧтобы удалить всё перечисленное, напишите слово «удалить»: %s' "${B:-}" "${N:-}"
|
||||
read -r ans </dev/tty 2>/dev/null || ans=""
|
||||
[[ ${ans,,} == "удалить" ]] || { kl_warn "не подтверждено — ничего не удаляю"; return 1; }
|
||||
fi
|
||||
|
||||
kl_step "Останавливаю и отключаю службы"
|
||||
local s
|
||||
for s in "${svc[@]}"; do
|
||||
systemctl disable --now "$s" >/dev/null 2>&1 && kl_ok "остановлено и отключено: $s" || kl_warn "служба $s не найдена (уже удалена?)"
|
||||
done
|
||||
if kl_have nginx; then
|
||||
local others
|
||||
others=$(find /etc/nginx/conf.d /etc/nginx/sites-enabled -type f -o -type l 2>/dev/null | grep -vE 'kit\.conf$|default$' | wc -l)
|
||||
if [[ $others -eq 0 ]]; then
|
||||
kl_run systemctl disable --now nginx >/dev/null 2>&1 && kl_ok "nginx остановлен (своих сайтов, кроме нашего, не было)"
|
||||
KL_NGINX_PURGE=yes
|
||||
else
|
||||
kl_ok "nginx оставляю: в нём есть другие сайты ($others) — уберу только наши конфиги"
|
||||
KL_NGINX_PURGE=no
|
||||
fi
|
||||
fi
|
||||
|
||||
kl_step "Убираю файлы"
|
||||
for f in "${files[@]}"; do
|
||||
[[ -e $f ]] || continue
|
||||
kl_run rm -rf "$f" && kl_ok "удалено: $f"
|
||||
done
|
||||
for f in "${backups[@]}"; do kl_run rm -rf "$f" && kl_ok "удалено: $f"; done
|
||||
[[ ${KL_NGINX_PURGE:-no} == yes ]] && { kl_run apt-get remove -y --purge nginx nginx-common >/dev/null 2>&1 && kl_ok "пакет nginx удалён"; }
|
||||
|
||||
kl_step "Возвращаю систему"
|
||||
kl_run systemctl daemon-reload
|
||||
if kl_ufw_active; then
|
||||
local p
|
||||
for p in 80/tcp 443/tcp 443/udp; do kl_run ufw delete allow "$p" >/dev/null 2>&1 && kl_ok "правило ufw снято: $p"; done
|
||||
if [[ -f $KL_UFW_RULES ]]; then
|
||||
kl_backup "$KL_UFW_RULES"; kl_backup "$KL_UFW6_RULES"
|
||||
kl_run sed -i 's/^# -A ufw-before-input -p icmp --icmp-type echo-request/-A ufw-before-input -p icmp --icmp-type echo-request/' "$KL_UFW_RULES"
|
||||
[[ -f $KL_UFW6_RULES ]] && kl_run sed -i 's/^# -A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request/' "$KL_UFW6_RULES"
|
||||
kl_run ufw reload >/dev/null 2>&1
|
||||
kl_ok "ответ на ping возвращён"
|
||||
fi
|
||||
fi
|
||||
kl_run sysctl -q -w net.ipv4.icmp_echo_ignore_all=0 >/dev/null 2>&1 || true
|
||||
|
||||
kl_step "Проверка"
|
||||
local left=0
|
||||
for s in "${svc[@]}"; do systemctl list-unit-files 2>/dev/null | grep -q "^$s" && { kl_warn "осталась служба: $s"; left=1; }; done
|
||||
command -v kit >/dev/null && { kl_warn "команда kit ещё на месте (запустите команду ещё раз или удалите /usr/local/bin/kit)"; left=1; }
|
||||
ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE '[:.]443$' && { kl_warn "порт 443 всё ещё кем-то слушается"; left=1; }
|
||||
(( left )) || kl_ok "от установки ничего не осталось, порт 443 свободен"
|
||||
kl_ok "SSH не тронут: порт 22 (или ваш текущий) и правила для него на месте"
|
||||
kl_step "Готово. Сервер вернулся к состоянию до установки."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- полная справка `kit help`
|
||||
# Категории и все команды: сначала авторские, затем наши. Если автор добавит команду, а справка
|
||||
# про неё не знает — сборку релиза остановит проверка tools/check-help.sh.
|
||||
kit_help_all() {
|
||||
cat <<EOF
|
||||
${B:-}3X-UI_autoVPS ${KIT_VERSION:-?} — все команды${N:-} (то же самое доступно в меню: kit без аргументов)
|
||||
|
||||
Состояние и версии
|
||||
kit меню
|
||||
kit version версии: сборка, панель 3X-UI, ядро Xray
|
||||
kit local version наша сборка, источник обновлений, дата файла
|
||||
kit local channel откуда берутся обновления и чем проверяется подпись
|
||||
kit local seams какие места форк правит в файлах автора
|
||||
kit help эта справка
|
||||
|
||||
Пользователи
|
||||
kit user add <имя> [--gb N] [--days N] [--devices N] создать (пусто/0 — без ограничения)
|
||||
kit user list список пользователей
|
||||
kit user link <имя> [--all | --amnezia | --telegram] подписка с QR-кодом и ссылки
|
||||
kit user limit <имя> --gb N --days N --devices N лимиты трафика, срока, устройств
|
||||
kit user on <имя> включить пользователя
|
||||
kit user off <имя> выключить пользователя
|
||||
kit user del <имя> удалить
|
||||
kit user enforce применить лимиты сейчас
|
||||
kit user vision <имя> [on|off] поток xtls-rprx-vision у REALITY
|
||||
|
||||
Подключения и порты
|
||||
kit net состояние подключений
|
||||
kit net on <имя> включить подключение
|
||||
kit net off <имя> выключить подключение
|
||||
kit net port <имя> <порт> перевести подключение на другой порт
|
||||
kit net vision on|off Vision у всех REALITY сразу
|
||||
kit net split on|off|check|apply разделение трафика по правилам (split)
|
||||
kit port какие порты слушают подключения
|
||||
kit port set <имя> <порт> сменить порт подключения
|
||||
kit reality add [порт] добавить второй REALITY
|
||||
|
||||
Маскировка и сайт
|
||||
kit net site [сайт] сменить сайт маскировки (без аргумента — подобрать соседа по подсети)
|
||||
kit net fp <отпечаток> отпечаток TLS (chrome, firefox, safari, …)
|
||||
kit sni какие сайты маскировки у подключений
|
||||
kit sni rotate сменить сайты маскировки у всех подключений
|
||||
kit local masking состояние маскировки (у REALITY должен быть свой домен)
|
||||
kit local site [--list | N | --show] сайт-заглушка: случайный ИТ-сайт, список, конкретный, какой стоит
|
||||
|
||||
Проверка и починка
|
||||
kit local uninstall снести установку целиком (панель, туннель, службы kit, сайт, сертификаты); SSH не трогает
|
||||
kit check [--deep | --fix] проверка сервера (--deep — ходит наружу как клиент)
|
||||
kit fix [--dry-run] безопасные починки: время, службы, конфиги
|
||||
kit local hardening сводка по защите и что ещё можно включить
|
||||
|
||||
Обслуживание и обновление
|
||||
kit update [--panel | --force | --auto | --manual] обновить сборку, панель, автообновление
|
||||
kit panel update обновить панель 3X-UI
|
||||
kit backup копия сервера: панель, сертификаты, настройки
|
||||
kit net masq on|off ответ Hysteria2 страницей сайта
|
||||
kit net dns on|off DoH в подписках
|
||||
|
||||
Защита сервера (наши команды)
|
||||
kit local ssh <порт> | --status | --close-old <порт> добавить порт SSH, какие слушают, закрыть прежние
|
||||
kit local ping off | on | status ответ на ping (правилом ufw)
|
||||
kit local passwd [--generate] [пользователь] сменить пароль (в логи не попадает)
|
||||
kit local upgrades on | off | status обновления безопасности
|
||||
|
||||
Общие правила
|
||||
* у наших команд есть --dry-run: показывает план и ничего не меняет
|
||||
* подробная справка по нашему слою: kit local help
|
||||
* копии изменённых файлов: /root/kit-local-backup-<дата>/
|
||||
EOF
|
||||
}
|
||||
|
||||
kit_local_dispatch() {
|
||||
case ${1:-} in
|
||||
local)
|
||||
@@ -883,6 +1095,11 @@ kit_local_dispatch() {
|
||||
kit_local_cmd "$@"
|
||||
exit $?
|
||||
;;
|
||||
help | -h | --help)
|
||||
# Полная справка: авторские команды + наши, по категориям.
|
||||
kit_help_all
|
||||
exit 0
|
||||
;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
+2
-2
@@ -494,11 +494,11 @@ def routing_payload(kind, loaded):
|
||||
else:
|
||||
ips.append(v)
|
||||
if kind == "happ":
|
||||
data = {"Name": "3X-UI KIT", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,
|
||||
data = {"Name": "3X-UI_autoVPS", "GlobalProxy": "false", "DomainStrategy": "IPIfNonMatch", "ProxySites": sites,
|
||||
"ProxyIp": ips, "DirectIp": ["geoip:private"]}
|
||||
else:
|
||||
# localhost – системный DNS клиента для прямых сайтов (как в подписке Mihomo); список direct_dns его заменяет.
|
||||
data = {"Name": "3X-UI KIT", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",
|
||||
data = {"Name": "3X-UI_autoVPS", "DomainStrategy": "IPIfNonMatch", "RouteOrder": "block-proxy-direct",
|
||||
"ProxySites": sites, "ProxyIp": ["1.1.1.1", "8.8.8.8"] + ips, "DirectSites": ["regexp:.*"],
|
||||
"DirectIp": ["geoip:private", "0.0.0.0/0", "::/0"], "DomesticDNSDomain": direct_dns[0] if direct_dns else "localhost"}
|
||||
text = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
+19
-17
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# kit – управление сервером 3X-UI KIT: пользователи, обновление, резервная копия.
|
||||
# kit – управление сервером 3X-UI_autoVPS: пользователи, обновление, резервная копия.
|
||||
# https://github.com/itsnotkubrick/3X-UI_KIT
|
||||
#
|
||||
# kit user add имя [--gb 50] [--days 30] [--devices 3]
|
||||
@@ -12,7 +12,7 @@
|
||||
set -Eeuo pipefail
|
||||
export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам
|
||||
|
||||
KIT_VERSION="1.2.6"
|
||||
KIT_VERSION="1.2.8"
|
||||
KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main"
|
||||
# Файлы новой версии берём из её тега, а не из меняющейся ветки main.
|
||||
kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; }
|
||||
@@ -387,7 +387,7 @@ cmd_enforce() {
|
||||
limit_timer_on() {
|
||||
cat >/etc/systemd/system/kit-limit.service <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: общий лимит трафика пользователей (сумма по всем записям)
|
||||
Description=3X-UI_autoVPS: общий лимит трафика пользователей (сумма по всем записям)
|
||||
After=x-ui.service
|
||||
|
||||
[Service]
|
||||
@@ -397,7 +397,7 @@ TimeoutStartSec=120
|
||||
UNIT
|
||||
cat >/etc/systemd/system/kit-limit.timer <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: проверка общего лимита трафика раз в 5 минут
|
||||
Description=3X-UI_autoVPS: проверка общего лимита трафика раз в 5 минут
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
@@ -462,7 +462,7 @@ cmd_del() {
|
||||
# Новый ключ приходит только в релизе, подписанном старым.
|
||||
KIT_SIGNERS=(
|
||||
# Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)"
|
||||
)
|
||||
KIT_SIG_NS="3x-ui-kit-release"
|
||||
KIT_SIG_ID="releases@3x-ui-kit"
|
||||
@@ -496,7 +496,7 @@ update_hint() {
|
||||
}
|
||||
|
||||
cmd_version() {
|
||||
echo "3X-UI KIT $KIT_VERSION"
|
||||
echo "3X-UI_autoVPS $KIT_VERSION"
|
||||
echo "${D}панель 3X-UI $(/usr/local/x-ui/x-ui -v 2>/dev/null | head -1 || echo '?'), ядро Xray $(xray_version)${N}"
|
||||
if auto_enabled; then echo "${D}автообновление: включено (только подписанные релизы), журнал $KIT_UPDATE_LOG${N}"
|
||||
else echo "${D}автообновление: выключено, включить: kit update --auto${N}"; fi
|
||||
@@ -525,7 +525,7 @@ fetch_release() { # версия каталог
|
||||
return 1
|
||||
fi
|
||||
# Версия записана внутри подписанного файла: старый подписанный релиз под видом нового не пройдёт.
|
||||
grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }
|
||||
grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS" || { warn "Подписанный релиз не той версии – ничего не ставлю."; return 1; }
|
||||
for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do
|
||||
sum=$(awk -v f="$f" '$2 == f {print $1}' "$d/SHA256SUMS")
|
||||
[[ $sum =~ ^[0-9a-f]{64}$ ]] || { warn "В подписанном списке нет $f."; return 1; }
|
||||
@@ -540,7 +540,7 @@ fetch_release() { # версия каталог
|
||||
kit_sub_unit() { # путь-к-сертификату путь-к-ключу (пусто – за nginx)
|
||||
cat <<UNIT
|
||||
[Unit]
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI KIT)
|
||||
Description=kit-sub: подписка с учётом приложения (3X-UI_autoVPS)
|
||||
After=network-online.target x-ui.service
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -604,7 +604,7 @@ needs_migration() {
|
||||
auto_on() {
|
||||
cat >/etc/systemd/system/kit-update.service <<UNIT
|
||||
[Unit]
|
||||
Description=3X-UI KIT: автообновление kit и kit-sub (только подписанные релизы)
|
||||
Description=3X-UI_autoVPS: автообновление kit и kit-sub (только подписанные релизы)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -616,7 +616,7 @@ StandardError=append:$KIT_UPDATE_LOG
|
||||
UNIT
|
||||
cat >/etc/systemd/system/kit-update.timer <<'UNIT'
|
||||
[Unit]
|
||||
Description=3X-UI KIT: проверка обновлений раз в сутки
|
||||
Description=3X-UI_autoVPS: проверка обновлений раз в сутки
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:00:00
|
||||
@@ -666,7 +666,7 @@ cmd_update() {
|
||||
[[ $unattended == yes ]] || echo "${D}Переустановить файлы kit той же версии: kit update --force${N}"
|
||||
return
|
||||
fi
|
||||
if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI KIT $KIT_VERSION → $latest"; fi
|
||||
if [[ $latest == "$KIT_VERSION" ]]; then say "Применяю исправления версии $latest"; else say "3X-UI_autoVPS $KIT_VERSION → $latest"; fi
|
||||
tmp=$(mktemp -d)
|
||||
# shellcheck disable=SC2064 # путь подставляем сразу: при выходе локальной переменной уже нет
|
||||
trap "rm -rf -- '$tmp'" EXIT
|
||||
@@ -728,7 +728,7 @@ cmd_update() {
|
||||
# Новые пути подписки (Clash и JSON) подключает уже новая версия kit.
|
||||
/usr/local/bin/kit __subextra >/dev/null 2>&1 || warn "Дополнительные пути подписки не подключились: kit update --force"
|
||||
echo
|
||||
echo "${G}✓ Готово: 3X-UI KIT $latest.${N} Пользователи, ссылки и подписки не менялись."
|
||||
echo "${G}✓ Готово: 3X-UI_autoVPS $latest.${N} Пользователи, ссылки и подписки не менялись."
|
||||
[[ $unattended == yes ]] && return
|
||||
local news
|
||||
news=$(changelog_of "$latest")
|
||||
@@ -1914,10 +1914,10 @@ YAML
|
||||
|
||||
# Приложениям на Xray правила раздаёт сама панель (3X-UI 3.9+): заголовок Routing для Happ и правила JSON-подписки.
|
||||
# Собираем их из того же rules.yaml. Чужой профиль, уже стоящий в панели, не трогаем.
|
||||
split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI KIT"
|
||||
split_panel_ours() { # наш профиль: JSON или ссылка happ://routing/onadd/<base64> с Name "3X-UI_autoVPS"
|
||||
local v=$1
|
||||
[[ $v == happ://routing/onadd/* ]] && v=$(printf '%s' "${v#happ://routing/onadd/}" | base64 -d 2>/dev/null || true)
|
||||
grep -q '"Name":"3X-UI KIT"' <<<"$v"
|
||||
grep -q '"Name":"3X-UI_autoVPS"' <<<"$v"
|
||||
}
|
||||
|
||||
split_panel_sync() { # on|off
|
||||
@@ -2551,14 +2551,15 @@ menu_main() {
|
||||
local c
|
||||
while :; do
|
||||
echo
|
||||
box "3X-UI KIT $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||
box "3X-UI_autoVPS $KIT_VERSION – управление сервером" "1. Пользователи" "2. Протоколы, порты и маскировка" "3. Проверка сервера" "" \
|
||||
"4. Обновить kit и подписку" "5. Обновить панель 3X-UI" "6. Автообновление: включить или выключить" "" \
|
||||
"7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"
|
||||
"10. Удалить всё установленное (наш пункт)"
|
||||
echo
|
||||
menu_status
|
||||
update_hint
|
||||
echo
|
||||
ask_num "Выбор [0-9]: "; c=$REPLY
|
||||
ask_num "Выбор [0-9, 10]: "; c=$REPLY
|
||||
case $c in
|
||||
1) menu_users ;;
|
||||
2) menu_net ;;
|
||||
@@ -2569,6 +2570,7 @@ menu_main() {
|
||||
7) run_action cmd_backup; pause ;;
|
||||
8) run_action cmd_version; pause ;;
|
||||
9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;
|
||||
10) kit_local_uninstall ;;
|
||||
0 | "") return 0 ;;
|
||||
*) echo "Нет такого пункта." ;;
|
||||
esac
|
||||
@@ -2577,7 +2579,7 @@ menu_main() {
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${B}3X-UI KIT $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})
|
||||
${B}3X-UI_autoVPS $KIT_VERSION${N} – команды (то же самое есть в меню: просто ${B}kit${N})
|
||||
|
||||
kit меню
|
||||
kit user … add, list, link, limit, on, off, del
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Справка `kit help` обязана перечислять все команды: авторские (их берём прямо из диспетчера
|
||||
# scripts/kit.sh) и наши. Если автор добавит команду, а мы забудем про справку — проверка упадёт,
|
||||
# и сборка релиза остановится: так справка не отстанет молча.
|
||||
set -Eeuo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
help=$(awk '/^kit_help_all\(\) \{/,/^\}/' scripts/kit-local.sh)
|
||||
[[ -n $help ]] || { echo "✗ не нашёл kit_help_all в scripts/kit-local.sh" >&2; exit 1; }
|
||||
|
||||
bad=0
|
||||
# 1) авторские команды: первое слово каждой ветки диспетчера kit.sh
|
||||
while read -r word; do
|
||||
[[ -n $word ]] || continue
|
||||
case $word in __* | local | help | -h | --help | -v | --version) continue ;; esac
|
||||
# «user off» в справке может стоять как «user on|off» — поэтому пробел допускаем и как разделитель вариантов
|
||||
pat=${word// /[ |]}
|
||||
grep -qE " kit ${pat}" <<<"$help" || { echo " ✗ в справке нет авторской команды: kit $word" >&2; bad=1; }
|
||||
done < <(grep -oE '^ +"[a-z][a-z0-9 -]*"\)|^ +"[a-z][a-z0-9 -]*" \*\)' scripts/kit.sh | sed 's/)$//' | tr -d '"' | sed 's/^ *//' | sort -u)
|
||||
|
||||
# 2) наши команды из ветки kit_local_cmd
|
||||
while read -r word; do
|
||||
[[ -n $word ]] || continue
|
||||
grep -qF " kit local $word" <<<"$help" || { echo " ✗ в справке нет нашей команды: kit local $word" >&2; bad=1; }
|
||||
done < <(awk '/^kit_local_cmd\(\) \{/,/^\}/' scripts/kit-local.sh | grep -oE '^ [a-z-]+' | tr -d ' ' | sort -u)
|
||||
|
||||
if (( bad )); then
|
||||
echo "✗ справка kit help отстала от набора команд — поправьте kit_help_all в scripts/kit-local.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ справка kit help содержит все команды (авторские и наши)"
|
||||
@@ -1 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI_autoVPS fork releases (hacker07)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
# Робот ежесуточной синхронизации: проверяет обновления у автора проекта и, если они есть,
|
||||
# переносит их в нашу ветку dev — но только когда всё сошлось.
|
||||
#
|
||||
# Где живёт: запускается из .gitea/workflows/fork-sync.yml на раннере (CT 114 «runner»).
|
||||
# Что НЕ делает: не выпускает релиз и не подписывает его — закрытый ключ подписи остаётся
|
||||
# только на рабочей станции владельца, поэтому версия автора публикуется вручную.
|
||||
#
|
||||
# Переменные окружения (все имеют значения по умолчанию, кроме TOKEN):
|
||||
# TOKEN токен Gitea с правом записи в репозиторий (берётся из секретов workflow)
|
||||
# GITEA_URL внутренний адрес Gitea (снаружи контейнер её не видит: имя резолвится в публичный IP)
|
||||
# REPO владелец/репозиторий
|
||||
# UPSTREAM адрес авторского репозитория
|
||||
# WORK рабочая копия на раннере
|
||||
# BRANCH наша ветка для синхронизации
|
||||
# LOG файл отчёта последнего запуска (его читают из контейнера руками)
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
GITEA_URL=${GITEA_URL:-http://10.10.10.54:3000}
|
||||
REPO=${REPO:-hacker07/3X-UI_autoVPS}
|
||||
UPSTREAM=${UPSTREAM:-https://github.com/itsnotkubrick/3X-UI_KIT}
|
||||
WORK=${WORK:-/opt/kit-fork-sync/repo}
|
||||
BRANCH=${BRANCH:-dev}
|
||||
LOG=${LOG:-/opt/kit-fork-sync/last-run.log}
|
||||
TOKEN=${TOKEN:-}
|
||||
|
||||
say() { printf '\n==> %s\n' "$*"; }
|
||||
ok() { printf ' ✓ %s\n' "$*"; }
|
||||
die() { printf '✗ %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
printf '\n===== запуск %s =====\n' "$(date '+%F %T %Z')"
|
||||
|
||||
[[ -n $TOKEN ]] || die "нет TOKEN: workflow не передал секрет GITEA_TOKEN"
|
||||
command -v git >/dev/null || die "нет git"
|
||||
PY=${PY:-$(command -v python3 || command -v python || true)}
|
||||
[[ -n $PY ]] || die "нет python3 — без него не применить швы"
|
||||
|
||||
auth_url="${GITEA_URL}/${REPO}.git"
|
||||
auth_url="${auth_url/http:\/\//http://x-access-token:${TOKEN}@}"
|
||||
|
||||
# ---------------------------------------------------------------- рабочая копия
|
||||
|
||||
if [[ ! -d $WORK/.git ]]; then
|
||||
say "Первая синхронизация: клонирую репозиторий"
|
||||
git clone -q "$auth_url" "$WORK" || die "не смог склонировать $REPO"
|
||||
git -C "$WORK" remote set-url origin "$auth_url"
|
||||
git -C "$WORK" remote add upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote set-url upstream "$UPSTREAM"
|
||||
else
|
||||
git -C "$WORK" remote set-url origin "$auth_url"
|
||||
git -C "$WORK" remote set-url upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote add upstream "$UPSTREAM"
|
||||
fi
|
||||
cd "$WORK"
|
||||
|
||||
say "Забираю ветки: наша $BRANCH и авторскую main"
|
||||
git fetch -q origin --prune
|
||||
git fetch -q upstream 'refs/heads/main:refs/heads/upstream' --prune
|
||||
git checkout -q "$BRANCH"
|
||||
git reset -q --hard "origin/$BRANCH"
|
||||
|
||||
author_version=$(git show upstream:VERSION 2>/dev/null | tr -d '[:space:]' || true)
|
||||
[[ -n $author_version ]] || die "у автора не читается VERSION — проверьте ветку upstream"
|
||||
ours=$(tr -d '[:space:]' <VERSION)
|
||||
echo " у автора: $author_version · у нас: $ours"
|
||||
|
||||
new_commits=$(git rev-list --count "$BRANCH..upstream")
|
||||
if [[ $new_commits == 0 ]]; then
|
||||
say "У автора нет новых коммитов — публиковать нечего"
|
||||
ok "проверка выполнена, ветка $BRANCH не изменялась"
|
||||
exit 0
|
||||
fi
|
||||
say "У автора $new_commits новых коммитов — переношу наши правки на свежий код"
|
||||
git --no-pager log --oneline "$BRANCH..upstream" | sed 's/^/ /'
|
||||
|
||||
# ---------------------------------------------------------------- перенос и проверки
|
||||
|
||||
# Скрипт форка делает перенос (rebase на ветку автора) и все проверки: швы, синтаксис, LF, тесты.
|
||||
# Без --apply: швы в нашей ветке уже накатаны, повторный накат на такое дерево падает (якоря автора
|
||||
# заменены нашими строками). Номер версии поднимаем сами — по той же логике, что и в fork-sync.sh.
|
||||
if ! bash tools/fork-sync.sh; then
|
||||
die "перенос не прошёл (конфликт в местах наших правок). Ветка $BRANCH не изменена. Разберите вручную на рабочей станции"
|
||||
fi
|
||||
|
||||
# Номер версии: у автора 1.3 → наш 1.3.1; если база та же, а мы дорабатываем — 1.3.2. Обязан расти.
|
||||
base=${author_version%%.*}
|
||||
cur=$(tr -d '[:space:]' <VERSION)
|
||||
if [[ ${cur%%.*} == "$base" ]]; then
|
||||
new_version="$base.$(( ${cur##*.} + 1 ))"
|
||||
else
|
||||
new_version="${author_version}.1"
|
||||
fi
|
||||
printf '%s\n' "$new_version" >VERSION
|
||||
for f in scripts/kit.sh scripts/3x-ui.sh scripts/hysteria2.sh scripts/3x-ui-local.sh; do
|
||||
sed -i "s/^KIT_VERSION=\"[0-9.]*\"/KIT_VERSION=\"$new_version\"/" "$f"
|
||||
done
|
||||
"$PY" tools/fork_apply.py >/dev/null 2>&1 || true # на дереве со швами это ожидаемо: якорей уже нет
|
||||
echo " версия сборки: $cur → $new_version"
|
||||
|
||||
# Заглушка записи в нашем журнале версий: без раздела «## v<версия>» сборщик релиза откажется
|
||||
# подписывать. Текст — на владельца, он правит его перед выпуском.
|
||||
if ! grep -q "^## v$new_version" FORK-CHANGELOG.md; then
|
||||
say "Добавляю запись в FORK-CHANGELOG для версии $new_version"
|
||||
"$PY" - "$new_version" "$author_version" <<'PY'
|
||||
import sys, pathlib
|
||||
new, author = sys.argv[1], sys.argv[2]
|
||||
p = pathlib.Path("FORK-CHANGELOG.md")
|
||||
text = p.read_text(encoding="utf-8")
|
||||
block = (f"## v{new}: обновление от автора {author}\n\n"
|
||||
f"* Автоматическая синхронизация подтянула код автора {author} в нашу сборку: наши правки (швы)\n"
|
||||
f" перенесены на свежий код, проверки прошли. Описать, что изменилось у автора, до выпуска релиза.\n\n")
|
||||
marker = "## v"
|
||||
i = text.find(marker)
|
||||
p.write_text(text[:i] + block + text[i:], encoding="utf-8", newline="\n")
|
||||
print(f" ✓ раздел «## v{new}» добавлен")
|
||||
PY
|
||||
fi
|
||||
|
||||
# Финальный контроль: швы на месте, синтаксис и LF — как того требует сборщик релиза.
|
||||
"$PY" tools/fork_apply.py --check >/dev/null || die "после переноса швы не проверяются — ничего не публикую"
|
||||
for f in scripts/*.sh tools/*.sh; do bash -n "$f" || die "$f: синтаксис"; done
|
||||
if grep -qU $'\r' scripts/*.sh tools/*.sh VERSION 2>/dev/null; then
|
||||
die "в файлах CRLF — сервер сверяет SHA256, сумма не сойдётся. Ничего не публикую"
|
||||
fi
|
||||
ok "швы на месте, синтаксис и переводы строк в порядке"
|
||||
|
||||
# ---------------------------------------------------------------- публикация
|
||||
|
||||
if git diff --quiet origin/"$BRANCH" -- VERSION scripts tools .gitea 2>/dev/null && [[ $new_version == "$ours" ]]; then
|
||||
say "Содержимое не изменилось — публиковать нечего"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "Публикую в $BRANCH"
|
||||
git add -A
|
||||
git -c user.name="fork-sync-bot" -c user.email="fork-sync@rukavishnikoff.ru" \
|
||||
commit -q -m "Синхронизация с автором ${author_version}: швы и версия ${new_version}" \
|
||||
-m "Автоматика CT 114. Проверки: швы на месте, bash -n, LF. Релиз и подпись — вручную с рабочей станции." \
|
||||
|| die "не смог закоммитить"
|
||||
curl -fsS -o /dev/null -H "Authorization: token ${TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/branches/${BRANCH}" || die "Gitea недоступна перед отправкой"
|
||||
# Перенос идёт rebase'ом: наша ветка переписывается на свежий код автора, поэтому отправляем
|
||||
# с --force-with-lease — если за это время в ветку что-то попало с рабочей станции, отправка
|
||||
# откажется, а не затрёт чужой коммит.
|
||||
git push -q --force-with-lease=refs/heads/"$BRANCH" origin "$BRANCH" \
|
||||
|| die "не смог отправить ветку $BRANCH (возможно, в ней есть ваши свежие коммиты — тогда синхронизируйте вручную)"
|
||||
ok "ветка $BRANCH обновлена: $(git --no-pager log --oneline -1)"
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Готово. Дальше вручную, с рабочей станции:
|
||||
1) git fetch origin && git checkout $BRANCH && git reset --hard origin/$BRANCH
|
||||
2) описать изменения в FORK-CHANGELOG.md (раздел уже добавлен)
|
||||
3) bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key — подпись и релиз
|
||||
4) git tag v$new_version && git push origin $BRANCH v$new_version
|
||||
EOF
|
||||
+95
-3
@@ -60,7 +60,7 @@ def main():
|
||||
args.remove("--check")
|
||||
root = pathlib.Path(args[0] if args else ".").resolve()
|
||||
if not (root / "VERSION").exists():
|
||||
die("%s не похож на репозиторий 3X-UI KIT (нет VERSION)" % root)
|
||||
die("%s не похож на репозиторий 3X-UI_autoVPS (нет VERSION)" % root)
|
||||
os.chdir(root)
|
||||
|
||||
version = (root / "VERSION").read_text(encoding="utf-8").strip()
|
||||
@@ -177,9 +177,9 @@ def main():
|
||||
),
|
||||
(
|
||||
"tools/release.sh",
|
||||
'{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}"; } >SHA256SUMS',
|
||||
'{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}"; } >SHA256SUMS',
|
||||
False,
|
||||
"{ echo \"# 3X-UI KIT $v\"; sha \"${FILES[@]}\" | awk '{ sub(/^\\*/, \"\", $2); print $1\" \"$2 }'; } >SHA256SUMS",
|
||||
"{ echo \"# 3X-UI KIT $v\"; echo \"# 3X-UI_autoVPS $v\"; sha \"${FILES[@]}\" | awk '{ sub(/^\\*/, \"\", $2); print $1\" \"$2 }'; } >SHA256SUMS",
|
||||
"суммы в POSIX-виде без звёздочки (на Windows sha256sum её ставит, и kit её не понимает)",
|
||||
),
|
||||
# --- свои записи о версии (наш файл, авторский CHANGELOG не трогаем) ----
|
||||
@@ -205,6 +205,48 @@ def main():
|
||||
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'',
|
||||
"«что нового» ищет и в наших записях",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
" _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\",
|
||||
False,
|
||||
" _ _ _ ___ _____\n | | | | __ _ ___| | _____ _ __ / _ \\___ |\n | |_| |/ _` |/ __| |/ / _ \\ '__| | | | | / /\n | _ | (_| | (__| < __/ | | |_| |/ /\n |_| |_|\\__,_|\\___|_|\\_\\___|_| \\___//_/",
|
||||
"баннер сервера: наша надпись вместо авторской",
|
||||
),
|
||||
(
|
||||
"scripts/3x-ui.sh",
|
||||
' echo "${B}3X-UI KIT $KIT_VERSION${N} · it\'s not Kubrick. it\'s just a VPN."',
|
||||
False,
|
||||
' echo "${B}3X-UI_autoVPS $KIT_VERSION${N} · сборка форка: панель 3X-UI и VPN-протоколы на одном порту"',
|
||||
"подпись под баннером: наше имя сборки",
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
' grep -qx "# 3X-UI KIT $v" "$d/SHA256SUMS"',
|
||||
False,
|
||||
' grep -qxE "# (3X-UI KIT|3X-UI_autoVPS) $v" "$d/SHA256SUMS"',
|
||||
"проверка шапки подписанного релиза принимает и старое, и новое имя",
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"',
|
||||
False,
|
||||
' "7. Резервная копия" "8. Версии" "" "9. Панель 3X-UI (меню x-ui)" "0. Выход"\n "10. Удалить всё установленное (наш пункт)"',
|
||||
'пункт меню: снос установки',
|
||||
),
|
||||
(
|
||||
'scripts/kit.sh',
|
||||
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;',
|
||||
False,
|
||||
' 9) command -v x-ui >/dev/null && x-ui || echo "Меню x-ui не найдено." ;;\n 10) kit_local_uninstall ;;',
|
||||
'пункт меню: снос установки (обработчик)',
|
||||
),
|
||||
(
|
||||
"scripts/kit.sh",
|
||||
'ask_num "Выбор [0-9]: "',
|
||||
False,
|
||||
'ask_num "Выбор [0-9, 10]: "',
|
||||
"подсказка выбора в меню учитывает новый пункт",
|
||||
),
|
||||
]
|
||||
|
||||
bad = 0
|
||||
@@ -239,6 +281,56 @@ def main():
|
||||
p.write_text(text, encoding="utf-8", newline="")
|
||||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||||
|
||||
|
||||
# Массовая замена бренда в файлах автора: строки, которые видит человек (справка, версия,
|
||||
# описания служб, баннер). Строку проверки шапки SHA256SUMS не трогаем — её правит отдельный шов,
|
||||
# а сама шапка ещё один релиз пишется в старом виде (иначе уже установленные серверы откажутся
|
||||
# принимать обновление: они сверяют точный вид строки).
|
||||
globals_ = [
|
||||
("scripts/kit.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/3x-ui.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/hysteria2.sh", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
("scripts/kit-sub.py", "3X-UI KIT", "3X-UI_autoVPS"),
|
||||
]
|
||||
for path, old, new in globals_:
|
||||
p = pathlib.Path(path)
|
||||
if not p.exists():
|
||||
print("✗ %s: файла нет" % path)
|
||||
bad += 1
|
||||
continue
|
||||
text = p.read_text(encoding="utf-8", newline="")
|
||||
# строки, где старое имя — часть служебной сверки, не переименовываем
|
||||
keep = lambda line: "grep -qx" in line
|
||||
if check:
|
||||
if new not in text:
|
||||
print("✗ %-20s нет нашего имени «%s»" % (path, new))
|
||||
bad += 1
|
||||
continue
|
||||
left = [l for l in text.splitlines() if old in l and not keep(l)]
|
||||
if left:
|
||||
print("✗ %-20s осталось старое имя в %d строк(ах)" % (path, len(left)))
|
||||
bad += 1
|
||||
continue
|
||||
print("✓ %-20s бренд: %s" % (path, new))
|
||||
continue
|
||||
out = []
|
||||
changed = 0
|
||||
for line in text.splitlines(keepends=True):
|
||||
if old in line and not keep(line):
|
||||
out.append(line.replace(old, new))
|
||||
changed += line.count(old)
|
||||
else:
|
||||
out.append(line)
|
||||
if not changed:
|
||||
if new in text:
|
||||
print("– %-20s уже переименовано" % path)
|
||||
continue
|
||||
print("✗ %-20s не нашёл «%s»" % (path, old))
|
||||
bad += 1
|
||||
continue
|
||||
p.write_text("".join(out), encoding="utf-8", newline="")
|
||||
print("✓ %-20s переименовано вхождений: %d" % (path, changed))
|
||||
|
||||
if bad:
|
||||
if check:
|
||||
die("якорей не найдено: %d — автор изменил код, пересмотри правки в tools/fork_apply.py" % bad)
|
||||
|
||||
+7
-4
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Подпись релиза 3X-UI KIT. Запускает только автор, на своём компьютере, перед тегом:
|
||||
# Подпись релиза 3X-UI_autoVPS. Запускает только автор, на своём компьютере, перед тегом:
|
||||
#
|
||||
# tools/release.sh [~/.ssh/3xui_kit_release]
|
||||
#
|
||||
# Проверяет, что версии сходятся и установщик 3X-UI не подменили, пишет SHA256SUMS,
|
||||
# подписывает его закрытым ключом (ssh-keygen спросит пароль ключа) и сразу проверяет
|
||||
# подпись открытым ключом, записанным в kit. Сервер 3X-UI KIT ставит обновление, только
|
||||
# подпись открытым ключом, записанным в kit. Сервер 3X-UI_autoVPS ставит обновление, только
|
||||
# если эта проверка у него тоже сошлась. Закрытый ключ никуда не отправляется.
|
||||
|
||||
set -euo pipefail
|
||||
@@ -16,7 +16,7 @@ ok() { printf '✓ %s\n' "$*"; }
|
||||
sha() { if command -v sha256sum >/dev/null; then sha256sum "$@"; else shasum -a 256 "$@"; fi; }
|
||||
|
||||
key=${1:-$HOME/.ssh/3xui_kit_release}
|
||||
[[ -f $key ]] || die "Нет закрытого ключа $key. Создать: ssh-keygen -t ed25519 -C \"3X-UI KIT releases\" -f $key"
|
||||
[[ -f $key ]] || die "Нет закрытого ключа $key. Создать: ssh-keygen -t ed25519 -C \"3X-UI_autoVPS releases\" -f $key"
|
||||
NS="3x-ui-kit-release"
|
||||
ID="releases@3x-ui-kit"
|
||||
FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)
|
||||
@@ -36,6 +36,9 @@ for f in "${FILES[@]}"; do
|
||||
done
|
||||
ok "Переводы строк во всех файлах релиза — LF"
|
||||
|
||||
export PY
|
||||
bash tools/check-help.sh || die "справка kit help отстала от набора команд"
|
||||
|
||||
[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]] || die "Есть незакоммиченные изменения в файлах релиза – сначала коммит."
|
||||
|
||||
# Установщик 3X-UI: хеш в скрипте должен совпадать с тем, что сейчас лежит на GitHub.
|
||||
@@ -77,7 +80,7 @@ pub=$(ssh-keygen -y -f "$key" | awk '{print $1, $2}')
|
||||
signers scripts/kit.sh | awk '{print $1, $2}' | grep -qxF "$pub" || die "Ключа $key нет в KIT_SIGNERS – серверы не примут такую подпись."
|
||||
ok "Ключ подписи записан в kit"
|
||||
|
||||
{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
|
||||
{ echo "# 3X-UI KIT $v"; echo "# 3X-UI_autoVPS $v"; sha "${FILES[@]}" | awk '{ sub(/^\*/, "", $2); print $1" "$2 }'; } >SHA256SUMS
|
||||
rm -f SHA256SUMS.sig
|
||||
ssh-keygen -Y sign -f "$key" -n "$NS" SHA256SUMS >/dev/null
|
||||
tmp=$(mktemp)
|
||||
|
||||
@@ -113,7 +113,7 @@ class RestoreEnv(unittest.TestCase):
|
||||
def bad(self, over):
|
||||
r = restore_env(over)
|
||||
self.assertNotIn("ENV-OK", r.stdout)
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", r.stdout + r.stderr)
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", r.stdout + r.stderr)
|
||||
|
||||
def test_mtproto_inner_sed(self):
|
||||
for v in (b"MTPROTO_INNER='[0-9]*.*/touch \\/tmp\\/pwn-test/e;#'", b"MTPROTO_INNER='1/x/;1e touch /tmp/pwn-test #'", b"MTPROTO_INNER=1/x/e"):
|
||||
@@ -148,7 +148,7 @@ class RestoreEnv(unittest.TestCase):
|
||||
self.assertIn("байты " + name, check(p).stderr)
|
||||
|
||||
def test_3xui_txt(self):
|
||||
good = "3X-UI KIT – данные для входа\n\nПанель: https://1.2.3.4/x/\n\tvless://a@1.2.3.4:443?x=1#Мой 🙂\n".encode()
|
||||
good = "3X-UI_autoVPS – данные для входа\n\nПанель: https://1.2.3.4/x/\n\tvless://a@1.2.3.4:443?x=1#Мой 🙂\n".encode()
|
||||
r = check(archive([(k, v) for k, v in ENVS.items()] + [("root/3x-ui.txt", good)]))
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
for bad in (b"\0", b"\x1b]0;x\x07", b"\x1b[2J", b"\r", b"\xff", b"\xc2\x9b", b"\x7f"):
|
||||
|
||||
@@ -102,7 +102,7 @@ class SafeEnv(unittest.TestCase):
|
||||
def rejected(self, name, text):
|
||||
code, out = run(name, text, 'echo "RESULT=$RESULT"\n')
|
||||
self.assertNotEqual(code, 0, out)
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", out)
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", out)
|
||||
self.assertNotIn("\nOK\n", "\n" + out)
|
||||
|
||||
def test_result_in_backup_env(self):
|
||||
@@ -148,7 +148,7 @@ class SafeEnv(unittest.TestCase):
|
||||
r = subprocess.run(["bash", "-c", script, "t", p], capture_output=True)
|
||||
out = (r.stdout + r.stderr).decode("utf-8", "replace")
|
||||
self.assertNotEqual(r.returncode, 0, "%s %r: %s" % (name, data, out))
|
||||
self.assertIn("Это не резервная копия 3X-UI KIT", out, "%s %r" % (name, data))
|
||||
self.assertIn("Это не резервная копия 3X-UI_autoVPS", out, "%s %r" % (name, data))
|
||||
self.assertNotIn("\nOK\n", "\n" + out)
|
||||
|
||||
def test_substitutions(self):
|
||||
|
||||
@@ -128,7 +128,7 @@ class Rules(unittest.TestCase):
|
||||
link = ks.routing_payload("happ", loaded)
|
||||
self.assertTrue(link.startswith("happ://routing/onadd/"))
|
||||
h = json.loads(base64.b64decode(link.split("/onadd/")[1]))
|
||||
self.assertEqual(h["Name"], "3X-UI KIT")
|
||||
self.assertEqual(h["Name"], "3X-UI_autoVPS")
|
||||
self.assertEqual(h["GlobalProxy"], "false")
|
||||
self.assertEqual(h["ProxySites"], ["geosite:youtube", "domain:example.org", "full:api.example.com"])
|
||||
self.assertEqual(h["ProxyIp"], ["geoip:telegram", "203.0.113.0/24"])
|
||||
|
||||
Reference in New Issue
Block a user