Repository: jameszeroX/XKeen
Branch: main
Commit: 115123510a92
Files: 97
Total size: 356.9 KB
Directory structure:
gitextract_qdk3i1xx/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── package-folder.yaml
│ ├── release.yaml
│ └── wiki-sync.yaml
├── .gitignore
├── 01_info_variable.sh
├── LICENSE
├── README.md
├── configuration.md
├── docs/
│ ├── README.md
│ ├── architecture.md
│ ├── build-and-release.md
│ ├── commands.md
│ ├── contributing.md
│ └── runtime-paths.md
├── forkinfo.md
├── install.sh
├── knownissues.md
├── scripts/
│ ├── _xkeen/
│ │ ├── 01_info/
│ │ │ ├── 00_info_import.sh
│ │ │ ├── 01_info_variable.sh
│ │ │ ├── 02_info_packages.sh
│ │ │ ├── 03_info_cpu.sh
│ │ │ ├── 04_info_mihomo.sh
│ │ │ ├── 04_info_xray.sh
│ │ │ ├── 05_info_geofile.sh
│ │ │ ├── 06_info_console.sh
│ │ │ ├── 07_info_cron.sh
│ │ │ └── 08_info_version/
│ │ │ ├── 00_version_import.sh
│ │ │ ├── 01_version_xkeen.sh
│ │ │ ├── 02_version_mihomo.sh
│ │ │ └── 02_version_xray.sh
│ │ ├── 02_install/
│ │ │ ├── 00_install_import.sh
│ │ │ ├── 01_install_packages.sh
│ │ │ ├── 02_install_mihomo.sh
│ │ │ ├── 02_install_xray.sh
│ │ │ ├── 03_install_xkeen.sh
│ │ │ ├── 04_install_geofile.sh
│ │ │ ├── 05_install_geoipset.sh
│ │ │ ├── 06_install_cron.sh
│ │ │ ├── 07_install_register/
│ │ │ │ ├── 00_register_common.sh
│ │ │ │ ├── 00_register_import.sh
│ │ │ │ ├── 01_register_mihomo.sh
│ │ │ │ ├── 01_register_xray.sh
│ │ │ │ ├── 02_register_xkeen.sh
│ │ │ │ ├── 03_register_cron.sh
│ │ │ │ └── 04_register_init.sh
│ │ │ └── 08_install_configs/
│ │ │ ├── 00_configs_import.sh
│ │ │ ├── 01_configs_install.sh
│ │ │ └── 02_configs_dir/
│ │ │ ├── 01_log.json
│ │ │ ├── 02_dns.json
│ │ │ ├── 03_inbounds.json
│ │ │ ├── 04_outbounds.json
│ │ │ ├── 05_routing.json
│ │ │ └── 06_policy.json
│ │ ├── 03_delete/
│ │ │ ├── 00_delete_import.sh
│ │ │ ├── 01_delete_geofile.sh
│ │ │ ├── 02_delete_geoipset.sh
│ │ │ ├── 03_delete_cron.sh
│ │ │ ├── 04_delete_configs.sh
│ │ │ ├── 05_delete_register.sh
│ │ │ └── 06_delete_tmp.sh
│ │ ├── 04_tools/
│ │ │ ├── 00_tools_import.sh
│ │ │ ├── 01_tools_ports.sh
│ │ │ ├── 02_tools_modules.sh
│ │ │ ├── 03_tools_diagnostic.sh
│ │ │ ├── 04_tools_delay.sh
│ │ │ ├── 05_tools_choice/
│ │ │ │ ├── 00_choice_import.sh
│ │ │ │ ├── 01_choice_cores.sh
│ │ │ │ ├── 02_choice_xkeen.sh
│ │ │ │ ├── 03_choice_geofile.sh
│ │ │ │ ├── 04_choice_input.sh
│ │ │ │ └── 05_choice_cron/
│ │ │ │ ├── 00_cron_import.sh
│ │ │ │ ├── 01_cron_status.sh
│ │ │ │ └── 02_cron_time.sh
│ │ │ ├── 06_tools_backups/
│ │ │ │ ├── 00_backups_import.sh
│ │ │ │ ├── 01_backups_xkeen.sh
│ │ │ │ ├── 02_backups_configs_mihomo.sh
│ │ │ │ └── 02_backups_configs_xray.sh
│ │ │ └── 07_tools_downloaders/
│ │ │ ├── 00_downloaders_import.sh
│ │ │ ├── 00_fetch_with_mirrors.sh
│ │ │ ├── 01_downloaders_mihomo.sh
│ │ │ ├── 01_downloaders_xray.sh
│ │ │ └── 02_donwloaders_xkeen.sh
│ │ ├── 05_tests/
│ │ │ ├── 00_tests_import.sh
│ │ │ ├── 01_tests_connected.sh
│ │ │ ├── 02_tests_xports.sh
│ │ │ └── 03_tests_storage.sh
│ │ ├── about.sh
│ │ └── import.sh
│ └── xkeen
├── test/
│ └── README.md
└── wiki/
├── DNS-over-VLESS.md
├── FAQ.md
├── Home.md
├── _Footer.md
├── _Sidebar.md
└── Маршрутизация-по-DSCP.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Unix files that are always LF
*.md text eol=lf
*.sh text eol=lf
*.json text eol=lf
xkeen text eol=lf
================================================
FILE: .github/workflows/package-folder.yaml
================================================
name: Create Test build to `main/test/` folder
on:
push:
branches:
- main
paths:
- 'scripts/**'
workflow_dispatch:
jobs:
build-and-push-to-main-test-folder:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
git_tag_gpgsign: true
git_config_global: true
- name: Prepare scripts with build timestamp
run: |
export TZ="Europe/Moscow"
BUILD_TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S MSK")
mkdir -p scripts_for_build
cp -r scripts/* scripts_for_build/
sed -i "s/^build_timestamp=\".*\"/build_timestamp=\"$BUILD_TIMESTAMP\"/" \
scripts_for_build/_xkeen/01_info/01_info_variable.sh
- name: Create tar.gz archive
run: |
mkdir -p output
cd scripts_for_build
chmod +x xkeen
find . -type f -o -type l | sed 's|^\./||' | tar -czf "../output/xkeen.tar.gz" -T -
- name: Move archive to test folder in main
run: |
mkdir -p test
mv output/xkeen.tar.gz test/
- name: Clean up temporary files
run: |
rm -rf scripts_for_build output
- name: Commit and push signed archive to test folder
run: |
git add test/xkeen.tar.gz
git commit -S -m "[github-actions] automated compiling build"
git push origin main
================================================
FILE: .github/workflows/release.yaml
================================================
name: Create Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version number (e.g., 1.0.0)'
required: true
type: string
prerelease:
description: 'Is this a pre-release?'
required: true
default: false
type: boolean
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
git_tag_gpgsign: true
git_config_global: true
- name: Set up variables
run: |
echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
echo "ARCHIVE_NAME=xkeen.tar.gz" >> $GITHUB_ENV
echo "ARCHIVE_NAME_TAR=xkeen.tar" >> $GITHUB_ENV
- name: Prepare scripts with build timestamp
run: |
export TZ="Europe/Moscow"
BUILD_TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S MSK")
mkdir -p scripts_for_release
cp -r scripts/* scripts_for_release/
sed -i "s/^build_timestamp=\".*\"/build_timestamp=\"$BUILD_TIMESTAMP\"/" \
scripts_for_release/_xkeen/01_info/01_info_variable.sh
- name: Create release archive
run: |
mkdir -p dist
cd scripts_for_release
chmod +x xkeen
find . -type f -o -type l | sed 's|^\./||' | tar -czf "../dist/${ARCHIVE_NAME}" -T -
find . -type f -o -type l | sed 's|^\./||' | tar -cf "../dist/${ARCHIVE_NAME_TAR}" -T -
- name: Clean up temporary files
run: rm -rf scripts_for_release
- name: Generate release notes with downloads badge
run: |
cat > /tmp/release_notes.md << EOF

EOF
- name: Create signed tag and release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${{ env.VERSION }}"
# Удаляем старый тег если существует
git push origin --delete "$TAG_NAME" 2>/dev/null || true
git tag -d "$TAG_NAME" 2>/dev/null || true
# Создаем подписанный тег
git tag -s "$TAG_NAME" -m "Release $TAG_NAME"
git push origin "$TAG_NAME"
# Создаем релиз
gh release create "$TAG_NAME" \
dist/*.tar.gz \
dist/*.tar \
--title "${{ env.VERSION }}" \
--notes-file /tmp/release_notes.md \
${{ github.event.inputs.prerelease == 'true' && '--prerelease' || '' }} \
--verify-tag
- name: Verify signed tag
run: |
if git tag -v "${{ env.VERSION }}" 2>&1; then
echo "✅ Тег ${{ env.VERSION }} успешно подписан и верифицирован!"
else
echo "⚠️ Предупреждение: Не удалось верифицировать подпись тега"
fi
================================================
FILE: .github/workflows/wiki-sync.yaml
================================================
name: Sync GitHub Wiki
on:
push:
branches:
- main
paths:
- 'wiki/**'
- '.github/workflows/wiki-sync.yaml'
workflow_dispatch:
jobs:
sync-wiki:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
git_tag_gpgsign: true
git_config_global: true
- name: Clone wiki repository
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git clone "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git" wiki-repo
- name: Sync wiki content
run: |
rsync -a --delete --exclude='.git' wiki/ wiki-repo/
- name: Commit and push signed wiki update
working-directory: wiki-repo
run: |
git add -A
if git diff --staged --quiet; then
echo "No wiki changes to commit"
exit 0
fi
SHORT_SHA="${GITHUB_SHA:0:7}"
git commit -S -m "[github-actions] sync wiki from main@${SHORT_SHA}"
BRANCH=$(git symbolic-ref --short HEAD)
git push origin "$BRANCH"
================================================
FILE: .gitignore
================================================
.claude
graphify-out
done
src/graphify
src/output
================================================
FILE: 01_info_variable.sh
================================================
# -------------------------------------
# Цвета
# -------------------------------------
green="\033[92m" # Зеленый
red="\033[91m" # Красный
yellow="\033[93m" # Желтый
light_blue="\033[96m" # Голубой
italic="\033[3m" # Курсив
reset="\033[0m" # Сброс цветов
# -------------------------------------
# Директории
# -------------------------------------
tmp_dir_global="/opt/tmp" # Временная директория общая
tmp_dir="/opt/tmp/xkeen" # Временная директория XKeen
xtmp_dir="/opt/tmp/xray" # Временная директория Xray
mtmp_dir="/opt/tmp/mihomo" # Временная директория Mihomo
xkeen_dir="/opt/sbin/.xkeen" # Директория скриптов XKeen
xkeen_cfg="/opt/etc/xkeen" # Директория конфигурации XKeen
xkeen_log_dir="/opt/var/log/xkeen" # Директория логов XKeen
xray_log_dir="/opt/var/log/xray" # Директория логов Xray
initd_dir="/opt/etc/init.d" # Директория init.d
pid_dir="/opt/var/run" # Директория pid файлов
backups_dir="/opt/backups" # Директория бекапов
install_dir="/opt/sbin" # Директория установки
geo_dir="/opt/etc/xray/dat" # Директория для dat
cron_dir="/opt/var/spool/cron/crontabs" # Директория планировщика
cron_file="root" # Файл планировщика
install_conf_dir="/opt/etc/xray/configs" # Директория конфигурации Xray
mihomo_conf_dir="/opt/etc/mihomo" # Директория конфигурации Mihomo
xray_conf_dir="$xkeen_dir/02_install/08_install_configs/02_configs_dir"
xkeen_var_file="$xkeen_dir/01_info/01_info_variable.sh"
register_dir="/opt/lib/opkg/info"
status_file="/opt/lib/opkg/status"
os_modules="/lib/modules/$(uname -r)"
user_modules="/opt/lib/modules"
xkeen_current_version="1.1.3.9"
xkeen_build="Stable"
build_timestamp="2026-02-07 08:58:11 MSK (fix yq)"
# -------------------------------------
# Время
# -------------------------------------
existing_content=$(cat "$status_file")
installed_size=$(du -s "$install_dir" | cut -f1)
source_date_epoch=$(date +%s)
current_datetime=$(date "+%d-%b-%y_%H-%M")
# -------------------------------------
# IP для проверки доступа в интернет
# -------------------------------------
conn_IP1="195.208.4.1"
conn_IP2="77.88.44.55"
# -------------------------------------
# URL
# -------------------------------------
xkeen_api_url="https://api.github.com/repos/jameszeroX/xkeen/releases/latest" # url api для XKeen
xkeen_jsd_url="https://data.jsdelivr.com/v1/package/gh/jameszeroX/xkeen" # резервный url api для XKeen
xkeen_tar_url="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz" # url для загрузки XKeen
xkeen_dev_url="https://raw.githubusercontent.com/jameszeroX/xkeen/main/test/xkeen.tar.gz" # url для загрузки XKeen dev
xray_api_url="https://api.github.com/repos/XTLS/Xray-core/releases" # url api для Xray
xray_jsd_url="https://data.jsdelivr.com/v1/package/gh/XTLS/Xray-core" # резервный url api для Xray
xray_zip_url="https://github.com/XTLS/Xray-core/releases/download" # url для загрузки Xray
mihomo_api_url="https://api.github.com/repos/MetaCubeX/mihomo/releases" # url api для Mihomo
mihomo_jsd_url="https://data.jsdelivr.com/v1/package/gh/MetaCubeX/mihomo" # резервный url api для Mihomo
mihomo_gz_url="https://github.com/MetaCubeX/mihomo/releases/download" # url для загрузки Mihomo
yq_dist_url="https://github.com/jameszeroX/yq/releases/latest/download" # url для загрузки Yq
gh_proxy1="https://ghfast.top" # 1 прокси для загрузок с GitHub
gh_proxy2="https://gh-proxy.com" # 2 прокси для загрузок с GitHub
# url для загрузки геофайлов
refilter_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geosite.dat"
refilterip_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geoip.dat"
v2fly_url="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"
v2flyip_url="https://github.com/loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
zkeen_url="https://github.com/jameszeroX/zkeen-domains/releases/latest/download/zkeen.dat"
zkeenip_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/zkeenip.dat"
# -------------------------------------
# Создание директорий и файлов
# -------------------------------------
mkdir -p "$xray_log_dir" || { echo "Ошибка: Не удалось создать директорию $xray_log_dir"; exit 1; }
mkdir -p "$initd_dir" || { echo "Ошибка: Не удалось создать директорию $initd_dir"; exit 1; }
mkdir -p "$pid_dir" || { echo "Ошибка: Не удалось создать директорию $pid_dir"; exit 1; }
mkdir -p "$backups_dir" || { echo "Ошибка: Не удалось создать директорию $backups_dir"; exit 1; }
mkdir -p "$install_dir" || { echo "Ошибка: Не удалось создать директорию $install_dir"; exit 1; }
mkdir -p "$cron_dir" || { echo "Ошибка: Не удалось создать директорию $cron_dir"; exit 1; }
# -------------------------------------
# Журналы
# -------------------------------------
xray_access_log="$xray_log_dir/access.log"
xray_error_log="$xray_log_dir/error.log"
touch "$xray_access_log" || { echo "Ошибка: Не удалось создать файл $xray_access_log"; exit 1; }
touch "$xray_error_log" || { echo "Ошибка: Не удалось создать файл $xray_error_log"; exit 1; }
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2026, jameszeroX
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# XKeen 1.1.3.9
> **XKeen** — утилита для выборочной маршрутизации сетевого трафика через прокси‑движки **Xray** и **Mihomo** на роутерах **Keenetic**/**Netcraze**.
> Позволяет прозрачно направлять TCP/UDP‑трафик только выбранных клиентов, не затрагивая остальную сеть.
---
## Основные возможности
- Выборочная маршрутизация для клиентов в политике доступа в интернет
- Сохранение прямого выхода в интернет для остальных клиентов
- Маршрутизация без политики для всех клиентов роутера
- Поддержка режимов **TProxy**, **Mixed**, **Redirect**, **Other** (socks5/http)
- Прозрачное проксирование **TCP** и **UDP**
- Поддержка ядер-проксирования **Xray** и **Mihomo**
- Совместимость с **KeeneticOS 5+**
- Управление через shell и [веб-панели](https://github.com/jameszeroX/XKeen?tab=readme-ov-file#дополнения) сторонних разработчиков
XKeen работает полностью на стороне роутера, не меняет настройки клиентов и не требует установки на них дополнительных программ.
---
## Предупреждения
> [!WARNING]
> Данный материал подготовлен в научно‑технических целях. XKeen предназначен для управления межсетевым экраном роутера Keenetic, защищающим домашнюю сеть. Разработчик не несёт ответственности за иное использование утилиты. Перед применением убедитесь, что ваши действия соответствуют законодательству вашей страны.
> [!CAUTION]
> В некоторых случаях протокол IPv6 создаёт проблемы при проксировании. В KeeneticOS IPv6 нельзя полностью отключить стандартными средствами. В XKeen реализован альтернативный механизм его отключения, который полностью убирает IPv6‑трафик на роутере. Это **экспериментальная функция** и может привести к некорректной работе отдельных сервисов Keenetic. Используйте её только при необходимости.
> [!NOTE]
> Установка XKeen гарантируется на внешние USB‑накопители. Установка во внутреннюю память роутера возможна, но требует опыта пользователя. Проблемы, связанные с установкой во внутреннюю память, не считаются ошибками XKeen.
---
Данный репозиторий является форком оригинального XKeen с исправлениями, расширенной функциональностью и поддержкой актуальных версий KeeneticOS.
## Ключевые изменения форка
### Исправлено
- автозапуск XKeen
- сняты ограничения на количество используемых портов
### Добавлено
- поддержка **KeeneticOS 5+**
- управление IPv6
- поддержка ядра **Mihomo**
- быстрое переключение Xray / Mihomo
- контроль [файловых дескрипторов](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#контроль-файловых-дескрипторов)
- [внешние списки](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#внешние-списки-портов-и-ip) IP и портов
- [OffLine](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#offline-установка)‑установка
- [Self-Hosted](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки)-прокси для загрузки компонентов
### Удалено
- не актуальные и повреждённые геобазы
- неиспользуемые конфигурационные файлы
- устаревшие параметры запуска и задачи планировщика
---
### Подробное [описание изменений](https://github.com/jameszeroX/XKeen/blob/main/forkinfo.md)
---
Список параметров запуска XKeen доступен в справке:
```bash
xkeen -h
```
---
## Порядок установки
Требуется роутер **Keenetic**/**Netcraze** с предварительно установленной средой Entware и компонентом `Модули ядра подсистемы Netfilter`
```bash
opkg update && opkg upgrade && opkg install curl tar && cd /tmp
sh -c "$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)"
```
---
## Поддержка проекта
Форк XKeen, как и оригинал, совершено бесплатен и не имеет каких либо ограничений по использованию. Надеюсь, доработки XKeen, многие из которых я сделал по Вашим просьбам, оказались полезны, так же, как и мои сообщения в [телеграм-чате](https://t.me/+8Cvh7oVf6cE0MWRi). Для меня очень важно понимать, что труд и время потрачены не зря. Буду благодарен за любую Вашу поддержку на развитие проекта:
- [CloudTips](https://pay.cloudtips.ru/p/7edb30ec)
- [ЮMoney](https://yoomoney.ru/to/41001350776240)
- Карта МИР: `2204 1201 2976 4110`
- USDT, сеть TRC20: `TQhy1LbuGe3Bz7EVrDYn67ZFLDjDBa2VNX`
- USDT, сеть ERC20: `0x6a5DF3b5c67E1f90dF27Ff3bd2a7691Fad234EE2`
Уточните актуальность крипто-адресов перед переводом
---
## Дополнения
- XKeen UI — https://github.com/zxc-rv/XKeen-UI
- XKeen UI — https://github.com/umarcheh001/Xkeen-UI
- XKeen UI — https://github.com/fan92rus/xkeen-ui
- Генератор Outbound — https://zxc-rv.github.io/XKeen-UI/Outbound_Generator/
- Парсер подписок - https://github.com/tkukushkin/xkeen-subscription-watcher
- Парсер подписок — https://github.com/V2as/SubKeen
- Mihomo Studio — https://github.com/l-ptrol/mihomo_studio
- Конвертер JSON-подписок — https://sngvy.github.io/json-sub-to-outbounds
- Mihomo HWID Subscription Installer — https://github.com/dorian6996/Mihomo-HWID-Subscription
---
## Источники и ссылки
- Origin XKeen — https://github.com/Skrill0/XKeen
- Xray-core — https://github.com/XTLS/Xray-core
- Mihomo — https://github.com/MetaCubeX/mihomo
- Yq — https://github.com/mikefarah/yq
- FAQ — https://jameszero.net/faq-xkeen.htm
- Telegram‑чат — https://t.me/+8Cvh7oVf6cE0MWRi
================================================
FILE: configuration.md
================================================
---
## Внешние списки портов и IP
Предусмотрена возможность добавить в конфигурационные файлы XKeen необходимые порты проксирования или исключения из проксирования, а также IP-адреса и подсети, проксирование которых не требуется. Файлы находятся в директории `/opt/etc/xkeen/`
- `port_proxying.lst` - порты проксирования, например 80 и 443
- `port_exclude.lst` - порты, которые необходимо исключить из проксирования, например, 3389.
- `ip_exclude.lst` - IP-адреса и подсети, которые необходимо исключить из проксирования, например, 77.88.8.8
Каждый порт и IP указываются в этих файлах с новой строки. Пустые строки и строки, начинающиеся со знака комментария `#` игнорируются. Порты проксирования и порты исключенные из проксирования не могут применяться вместе, используйте или то, или другое. Если по ошибке будут заполнены оба списка, то приоритет имеют порты проксирования, а список исключенных портов игнорируется. При чистой установке XKeen создаются шаблоны вышеуказанных файлов с примерами их заполнения.
---
## Контроль файловых дескрипторов
В среде entware Keenetic довольно низкий предел файловых дескрипторов (fd) выделяемых на один процесс - 1024. Превышение этого значения приводит к отказу прокси-клиенту создавать новые коннекты. На процессорах arm64 суммарно на все процессы отведено около 50000 fd, а на mips процессорах около 12000 fd (цифры могут отличаться для разных моделей роутеров). Оригинал XKeen выделяет для процесса xray 1 миллион! дескрипторов и если xray попытается их занять, роутер может просто зависнуть. Форк XKeen версии 1.1.3.6+, в зависимости от процессора, устанавливает лимит 40000 fd для arm64 и 10000 fd для остальных процессоров, а при достижении 90% от этой цифры, перезапускает прокси-клиент. В параметрах `arm64_fd` и `other_fd` стартового скрипта XKeen, при необходимости, можно откорректировать лимиты, установив другие значения. Контроль fd может быть полезен для высоконагруженных роутеров с большим количеством клиентов. Для типичного self-use применения XKeen, данный функционал использовать необязательно, поэтому по умолчанию контроль открытых файловых дескрипторов прокси-клиентом отключен (включить/отключить можно командой `xkeen -fd`). Возможностью контролировать открытые файловые дескрипторы следует пользоваться в крайних ситуациях, когда ничего уже не помогает, так как при этом прокси-клиент будет постоянно перезапускаться для сброса дескрипторов. Рекомендуется вместо этого найти и устранить причину. Это может быть устройство или приложение, открывающее множество подключений.
---
## Self-Hosted-прокси для загрузки
В базовый конфиг добавлены два GitHub-прокси, через которые возможна загрузка XKeen и его компонентов в случае недоступности GitHub. Если же и они окажутся недоступны, можете установить [Self-Hosted прокси](https://github.com/hunshcn/gh-proxy) на своём сервере и указать его в переменной `gh_proxy1` или `gh_proxy2` файла `/opt/sbin/.xkeen/01_info/01_info_variable.sh`
---
## OffLine-установка
Обычная установка XKeen и необходимых компонентов выполняется в OnLine режиме и жёстко привязана к GitHub, а в случае его недоступности будет невозможна. Поэтому в форк дополнительно к способу установки через [Self-Hosted](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки)-прокси добавлен режим OffLine-установки по команде `xkeen -io`
Для OffLine-установки необходимо заранее любым способом скачать установочный архив XKeen версии 1.1.3.9+, ядро проксирования [xray](https://github.com/XTLS/Xray-core/releases/latest) и(или) [mihomo](https://github.com/MetaCubeX/mihomo/releases/latest) + парсер yaml-файлов [yq](https://github.com/jameszeroX/yq/releases/latest) подходящей архитектуры. Если планируте использовать xray и геофайлы в роутинге, то загрузите и их. Следующим шагом поместите в папку /opt/sbin/ архив XKeen (не распаковывая) и предварительно извлечённые из архива и при необходимости переименованные в `xray` `mihomo` и `yq` бинарники, затем выполните OffLine-установку командами в ssh-консоли entware Keenetic:
```
cd /opt/sbin
tar -xvzf xkeen.tar.gz && rm xkeen.tar.gz
xkeen -io
#
```
Копирование файлов конфигурации xray, mihomo и необходимых геофайлов в директории /opt/etc/xray/configs, /opt/etc/mihomo, /opt/etc/xray/dat выполните вручную, после чего можете запустить проксирование командой `xkeen -start`
При OffLine-установке XKeen не проверяет соответствие архитектуры процессора и бинарников, поэтому выбирайте совместимые бинарники внимательно. Если затрудняетесь в выборе, запустите `xkeen -io` без xray и mihomo в папке /opt/sbin/ и XKeen сообщит, какая архитектура требуется для вашего роутера.
При недоступности GitHub, обновление геофайлов по планировщику работать не будет, выполняйте его вручную.
Если недоступен не только GitHub, но и [репозиторий Entware](http://bin.entware.net), то перед OffLine установкой XKeen требуется вручную установить недостающие пакеты из следующего списка:
```
curl, tar, lscpu, jq, libc, libssp, librt, libpthread, iptables, ca-bundle, coreutils-uname, coreutils-nohup
```
либо прописать в файл `/opt/etc/opkg.conf` рабочее зеркало репозитория
================================================
FILE: docs/README.md
================================================
# Документация XKeen
Этот каталог содержит техническую документацию для разработчиков и контрибьюторов. Если вы пользователь и просто хотите установить XKeen — начните с корневого [`README.md`](../README.md) и [`configuration.md`](../configuration.md).
## Содержание
| Документ | О чём |
| --- | --- |
| [architecture.md](architecture.md) | Точка входа, фазы импорта модулей, режимы проксирования, SSoT-переменные |
| [build-and-release.md](build-and-release.md) | GitHub Actions: пакет в Beta-канал, релиз, синхронизация Wiki. Каналы обновлений |
| [runtime-paths.md](runtime-paths.md) | Раскладка файлов и каталогов на роутере (`/opt/...`) |
| [commands.md](commands.md) | Справочник флагов `xkeen` |
| [contributing.md](contributing.md) | Правила правки кода, ограничения POSIX-`sh`, рабочий цикл проверки |
## Связанные документы в корне репозитория
- [`README.md`](../README.md) — обзор и установка для пользователей.
- [`configuration.md`](../configuration.md) — внешние списки портов/IP, fd-контроль, Self-Hosted прокси, OffLine-установка.
- [`forkinfo.md`](../forkinfo.md) — отличия форка от оригинала Skrill0/XKeen.
- [`knownissues.md`](../knownissues.md) — известные ограничения. Читать перед триажом багов.
- [`test/README.md`](../test/README.md) — release-notes 2.0 Beta, новые параметры и инварианты.
## Wiki
Исходники GitHub Wiki лежат в [`../wiki/`](../wiki) и автоматически синхронизируются в `.wiki.git` через workflow `.github/workflows/wiki-sync.yaml`. См. [build-and-release.md](build-and-release.md#workflow-wiki-syncyaml).
================================================
FILE: docs/architecture.md
================================================
# Архитектура XKeen
XKeen — POSIX-shell утилита (`sh`, не `bash`) для роутеров Keenetic/Netcraze под Entware. Кода на компилируемых языках нет. Целевые архитектуры — `arm64-v8a`, `mips32le`, `mips32`. Запускается на роутере; в этом репозитории — только исходники и упаковка.
## Точка входа
[`scripts/xkeen`](../scripts/xkeen) — монолитный POSIX-`sh` диспетчер (~1450 строк). Парсит флаги через большой `while/case` начиная со строки 119. Каждый флаг — самостоятельная команда.
### Скрытие установочного каталога
При первом запуске функция `install_xkeen_rename` ([`scripts/xkeen:7-21`](../scripts/xkeen)) переименовывает `_xkeen/` → `.xkeen/`. **Все runtime-пути в коде ссылаются на `.xkeen`. В репозитории каталог называется `_xkeen` — путать легко.** CI пакует именно `_xkeen`.
### Self-detach
Если процесс запущен без TTY (cron, ssh без `-t`, CGI) и команда из `{-start,-stop,-restart}`, `xkeen` форкается через `start-stop-daemon -b` в новую session/pgid с логом в `/opt/var/log/xkeen-detached.log`. Это защищает от обрыва родительской сессии. См. [`scripts/xkeen:43-70`](../scripts/xkeen). Переменная `XKEEN_FOREGROUND=1` отключает детач для скриптов с синхронной семантикой (`xkeen -start && cleanup`).
## Импорт модулей
Все модули — это `. file.sh`-импортируемые библиотеки функций. Точка сборки — [`scripts/_xkeen/import.sh`](../scripts/_xkeen/import.sh), которая последовательно тянет `00_*_import.sh` каждой фазы:
| Фаза | Каталог | Назначение |
| --- | --- | --- |
| 01 | [`01_info/`](../scripts/_xkeen/01_info) | Переменные (SSoT — `01_info_variable.sh`), детекция CPU, проверка установленных Xray/Mihomo/geofile, версии, консольный вывод, cron-статус |
| 02 | [`02_install/`](../scripts/_xkeen/02_install) | Установка: opkg-пакеты → ядра (Xray, Mihomo) → XKeen → geofile/IPSET → cron → регистрация (`07_install_register/`) → шаблоны конфигов (`08_install_configs/02_configs_dir/`) |
| 03 | [`03_delete/`](../scripts/_xkeen/03_delete) | Точечное удаление компонентов + полная деинсталляция (`-remove`) |
| 04 | [`04_tools/`](../scripts/_xkeen/04_tools) | Сервис: управление портами, модули ядра, диагностика, задержка автозапуска, интерактивный выбор (`05_tools_choice/`), бэкапы (`06_tools_backups/`), загрузчики через GH-proxy fallback (`07_tools_downloaders/`) |
| 05 | [`05_tests/`](../scripts/_xkeen/05_tests) | Runtime-проверки сети, портов, носителя |
## Single Source of Truth: `01_info_variable.sh`
Файл [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh) — единственное место, где определены:
- Версия и канал: `xkeen_current_version`, `xkeen_build`, `build_timestamp` (последнее — подставляется CI).
- Все runtime-каталоги: `xkeen_dir=/opt/sbin/.xkeen`, `xkeen_cfg=/opt/etc/xkeen`, `geo_dir=/opt/etc/xray/dat`, и др.
- Все внешние URL: GitHub API для XKeen/Xray/Mihomo, прямые URL архивов, geofile-репозитории.
- GitHub-прокси для регионов с ограничениями: `gh_proxy1=https://gh-proxy.com`, `gh_proxy2=https://ghfast.top`.
При смене версии или URL правится только этот файл. Релизный workflow перезаписывает в нём только `build_timestamp`.
## GH-proxy fallback
Любая загрузка с GitHub в [`04_tools/07_tools_downloaders/`](../scripts/_xkeen/04_tools/07_tools_downloaders) и в корневом [`install.sh`](../install.sh) идёт по цепочке:
1. Прямой URL (например, `github.com/.../xkeen.tar.gz`).
2. `gh_proxy1` префиксом — `https://gh-proxy.com/`.
3. `gh_proxy2` префиксом — `https://ghfast.top/`.
С версии 2.0 Beta параметр `gh_proxy` из `/opt/etc/xkeen/xkeen.json` имеет приоритет над встроенными значениями.
Маркер `/tmp/toff` (создаётся при запуске с флагом `-toff`) отключает `curl -m 180` на одну сессию — полезно для медленных каналов.
## Режимы проксирования
В рантайме определяется один из четырёх режимов:
| Режим | Признак |
| --- | --- |
| TProxy | Inbound с `streamSettings.sockopt.tproxy == "tproxy"` (Xray) или `listeners[].type == "tproxy"` (Mihomo) |
| Hybrid | Бывший Mixed — комбинация TProxy + Redirect |
| Redirect | Inbound с `sockopt.tproxy == "redirect"`. Самый быстрый, но без UDP |
| Other | socks5/http inbound |
Определение режима — в [`scripts/_xkeen/02_install/07_install_register/04_register_init.sh`](../scripts/_xkeen/02_install/07_install_register/04_register_init.sh) парсингом конфигов Xray (`tproxy`/`redirect` в `streamSettings.sockopt`) и Mihomo (`tproxy-port`, `listeners[].type == "tproxy"`).
**Имена inbound-тегов больше не влияют на режим — исправление 2.0 Beta.** Ранее теги вроде `tproxy-in`/`redirect-in` использовались как fallback, что приводило к ошибкам при кастомных тегах.
## Beta-функции
Описаны в [`test/README.md`](../test/README.md). Кратко:
- Кастомные политики маршрутизации в `xkeen.json`.
- IPSET `ru-exclude` — исключение российских IP из проксирования на уровне ipset.
- DSCP-метки 62 (исключение) и 63 (проксирование) — маршрутизация по приоритетам QoS-пакетов. См. также wiki-страницу [Маршрутизация по DSCP](../wiki/Маршрутизация-по-DSCP.md).
- Проксирование трафика Entware-пакетов с `routing-mark: 255` (Xray) / `mark: 255` (Mihomo).
================================================
FILE: docs/build-and-release.md
================================================
# Сборка и релиз
Локальной сборки нет. Всё делает CI на GitHub Actions. В этом разделе — три workflow-а и две схемы каналов обновлений.
## Workflow-ы
### `package-folder.yaml`
[`.github/workflows/package-folder.yaml`](../.github/workflows/package-folder.yaml)
| Параметр | Значение |
| --- | --- |
| Триггер | `push` в `main` с изменениями в `scripts/**`, либо `workflow_dispatch` |
| Результат | `test/xkeen.tar.gz` — Beta-канал, упакован из `scripts/*` |
| Подпись | GPG-подписанный автокоммит `[github-actions] automated compiling build` |
Шаги:
1. Checkout с `fetch-depth: 0`.
2. Импорт GPG-ключа через `crazy-max/ghaction-import-gpg@v7` с `git_config_global: true`.
3. Подмена `build_timestamp="…"` в `scripts/_xkeen/01_info/01_info_variable.sh` на текущее MSK-время.
4. Упаковка: `cd scripts_for_build && find . -type f -o -type l | sed 's|^\./||' | tar -czf .../xkeen.tar.gz -T -`. На верхнем уровне архива — `xkeen` и `_xkeen/`, без вложенного `scripts/`.
5. Перемещение архива в `test/` и подписанный коммит обратно в `main`.
**Файл `test/xkeen.tar.gz` — артефакт CI, руками не редактировать.**
### `release.yaml`
[`.github/workflows/release.yaml`](../.github/workflows/release.yaml)
| Параметр | Значение |
| --- | --- |
| Триггер | `workflow_dispatch` с входами `version` (string) и `prerelease` (boolean) |
| Результат | `dist/xkeen.tar.gz` + `dist/xkeen.tar` + GitHub Release + подписанный GPG-тег |
Шаги:
1. Checkout с `fetch-depth: 0`.
2. Импорт GPG-ключа.
3. Подмена `build_timestamp` (как в `package-folder.yaml`).
4. Двойная упаковка: `.tar.gz` (для роутеров с `tar`+gzip) и `.tar` (для альтернативных распаковщиков).
5. Удаление существующего тега, создание подписанного `git tag -s "$VERSION"`, push.
6. `gh release create` с обоими архивами. При `prerelease=true` — флаг `--prerelease`.
7. Верификация подписи `git tag -v`.
### `wiki-sync.yaml`
[`.github/workflows/wiki-sync.yaml`](../.github/workflows/wiki-sync.yaml)
| Параметр | Значение |
| --- | --- |
| Триггер | `push` в `main` с изменениями в `wiki/**` или сам workflow, либо `workflow_dispatch` |
| Результат | Содержимое `wiki/` синхронизировано в `.wiki.git` подписанным коммитом |
Шаги:
1. Checkout главного репо.
2. Импорт GPG-ключа (тот же `crazy-max/ghaction-import-gpg@v7`).
3. Клонирование `.wiki.git` через `https://x-access-token:${GITHUB_TOKEN}@github.com/.wiki.git`.
4. `rsync -a --delete --exclude='.git' wiki/ wiki-repo/` — добавление, обновление, удаление.
5. Подписанный коммит `[github-actions] sync wiki from main@` и push в дефолтную ветку Wiki.
Пререкизиты для прода:
- В Settings → Features → Wikis: ✅ enabled.
- В Wiki создана хотя бы одна страница через UI (иначе `.wiki.git` отдаёт 404).
- В Settings → Actions → General → Workflow permissions: `Read and write permissions`.
- Secret `GPG_PRIVATE_KEY` (passphrase не используется).
## Каналы обновлений
| Канал | Источник | Триггер |
| --- | --- | --- |
| Stable | GitHub Release с тегом, `xkeen_tar_url` | Прогон `release.yaml` |
| Beta | `test/xkeen.tar.gz` в ветке `main`, `xkeen_dev_url` | Любой merge в `main` с изменениями `scripts/**` |
На роутере переключение каналов — `xkeen -channel`. Текущая версия и канал хранятся в `01_info_variable.sh` (`xkeen_current_version`, `xkeen_build`).
## Воспроизвести локальную сборку
Без CI, для отладки упаковки:
```sh
cd scripts && find . -type f -o -type l | sed 's|^\./||' | tar -czf /tmp/xkeen.tar.gz -T -
```
Результат идентичен тому, что генерирует `package-folder.yaml` (за исключением подменённого `build_timestamp`).
================================================
FILE: docs/commands.md
================================================
# Справочник флагов `xkeen`
Полный список флагов диспетчера [`scripts/xkeen`](../scripts/xkeen). Извлечён из `help_xkeen()` в [`scripts/_xkeen/about.sh`](../scripts/_xkeen/about.sh). Деструктивные флаги отмечены ⚠ — они интерактивные, требуют подтверждения, не имеют опции «тихого» режима.
## Установка
| Флаг | Действие |
| --- | --- |
| `-i`, `-install` | Полный цикл: XKeen + Xray + GeoFile/GeoIPSET + Mihomo |
| `-io` | OffLine-установка XKeen из локальной флешки |
| `-toff` | Отключить таймаут `curl` для медленных каналов: `xkeen -i -toff` |
## Переустановка
| Флаг | Действие |
| --- | --- |
| `-k` | XKeen |
| `-g` | GeoFile (GeoSite + GeoIP) |
| `-gips` | GeoIPSET |
## Обновление
| Флаг | Действие |
| --- | --- |
| `-uk` | XKeen (получает Stable или Beta — см. `-channel`) |
| `-ug` | GeoFile/GeoIPSET |
| `-ux` | Xray — повышение или понижение версии |
| `-um` | Mihomo — повышение или понижение версии |
## Автообновление GeoFile/GeoIPSET (cron-задача)
| Флаг | Действие |
| --- | --- |
| `-ugc` | Создать cron-задачу |
| `-dgc` | Удалить cron-задачу |
## Резервные копии
| Флаг | Действие |
| --- | --- |
| `-kb` | Создать резервную копию XKeen |
| `-kbr` | Восстановить XKeen из резервной копии |
| `-xb` | Создать резервную копию конфигурации Xray |
| `-xbr` | Восстановить конфигурацию Xray |
| `-mb` | Создать резервную копию конфигурации Mihomo |
| `-mbr` | Восстановить конфигурацию Mihomo |
## Удаление ⚠
| Флаг | Действие |
| --- | --- |
| `-remove` | ⚠ Полная деинсталляция XKeen |
| `-dgs` | ⚠ Удалить GeoSite |
| `-dgi` | ⚠ Удалить GeoIP |
| `-dgips` | ⚠ Удалить GeoIPSET |
| `-dx` | ⚠ Удалить Xray |
| `-dm` | ⚠ Удалить Mihomo |
| `-dk` | ⚠ Удалить XKeen (сохраняет ядра) |
## Порты проксирования
| Флаг | Действие |
| --- | --- |
| `-ap` | Добавить порт |
| `-dp` | Удалить порт |
| `-cp` | Посмотреть список |
## Порты, исключённые из проксирования
| Флаг | Действие |
| --- | --- |
| `-ape` | Добавить порт-исключение |
| `-dpe` | Удалить порт-исключение |
| `-cpe` | Посмотреть список |
## Управление прокси-клиентом
| Флаг | Действие |
| --- | --- |
| `-start` | Запуск |
| `-stop` | Остановка |
| `-restart` | Перезапуск |
| `-status` | Статус работы |
| `-tp` | Порты, шлюз и протокол прокси-клиента |
| `-auto` | Включить / отключить автозапуск |
| `-d` | Установить задержку автозапуска: `xkeen -d 30` (секунд) |
| `-fd` | Включить / отключить контроль файловых дескрипторов |
| `-cfd` | Посчитать количество открытых файловых дескрипторов прокси-клиента |
| `-diag` | Выполнить полную диагностику (единственный поддерживаемый канал для отчёта о проблеме) |
| `-channel` | Переключить канал обновлений (Stable / Beta) |
| `-xray` | Переключить XKeen на ядро Xray |
| `-mihomo` | Переключить XKeen на ядро Mihomo |
| `-ipv6` | Включить / отключить протокол IPv6 в KeeneticOS |
| `-dns` | Включить / отключить перенаправление DNS в прокси |
| `-pr` | Включить / отключить проксирование трафика Entware |
| `-extmsg` | Включить / отключить расширенные сообщения при запуске |
| `-cbk` | Включить / отключить резервное копирование XKeen при обновлении |
| `-aghfix` | Включить / отключить отображение клиентов под своими IP в журнале AdGuard Home |
## Информация
| Флаг | Действие |
| --- | --- |
| `-about` | О программе |
| `-ad` | Поддержать разработчиков |
| `-af` | Обратная связь / контакты |
| `-v` | Версия XKeen |
| `-h`, `-help` | Показать встроенную справку (`help_xkeen` из `about.sh`) |
## Переменные окружения
| Переменная | Значение | Эффект |
| --- | --- | --- |
| `XKEEN_FOREGROUND` | `1` | Отключает self-detach при запуске без TTY. Использовать в скриптах с синхронной семантикой (`xkeen -start && cleanup`) |
| `XKEEN_DETACHED` | `1` | Внутренний маркер — выставляется самим `xkeen` после форка через `start-stop-daemon -b`. Руками не выставлять |
================================================
FILE: docs/contributing.md
================================================
# Правила правки
## Язык — POSIX `sh`
Целевая среда — Entware на BusyBox `ash`. **Никаких bash-измов:**
| Запрещено | Использовать вместо |
| --- | --- |
| `[[ … ]]` | `[ … ]` (POSIX `test`) |
| `${var,,}`, `${var^^}` | `echo "$var" \| tr 'A-Z' 'a-z'` |
| Массивы (`arr=(a b c)`, `${arr[i]}`) | Позиционные параметры, IFS-split строки |
| `<<<` (here-string) | `echo "…" \| cmd` или `<< EOF` |
| `function name()` | `name()` |
| `local var` | Не использовать — `local` не POSIX |
| `(( … ))` арифметика | `$(( … ))` или `expr` |
| `read -p` | `printf '...'; read var` |
Проверка перед PR:
```sh
shellcheck scripts/xkeen scripts/_xkeen/**/*.sh
```
## Пути и URL — только из переменных
Все пути и URL определены в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh). **Не хардкодить ни одного `/opt/...` пути и ни одного `https://github.com/...` URL** в других файлах. Если нужен новый путь — добавить переменную в `01_info_variable.sh`.
## Добавление модуля
1. Определить, к какой фазе относится: `01_info/`, `02_install/`, `03_delete/`, `04_tools/`, `05_tests/`. Если новый раздел внутри `02_install/` (например, поддиректория `09_install_X/`) — создать каталог и поместить туда `00__import.sh`.
2. Создать файл `NN_.sh` в нужном каталоге (нумерация — следующая свободная).
3. Подключить через `.` в соответствующем `00_*_import.sh` родительского каталога.
4. Если модуль зависит от других — следить за порядком импорта.
## Добавление новой команды
1. Case-ветка в [`scripts/xkeen`](../scripts/xkeen) в большом `while/case` (начинается со строки 119).
2. Если команда из `{-start, -stop, -restart}` или другая, требующая self-detach в фоне — добавить в проверку на строках 43-48 (`detach_eligible=true`).
3. Описание флага — в `help_xkeen()` функции [`scripts/_xkeen/about.sh`](../scripts/_xkeen/about.sh) под подходящим разделом.
4. Если команда деструктивная — обязательно интерактивное подтверждение перед действием. Не делать «тихие» деструктивные операции.
## Лимиты файловых дескрипторов
Значения в стартовом скрипте: `arm64_fd=40000`, `other_fd=10000`. Не править наугад — увеличение влечёт расход RAM, уменьшение — обрывы соединений на пиках. См. также соответствующий раздел в [`configuration.md`](../configuration.md).
## Self-detach
Блок в [`scripts/xkeen:43-70`](../scripts/xkeen) — критичный для cron-перезапусков. Без него родитель убивает дочерний процесс по SIGHUP при обрыве ssh-сессии. Трогать только осознанно.
## Проверка перед PR
1. `shellcheck scripts/xkeen scripts/_xkeen/**/*.sh` — нулевая толерантность к новым warning-ам.
2. Деплой архива на тестовый роутер и прогон сценариев: `xkeen -i`, `-start`, `-stop`, `-restart`, `-uk`, `-diag`.
3. Если правились флаги управления (`-ap`, `-dp`, `-ape`, `-dpe`) или режимы проксирования — отдельно прогнать с обоими ядрами (Xray и Mihomo) и в каждом из режимов TProxy/Hybrid/Redirect.
4. `xkeen -diag` — единственный поддерживаемый канал для отчёта о проблеме.
## CI-файлы — не трогать руками
- [`.github/workflows/package-folder.yaml`](../.github/workflows/package-folder.yaml) и сам артефакт [`test/xkeen.tar.gz`](../test/xkeen.tar.gz) — генерируются CI. Любые ручные правки будут перезаписаны при следующем push в `main` с изменениями `scripts/**`.
- [`.github/workflows/release.yaml`](../.github/workflows/release.yaml) — менять только если действительно меняется процесс релиза.
- [`.github/workflows/wiki-sync.yaml`](../.github/workflows/wiki-sync.yaml) — синхронизирует [`wiki/`](../wiki) в GitHub Wiki. Менять только при изменении логики синхронизации.
## Документация
- Корневые `README.md`, `configuration.md`, `forkinfo.md`, `knownissues.md` — пользовательская документация. При фичах, затрагивающих пользователя, — обновлять.
- [`test/README.md`](../test/README.md) — release-notes 2.0 Beta. При новой Beta-фиче — добавить запись.
- [`docs/`](.) — техническая документация для контрибьюторов. При структурных изменениях кода — обновлять `architecture.md` / `runtime-paths.md` / `commands.md`.
- [`wiki/`](../wiki) — публичная Wiki для пользователей. Обновления синхронизируются автоматически.
## Каналы и версии
- Ветка `main` → Beta-канал, `test/xkeen.tar.gz`, автоматически после push.
- GitHub Release с подписанным тегом → Stable-канал.
- Версия и канал хранятся в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh): `xkeen_current_version`, `xkeen_build`.
================================================
FILE: docs/runtime-paths.md
================================================
# Раскладка на роутере
Все runtime-пути на роутере. В этом репозитории каталог `_xkeen/`; после установки на роутер он переименовывается в `.xkeen/` функцией `install_xkeen_rename`. Все переменные путей определены в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh) — не хардкодить.
## Исполняемые файлы и модули
| Путь | Назначение |
| --- | --- |
| `/opt/sbin/xkeen` | Диспетчер (исполняемый, монолитный POSIX-sh) |
| `/opt/sbin/.xkeen/` | Каталог импортируемых модулей XKeen |
| `/opt/sbin/.xkeen/import.sh` | Точка сборки модулей |
| `/opt/etc/init.d/S05xkeen` | Init-скрипт XKeen, генерируется `04_register_init.sh` |
| `/opt/etc/init.d/S05crond` | Cron-демон, обслуживает автообновления geofile |
## Пользовательский конфиг
`/opt/etc/xkeen/` — все настройки, которые правит пользователь.
| Файл | Назначение |
| --- | --- |
| `xkeen.json` | Главный конфиг: `gh_proxy`, политики, расширения 2.0 Beta |
| `ip_exclude.lst` | IP/подсети, исключённые из проксирования (с маской `/32` для одиночных адресов) |
| `port_proxying.lst` | Порты, направляемые в прокси. С 2.0 Beta — единственный источник, старая `port_donor` упразднена |
| `port_exclude.lst` | Порты, исключённые из проксирования. С 2.0 Beta — единственный источник, старая `port_exclude` (как переменная) упразднена |
| `ipset/ru_exclude_ipv4.lst` | IPv4-сеты для российских IP — Beta-функция исключения по ipset |
| `ipset/ru_exclude_ipv6.lst` | То же для IPv6 |
## Конфиги ядер
| Путь | Назначение |
| --- | --- |
| `/opt/etc/xray/configs/` | Все JSON-конфиги Xray (`inbounds.json`, `outbounds.json`, `routing.json`, `dns.json`) |
| `/opt/etc/xray/dat/` | GeoSite (`*.dat`) и GeoIP (`*.dat`) базы |
| `/opt/etc/mihomo/` | Конфигурация Mihomo (`config.yaml` и подключаемые) |
## Логи
| Путь | Назначение |
| --- | --- |
| `/opt/var/log/xkeen/` | Логи самого XKeen |
| `/opt/var/log/xray/access.log` | Access-лог Xray |
| `/opt/var/log/xray/error.log` | Error-лог Xray |
| `/opt/var/log/xkeen-detached.log` | Лог фоновых запусков (self-detach из `-start/-stop/-restart` без TTY) |
## Runtime-state
| Путь | Назначение |
| --- | --- |
| `/opt/var/run/` | PID-файлы (`xkeen.pid`, `xray.pid`, `mihomo.pid`) |
| `/opt/tmp/xkeen/` | Временная директория XKeen |
| `/opt/tmp/xray/`, `/opt/tmp/mihomo/` | Временные директории ядер |
| `/opt/backups/` | Архивы резервных копий (флаги `-kb`, `-xb`, `-mb`) |
| `/opt/var/spool/cron/crontabs/root` | Cron-задачи (создаются флагом `-ugc`) |
## Хуки в netfilter.d / schedule.d
| Путь | Назначение |
| --- | --- |
| `/opt/etc/ndm/netfilter.d/proxy.sh` | Хук при пересборке правил межсетевого экрана Keenetic — переставляет iptables-правила прокси |
| `/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh` | Хук на смену клиентов hotspot — обновляет ipset `xkeen_deny_mac` |
## Маркеры
| Файл | Что значит |
| --- | --- |
| `/tmp/toff` | Маркер сессии: отключает таймаут `curl -m 180`. Создаётся флагом `-toff`, очищается trap-ом INT/TERM |
| `/opt/etc/ndm/netfilter.d/aghfix.sh` | Опциональный фикс отображения клиентов в AdGuard Home (флаг `-aghfix`) |
================================================
FILE: forkinfo.md
================================================
## Сравнение форка с оригинальным XKeen
Изменения:
- Исправлено добавление портов в исключения (ранее команду `xkeen -ape` нужно было прерывать по ctrl+c)
- Исправлена совместная работа режима TProxy и socks5 (ранее Xkeen запускался в Mixed режиме, что приводило к неработоспособности прозрачного проксирования)
- Исправлен автозапуск XKeen при старте роутера (ранее XKeen в некоторых случаях не запускался или запускался для всего устройства, а не только для своей политики - [FAQ п.12](https://jameszero.net/faq-xkeen.htm#12))
- Снято техническое ограничение, позволявшее использовать не более 15 портов проксирования и портов исключенных из проксирования
- Переработана логика загрузки XKeen, Xray, Mihomo и GeoFile из интернета, уменьшающая вероятность их повреждения
- Переработана логика применения правил iptables и ip6tables (ранее XKeen применял все правила, даже при не установленном компоненте IPv6)
- Переработана логика добавления и удаления портов проксирования и исключаемых портов
- При обновлении геофайлов, добавлении/удалении портов проксирования или портов исключений, а также выполнении других настроек, требующих перезапуск XKeen, прокси-клиент теперь перезапускается если был до этого запущен
- При запуске `xkeen -d` без цифрового параметра, теперь отображается информация о текущей задержке автозапуска
- При запуске или перезапуске XKeen теперь отображается информация о режиме работы - TProxy, Mixed, Redirect, Other
- Не актуальные GeoSite и GeoIP antifilter-community заменены на базы [Re:filter](https://github.com/1andrevich/Re-filter-lists)
- Объединены задачи планировщика по обновлению GeoSite и GeoIP. В связи с этим упразднены параметры запуска `-ugs`, `-ugi`, `-ugsc`, `-ugic`, `-dgsc`, `-dgic`
- Параметр запуска `-ux` для обновления ядра Xray теперь поддерживает повышение/понижение версии
- Корректная деинсталляция xray-core (ранее пакет xray не удалялся при деинсталляции)
- Справка (`xkeen -h`) выровнена по табуляции и повышен контраст текста
- Скрипт запуска S24xray переименован в S99xkeen
- Рефакторинг кода скриптов
- Актуализация конфигурационных файлов xray-core
Добавлено:
- Совместимость с прошивкой KeeneticOS 5+
- Возможность отключить/включить протокол IPv6 в KeeneticOS (параметр запуска `-ipv6`)
- Поддержка ядра Mihomo
- Возможность сменить ядро проксирования (Xray/Mihomo) параметрами `-xray` и `-mihomo`
- При обновлении Xray и Mihomo теперь отображается версия уже установленного в роутере бинарника
- Добавлена возможность отключить/включить перехват DNS запросов при соответствующей настройке прокси-клиента (параметр запуска `-dns`)
- Поддержка внешних файлов `ip_exclude.lst`, `port_proxying.lst` и `port_exclude.lst` в директории `/opt/etc/xkeen/` для указания IP и портов (проксирования/исключения из проксирования)
- Возможность загружать компоненты XKeen через [Self-Hosted прокси](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки-компонентов) при недоступности GitHub (переменные `gh_proxy(1|2)` в файле `01_info_variable.sh`)
- Возможность отключить резервное копирование XKeen при обновлении (переменная `backup` в стартовом скрипте)
- Возможность [OffLine установки](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#offline-установка) (параметр `-io`)
- Возможность установки GeoIP базы [zkeenip.dat](https://github.com/jameszeroX/zkeen-ip)
- Обновление [zkeen.dat](https://github.com/jameszeroX/zkeen-domains) и [zkeenip.dat](https://github.com/jameszeroX/zkeen-ip) по расписанию средствами XKeen
- При недоступности GitHub API используется резервный источник релизов для XKeen, Xray и Mihomo
- При установке теперь можно выбрать, добавлять ли XKeen в автозагрузку при включении роутера или нет
- При пропуске установки Xray, его конфигурационные файлы и геобазы так же пропускаются и не устанавливаются
- Mihomo и парсер yaml-файлов Yq устанавливаются и регистрируются в entware, как полноценные ipk-пакеты
- Параметр запуска `-remove` для полной деинсталляции XKeen (ранее деинсталляцию нужно было выполнять покомпонентно)
- Параметры запуска `-ug` (обновление геофайлов), `-ugc` (управление заданием Cron, обновляющим геофайлы), `-dgc` (удаление задания Cron, обновляющего геофайлы)
- Параметр запуска `-um` для обновления/установки ядра Mihomo (поддерживается повышение/понижение версии)
- Параметры запуска: `-rrm` (обновить регистрацию Mihomo), `-drm` (удалить регистрацию Mihomo)
- Параметр запуска `-dm` для деинсталляции ядра Mihomo
- Параметр запуска `-g`, позволяющий переустановить (добавить/удалить) геофайлы для Xray
- Параметр запуска `-channel`, позволяющий выбрать канал обновления XKeen между Stable и Dev ветками
- Возможность резервного копирования и восстановления конфигурации Mihomo (параметры `-mb`, `-mbr`)
- Возможность контролировать число открытых файловых дескрипторов, используемых прокси-клиентом и перезапускать процесс при исчерпании лимита [подробнее](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#контроль-файловых-дескрипторов)
Удалено:
- Поддержка внешнего файла `/opt/etc/xkeen_exclude.lst` c IP-адресами и подсетями для исключения из проксирования
- Возможность установки GeoSite Antizapret (база повреждена в репозитории)
- Конфигурационный файл `02_transport.json` (не используется новыми ядрами xray-core)
- Запрос на перезапись и сама перезапись конфигурационных файлов Xray, если они уже существуют на момент установки XKeen
- Создание резервных копий Xray, так как теперь можно интерактивно установить предыдущую версию ядра параметром `-ux`. В связи с этим упразднены параметры запуска `-xb` и `-xbr`
- Логирование процесса установки XKeen в директорию `/opt/var/log/xkeen` (на практике не использовалось)
- Задачи планировщика по автообновлению XKeen/Xray. В связи с этим упразднены параметры запуска `-uac`, `-ukc`, `-uxc`, `-dac`, `-dkc` и `-dxc`
- Параметры запуска: `-x` (заменён на `-ux`), `-rk` (заменён на `-rrk`), `-rx` (заменён на `-rrx`), `-rc` (не актуален)
================================================
FILE: install.sh
================================================
#!/bin/sh
green="\033[92m"
red="\033[91m"
yellow="\033[93m"
light_blue="\033[96m"
reset="\033[0m"
url_stable="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz"
url_beta="https://raw.githubusercontent.com/jameszeroX/XKeen/main/test/xkeen.tar.gz"
archive_name="xkeen.tar.gz"
release_fix_url="https://raw.githubusercontent.com/jameszeroX/XKeen/main/01_info_variable.sh"
clear
echo
printf " Какую версию ${yellow}XKeen${reset} вы хотите установить?\n\n"
printf " 1) Стабильную версию (${light_blue}Stable${reset})\n"
printf " 2) Новую Бета-версию (${light_blue}Beta${reset})\n\n"
printf " Выберите 1 или 2 [по умолчанию 1]: "
read -r version_choice
case "$version_choice" in
2)
url="$url_beta"
echo
printf " Выбрана ${light_blue}Бета-версия${reset}\n"
;;
*)
url="$url_stable"
echo
printf " Выбрана ${light_blue}Стабильная версия${reset}\n"
;;
esac
echo
get_release_var_file() {
if [ -f /opt/sbin/_xkeen/01_info/01_info_variable.sh ]; then
printf '%s\n' "/opt/sbin/_xkeen/01_info/01_info_variable.sh"
return 0
fi
if [ -f /opt/sbin/.xkeen/01_info/01_info_variable.sh ]; then
printf '%s\n' "/opt/sbin/.xkeen/01_info/01_info_variable.sh"
return 0
fi
return 1
}
download_xkeen_release() {
if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "$url"; then
return 0
fi
if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "https://gh-proxy.com/$url"; then
return 0
fi
if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "https://ghfast.top/$url"; then
return 0
fi
printf " ${red}Ошибка${reset}: не удалось загрузить ${yellow}xkeen.tar.gz${reset}\n"
return 1
}
download_release_fix() {
target_file="$1"
if curl -fLo "$target_file" --connect-timeout 10 -m 15 "$release_fix_url"; then
return 0
fi
if curl -fLo "$target_file" --connect-timeout 10 -m 15 "https://gh-proxy.com/$release_fix_url"; then
return 0
fi
if curl -fLo "$target_file" --connect-timeout 10 -m 15 "https://ghfast.top/$release_fix_url"; then
return 0
fi
printf " ${red}Ошибка${reset}: не удалось применить исправление ${yellow}01_info_variable.sh${reset} для релиза ${green}1.1.3.9${reset}\n"
return 1
}
apply_release_1139_yq_fix() {
release_var_file="$(get_release_var_file)" || {
printf " ${red}Ошибка${reset}: после распаковки не найден файл ${yellow}01_info_variable.sh${reset}\n"
return 1
}
release_version=$(sed -n 's/^xkeen_current_version="\([^"]*\)".*/\1/p' "$release_var_file" | head -n 1)
release_build=$(sed -n 's/^xkeen_build="\([^"]*\)".*/\1/p' "$release_var_file" | head -n 1)
if [ "$release_version" = "1.1.3.9" ] && [ "$release_build" = "Stable" ]; then
if ! download_release_fix "$release_var_file"; then
return 1
fi
fi
}
if ! download_xkeen_release; then
exit 1
fi
if ! tar -xzf "$archive_name" -C /opt/sbin; then
rm -f "$archive_name"
printf " ${red}Ошибка${reset}: не удалось распаковать ${yellow}xkeen.tar.gz${reset}\n"
exit 1
fi
rm -f "$archive_name"
if [ ! -x /opt/sbin/xkeen ]; then
printf " ${red}Ошибка${reset}: после распаковки не найден исполняемый файл ${yellow}/opt/sbin/xkeen${reset}\n"
exit 1
fi
if ! apply_release_1139_yq_fix; then
exit 1
fi
exec /opt/sbin/xkeen -i
================================================
FILE: knownissues.md
================================================
- При проксировании DNS с помощью XKeen, в профиле "Политика по умолчанию" отсутствует интернет, создайте пользовательсую политкику вместо этого профиля
- При подключении к роутеру нескольких провайдеров, XKeen работает через "Основное подключение", даже если в политике XKeen отметить галкой "Резервное"
================================================
FILE: scripts/_xkeen/01_info/00_info_import.sh
================================================
# Импорт информационных модулей
# Модуль информации
. "$xinfo_dir/01_info_variable.sh"
. "$xinfo_dir/02_info_packages.sh"
. "$xinfo_dir/03_info_cpu.sh"
. "$xinfo_dir/04_info_mihomo.sh"
. "$xinfo_dir/04_info_xray.sh"
. "$xinfo_dir/05_info_geofile.sh"
. "$xinfo_dir/06_info_console.sh"
. "$xinfo_dir/07_info_cron.sh"
. "$xinfo_dir/08_info_version/00_version_import.sh"
================================================
FILE: scripts/_xkeen/01_info/01_info_variable.sh
================================================
# -------------------------------------
# Цвета
# -------------------------------------
green="\033[92m" # Зеленый
red="\033[91m" # Красный
yellow="\033[93m" # Желтый
light_blue="\033[96m" # Голубой
italic="\033[3m" # Курсив
reset="\033[0m" # Сброс цветов
# -------------------------------------
# Информация
# -------------------------------------
current_datetime=$(date +"%Y-%m-%d_%H-%M")
xkeen_current_version="2.0"
xkeen_build="Beta"
build_timestamp=""
# -------------------------------------
# Директории
# -------------------------------------
tmp_dir="/opt/tmp" # Временная директория
ktmp_dir="$tmp_dir/xkeen" # Временная директория XKeen
xtmp_dir="$tmp_dir/xray" # Временная директория Xray
mtmp_dir="$tmp_dir/mihomo" # Временная директория Mihomo
install_dir="/opt/sbin" # Директория установки
xkeen_dir="$install_dir/.xkeen" # Директория скриптов XKeen
xkeen_cfg="/opt/etc/xkeen" # Директория конфигурации XKeen
ipset_cfg="$xkeen_cfg/ipset" # Директория IPSET
log_dir="/opt/var/log" # Директория логов
xray_log_dir="$log_dir/xray" # Директория логов Xray
initd_dir="/opt/etc/init.d" # Директория init.d
backups_dir="/opt/backups" # Директория бекапов
geo_dir="/opt/etc/xray/dat" # Директория для dat
cron_dir="/opt/var/spool/cron/crontabs" # Директория планировщика
mihomo_conf_dir="/opt/etc/mihomo" # Директория конфигурации Mihomo
xray_conf_dir="/opt/etc/xray/configs" # Директория конфигурации Xray
xray_conf_smpl="$xkeen_dir/02_install/08_install_configs/02_configs_dir"
register_dir="/opt/lib/opkg/info"
os_modules="/lib/modules/$(uname -r)"
user_modules="/opt/lib/modules"
# -------------------------------------
# Файлы
# -------------------------------------
xkeen_var_file="$xkeen_dir/01_info/01_info_variable.sh"
file_port_proxying="$xkeen_cfg/port_proxying.lst"
file_port_exclude="$xkeen_cfg/port_exclude.lst"
file_ip_exclude="$xkeen_cfg/ip_exclude.lst"
ru_exclude_ipv4="$ipset_cfg/ru_exclude_ipv4.lst"
ru_exclude_ipv6="$ipset_cfg/ru_exclude_ipv6.lst"
xkeen_config="$xkeen_cfg/xkeen.json"
status_file="/opt/lib/opkg/status"
initd_file="$initd_dir/S05xkeen"
initd_cron="$initd_dir/S05crond"
cron_file="root"
file_netfilter_hook="/opt/etc/ndm/netfilter.d/proxy.sh"
file_schedule_hook="/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh"
name_ipset_deny_mac="xkeen_deny_mac"
# -------------------------------------
# Ресурсы для проверки доступа в интернет
# -------------------------------------
conn_URL="ya.ru"
conn_IP1="195.208.4.1"
conn_IP2="77.88.44.55"
# -------------------------------------
# URL
# -------------------------------------
xkeen_api_url="https://api.github.com/repos/jameszeroX/xkeen/releases/latest" # url api для XKeen
xkeen_jsd_url="https://data.jsdelivr.com/v1/package/gh/jameszeroX/xkeen" # резервный url api для XKeen
xkeen_tar_url="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz" # url для загрузки XKeen
xkeen_dev_url="https://raw.githubusercontent.com/jameszeroX/xkeen/main/test/xkeen.tar.gz" # url для загрузки XKeen dev
xray_api_url="https://api.github.com/repos/XTLS/Xray-core/releases" # url api для Xray
xray_jsd_url="https://data.jsdelivr.com/v1/package/gh/XTLS/Xray-core" # резервный url api для Xray
xray_zip_url="https://github.com/XTLS/Xray-core/releases/download" # url для загрузки Xray
mihomo_api_url="https://api.github.com/repos/MetaCubeX/mihomo/releases" # url api для Mihomo
mihomo_jsd_url="https://data.jsdelivr.com/v1/package/gh/MetaCubeX/mihomo" # резервный url api для Mihomo
mihomo_gz_url="https://github.com/MetaCubeX/mihomo/releases/download" # url для загрузки Mihomo
yq_upstream_dist_url="https://github.com/mikefarah/yq/releases/latest/download" # url для загрузки оригинального Yq
yq_workaround_dist_url="https://github.com/jameszeroX/yq/releases/latest/download" # url для загрузки рабочего Yq
gh_proxy1="https://gh-proxy.com" # 1 прокси для загрузок с GitHub
gh_proxy2="https://ghfast.top" # 2 прокси для загрузок с GitHub
yq_use_workaround="true" # отключить после исправления issue 2609 (по желанию)
yq_workaround_issue_url="https://github.com/mikefarah/yq/issues/2609" # issue с поломанным релизом Yq
get_yq_dist_url() {
if [ "$yq_use_workaround" = "true" ]; then
printf '%s\n' "$yq_workaround_dist_url"
else
printf '%s\n' "$yq_upstream_dist_url"
fi
}
# url для загрузки геофайлов
refilter_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geosite.dat"
refilterip_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geoip.dat"
v2fly_url="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"
v2flyip_url="https://github.com/loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
zkeen_url="https://github.com/jameszeroX/zkeen-domains/releases/latest/download/zkeen.dat"
zkeenip_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/zkeenip.dat"
geoipv4_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/ru"
geoipv6_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/ru6"
# -------------------------------------
# Журналы
# -------------------------------------
xray_access_log="$xray_log_dir/access.log"
xray_error_log="$xray_log_dir/error.log"
# -------------------------------------
# Создание директорий и файлов
# -------------------------------------
init_directories() {
mkdir -p "$xray_log_dir" || { echo "Ошибка: Не удалось создать директорию $xray_log_dir"; exit 1; }
mkdir -p "$initd_dir" || { echo "Ошибка: Не удалось создать директорию $initd_dir"; exit 1; }
mkdir -p "$backups_dir" || { echo "Ошибка: Не удалось создать директорию $backups_dir"; exit 1; }
mkdir -p "$install_dir" || { echo "Ошибка: Не удалось создать директорию $install_dir"; exit 1; }
mkdir -p "$cron_dir" || { echo "Ошибка: Не удалось создать директорию $cron_dir"; exit 1; }
touch "$xray_access_log" || { echo "Ошибка: Не удалось создать файл $xray_access_log"; exit 1; }
touch "$xray_error_log" || { echo "Ошибка: Не удалось создать файл $xray_error_log"; exit 1; }
}
# Таймаут curl
[ -e "/tmp/toff" ] && curl_timeout="" || curl_timeout="-m 180"
# Дополнительные параметры curl
curl_extra=""
================================================
FILE: scripts/_xkeen/01_info/02_info_packages.sh
================================================
# Кэшируем список установленных пакетов один раз вместо opkg-форка на каждую проверку
_packages_cache=$(opkg list-installed 2>/dev/null)
# Функция для проверки наличия необходимых пакетов
info_packages() {
package_name="$1"
# Newline-prefix эмулирует якорь "^pkg ", чтобы libc не матчил libcurl
case "
$_packages_cache" in
*"
$package_name "*) package_status="installed" ;;
*) package_status="not_installed" ;;
esac
}
# Проверка наличия пакета "coreutils-uname"
info_packages "coreutils-uname"
info_packages_uname=$package_status
# Проверка наличия пакета "coreutils-nohup"
info_packages "coreutils-nohup"
info_packages_nohup=$package_status
# Проверка наличия пакета "curl"
info_packages "curl"
info_packages_curl=$package_status
# Проверка наличия пакета "jq"
info_packages "jq"
info_packages_jq=$package_status
# Проверка наличия пакета "libc"
info_packages "libc"
info_packages_libc=$package_status
# Проверка наличия пакета "libssp"
info_packages "libssp"
info_packages_libssp=$package_status
# Проверка наличия пакета "librt"
info_packages "librt"
info_packages_librt=$package_status
# Проверка наличия пакета "libpthread"
info_packages "libpthread"
info_packages_libpthread=$package_status
# Проверка наличия пакета "ca-bundle"
info_packages "ca-bundle"
info_packages_cabundle=$package_status
# Проверка наличия пакета "iptables"
info_packages "iptables"
info_packages_iptables=$package_status
# Проверка наличия пакета "ipset"
info_packages "ipset"
info_packages_ipset=$package_status
================================================
FILE: scripts/_xkeen/01_info/03_info_cpu.sh
================================================
# Функция для получения информации о процессоре
info_cpu() {
if command -v opkg >/dev/null 2>&1; then
opkg_arch=$(opkg print-architecture | awk '!/all/ {print $2; exit}' | cut -d- -f1)
case "$opkg_arch" in
*'aarch64'*) architecture='arm64-v8a' ;;
*'mipsel'*) architecture='mips32le' ;;
*'mips'*) architecture='mips32' ;;
*) architecture="$opkg_arch" ;;
esac
fi
# Получение информации о архитектуре из файла состояния (status_file)
status_architecture=$(grep -m 1 '^Architecture:' "${status_file}" | awk '{print $2}')
# Получение информации о необходимости softfloat банарников
[ "$architecture" != "mips32le" ] && echo && return
version="$(curl -kfsS "localhost:79/rci/show/version" 2>/dev/null)"
case "$version" in
*KN-1212*|*KN-2910*) softfloat="true" ;;
*) echo; return ;;
esac
}
================================================
FILE: scripts/_xkeen/01_info/04_info_mihomo.sh
================================================
# Функция для проверки установки Mihomo
info_mihomo() {
if [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ]; then
mihomo_installed="installed"
else
mihomo_installed="not_installed"
fi
}
================================================
FILE: scripts/_xkeen/01_info/04_info_xray.sh
================================================
# Функция для проверки установки Xray
info_xray() {
if [ -f "$install_dir/xray" ]; then
xray_installed="installed"
else
xray_installed="not_installed"
fi
}
================================================
FILE: scripts/_xkeen/01_info/05_info_geofile.sh
================================================
# Функция для проверки наличия и записи информации о базах GeoSite
info_geosite() {
update_refilter_geosite=false
update_v2fly_geosite=false
update_zkeen_geosite=false
[ -f "$geo_dir/geosite_refilter.dat" ] && update_refilter_geosite=true
[ -f "$geo_dir/geosite_v2fly.dat" ] && update_v2fly_geosite=true
[ -f "$geo_dir/geosite_zkeen.dat" ] || [ -f "$geo_dir/zkeen.dat" ] && update_zkeen_geosite=true
}
# Функция для проверки наличия и записи информации о базах GeoIP
info_geoip() {
update_refilter_geoip=false
update_v2fly_geoip=false
update_zkeenip_geoip=false
[ -f "$geo_dir/geoip_refilter.dat" ] && update_refilter_geoip=true
[ -f "$geo_dir/geoip_v2fly.dat" ] && update_v2fly_geoip=true
[ -f "$geo_dir/geoip_zkeenip.dat" ] || [ -f "$geo_dir/zkeenip.dat" ] && update_zkeenip_geoip=true
}
================================================
FILE: scripts/_xkeen/01_info/06_info_console.sh
================================================
print_log_status() {
local status_code=$1
local success_msg=$2
local error_msg=$3
if [ "$status_code" -eq 0 ]; then
echo -e " ${green}Успешно${reset}: $success_msg"
else
echo -e " ${red}Ошибка${reset}: $error_msg"
fi
}
# Обратная связь в консоль
logs_cpu_info_console() {
echo -e " Набор инструкций процессора: ${yellow}$architecture${reset}"
case "$architecture" in
arm64-v8a|mips32le|mips32)
echo -e " Процессор ${green}поддерживается${reset} XKeen"
;;
*)
echo -e " Процессор ${red}не поддерживается${reset} XKeen"
;;
esac
}
logs_delete_configs_info_console() {
local deleted_files=""
if [ -d "$xray_conf_dir" ]; then
deleted_files=$(find "$xray_conf_dir" -maxdepth 1 -name '*.json' -type f)
fi
if [ -z "$deleted_files" ]; then
echo -e " ${green}Успешно${reset}: Все конфигурационные файлы Xray удалены"
else
echo -e " ${red}Ошибка${reset}: Не удалены следующие конфигурационные файлы:"
for file in $deleted_files; do
echo -e " $file"
done
fi
}
logs_delete_geosite_info_console() {
echo -e " ${yellow}Проверка${reset} выполнения операции"
# antifilter переименован в refilter в install/delete, имя verification отстало
for file in "geosite_refilter.dat" "geosite_v2fly.dat" "geosite_zkeen.dat"; do
[ ! -f "$geo_dir/$file" ]
print_log_status $? "Файл $file отсутствует в директории '$geo_dir'" "Файл $file не удален"
done
}
logs_delete_geoip_info_console() {
echo -e " ${yellow}Проверка${reset} выполнения операции"
for file in "geoip_refilter.dat" "geoip_v2fly.dat" "geoip_zkeenip.dat"; do
[ ! -f "$geo_dir/$file" ]
print_log_status $? "Файл $file отсутствует в директории '$geo_dir'" "Файл $file не удален"
done
}
logs_delete_geoipset_info_console() {
echo -e " ${yellow}Проверка${reset} выполнения операции"
[ ! -f "$ru_exclude_ipv4" ]
print_log_status $? "Файл ru_exclude_ipv4.lst отсутствует в директории '$ipset_cfg'" "Файл ru_exclude_ipv4.lst не удален"
[ ! -f "$ru_exclude_ipv6" ]
print_log_status $? "Файл ru_exclude_ipv6.lst отсутствует в директории '$ipset_cfg'" "Файл ru_exclude_ipv6.lst не удален"
}
# Проверки регистрации XKeen
logs_register_xkeen_status_info_console() {
grep -q "Package: xkeen" "$status_file"
print_log_status $? "Запись XKeen найдена в '$status_file'" "Запись XKeen не найдена в '$status_file'"
}
logs_register_xkeen_control_info_console() {
[ -f "$register_dir/xkeen.control" ]
print_log_status $? "Файл xkeen.control найден в директории '$register_dir/'" "Файл xkeen.control не найден в директории '$register_dir/'"
}
logs_register_xkeen_list_info_console() {
[ -f "$register_dir/xkeen.list" ]
print_log_status $? "Файл xkeen.list найден в директории '$register_dir/'" "Файл xkeen.list не найден в директории '$register_dir/'"
}
logs_register_xkeen_initd_info_console() {
[ -f "$initd_file" ]
print_log_status $? "init скрипт XKeen найден в директории '$initd_dir/'" "init скрипт XKeen не найден в директории '$initd_dir/'"
}
logs_delete_register_xkeen_info_console() {
[ ! -f "$register_dir/xkeen.list" ]
print_log_status $? "Файл xkeen.list не найден в директории '$register_dir/'" "Файл xkeen.list найден в директории '$register_dir/'"
[ ! -f "$register_dir/xkeen.control" ]
print_log_status $? "Файл xkeen.control не найден в директории '$register_dir/'" "Файл xkeen.control найден в директории '$register_dir/'"
! grep -q 'Package: xkeen' "$status_file"
print_log_status $? "Регистрация пакета xkeen не обнаружена в '$status_file'" "Регистрация пакета xkeen обнаружена в '$status_file'"
}
# Проверки регистрации Xray
logs_register_xray_status_info_console() {
grep -q "Package: xray_s" "$status_file"
print_log_status $? "Запись Xray найдена в '$status_file'" "Запись Xray не найдена в '$status_file'"
}
logs_register_xray_control_info_console() {
[ -f "$register_dir/xray_s.control" ]
print_log_status $? "Файл xray_s.control найден в директории '$register_dir/'" "Файл xray_s.control не найден в директории '$register_dir/'"
}
logs_register_xray_list_info_console() {
[ -f "$register_dir/xray_s.list" ]
print_log_status $? "Файл xray_s.list найден в директории '$register_dir/'" "Файл xray_s.list не найден в директории '$register_dir/'"
}
logs_delete_register_xray_info_console() {
[ ! -f "$register_dir/xray_s.list" ]
print_log_status $? "Файл xray_s.list не найден в директории '$register_dir/'" "Файл xray_s.list найден в директории '$register_dir/'"
[ ! -f "$register_dir/xray_s.control" ]
print_log_status $? "Файл xray_s.control не найден в директории '$register_dir/'" "Файл xray_s.control найден в директории '$register_dir/'"
! grep -q 'Package: xray_s' "$status_file"
print_log_status $? "Регистрация пакета xray не обнаружена в '$status_file'" "Регистрация пакета xray обнаружена в '$status_file'"
}
# Проверки регистрации Mihomo
logs_register_mihomo_status_info_console() {
grep -q "Package: mihomo" "$status_file"
print_log_status $? "Запись mihomo найдена в '$status_file'" "Запись mihomo не найдена в '$status_file'"
}
logs_register_mihomo_control_info_console() {
[ -f "$register_dir/mihomo_s.control" ]
print_log_status $? "Файл mihomo_s.control найден в директории '$register_dir/'" "Файл mihomo_s.control не найден в директории '$register_dir/'"
}
logs_register_mihomo_list_info_console() {
[ -f "$register_dir/mihomo_s.list" ]
print_log_status $? "Файл mihomo_s.list найден в директории '$register_dir/'" "Файл mihomo_s.list не найден в директории '$register_dir/'"
}
logs_delete_register_mihomo_info_console() {
[ ! -f "$register_dir/mihomo_s.list" ]
print_log_status $? "Файл mihomo_s.list не найден в директории '$register_dir/'" "Файл mihomo_s.list найден в директории '$register_dir/'"
[ ! -f "$register_dir/mihomo_s.control" ]
print_log_status $? "Файл mihomo_s.control не найден в директории '$register_dir/'" "Файл mihomo_s.control найден в директории '$register_dir/'"
! grep -q 'Package: mihomo_s' "$status_file"
print_log_status $? "Регистрация пакета mihomo не обнаружена в '$status_file'" "Регистрация пакета mihomo обнаружена в '$status_file'"
}
# Проверки регистрации YQ
logs_register_yq_status_info_console() {
grep -q "Package: yq" "$status_file"
print_log_status $? "Запись yq найдена в '$status_file'" "Запись yq не найдена в '$status_file'"
}
logs_register_yq_control_info_console() {
[ -f "$register_dir/yq_s.control" ]
print_log_status $? "Файл yq_s.control найден в директории '$register_dir/'" "Файл yq_s.control не найден в директории '$register_dir/'"
}
logs_register_yq_list_info_console() {
[ -f "$register_dir/yq_s.list" ]
print_log_status $? "Файл yq_s.list найден в директории '$register_dir/'" "Файл yq_s.list не найден в директории '$register_dir/'"
}
logs_delete_register_yq_info_console() {
[ ! -f "$register_dir/yq_s.list" ]
print_log_status $? "Файл yq_s.list не найден в директории '$register_dir/'" "Файл yq_s.list найден в директории '$register_dir/'"
[ ! -f "$register_dir/yq_s.control" ]
print_log_status $? "Файл yq_s.control не найден в директории '$register_dir/'" "Файл yq_s.control найден в директории '$register_dir/'"
! grep -q 'Package: yq_s' "$status_file"
print_log_status $? "Регистрация пакета yq не обнаружена в '$status_file'" "Регистрация пакета yq обнаружена в '$status_file'"
}
# Остальные проверки
logs_delete_cron_geofile_info_console() {
if [ -f "$cron_dir/$cron_file" ]; then
! grep -q "ug" "$cron_dir/$cron_file"
print_log_status $? "Задача автоматического обновления GeoFile удалена из cron" "Задача автоматического обновления GeoFile не удалена из cron"
fi
}
================================================
FILE: scripts/_xkeen/01_info/07_info_cron.sh
================================================
# Проверка наличия задач автоматического обновления в cron
info_cron() {
# Получаем текущую crontab конфигурацию для пользователя root
cron_output=$(crontab -l -u root 2>/dev/null)
# Проверяем наличие задачи с ключевым словом "ug" в crontab
if echo "$cron_output" | grep -q "ug"; then
info_update_geofile_cron="installed"
else
info_update_geofile_cron="not_installed"
fi
}
================================================
FILE: scripts/_xkeen/01_info/08_info_version/00_version_import.sh
================================================
# Импорт модулей проверки версий
# Модули проверки версий
. "$xinfo_dir/08_info_version/01_version_xkeen.sh"
. "$xinfo_dir/08_info_version/02_version_mihomo.sh"
. "$xinfo_dir/08_info_version/02_version_xray.sh"
================================================
FILE: scripts/_xkeen/01_info/08_info_version/01_version_xkeen.sh
================================================
# Функция для получения версии из xkeen API и сохранения ее в переменной
info_version_xkeen() {
version=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xkeen_api_url" | jq -r '.tag_name // .name // ""' 2>/dev/null)
if [ -z "$version" ]; then
echo
printf "${red}Нет доступа${reset} к ${yellow}GitHub API${reset}, пробуем ${yellow}jsDelivr${reset}...\n"
version=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xkeen_jsd_url" | jq -r '.versions | first' 2>/dev/null)
if [ -z "$version" ]; then
echo
printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n"
echo
printf "${red}Ошибка${reset}: Не удалось получить версию ни с ${yellow}GitHub${reset}, ни с ${yellow}jsDelivr${reset}\n
Проверьте соединение с интернетом или повторите позже\n
Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n
https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n"
echo
exit 1
fi
fi
xkeen_github_version="${version}"
}
# Функция для сравнения версий XKeen
info_compare_xkeen() {
if [ "$xkeen_current_version" = "$xkeen_github_version" ]; then
info_compare_xkeen="actual"
else
info_compare_xkeen="update"
fi
}
================================================
FILE: scripts/_xkeen/01_info/08_info_version/02_version_mihomo.sh
================================================
# Функции для получения информации о версиях Mihomo и Yq
info_version_mihomo() {
if [ "$mihomo_installed" = "installed" ]; then
mihomo_current_version=$("$install_dir/mihomo" -v 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1)
mihomo_current_version=${mihomo_current_version:-"unknown"}
else
mihomo_current_version="unknown"
fi
}
info_version_yq() {
if [ "$mihomo_installed" = "installed" ]; then
yq_current_version=$("$install_dir/yq" -V 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1)
yq_current_version=${yq_current_version:-"unknown"}
else
yq_current_version="unknown"
fi
}
================================================
FILE: scripts/_xkeen/01_info/08_info_version/02_version_xray.sh
================================================
# Функция для получения информации о версии Xray
info_version_xray() {
# Проверяем, установлен ли Xray
if [ "$xray_installed" = "installed" ]; then
# Если Xray установлен, получаем текущую версию
xray_current_version=$("$install_dir/xray" version 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1)
xray_current_version=${xray_current_version:-"unknown"}
else
xray_current_version="unknown"
fi
}
================================================
FILE: scripts/_xkeen/02_install/00_install_import.sh
================================================
# Импорт модулей установки
# Модули установки
. "$xinstall_dir/01_install_packages.sh"
. "$xinstall_dir/02_install_xray.sh"
. "$xinstall_dir/02_install_mihomo.sh"
. "$xinstall_dir/03_install_xkeen.sh"
. "$xinstall_dir/04_install_geofile.sh"
. "$xinstall_dir/05_install_geoipset.sh"
. "$xinstall_dir/06_install_cron.sh"
. "$xinstall_dir/07_install_register/00_register_import.sh"
. "$xinstall_dir/08_install_configs/00_configs_import.sh"
================================================
FILE: scripts/_xkeen/02_install/01_install_packages.sh
================================================
# Установка необходимых пакетов
install_packages() {
package_status="$1"
package_name="$2"
if [ "${package_status}" = "not_installed" ]; then
opkg install "$package_name" &>/dev/null
fi
}
install_packages "$info_packages_curl" "curl"
install_packages "$info_packages_jq" "jq"
install_packages "$info_packages_libc" "libc"
install_packages "$info_packages_libssp" "libssp"
install_packages "$info_packages_librt" "librt"
install_packages "$info_packages_iptables" "iptables"
install_packages "$info_packages_libpthread" "libpthread"
install_packages "$info_packages_ipset" "ipset"
install_packages "$info_packages_cabundle" "ca-bundle"
install_packages "$info_packages_uname" "coreutils-uname"
install_packages "$info_packages_nohup" "coreutils-nohup"
================================================
FILE: scripts/_xkeen/02_install/02_install_mihomo.sh
================================================
# Функция для установки Mihomo
install_mihomo() {
echo -e " ${yellow}Выполняется установка${reset} Mihomo. Пожалуйста, подождите..."
# Определение переменных
mihomo_archive="${mtmp_dir}/mihomo.gz"
# Проверка наличия архива Mihomo
if [ ! -f "${mihomo_archive}" ]; then
echo -e " ${red}Ошибка${reset}: Архив Mihomo не найден в '${mtmp_dir}'"
return 1
fi
if [ -f "$install_dir/mihomo" ]; then
mv "$install_dir/mihomo" "$install_dir/mihomo_bak"
fi
if ! gzip -d "${mihomo_archive}" || [ ! -f "${mtmp_dir}/mihomo" ]; then
echo -e " ${red}Ошибка${reset}: Не удалось распаковать архив или файл отсутствует"
if [ -f "$install_dir/mihomo_bak" ]; then
mv "$install_dir/mihomo_bak" "$install_dir/mihomo"
echo -e " ${yellow}Восстановлен${reset} предыдущий бинарник Mihomo"
fi
rm -f "${mtmp_dir}/mihomo.gz" "${mtmp_dir}/mihomo"
return 1
fi
if ! mv "${mtmp_dir}/mihomo" "$install_dir/"; then
echo -e " ${red}Ошибка${reset}: Не удалось установить Mihomo"
[ -f "$install_dir/mihomo_bak" ] && mv "$install_dir/mihomo_bak" "$install_dir/mihomo"
return 1
fi
chmod +x "$install_dir/mihomo"
echo -e " Mihomo ${green}успешно установлен${reset}"
return 0
}
================================================
FILE: scripts/_xkeen/02_install/02_install_xray.sh
================================================
# Функция для установки Xray
install_xray() {
echo -e " ${yellow}Выполняется установка${reset} Xray. Пожалуйста, подождите..."
# Определение переменных
xray_archive="${xtmp_dir}/xray.zip"
# Проверка наличия архива Xray
if [ ! -f "${xray_archive}" ]; then
echo -e " ${red}Ошибка${reset}: Архив Xray не найден в '${xtmp_dir}'"
return 1
fi
if [ -f "$install_dir/xray" ]; then
mv "$install_dir/xray" "$install_dir/xray_bak"
fi
# Распаковка архива Xray
if [ -d "${xtmp_dir}/xray" ]; then
rm -rf "${xtmp_dir}/xray"
fi
if ! unzip -q "${xray_archive}" -d "${xtmp_dir}/xray"; then
echo -e " ${red}Ошибка${reset}: Не удалось распаковать архив"
[ -f "$install_dir/xray_bak" ] && mv "$install_dir/xray_bak" "$install_dir/xray"
return 1
fi
bin_source="${xtmp_dir}/xray/xray"
if [ "$softfloat" = "true" ]; then
if [ -f "${xtmp_dir}/xray/xray_softfloat" ]; then
bin_source="${xtmp_dir}/xray/xray_softfloat"
fi
fi
if [ ! -f "$bin_source" ]; then
echo -e " ${red}Ошибка${reset}: Бинарный файл Xray не найден в архиве"
if [ -f "$install_dir/xray_bak" ]; then
mv "$install_dir/xray_bak" "$install_dir/xray"
echo -e " ${yellow}Восстановлен${reset} предыдущий бинарник Xray"
fi
rm -f "$xray_archive"
rm -rf "${xtmp_dir}/xray"
return 1
fi
mv "$bin_source" "$install_dir/xray"
chmod +x "$install_dir/xray"
echo -e " Xray ${green}успешно установлен${reset}"
rm -f "$xray_archive"
rm -rf "${xtmp_dir}/xray"
# Фикс для новых ядер xray
if [ -d "$xray_conf_dir" ]; then
for file in "$xray_conf_dir"/*.json; do
[ -f "$file" ] || continue
if grep -qE '"transport"\s*:' "$file"; then
mv "$file" "${file}.obsolete"
fi
done
fi
return 0
}
================================================
FILE: scripts/_xkeen/02_install/03_install_xkeen.sh
================================================
# Функция для установки XKeen
install_xkeen() {
xkeen_archive="$ktmp_dir/xkeen.tar.gz"
# Проверка наличия архива XKeen
if [ -f "$xkeen_archive" ]; then
# Распаковка архива
tar -xzf "$xkeen_archive" -C "$install_dir" xkeen _xkeen
# Проверка наличия _xkeen в install_dir и его перемещение
if [ -d "$install_dir/_xkeen" ]; then
rm -rf "$install_dir/.xkeen"
mv "$install_dir/_xkeen" "$install_dir/.xkeen"
else
echo -e " ${red}Ошибка${reset}: _xkeen не была правильно перенесена"
fi
# Удаление архива
rm "$xkeen_archive"
fi
[ -d "$log_dir/xkeen" ] && rm -rf "$log_dir/xkeen"
}
check_keen_mode() {
[ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null)" = "1" ] && return 0
keen_mode="unsupported"
}
================================================
FILE: scripts/_xkeen/02_install/04_install_geofile.sh
================================================
# Функция для загрузки и обработки геофайлов
process_geo_file() {
local url="$1"
local filename="$2"
local display_name="$3"
local update_flag="$4"
# Защита от path traversal
if case "$filename" in */*|*\\*|..|.) true;; *) false;; esac; then
printf " ${red}Ошибка${reset}: Недопустимое имя файла %s (path traversal)\n" "$filename"
return 1
fi
local min_size=24576 # 24 KB
printf " Загрузка %s...\n" "$display_name"
if ! fetch_with_mirrors "$url" "$geo_dir/$filename" "$min_size"; then
case "$_last_error" in
size)
printf " ${red}Ошибка${reset}: загруженный файл слишком мал (%s bytes) или повреждён\n Невозможно обновить. Оставляем старый файл\n\n" "$_last_size"
;;
html_stub)
printf " ${red}Ошибка${reset}: получена HTML-страница вместо dat-файла\n Невозможно обновить. Оставляем старый файл\n\n"
;;
*)
printf " ${red}Ошибка${reset}: не удалось загрузить %s\n" "$display_name"
;;
esac
return 1
fi
if [ "$update_flag" = "true" ]; then
printf " %s ${green}успешно обновлён${reset}\n\n" "$display_name"
else
printf " %s ${green}успешно установлен${reset}\n\n" "$display_name"
fi
return 0
}
# Функция для установки и обновления GeoSite
install_geosite() {
mkdir -p "$geo_dir" || { echo "Ошибка: Не удалось создать директорию $geo_dir"; exit 1; }
local zkeen_datfile=""
if [ "$install_zkeen_geosite" = "true" ] || [ "$update_zkeen_geosite" = "true" ]; then
zkeen_datfile="geosite_zkeen.dat"
if [ -L "$geo_dir/geosite_zkeen.dat" ]; then
zkeen_datfile="zkeen.dat"
elif [ -L "$geo_dir/zkeen.dat" ]; then
zkeen_datfile="geosite_zkeen.dat"
elif [ -f "$geo_dir/zkeen.dat" ] && ! [ -f "$geo_dir/geosite_zkeen.dat" ]; then
zkeen_datfile="zkeen.dat"
fi
fi
# Параллельная загрузка независимых геофайлов
local _pids=""
if [ "$install_refilter_geosite" = "true" ] || [ "$update_refilter_geosite" = "true" ]; then
process_geo_file "$refilter_url" "geosite_refilter.dat" \
"GeoSite Re:filter" "$update_refilter_geosite" &
_pids="$_pids $!"
fi
if [ "$install_v2fly_geosite" = "true" ] || [ "$update_v2fly_geosite" = "true" ]; then
process_geo_file "$v2fly_url" "geosite_v2fly.dat" \
"GeoSite V2Fly" "$update_v2fly_geosite" &
_pids="$_pids $!"
fi
if [ -n "$zkeen_datfile" ]; then
process_geo_file "$zkeen_url" "$zkeen_datfile" \
"GeoSite ZKeen" "$update_zkeen_geosite" &
_pids="$_pids $!"
fi
[ -n "$_pids" ] && wait $_pids
# Симлинки zkeen после успешной загрузки
if [ -n "$zkeen_datfile" ]; then
if [ "$zkeen_datfile" = "geosite_zkeen.dat" ]; then
rm -f "$geo_dir/zkeen.dat"
ln -sf "$geo_dir/geosite_zkeen.dat" "$geo_dir/zkeen.dat"
else
rm -f "$geo_dir/geosite_zkeen.dat"
ln -sf "$geo_dir/zkeen.dat" "$geo_dir/geosite_zkeen.dat"
fi
fi
}
# Функция для установки и обновления GeoIP
install_geoip() {
mkdir -p "$geo_dir" || { echo "Ошибка: Не удалось создать директорию $geo_dir"; exit 1; }
local zkeenip_datfile=""
if [ "$install_zkeenip_geoip" = "true" ] || [ "$update_zkeenip_geoip" = "true" ]; then
zkeenip_datfile="geoip_zkeenip.dat"
if [ -L "$geo_dir/geoip_zkeenip.dat" ]; then
zkeenip_datfile="zkeenip.dat"
elif [ -L "$geo_dir/zkeenip.dat" ]; then
zkeenip_datfile="geoip_zkeenip.dat"
elif [ -f "$geo_dir/zkeenip.dat" ] && ! [ -f "$geo_dir/geoip_zkeenip.dat" ]; then
zkeenip_datfile="zkeenip.dat"
fi
fi
# Параллельная загрузка независимых геофайлов
local _pids=""
if [ "$install_refilter_geoip" = "true" ] || [ "$update_refilter_geoip" = "true" ]; then
process_geo_file "$refilterip_url" "geoip_refilter.dat" \
"GeoIP Re:filter" "$update_refilter_geoip" &
_pids="$_pids $!"
fi
if [ "$install_v2fly_geoip" = "true" ] || [ "$update_v2fly_geoip" = "true" ]; then
process_geo_file "$v2flyip_url" "geoip_v2fly.dat" \
"GeoIP V2Fly" "$update_v2fly_geoip" &
_pids="$_pids $!"
fi
if [ -n "$zkeenip_datfile" ]; then
process_geo_file "$zkeenip_url" "$zkeenip_datfile" \
"GeoIP ZKeenIP" "$update_zkeenip_geoip" &
_pids="$_pids $!"
fi
[ -n "$_pids" ] && wait $_pids
# Симлинки zkeenip после успешной загрузки
if [ -n "$zkeenip_datfile" ]; then
if [ "$zkeenip_datfile" = "geoip_zkeenip.dat" ]; then
rm -f "$geo_dir/zkeenip.dat"
ln -sf "$geo_dir/geoip_zkeenip.dat" "$geo_dir/zkeenip.dat"
else
rm -f "$geo_dir/geoip_zkeenip.dat"
ln -sf "$geo_dir/zkeenip.dat" "$geo_dir/geoip_zkeenip.dat"
fi
fi
}
================================================
FILE: scripts/_xkeen/02_install/05_install_geoipset.sh
================================================
# Валидаторы для fetch_with_mirrors: проверяют размер + базовый синтаксис
# содержимого (catch HTML-stub и мусор от proxy-error-page).
_validate_geoipset_v4() {
_validate_default "$1" "$2" || return 1
if ! grep -q "^[0-9]" "$1"; then
_last_error="content_v4"
return 1
fi
return 0
}
_validate_geoipset_v6() {
_validate_default "$1" "$2" || return 1
if ! grep -q ":" "$1"; then
_last_error="content_v6"
return 1
fi
return 0
}
# Функция для установки и обновления GeoIPSET
install_geoipset_lst() {
mkdir -p "$ipset_cfg" || { echo "Ошибка: Не удалось создать директорию $ipset_cfg"; exit 1; }
url="$1"
dest_file="$2"
display_name="$3"
ip_type="$4"
printf " Загрузка %s...\n" "$display_name"
if [ "$ip_type" = "ipv4" ]; then
_validator_name="_validate_geoipset_v4"
else
_validator_name="_validate_geoipset_v6"
fi
if ! fetch_with_mirrors "$url" "$dest_file" 0 "$_validator_name"; then
case "$_last_error" in
html_stub)
printf " ${red}Ошибка${reset}: получена HTML-страница вместо списка IP\n Оставляем старый файл\n\n"
;;
content_v4)
printf " ${red}Ошибка${reset}: %s не содержит корректных IPv4-адресов\n Оставляем старый файл\n\n" "$display_name"
;;
content_v6)
printf " ${red}Ошибка${reset}: %s не содержит корректных IPv6-адресов\n Оставляем старый файл\n\n" "$display_name"
;;
*)
printf " ${red}Ошибка${reset}: не удалось загрузить %s\n\n" "$display_name"
;;
esac
return 1
fi
[ "$action" = "init" ] && msg_geoipset="установлен" || msg_geoipset="обновлён"
printf " %s ${green}успешно $msg_geoipset${reset}\n\n" "$display_name"
return 0
}
load_geoipset() {
set="$1"
file="$2"
family="$3"
tmp="${set}_tmp"
# Заполняем tmp; основной набор подменяется только после успешного restore
ipset create "$set" hash:net family "$family" -exist
ipset create "$tmp" hash:net family "$family" -exist
ipset flush "$tmp"
if [ -f "$file" ] && awk '/^[0-9a-fA-F]/ {print "add '"$tmp"' "$1}' "$file" | ipset restore -exist; then
ipset swap "$set" "$tmp"
fi
ipset destroy "$tmp"
}
install_geoipset() {
action="$1"
if [ "$action" = "init" ]; then
# Без TTY (cron, ssh -T) read получает EOF, default-case крутит while true
# бесконечно: процесс висит в R-state с CPU-spin. Дефолтим выбор на "1"
# (установить), потому что xkeen -gips из cron это типичный
# non-interactive caller, где пользователь явно ожидает установку.
if [ ! -t 0 ]; then
printf " Не интерактивный режим (нет TTY): автоматическая установка GeoIPSET\n"
bypass_cron_geoipset=false
else
while true; do
printf "\n Желаете исключить российские IP-адреса из проксирования?\n\n"
printf " 1. Загрузить и установить в исключения IP-подсети России (${yellow}GeoIPSET${reset})\n"
printf " 0. Пропустить\n\n"
printf " Ваш выбор: "
read -r choice
case "$choice" in
0)
printf " Пропуск установки списков GeoIPSET\n\n"
if [ ! -f "$ru_exclude_ipv4" ] && [ ! -f "$ru_exclude_ipv6" ]; then
bypass_cron_geoipset=true
fi
return 0
;;
1)
bypass_cron_geoipset=false
break
;;
*)
printf " Неверный ввод. Пожалуйста, введите 1 или 0.\n"
;;
esac
done
fi
fi
local do_v4=0 do_v6=0
if ip -4 addr show 2>/dev/null | grep -q "inet " && command -v iptables >/dev/null 2>&1; then
if [ "$action" = "init" ] || [ -f "$ru_exclude_ipv4" ]; then
do_v4=1
fi
fi
if ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && command -v ip6tables >/dev/null 2>&1; then
if [ "$action" = "init" ] || [ -f "$ru_exclude_ipv6" ]; then
do_v6=1
fi
fi
# Параллельная загрузка независимых списков
local _pids=""
[ "$do_v4" = "1" ] && { install_geoipset_lst "$geoipv4_url" "$ru_exclude_ipv4" "IPv4 (IPSet)" "ipv4" & _pids="$_pids $!"; }
[ "$do_v6" = "1" ] && { install_geoipset_lst "$geoipv6_url" "$ru_exclude_ipv6" "IPv6 (IPSet)" "ipv6" & _pids="$_pids $!"; }
[ -n "$_pids" ] && wait $_pids
[ "$do_v4" = "1" ] && load_geoipset geo_exclude "$ru_exclude_ipv4" inet
[ "$do_v6" = "1" ] && load_geoipset geo_exclude6 "$ru_exclude_ipv6" inet6
}
================================================
FILE: scripts/_xkeen/02_install/06_install_cron.sh
================================================
# Функция для установки задач Cron
install_cron() {
cron_entry=
# Добавление задачи Cron для обновления GeoFile
if [ -n "$choice_geofile_cron_time" ]; then
cron_entry="$choice_geofile_cron_time $install_dir/xkeen -ug"
fi
# Если есть записи для задач Cron, то сохраняем их
if [ -n "$cron_entry" ] || [ -n "$choice_cancel_cron_select" ]; then
cron_file_path="$cron_dir/$cron_file"
touch "$cron_file_path"
chmod +x "$cron_file_path"
if [ -n "$cron_entry" ]; then
grep -v "$install_dir/xkeen -ug" "$cron_file_path" > "$cron_file_path.tmp"
mv "$cron_file_path.tmp" "$cron_file_path"
printf "%s\n" "$cron_entry" >> "$cron_file_path"
fi
sed -i '/^$/d' "$cron_file_path"
fi
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/00_register_common.sh
================================================
# Общие функции для регистрации пакетов в opkg
write_opkg_control() {
package_name="$1"
package_version="$2"
package_depends="$3"
package_source="$4"
package_source_name="$5"
package_maintainer="$6"
package_description="$7"
_installed_size=$(du -s "$install_dir" | cut -f1)
_source_date_epoch=$(date +%s)
{
echo "Package: $package_name"
echo "Version: $package_version"
[ -n "$package_depends" ] && echo "Depends: $package_depends"
echo "Source: $package_source"
echo "SourceName: $package_source_name"
echo "Section: net"
echo "SourceDateEpoch: $_source_date_epoch"
echo "Maintainer: $package_maintainer"
echo "Architecture: $status_architecture"
echo "Installed-Size: $_installed_size"
echo "Description: $package_description"
} > "$register_dir/$package_name.control"
}
write_opkg_status() {
package_name="$1"
package_version="$2"
package_depends="$3"
status_entry="$(mktemp)"
{
echo "Package: $package_name"
echo "Version: $package_version"
[ -n "$package_depends" ] && echo "Depends: $package_depends"
echo "Status: install user installed"
echo "Architecture: $status_architecture"
echo "Installed-Time: $(date +%s)"
} > "$status_entry"
echo "" >> "$status_file"
cat "$status_entry" >> "$status_file"
echo "" >> "$status_file"
rm -f "$status_entry"
sed -i '/^$/{N;/^\n$/D}' "$status_file"
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/00_register_import.sh
================================================
# Импорт модулей регистраций
# Модули регистрации
. "$xinstall_dir/07_install_register/00_register_common.sh"
. "$xinstall_dir/07_install_register/01_register_xray.sh"
. "$xinstall_dir/07_install_register/01_register_mihomo.sh"
. "$xinstall_dir/07_install_register/02_register_xkeen.sh"
. "$xinstall_dir/07_install_register/03_register_cron.sh"
================================================
FILE: scripts/_xkeen/02_install/07_install_register/01_register_mihomo.sh
================================================
# Регистрация Mihomo
register_mihomo_list() {
cd "$register_dir/" || exit
touch mihomo_s.list
echo "/opt/sbin/mihomo" >> mihomo_s.list
echo "/opt/etc/mihomo/config.yaml" >> mihomo_s.list
echo "/opt/etc/mihomo" >> mihomo_s.list
}
register_mihomo_control() {
write_opkg_control \
"mihomo_s" \
"$mihomo_current_version" \
"yq_s" \
"MetaCubeX" \
"mihomo_s" \
"jameszero" \
"A unified platform for anti-censorship."
}
register_mihomo_status() {
write_opkg_status \
"mihomo_s" \
"$mihomo_current_version" \
"yq_s"
}
register_yq_list() {
cd "$register_dir/" || exit
touch yq_s.list
echo "/opt/sbin/yq" >> yq_s.list
}
register_yq_control() {
write_opkg_control \
"yq_s" \
"$yq_current_version" \
"" \
"mikefarah" \
"yq_s" \
"jameszero" \
"A lightweight and portable command-line YAML, JSON, INI and XML processor."
}
register_yq_status() {
write_opkg_status \
"yq_s" \
"$yq_current_version" \
""
}
add_mihomo_config() {
if [ -f $install_dir/mihomo ]; then
if [ -f "$mihomo_conf_dir/config.yaml" ]; then
return 0
elif [ ! -d $mihomo_conf_dir ]; then
mkdir $mihomo_conf_dir
fi
cat << EOF > "$mihomo_conf_dir/config.yaml"
tproxy-port: 1181
redir-port: 1182
# Руководство по конфигурации Mihomo - https://wiki.metacubex.one/ru/config/
EOF
echo
echo " Добавлен шаблон конфигурационного файла Mihomo:"
echo -e " ${yellow}config.yaml${reset}"
sleep 2
fi
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/01_register_xray.sh
================================================
# Регистрация xray
register_xray_control() {
write_opkg_control \
"xray_s" \
"$xray_current_version" \
"libc, libssp, librt, libpthread, ca-bundle" \
"XTLS Team" \
"xray_s" \
"Skrill / jameszero" \
"A unified platform for anti-censorship."
}
register_xray_list() {
cd "$register_dir/" || exit
touch xray_s.list
# Генерация списка файлов
find /opt/etc/xray/dat -maxdepth 1 -name "*.dat" -type f | while read -r file; do
echo "$file" >> xray_s.list
done
find /opt/etc/xray/configs -maxdepth 1 -name "*.json" -type f | while read -r file; do
echo "$file" >> xray_s.list
done
find /opt/var/log/xray -maxdepth 1 -name "*.log" -type f | while read -r file; do
echo "$file" >> xray_s.list
done
# Добавление дополнительных путей
echo "/opt/var/log/xray" >> xray_s.list
echo "/opt/etc/xray/configs" >> xray_s.list
echo "/opt/etc/xray/dat" >> xray_s.list
echo "/opt/etc/xray" >> xray_s.list
echo "/opt/sbin/xray" >> xray_s.list
}
register_xray_status() {
write_opkg_status \
"xray_s" \
"$xray_current_version" \
"libc, libssp, librt, libpthread, ca-bundle"
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/02_register_xkeen.sh
================================================
# Регистрация XKeen
# Функция для создания файла xkeen.control
register_xkeen_control() {
write_opkg_control \
"xkeen" \
"$xkeen_current_version" \
"jq, curl, coreutils-uname, coreutils-nohup, iptables, ipset" \
"Skrill" \
"xkeen" \
"Skrill / jameszero" \
"The platform that makes Xray work."
}
register_xkeen_list() {
cd "$register_dir/" || exit
# Создание файла xkeen.list
touch xkeen.list
# Генерация списка файлов и директорий
find "$xkeen_dir" -mindepth 1 | while read -r entry; do
echo "$entry" >> xkeen.list
done
# Добавление дополнительных путей
echo "$install_dir/xkeen" >> xkeen.list
echo "$xkeen_dir" >> xkeen.list
echo "$initd_file" >> xkeen.list
echo "$log_dir/xkeen-detached.log" >> xkeen.list
}
register_xkeen_status() {
write_opkg_status \
"xkeen" \
"$xkeen_current_version" \
"jq, curl, coreutils-uname, coreutils-nohup, iptables, ipset"
}
fixed_register_packages() {
awk 'BEGIN {RS=""; ORS="\n\n"} {gsub(/\n\n+/,"\n\n")}1' "$status_file" > tmp_status_file && mv tmp_status_file "$status_file"
}
register_xkeen_initd() {
old_initd_file="${initd_dir}/S24xray"
pre_initd_file="${initd_dir}/S99xkeen"
old_start_file="${initd_dir}/S99xkeenstart"
script_file="${xinstall_dir}/07_install_register/04_register_init.sh"
current_datetime=$(date "+%Y-%m-%d_%H-%M-%S")
variables_to_extract="name_client name_policy table_id table_mark custom_mark dscp_exclude dscp_proxy ipv4_proxy ipv4_exclude ipv6_proxy ipv6_exclude proxy_dns proxy_router start_attempts check_fd arm64_fd other_fd delay_fd ipv6_support extended_msg backup aghfix"
source_main_backup=""
source_start_backup=""
if [ -f "$initd_file" ]; then
source_main_backup="${backups_dir}/${current_datetime}_$(basename "$initd_file")"
mv "$initd_file" "$source_main_backup"
elif [ -f "$pre_initd_file" ]; then
source_main_backup="${backups_dir}/${current_datetime}_$(basename "$pre_initd_file")"
mv "$pre_initd_file" "$source_main_backup"
elif [ -f "$old_initd_file" ] || [ -f "$old_start_file" ]; then
if [ -f "$old_initd_file" ]; then
source_main_backup="${backups_dir}/${current_datetime}_$(basename "$old_initd_file")"
mv "$old_initd_file" "$source_main_backup"
fi
if [ -f "$old_start_file" ]; then
source_start_backup="${backups_dir}/${current_datetime}_$(basename "$old_start_file")"
mv "$old_start_file" "$source_start_backup"
fi
fi
cp "$script_file" "$initd_file" || exit 1
if [ -n "$source_main_backup" ] || [ -n "$source_start_backup" ]; then
autostart_val=""
start_delay_val=""
if [ -n "$source_start_backup" ] && [ -f "$source_start_backup" ]; then
autostart_val=$(grep '^autostart=' "$source_start_backup" | head -n 1 | cut -d'=' -f2)
start_delay_val=$(grep '^start_delay=' "$source_start_backup" | head -n 1 | cut -d'=' -f2)
fi
if [ -n "$source_main_backup" ] && [ -f "$source_main_backup" ]; then
[ -z "$autostart_val" ] && autostart_val=$(grep '^start_auto=' "$source_main_backup" | head -n 1 | cut -d'=' -f2)
[ -z "$start_delay_val" ] && start_delay_val=$(grep '^start_delay=' "$source_main_backup" | head -n 1 | cut -d'=' -f2)
fi
if [ -n "$autostart_val" ]; then
sed -i "s|^start_auto=.*|start_auto=$autostart_val|" "$initd_file"
fi
if [ -n "$start_delay_val" ]; then
sed -i "s|^start_delay=.*|start_delay=$start_delay_val|" "$initd_file"
fi
if [ -n "$source_main_backup" ] && [ -f "$source_main_backup" ]; then
for var in $variables_to_extract; do
value=$(grep -m1 "^${var}=" "$source_main_backup") || continue
escaped_value=$(printf '%s\n' "$value" | sed 's:[/]:\\&:g')
position=$(grep -n "^${var}=" "$initd_file" | head -n 1 | cut -d: -f1)
[ -n "$position" ] && sed -i "${position}s#.*#${escaped_value}#" "$initd_file"
done
fi
fi
chmod +x "$initd_file"
if choice_backup_xkeen; then
rm -f "$source_main_backup" "$source_start_backup"
fi
# Пропущенный $ ломал очистку легаси S99xkeenstart при апгрейде с 1.x
rm -f "$old_initd_file" "$old_start_file" "$pre_initd_file"
}
# Миграция скрипта
register_xray_initd() {
register_xkeen_initd
}
register_autostart() {
:
}
# Создание конфигурации XKeen
create_xkeen_cfg() {
mkdir -p "$xkeen_cfg" || { echo "Ошибка: Не удалось создать директорию $xkeen_cfg"; exit 1; }
if [ -f "/opt/etc/xkeen_exclude.lst" ] && [ ! -f "$file_ip_exclude" ]; then
mv "/opt/etc/xkeen_exclude.lst" "$file_ip_exclude"
elif [ ! -f "$file_ip_exclude" ]; then
cat << EOF > "$file_ip_exclude"
#192.168.0.0/16
#2001:db8::/32
# Добавьте необходимые IP и подсети без комментария # для исключения их из проксирования
EOF
fi
if [ ! -f "$file_port_exclude" ]; then
cat << EOF > "$file_port_exclude"
#
# Одновременно использовать порты проксирования и исключать порты нельзя
# Приоритет у портов проксирования
EOF
fi
if [ ! -f "$file_port_proxying" ]; then
cat << EOF > "$file_port_proxying"
#80
#443
#596:599
# (Раскомментируйте/добавьте по образцу) единичные порты и диапазоны для проскирования
EOF
fi
if [ ! -f "$xkeen_config" ]; then
cat << EOF > "$xkeen_config"
{
}
EOF
fi
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/03_register_cron.sh
================================================
# Функция для регистрации инициализационного скрипта cron
register_cron_initd() {
# Проверка наличия пакета cron
opkg list-installed 2>/dev/null | grep -q "^cron " && return
# Определение переменных
s05crond_filename="${current_datetime}_S05crond"
required_script_version="0.6"
# Получение текущей версии скрипта
if [ -e "${initd_cron}" ]; then
script_version=$(grep 'version=' "${initd_cron}" | grep -o '[0-9.]\+')
fi
# Содержимое скрипта
script_content='#!/bin/sh
# Информация о службе: Запуск / Остановка Cron
# version="0.6"
green="\\033[32m"
red="\\033[31m"
yellow="\\033[33m"
reset="\\033[0m"
cron_initd="/opt/sbin/crond"
# Функция для проверки статуса cron
cron_status() {
if pidof crond > /dev/null; then
return 0 # Процесс существует и работает
else
return 1 # Процесс не существует
fi
}
# Функция для запуска cron
start() {
if cron_status; then
printf " Cron ${yellow}уже запущен${reset}\\n"
else
$cron_initd -L /dev/null
printf " Cron ${green}запущен${reset}\\n"
fi
}
# Функция для остановки cron
stop() {
if cron_status; then
killall crond
printf " Cron ${yellow}остановлен${reset}\\n"
else
printf " Cron ${red}не запущен${reset}\\n"
fi
}
# Функция для перезапуска cron
restart() {
stop > /dev/null 2>&1
sleep 1
start > /dev/null 2>&1
printf " Cron ${green}перезапущен${reset}\\n"
}
# Обработка аргументов командной строки
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
if cron_status; then
printf " Cron ${green}запущен${reset}\\n"
else
printf " Cron ${red}не запущен${reset}\\n"
fi
;;
*)
printf " Команды: ${green}start${reset} | ${red}stop${reset} | ${yellow}restart${reset} | status\\n"
;;
esac
exit 0'
# Создание или замена файла, если версия скрипта не соответствует требуемой версии
if [ "${script_version}" != "${required_script_version}" ]; then
echo -e "${script_content}" > "${initd_cron}"
chmod +x "${initd_cron}"
fi
}
# Обновление cron задач
update_cron_geofile_task() {
if [ -f "$cron_dir/$cron_file" ]; then
tmp_file="$cron_dir/${cron_file}.tmp"
cp "$cron_dir/$cron_file" "$tmp_file"
if [ -z "$choice_cancel_cron_select" ]; then
grep -v -e "ug" -e "ux" -e "uk" -e '^\s*$' "$tmp_file" > "$cron_dir/$cron_file"
else
grep -v -e "ugi" -e "ugs" -e "ux" -e "uk" -e '^\s*$' "$tmp_file" > "$cron_dir/$cron_file"
fi
fi
}
================================================
FILE: scripts/_xkeen/02_install/07_install_register/04_register_init.sh
================================================
#!/bin/sh
# Информация о службе: Запуск / Остановка XKeen
# Версия: 2.30
# Окружение
PATH="/opt/bin:/opt/sbin:/sbin:/bin:/usr/sbin:/usr/bin"
# Цвета
green="\033[92m"
red="\033[91m"
yellow="\033[93m"
light_blue="\033[96m"
reset="\033[0m"
# Имена
name_client="xray"
name_app="XKeen"
name_policy="xkeen"
name_profile="xkeen"
name_chain="xkeen"
name_ipset_deny_mac="xkeen_deny_mac"
# Директории
directory_os_modules="/lib/modules/$(uname -r)"
directory_user_modules="/opt/lib/modules"
directory_configs_app="/opt/etc/$name_client"
directory_xray_config="$directory_configs_app/configs"
directory_xray_asset="$directory_configs_app/dat"
directory_logs="/opt/var/log"
xkeen_cfg="/opt/etc/xkeen"
ipset_cfg="$xkeen_cfg/ipset"
install_dir="/opt/sbin"
# Файлы
file_netfilter_hook="/opt/etc/ndm/netfilter.d/proxy.sh"
file_schedule_hook="/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh"
log_access="$directory_logs/$name_client/access.log"
log_error="$directory_logs/$name_client/error.log"
mihomo_config="$directory_configs_app/config.yaml"
file_port_proxying="$xkeen_cfg/port_proxying.lst"
file_port_exclude="$xkeen_cfg/port_exclude.lst"
file_ip_exclude="$xkeen_cfg/ip_exclude.lst"
xkeen_config="$xkeen_cfg/xkeen.json"
file_pid_fd="/var/run/xkeen_fd.pid"
ru_exclude_ipv4="$ipset_cfg/ru_exclude_ipv4.lst"
ru_exclude_ipv6="$ipset_cfg/ru_exclude_ipv6.lst"
# URL
url_server="localhost:79"
url_policy="rci/show/ip/policy"
url_keenetic_port="rci/ip/http"
url_redirect_port="rci/ip/static"
url_hotspot="rci/show/ip/hotspot"
# Настройки правил iptables
table_id="111"
table_mark="0x111"
table_redirect="nat"
table_tproxy="mangle"
comment_tag="xkeen_rule"
comment="-m comment --comment $comment_tag"
custom_mark=""
# DSCP-метки
dscp_exclude="62"
dscp_proxy="63"
ipv4_proxy="127.0.0.1"
ipv4_exclude="0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 255.255.255.255"
ipv6_proxy="::1"
ipv6_exclude="::/128 ::1/128 64:ff9b::/96 2001::/32 2002::/16 fd00::/8 ff00::/8 fe80::/10"
# Перехват DNS в прокси
proxy_dns="off"
# Проксирование трафика Entware
proxy_router="off"
# Настройки запуска
start_attempts=10
start_auto="on"
start_delay=20
# Контроль файловых дескрипторов
check_fd="off"
arm64_fd=40000
other_fd=10000
delay_fd=60
# Поддержка IPv6
ipv6_support="on"
## Расширенные сообщения запуска
extended_msg="off"
## Резервное копирование XKeen при обновлении
backup="on"
## Клиенты XKeen под своими IP в журнале AdGuard Home
aghfix="off"
# Функции журналирования
log_info_router() { logger -p notice -t "$name_app" "$1"; }
log_warning_router() { logger -p warning -t "$name_app" "$1"; }
log_error_router() { logger -p error -t "$name_app" "$1"; }
log_info_terminal() { echo -e "\n${green}Информация${reset}: $1" >&2; }
log_warning_terminal() { echo -e "\n${yellow}Предупреждение${reset}: $1" >&2; }
log_error_terminal() { echo -e "\n${red}Ошибка${reset}: $1" >&2; exit 1; }
print_policy_info() {
found="$1"
has_custom="$2"
ignored_custom="$3"
ignore_line=""
if [ "$ignored_custom" = "yes" ]; then
ignore_line="
Пользовательские политики из '${yellow}xkeen.json${reset}' будут проигнорированы"
fi
if [ "$extended_msg" != "on" ]; then
if [ "$found" = "no" ]; then
log_info_terminal "
Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line}
Прокси будет запущен для всего устройства
"
fi
return
fi
if [ "$found" = "yes" ]; then
if [ "$has_custom" = "yes" ]; then
custom_names=$(echo "$user_policies" | cut -d'|' -f1 | tr '\n' ',' | sed 's/,$//; s/,/, /g')
policies="${name_policy}, ${custom_names}"
detail_list=""
if [ -n "$port_donor" ]; then
detail_list=" - ${yellow}$name_policy${reset} на портах ${green}${port_donor}${reset}"
elif [ -n "$port_exclude" ]; then
detail_list=" - ${yellow}$name_policy${reset} на всех портах кроме ${green}${port_exclude}${reset}"
else
detail_list=" - ${yellow}$name_policy${reset} на всех портах"
fi
custom_details=$(echo "$user_policies" | while IFS='|' read -r p_name p_mark p_mode p_ports; do
if [ "$p_mode" = "include" ]; then
echo " - ${yellow}$p_name${reset} на портах ${green}${p_ports}${reset}"
elif [ "$p_mode" = "exclude" ]; then
echo " - ${yellow}$p_name${reset} на всех портах кроме ${green}${p_ports}${reset}"
else
echo " - ${yellow}$p_name${reset} на всех портах"
fi
done)
log_info_terminal "
Найдены политики '${yellow}${policies}${reset}'
Прокси будет запущен для клиентов политик:
${detail_list}
${custom_details}
"
else
if [ -z "$port_donor" ] && [ -z "$port_exclude" ]; then
log_info_terminal "
Найдена политика '${yellow}$name_policy${reset}'
Не определены целевые порты для XKeen
Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}' на всех портах
"
elif [ -n "$port_donor" ]; then
log_info_terminal "
Найдена политика '${yellow}$name_policy${reset}'
Определены целевые порты для XKeen
Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}'
на портах ${green}${port_donor}${reset}
"
else
log_info_terminal "
Найдена политика '${yellow}$name_policy${reset}'
Определены порты исключения для XKeen
Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}'
на всех портах кроме ${green}${port_exclude}${reset}
"
fi
fi
else
if [ -n "$port_donor" ]; then
log_info_terminal "
Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line}
Определены целевые порты для XKeen
Прокси будет запущен для всех клиентов
на портах ${green}${port_donor}${reset}
"
elif [ -n "$port_exclude" ]; then
log_info_terminal "
Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line}
Определены порты исключения для XKeen
Прокси будет запущен для всех клиентов
на всех портах кроме ${green}${port_exclude}${reset}
"
else
log_info_terminal "
Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line}
Не определены целевые порты для XKeen
Прокси будет запущен для всех клиентов на всех портах
"
fi
fi
}
utils="jq curl grep awk sed ipset"
[ "$name_client" = "mihomo" ] && utils="$utils yq"
for cmd in $utils; do
command -v "$cmd" >/dev/null 2>&1 || log_error_terminal "Не найдена необходимая утилита: ${yellow}$cmd${reset}"
done
log_clean() { [ "$name_client" = "xray" ] && : > "$log_access" && : > "$log_error"; }
api_cache_init() {
api_policy_json=$(curl -kfsS "${url_server}/${url_policy}" 2>/dev/null)
api_port_json=$(curl -kfsS "${url_server}/${url_keenetic_port}" 2>/dev/null)
api_static_json=$(curl -kfsS "${url_server}/${url_redirect_port}" 2>/dev/null)
}
refresh_port_cache() { api_port_json=$(curl -kfsS "${url_server}/${url_keenetic_port}" 2>/dev/null); }
json_get_ports() { [ -n "$api_port_json" ] && printf '%s' "$api_port_json" | jq -r '.port, (.ssl.port // empty)' 2>/dev/null; }
# Получение портов Keenetic
get_keenetic_port() {
ports=""
ports=$(json_get_ports)
case " $ports " in
*" 443 "*) return 1 ;;
esac
if [ -z "$ports" ]; then
ndmc -c 'ip http port 8080' >/dev/null 2>&1
ndmc -c 'ip http port 80' >/dev/null 2>&1
ndmc -c 'system configuration save' >/dev/null 2>&1
sleep 2
refresh_port_cache
ports=$(json_get_ports)
fi
[ -n "$ports" ] || return 1
echo "$ports"
return 0
}
wait_for_webui() {
max_wait=10
i=0
while [ "$i" -lt "$max_wait" ]; do
pidof nginx >/dev/null 2>&1 && return 0
sleep 1
i=$((i + 1))
done
return 1
}
apply_ipv6_state() {
ipv6_disabled=
ipv6_disabled=$(sysctl -n net.ipv6.conf.default.disable_ipv6 2>/dev/null || echo "0")
[ "$ipv6_disabled" -eq 1 ] && return 0
[ "$ipv6_support" != "off" ] && return 0
ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" || return 0
wait_for_webui || { log_error_router "Веб-интерфейс недоступен"; return 1; }
sleep 5
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1
for dir in /proc/sys/net/ipv6/conf/*; do
[ -d "$dir" ] || continue
iface="${dir##*/}"
case "$iface" in
all|ezcfg0|t2s*)
continue
;;
*)
[ -f "$dir/disable_ipv6" ] && echo "1" > "$dir/disable_ipv6" 2>/dev/null
;;
esac
done
sleep 2
if [ "$(sysctl -n net.ipv6.conf.default.disable_ipv6 2>/dev/null)" -eq 1 ]; then
log_info_router "Отключение IPv6 выполнено"
return 0
fi
}
get_ipver_support() {
ip4_supported=$(ip -4 addr show 2>/dev/null | grep -q "inet " && echo true || echo false)
ip6_supported=$(ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && echo true || echo false)
iptables_supported=$([ "$ip4_supported" = "true" ] && command -v iptables >/dev/null 2>&1 && echo true || echo false)
ip6tables_supported=$([ "$ip6_supported" = "true" ] && command -v ip6tables >/dev/null 2>&1 && echo true || echo false)
}
strip_json_comments() {
sed -e ':a; s:/\*[^*]*\*[^/]*\*/::g; ta' \
-e 's/^[[:space:]]*\/\/.*$//' \
-e 's/[[:space:]]\{1,\}\/\/.*$//' "$@"
}
# Функция валидации xkeen.json
validate_xkeen_json() {
[ ! -f "$xkeen_config" ] && return 0
if ! jq -e . "$xkeen_config" >/dev/null 2>&1; then
log_error_terminal "
Валидация JSON: файл '${yellow}xkeen.json${reset}' содержит синтаксические ошибки
Запуск прокси невозможен
"
fi
if ! jq -e '.xkeen.policy[]? | .name' "$xkeen_config" >/dev/null 2>&1; then
if jq -e '.xkeen' "$xkeen_config" >/dev/null 2>&1; then
log_error_terminal "
Файл '${yellow}xkeen.json${reset}' имеет неверную структуру
Запуск прокси невозможен
"
fi
fi
return 0
}
# Функция поиска резервных копий конфигурационных файлов Xray
check_xray_backups() {
[ "$name_client" != "xray" ] && return 0
# Ищем json-файлы с типичными признаками копий
bad_files=$(find "$directory_xray_config" -maxdepth 1 -type f \( -iname "*bak*.json" -o -iname "*old*.json" -o -iname "*copy*.json" -o -iname "*копия*.json" -o -iname "*orig*.json" -o -iname "*save*.json" -o -iname "*temp*.json" -o -iname "*tmp*.json" -o -name "*(*).json" \))
if [ -n "$bad_files" ]; then
bad_list=$(printf '%s\n' "$bad_files" | awk -F/ '{print " - " $NF}')
log_error_terminal "
В директории конфигурации Xray найдены резервные копии:
${light_blue}${bad_list}${reset}
Измените расширение резервных копий, например, на ${yellow}.bak${reset}
Либо переместите их в поддиректорию
Запуск ${yellow}$name_client${reset} ${red}отменен${reset}
"
fi
return 0
}
# Функция проверки наличия метки 255
validate_routing_mark() {
[ "$proxy_router" != "on" ] && return 0
mark_valid="false"
mark_msg=""
bad_items=""
has_items="false"
all_marks_ok="true"
if [ "$name_client" = "xray" ]; then
mark_msg="mark"
for file in "$directory_xray_config"/*.json; do
[ -f "$file" ] || continue
if strip_json_comments "$file" | jq -e '.outbounds != null' >/dev/null 2>&1; then
has_items="true"
current_bad=$(strip_json_comments "$file" | jq -r '
.outbounds[]? |
select(.protocol != "blackhole" and .protocol != "dns") |
select(.streamSettings.sockopt.mark != 255) |
(.tag // .protocol)
')
if [ -n "$current_bad" ]; then
bad_items="${bad_items}${bad_items:+\n}$current_bad"
all_marks_ok="false"
fi
fi
done
elif [ "$name_client" = "mihomo" ]; then
mark_msg="routing-mark"
if [ -f "$mihomo_config" ]; then
if yq -e '.["routing-mark"] == 255' "$mihomo_config" >/dev/null 2>&1; then
mark_valid="true"
elif yq -e '
.proxy-providers[]? |
select(.override."routing-mark" == 255)
' "$mihomo_config" >/dev/null 2>&1; then
mark_valid="true"
else
if yq -e '.proxies != null' "$mihomo_config" >/dev/null 2>&1; then
has_items="true"
current_bad=$(yq -r '
.proxies[]? |
select(."routing-mark" != 255) |
.name
' "$mihomo_config")
if [ -n "$current_bad" ]; then
bad_items="${bad_items}${bad_items:+\n}$current_bad"
all_marks_ok="false"
fi
fi
fi
fi
fi
if [ "$mark_valid" != "true" ]; then
if [ "$has_items" = "true" ] && [ "$all_marks_ok" = "true" ]; then
mark_valid="true"
fi
fi
if [ "$mark_valid" != "true" ]; then
error_details=""
if [ -n "$bad_items" ]; then
bad_list=$(printf "%b\n" "$bad_items" | awk '!seen[$0]++ {print " - " $0}')
if [ "$name_client" = "xray" ]; then
error_details="
Подключения без метки:
${light_blue}${bad_list}${reset}"
proxy_hint=" Добавьте маркировку во ВСЕ исходящие подключения (кроме blackhole и dns)"
else
error_details="
Прокси без метки:
${light_blue}${bad_list}${reset}"
proxy_hint=" Добавьте в config.yaml маркировку трафика глобально либо в каждое исходящее подключение"
fi
fi
log_warning_terminal "
Для проксирования трафика Entware требуется его маркировка
В конфигурации ${yellow}$name_client${reset} параметр ${green}$mark_msg: 255${reset} прописан не везде$error_details
$proxy_hint
Проксирование трафика Entware ${red}отключено${reset}
"
proxy_router="off"
fi
return 0
}
load_user_ipset_family() {
set_name="$1"
family="$2"
addr_regex="$3"
tmp="${set_name}_tmp"
# Заполняем tmp; основной набор подменяется только после успешного pipeline
ipset create "$set_name" hash:net family "$family" -exist
ipset create "$tmp" hash:net family "$family" -exist
ipset flush "$tmp"
if sed -e 's/\r$//' -e 's/#.*//' -e '/^[[:space:]]*$/d' "$file_ip_exclude" |
grep -Eo "$addr_regex" |
awk -v s="$tmp" '{print "add "s" "$1}' | ipset restore -exist; then
ipset swap "$set_name" "$tmp"
fi
ipset destroy "$tmp"
}
# Функция загрузки пользовательских исключений в ipset
load_user_ipset() {
[ ! -f "$file_ip_exclude" ] && return
[ "$iptables_supported" = "true" ] && load_user_ipset_family user_exclude inet '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?'
[ "$ip6tables_supported" = "true" ] && load_user_ipset_family user_exclude6 inet6 '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/[0-9]{1,3})?'
}
# Функция чтения пользовательских портов из файлов
read_ports_from_file() {
file_ports="$1"
[ -f "$file_ports" ] || return
sed -e 's/\r$//' -e 's/#.*//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' "$file_ports"
}
# Функция обработки, валидации и нормализации списка портов
validate_and_clean_ports() {
input_ports="$1"
mandatory_ports="$2"
[ -z "$input_ports" ] && [ -z "$mandatory_ports" ] && return 1
echo "${mandatory_ports}${mandatory_ports:+,}${input_ports}" | tr ',' '\n' | awk '
function is_valid(p) {
return p ~ /^[0-9]+$/ && p > 0 && p <= 65535
}
{
gsub(/[[:space:]]/, "", $0)
gsub(/-/, ":", $0)
if ($0 == "") next
n = split($0, a, ":")
if (n == 1) {
if (is_valid(a[1])) {
print a[1]
}
}
else if (n == 2) {
if (is_valid(a[1]) && is_valid(a[2])) {
start = a[1]
end = a[2]
if (start > end) {
tmp = start
start = end
end = tmp
}
if (start <= end) {
print start ":" end
}
}
}
}
' | sort -n -u | tr '\n' ',' | sed 's/,$//'
}
# Функция обработки пользовательских портов
process_user_ports() {
raw_donor=$(read_ports_from_file "$file_port_proxying")
[ -n "$raw_donor" ] && port_donor=$(validate_and_clean_ports "$raw_donor" "80,443") || port_donor=""
port_exclude=$(validate_and_clean_ports "$(read_ports_from_file "$file_port_exclude")")
if [ -n "$port_donor" ] && [ -n "$port_exclude" ]; then
log_warning_terminal "
Заданы и порты проксирования, и порты исключения
Прокси будет запущен на портах проксирования, порты исключения игнорируются
"
port_exclude=""
fi
}
# Функция нормализации сторонних политик
process_custom_mark() {
[ -z "$custom_mark" ] && return
clean_mark=""
for mark in $(echo "$custom_mark" | tr ',' ' '); do
val="${mark#0x}"
echo "$val" | grep -Eq '^[0-9a-fA-F]+$' && clean_mark="$clean_mark 0x$val"
done
custom_mark="${clean_mark# }"
}
# Проверка статуса прокси-клиента
proxy_status() { pidof "$name_client" >/dev/null; }
# Поиск конфигураций DNS
check_dns_config() {
[ "$proxy_dns" != "on" ] && echo "false" && return
if [ "$name_client" = "xray" ]; then
for file in "$directory_xray_config"/*.json; do
[ -f "$file" ] || continue
strip_json_comments "$file" | jq -e '.dns.servers? != null' >/dev/null 2>&1 && { echo "true"; return; }
done
elif [ "$name_client" = "mihomo" ]; then
[ -f "$mihomo_config" ] && yq -e '.dns.enable == true' "$mihomo_config" >/dev/null 2>&1 && { echo "true"; return; }
fi
echo "false"
}
file_dns=$(check_dns_config)
# Кэш списка загруженных модулей; is_module_loaded читает его без форков
_loaded_modules=""
_refresh_modules_cache() { _loaded_modules=" $(lsmod 2>/dev/null | awk '{print $1}' | tr '\n' ' ') "; }
is_module_loaded() {
case "$_loaded_modules" in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
# Загрузка модулей
load_modules() {
name="${1%.ko}"
if ! is_module_loaded "$name"; then
for dir in "$directory_os_modules" "$directory_user_modules"; do
[ -f "$dir/$1" ] && insmod "$dir/$1" >/dev/null 2>&1 && return
done
fi
}
# Обработка модулей и портов
get_modules() {
_refresh_modules_cache
load_modules xt_comment.ko
load_modules xt_TPROXY.ko
load_modules xt_socket.ko
load_modules xt_multiport.ko
load_modules xt_dscp.ko
_refresh_modules_cache # подхватить только что insmod-нутые модули
if ! is_module_loaded xt_comment; then
log_error_router "Модуль xt_comment не загружен"
log_error_terminal "
Модуль '${light_blue}xt_comment${reset}' не загружен
Невозможно запустить XKeen без него
Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}'
"
fi
if [ "$mode_proxy" = "TProxy" ] || [ "$mode_proxy" = "Hybrid" ]; then
for module in xt_TPROXY.ko xt_socket.ko; do
if ! is_module_loaded "${module%.ko}"; then
proxy_stop
log_error_router "Модуль ${module} не загружен"
log_error_terminal "
Модуль '${light_blue}${module}${reset}' не загружен
Невозможно запустить XKeen в режиме ${mode_proxy} без него
Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}'
"
fi
done
fi
if [ -n "$port_donor" ] || [ -n "$port_exclude" ]; then
if ! is_module_loaded xt_multiport; then
log_warning_router "Модуль xt_multiport не загружен"
log_warning_terminal "
Модуль '${light_blue}xt_multiport${reset}' не загружен
Невозможно использовать выбранные порты без него
Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}'
Прокси будет запущен на всех портах
"
port_donor=""
port_exclude=""
fi
fi
if [ -n "$dscp_exclude" ] || [ -n "$dscp_proxy" ]; then
if ! is_module_loaded xt_dscp; then
log_warning_router "Модуль xt_dscp не загружен"
log_warning_terminal "
Модуль '${light_blue}xt_dscp${reset}' не загружен
Работа с DSCP-метками невозможна
Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}'
"
dscp_exclude=""
dscp_proxy=""
fi
fi
}
# Получение transparent inbound'ов Xray
_invalidate_inbounds_cache() { rm -f /tmp/xkeen-inbounds-cache; }
get_xray_transparent_inbounds() {
cache_file="/tmp/xkeen-inbounds-cache"
cache_valid=0
if [ -f "$cache_file" ]; then
newer=$(find "$directory_xray_config" -maxdepth 1 -name '*.json' -newer "$cache_file" 2>/dev/null | head -n 1)
[ -z "$newer" ] && cache_valid=1
fi
if [ "$cache_valid" = "1" ]; then
cat "$cache_file"
return 0
fi
cache_tmp="${cache_file}.tmp.$$"
{
for file in "$directory_xray_config"/*.json; do
[ -f "$file" ] || continue
strip_json_comments "$file" |
jq -r --arg file "$file" '
.inbounds[]? |
select(
(.protocol == "dokodemo-door" or .protocol == "tunnel") and
((.settings.followRedirect? // false) == true)
) |
(.streamSettings.sockopt.tproxy? // "") as $tproxy |
select($tproxy == "" or $tproxy == "redirect" or $tproxy == "tproxy") |
[
(if $tproxy == "tproxy" then "tproxy" else "redirect" end),
(.port // ""),
(.settings.network // ""),
(.tag // ""),
$file
] | @tsv
' 2>/dev/null
done
} > "$cache_tmp"
mv "$cache_tmp" "$cache_file"
cat "$cache_file"
}
get_xray_port_by_mode() {
mode="$1"
port=$(
get_xray_transparent_inbounds |
awk -F '\t' -v mode="$mode" '
$1 == mode && $2 != "" {
print $2
exit
}
'
)
echo "$port"
}
get_xray_network_by_mode() {
mode="$1"
network=$(
get_xray_transparent_inbounds |
awk -F '\t' -v mode="$mode" '
function add_networks(value, count, i, item) {
gsub(/,/, " ", value)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
if (value == "") {
return
}
count = split(value, items, /[[:space:]]+/)
for (i = 1; i <= count; i++) {
item = items[i]
if (item != "" && !seen[item]++) {
order[++order_count] = item
}
}
}
$1 == mode {
add_networks($3)
}
END {
for (i = 1; i <= order_count; i++) {
printf "%s%s", order[i], (i < order_count ? " " : "")
}
}
'
)
echo "$network"
}
# Получение порта для Redirect
get_port_redirect() {
if [ "$name_client" = "xray" ]; then
port=$(get_xray_port_by_mode "redirect")
[ -n "$port" ] && echo "$port" && return 0
elif [ "$name_client" = "mihomo" ]; then
port=$(yq eval '.redir-port // ""' "$mihomo_config" 2>/dev/null)
if [ -z "$port" ]; then
port=$(yq eval '.listeners[] | select(.type == "redir") | .port // ""' "$mihomo_config" 2>/dev/null)
fi
[ -n "$port" ] && echo "$port" && return 0
else
return 1
fi
}
# Получение порта для TProxy
get_port_tproxy() {
if [ "$name_client" = "xray" ]; then
port=$(get_xray_port_by_mode "tproxy")
[ -n "$port" ] && echo "$port" && return 0
elif [ "$name_client" = "mihomo" ]; then
port=$(yq eval '.tproxy-port // ""' "$mihomo_config" 2>/dev/null)
if [ -z "$port" ]; then
port=$(yq eval '.listeners[] | select(.type == "tproxy") | .port // ""' "$mihomo_config" 2>/dev/null)
fi
[ -n "$port" ] && echo "$port" && return 0
else
return 1
fi
}
# Получение сети для Redirect
get_network_redirect() {
if [ "$name_client" = "xray" ]; then
network=$(get_xray_network_by_mode "redirect")
[ -n "$network" ] && echo "$network" && return 0
elif [ "$name_client" = "mihomo" ]; then
[ -n "$port_redirect" ] && echo "tcp" && return 0
echo "" && return 0
else
return 1
fi
}
# Получение сети для TProxy
get_network_tproxy() {
if [ "$name_client" = "xray" ]; then
network=$(get_xray_network_by_mode "tproxy")
[ -n "$network" ] && echo "$network" && return 0
elif [ "$name_client" = "mihomo" ]; then
if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then
echo "udp"
elif [ -z "$port_redirect" ] && [ -n "$port_tproxy" ]; then
echo "tcp udp"
else
echo ""
fi
return 0
else
return 1
fi
}
# Получение портов исключения из статических пробросов
get_api_exclude_ports() {
api_redir_result=""
if [ -n "$api_static_json" ]; then
api_redir_result=$(echo "$api_static_json" | jq -r '
[
.[] |
select(.disable != true) |
if has("end-port") then
"\(.port):\(.["end-port"])"
else
.port
end |
select(. != "80" and . != "443")
] |
sort |
join(",")')
fi
echo "$api_redir_result"
}
# Получение исключенных портов
get_port_exclude() {
port_exclude_redirect=""
port_exclude_result=""
port_exclude_redirect=$(get_api_exclude_ports)
if [ -n "$port_exclude" ]; then
if [ -n "$port_exclude_redirect" ]; then
port_exclude_result="$port_exclude,$port_exclude_redirect"
else
port_exclude_result="$port_exclude"
fi
else
port_exclude_result="$port_exclude_redirect"
fi
port_exclude_result=$(printf '%s\n' "$port_exclude_result" | tr -dc '0-9,:' | tr -s ',' | sed 's/^,//; s/,$//')
echo "$port_exclude_result"
}
# Получение исключений IPv4
get_exclude_ip4() {
[ "$iptables_supported" != "true" ] && return
# Получаем провайдерский IPv4
ipv4_eth=$(ip -o route get 195.208.4.1 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p' || \
ip -o route get 77.88.8.8 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p')
[ -n "$ipv4_eth" ] && ipv4_eth="${ipv4_eth}/32"
echo "${ipv4_eth} ${ipv4_exclude}" | tr ' ' '\n' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/^ //; s/ $//'
}
# Получение исключений IPv6
get_exclude_ip6() {
[ "$ip6tables_supported" != "true" ] && return
# Получаем провайдерский IPv6
ipv6_eth=$(ip -o -6 route get 2a0c:a9c7:8::1 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p' || \
ip -o -6 route get 2a02:6b8::feed:0ff 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p')
[ -n "$ipv6_eth" ] && ipv6_eth="${ipv6_eth}/128"
echo "${ipv6_eth} ${ipv6_exclude}" | tr ' ' '\n' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/^ //; s/ $//'
}
# Получение метки политики
get_policy_mark() {
if [ -n "$api_policy_json" ]; then
policy_mark=$(echo "$api_policy_json" | jq -r --arg pname "$name_policy" '.[] | select(.description | ascii_downcase == ($pname | ascii_downcase)) | .mark' 2>/dev/null)
fi
if [ -n "$policy_mark" ]; then
echo "0x${policy_mark}"
else
echo ""
fi
}
# Атомарная синхронизация ipset xkeen_deny_mac с текущим состоянием hotspot API.
# Идемпотентна: создаёт основной набор при первом вызове, в дальнейшем
# наполняет tmp-набор и делает ipset swap. Вызывается на старте XKeen и
# на каждой netfilter.d/schedule.d-инвокации — это даёт динамику без
# `xkeen -restart` при работе Keenetic-расписаний (родительский контроль).
sync_deny_mac_ipset() {
command -v ipset >/dev/null 2>&1 || return 0
ipset create "$name_ipset_deny_mac" hash:mac -exist 2>/dev/null || return 0
_xkeen_deny_tmp="${name_ipset_deny_mac}_tmp"
ipset create "$_xkeen_deny_tmp" hash:mac -exist 2>/dev/null
ipset flush "$_xkeen_deny_tmp" >/dev/null 2>&1
_xkeen_hotspot_json=$(curl -kfsS "${url_server}/${url_hotspot}" 2>/dev/null)
if [ -n "$_xkeen_hotspot_json" ]; then
printf '%s' "$_xkeen_hotspot_json" | jq -r '
((.host // . // []) |
(if type == "array" then .[] else . end)) |
select((.access // "") == "deny" and (.mac // "") != "") |
.mac
' 2>/dev/null | tr '[:lower:]' '[:upper:]' | while IFS= read -r _xkeen_mac; do
[ -n "$_xkeen_mac" ] && ipset add "$_xkeen_deny_tmp" "$_xkeen_mac" -exist 2>/dev/null
done
fi
ipset swap "$_xkeen_deny_tmp" "$name_ipset_deny_mac" 2>/dev/null
ipset destroy "$_xkeen_deny_tmp" 2>/dev/null
unset _xkeen_deny_tmp _xkeen_hotspot_json _xkeen_mac
}
# Получаем пользовательские политики
get_user_policies() {
[ ! -f "$xkeen_config" ] && return
jq -r '.xkeen.policy[]? | "\(.name)|\(.port // "")" ' "$xkeen_config" 2>/dev/null
}
# Проверка на конфликт имен политик
check_policy_name_conflict() {
if [ -f "$xkeen_config" ]; then
conflict=$(jq -r --arg main "$name_policy" '.xkeen.policy[] | select((.name | ascii_downcase) == ($main | ascii_downcase)) | .name' "$xkeen_config" 2>/dev/null | head -n 1)
if [ -n "$conflict" ]; then
log_error_router "Ошибка конфигурации: Имя политики в xkeen.json совпадает с зарезервированным"
log_error_terminal "
В файле '${yellow}xkeen.json${reset}' найдена политика с именем '${red}${conflict}${reset}'
Это имя зарезервировано основной службой XKeen
Переименуйте пользовательскую политику в json-файле
Запуск ${yellow}$name_client${reset} ${red}отменен${reset}
"
fi
fi
}
# Получаем порты пользовательских политик
resolve_user_policies() {
[ -f "$xkeen_config" ] && [ -n "$api_policy_json" ] || return
api_exclude_ports=$(get_api_exclude_ports)
# Получаем сопоставленные политики одним вызовом jq
matched_policies=$(printf '%s' "$api_policy_json" | jq -r --argjson user_cfg "$(cat "$xkeen_config")" '
($user_cfg.xkeen.policy // []) as $up |
.[] as $api |
$up[] |
select(
(.name | ascii_downcase) ==
($api.description | ascii_downcase)
) |
"\(.name)|\($api.mark)|\(.port // "")"
' 2>/dev/null)
[ -z "$matched_policies" ] && return
# Обрабатываем каждую политику в одном цикле
echo "$matched_policies" | while IFS='|' read -r pname mark pports; do
if [ -z "$pports" ]; then
# Порты не указаны -> режим "all" (все порты)
if [ -n "$api_exclude_ports" ]; then
echo "${pname}|${mark}|exclude|${api_exclude_ports}"
else
echo "${pname}|${mark}|all|"
fi
else
case "$pports" in
!*) mode="exclude"; ports="${pports#!}"
[ -n "$api_exclude_ports" ] && ports="${ports:+$ports,}$api_exclude_ports" ;;
*) mode="include"; ports="$pports"
if [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ]; then
case ",$ports," in
*,53,*) ;;
*) ports="53,$ports" ;;
esac
fi
;;
esac
clean_ports=$(validate_and_clean_ports "$ports")
[ -n "$clean_ports" ] && echo "${pname}|${mark}|${mode}|${clean_ports}"
fi
done
}
# Получение режима прокси-клиента
get_mode_proxy() {
if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then
mode_proxy="Hybrid"
elif [ -n "$port_tproxy" ]; then
mode_proxy="TProxy"
elif [ -n "$port_redirect" ]; then
mode_proxy="Redirect"
else
mode_proxy="Other"
fi
echo "$mode_proxy"
}
# Настройка брандмауэра
configure_firewall() {
: > "$file_netfilter_hook"
# Pre-evaluate dynamic variables
val_exclude_ip6="$(get_exclude_ip6)"
val_exclude_ip4="$(get_exclude_ip4)"
cat > "$file_netfilter_hook" <<'EOL'
#!/bin/sh
# XKeen: Auto-generated file. DO NOT EDIT!
[ -f /tmp/xkeen_ready ] || exit 0
EOL
# Securely inject variables into the script
inject_var() {
local name="$1"
local val="$2"
local safe_val
safe_val="${val//\'/\'\\\'\'}"
printf "%s='%s'\n" "$name" "$safe_val" >> "$file_netfilter_hook"
}
inject_var name_client "$name_client"
inject_var name_profile "$name_profile"
inject_var mode_proxy "$mode_proxy"
inject_var network_redirect "$network_redirect"
inject_var network_tproxy "$network_tproxy"
inject_var networks "$networks"
inject_var name_chain "$name_chain"
inject_var port_redirect "$port_redirect"
inject_var port_tproxy "$port_tproxy"
inject_var port_donor "$port_donor"
inject_var port_exclude "$port_exclude"
inject_var policy_mark "$policy_mark"
inject_var comment_tag "$comment_tag"
inject_var comment "$comment"
inject_var custom_mark "$custom_mark"
inject_var dscp_exclude "$dscp_exclude"
inject_var dscp_proxy "$dscp_proxy"
inject_var user_policies "$user_policies"
inject_var table_redirect "$table_redirect"
inject_var table_tproxy "$table_tproxy"
inject_var table_mark "$table_mark"
inject_var table_id "$table_id"
inject_var file_dns "$file_dns"
inject_var proxy_dns "$proxy_dns"
inject_var proxy_router "$proxy_router"
inject_var directory_os_modules "$directory_os_modules"
inject_var directory_user_modules "$directory_user_modules"
inject_var directory_configs_app "$directory_configs_app"
inject_var directory_xray_config "$directory_xray_config"
inject_var directory_xray_asset "$directory_xray_asset"
inject_var iptables_supported "$iptables_supported"
inject_var ip6tables_supported "$ip6tables_supported"
inject_var arm64_fd "$arm64_fd"
inject_var other_fd "$other_fd"
inject_var aghfix "$aghfix"
inject_var ipv6_proxy "$ipv6_proxy"
inject_var ipv4_proxy "$ipv4_proxy"
inject_var val_exclude_ip6 "$val_exclude_ip6"
inject_var val_exclude_ip4 "$val_exclude_ip4"
inject_var name_ipset_deny_mac "$name_ipset_deny_mac"
inject_var url_server "$url_server"
inject_var url_hotspot "$url_hotspot"
cat >> "$file_netfilter_hook" <<'EOL'
# Перезапуск скрипта
restart_script() {
exec /bin/sh "$0" "$@"
}
if pidof "$name_client" >/dev/null; then
# Динамическая синхронизация ipset с deny-MAC из hotspot API.
# Закрывает обход built-in политики «Без доступа в интернет» при включенном
# проксировании: PREROUTING на эти MAC делает RETURN до TPROXY, пакет идёт
# в FORWARD, где штатно дропается NDM-цепочкой _NDM_HOTSPOT_FWD.
# Хук перезапускается NDM при netfilter rewrite, schedule.d дёргает этот же
# скрипт на start/stop расписаний — список MAC всегда актуален.
_xkeen_sync_deny_mac_ipset() {
command -v ipset >/dev/null 2>&1 || return 0
ipset create "$name_ipset_deny_mac" hash:mac -exist 2>/dev/null || return 0
_tmp="${name_ipset_deny_mac}_tmp"
ipset create "$_tmp" hash:mac -exist 2>/dev/null
ipset flush "$_tmp" >/dev/null 2>&1
_hjson=$(curl -kfsS "${url_server}/${url_hotspot}" 2>/dev/null)
if [ -n "$_hjson" ]; then
printf '%s' "$_hjson" | jq -r '
((.host // . // []) |
(if type == "array" then .[] else . end)) |
select((.access // "") == "deny" and (.mac // "") != "") |
.mac
' 2>/dev/null | tr '[:lower:]' '[:upper:]' | while IFS= read -r _m; do
[ -n "$_m" ] && ipset add "$_tmp" "$_m" -exist 2>/dev/null
done
fi
ipset swap "$_tmp" "$name_ipset_deny_mac" 2>/dev/null
ipset destroy "$_tmp" 2>/dev/null
}
_xkeen_sync_deny_mac_ipset
# Аккумулируем правила в строки, применяем атомарно одним
# iptables-restore --noflush на (family, table) в _xkeen_apply.
# Сохраняем семантику старого ipt() для всех существующих helper'ов.
_xkeen_v4_nat_rules=""
_xkeen_v4_mangle_rules=""
_xkeen_v6_nat_rules=""
_xkeen_v6_mangle_rules=""
ipt() {
[ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && return 0
[ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && return 0
case "$1" in
-A|-I|-D)
_line=$*
case "${family}_${table}" in
iptables_nat) _xkeen_v4_nat_rules="${_xkeen_v4_nat_rules}${_line}
" ;;
iptables_mangle) _xkeen_v4_mangle_rules="${_xkeen_v4_mangle_rules}${_line}
" ;;
ip6tables_nat) _xkeen_v6_nat_rules="${_xkeen_v6_nat_rules}${_line}
" ;;
ip6tables_mangle) _xkeen_v6_mangle_rules="${_xkeen_v6_mangle_rules}${_line}
" ;;
esac
return 0
;;
*)
# Прочие операции (-F, -X) - в реальный iptables.
if [ "$family" = "iptables" ]; then
iptables -w -t "$table" "$@"
else
ip6tables -w -t "$table" "$@"
fi
return $?
;;
esac
}
# Применяет аккумулированные правила одной таблицы атомарно через
# iptables-restore --noflush. Custom chain $name_chain flush'ится
# объявлением ":$name_chain -" перед добавлением новых правил.
_xkeen_apply_table() {
_family="$1"
_table="$2"
_rules_var="$3"
eval "_rules=\${$_rules_var}"
[ -z "$_rules" ] && return 0
# Удаляем устаревшие xkeen-tagged правила из built-in/system chain'ов
# (PREROUTING, OUTPUT, _NDM_HOTSPOT_DNSREDIR), правила из самой $name_chain
# игнорируются - там ":chain -" в blob их сам flush'ит.
save_cmd=""
[ "$_family" = "iptables" ] && [ "$iptables_supported" = "true" ] && save_cmd="iptables-save"
[ "$_family" = "ip6tables" ] && [ "$ip6tables_supported" = "true" ] && save_cmd="ip6tables-save"
[ -z "$save_cmd" ] && { _deletes=""; return; }
_deletes=$($save_cmd -t "$_table" 2>/dev/null | awk \
-v tag="$comment_tag" \
-v c1="$name_chain" \
-v c2="${name_chain}_out" '
index($0, tag) &&
$1 == "-A" &&
$2 != c1 &&
$2 != c2 {
sub(/^-A /, "-D ")
print
}
')
{
printf '*%s\n' "$_table"
printf ':%s -\n' "$name_chain"
[ "$proxy_router" = "on" ] && printf ':%s_out -\n' "$name_chain"
[ -n "$_deletes" ] && printf '%s\n' "$_deletes"
printf '%s' "$_rules"
printf 'COMMIT\n'
} | if [ "$_family" = "iptables" ]; then
iptables-restore --noflush
else
ip6tables-restore --noflush
fi
}
_xkeen_apply() {
[ "$iptables_supported" = "true" ] && _xkeen_apply_table iptables nat _xkeen_v4_nat_rules || true
[ "$iptables_supported" = "true" ] && _xkeen_apply_table iptables mangle _xkeen_v4_mangle_rules || true
[ "$ip6tables_supported" = "true" ] && _xkeen_apply_table ip6tables nat _xkeen_v6_nat_rules || true
[ "$ip6tables_supported" = "true" ] && _xkeen_apply_table ip6tables mangle _xkeen_v6_mangle_rules || true
}
# Добавление правил-исключений
add_exclude_rules() {
chain="$1"
for exclude in $exclude_list; do
if [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && [ "$chain" != "${name_chain}_out" ]; then
case "$exclude" in
10.0.0.0/8|172.16.0.0/12|192.168.0.0/16|fd00::/8|fe80::/10)
if [ "$table" = "mangle" ] && [ "$mode_proxy" = "Hybrid" ]; then
ipt -A "$chain" -d "$exclude" -p tcp --dport 53 $comment -j RETURN >/dev/null 2>&1
ipt -A "$chain" -d "$exclude" -p udp ! --dport 53 $comment -j RETURN >/dev/null 2>&1
elif [ "$table" = "nat" ] && [ "$mode_proxy" = "Hybrid" ]; then
ipt -A "$chain" -d "$exclude" -p tcp ! --dport 53 $comment -j RETURN >/dev/null 2>&1
ipt -A "$chain" -d "$exclude" -p udp --dport 53 $comment -j RETURN >/dev/null 2>&1
elif [ "$table" = "mangle" ] && [ "$mode_proxy" = "TProxy" ]; then
ipt -A "$chain" -d "$exclude" -p tcp ! --dport 53 $comment -j RETURN >/dev/null 2>&1
ipt -A "$chain" -d "$exclude" -p udp ! --dport 53 $comment -j RETURN >/dev/null 2>&1
fi
;;
esac
else
ipt -A "$chain" -d "$exclude" $comment -j RETURN >/dev/null 2>&1
fi
done
}
add_ipset_exclude() {
base_set="$1"
set_type="${2:-hash:net}"
if [ "$family" = "ip6tables" ]; then
set_name="${base_set}6"
ipset_family="inet6"
else
set_name="$base_set"
ipset_family="inet"
fi
ipset create "$set_name" "$set_type" family "$ipset_family" -exist || return
ipt -I "$chain" 1 -m set --match-set "$set_name" dst $comment -j RETURN >/dev/null 2>&1
}
# Добавление правил iptables
add_ipt_rule() {
family="$1"
table="$2"
chain="$3"
shift 3
[ "$family" = "iptables" ] && [ "$iptables_supported" = "false" ] && return
[ "$family" = "ip6tables" ] && [ "$ip6tables_supported" = "false" ] && return
# Custom chain создаётся/flush'ится одной строкой ":$name_chain -" в blob,
# поэтому ни -nL guard, ни -N не нужны - всегда заполняем body.
add_exclude_rules "$chain"
if [ "$table" = "$table_tproxy" ]; then
if [ "$mode_proxy" = "Hybrid" ]; then
set -- -p udp -m conntrack --ctstate ESTABLISHED,RELATED $comment -j CONNMARK --restore-mark
else
set -- -m conntrack --ctstate ESTABLISHED,RELATED $comment -j CONNMARK --restore-mark
fi
ipt -I "$chain" 1 "$@" >/dev/null 2>&1
fi
case "$mode_proxy" in
Hybrid)
if [ "$table" = "$table_redirect" ]; then
ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1
add_ipset_exclude ext_exclude hash:ip
add_ipset_exclude geo_exclude hash:net
add_ipset_exclude user_exclude hash:net
ipt -A "$chain" -p tcp $comment -j REDIRECT --to-port "$port_redirect" >/dev/null 2>&1
else
ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1
add_ipset_exclude ext_exclude hash:ip
add_ipset_exclude geo_exclude hash:net
add_ipset_exclude user_exclude hash:net
ipt -A "$chain" -p udp -m socket --transparent $comment -j MARK --set-mark "$table_mark" >/dev/null 2>&1
ipt -A "$chain" -p udp -m mark ! --mark 0 $comment -j CONNMARK --save-mark >/dev/null 2>&1
ipt -A "$chain" -p udp $comment -j TPROXY --on-ip "$proxy_ip" --on-port "$port_tproxy" --tproxy-mark "$table_mark" >/dev/null 2>&1
fi
;;
TProxy)
ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1
for net in $network_tproxy; do
add_ipset_exclude ext_exclude hash:ip
add_ipset_exclude geo_exclude hash:net
add_ipset_exclude user_exclude hash:net
ipt -A "$chain" -p "$net" -m socket --transparent $comment -j MARK --set-mark "$table_mark" >/dev/null 2>&1
ipt -A "$chain" -p "$net" -m mark ! --mark 0 $comment -j CONNMARK --save-mark >/dev/null 2>&1
ipt -A "$chain" -p "$net" $comment -j TPROXY --on-ip "$proxy_ip" --on-port "$port_tproxy" --tproxy-mark "$table_mark" >/dev/null 2>&1
done
;;
Redirect)
ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1
add_ipset_exclude ext_exclude hash:ip
add_ipset_exclude geo_exclude hash:net
add_ipset_exclude user_exclude hash:net
for net in $network_redirect; do
ipt -A "$chain" -p "$net" $comment -j REDIRECT --to-port "$port_redirect" >/dev/null 2>&1
done
;;
*) exit 0 ;;
esac
if [ -n "$dscp_exclude" ]; then
for dscp in $dscp_exclude; do
ipt -I "$chain" -m dscp --dscp "$dscp" $comment -j RETURN >/dev/null 2>&1
done
fi
}
# Настройка таблицы маршрутов
configure_route() {
ip_version="$1"
# Определяем таблицу маршрутизации
if [ -n "$policy_mark" ]; then
policy_table=$(ip rule show | awk -v policy="$policy_mark" '$0 ~ policy && /lookup/ && !/blackhole/ {print $(NF); exit}')
fi
source_table="${policy_table:-main}"
# Проверяем есть ли default маршрут
check_default() {
if [ "$ip_version" = "6" ] && ! ip -6 route show default 2>/dev/null | grep -q .; then
return 0
fi
if [ "$source_table" = "main" ]; then
ip -"$ip_version" route show default 2>/dev/null | grep -q '^default'
else
ip -"$ip_version" route show table all 2>/dev/null | grep -E "^[[:space:]]*default .* table $policy_table([[:space:]]|$)" | grep -vq 'unreachable' >/dev/null
fi
}
attempts=0
max_attempts=4
until check_default; do
attempts=$((attempts + 1))
if [ "$attempts" -ge "$max_attempts" ]; then
[ "$ip_version" = "4" ] && touch "/tmp/noinet"
return 1
fi
sleep 1
done
[ "$ip_version" = "4" ] && rm -f "/tmp/noinet"
ip -"$ip_version" rule del fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1 || true
ip -"$ip_version" route flush table "$table_id" >/dev/null 2>&1 || true
ip -"$ip_version" route add local default dev lo table "$table_id" >/dev/null 2>&1 || true
ip -"$ip_version" rule add fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1 || true
# Копируем маршруты
ip -"$ip_version" route show table "$source_table" 2>/dev/null | while read -r route_line; do
case "$route_line" in
default*|unreachable*|blackhole*) continue ;;
*) ip -"$ip_version" route add table "$table_id" $route_line >/dev/null 2>&1 || true ;;
esac
done
return 0
}
# Создание множественных правил multiport
add_multiport_rules() {
family="$1"
table="$2"
net="$3"
mark="$4"
ports="$5"
target="$6"
[ -z "$ports" ] && return
num_ports=$(echo "$ports" | tr ',' '\n' | wc -l)
i=1
while [ "$i" -le "$num_ports" ]; do
end=$((i + 6))
chunk=$(echo "$ports" | tr ',' '\n' | sed -n "${i},${end}p" | tr '\n' ',' | sed 's/,$//')
[ -z "$chunk" ] && break
if [ -n "$mark" ]; then
set -- -m connmark --mark "$mark" -m conntrack ! --ctstate INVALID -p "$net" -m multiport --dports "$chunk" $comment -j "$target"
else
set -- -m conntrack ! --ctstate INVALID -p "$net" -m multiport --dports "$chunk" $comment -j "$target"
fi
ipt -A PREROUTING "$@" >/dev/null 2>&1
i=$((i + 7))
done
}
# Добавление цепочек PREROUTING
add_prerouting() {
family="$1"
table="$2"
# MAC-bypass для built-in «Без доступа в интернет»: RETURN из PREROUTING
# до xkeen-jumps, пакет минует TPROXY/REDIRECT/MARK и попадает в FORWARD,
# где NDM-цепочка _NDM_HOTSPOT_FWD его дропнет штатно. -m mac --mac-source
# видит L2-MAC только для устройств в одном broadcast-домене с роутером
# (LAN/Wi-Fi/guest-bridge); за L3-VLAN правило безвредно неактивно.
ipt -I PREROUTING 1 -m set --match-set "$name_ipset_deny_mac" src $comment -j RETURN >/dev/null 2>&1
for net in $networks; do
if [ "$mode_proxy" = "Hybrid" ]; then
[ "$table" = "nat" ] && [ "$net" != "tcp" ] && continue
[ "$table" = "mangle" ] && [ "$net" != "udp" ] && continue
fi
if [ "$mode_proxy" = "TProxy" ]; then
proto_match=""
else
proto_match="-p $net"
fi
for dscp in $dscp_proxy; do
set -- -m conntrack ! --ctstate INVALID $proto_match -m dscp --dscp "$dscp" $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
done
if [ "$proxy_router" = "on" ]; then
set -- -i lo -m mark --mark "$table_mark" $proto_match $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
fi
# Пользовательские политики из xkeen.json
# Heredoc вместо echo|while - while должен исполниться в parent shell,
# чтобы аккумуляторы _xkeen_*_rules в ipt() модифицировались в нужном scope.
while IFS='|' read -r pname pmark pmode pports; do
[ -z "$pmark" ] && continue
pmark=$(echo "$pmark" | tr -d ' \r\n')
pmode=$(echo "$pmode" | tr -d ' \r\n')
pports=$(echo "$pports" | tr -d ' \r\n')
if [ "$pmode" = "all" ]; then
set -- -m connmark --mark 0x"$pmark" -m conntrack ! --ctstate INVALID $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
elif [ "$pmode" = "include" ]; then
add_multiport_rules "$family" "$table" "$net" "0x$pmark" "$pports" "$name_chain"
elif [ "$pmode" = "exclude" ]; then
add_multiport_rules "$family" "$table" "$net" "0x$pmark" "$pports" "RETURN"
set -- -m connmark --mark 0x"$pmark" -m conntrack ! --ctstate INVALID -p "$net" $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
fi
done </dev/null 2>&1
else
# Политика xkeen, когда порты не указаны (проксирование на всех портах)
set -- -m connmark --mark "$policy_mark" -m conntrack ! --ctstate INVALID $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
fi
# НЕТ политики xkeen
else
# заданы порты проксирования
if [ -n "$port_donor" ]; then
add_multiport_rules "$family" "$table" "$net" "" "$port_donor" "$name_chain"
# заданы порты исключения
elif [ -n "$port_exclude" ]; then
add_multiport_rules "$family" "$table" "$net" "" "$port_exclude" "RETURN"
set -- -m conntrack ! --ctstate INVALID -p "$net" $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
# Если нет ни xkeen, ни пользовательских политик -> перехватываем всё
else
set -- -m conntrack ! --ctstate INVALID $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/dev/null 2>&1
fi
fi
done
}
# Добавление цепочек для проксирования трафика Entware
add_output() {
family="$1"
table="$2"
[ "$proxy_router" != "on" ] && return
out_chain="${name_chain}_out"
# ":${name_chain}_out -" в blob создаст/flush'ит chain атомарно,
# body заполняется всегда.
orig_chain="$chain"
chain="$out_chain"
ipt -A "$out_chain" -o lo $comment -j RETURN >/dev/null 2>&1
ipt -A "$out_chain" -m mark --mark 255 $comment -j RETURN >/dev/null 2>&1
add_exclude_rules "$out_chain"
add_ipset_exclude ext_exclude hash:ip
add_ipset_exclude geo_exclude hash:net
add_ipset_exclude user_exclude hash:net
chain="$orig_chain"
for net in $networks; do
if [ "$mode_proxy" = "Hybrid" ]; then
[ "$table" = "nat" ] && [ "$net" != "tcp" ] && continue
[ "$table" = "mangle" ] && [ "$net" != "udp" ] && continue
fi
if [ "$mode_proxy" = "TProxy" ]; then
proto_match=""
else
proto_match="-p $net"
fi
set -- -m conntrack ! --ctstate INVALID $proto_match $comment -j "$out_chain"
ipt -A OUTPUT "$@" >/dev/null 2>&1
if [ "$table" = "$table_redirect" ]; then
set -- -p "$net" $comment -j REDIRECT --to-port "$port_redirect"
ipt -A "$out_chain" "$@" >/dev/null 2>&1
elif [ "$table" = "$table_tproxy" ]; then
set -- -p "$net" $comment -j MARK --set-mark "$table_mark"
ipt -A "$out_chain" "$@" >/dev/null 2>&1
fi
done
}
dns_redir() {
family="$1"
table="nat"
[ "$aghfix" != "on" ] && return
[ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && return
all_marks=""
[ -n "$policy_mark" ] && all_marks="$policy_mark"
[ -n "$custom_mark" ] && all_marks="$custom_mark $all_marks"
if [ -n "$user_policies" ]; then
user_marks=$(echo "$user_policies" | awk -F'|' '{if ($2 != "") print "0x"$2}')
all_marks="$all_marks $user_marks"
fi
for mark in $all_marks; do
mark=$(echo "$mark" | tr -d ' \r\n')
[ -z "$mark" ] && continue
for proto in udp tcp; do
set -- -p "$proto" -m mark --mark "$mark" -m pkttype --pkt-type unicast -m "$proto" --dport 53 $comment -j REDIRECT --to-ports 53
ipt -I _NDM_HOTSPOT_DNSREDIR "$@" >/dev/null 2>&1
done
done
}
if [ -n "$port_donor" ] || [ -n "$port_exclude" ]; then
[ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && [ -n "$port_donor" ] && port_donor="53,$port_donor"
fi
for family in iptables ip6tables; do
[ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && continue
[ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && continue
if [ "$family" = "ip6tables" ]; then
exclude_list="$val_exclude_ip6"
proxy_ip="$ipv6_proxy"
configure_route 6
else
exclude_list="$val_exclude_ip4"
proxy_ip="$ipv4_proxy"
configure_route 4
fi
if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then
for table in "$table_tproxy" "$table_redirect"; do
add_ipt_rule "$family" "$table" "$name_chain"
add_prerouting "$family" "$table"
add_output "$family" "$table"
done
elif [ -z "$port_redirect" ] && [ -n "$port_tproxy" ]; then
table="$table_tproxy"
add_ipt_rule "$family" "$table" "$name_chain"
add_prerouting "$family" "$table"
add_output "$family" "$table"
elif [ -n "$port_redirect" ] && [ -z "$port_tproxy" ]; then
table="$table_redirect"
add_ipt_rule "$family" "$table" "$name_chain"
add_prerouting "$family" "$table"
add_output "$family" "$table"
fi
dns_redir "$family"
done
# Атомарно применяем все аккумулированные правила одним
# iptables-restore --noflush per (family, table).
_xkeen_apply
else
[ -f "/tmp/xkeen_starting.lock" ] && exit 0
touch "/tmp/xkeen_starting.lock"
. "/opt/sbin/.xkeen/01_info/03_info_cpu.sh"
status_file="/opt/lib/opkg/status"
info_cpu
fd_limit="$other_fd"
[ "$architecture" = "arm64-v8a" ] && fd_limit="$arm64_fd"
ulimit -SHn "$fd_limit"
case "$name_client" in
xray)
export XRAY_LOCATION_CONFDIR="$directory_xray_config"
export XRAY_LOCATION_ASSET="$directory_xray_asset"
"$name_client" run >/dev/null 2>&1 &
;;
mihomo)
export CLASH_HOME_DIR="$directory_configs_app"
"$name_client" >/dev/null 2>&1 &
;;
esac
_probe=0
while [ "$_probe" -lt 60 ]; do
pidof "$name_client" >/dev/null 2>&1 && break
_probe=$((_probe + 1))
usleep 100000
done
unset _probe
rm -f "/tmp/xkeen_starting.lock"
if pidof "$name_client" >/dev/null; then
restart_script "$@"
else
exit 1
fi
fi
EOL
sed -i '1,2!{/^[[:space:]]*#/d; /^[[:space:]]*$/d}' "$file_netfilter_hook"
chmod 700 "$file_netfilter_hook"
# Schedule.d-хук: NDM вызывает scripts/schedule.d при start/stop расписаний
# (родительский контроль). Хук дёргает netfilter.d/proxy.sh, который
# ре-синхронизирует ipset deny-MAC из актуального hotspot API.
mkdir -p "$(dirname "$file_schedule_hook")" 2>/dev/null
cat > "$file_schedule_hook" <<'SCHEDULE_EOL'
#!/bin/sh
# XKeen: re-sync deny MAC ipset on schedule start/stop. Auto-generated. DO NOT EDIT!
[ "$1" = "start" ] || [ "$1" = "stop" ] || exit 0
[ -x /opt/etc/ndm/netfilter.d/proxy.sh ] && /opt/etc/ndm/netfilter.d/proxy.sh
SCHEDULE_EOL
chmod 755 "$file_schedule_hook"
sh "$file_netfilter_hook"
}
# Удаление правил iptables
clean_firewall() {
[ -f "$file_netfilter_hook" ] && : > "$file_netfilter_hook"
get_ipver_support
for family in iptables ip6tables; do
[ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && continue
[ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && continue
if "$family" -w -t nat -nL _NDM_HOTSPOT_DNSREDIR >/dev/null 2>&1; then
"$family" -w -t nat -S _NDM_HOTSPOT_DNSREDIR | grep -E -- "$comment_tag" | sed 's/^-A /-D /' | while IFS= read -r rule; do
[ -n "$rule" ] && "$family" -w -t nat $rule >/dev/null 2>&1
done
fi
done
clean_run() {
family="$1"
table="$2"
name_chain="$3"
for sys_chain in PREROUTING OUTPUT; do
"$family" -w -t "$table" -S "$sys_chain" 2>/dev/null | grep -E -- "$comment_tag" | sed 's/^-A /-D /' | while IFS= read -r rule; do
[ -n "$rule" ] && "$family" -w -t "$table" $rule >/dev/null 2>&1
done
done
if "$family" -w -t "$table" -nL "$name_chain" >/dev/null 2>&1; then
"$family" -w -t "$table" -F "$name_chain" >/dev/null 2>&1
"$family" -w -t "$table" -X "$name_chain" >/dev/null 2>&1
fi
out_chain="${name_chain}_out"
if "$family" -w -t "$table" -nL "$out_chain" >/dev/null 2>&1; then
"$family" -w -t "$table" -F "$out_chain" >/dev/null 2>&1
"$family" -w -t "$table" -X "$out_chain" >/dev/null 2>&1
fi
}
for family in iptables ip6tables; do
for chain in nat mangle; do
clean_run "$family" "$chain" "$name_chain"
done
done
if command -v ip >/dev/null 2>&1; then
for family in 4 6; do
while ip -"$family" rule del fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1; do :; done
ip -"$family" route flush table "$table_id" >/dev/null 2>&1 || true
done
fi
# Очистка и удаление списков ipset
if command -v ipset >/dev/null 2>&1; then
for set in geo_exclude geo_exclude6 user_exclude user_exclude6 "$name_ipset_deny_mac"; do
ipset flush "$set" >/dev/null 2>&1
ipset destroy "$set" >/dev/null 2>&1
done
fi
# Schedule.d-hook идемпотентно перегенерируется в configure_firewall,
# на остановке убираем чтобы NDM не дёргал мёртвый netfilter.d/proxy.sh.
[ -f "$file_schedule_hook" ] && rm -f "$file_schedule_hook"
}
# Мониторинг файловых дескрипторов
monitor_fd() {
while true; do
client_pid=$(pidof "$name_client" | awk '{print $1}')
if [ -n "$client_pid" ] && [ -d "/proc/$client_pid/fd" ]; then
limit=$(awk '/Max open files/ {print $4}' "/proc/$client_pid/limits")
set -- /proc/$client_pid/fd/*
[ -e "$1" ] || set --
current=$#
if [ "$limit" -gt 0 ] && [ "$current" -gt $((limit * 90 / 100)) ]; then
log_warning_router "$name_client открыл $current из $limit файловых дескрипторов, инициирован перезапуск"
rm -f "$file_pid_fd"
fd_out=true
proxy_stop
proxy_start "on"
exit 0
fi
fi
sleep "$delay_fd"
done
}
load_ipset() {
set="$1"
file="$2"
family="$3"
tmp="${set}_tmp"
# Заполняем tmp; основной набор подменяется только после успешного restore
ipset create "$set" hash:net family "$family" -exist
ipset create "$tmp" hash:net family "$family" -exist
ipset flush "$tmp"
if [ -f "$file" ] && sed -e 's/\r$//' -e 's/#.*//' -e '/^[[:space:]]*$/d' "$file" | awk '{print "add '"$tmp"' "$1}' | ipset restore -exist; then
ipset swap "$set" "$tmp"
fi
ipset destroy "$tmp"
}
apply_fd_limit() {
fd_limit="$other_fd"
[ "$architecture" = "arm64-v8a" ] && fd_limit="$arm64_fd"
ulimit -SHn "$fd_limit"
}
cleanup_fd_monitor() {
[ -f "$file_pid_fd" ] || return 0
kill "$(cat "$file_pid_fd")" 2>/dev/null
rm -f "$file_pid_fd"
}
missing_files_template='
'"${light_blue}"'Отсутствуют исполняемые файлы:'"${reset}"'
'"${yellow}"'%b'"${reset}"'
'"${green}"'Возможные причины:'"${reset}"'
• XKeen установлен во внутреннюю память и на ней недостаточно места
• У файла отсутствуют права на выполнение
'"${green}"'Рекомендуемые действия:'"${reset}"'
• Переустановите XKeen на внешний накопитель
• Скопируйте недостающий файл вручную и сделайте исполняемым
'
check_binary() {
file="$1"
path="$install_dir/$file"
if [ ! -f "$path" ] || [ ! -x "$path" ]; then
return 1
fi
check_cmd="version"
[ "$file" = "xray" ] && check_cmd="version"
[ "$file" = "yq" ] && check_cmd="--version"
[ "$file" = "mihomo" ] && check_cmd="-v"
if ! "$file" $check_cmd >/dev/null 2>&1; then
log_error_router "Бинарный файл $file аварийно остановлен"
log_error_terminal "
Бинарный файл ${yellow}$file${reset} аварийно остановлен
${red}Файл повреждён или несовместим с процессором${reset} вашего роутера
Установите другую версию ${yellow}$file${reset}
"
fi
return 0
}
info_health_binary() {
missing_files=""
add_to_missing() {
file_name="$1"
prefix=" - "
if [ -z "$missing_files" ]; then
missing_files="${prefix}${yellow}${file_name}${reset}"
else
missing_files="${missing_files}\n ${prefix}${yellow}${file_name}${reset}"
fi
}
case "$name_client" in
xray)
if ! check_binary xray; then add_to_missing "xray"; fi
;;
mihomo)
for file in mihomo yq; do
if ! check_binary "$file"; then add_to_missing "$file"; fi
done
;;
esac
if [ -n "$missing_files" ]; then
log_error_terminal "$(printf "$missing_files_template" "$missing_files")"
fi
}
# Очистка при аварийной остановке прокси-клиента
emergency_clear() {
rm -f "/tmp/xkeen_ready"
cleanup_fd_monitor
clean_firewall
}
# Запуск прокси-клиента
proxy_start() {
start_manual="$1"
if [ "$start_manual" = "on" ] || [ "$start_auto" = "on" ]; then
_invalidate_inbounds_cache
apply_ipv6_state
get_ipver_support
info_health_binary
validate_xkeen_json
check_policy_name_conflict
check_xray_backups
validate_routing_mark
log_clean
api_cache_init
sync_deny_mac_ipset
process_user_ports
process_custom_mark
port_redirect=$(get_port_redirect)
network_redirect=$(get_network_redirect)
port_tproxy=$(get_port_tproxy)
network_tproxy=$(get_network_tproxy)
mode_proxy=$(get_mode_proxy)
if [ "$mode_proxy" != "Other" ]; then
policy_mark=$(get_policy_mark)
if [ -n "$policy_mark" ]; then
user_policies=$(resolve_user_policies)
if [ -n "$user_policies" ]; then
print_policy_info "yes" "yes"
else
print_policy_info "yes" "no"
fi
else
raw_user_policies=$(get_user_policies)
ignored_custom="no"
if [ -n "$raw_user_policies" ]; then
ignored_custom="yes"
fi
print_policy_info "no" "no" "$ignored_custom"
user_policies=""
fi
networks=$(printf '%s\n' $network_redirect $network_tproxy | tr ',' ' ' | tr -s ' ' '\n' | sort -u | tr '\n' ' ')
networks=${networks% }
if [ -n "$policy_mark" ] && [ -z "$port_donor" ]; then
port_exclude=$(get_port_exclude)
fi
if ! proxy_status && { [ -n "$port_donor" ] || [ -n "$port_exclude" ] || [ "$mode_proxy" = "TProxy" ] || [ "$mode_proxy" = "Hybrid" ]; }; then
get_modules
fi
if [ "$mode_proxy" = "TProxy" ]; then
keenetic_ssl="$(get_keenetic_port)" || {
proxy_stop
log_error_router "Порт 443 занят сервисами Keenetic"
log_error_terminal "
Необходимый для режима ${light_blue}TProxy${reset} ${red}443 порт занят${reset} сервисами Keenetic
Освободите его на странице 'Пользователи и доступ' веб-интерфейса роутера
"
}
fi
fi
if proxy_status; then
echo -e " Прокси-клиент уже ${green}запущен${reset}"
# Marker до configure_firewall: тот завершается `sh proxy.sh`,
# gate в хуке читает /tmp/xkeen_ready.
touch "/tmp/xkeen_ready"
[ "$mode_proxy" != "Other" ] && configure_firewall
if [ "$start_manual" = "on" ]; then
log_error_terminal "Не удалось запустить ${yellow}$name_client${reset}, так как он уже запущен"
else
log_info_router "Прокси-клиент успешно запущен в режиме $mode_proxy"
rm -f "/tmp/xkeen_coldstart.lock"
fi
else
log_info_router "Инициирован запуск прокси-клиента"
attempt=1
. "/opt/sbin/.xkeen/01_info/03_info_cpu.sh"
status_file="/opt/lib/opkg/status"
info_cpu
while [ "$attempt" -le "$start_attempts" ]; do
case "$name_client" in
xray)
export XRAY_LOCATION_CONFDIR="$directory_xray_config"
export XRAY_LOCATION_ASSET="$directory_xray_asset"
find "$directory_xray_config" -maxdepth 1 -name '._*.json' -type f -delete
apply_fd_limit
if [ -n "$fd_out" ]; then
nohup "$name_client" run >/dev/null 2>&1 &
unset fd_out
else
"$name_client" run &
fi
;;
mihomo)
export CLASH_HOME_DIR="$directory_configs_app"
apply_fd_limit
if [ -n "$fd_out" ]; then
nohup "$name_client" >/dev/null 2>&1 &
unset fd_out
else
"$name_client" &
fi
;;
*) log_error_terminal "Неизвестный прокси-клиент: ${yellow}$name_client${reset}" ;;
esac
_probe_attempt=0
while [ "$_probe_attempt" -lt 60 ]; do
proxy_status && break
_probe_attempt=$((_probe_attempt + 1))
usleep 50000
done
unset _probe_attempt
if proxy_status; then
# См. alive-branch: marker до configure_firewall.
touch "/tmp/xkeen_ready"
[ "$mode_proxy" != "Other" ] && configure_firewall
_pids=""
[ "$iptables_supported" = "true" ] && [ -f "$ru_exclude_ipv4" ] && { load_ipset geo_exclude "$ru_exclude_ipv4" inet & _pids="$_pids $!"; }
[ "$ip6tables_supported" = "true" ] && [ -f "$ru_exclude_ipv6" ] && { load_ipset geo_exclude6 "$ru_exclude_ipv6" inet6 & _pids="$_pids $!"; }
load_user_ipset & _pids="$_pids $!"
[ -n "$_pids" ] && wait $_pids
unset _pids
echo -e " Прокси-клиент ${green}запущен${reset} в режиме ${light_blue}${mode_proxy}${reset}"
(
# Даём ядру прокси время полностью инициализироваться
# Это защищает от ситуаций, когда xray/mihomo
# успевает создать PID, но затем аварийно завершается,
# например, из-за битой конфигурации
sleep 3
if ! proxy_status; then
echo
echo -e " Прокси-клиент ${red}аварийно завершился${reset}"
echo -e " ${green}Выполняется очистка${reset} правил прозрачного проксирования"
log_error_router "Прокси-клиент аварийно завершился после запуска"
emergency_clear
printf '\n~ # '
fi
) &
if [ -n "$api_policy_json" ]; then
if echo "$api_policy_json" | jq --arg policy "$name_policy" -e 'any(.[]; .description | ascii_downcase == $policy)' > /dev/null; then
if [ -e "/tmp/noinet" ]; then
echo
echo -e " У политики ${yellow}$name_policy${reset} ${red}нет доступа в интернет${reset}"
echo " Проверьте, установлена ли галка на подключении к провайдеру"
fi
fi
fi
[ "$mode_proxy" = "Other" ] && echo -e " Функция прозрачного прокси ${red}не активна${reset}. Направляйте соединения на ${yellow}${name_client}${reset} вручную"
log_info_router "Прокси-клиент успешно запущен в режиме $mode_proxy"
rm -f "/tmp/xkeen_coldstart.lock"
if [ "$check_fd" = "on" ]; then
cleanup_fd_monitor
monitor_fd &
echo $! > "$file_pid_fd"
log_info_router "Запущен контроль файловых дескрипторов $name_client"
fi
return 0
fi
attempt=$((attempt + 1))
done
echo -e " ${red}Не удалось запустить${reset} прокси-клиент"
log_error_terminal "Не удалось запустить прокси-клиент"
fi
else
clean_firewall
fi
}
# Активная проба готовности окружения вместо sleep $start_delay.
# Ждём ndmc, default route и insmod-ability xt_TPROXY (deps ndm
# подгружает асинхронно уже после ndmc-ready). $start_delay сохранён
# как safety cap (FAQ #12).
wait_for_ready() {
_max=$(( ${start_delay:-60} * 2 ))
_attempt=0
_probe_ko="$directory_os_modules/xt_TPROXY.ko"
while [ "$_attempt" -lt "$_max" ]; do
if ndmc -c "show version" >/dev/null 2>&1 \
&& ip route show default 2>/dev/null | grep -q '^default'; then
# .ko отсутствует (не TProxy/Hybrid), уже загружен, либо insmod удался
if [ ! -f "$_probe_ko" ] \
|| grep -q '^xt_TPROXY ' /proc/modules 2>/dev/null \
|| insmod "$_probe_ko" >/dev/null 2>&1; then
return 0
fi
fi
usleep 500000
_attempt=$((_attempt + 1))
done
return 0
}
# Остановка прокси-клиента
proxy_stop() {
rm -f "/tmp/xkeen_ready"
if ! proxy_status; then
echo -e " Прокси-клиент ${red}не запущен${reset}"
cleanup_fd_monitor
else
[ -f "/tmp/xkeen_coldstart.lock" ] || log_info_router "Инициирована остановка прокси-клиента"
cleanup_fd_monitor
attempt=1
while [ "$attempt" -le "$start_attempts" ]; do
clean_firewall
killall -q "$name_client" 2>/dev/null
_stop_attempt=0
while [ "$_stop_attempt" -lt 30 ]; do
pidof "$name_client" >/dev/null 2>&1 || break
_stop_attempt=$((_stop_attempt + 1))
usleep 50000
done
unset _stop_attempt
if pidof "$name_client" >/dev/null 2>&1; then
killall -q -9 "$name_client" 2>/dev/null
usleep 200000
fi
if ! proxy_status; then
echo -e " Прокси-клиент ${red}остановлен${reset}"
[ -f "/tmp/xkeen_coldstart.lock" ] || log_info_router "Прокси-клиент успешно остановлен"
rm -f "/tmp/xkeen_coldstart.lock"
return 0
fi
attempt=$((attempt + 1))
done
echo -e " Прокси-клиент ${red}не удалось остановить${reset}"
log_error_terminal "Не удалось остановить прокси-клиент"
fi
}
# Менеджер команд
case "$1" in
start)
ipset create ext_exclude hash:ip family inet -exist
ipset create ext_exclude6 hash:ip family inet6 -exist
if [ -z "$2" ]; then
[ "$start_auto" != "on" ] && exit 0
log_info_router "Подготовка к запуску прокси-клиента"
nohup "$0" cold_start >/dev/null 2>&1 &
touch "/tmp/xkeen_coldstart.lock"
exit 0
fi
proxy_start "$2"
;;
stop) proxy_stop ;;
status)
if proxy_status; then
mode_proxy=""
if [ -f "$file_netfilter_hook" ]; then
mode_proxy=$(grep '^mode_proxy=' "$file_netfilter_hook" | awk -F"=" '{print $2}' | tr -d "'" 2>/dev/null)
fi
[ -z "$mode_proxy" ] && mode_proxy="Other"
echo -e " Прокси-клиент ${yellow}$name_client${reset} ${green}запущен${reset} в режиме ${light_blue}$mode_proxy${reset}"
else
echo -e " Прокси-клиент ${red}не запущен${reset}"
fi
;;
restart) proxy_stop; proxy_start "$2" ;;
cold_start)
# Re-spawn в чистый S05xkeen: sh-функции (wait_for_ready) не
# наследуются через nohup sh -c, поэтому пробу зовём отсюда.
wait_for_ready
proxy_start ""
;;
*) echo -e " Команды: ${green}start${reset} | ${red}stop${reset} | ${yellow}restart${reset} | status" ;;
esac
exit 0
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/00_configs_import.sh
================================================
# Импорт модулей конфигураций
# Модуль конфигурации
. "$xinstall_dir/08_install_configs/01_configs_install.sh"
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/01_configs_install.sh
================================================
# Функция для установки файлов конфигурации Xray
install_configs() {
if [ ! -d "$xray_conf_dir" ]; then
mkdir -p "$xray_conf_dir"
fi
if ls "$xray_conf_dir"/*.json >/dev/null 2>&1; then
return 0
fi
xray_files="$xray_conf_smpl"/*.json
for file in $xray_files; do
filename=$(basename "$file")
cp "$file" "$xray_conf_dir/"
echo " Добавлен шаблон конфигурационного файла Xray:"
echo -e " ${yellow}$filename${reset}"
sleep 1
done
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/01_log.json
================================================
{
"log": {
"access": "/opt/var/log/xray/access.log",
"error": "/opt/var/log/xray/error.log",
"dnsLog": true,
"loglevel": "none"
}
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/02_dns.json
================================================
{
// Пример настройки - https://jameszero.net/3398.htm
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/03_inbounds.json
================================================
{
"inbounds": [
{
"port": 1181,
"protocol": "tunnel",
"settings": {
"network": "tcp",
"followRedirect": true
},
"sniffing": {
"enabled": true,
"routeOnly": true,
"destOverride": ["http","tls"]
},
"tag": "redirect"
},
{
"port": 1181,
"protocol": "tunnel",
"settings": {
"network": "udp",
"followRedirect": true
},
"streamSettings": {
"sockopt": {"tproxy": "tproxy"}
},
"sniffing": {
"enabled": true,
"routeOnly": true,
"destOverride": ["quic"]
},
"tag": "tproxy"
}
]
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/04_outbounds.json
================================================
{
// Создайте файл по ссылке https://zxc-rv.github.io/XKeen-UI/Outbound_Generator/
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/05_routing.json
================================================
{
// Создайте файл по ссылке https://xray-routing-generator.netlify.app
}
================================================
FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/06_policy.json
================================================
{
"policy": {
"levels": {
"0": {
"uplinkOnly": 0,
"downlinkOnly": 0
}
}
}
}
================================================
FILE: scripts/_xkeen/03_delete/00_delete_import.sh
================================================
# Импорт модулей удаления
# Модули удаления
. "$xdelete_dir/01_delete_geofile.sh"
. "$xdelete_dir/02_delete_geoipset.sh"
. "$xdelete_dir/03_delete_cron.sh"
. "$xdelete_dir/04_delete_configs.sh"
. "$xdelete_dir/05_delete_register.sh"
. "$xdelete_dir/06_delete_tmp.sh"
================================================
FILE: scripts/_xkeen/03_delete/01_delete_geofile.sh
================================================
# Функция для удаления выбранных файлов GeoSite
delete_geosite() {
[ "$choice_delete_geosite_refilter_select" = "true" ] && rm -f "$geo_dir/geosite_refilter.dat"
[ "$choice_delete_geosite_v2fly_select" = "true" ] && rm -f "$geo_dir/geosite_v2fly.dat"
[ "$choice_delete_geosite_zkeen_select" = "true" ] && rm -f "$geo_dir/"geosite_zkeen.dat "$geo_dir/"zkeen.dat
}
# Функция для удаления всех файлов GeoSite
delete_geosite_key() {
rm -f "$geo_dir/geosite_refilter.dat" \
"$geo_dir/geosite_v2fly.dat" \
"$geo_dir/geosite_zkeen.dat" \
"$geo_dir/zkeen.dat"
}
# Функция для удаления выбранных файлов GeoIP
delete_geoip() {
[ "$choice_delete_geoip_refilter_select" = "true" ] && rm -f "$geo_dir/geoip_refilter.dat"
[ "$choice_delete_geoip_v2fly_select" = "true" ] && rm -f "$geo_dir/geoip_v2fly.dat"
[ "$choice_delete_geoip_zkeenip_select" = "true" ] && rm -f "$geo_dir/geoip_zkeenip.dat" "$geo_dir/zkeenip.dat"
}
# Функция для удаления всех файлов GeoIP
delete_geoip_key() {
rm -f "$geo_dir/geoip_refilter.dat" \
"$geo_dir/geoip_v2fly.dat" \
"$geo_dir/geoip_zkeenip.dat" \
"$geo_dir/zkeenip.dat"
}
================================================
FILE: scripts/_xkeen/03_delete/02_delete_geoipset.sh
================================================
# Функция для удаления GeoIPSET
delete_geoipset() {
while true; do
printf "\n Желаете удалить российские IP-адреса из исключений проксирования?\n\n"
printf " 1. Да. Загруженные файлы подсетей будут удалены, а списки очищены\n"
printf " 0. Нет. Отмена удаления\n\n"
printf " Ваш выбор: "
read -r choice
case "$choice" in
0)
echo
printf " Отмена удаления списков GeoIPSET.\n\n"
return 0
;;
1)
echo
break
;;
*)
printf " Неверный ввод. Пожалуйста, введите 1 или 0.\n"
;;
esac
done
ipset flush geo_exclude 2>/dev/null
ipset flush geo_exclude6 2>/dev/null
[ -f "$ru_exclude_ipv4" ] && rm -f "$ru_exclude_ipv4" 2>/dev/null
[ -f "$ru_exclude_ipv6" ] && rm -f "$ru_exclude_ipv6" 2>/dev/null
# [ -d "$ipset_cfg" ] && rm -rf "$ipset_cfg"
printf " Списки исключений GeoIPSET ${green}успешно удалены${reset}\n\n"
return 0
}
delete_geoipset_key() {
ipset flush geo_exclude 2>/dev/null
ipset flush geo_exclude6 2>/dev/null
[ -f "$ru_exclude_ipv4" ] && rm -f "$ru_exclude_ipv4" 2>/dev/null
[ -f "$ru_exclude_ipv6" ] && rm -f "$ru_exclude_ipv6" 2>/dev/null
# [ -d "$ipset_cfg" ] && rm -rf "$ipset_cfg"
}
================================================
FILE: scripts/_xkeen/03_delete/03_delete_cron.sh
================================================
# Функция для удаления cron задачи для GeoFile
delete_cron_geofile() {
if [ -f "$cron_dir/$cron_file" ]; then
tmp_file="$cron_dir/${cron_file}.tmp"
cp "$cron_dir/$cron_file" "$tmp_file"
grep -v "ug" "$tmp_file" | grep -v '^\s*$' > "$cron_dir/$cron_file"
fi
}
================================================
FILE: scripts/_xkeen/03_delete/04_delete_configs.sh
================================================
# Удаление всех конфигураций Xray
delete_configs() {
if [ -d "$xray_conf_dir" ]; then
find "$xray_conf_dir" -maxdepth 1 -name '*.json' -type f -delete
fi
}
================================================
FILE: scripts/_xkeen/03_delete/05_delete_register.sh
================================================
# Удаление регистрации Xray
delete_register_xray() {
# Удаляем соответствующие записи из файла статуса opkg
sed -i -e '/Package: xray_s/,/Installed-Time:/d' "/opt/lib/opkg/status"
# Удаляем файлы регистрации, если они существуют
if [ -f "$register_dir/xray_s.control" ] || [ -f "$register_dir/xray_s.list" ]; then
rm -f "$register_dir/xray_s.control" "$register_dir/xray_s.list"
fi
}
# Удаление регистрации Mihomo
delete_register_mihomo() {
# Удаляем соответствующие записи из файла статуса opkg
sed -i -e '/Package: mihomo_s/,/Installed-Time:/d' "/opt/lib/opkg/status"
sed -i -e '/Package: yq_s/,/Installed-Time:/d' "/opt/lib/opkg/status"
# Удаляем файлы регистрации, если они существуют
if [ -f "$register_dir/mihomo_s.control" ] || [ -f "$register_dir/mihomo_s.list" ]; then
rm -f "$register_dir/mihomo_s.control" "$register_dir/mihomo_s.list"
fi
if [ -f "$register_dir/yq_s.control" ] || [ -f "$register_dir/yq_s.list" ]; then
rm -f "$register_dir/yq_s.control" "$register_dir/yq_s.list"
fi
}
# Удаление регистрации XKeen
delete_register_xkeen() {
# Удаляем соответствующие записи из файла статуса opkg
sed -i -e '/Package: xkeen/,/Installed-Time:/d' "/opt/lib/opkg/status"
# Удаляем файлы регистрации, если они существуют
if [ -f "$register_dir/xkeen.control" ] || [ -f "$register_dir/xkeen.list" ]; then
rm -f "$register_dir/xkeen.control" "$register_dir/xkeen.list"
fi
}
================================================
FILE: scripts/_xkeen/03_delete/06_delete_tmp.sh
================================================
# Удаление временных файлов и директорий
delete_tmp() {
[ -d "$ktmp_dir" ] && rm -rf "$ktmp_dir"
[ -d "$xtmp_dir" ] && rm -rf "$xtmp_dir"
[ -d "$mtmp_dir" ] && rm -rf "$mtmp_dir"
[ -f "$cron_dir/root.tmp" ] && rm -f "$cron_dir/root.tmp"
[ -f "$register_dir/new_entry.txt" ] && rm -f "$register_dir/new_entry.txt"
[ -f "$install_dir/xray_bak" ] && rm -f "$install_dir/xray_bak"
[ -f "$install_dir/mihomo_bak" ] && rm -f "$install_dir/mihomo_bak"
[ -f "/tmp/xkrun" ] && rm -f "/tmp/xkrun"
[ -f "/tmp/toff" ] && rm -f "/tmp/toff"
if ! pidof xray >/dev/null && ! pidof mihomo >/dev/null ; then
[ -f "$file_netfilter_hook" ] && rm "$file_netfilter_hook"
[ -f "$file_schedule_hook" ] && rm "$file_schedule_hook"
if command -v ipset >/dev/null 2>&1; then
ipset flush "$name_ipset_deny_mac" >/dev/null 2>&1
ipset destroy "$name_ipset_deny_mac" >/dev/null 2>&1
fi
fi
echo
echo -e " Очистка временных файлов ${green}выполнена${reset}"
}
delete_all() {
echo
echo -e " Удалить резервные копии и пользовательские настройки?"
echo -e " ${yellow}$backups_dir${reset}"
echo -e " ${yellow}$xkeen_cfg${reset}"
echo
echo " 1. Да, удалить"
echo " 0. Нет, оставить"
echo
while true; do
read -r -p " Ваш выбор: " choice
case "$choice" in
1)
[ -d "$backups_dir" ] && rm -rf "$backups_dir"
[ -d "$xkeen_cfg" ] && rm -rf "$xkeen_cfg"
return 0
;;
0)
return 0
;;
*)
echo -e " ${red}Некорректный ввод${reset}"
;;
esac
done
}
================================================
FILE: scripts/_xkeen/04_tools/00_tools_import.sh
================================================
# Дополнительные инструменты
. "$xtools_dir/01_tools_ports.sh"
. "$xtools_dir/02_tools_modules.sh"
. "$xtools_dir/03_tools_diagnostic.sh"
. "$xtools_dir/04_tools_delay.sh"
# Модуль выбора
. "$xtools_dir/05_tools_choice/00_choice_import.sh"
# Модуль резервного копирования
. "$xtools_dir/06_tools_backups/00_backups_import.sh"
# Модуль загрузок
. "$xtools_dir/07_tools_downloaders/00_downloaders_import.sh"
================================================
FILE: scripts/_xkeen/04_tools/01_tools_ports.sh
================================================
read_ports_file() {
file="$1"
[ -f "$file" ] || return
sed 's/\r$//' "$file" | \
sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | \
grep -v '^#' | \
grep -v '^$' | \
sed 's/-/:/g' | \
grep -E '^[0-9]+(:[0-9]+)?$' | \
tr '\n' ',' | \
sed 's/,$//'
}
write_ports_file() {
file="$1"
ports="$2"
tmpfile=$(mktemp)
echo "# XKeen ports list" > "$tmpfile"
echo "$ports" | tr ',' '\n' >> "$tmpfile"
mv "$tmpfile" "$file"
}
ports_conflict_check() {
file1="$1"
file2="$2"
ports1=$(read_ports_file "$file1")
ports2=$(read_ports_file "$file2")
if [ -n "$ports1" ] && [ -n "$ports2" ]; then
return 0
fi
return 1
}
data_is_updated_donor() {
file=$1
new_ports=$2
current_ports=$(
awk -F= '/^port_donor/{print $2; exit}' "$file" \
| tr -d '"'
)
if [ "$current_ports" = "$new_ports" ]; then
return 0
else
return 1
fi
}
data_is_updated_excluded() {
file=$1
new_ports=$2
current_ports=$(
awk -F= '/^port_exclude/{print $2; exit}' "$file" \
| tr -d '"'
)
if [ "$current_ports" = "$new_ports" ]; then
return 0
else
return 1
fi
}
normalize_ports() {
echo "$1" | tr ',' '\n' | awk '
function valid(p) {
return (p ~ /^[0-9]+$/ && p >= 0 && p <= 65535)
}
{
gsub(/[[:space:]]/, "")
if ($0 == "") next
gsub(/-/, ":")
n = split($0, a, ":")
if (n == 1) {
if (valid(a[1])) ports[a[1]]
}
else if (n == 2) {
if (valid(a[1]) && valid(a[2])) {
start = a[1]
end = a[2]
if (start > end) {
tmp = start
start = end
end = tmp
}
ports[start ":" end]
}
}
}
END {
for (p in ports)
print p
}
' | sort -n | tr '\n' ',' | sed 's/,$//'
}
ensure_web_ports() {
ports="$1"
echo "$ports" | tr ',' '\n' | grep -qx "80" || ports="$ports,80"
echo "$ports" | tr ',' '\n' | grep -qx "443" || ports="$ports,443"
normalize_ports "$ports"
}
add_ports_donor() {
[ -z "$1" ] && {
echo -e " ${red}Ошибка${reset}: список портов не может быть пустым"
return 1
}
if ports_conflict_check "$file_port_proxying" "$file_port_exclude"; then
echo -e " ${yellow}Внимание${reset}: вы добавляете порты проксирования, но уже заданы порты исключения
Приоритет у портов проксирования, порты исключения будут проигнорированы"
fi
new_ports=$(normalize_ports "$1")
current_ports=$(read_ports_file "$file_port_proxying")
current_ports=$(normalize_ports "$current_ports")
if [ -n "$current_ports" ]; then
all_ports=$(echo "$current_ports,$new_ports" | tr ',' '\n' | sort -n -u | tr '\n' ',' | sed 's/,$//')
else
all_ports="$new_ports"
fi
all_ports=$(ensure_web_ports "$all_ports")
write_ports_file "$file_port_proxying" "$all_ports"
echo -e " ${green}Порты проксирования обновлены${reset}"
}
dell_ports_donor() {
ports_to_del=$(normalize_ports "$1")
current_ports=$(read_ports_file "$file_port_proxying")
[ -z "$current_ports" ] && {
echo -e " ${yellow}Файл пуст${reset}"
return
}
if [ -z "$ports_to_del" ]; then
> "$file_port_proxying"
echo -e " ${green}Все порты удалены${reset}"
return
fi
new_ports="$current_ports"
for port in $(echo "$ports_to_del" | tr ',' '\n'); do
new_ports=$(echo "$new_ports" | tr ',' '\n' | grep -vFx "$port" | tr '\n' ',' | sed 's/,$//')
done
write_ports_file "$file_port_proxying" "$new_ports"
echo -e " ${green}Порты удалены${reset}"
}
add_ports_exclude() {
[ -z "$1" ] && {
echo -e " ${red}Ошибка${reset}: список портов не может быть пустым"
return 1
}
if ports_conflict_check "$file_port_proxying" "$file_port_exclude"; then
echo -e " ${yellow}Внимание${reset}: вы добавляете порты исключения, но уже заданы порты проксирования
Приоритет у портов проксирования, порты исключения будут проигнорированы"
fi
new_ports=$(normalize_ports "$1")
current_ports=$(read_ports_file "$file_port_exclude")
current_ports=$(normalize_ports "$current_ports")
if [ -n "$current_ports" ]; then
all_ports=$(echo "$current_ports,$new_ports" | tr ',' '\n' | sort -n -u | tr '\n' ',' | sed 's/,$//')
else
all_ports="$new_ports"
fi
write_ports_file "$file_port_exclude" "$all_ports"
echo -e " ${green}Порты исключения обновлены${reset}"
}
dell_ports_exclude() {
ports_to_del=$(normalize_ports "$1")
current_ports=$(read_ports_file "$file_port_exclude")
[ -z "$current_ports" ] && {
echo -e " ${yellow}Файл пуст${reset}"
return
}
if [ -z "$ports_to_del" ]; then
> "$file_port_exclude"
echo -e " ${green}Все исключения удалены${reset}"
return
fi
new_ports="$current_ports"
for port in $(echo "$ports_to_del" | tr ',' '\n'); do
new_ports=$(echo "$new_ports" | tr ',' '\n' | grep -vFx "$port" | tr '\n' ',' | sed 's/,$//')
done
write_ports_file "$file_port_exclude" "$new_ports"
echo -e " ${green}Порты исключения удалены${reset}"
}
# Получить список портов проксирования
get_ports_donor() {
ports=$(read_ports_file "$file_port_proxying")
if [ -z "$ports" ]; then
echo -e " Прокси-клиент работает ${yellow}на всех портах${reset}"
else
echo "$ports" | tr ',' '\n' | sed 's/^/ /'
fi
}
# Получить список портов, исключённых из проксирования
get_ports_exclude() {
ports=$(read_ports_file "$file_port_exclude")
if [ -z "$ports" ]; then
echo -e " Нет портов исключённых из проксирования"
else
echo "$ports" | tr ',' '\n' | sed 's/^/ /'
fi
}
migrate_ports_from_initd() {
legacy_initd=""
for f in "/opt/etc/init.d/S99xkeen" "/opt/etc/init.d/S24xray"; do
[ -f "$f" ] && { legacy_initd="$f"; break; }
done
[ -n "$legacy_initd" ] || return
# Читаем старые значения
port_donor_val=$(
awk -F= '/^port_donor=/{print $2; exit}' "$legacy_initd" | tr -d '"'
)
port_exclude_val=$(
awk -F= '/^port_exclude=/{print $2; exit}' "$legacy_initd" | tr -d '"'
)
port_donor_val=$(normalize_ports "$port_donor_val")
port_exclude_val=$(normalize_ports "$port_exclude_val")
migrated=0
# Миграция port_donor
if [ -n "$port_donor_val" ]; then
current_proxy=$(normalize_ports "$(read_ports_file "$file_port_proxying")")
combined=$(normalize_ports "$current_proxy,$port_donor_val")
if [ "$combined" != "$current_proxy" ]; then
tmpfile=$(mktemp)
echo "# XKeen port proxying list (migrated)" > "$tmpfile"
echo "$combined" | tr ',' '\n' >> "$tmpfile"
mv "$tmpfile" "$file_port_proxying"
fi
fi
# Миграция port_exclude
if [ -n "$port_exclude_val" ]; then
current_exclude=$(normalize_ports "$(read_ports_file "$file_port_exclude")")
combined=$(normalize_ports "$current_exclude,$port_exclude_val")
if [ "$combined" != "$current_exclude" ]; then
tmpfile=$(mktemp)
echo "# XKeen port exclude list (migrated)" > "$tmpfile"
echo "$combined" | tr ',' '\n' >> "$tmpfile"
mv "$tmpfile" "$file_port_exclude"
fi
fi
}
================================================
FILE: scripts/_xkeen/04_tools/02_tools_modules.sh
================================================
show_deprecation_warning() {
echo -e " ${red}Внимание!${reset} Команда устарела и удалена из XKeen"
echo -e " Компонент '${yellow}Модули ядра подсистемы Netfilter${reset}' обязателен"
echo
}
migration_modules() {
show_deprecation_warning && return
}
remove_modules() {
show_deprecation_warning && return
}
================================================
FILE: scripts/_xkeen/04_tools/03_tools_diagnostic.sh
================================================
diagnostic() {
# Установка пути к файлу diagnostic
diagnostic="/opt/diagnostic.txt"
if pidof "xray" >/dev/null; then
name_client=xray
elif pidof "mihomo" >/dev/null; then
name_client=mihomo
else
echo
echo -e " Диагностика возможна только при работающем ${yellow}XKeen${reset}"
echo -e " Запустите ${yellow}XKeen${reset} командой '${green}xkeen -start${reset}'"
exit 1
fi
ip4_supported=$(ip -4 addr show | grep -q "inet " && echo true || echo false)
ip6_supported=$(ip -6 addr show | grep -q "inet6 fe80::" && echo true || echo false)
iptables_supported=$([ "$ip4_supported" = "true" ] && command -v iptables >/dev/null 2>&1 && echo true || echo false)
ip6tables_supported=$([ "$ip6_supported" = "true" ] && command -v ip6tables >/dev/null 2>&1 && echo true || echo false)
echo
echo " Выполняется диагностика. Пожалуйста, подождите..."
# Очищаем файл diagnostic перед записью новых данных
> "$diagnostic"
chmod 600 "$diagnostic"
# Функция записи заголовка
write_header() {
echo "-------------------------" >> "$diagnostic"
echo -e "$1" >> "$diagnostic"
echo "-------------------------" >> "$diagnostic"
echo >> "$diagnostic"
}
# Функция логирования блоков
log_block() {
write_header "$1"
cat >> "$diagnostic"
echo >> "$diagnostic"; echo >> "$diagnostic"
}
# Функция маскировки чувствительных данных в конфигах Xray
mask_xray_sensitive_data() {
sed -E \
-e 's/("(id|uuid|password|user|pass|auth|secretKey|preSharedKey)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g' \
-e 's/("(address|host|serverName|sni|path|token|spiderX)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g' \
-e 's/("(publicKey|privateKey|shortId|mldsa65Verify|encryption)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g'
}
# Функция маскировки чувствительных данных в конфигах Mihomo
mask_mihomo_sensitive_data() {
sed -E \
-e 's/^([[:space:]]*(- )?(password|username|uuid|pre-shared-key|private-key|private-key-passphrase):).*/\1 ***MASKED***/i' \
-e 's/^([[:space:]]*(- )?(server|servername|sni|host|query-server-name|external-controller):).*/\1 ***MASKED***/i' \
-e 's/^([[:space:]]*(- )?(url|path|certificate|config|public-key|short-id|client-id|auth-str):).*/\1 ***MASKED***/i' \
-e 's/^([[:space:]]*(- )?(obfs-password|encryption|token|secret|psk):).*/\1 ***MASKED***/i'
}
# Функция логирования файлов
log_file() {
local file="$1"
local title="$2"
if [ -f "$file" ]; then
cat "$file" | log_block "$title"
else
echo "Файл $file не найден" | log_block "$title"
fi
}
# Функция дампа iptables/ip6tables
dump_tables() {
local cmd="$1"
local ver="$2"
for chain in PREROUTING xkeen xkeen_out OUTPUT; do
$cmd -w -t nat -nvL "$chain" 2>&1 | log_block "Результат таблицы NAT цепи $chain $ver"
$cmd -w -t mangle -nvL "$chain" 2>&1 | log_block "Результат таблицы MANGLE цепи $chain $ver"
done
$cmd -w -t nat -nvL "_NDM_HOTSPOT_DNSREDIR" 2>&1 | log_block "Результат таблицы NAT цепи _NDM_HOTSPOT_DNSREDIR $ver"
}
# Сбор данных
write_header "XKeen работает на ядре ${name_client}\nи установлен ${entware_storage}"
{
echo "Поддержка IPv4 - $ip4_supported"
echo "Поддержка IPv6 - $ip6_supported"
echo
echo "Поддержка iptables - $iptables_supported"
echo "Поддержка ip6tables - $ip6tables_supported"
} | log_block "Доступность IPv4 и IPv6"
[ "$iptables_supported" = "true" ] && dump_tables "iptables" "IPv4"
[ "$ip6tables_supported" = "true" ] && dump_tables "ip6tables" "IPv6"
if command -v ipset >/dev/null 2>&1; then
sets=$(ipset list -n 2>/dev/null | grep -v '^_NDM_' | grep -v '^_UPNP')
if [ -n "$sets" ]; then
echo "$sets" | {
total=0
while read -r set; do
count=$(ipset save "$set" 2>/dev/null | grep -c '^add')
printf "%-30s %s\n" "$set" "$count"
total=$((total + count))
done
echo
echo "Всего записей во всех списках: $total"
} | log_block "Списки ipset и количество записей в каждом"
fi
fi
log_file "/opt/etc/ndm/netfilter.d/proxy.sh" "Содержимое файла /opt/etc/ndm/netfilter.d/proxy.sh"
curl -kfsS "localhost:79/rci/ip/http/ssl" | jq -r '.port' | log_block "Проверка использования SSL порта"
curl -kfsS "localhost:79/rci/show/ip/policy" | jq -r '.[] | select(.description | ascii_downcase == "xkeen")' | log_block "Данные о политике доступа"
ip rule show | log_block "Результат команды ip rule show"
ip route show table main | log_block "Результат команды ip route show table main"
curl -kfsS "localhost:79/rci/show/version" | jq -r '.title, .model, .region' | log_block "Данные из localhost:79/rci/show/version"
{
if [ "${name_client}" = "xray" ]; then xray version; else mihomo -v; fi
echo
echo "Открыто файловых дескрипторов:"
ls "/proc/$(pidof ${name_client})/fd" | wc -l
echo "Лимит файловых дескрипторов:"
grep 'Max open files' "/proc/$(pidof ${name_client})/limits" | awk '{print $4}'
} | log_block "Версия $name_client и файловые дескрипторы"
echo "Версия XKeen $xkeen_current_version $xkeen_build (время сборки: $build_timestamp)" | log_block "Версия XKeen"
[ -f "$xkeen_config" ] && log_file "$xkeen_config" "Файл xkeen.json"
if [ "${name_client}" = "xray" ] && [ -d "$xray_conf_dir" ]; then
ls -p "$xray_conf_dir" | log_block "Содержимое директории configs"
for conf in dns inbounds routing outbounds; do
file=$(ls "$xray_conf_dir"/*${conf}*.json 2>/dev/null | head -n 1)
if [ -n "$file" ]; then
write_header "Содержимое файла $file"
mask_xray_sensitive_data < "$file" >> "$diagnostic"
echo >> "$diagnostic"; echo >> "$diagnostic"
fi
done
fi
if [ "${name_client}" = "mihomo" ]; then
for conf_file in "$mihomo_conf_dir/config.yaml" "$mihomo_conf_dir/config.yml"; do
if [ -f "$conf_file" ]; then
write_header "Содержимое файла $conf_file"
mask_mihomo_sensitive_data < "$conf_file" >> "$diagnostic"
echo >> "$diagnostic"; echo >> "$diagnostic"
fi
done
fi
echo
echo -e " Диагностика ${green}выполнена${reset}"
echo -e " Отправьте файл '${yellow}$diagnostic${reset}' в телеграм-чат ${yellow}XKeen${reset}, подробно описав возникшую проблему"
echo
echo -e " ${red}Примечание${reset}: Диагностика не проверяет доступ к прокси-серверу, правильность заполнения конфигов"
echo -e " и настройки роутера/сервера. Она проверяет ${green}только${reset} корректность инициализации ${yellow}XKeen${reset} в роутере"
}
================================================
FILE: scripts/_xkeen/04_tools/04_tools_delay.sh
================================================
get_current_delay() {
awk -F= '/^[[:space:]]*start_delay=/{print $2; exit}' "$1" | tr -d '[:space:]"'
}
delay_autostart() {
new_delay="$1"
if [ ! -f "$initd_file" ]; then
echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}"
return 1
fi
current_delay=$(get_current_delay "$initd_file")
if [ -z "$new_delay" ]; then
echo -e " Текущая задержка автозапуска XKeen ${yellow}${current_delay} секунд(ы)${reset}"
return 0
fi
case "$new_delay" in
''|*[!0-9]*)
echo -e " ${red}Ошибка${reset}"
echo " Новая задержка должна быть числом"
return 1
;;
esac
if [ "$current_delay" = "$new_delay" ]; then
echo " Обновление задержки автозапуска XKeen не требуется"
return 0
fi
tmpfile=$(mktemp) || return 1
awk -v d="$new_delay" '
/^[[:space:]]*start_delay=/ && !done {
sub(/=.*/, "=" d)
done=1
}
{print}
' "$initd_file" > "$tmpfile" && mv "$tmpfile" "$initd_file"
if [ "$(get_current_delay "$initd_file")" = "$new_delay" ]; then
echo -e " Установлена задержка автозапуска XKeen ${yellow}${new_delay} секунд(ы)${reset}"
else
echo -e " ${red}Ошибка${reset}: не удалось обновить параметр"
return 1
fi
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/00_choice_import.sh
================================================
# Импорт модулей выбора пользователя
# Модули выбора
. "$xtools_dir/05_tools_choice/01_choice_cores.sh"
. "$xtools_dir/05_tools_choice/02_choice_xkeen.sh"
. "$xtools_dir/05_tools_choice/03_choice_geofile.sh"
. "$xtools_dir/05_tools_choice/04_choice_input.sh"
. "$xtools_dir/05_tools_choice/05_choice_cron/00_cron_import.sh"
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/01_choice_cores.sh
================================================
# Запрос на добавление ядер проксирования
choice_add_proxy_cores() {
while true; do
echo
echo -e " Выберите ${yellow}ядро проксирования${reset} для загрузки и установки:"
echo
echo " 1. Xray"
echo " 2. Mihomo"
echo " 3. Xray + Mihomo"
echo
echo " 0. Пропустить загрузку ядра проксирования, если оно уже установлено"
echo
valid_input=true
add_xray=false
add_mihomo=false
while true; do
read -r -p " Ваш выбор: " proxy_choice
proxy_choice=$(echo "$proxy_choice" | sed 's/,/, /g')
if echo "$proxy_choice" | grep -qE '^[0-3]$'; then
break
else
echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов"
fi
done
case "$proxy_choice" in
1)
add_xray=true
;;
2)
add_mihomo=true
;;
3)
add_xray=true
add_mihomo=true
;;
0)
add_xray=false
add_mihomo=false
;;
*)
echo -e " ${red}Некорректный ввод${reset}"
valid_input=false
;;
esac
[ "$valid_input" = "true" ] && break
done
}
# Смена ядра проксирования на Xray
choice_xray_core() {
command -v xray >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: Ядро Xray не установлено. Выполните установку командой ${yellow}xkeen -ux${reset}"; exit 1; }
if [ -f "$initd_file" ]; then
if grep -q 'name_client="xray"' $initd_file; then
echo -e " Смена ядра ${red}не выполнена${reset}. Устройство уже работает на ядре ${yellow}Xray${reset}"
elif grep -q 'name_client="mihomo"' $initd_file; then
if pidof "mihomo" >/dev/null; then
$initd_file stop
fi
sed -i 's/name_client="mihomo"/name_client="xray"/' $initd_file
add_chmod_init
echo -e " ${green}Выполнена${reset} смена ядра на ${yellow}Xray${reset}"
echo -e " Настройте конфигурацию по пути '${yellow}$xray_conf_dir/${reset}'"
echo -e " И запустите проксирование командой ${yellow}xkeen -start${reset}"
else
echo -e " Произошла ${red}ошибка${reset} при смене ядра проксирования"
fi
else
echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}"
return 1
fi
}
# Смена ядра проксирования на Mihomo
choice_mihomo_core() {
command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: Ядро Mihomo не установлено. Выполните установку командой ${yellow}xkeen -um${reset}"; exit 1; }
command -v yq >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: не установлен парсер конфигурационных файлов Mihomo - ${yellow}Yq${reset}"; exit 1; }
if [ -f "$initd_file" ]; then
if grep -q 'name_client="mihomo"' $initd_file; then
echo -e " Смена ядра ${red}не выполнена${reset}. Устройство уже работает на ядре ${yellow}Mihomo${reset}"
elif [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ] && grep -q 'name_client="xray"' $initd_file; then
if pidof "xray" >/dev/null; then
$initd_file stop
fi
sed -i 's/name_client="xray"/name_client="mihomo"/' $initd_file
add_chmod_init
echo -e " ${green}Выполнена${reset} смена ядра на ${yellow}Mihomo${reset}"
echo -e " Настройте конфигурацию по пути '${yellow}$mihomo_conf_dir/${reset}'"
echo -e " И запустите проксирование командой ${yellow}xkeen -start${reset}"
else
echo -e " Произошла ${red}ошибка${reset} при смене ядра проксирования"
fi
else
echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}"
return 1
fi
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/02_choice_xkeen.sh
================================================
# Запрос на смену канала обновлений XKeen (Stable/Dev)
choice_channel_xkeen() {
echo
echo -e " Текущий канал обновлений ${yellow}XKeen${reset}:"
if [ "$xkeen_build" = "Stable" ]; then
echo -e " Стабильная версия (${green}Stable${reset})"
echo
echo " 1. Переключиться на канал разработки"
echo " 0. Остаться на стабильной версии"
else
echo -e " Версия в разработке (${green}$xkeen_build${reset})"
echo
echo " 1. Переключиться на стабильную версию"
echo " 0. Остаться на версии разработки"
fi
echo
while true; do
read -r -p " Ваш выбор: " choice
if echo "$choice" | grep -qE '^[0-1]$'; then
case "$choice" in
1)
if [ "$xkeen_build" = "Stable" ]; then
choice_build="Dev"
else
choice_build="Stable"
fi
return 0
;;
0)
echo " Остаёмся на текущей ветке XKeen"
return 0
;;
esac
else
echo -e " ${red}Некорректный ввод${reset}"
fi
done
}
change_channel_xkeen() {
echo
if [ "$choice_build" = "Stable" ]; then
sed -i 's/^xkeen_build="[^"]*"/xkeen_build="Stable"/' "$xkeen_var_file"
if grep -q '^xkeen_build="Stable"$' "$xkeen_var_file"; then
echo -e " Канал получения обновлений ${yellow}XKeen${reset} переключен на ${green}стабильную ветку${reset}"
else
echo -e " ${red}Возникла ошибка${reset} при переключении канала обновлений"
unset choice_build
fi
elif [ "$choice_build" = "Dev" ]; then
sed -i 's/xkeen_build="Stable"/xkeen_build="Dev"/' $xkeen_var_file
if grep -q '^xkeen_build="Dev"$' "$xkeen_var_file"; then
echo -e " Канал получения обновлений ${yellow}XKeen${reset} переключен на ${green}ветку разработки${reset}"
else
echo -e " ${red}Возникла ошибка${reset} при переключении канала обновлений"
unset choice_build
fi
fi
if [ -n "$choice_build" ]; then
echo
echo -e " Командой ${green}xkeen -uk${reset} вы можете обновить ${yellow}XKeen${reset} до последней версии в выбраной ветке"
fi
}
change_ipv6_support() {
ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && ip6_supported="true" || ip6_supported="false"
if [ "$1" = "on" ]; then
[ "$ip6_supported" = "true" ] && return 0
desired_state="on"
elif [ "$1" = "off" ]; then
[ "$ip6_supported" = "false" ] && return 0
desired_state="off"
else
echo
echo -e " Текущее состояние IPv6 в ${yellow}KeeneticOS${reset}:"
if [ "$ip6_supported" = "true" ]; then
echo -e " IPv6 ${green}включён${reset}"
echo
echo " 1. Отключить IPv6"
echo " 0. Оставить без изменений"
desired_state="off"
else
echo -e " IPv6 ${green}отключён${reset}"
echo
echo " 1. Включить IPv6"
echo " 0. Оставить без изменений"
desired_state="on"
fi
echo
while true; do
read -r -p " Ваш выбор: " choice
if echo "$choice" | grep -qE '^[0-1]$'; then
case "$choice" in
0) return 0 ;;
1) break ;;
esac
else
echo -e " ${red}Некорректный ввод${reset}"
fi
done
fi
if [ -f "$initd_file" ]; then
sed -i "s/ipv6_support=\"[a-z]*\"/ipv6_support=\"$desired_state\"/" "$initd_file"
if [ "$desired_state" = "off" ]; then
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1
for dir in /proc/sys/net/ipv6/conf/*; do
[ -d "$dir" ] || continue
iface="${dir##*/}"
case "$iface" in
all|ezcfg0|t2s*) continue ;;
*) [ -f "$dir/disable_ipv6" ] && echo "1" > "$dir/disable_ipv6" 2>/dev/null ;;
esac
done
else
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1
fi
# Перезапуск прокси-клиента, если запущен
if pidof xray >/dev/null || pidof mihomo >/dev/null; then
echo -e " ${yellow}Выполняется${reset}. Пожалуйста, подождите..."
"$initd_file" restart on >/dev/null 2>&1
fi
# Проверка и вывод результата
if [ "$desired_state" = "off" ]; then
if ! ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::"; then
echo -e " Поддержка IPv6 в KeeneticOS ${green}отключена${reset}"
echo -e " ${red}Дополнительно убедитесь, что IPv6 отключен в веб-интерфейсе роутера${reset}"
else
echo -e " ${red}Ошибка${reset} при выключении IPv6"
fi
else
if [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)" -eq 0 ]; then
echo -e " Поддержка IPv6 в KeeneticOS ${green}включена${reset}"
else
echo -e " ${red}Ошибка${reset} при включении IPv6"
fi
fi
else
echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}"
return 1
fi
}
choice_backup_xkeen() {
[ -f "$initd_file" ] || return 1
backup_value=$(awk -F= '/^[[:space:]]*backup[[:space:]]*=/ { gsub(/"| /,"",$2); print tolower($2); exit }' "$initd_file")
[ "$backup_value" = "off" ]
}
choice_autostart_xkeen() {
if [ -f "$initd_file" ] && grep -q 'start_auto="off"' "$initd_file"; then
return 1
fi
if choice_menu \
"Добавить ${yellow}XKeen${reset} в автозагрузку при включении роутера?" \
"Да" \
"Нет"; then
echo -e " Автозагрузка XKeen ${green}включена${reset}"
return 0
else
bypass_autostart_msg="yes"
change_autostart_xkeen
unset bypass_autostart_msg
return 0
fi
}
choice_redownload_xkeen() {
if choice_menu \
"Выберите вариант переустановки ${yellow}XKeen${reset}" \
"Загрузить дистрибутив XKeen из интернета" \
"Локальная переустановка XKeen"; then
redownload_xkeen="yes"
fi
}
choice_remove() {
if choice_menu \
"Вы действительно хотите ${red}удалить ${choice_for_remove}${reset}?" \
"Да, хочу удалить" \
"Нет, передумал(а)"; then
return 0
else
exit 0
fi
}
check_file_descriptors() {
pid=""
if pid=$(pidof xray | awk '{print $1}') && [ -n "$pid" ]; then
name_client="xray"
elif pid=$(pidof mihomo | awk '{print $1}') && [ -n "$pid" ]; then
name_client="mihomo"
else
echo -e "\n Команда работает только при работающем ${yellow}XKeen${reset}"
return 1
fi
fd_count=$(ls /proc/"$pid"/fd | wc -l)
maxfd=$(grep 'Max open files' "/proc/$pid/limits" | awk '{print $4}')
echo -e "\n Прокси-клиент ${light_blue}$name_client${reset} открыл файловых дескрипторов - ${green}$fd_count${reset}"
echo -e " Лимит файловых дескрипторов для вашего роутера - ${green}$maxfd${reset}"
echo -e "\n При высоких значениях открытых файловых дескрипторов,"
echo -e " можете включить их контроль командой ${yellow}xkeen -fd${reset}"
}
warn_proxy_dns() {
echo
echo -e " ${red}Внимание!${reset} Значение данного параметра без соответствующих настроек прокси-клиента ${green}игнорируется${reset}"
}
change_proxy_dns() {
toggle_param "proxy_dns" "перехвата DNS" "restart" "$1"
}
change_autostart_xkeen() {
toggle_param "start_auto" "автозапуска XKeen" "none" "$1"
}
change_file_descriptors() {
toggle_param "check_fd" "контроля файловых дескрипторов" "restart" "$1"
}
change_proxy_router() {
toggle_param "proxy_router" "проксирования трафика Entware" "restart" "$1"
}
change_extended_msg() {
toggle_param "extended_msg" "расширенных сообщений при запуcке XKeen" "none" "$1"
}
change_backup_xkeen() {
toggle_param "backup" "резервного копирования XKeen при обновлении" "none" "$1"
}
change_aghfix_xkeen() {
toggle_param "aghfix" "отображения клиентов XKeen под своими IP в журнале AaGuard Home" "restart" "$1"
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/03_choice_geofile.sh
================================================
choice_geodata() {
type="$1"
type_name="$2"
src3="$3"
src3_name="$4"
var_bypass="$5"
has_missing_bases=false
has_updatable_bases=false
for source in refilter v2fly "$src3"; do
var="update_${source}_${type}"
msg_var="update_${source}_${type}_msg"
if [ "$(eval echo \$$var)" = "false" ]; then
has_missing_bases=true
else
eval "$msg_var=true"
has_updatable_bases=true
fi
done
while true; do
eval "install_refilter_${type}=false"
eval "install_v2fly_${type}=false"
eval "install_${src3}_${type}=false"
eval "update_refilter_${type}=false"
eval "update_v2fly_${type}=false"
eval "update_${src3}_${type}=false"
eval "choice_delete_${type}_refilter_select=false"
eval "choice_delete_${type}_v2fly_select=false"
eval "choice_delete_${type}_${src3}_select=false"
invalid_choice=false
echo
echo -e " Выберите номер или номера действий через пробел для ${yellow}${type_name}${reset}"
echo
[ "$has_missing_bases" = true ] && echo " 1. Установить отсутствующие и обновить установленные ${type_name}" || echo -e " 1. ${italic}Все доступные ${type_name} установлены${reset}"
[ "$has_updatable_bases" = true ] && echo " 2. Обновить установленные ${type_name}" || echo -e " 2. ${italic}Нет доступных ${type_name} для обновления${reset}"
[ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && refilter_choice="Обновить" || refilter_choice="Установить"
[ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && v2fly_choice="Обновить" || v2fly_choice="Установить"
[ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && src3_choice="Обновить" || src3_choice="Установить"
echo " 3. $refilter_choice Re:filter"
echo " 4. $v2fly_choice v2fly"
echo " 5. $src3_choice ${src3_name}"
echo
echo " 0. Пропустить"
[ "$has_updatable_bases" = true ] && echo && echo " 6. Удалить установленные ${type_name}"
echo
valid_input=true
while true; do
read -r -p " Ваш выбор: " data_choices
data_choices=$(echo "$data_choices" | sed 's/,/, /g')
if echo "$data_choices" | grep -qE '^[0-6 ]+$'; then
break
else
echo -e " ${red}Некорректный ввод.${reset} Пожалуйста, выберите снова"
fi
done
for choice in $data_choices; do
case "$choice" in
1)
if [ "$has_missing_bases" = "false" ]; then
echo -e " Все ${type_name} ${green}уже установлены${reset}"
if input_concordance_list "Вы хотите обновить их?"; then
eval "update_refilter_${type}=true"
eval "update_v2fly_${type}=true"
eval "update_${src3}_${type}=true"
else
invalid_choice=true
fi
else
[ "$(eval echo \$update_refilter_${type}_msg)" != "true" ] && eval "install_refilter_${type}=true"
[ "$(eval echo \$update_v2fly_${type}_msg)" != "true" ] && eval "install_v2fly_${type}=true"
[ "$(eval echo \$update_${src3}_${type}_msg)" != "true" ] && eval "install_${src3}_${type}=true"
[ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && eval "update_refilter_${type}=true"
[ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && eval "update_v2fly_${type}=true"
[ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && eval "update_${src3}_${type}=true"
fi
;;
2)
if [ "$has_updatable_bases" = "false" ]; then
echo -e " ${red}Нет установленных ${type_name}${reset} для обновления"
if input_concordance_list "Вы хотите установить их?"; then
eval "install_refilter_${type}=true"
eval "install_v2fly_${type}=true"
eval "install_${src3}_${type}=true"
else
invalid_choice=true
fi
else
[ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && eval "update_refilter_${type}=true"
[ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && eval "update_v2fly_${type}=true"
[ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && eval "update_${src3}_${type}=true"
fi
;;
3)
[ "$(eval echo \$update_refilter_${type}_msg)" != "true" ] && eval "install_refilter_${type}=true" || eval "update_refilter_${type}=true"
;;
4)
[ "$(eval echo \$update_v2fly_${type}_msg)" != "true" ] && eval "install_v2fly_${type}=true" || eval "update_v2fly_${type}=true"
;;
5)
[ "$(eval echo \$update_${src3}_${type}_msg)" != "true" ] && eval "install_${src3}_${type}=true" || eval "update_${src3}_${type}=true"
;;
6)
if [ "$has_updatable_bases" = "false" ]; then
echo -e " ${red}Нет установленных ${type_name} для удаления${reset}. Выберите другой пункт"
invalid_choice=true
else
eval "choice_delete_${type}_refilter_select=true"
eval "choice_delete_${type}_v2fly_select=true"
eval "choice_delete_${type}_${src3}_select=true"
fi
;;
0)
echo " Выполнен пропуск установки / обновления ${type_name}"
if [ "$has_updatable_bases" = "true" ]; then
eval "$var_bypass=false"
else
eval "$var_bypass=true"
fi
return
;;
*)
echo -e " ${red}Некорректный ввод.${reset} Пожалуйста, выберите снова"
invalid_choice=true
;;
esac
done
[ "$invalid_choice" = true ] && continue
install_list=""
update_list=""
delete_list=""
[ "$(eval echo \$install_refilter_${type})" = "true" ] && install_list="$install_list ${yellow}Re:filter${reset},"
[ "$(eval echo \$install_v2fly_${type})" = "true" ] && install_list="$install_list ${yellow}v2fly${reset},"
[ "$(eval echo \$install_${src3}_${type})" = "true" ] && install_list="$install_list ${yellow}${src3_name}${reset},"
[ "$(eval echo \$update_refilter_${type})" = "true" ] && update_list="$update_list ${yellow}Re:filter${reset},"
[ "$(eval echo \$update_v2fly_${type})" = "true" ] && update_list="$update_list ${yellow}v2fly${reset},"
[ "$(eval echo \$update_${src3}_${type})" = "true" ] && update_list="$update_list ${yellow}${src3_name}${reset},"
[ "$(eval echo \$choice_delete_${type}_refilter_select)" = "true" ] && delete_list="$delete_list ${yellow}Re:filter${reset},"
[ "$(eval echo \$choice_delete_${type}_v2fly_select)" = "true" ] && delete_list="$delete_list ${yellow}v2fly${reset},"
[ "$(eval echo \$choice_delete_${type}_${src3}_select)" = "true" ] && delete_list="$delete_list ${yellow}${src3_name}${reset},"
if [ -n "$install_list" ]; then
echo -e " Устанавливаются следующие ${type_name}: ${install_list%,}"
fi
if [ -n "$update_list" ]; then
echo -e " Обновляются следующие ${type_name}: ${update_list%,}"
fi
if [ -n "$delete_list" ]; then
echo -e " Удаляются следующие ${type_name}: ${delete_list%,}"
fi
break
done
if [ -z "$install_list" ] && [ -z "$update_list" ] && [ -z "$delete_list" ]; then
eval "$var_bypass=true"
else
eval "$var_bypass=false"
fi
}
choice_geosite() {
choice_geodata "geosite" "GeoSite" "zkeen" "ZKeen" "bypass_cron_geosite"
}
choice_geoip() {
choice_geodata "geoip" "GeoIP" "zkeenip" "ZKeenIP" "bypass_cron_geoip"
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/04_choice_input.sh
================================================
# Функция для выбора пользователя между "Да" и "Нет" с номерами 0 и 1
input_concordance_list() {
prompt_message=" $1"
error_message=" ${yellow}Пожалуйста, выберите вариант, введя номер 0 (Нет) или 1 (Да)${reset}"
echo
echo -e "$prompt_message"
echo " 0. Нет"
echo " 1. Да"
while true; do
echo
read -r -p " Введите номер: " user_input
case "$user_input" in
0) return 1 ;;
1) return 0 ;;
*)
echo
echo -e " $error_message"
continue
;;
esac
done
}
toggle_param() {
param="$1"
description="$2"
restart_needed="$3"
force_state="$4"
echo
if [ ! -f "$initd_file" ]; then
echo -e " ${red}Ошибка${reset}: Не найден файл ${yellow}S05xkeen${reset}"
return 1
fi
current_state=$(grep -m 1 -E "^[[:space:]]*$param=" "$initd_file" | cut -d'=' -f2 | tr -d '"[:space:]')
if [ "$force_state" = "on" ] || [ "$force_state" = "off" ]; then
if [ "$current_state" = "$force_state" ]; then
if [ "$current_state" = "on" ]; then
echo -e " Состояние ${description} уже ${green}включено${reset}"
else
echo -e " Состояние ${description} уже ${red}отключено${reset}"
fi
[ "$apply" = "restart" ] && echo
return 0
fi
desired_state="$force_state"
elif [ "$bypass_autostart_msg" = "yes" ]; then
if [ "$current_state" = "on" ]; then
desired_state="off"
else
desired_state="on"
fi
else
echo -e " Текущее состояние ${description}:"
if [ "$current_state" = "on" ]; then
echo -e " ${green}Включено${reset}"
echo
echo " 1. Отключить"
echo " 0. Оставить без изменений"
desired_state="off"
else
echo -e " ${red}Отключено${reset}"
echo
echo " 1. Включить"
echo " 0. Оставить без изменений"
desired_state="on"
fi
echo
while true; do
read -r -p " Ваш выбор: " choice
case "$choice" in
0) return 0 ;;
1) break ;;
*) echo -e " ${red}Некорректный ввод${reset}" ;;
esac
done
fi
if awk -v param="$param" -v value="$desired_state" '
!found && $0 ~ "^[[:space:]]*" param "=" {
sub(/"[^"]*"/, "\"" value "\"")
found=1
}
{print}
' "$initd_file" > "$initd_file.tmp" && mv "$initd_file.tmp" "$initd_file"; then
[ "$bypass_autostart_msg" = "yes" ] && return 0
if [ "$desired_state" = "on" ]; then
echo -e " Новое состояние ${description} ${green}включено${reset}"
else
echo -e " Новое состояние ${description} ${red}отключено${reset}"
fi
if [ "$restart_needed" = "reboot" ]; then
echo
echo -e " ${yellow}Перезагрузите роутер для применения изменений${reset}"
elif [ "$restart_needed" = "restart" ] && [ "$apply" != "restart" ]; then
echo
echo -e " ${yellow}Перезапустите XKeen для применения изменений${reset}"
fi
add_chmod_init
else
echo
echo -e " ${red}Ошибка${reset} при изменении параметра $param"
return 1
fi
}
choice_menu() {
title="$1"
option_yes="$2"
option_no="$3"
echo
[ -n "$title" ] && echo -e " $title"
echo
echo " 1. $option_yes"
echo " 0. $option_no"
echo
while true; do
read -r -p " Ваш выбор: " choice
case "$choice" in
1) return 0 ;;
0) return 1 ;;
*) echo -e " ${red}Некорректный ввод${reset}" ;;
esac
done
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/00_cron_import.sh
================================================
# Импорт модулей вопросов cron
# Модули вопросов cron
. "$xtools_dir/05_tools_choice/05_choice_cron/01_cron_status.sh"
. "$xtools_dir/05_tools_choice/05_choice_cron/02_cron_time.sh"
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/01_cron_status.sh
================================================
# Определение статуса для задач cron
get_existing_cron_time() {
crontab -l 2>/dev/null | grep 'xkeen -ug' | head -n1 | awk '{print $1,$2,$3,$4,$5}'
}
format_cron_time() {
cron="$1"
minute=$(echo "$cron" | awk '{print $1}')
hour=$(echo "$cron" | awk '{print $2}')
dow=$(echo "$cron" | awk '{print $5}')
formatted_hour=$(printf "%02d" "$hour")
formatted_minute=$(printf "%02d" "$minute")
case "$dow" in
"*") day="Ежедневно" ;;
1) day="Понедельник" ;;
2) day="Вторник" ;;
3) day="Среда" ;;
4) day="Четверг" ;;
5) day="Пятница" ;;
6) day="Суббота" ;;
0) day="Воскресенье" ;;
*) day="Неизвестно" ;;
esac
echo "$day в $formatted_hour:$formatted_minute"
}
choice_update_cron() {
has_updatable_cron_tasks=false
[ "$info_update_geofile_cron" = "installed" ] && has_updatable_cron_tasks=true
existing_cron=$(get_existing_cron_time)
if [ -n "$existing_cron" ]; then
echo
echo -e " Время обновления ${yellow}геофайлов${reset} установлено на: ${green}$(format_cron_time "$existing_cron")${reset}"
fi
while true; do
choice_cancel_cron_select=false
choice_geofile_cron_select=false
choice_delete_all_cron_select=false
invalid_choice=false
echo
echo -e " Выберите номер действия для автообновления ${yellow}GeoFile/GeoIPSET${reset}"
echo
[ "$info_update_geofile_cron" != "installed" ] && geofile_choice="Включить" || geofile_choice="Обновить"
echo " 1. $geofile_choice задачу"
echo " 0. Пропустить"
[ "$has_updatable_cron_tasks" = true ] && echo && echo " 2. Выключить автообновление"
echo
while true; do
read -r -p " Ваш выбор: " update_choices
update_choices=$(echo "$update_choices" | sed 's/,/, /g')
if echo "$update_choices" | grep -qE '^[0-2]$'; then
break
else
echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов"
fi
done
for choice in $update_choices; do
case "$choice" in
1)
choice_geofile_cron_select=true
if [ "$info_update_geofile_cron" = "installed" ]; then
echo -e " ${yellow}Будет выполнено${reset} обновление задачи GeoFile/GeoIPSET"
else
echo -e " ${yellow}Будет выполнено${reset} включение задачи GeoFile/GeoIPSET"
fi
;;
0)
choice_cancel_cron_select=true
echo " Выполнен пропуск настройки автообновления"
return
;;
2)
if [ "$has_updatable_cron_tasks" = true ]; then
delete_cron_geofile
echo -e " Автообновление баз GeoFile/GeoIPSET ${green}выключено${reset}"
else
echo -e " ${red}Автообновление баз GeoFile/GeoIPSET не включено${reset}. Выберите другой пункт"
invalid_choice=true
fi
;;
*)
echo -e " ${red}Некорректный ввод${reset}"
invalid_choice=true
;;
esac
done
[ "$invalid_choice" = true ] || break
done
}
================================================
FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/02_cron_time.sh
================================================
# Определение времени для задач cron
choice_cron_time() {
[ "$choice_geofile_cron_select" = true ] || return
echo
echo -e " Время автоматического обновления ${yellow}геофайлов${reset}:"
echo
echo " Выберите день"
echo " 0. Отмена"
echo " 1. Понедельник"
echo " 2. Вторник"
echo " 3. Среда"
echo " 4. Четверг"
echo " 5. Пятница"
echo " 6. Суббота"
echo " 7. Воскресенье"
echo " 8. Ежедневно"
echo
while :; do
read -r -p " Ваш выбор: " day_choice
echo "$day_choice" | grep -qE '^[0-8]$' && break
echo -e " ${red}Некорректный номер действия.${reset} Пожалуйста, выберите снова"
done
[ "$day_choice" -eq 0 ] && {
echo -e " Включение автоматического обновления ${yellow}геофайлов${reset} отменено."
return
}
echo
while :; do
read -r -p " Выберите час (0-23): " hour
case "$hour" in
''|*[!0-9]*) ;;
*) [ "$hour" -ge 0 ] && [ "$hour" -le 23 ] && break ;;
esac
echo -e " ${red}Некорректный час.${reset} Пожалуйста, попробуйте снова"
done
while :; do
read -r -p " Выберите минуту (0-59): " minute
case "$minute" in
''|*[!0-9]*) ;;
*) [ "$minute" -ge 0 ] && [ "$minute" -le 59 ] && break ;;
esac
echo -e " ${red}Некорректные минуты.${reset} Пожалуйста, попробуйте снова"
done
if [ "$day_choice" -eq 8 ]; then
cron_expression="$minute $hour * * *"
day_name="Ежедневно"
else
case "$day_choice" in
1) dow=1; day_name="Понедельник" ;;
2) dow=2; day_name="Вторник" ;;
3) dow=3; day_name="Среда" ;;
4) dow=4; day_name="Четверг" ;;
5) dow=5; day_name="Пятница" ;;
6) dow=6; day_name="Суббота" ;;
7) dow=0; day_name="Воскресенье" ;;
esac
cron_expression="$minute $hour * * $dow"
fi
formatted_hour=$(printf "%02d" "$hour")
formatted_minute=$(printf "%02d" "$minute")
echo
echo -e " Выбранное время обновления ${yellow}геофайлов${reset}: $day_name в $formatted_hour:$formatted_minute"
choice_geofile_cron_time="$cron_expression"
}
================================================
FILE: scripts/_xkeen/04_tools/06_tools_backups/00_backups_import.sh
================================================
# Импорт модулей резервного копирования
# Модули резервного копирования
. "$xtools_dir/06_tools_backups/01_backups_xkeen.sh"
. "$xtools_dir/06_tools_backups/02_backups_configs_xray.sh"
. "$xtools_dir/06_tools_backups/02_backups_configs_mihomo.sh"
================================================
FILE: scripts/_xkeen/04_tools/06_tools_backups/01_backups_xkeen.sh
================================================
# Создание резервной копии XKeen
backup_xkeen() {
if choice_backup_xkeen && [ -z "$manual_backup" ]; then
return 0
fi
backup_filename="${current_datetime}_xkeen_v${xkeen_current_version}"
backup_dir="${backups_dir}/${backup_filename}"
mkdir -p "$backup_dir"
# Копирование файлов. Проверяем успех всей операции копирования
if cp -r "$install_dir/.xkeen" "$install_dir/xkeen" "$backup_dir/"; then
# Переименование скрытой директории для удобства хранения
mv "$backup_dir/.xkeen" "$backup_dir/_xkeen"
echo -e " Резервная копия XKeen создана: ${yellow}${backup_filename}${reset}"
else
echo -e " ${red}Ошибка${reset} при создании резервной копии XKeen"
fi
}
# Восстановление XKeen из резервной копии
restore_backup_xkeen() {
latest_backup_dir=""
for entry in "$backups_dir"/*xkeen*; do
if [ -d "$entry" ]; then
latest_backup_dir="$entry"
fi
done
if [ -n "$latest_backup_dir" ]; then
# Используем временную директорию для безопасности при восстановлении
# Чтобы не удалить старый .xkeen, пока не убедимся, что копия цела
if cp -r "$latest_backup_dir/_xkeen" "$install_dir/" && \
cp -f "$latest_backup_dir/xkeen" "$install_dir/"; then
rm -rf "${install_dir:?}/.xkeen"
mv "$install_dir/_xkeen" "$install_dir/.xkeen"
echo -e " XKeen ${green}успешно восстановлен${reset} из: $(basename "$latest_backup_dir")"
else
echo -e " ${red}Ошибка:${reset} Не удалось скопировать файлы из резервной копии"
fi
else
echo -e " ${red}Ошибка:${reset} Подходящая резервная копия XKeen не найдена"
fi
}
================================================
FILE: scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_mihomo.sh
================================================
backup_configs_mihomo() {
backup_filename="${current_datetime}_configs_mihomo"
backup_configs_dir="$backups_dir/$backup_filename"
mkdir -p "$backup_configs_dir"
if cp -r "$mihomo_conf_dir"/* "$backup_configs_dir/"; then
echo -e " Резервная копия конфигурации Mihomo создана: ${yellow}$backup_filename${reset}"
else
echo -e " ${red}Ошибка${reset} при создании резервной копии конфигураций Mihomo"
fi
}
restore_backup_configs_mihomo() {
latest_backup=""
for entry in "$backups_dir"/*_configs_mihomo; do
if [ -e "$entry" ]; then
latest_backup="$entry"
fi
done
if [ -n "$latest_backup" ]; then
rm -rf "${mihomo_conf_dir:?}"/*
if cp -r "$latest_backup"/* "$mihomo_conf_dir/"; then
echo -e " Конфигурация Mihomo ${green}успешно восстановлена${reset} из: $(basename "$latest_backup")"
else
echo -e " ${red}Ошибка${reset} при восстановлении файлов"
fi
else
echo -e " ${red}Ошибка:${reset} Резервные копии не найдены в $backups_dir"
fi
}
================================================
FILE: scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_xray.sh
================================================
backup_configs_xray() {
backup_filename="${current_datetime}_configs_xray"
backup_configs_dir="$backups_dir/$backup_filename"
mkdir -p "$backup_configs_dir"
if cp -r "$xray_conf_dir"/* "$backup_configs_dir/"; then
echo -e " Резервная копия конфигурации Xray создана: ${yellow}$backup_filename${reset}"
else
echo -e " ${red}Ошибка${reset} при создании резервной копии конфигураций Xray"
fi
}
restore_backup_configs_xray() {
latest_backup=""
for entry in "$backups_dir"/*_configs_xray; do
if [ -e "$entry" ]; then
latest_backup="$entry"
fi
done
if [ -n "$latest_backup" ]; then
rm -rf "${xray_conf_dir:?}"/*
if cp -r "$latest_backup"/* "$xray_conf_dir/"; then
echo -e " Конфигурация Xray ${green}успешно восстановлена${reset} из: $(basename "$latest_backup")"
else
echo -e " ${red}Ошибка${reset} при восстановлении файлов"
fi
else
echo -e " ${red}Ошибка:${reset} Резервные копии не найдены в $backups_dir"
fi
}
================================================
FILE: scripts/_xkeen/04_tools/07_tools_downloaders/00_downloaders_import.sh
================================================
# Импорт модулей загрузки
# fetch_with_mirrors / probe_with_mirrors уже подгружены из xkeen/import.sh
# (раньше install-модуля, который тоже их вызывает).
# Модули загрузки
. "$xtools_dir/07_tools_downloaders/01_downloaders_xray.sh"
. "$xtools_dir/07_tools_downloaders/01_downloaders_mihomo.sh"
. "$xtools_dir/07_tools_downloaders/02_donwloaders_xkeen.sh"
================================================
FILE: scripts/_xkeen/04_tools/07_tools_downloaders/00_fetch_with_mirrors.sh
================================================
# Загрузка с per-call mirror-fallback'ом.
#
# Заменяет паттерн "test_github -> один gh_proxy на сессию -> один curl
# без fallback'а". Старый flow ломается когда выбранный mirror транзиентно
# падает между test_github и фактической загрузкой: curl получает 5xx или
# таймаут, caller тихо return 1, geo-файлы устаревают.
#
# fetch_with_mirrors пробует префиксы по очереди (gh_proxy_user
# exclusive, иначе direct + gh_proxy1 + gh_proxy2), кэширует удачный
# выбор на TTL_SEC, валидирует ответ (HTTP-код + min-size + HTML-stub
# detect). После неудачного вызова caller может прочитать причину из
# глобальных переменных _last_error / _last_size (для fetch) и
# _last_http (для probe), чтобы напечатать осмысленное сообщение.
_mirror_cache="/tmp/.xkeen_mirror_cache"
_mirror_ttl=60
_DIRECT_TOKEN="__direct__"
# Чтение закэшированного префикса. stdout = префикс ("" для direct),
# rc = 0 если кэш свежий, 1 если просрочен/garbage/отсутствует.
_mirror_cache_read() {
[ -r "$_mirror_cache" ] || return 1
_cache_ts=""
_cache_pfx=""
IFS=' ' read -r _cache_ts _cache_pfx < "$_mirror_cache" 2>/dev/null || return 1
case "$_cache_ts" in
''|*[!0-9]*) return 1 ;;
esac
_cache_now=$(date +%s 2>/dev/null) || return 1
[ $((_cache_now - _cache_ts)) -lt "$_mirror_ttl" ] || return 1
[ "$_cache_pfx" = "$_DIRECT_TOKEN" ] && _cache_pfx=""
printf '%s' "$_cache_pfx"
return 0
}
# Сохранение удачного префикса в кэш. $1 = "" для direct, иначе url.
_mirror_cache_write() {
_w_pfx="$1"
[ -z "$_w_pfx" ] && _w_pfx="$_DIRECT_TOKEN"
printf '%s %s\n' "$(date +%s)" "$_w_pfx" > "$_mirror_cache" 2>/dev/null
}
# Список префиксов для попыток, по одному на строку, в порядке приоритета.
# Используется token __direct__ для direct GitHub (пустая строка ломала
# бы heredoc-итерацию).
_mirror_order() {
if [ -n "$gh_proxy_user" ]; then
printf '%s\n' "${gh_proxy_user%/}"
return
fi
_order_cached_set=0
if _order_cached=$(_mirror_cache_read); then
_order_cached_set=1
printf '%s\n' "${_order_cached:-$_DIRECT_TOKEN}"
fi
# direct и дефолтные mirror'ы, пропуская тот что уже в кэше
if [ "$_order_cached_set" = "0" ] || [ -n "$_order_cached" ]; then
printf '%s\n' "$_DIRECT_TOKEN"
fi
if [ -n "$gh_proxy1" ] && [ "$_order_cached" != "${gh_proxy1%/}" ]; then
printf '%s\n' "${gh_proxy1%/}"
fi
if [ -n "$gh_proxy2" ] && [ "$_order_cached" != "${gh_proxy2%/}" ]; then
printf '%s\n' "${gh_proxy2%/}"
fi
}
# Дефолтный валидатор для скачанного файла.
# $1 = path, $2 = min_size (байт, 0 = без проверки размера).
# Сетит _last_error и _last_size для caller-сообщений.
#
# HTML-stub detect: cloudflare challenge, jsdelivr "429: Too Many
# Requests", proxy-error 404-page под HTTP 200. Маркеры якорные (^...)
# чтобы не словить false-positive на байтах в gzip/zip/ELF метадате.
_validate_default() {
_v_f="$1"
_v_min="${2:-0}"
_last_error=""
_last_size=0
if [ ! -s "$_v_f" ]; then
_last_error="curl_failed"
return 1
fi
_last_size=$(wc -c < "$_v_f" 2>/dev/null | tr -d ' ')
if [ "$_v_min" -gt 0 ]; then
[ -n "$_last_size" ] && [ "$_last_size" -ge "$_v_min" ] || {
_last_error="size"
return 1
}
fi
if head -c 100 "$_v_f" 2>/dev/null | grep -iqE '^( [min_size] [validator]
#
# Качает в через цепочку префиксов, валидирует.
# Атомарная замена: запись в "${dest}.tmp.$$" + mv.
#
# Возврат: 0 на успех, 1 на полный провал (все попытки failed/invalid).
# При rc != 0: _last_error содержит причину последней неудачи
# (curl_failed / size / html_stub), _last_size содержит размер файла
# при size-fail.
fetch_with_mirrors() {
_fwm_url="$1"
_fwm_dest="$2"
_fwm_min="${3:-0}"
_fwm_validator="${4:-_validate_default}"
_fwm_tmp="${_fwm_dest}.tmp.$$"
_fwm_winner=""
_last_error=""
_last_size=0
rm -f "$_fwm_tmp"
_fwm_orders=$(_mirror_order)
while IFS= read -r _fwm_prefix; do
[ "$_fwm_prefix" = "$_DIRECT_TOKEN" ] && _fwm_prefix=""
if [ -n "$_fwm_prefix" ]; then
_fwm_fetch="$_fwm_prefix/$_fwm_url"
else
_fwm_fetch="$_fwm_url"
fi
if eval curl $curl_extra --connect-timeout 10 $curl_timeout \
-fL -o "$_fwm_tmp" "$_fwm_fetch" >/dev/null 2>&1; then
if "$_fwm_validator" "$_fwm_tmp" "$_fwm_min"; then
_fwm_winner="$_fwm_prefix"
break
fi
else
_last_error="curl_failed"
fi
rm -f "$_fwm_tmp"
done <
#
# HEAD-probe (с fallback на range-byte для mirror'ов которые не разрешают
# HEAD и отдают 405). Используется в xray/mihomo downloader'ах для
# быстрой проверки "существует ли такая версия" перед полной загрузкой.
#
# Возврат: 0 на 2xx; 2 если все попытки получили 4xx (definitive miss,
# например пользователь ввёл неверную версию); 1 на прочие транзиентные
# ошибки. _last_http содержит HTTP-код последней значимой попытки (для
# error сообщений caller'а), _last_curl_rc содержит exit-код curl
# последней попытки (28 = таймаут, остальные см. man curl).
probe_with_mirrors() {
_pwm_url="$1"
_pwm_attempts=0
_pwm_fail_4xx=0
_last_http=""
_last_curl_rc=0
_pwm_orders=$(_mirror_order)
while IFS= read -r _pwm_prefix; do
[ "$_pwm_prefix" = "$_DIRECT_TOKEN" ] && _pwm_prefix=""
if [ -n "$_pwm_prefix" ]; then
_pwm_probe="$_pwm_prefix/$_pwm_url"
else
_pwm_probe="$_pwm_url"
fi
_pwm_attempts=$((_pwm_attempts + 1))
_pwm_code=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout \
-I -s -L -w '%{http_code}' -o /dev/null "$_pwm_probe" 2>/dev/null)
_last_curl_rc=$?
if [ "$_pwm_code" = "405" ]; then
_pwm_code=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout \
-s -L -r 0-0 -w '%{http_code}' -o /dev/null "$_pwm_probe" 2>/dev/null)
_last_curl_rc=$?
fi
_last_http="$_pwm_code"
case "$_pwm_code" in
2[0-9][0-9])
_mirror_cache_write "$_pwm_prefix"
return 0
;;
40[0-9])
_pwm_fail_4xx=$((_pwm_fail_4xx + 1))
;;
esac
done </dev/null | jq -r '.[] | select(.prerelease == false) | .tag_name' | head -n 8)
if [ -z "$RELEASE_TAGS" ]; then
echo
printf " ${red}Нет доступа${reset} к ${yellow}GitHub API${reset}. Пробуем ${yellow}jsDelivr${reset}...\n"
# Получаем список релизов через jsDelivr
RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$mihomo_jsd_url" 2>/dev/null | jq -r '.versions[]' | head -n 8)
if [ -z "$RELEASE_TAGS" ]; then
echo
printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n"
echo
printf " ${red}Ошибка${reset}: Не удалось получить список релизов ни через ${yellow}GitHub API${reset}, ни через ${yellow}jsDelivr${reset}\n Проверьте соединение с интернетом или повторите позже\n Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n"
echo
exit 1
fi
echo
printf " Список релизов получен с использованием ${yellow}jsDelivr${reset}:\n"
USE_JSDELIVR="true"
else
echo
printf " Список релизов получен с использованием ${yellow}GitHub API${reset}:\n"
fi
while true; do
echo
echo "$RELEASE_TAGS" | awk '{printf " %2d. %s\n", NR, $0}'
echo
echo " 9. Ручной ввод версии"
echo
echo " 0. Пропустить загрузку Mihomo"
printf "\n Введите порядковый номер релиза (0 - пропустить, 9 - ручной ввод): "
read -r choice
case "$choice" in
[0-9]) ;;
*)
printf " ${red}Некорректный${reset} ввод. Пожалуйста, введите число\n"
sleep 1
continue
;;
esac
if [ "$choice" = "0" ]; then
bypass_mihomo="true"
printf " Загрузка Mihomo ${yellow}пропущена${reset}\n"
return
fi
if [ "$choice" = "9" ]; then
printf " Введите версию Mihomo для загрузки (например: v1.19.6): "
read -r version_selected
if [ -z "$version_selected" ]; then
printf " ${red}Ошибка${reset}: Версия не может быть пустой\n"
sleep 1
continue
fi
version_selected=$(echo "$version_selected" | sed 's/^v//')
version_selected="v$version_selected"
else
version_selected=$(echo "$RELEASE_TAGS" | awk -v line="$choice" 'NR == line {print $0; exit}')
if [ -z "$version_selected" ]; then
printf " Выбранный номер ${red}вне диапазона.${reset} Пожалуйста, попробуйте снова\n"
sleep 1
continue
fi
if [ "$USE_JSDELIVR" = "true" ]; then
version_selected="v$version_selected"
fi
fi
VERSION_ARG="$version_selected"
URL_BASE="${mihomo_gz_url}/$VERSION_ARG"
yq_download_base_url="$(get_yq_dist_url)"
case $architecture in
"arm64-v8a")
download_url="$URL_BASE/mihomo-linux-arm64-$VERSION_ARG.gz"
download_yq="$yq_download_base_url/yq_linux_arm64"
;;
"mips32le")
if [ "$softfloat" = "true" ]; then
download_url="$URL_BASE/mihomo-linux-mipsle-softfloat-$VERSION_ARG.gz"
else
download_url="$URL_BASE/mihomo-linux-mipsle-hardfloat-$VERSION_ARG.gz"
fi
download_yq="$yq_download_base_url/yq_linux_mipsle"
;;
"mips32")
download_url="$URL_BASE/mihomo-linux-mips-hardfloat-$VERSION_ARG.gz"
download_yq="$yq_download_base_url/yq_linux_mips"
;;
*)
download_url=
download_yq=
;;
esac
if [ -z "$download_url" ] || [ -z "$download_yq" ]; then
printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Mihomo\n"
exit 1
fi
filename=$(basename "$download_url")
extension="${filename##*.}"
mkdir -p "$mtmp_dir"
yq_available="false"
printf " ${yellow}Проверка${reset} доступности версии $version_selected...\n"
probe_with_mirrors "$download_url"
_rc=$?
case "$_rc" in
0)
printf " Файл ${green}доступен${reset}\n"
;;
2)
case "$_last_http" in
403) printf " ${red}Доступ запрещен${reset} (403)\n" ;;
404) printf " Файл ${red}не найден${reset} (404)\n" ;;
*) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;;
esac
printf " ${red}Ошибка${reset}: Версия Mihomo $version_selected недоступна\n"
continue
;;
*)
if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT
printf " ${red}Таймаут${reset} при проверке\n"
elif [ "$_last_curl_rc" != "0" ]; then
printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc"
elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then
printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http"
else
printf " ${red}Нет соединения${reset}\n"
fi
printf " ${red}Ошибка${reset}: Версия Mihomo $version_selected недоступна\n"
continue
;;
esac
printf " ${yellow}Выполняется загрузка${reset} парсера конфигурационных файлов Mihomo - Yq\n"
if probe_with_mirrors "$download_yq"; then
if fetch_with_mirrors "$download_yq" "$install_dir/yq" 1024; then
chmod +x "$install_dir/yq"
yq_available="true"
printf " Yq ${green}успешно загружен и установлен${reset}\n"
else
printf " ${red}Ошибка${reset}: Не удалось загрузить Yq\n"
fi
else
printf " ${yellow}Предупреждение${reset}: Yq недоступен для загрузки, продолжение без него\n"
fi
printf " ${yellow}Выполняется загрузка${reset} выбранной версии Mihomo\n"
if [ "$yq_available" != "true" ] && [ -x "$install_dir/yq" ]; then
yq_available="true"
printf " ${yellow}Используется${reset} уже установленный Yq\n"
fi
if [ "$yq_available" != "true" ]; then
printf " ${red}Ошибка${reset}: Для работы Mihomo требуется Yq. Установка прервана\n"
return 1
fi
if ! fetch_with_mirrors "$download_url" "$mtmp_dir/mihomo.$extension" 1024; then
printf " ${red}Ошибка${reset}: Не удалось загрузить Mihomo $version_selected\n"
continue
fi
printf " Mihomo ${green}успешно загружен${reset}\n"
return 0
done
}
================================================
FILE: scripts/_xkeen/04_tools/07_tools_downloaders/01_downloaders_xray.sh
================================================
# Загрузка Xray
download_xray() {
USE_JSDELIVR=""
printf " ${green}Запрос информации${reset} о релизах ${yellow}Xray${reset}\n"
# Получаем список релизов через GitHub API
RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "${xray_api_url}?per_page=50" 2>/dev/null | jq -r '.[] | select(.prerelease == false) | .tag_name' | head -n 8)
if [ -z "$RELEASE_TAGS" ]; then
echo
printf " ${red}Нет доступа${reset} к ${yellow}GitHub API${reset}. Пробуем ${yellow}jsDelivr${reset}...\n"
# Получаем список релизов через jsDelivr
RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xray_jsd_url" 2>/dev/null | jq -r '.versions[]' | head -n 8)
if [ -z "$RELEASE_TAGS" ]; then
echo
printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n"
echo
printf " ${red}Ошибка${reset}: Не удалось получить список релизов ни через ${yellow}GitHub API${reset}, ни через ${yellow}jsDelivr${reset}\n Проверьте соединение с интернетом или повторите позже\n Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n"
echo
exit 1
fi
echo
printf " Список релизов получен с использованием ${yellow}jsDelivr${reset}:\n"
USE_JSDELIVR="true"
else
echo
printf " Список релизов получен с использованием ${yellow}GitHub API${reset}:\n"
fi
if [ "$autoinstall_mode" = "true" ]; then
version_selected=$(echo "$RELEASE_TAGS" | head -1)
[ "$USE_JSDELIVR" = "true" ] && version_selected="v$version_selected"
printf " ${green}Авто-режим${reset}: выбрана последняя версия ${yellow}%s${reset}\n" "$version_selected"
VERSION_ARG="$version_selected"
URL_BASE="${xray_zip_url}/$VERSION_ARG"
case $architecture in
"arm64-v8a") download_url="$URL_BASE/Xray-linux-arm64-v8a.zip" ;;
"mips32le") download_url="$URL_BASE/Xray-linux-mips32le.zip" ;;
"mips32") download_url="$URL_BASE/Xray-linux-mips32.zip" ;;
*) download_url= ;;
esac
if [ -z "$download_url" ]; then
printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Xray\n"
exit 1
fi
filename=$(basename "$download_url")
extension="${filename##*.}"
mkdir -p "$xtmp_dir"
printf " ${yellow}Проверка${reset} доступности версии %s...\n" "$version_selected"
probe_with_mirrors "$download_url"
_rc=$?
case "$_rc" in
0)
printf " Файл ${green}доступен${reset}\n"
;;
2)
case "$_last_http" in
403) printf " ${red}Доступ запрещен${reset} (403)\n" ;;
404) printf " Файл ${red}не найден${reset} (404)\n" ;;
*) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;;
esac
printf " ${red}Ошибка${reset}: Версия %s недоступна\n" "$version_selected"
exit 1
;;
*)
if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT
printf " ${red}Таймаут${reset} при проверке\n"
elif [ "$_last_curl_rc" != "0" ]; then
printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc"
elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then
printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http"
else
printf " ${red}Нет соединения${reset}\n"
fi
printf " ${red}Ошибка${reset}: Версия %s недоступна\n" "$version_selected"
exit 1
;;
esac
printf " ${yellow}Выполняется загрузка${reset} последней версии Xray\n"
if ! fetch_with_mirrors "$download_url" "$xtmp_dir/xray.$extension" 1024; then
printf " ${red}Ошибка${reset}: Не удалось загрузить Xray %s\n" "$version_selected"
exit 1
fi
printf " Xray ${green}успешно загружен${reset}\n"
return 0
fi
while true; do
echo
echo "$RELEASE_TAGS" | awk '{printf " %2d. %s\n", NR, $0}'
echo
echo " 9. Ручной ввод версии"
echo
echo " 0. Пропустить загрузку Xray"
printf "\n Введите порядковый номер релиза (0 - пропустить, 9 - ручной ввод): "
read -r choice
case "$choice" in
[0-9]) ;;
*)
printf " ${red}Некорректный${reset} ввод. Пожалуйста, введите число\n"
sleep 1
continue
;;
esac
if [ "$choice" = "0" ]; then
bypass_xray="true"
printf " Загрузка Xray ${yellow}пропущена${reset}\n"
return
fi
if [ "$choice" = "9" ]; then
printf " Введите версию Xray для загрузки (например: v25.4.30): "
read -r version_selected
if [ -z "$version_selected" ]; then
printf " ${red}Ошибка${reset}: Версия не может быть пустой\n"
sleep 1
continue
fi
version_selected=$(echo "$version_selected" | sed 's/^v//')
version_selected="v$version_selected"
else
version_selected=$(echo "$RELEASE_TAGS" | awk -v line="$choice" 'NR == line {print $0; exit}')
if [ -z "$version_selected" ]; then
printf " Выбранный номер ${red}вне диапазона.${reset} Пожалуйста, попробуйте снова\n"
sleep 1
continue
fi
if [ "$USE_JSDELIVR" = "true" ]; then
version_selected="v$version_selected"
fi
fi
VERSION_ARG="$version_selected"
URL_BASE="${xray_zip_url}/$VERSION_ARG"
case $architecture in
"arm64-v8a") download_url="$URL_BASE/Xray-linux-arm64-v8a.zip" ;;
"mips32le") download_url="$URL_BASE/Xray-linux-mips32le.zip" ;;
"mips32") download_url="$URL_BASE/Xray-linux-mips32.zip" ;;
*) download_url= ;;
esac
if [ -z "$download_url" ]; then
printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Xray\n"
exit 1
fi
filename=$(basename "$download_url")
extension="${filename##*.}"
mkdir -p "$xtmp_dir"
printf " ${yellow}Проверка${reset} доступности версии $version_selected...\n"
probe_with_mirrors "$download_url"
_rc=$?
case "$_rc" in
0)
printf " Файл ${green}доступен${reset}\n"
;;
2)
case "$_last_http" in
403) printf " ${red}Доступ запрещен${reset} (403)\n" ;;
404) printf " Файл ${red}не найден${reset} (404)\n" ;;
*) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;;
esac
printf " ${red}Ошибка${reset}: Версия $version_selected недоступна\n"
continue
;;
*)
if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT
printf " ${red}Таймаут${reset} при проверке\n"
elif [ "$_last_curl_rc" != "0" ]; then
printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc"
elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then
printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http"
else
printf " ${red}Нет соединения${reset}\n"
fi
printf " ${red}Ошибка${reset}: Версия $version_selected недоступна\n"
continue
;;
esac
printf " ${yellow}Выполняется загрузка${reset} выбранной версии Xray\n"
if ! fetch_with_mirrors "$download_url" "$xtmp_dir/xray.$extension" 1024; then
printf " ${red}Ошибка${reset}: Не удалось загрузить Xray $version_selected\n"
continue
fi
printf " Xray ${green}успешно загружен${reset}\n"
return 0
done
}
================================================
FILE: scripts/_xkeen/04_tools/07_tools_downloaders/02_donwloaders_xkeen.sh
================================================
# Загрузка XKeen
download_xkeen() {
mkdir -p "$ktmp_dir"
printf " ${yellow}Выполняется загрузка${reset} XKeen\n"
if ! fetch_with_mirrors "$xkeen_tar_url" "$ktmp_dir/xkeen.tar.gz" 1024; then
printf " ${red}Ошибка${reset}: Не удалось загрузить XKeen\n"
exit 1
fi
printf " XKeen ${green}успешно загружен${reset}\n"
return 0
}
download_xkeen_dev() {
xkeen_tar_url="$xkeen_dev_url"
download_xkeen
}
================================================
FILE: scripts/_xkeen/05_tests/00_tests_import.sh
================================================
# Импорт модулей тестирования
# Модули тестирования
. "$xtests_dir/01_tests_connected.sh"
. "$xtests_dir/02_tests_xports.sh"
. "$xtests_dir/03_tests_storage.sh"
================================================
FILE: scripts/_xkeen/05_tests/01_tests_connected.sh
================================================
# Функция проверки доступности интернета
test_connection() {
nslookup "$conn_URL" >/dev/null 2>&1 && return 0
curl -Is --connect-timeout 1 "$conn_URL" >/dev/null && return 0
ping -c 1 -W 1 "$conn_IP1" >/dev/null 2>&1 && return 0
ping -c 1 -W 1 "$conn_IP2" >/dev/null 2>&1 && return 0
printf " ${red}Отсутствует${reset} интернет-соединение\n"
exit 1
}
# Функция загрузки
download_with_check() {
url="$1"
output_file="$2"
min_size="${3:-50000}"
eval curl $curl_extra --connect-timeout 5 -m 15 -y 1000 -Y 5 -s -L "$url" -o "$output_file" 2>/dev/null
if [ -f "$output_file" ]; then
size=$(wc -c < "$output_file" 2>/dev/null || echo 0)
if [ "$size" -gt "$min_size" ]; then
return 0
fi
fi
rm -f "$output_file" 2>/dev/null
return 1
}
# Функция проверки доступности Entware
test_entware() {
printf " ${yellow}Проверка доступности${reset} репозитория Entware. Подождите, пожалуйста...\n"
repo_url=$(awk '/^src/ {print $3; exit}' /opt/etc/opkg.conf 2>/dev/null)
if [ -z "$repo_url" ]; then
printf " ${red}Не удалось${reset} определить используемый репозиторий Entware\n"
exit 1
fi
repo_url="$repo_url/Packages.gz"
tmp_file="/tmp/pkg_check_$$"
if download_with_check "$repo_url" "$tmp_file"; then
printf " Репозиторий Entware ${green}доступен${reset}. Продолжаем...\n"
opkg update >/dev/null 2>&1
info_packages
install_packages
rm -f "$tmp_file" 2>/dev/null
return 0
else
printf " Репозиторий Entware ${red}недоступен${reset}\n"
printf " Укажите рабочее зеркало репозитория в файле ${yellow}/opt/etc/opkg.conf${reset}\n"
exit 1
fi
}
# Функция определения пользовательского прокси для GitHub
get_user_proxy() {
gh_proxy_user=""
[ ! -f "$xkeen_config" ] && return 1
if command -v jq >/dev/null 2>&1; then
gh_proxy_user=$(jq -r '.xkeen.gh_proxy // empty' "$xkeen_config" 2>/dev/null)
fi
if [ -z "$gh_proxy_user" ]; then
gh_proxy_user=$(sed -n 's/.*"gh_proxy": *"\([^"]*\)".*/\1/p' "$xkeen_config" | xargs 2>/dev/null)
fi
[ "$gh_proxy_user" = "null" ] && gh_proxy_user=""
}
# Функция проверки доступности GitHub.
# Тонкая обёртка над probe_with_mirrors: идентичная философия (пара URL
# через одну цепочку префиксов, exclusive gh_proxy_user, exit 1 на
# полную недоступность), но один engine fallback в helper'е вместо
# дублирования логики direct/proxy1/proxy2 здесь.
test_github() {
[ -n "$_gh_probed" ] && return 0
get_user_proxy
printf " ${yellow}Проверка доступности${reset} GitHub. Подождите, пожалуйста...\n"
# Pair probe: github.com (releases) + raw.githubusercontent.com
# (для dev-обновления). Разные CDN, разный uptime, проверяем оба.
if probe_with_mirrors "$xkeen_tar_url" && probe_with_mirrors "$xkeen_dev_url"; then
_gh_probed=1
if [ -n "$gh_proxy_user" ]; then
printf " GitHub ${green}доступен через ваш прокси${reset}: ${yellow}$gh_proxy_user${reset}. Продолжаем...\n"
elif [ -r /tmp/.xkeen_mirror_cache ] && grep -q "__direct__" /tmp/.xkeen_mirror_cache 2>/dev/null; then
printf " GitHub ${green}доступен${reset}. Продолжаем...\n"
else
printf " GitHub ${green}доступен через прокси${reset}. Продолжаем...\n"
fi
return 0
fi
if [ -n "$gh_proxy_user" ]; then
printf " ${red}Ошибка${reset}: Указанный вами прокси $gh_proxy_user недоступен\n"
else
printf " ${red}Ошибка${reset}: GitHub недоступен\n"
fi
exit 1
}
================================================
FILE: scripts/_xkeen/05_tests/02_tests_xports.sh
================================================
# Определение на каких портах слушает ядро прокси
tests_ports_client() {
if pidof "xray" >/dev/null; then
name_client=xray
elif pidof "mihomo" >/dev/null; then
name_client=mihomo
else
echo
echo " Определение портов прослушивания возможно только при работающем XKeen"
echo " Запустите XKeen командой 'xkeen -start'"
exit 1
fi
listening_ports_tcp=
listening_ports_udp=
output=" $name_client ${green}слушает${reset}"
listening_ports_tcp=$(netstat -ltunp | grep "$name_client" | grep "tcp")
listening_ports_udp=$(netstat -ltunp | grep "$name_client" | grep "udp")
if [ -n "$listening_ports_tcp" ] || [ -n "$listening_ports_udp" ]; then
printed=false
IFS='
'
for line in $listening_ports_tcp $listening_ports_udp; do
gateway=
port=
protocol=
if [ -n "$(echo "$line" | grep "tcp")" ]; then
protocol="TCP"
fi
if [ -n "$(echo "$line" | grep "udp")" ]; then
if [ -n "$protocol" ]; then
protocol="$protocol и UDP"
else
protocol="UDP"
fi
fi
full_address=$(echo "$line" | awk '{print $4}')
if echo "$full_address" | grep -q '^:::[0-9]'; then
# Если IPv4 отображается как :::port
gateway="0.0.0.0"
port=$(echo "$full_address" | awk -F':::' '{print $2}')
elif echo "$full_address" | grep -q '^\[::\]'; then
# Явный IPv6 [::]:port
gateway="[::]"
port=$(echo "$full_address" | awk -F'\\]:' '{print $2}')
elif echo "$full_address" | grep -q '\\]:'; then
# Обычный IPv6 [addr]:port
gateway=$(echo "$full_address" | awk -F'\\]:' '{print $1}')"]"
port=$(echo "$full_address" | awk -F'\\]:' '{print $2}')
elif echo "$full_address" | grep -q ':'; then
# Обычный IPv4
gateway=$(echo "$full_address" | cut -d':' -f1)
port=$(echo "$full_address" | cut -d':' -f2)
fi
if [ "$printed" = false ]; then
printf "%b\n" "$output"
printed=true
fi
printf "\n %bШлюз%b %s\n %bПорт%b %s\n %bПротокол%b %s\n" \
"$italic" "$reset" "$gateway" \
"$italic" "$reset" "$port" \
"$italic" "$reset" "$protocol"
done
else
printf "%b\n" " $name_client ${red}не слушает${reset} на каких-либо портах"
fi
}
================================================
FILE: scripts/_xkeen/05_tests/03_tests_storage.sh
================================================
# Определение места установки Entware
location_entware_storage() {
mount_point=$(mount | grep 'on /opt ')
device=$(echo "$mount_point" | awk '{print $1}')
if echo "$device" | grep -q "^/dev/sd"; then
entware_storage="на внешний USB-накопитель"
elif echo "$device" | grep -q "^/dev/ubi"; then
entware_storage="во внутреннюю память роутера"
preinstall_warn="true"
else
entware_storage="на неидентифицированный носитель информации"
fi
}
preinstall_warn() {
if [ -n "$preinstall_warn" ]; then
echo
echo -e " ${red}Внимание${reset}: Инициирована установка XKeen $entware_storage"
echo " Убедитесь, что на ней достаточно свободного места. Сбой при такой"
echo " установке не является проблемой XKeen и багрепорт не будет рассмотрен"
echo -e " XKeen ${green}рекомендуется${reset} устанавливать на внешний ${green}USB-накопитель${reset}"
echo
echo " 1. Продолжить установку $entware_storage"
echo " 2. Выйти из установщика"
echo
while true; do
read -p " Выберите действие: " choice
case $choice in
1)
clear
break
;;
2)
echo
echo -e " ${red}Установка отменена${reset}"
exit 0
;;
*)
echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов"
;;
esac
done
fi
}
================================================
FILE: scripts/_xkeen/about.sh
================================================
about_xkeen() {
echo
printf " Утилита ${green}XKeen${reset} предназначена для управления межсетевым\n экраном роутера ${yellow}Keenetic${reset}, защищающим домашнюю сеть.\n Разработчики ${red}не несут ответственности${reset} за использование\n ${green}XKeen${reset} вне прямого назначения. Перед использованием убедитесь,\n что ваши действия соответствуют законодательству вашей страны.\n Использование ${green}XKeen${reset} в противоправных целях ${red}строго запрещено${reset}.\n"
}
author_donate() {
echo
echo " Выберите удобный для Вас способ:"
echo
echo -e " Поддержать автора оригинального XKeen (${green}Skrill0${reset})"
echo " 1. Т-Банк"
echo " 2. DonationAlerts/ЮMoney"
echo " 3. Crypto"
echo
echo -e " Поддержать разработчика форка XKeen (${green}jameszero${reset})"
echo " 4. Карта МИР"
echo " 5. CloudTips/ЮMoney"
echo " 6. Crypto"
echo
echo " 0. Отмена"
echo
while true; do
read -r -p " Ваш выбор: " choice
case "$choice" in
1)
echo
echo -e " ${yellow}Прямая ссылка${reset}"
echo " https://www.tbank.ru/rm/krasilnikova.alina18/G4Z9433893"
echo
echo -e " ${yellow}Номер карты${reset}"
echo " 2200 7008 8716 3128"
echo
return 0
;;
2)
echo
echo -e " ${yellow}Прямая ссылка DonationAlerts${reset}"
echo " https://www.donationalerts.com/r/skrill0"
echo
echo -e " ${yellow}Прямая ссылка ЮMoney${reset}"
echo " https://yoomoney.ru/to/410018052017678"
echo
echo -e " ${yellow}Номер ЮMoney-кошелька${reset}"
echo " 4100 1805 201 7678"
echo
return 0
;;
3)
echo
echo -e " ${yellow}USDT${reset}, TRC20"
echo " tsc6emx5khk4cpyfkwj7dusybokravxs3m"
echo
echo -e " ${yellow}USDT${reset}, ERC20 и BEP20"
echo " 0x4a0369a762e3a23cc08f0bbbf39e169a647a5661"
echo
echo -e " ${light_blue}Уточните актуальность реквизитов перед переводом${reset}"
echo
return 0
;;
4)
echo
echo -e " ${yellow}Карта МИР${reset} ЮMoney"
echo " 2204 1201 2976 4110"
echo
return 0
;;
5)
echo
echo -e " ${yellow}Прямая ссылка CloudTips${reset}"
echo " https://pay.cloudtips.ru/p/7edb30ec"
echo
echo -e " ${yellow}Прямая ссылка ЮMoney${reset}"
echo " https://yoomoney.ru/to/41001350776240"
echo
echo -e " ${yellow}Номер ЮMoney-кошелька${reset}"
echo " 4100 1350 7762 40"
echo
return 0
;;
6)
echo
echo -e " ${yellow}USDT${reset}, TRC20"
echo " TQhy1LbuGe3Bz7EVrDYn67ZFLDjDBa2VNX"
echo
echo -e " ${yellow}USDT${reset}, ERC20"
echo " 0x6a5DF3b5c67E1f90dF27Ff3bd2a7691Fad234EE2"
echo
echo -e " ${light_blue}Уточните актуальность реквизитов перед переводом${reset}"
echo
return 0
;;
0)
echo
echo -e " ${yellow}Спасибо${reset}, что ознакомились с возможностью поддержать разработчиков"
echo
return 0
;;
*)
echo -e " ${red}Некорректный ввод${reset}"
;;
esac
done
}
author_feedback() {
echo
echo -e " ${green}Контакты разработчиков${reset}"
echo
echo -e " ${light_blue}Автор оригинального XKeen${reset}:"
echo -e " ${yellow}Профиль на форуме keenetic${reset}:"
echo " https://forum.keenetic.ru/profile/73583-skrill0"
echo -e " ${yellow}e-mail${reset}:"
echo " alinajoeyone@gmail.com"
echo -e " ${yellow}telegram${reset}:"
echo " @Skrill_zerro"
echo -e " ${yellow}telegram помощника${reset}:"
echo " @skride"
echo
echo -e " ${light_blue}Разработчик форка XKeen${reset}:"
echo -e " ${yellow}Профиль на форуме keenetic${reset}:"
echo " https://forum.keenetic.ru/profile/20945-jameszero"
echo -e " ${yellow}e-mail${reset}:"
echo " admin@jameszero.net"
echo -e " ${yellow}telegram${reset}:"
echo " @jameszero"
echo -e " ${yellow}сайт${reset}:"
echo " https://jameszero.net"
echo -e " ${yellow}GitHub${reset}:"
echo " https://github.com/jameszeroX"
echo
echo -e " Предоставленные выше контакты предназначены ${green}для личной переписки${reset}, а ${red}не для консультаций${reset}"
echo " Возникающие вопросы по XKeen, задавайте в телеграм-чате https://t.me/+8Cvh7oVf6cE0MWRi"
}
help_xkeen() {
echo
echo -e "${yellow}Установка${reset}"
echo -e " -i ${italic} Основной режим установки XKeen + Xray + GeoFile/GeoIPSET + Mihomo${reset}"
echo -e " -io ${italic} OffLine установка XKeen${reset}"
echo -e " -toff ${italic} Отключение таймаута при меделенной загрузке с GitHub (xkeen -i -toff)${reset}"
echo
echo -e "${green}Переустановка${reset}"
echo -e " -k ${italic} XKeen${reset}"
echo -e " -g ${italic} GeoFile${reset}"
echo -e " -gips ${italic} GeoIPSET${reset}"
echo
echo -e "${yellow}Обновление${reset}"
echo -e " -uk ${italic} XKeen${reset}"
echo -e " -ug ${italic} GeoFile/GeoIPSET${reset}"
echo -e " -ux ${italic} Xray (повышение/понижение версии)${reset}"
echo -e " -um ${italic} Mihomo (повышение/понижение версии)${reset}"
echo
echo -e "${yellow}Запланированная здача автообновления GeoFile/GeoIPSET${reset}"
echo -e " -ugc ${italic} Создание${reset}"
echo -e " -dgc ${italic} Удаление${reset}"
echo
echo -e "${green}Резервная копия XKeen${reset}"
echo -e " -kb ${italic} Создание${reset}"
echo -e " -kbr ${italic} Восстановление${reset}"
echo
echo -e "${green}Резервная копия конфигурации Xray${reset}"
echo -e " -xb ${italic} Создание${reset}"
echo -e " -xbr ${italic} Восстановление${reset}"
echo
echo -e "${green}Резервная копия конфигурации Mihomo${reset}"
echo -e " -mb ${italic} Создание${reset}"
echo -e " -mbr ${italic} Восстановление${reset}"
echo
echo -e "${red}Удаление${reset}"
echo -e " -remove ${italic} Полная деинсталляция XKeen${reset}"
echo -e " -dgs ${italic} GeoSite${reset}"
echo -e " -dgi ${italic} GeoIP${reset}"
echo -e " -dgips ${italic} GeoIPSET${reset}"
echo -e " -dx ${italic} Xray${reset}"
echo -e " -dm ${italic} Mihomo${reset}"
echo -e " -dk ${italic} XKeen${reset}"
echo
echo -e "${green}Порты проксирования${reset}"
echo -e " -ap ${italic} Добавить${reset}"
echo -e " -dp ${italic} Удалить${reset}"
echo -e " -cp ${italic} Посмотреть${reset}"
echo
echo -e "${green}Порты, исключённые из проксирования${reset}"
echo -e " -ape ${italic} Добавить${reset}"
echo -e " -dpe ${italic} Удалить${reset}"
echo -e " -cpe ${italic} Посмотреть${reset}"
echo
echo -e "${light_blue}Управление прокси-клиентом${reset}"
echo -e " -start ${italic} Запуск${reset}"
echo -e " -stop ${italic} Остановка${reset}"
echo -e " -restart${italic} Перезапуск${reset}"
echo -e " -status ${italic} Статус работы${reset}"
echo -e " -tp ${italic} Порты, шлюз и протокол прокси-клиента${reset}"
echo -e " -auto ${italic} Включить | Отключить автозапуск прокси-клиента${reset}"
echo -e " -d ${italic} Установить задержку автозапуска прокси-клиента${reset}"
echo -e " -fd ${italic} Включить | Отключить контроль файловых дескрипторов прокси-клиента${reset}"
echo -e " -cfd ${italic} Проверить количество файловых дескрипторов открытых прокси-клиентом${reset}"
echo -e " -diag ${italic} Выполнить диагностику${reset}"
echo -e " -channel${italic} Переключить канал получения обновлений XKeen (Stable/Dev версия)${reset}"
echo -e " -xray ${italic} Переключить XKeen на ядро Xray${reset}"
echo -e " -mihomo ${italic} Переключить XKeen на ядро Mihomo${reset}"
echo -e " -ipv6 ${italic} Включить | Отключить протокол IPv6 в KeeneticOS${reset}"
echo -e " -dns ${italic} Включить | Отключить перенаправление DNS в прокси${reset}"
echo -e " -pr ${italic} Включить | Отключить проксирование трафика Entware${reset}"
echo -e " -extmsg ${italic} Включить | Отключить расширенные сообщения при запуске XKeen${reset}"
echo -e " -cbk ${italic} Включить | Отключить резервное копирование XKeen при обновлении${reset}"
echo -e " -aghfix ${italic} Включить | Отключить отображение клиентов XKeen под своими IP в журнале AdGuard Home${reset}"
echo
echo -e "${light_blue}Информация${reset}"
echo -e " -about ${italic} О программе${reset}"
echo -e " -ad ${italic} Поддержать разработчиков${reset}"
echo -e " -af ${italic} Обратная связь${reset}"
echo -e " -v ${italic} Версия XKeen${reset}"
}
================================================
FILE: scripts/_xkeen/import.sh
================================================
# Импорт основных модулей и определение их путей
script_dir="$(cd "$(dirname "$0")" && pwd)"
xinfo_dir="$script_dir/.xkeen/01_info"
xinstall_dir="$script_dir/.xkeen/02_install"
xdelete_dir="$script_dir/.xkeen/03_delete"
xtools_dir="$script_dir/.xkeen/04_tools"
xtests_dir="$script_dir/.xkeen/05_tests"
main_dir="$script_dir/.xkeen"
# Модуль информации
. "$xinfo_dir/00_info_import.sh"
# Mirror-fallback helper. Загружается до install-модуля, потому что
# install-модуль (04_install_geofile, 05_install_geoipset) вызывает
# fetch_with_mirrors напрямую.
. "$xtools_dir/07_tools_downloaders/00_fetch_with_mirrors.sh"
# Модуль установки
. "$xinstall_dir/00_install_import.sh"
# Модуль удаления
. "$xdelete_dir/00_delete_import.sh"
# Модуль инструментария
. "$xtools_dir/00_tools_import.sh"
# Модуль тестирования
. "$xtests_dir/00_tests_import.sh"
# Модуль справки
. "$main_dir/about.sh"
================================================
FILE: scripts/xkeen
================================================
#!/bin/sh
# Определение директории, где находится xkeen
script_dir="$(cd "$(dirname "$0")" && pwd)"
# Скрываем основную директорию xkeen
install_xkeen_rename() {
source_dir="_xkeen"
target_dir=".xkeen"
source_path="$script_dir/$source_dir"
target_path="$script_dir/$target_dir"
if [ -d "$source_path" ]; then
if [ -d "$target_path" ]; then
rm -rf "$target_path" 2>/dev/null
fi
mv "$source_path" "$target_path"
fi
rm "/opt/root/install.sh" 2>/dev/null
}
install_xkeen_rename
add_chmod_init() {
[ -f "$initd_file" ] && chmod +x "$initd_file"
}
# Подсчет количества логических команд (параметров, начинающихся с - или --)
LOGICAL_CMD_COUNT=0
for arg in "$@"; do
case "$arg" in
-*) LOGICAL_CMD_COUNT=$((LOGICAL_CMD_COUNT + 1)) ;;
esac
done
# Функция умной очистки экрана
smart_clear() {
# Очищаем экран, только если запущена одна логическая команда
if [ "$LOGICAL_CMD_COUNT" -le 1 ]; then
clear
fi
}
for arg in "$@"; do
[ "$arg" = "-toff" ] && touch "/tmp/toff"
[ "$arg" = "-restart" ] && apply="restart"
case "$arg" in
-restart|-start|-stop) detach_eligible=true ;;
esac
done
# Self-detach в новую session/pgid когда вызвали без TTY (ssh без -t, cron, CGI)
# чтобы форсированный обрыв caller-сессии не убивал начальные проверки.
# Подробности в commit-message. XKEEN_FOREGROUND=1 отключает детач для callers
# требующих синхронной семантики (скрипты с && cleanup).
if [ -n "$detach_eligible" ] && [ -z "$XKEEN_DETACHED" ] \
&& [ ! -t 0 ] && [ ! -t 1 ] && [ -z "$XKEEN_FOREGROUND" ]; then
export XKEEN_DETACHED=1
log_dir="/opt/var/log"
[ -d "$log_dir" ] || mkdir -p "$log_dir"
log="$log_dir/xkeen-detached.log"
{
echo
echo "=== $(date '+%F %T') $0 $* ==="
} >>"$log"
start-stop-daemon -S -b -n "xkdtch.$$" \
-x "$0" -- "$@" \
>>"$log" 2>&1
echo " Запуск в фоновом режиме, лог: $log" >&2
exit 0
fi
get_state_arg() {
if [ "$1" = "on" ] || [ "$1" = "off" ]; then
echo "$1"
fi
}
# Импортируем модули
. "$script_dir/.xkeen/import.sh"
# Маркер /tmp/toff отключает curl_timeout — снимаем при прерывании
trap 'rm -f /tmp/toff; exit 1' INT TERM
xkeen_info() {
# Проверяем версию XKeen
info_version_xkeen
# Проверяем актуальность XKeen
info_compare_xkeen
}
xkeen_set_info() {
# Определяем архитектуру процессора
info_cpu
# Проверяем установку Xray
info_xray
# Проверяем установку Mihomo
info_mihomo
# Проверяем установленные базы GeoSite
info_geosite
# Проверяем установленные базы GeoiIP
info_geoip
# Проверяем статус автообновления
info_cron
# Проверяем версию Xray
info_version_xray
# Проверяем версию Mihomo и Yq
info_version_mihomo
info_version_yq
}
while [ $# -gt 0 ]; do
case "$1" in
-i|-install) # Запуск полного цикла установки
test_connection
test_entware
test_github
sleep 2
smart_clear
echo
check_keen_mode
if [ -n "$keen_mode" ]; then
echo -e " ${red}Ошибка${reset}: Установка XKeen возможна только на Keenetic в режиме ${green}роутера${reset}"
exit 1
fi
echo -e " Запуск полного цикла установки ${yellow}XKeen${reset}"
init_directories
xkeen_info
xkeen_set_info
logs_cpu_info_console
case "$architecture" in
arm64-v8a|mips32le|mips32) ;;
*) exit 1 ;;
esac
location_entware_storage
preinstall_warn
choice_add_proxy_cores
if [ "$xray_installed" != "installed" ] && [ "$mihomo_installed" != "installed" ] &&
[ "$add_xray" = "false" ] && [ "$add_mihomo" = "false" ]; then
echo -e " ${red}Невозможно установить${reset} XKeen без ядра проксирования"
exit 1
fi
if [ "$add_xray" = "true" ]; then
smart_clear
echo
download_xray
else
command -v xray >/dev/null 2>&1 || bypass_xray="true"
fi
if [ -z "$bypass_xray" ]; then
install_xray
info_xray
fi
if [ "$xray_installed" = "installed" ]; then
smart_clear
# Устанавливаем GeoSite
choice_geosite
delete_geosite
install_geosite
sleep 2
smart_clear
# Устанавливаем GeoIP
choice_geoip
delete_geoip
install_geoip
sleep 2
fi
smart_clear
# Устанавливаем GeoIPSET
install_geoipset init
sleep 2
if [ "$bypass_cron_geosite" = "false" ] || [ "$bypass_cron_geoip" = "false" ] || [ "$bypass_cron_geoipset" = "false" ]; then
smart_clear
# Настраиваем автоматические обновления
info_cron
choice_update_cron
update_cron_geofile_task
smart_clear
choice_cron_time
install_cron
sleep 2
fi
if [ "$xray_installed" = "installed" ]; then
smart_clear
echo
install_configs
fi
# Создаем init для cron
"$initd_dir/S05crond" stop >/dev/null 2>&1
[ -e "$initd_dir/S05crond" ] && rm -f "$initd_dir/S05crond"
register_cron_initd
"$initd_dir/S05crond" start >/dev/null 2>&1
if [ -z "$bypass_xray" ]; then
info_version_xray
delete_register_xray
echo
echo -e " Выполняется регистрация ${yellow}Xray${reset}"
register_xray_list
logs_register_xray_list_info_console
register_xray_control
logs_register_xray_control_info_console
register_xray_status
logs_register_xray_status_info_console
sleep 2
fi
if [ "$add_mihomo" = "true" ]; then
smart_clear
echo
if ! download_mihomo; then
bypass_mihomo="true"
fi
else
command -v mihomo >/dev/null 2>&1 || bypass_mihomo="true"
fi
if [ -z "$bypass_mihomo" ]; then
install_mihomo
info_mihomo
fi
if [ "$mihomo_installed" = "installed" ]; then
add_mihomo_config
fi
if [ -z "$bypass_mihomo" ]; then
info_version_mihomo
info_version_yq
delete_register_mihomo
echo
echo -e " Выполняется регистрация ${yellow}Mihomo${reset}"
register_mihomo_list
logs_register_mihomo_list_info_console
register_mihomo_control
logs_register_mihomo_control_info_console
register_mihomo_status
logs_register_mihomo_status_info_console
register_yq_list
logs_register_yq_list_info_console
register_yq_control
logs_register_yq_control_info_console
register_yq_status
logs_register_yq_status_info_console
sleep 2
fi
if [ "$mihomo_installed" != "installed" ] && [ "$xray_installed" != "installed" ]; then
echo
echo -e " ${red}Ошибка${reset}: Установка прервана. Не установлено ни одного ядра проксирования (Xray или Mihomo)"
exit 1
fi
if [ "$xray_installed" = "installed" ] || [ "$mihomo_installed" = "installed" ]; then
delete_register_xkeen
smart_clear
echo
echo -e " Выполняется регистрация ${yellow}XKeen${reset}"
register_xkeen_list
logs_register_xkeen_list_info_console
register_xkeen_control
logs_register_xkeen_control_info_console
register_xkeen_status
logs_register_xkeen_status_info_console
fixed_register_packages
migrate_ports_from_initd
register_xkeen_initd
create_xkeen_cfg
sleep 2
smart_clear
choice_autostart_xkeen
fi
if [ "$xray_installed" != "installed" ] && [ "$mihomo_installed" = "installed" ]; then
if [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ] && grep -q 'name_client="xray"' "$initd_file"; then
sed -i 's/name_client="xray"/name_client="mihomo"/' "$initd_file"
fi
elif [ "$xray_installed" = "installed" ] && [ "$mihomo_installed" != "installed" ]; then
if [ -f "$install_dir/xray" ] && grep -q 'name_client="mihomo"' "$initd_file"; then
sed -i 's/name_client="mihomo"/name_client="xray"/' "$initd_file"
fi
fi
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null ; then
"$initd_file" restart on >/dev/null 2>&1
fi
# Удаляем временные файлы
delete_tmp
sleep 2
smart_clear
echo
echo -e " ${green}Установка XKeen завершена!${reset}"
if [ "$xray_installed" = "installed" ] || [ "$mihomo_installed" = "installed" ]; then
if grep -q 'name_client="xray"' "$initd_file"; then
echo -e " 1. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'"
echo -e " 2. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 3. ${green}Enjoy!${reset}"
echo
elif grep -q 'name_client="mihomo"' "$initd_file"; then
echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'"
echo -e " 2. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 3. ${green}Enjoy!${reset}"
echo
fi
fi
if [ "$xray_installed" = "installed" ] && [ "$mihomo_installed" = "installed" ]; then
if grep -q 'name_client="xray"' "$initd_file"; then
echo -e " Если хотите переключить XKeen на ядро ${yellow}Mihomo${reset}"
echo
echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'"
echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -mihomo${reset}"
echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 4. ${green}Enjoy!${reset}"
elif grep -q 'name_client="mihomo"' "$initd_file"; then
echo -e " Если хотите переключить XKeen на ядро ${yellow}Xray${reset}"
echo
echo -e " 1. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'"
echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -xray${reset}"
echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 4. ${green}Enjoy!${reset}"
fi
echo
fi
echo -e " Для вывода Справки выполните ${yellow}xkeen -h${reset}"
;;
-io) # Установка XKeen OffLine
smart_clear
echo
check_keen_mode
if [ -n "$keen_mode" ]; then
echo -e " ${red}Ошибка${reset}: Установка XKeen возможна только на Keenetic в режиме ${green}роутера${reset}"
exit 1
fi
echo " Установка XKeen OffLine"
init_directories
xkeen_set_info
logs_cpu_info_console
case "$architecture" in
arm64-v8a|mips32le|mips32) ;;
*) exit 1 ;;
esac
if [ -f "$install_dir/xray" ]; then
chmod +x "$install_dir/xray"
elif [ -f "$install_dir/mihomo" ]; then
chmod +x "$install_dir/mihomo"
if [ -f "$install_dir/yq" ]; then
chmod +x "$install_dir/yq"
else
echo -e " ${red}Не найден${reset} парсер конфигурационных файлов Mihomo - Yq"
exit 1
fi
else
smart_clear
echo
echo -e " ${red}Не найдено ядро проксирования xray или mihomo${reset}"
echo
echo -e " Если планируете использовать ядро xray, поместите бинарник ${yellow}xray${reset}\n архитектуры ${green}$architecture${reset} в директорию /opt/sbin/ и начните установку снова"
echo -e " Страница загрузок xray: ${yellow}https://github.com/XTLS/Xray-core/releases/latest${reset}"
echo
echo -e " Если планируете использовать ядро mihomo, поместите бинарники ${yellow}mihomo${reset} и ${yellow}yq${reset}\n архитектуры ${green}$architecture${reset} в директорию /opt/sbin/ и начните установку снова"
echo -e " Страница загрузок mihomo: ${yellow}https://github.com/MetaCubeX/mihomo/releases/latest${reset}"
echo -e " Страница загрузок yq: ${yellow}https://github.com/mikefarah/yq/releases/latest${reset}"
echo
exit 1
fi
if [ -f "$install_dir/xray" ]; then
install_configs
if [ ! -d "$geo_dir" ]; then
mkdir -p "$geo_dir"
fi
smart_clear
delete_register_xray
info_xray
info_version_xray
echo
echo -e " Выполняется регистрация ${yellow}Xray${reset}"
register_xray_list
logs_register_xray_list_info_console
register_xray_control
logs_register_xray_control_info_console
register_xray_status
logs_register_xray_status_info_console
fi
if [ -f "$install_dir/mihomo" ]; then
add_mihomo_config
delete_register_mihomo
info_mihomo
info_version_mihomo
info_version_yq
echo
echo -e " Выполняется регистрация ${yellow}Mihomo${reset}"
register_mihomo_list
logs_register_mihomo_list_info_console
register_mihomo_control
logs_register_mihomo_control_info_console
register_mihomo_status
logs_register_mihomo_status_info_console
register_yq_list
logs_register_yq_list_info_console
register_yq_control
logs_register_yq_control_info_console
register_yq_status
logs_register_yq_status_info_console
sleep 2
fi
smart_clear
delete_register_xkeen
echo
echo -e " Выполняется регистрация ${yellow}XKeen${reset}"
register_xkeen_list
logs_register_xkeen_list_info_console
register_xkeen_control
logs_register_xkeen_control_info_console
register_xkeen_status
logs_register_xkeen_status_info_console
migrate_ports_from_initd
register_xkeen_initd
create_xkeen_cfg
fixed_register_packages
smart_clear
choice_autostart_xkeen
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null ; then
"$initd_file" restart on >/dev/null 2>&1
fi
# Удаляем временные файлы
delete_tmp
sleep 2
smart_clear
echo
echo -e " ${green}Установка XKeen завершена!${reset}"
echo
echo -e " Для использования ядра '${yellow}$Xray${reset}'"
echo -e " 1. Поместите необходимые геофайлы в директорию '${yellow}$geo_dir/${reset}'"
echo -e " 2. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'"
echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 4. ${green}Enjoy!${reset}"
echo
echo -e " Для использования ядра ${yellow}Mihomo${reset}"
echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'"
echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -mihomo${reset}"
echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}"
echo -e " 4. ${green}Enjoy!${reset}"
echo
echo -e " Для вывода Справки выполните ${yellow}xkeen -h${reset}"
;;
-ug) # Обновление баз GeoFile/GeoIPSET
test_connection
test_github
sleep 2
smart_clear
echo
echo " Обновление установленных баз GeoFile/GeoIPSET"
info_geosite
info_geoip
if
[ "$update_refilter_geosite" = "true" ] || \
[ "$update_v2fly_geosite" = "true" ] || \
[ "$update_zkeen_geosite" = "true" ] || \
[ "$update_refilter_geoip" = "true" ] || \
[ "$update_v2fly_geoip" = "true" ] || \
[ "$update_zkeenip_geoip" = "true" ]; then
echo
install_geosite
install_geoip
install_geoipset update
if pidof xray >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
echo -e " Обновление установленных баз GeoFile/GeoIPSET ${green}завершено${reset}"
else
echo -e " ${red}Не обнаружены${reset} базы GeoFile/GeoIPSET для обновления"
fi
;;
-uk) # Обновление XKeen
test_connection
test_github
sleep 2
smart_clear
echo
echo " Проверка обновлений XKeen"
xkeen_info
xkeen_set_info
if [ "$xkeen_build" != "Stable" ]; then
download_func="download_xkeen_dev"
else
if [ "$info_compare_xkeen" = "actual" ]; then
echo " Нет доступных обновлений XKeen"
exit 0
else
echo -e " Найдена новая версия ${yellow}XKeen${reset}"
download_func="download_xkeen"
fi
fi
backup_xkeen
"$download_func"
install_xkeen
# Перезапуск скрипта
grep -E "^\s*-uk_post_update\s*\)" "$0" > /dev/null && exec sh "$0" -uk_post_update
;;
-uk_post_update)
. "/opt/sbin/.xkeen/import.sh"
init_directories
xkeen_info
xkeen_set_info
info_packages
install_packages
echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}XKeen${reset}"
delete_register_xkeen
logs_delete_register_xkeen_info_console
echo -e " Выполняется регистрация новой версии ${yellow}XKeen${reset}"
register_xkeen_list
logs_register_xkeen_list_info_console
register_xkeen_control
logs_register_xkeen_control_info_console
register_xkeen_status
logs_register_xkeen_status_info_console
register_cron_initd
migrate_ports_from_initd
register_xkeen_initd
create_xkeen_cfg
choice_cancel_cron_select=true
update_cron_geofile_task
fixed_register_packages
if pidof xray >/dev/null || pidof mihomo >/dev/null ; then
"$initd_file" restart on >/dev/null 2>&1
fi
delete_tmp
echo -e " Обновление XKeen ${green}выполнено${reset}"
;;
-ux) # Обновление или установка ядра Xray
test_connection
test_github
sleep 2
. "/opt/sbin/.xkeen/01_info/03_info_cpu.sh"
status_file="/opt/lib/opkg/status"
info_cpu
smart_clear
info_xray
info_version_xray
[ "$xray_installed" = "installed" ] && echo -e " В роутере установлен Xray версии ${yellow}$xray_current_version${reset}" && echo
download_xray
if [ -z "$bypass_xray" ]; then
install_xray
if [ "$xray_installed" = "installed" ]; then
echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}Xray${reset}"
delete_register_xray
logs_delete_register_xray_info_console
info_version_xray
echo -e " Выполняется регистрация новой версии ${yellow}Xray${reset}"
register_xray_list
logs_register_xray_list_info_console
register_xray_control
logs_register_xray_control_info_console
register_xray_status
logs_register_xray_status_info_console
sleep 2
if pidof xray >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
echo
echo -e " Обновление ядра Xray ${green}выполнено${reset}"
else
xray_installed="installed"
info_version_xray
if [ -f "$install_dir/xray" ]; then
install_configs
if [ ! -d "$geo_dir" ]; then
mkdir -p "$geo_dir"
fi
delete_register_xray
echo
echo -e " Выполняется регистрация ${yellow}Xray${reset}"
register_xray_list
logs_register_xray_list_info_console
register_xray_control
logs_register_xray_control_info_console
register_xray_status
logs_register_xray_status_info_console
sleep 2
smart_clear
echo
echo -e " Установка ядра ${yellow}Xray${reset} ${green}выполнена${reset}"
fi
fi
fi
fixed_register_packages
delete_tmp
;;
-um) # Обновление или установка ядра Mihomo
test_connection
test_github
sleep 2
. "/opt/sbin/.xkeen/01_info/03_info_cpu.sh"
status_file="/opt/lib/opkg/status"
info_cpu
smart_clear
info_mihomo
info_version_mihomo
mihomo_was_installed="$mihomo_installed"
[ "$mihomo_installed" = "installed" ] && echo -e " В роутере установлен Mihomo версии ${yellow}$mihomo_current_version${reset}" && echo
if ! download_mihomo; then
delete_tmp
exit 1
fi
if [ -z "$bypass_mihomo" ]; then
install_mihomo
info_mihomo
if [ "$mihomo_installed" != "installed" ]; then
delete_tmp
echo -e " ${red}Ошибка${reset}: Mihomo не установлен, так как отсутствует обязательный Yq"
exit 1
elif [ "$mihomo_was_installed" = "installed" ]; then
echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}Mihomo${reset}"
delete_register_mihomo
logs_delete_register_mihomo_info_console
logs_delete_register_yq_info_console
info_version_mihomo
info_version_yq
echo -e " Выполняется регистрация новой версии ${yellow}Mihomo${reset}"
register_mihomo_list
logs_register_mihomo_list_info_console
register_mihomo_control
logs_register_mihomo_control_info_console
register_mihomo_status
logs_register_mihomo_status_info_console
register_yq_list
logs_register_yq_list_info_console
register_yq_control
logs_register_yq_control_info_console
register_yq_status
logs_register_yq_status_info_console
if pidof mihomo >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
echo
echo -e " Обновление ядра ${yellow}Mihomo${reset} ${green}выполнено${reset}"
else
info_version_mihomo
info_version_yq
add_mihomo_config
delete_register_mihomo
echo
echo -e " Выполняется регистрация ${yellow}Mihomo${reset}"
register_mihomo_list
logs_register_mihomo_list_info_console
register_mihomo_control
logs_register_mihomo_control_info_console
register_mihomo_status
logs_register_mihomo_status_info_console
register_yq_list
logs_register_yq_list_info_console
register_yq_control
logs_register_yq_control_info_console
register_yq_status
logs_register_yq_status_info_console
sleep 2
smart_clear
echo
echo -e " Установка ядра ${yellow}Mihomo${reset} ${green}выполнена${reset}"
fi
fi
fixed_register_packages
delete_tmp
;;
-ugc) # Создать или изменить существующюю задачу автообновления баз GeoFile/GeoIPSET
info_cron
smart_clear
echo -e " Создание или изменение задачи автообновления баз ${yellow}GeoFile/GeoIPSET${reset}"
choice_update_cron
update_cron_geofile_task
choice_cron_time
install_cron
delete_tmp
echo -e " Создание или изменение задачи автообновления баз GeoFile/GeoIPSET ${green}выполнено${reset}"
;;
-ri) # Пересоздать файл автозапуска XKeen
smart_clear
"$initd_file" stop >/dev/null 2>&1
[ -e "$initd_file" ] && rm -f "$initd_file"
echo -e " Создание файла автозапуска ${yellow}XKeen${reset}"
sleep 1
migrate_ports_from_initd
register_xkeen_initd
logs_register_xkeen_initd_info_console
echo
echo -e " Создание файла автозапуска XKeen ${green}выполнено${reset}"
echo -e " Если конфигурация настроена, то можете запустить проксирование командой '${yellow}xkeen -start${reset}'"
;;
-dgc) # Удалить задачу автообновления баз GeoFile/GeoIPSET
smart_clear
echo
choice_for_remove="задачу автообновления баз GeoFile/GeoIPSET"
choice_remove
info_cron
smart_clear
echo
echo -e " Удаление задачи автообновления баз ${yellow}GeoFile/GeoIPSET${reset}"
delete_cron_geofile
logs_delete_cron_geofile_info_console
delete_tmp
echo -e " Удаление задачи автообновления баз GeoFile/GeoIPSET ${green}выполнено${reset}"
;;
-dx) # Удалить Xray
smart_clear
echo
choice_for_remove="Xray"
choice_remove
smart_clear
echo
command -v xray >/dev/null 2>&1 || { echo -e " Xray ${red}не установлен${reset}"; exit 1; }
echo -e " Удаление ${yellow}Xray${reset}"
"$initd_file" stop >/dev/null 2>&1
opkg remove xray_s
rm -f "$install_dir/xray"
echo
echo -e " Удаление ${yellow}конфигурационных файлов Xray${reset}"
delete_configs
logs_delete_configs_info_console
echo
echo -e " Удаление Xray ${green}выполнено${reset}"
;;
-dm) # Удалить Mihomo
smart_clear
echo
choice_for_remove="Mihomo"
choice_remove
smart_clear
echo
command -v mihomo >/dev/null 2>&1 || { echo -e " Mihomo ${red}не установлен${reset}"; exit 1; }
echo -e " Удаление ${yellow}Mihomo${reset}"
"$initd_file" stop >/dev/null 2>&1
opkg remove mihomo_s
opkg remove yq_s
rm -f "$install_dir/mihomo" "$install_dir/yq"
rm -rf "$mihomo_conf_dir"
echo
echo -e " Удаление Mihomo ${green}выполнено${reset}"
;;
-dk) # Удалить XKeen
smart_clear
echo
choice_for_remove="XKeen"
choice_remove
smart_clear
echo
echo -e " Удаление ${yellow}XKeen${reset}"
opkg remove xkeen
delete_tmp
smart_clear
echo
delete_all
smart_clear
echo
echo -e " Удаление XKeen ${green}выполнено${reset}"
echo
echo -e " Установить ${yellow}XKeen${reset} заново можно командами:"
echo
echo -e " ${green}opkg update && opkg upgrade && opkg install curl tar && cd /tmp${reset}"
echo -e " ${green}sh -c \"\$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)\"${reset}"
;;
-dgi) # Удалить GeoIP
smart_clear
echo
choice_for_remove="GeoIP"
choice_remove
smart_clear
echo
echo -e " Удаление всех баз ${yellow}GeoIP${reset}"
delete_geoip_key
logs_delete_geoip_info_console
echo
echo -e " Удаление всех баз GeoIP ${green}выполнено${reset}"
;;
-dgs) # Удалить GeoSite
smart_clear
echo
choice_for_remove="GeoSite"
choice_remove
smart_clear
echo
echo -e " Удаление всех баз ${yellow}GeoSite${reset}"
delete_geosite_key
logs_delete_geosite_info_console
echo
echo -e " Удаление всех баз GeoSite ${green}выполнено${reset}"
;;
-remove) # Полная деинсталляция XKeen
smart_clear
choice_for_remove="XKeen полностью со всеми зависимостями"
choice_remove
info_cron
smart_clear
echo
echo -e " Удаление задачи автообновления баз ${yellow}GeoFile${reset}"
delete_cron_geofile
logs_delete_cron_geofile_info_console
echo
echo -e " Удаление задачи автообновления баз GeoFile ${green}выполнено${reset}"
sleep 2
# Удаление GeoSite's
smart_clear
echo
echo -e " Удаление всех баз ${yellow}GeoSite${reset}"
delete_geosite_key
logs_delete_geosite_info_console
echo -e " Удаление всех баз GeoSite ${green}выполнено${reset}"
sleep 2
# Удаление GeoIP's
smart_clear
echo
echo -e " Удаление всех баз ${yellow}GeoIP${reset}"
delete_geoip_key
logs_delete_geoip_info_console
echo -e " Удаление всех баз GeoIP ${green}выполнено${reset}"
sleep 2
# Удаление GeoIPSET
smart_clear
echo
echo -e " Удаление ${yellow}GeoIPSET${reset}"
delete_geoipset_key
logs_delete_geoipset_info_console
echo -e " Удаление GeoIPSET ${green}выполнено${reset}"
sleep 2
# Удаление файлов конфигурации Xray
smart_clear
echo
echo -e " Удаление ${yellow}конфигурационных файлов Xray${reset}"
delete_configs
logs_delete_configs_info_console
echo
echo -e " Удаление конфигурационных файлов Xray ${green}выполнено${reset}"
sleep 2
# Удаление Xray
smart_clear
echo
echo -e " ${yellow}Удаление${reset} Xray"
"$initd_file" stop >/dev/null 2>&1
opkg remove xray_s
rm -f "$install_dir/xray"
rm -rf "/opt/etc/xray"
echo
echo -e " Удаление Xray ${green}выполнено${reset}"
sleep 2
# Удаление Mihomo
smart_clear
echo
echo -e " ${yellow}Удаление${reset} Mihomo"
opkg remove mihomo_s
opkg remove yq_s
rm -f "$install_dir/mihomo" "$install_dir/yq"
rm -rf "$mihomo_conf_dir"
echo
echo -e " Удаление Mihomo ${green}выполнено${reset}"
sleep 2
# Удаление XKeen
smart_clear
echo
echo -e " Удаление ${yellow}XKeen${reset}"
opkg remove xkeen
delete_tmp
smart_clear
delete_all
smart_clear
echo
echo -e " Полная деинсталляция ${yellow}XKeen${reset} и всех зависимостей ${green}выполнена${reset}"
echo
echo -e " Установить ${yellow}XKeen${reset} заново можно командами:"
echo
echo -e " ${green}opkg update && opkg upgrade && opkg install curl tar && cd /tmp${reset}"
echo -e " ${green}sh -c \"\$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)\"${reset}"
;;
-k) # Переустановка XKeen
. "/opt/sbin/.xkeen/01_info/03_info_cpu.sh"
status_file="/opt/lib/opkg/status"
xkeen_info
xkeen_set_info
smart_clear
echo
echo -e " Переустановка ${yellow}XKeen${reset}"
choice_redownload_xkeen
if [ -n "$redownload_xkeen" ]; then
if [ "$xkeen_build" = "Stable" ]; then
download_func="download_xkeen"
else
download_func="download_xkeen_dev"
fi
test_connection
test_entware
test_github
sleep 2
"$download_func"
else
opkg update >/dev/null 2>&1
info_packages
install_packages
fi
echo
install_xkeen
# Перезапуск скрипта
grep -E "^\s*-k_post_install\s*\)" "$0" > /dev/null && exec sh "$0" -k_post_install
;;
-k_post_install)
. "/opt/sbin/.xkeen/import.sh"
init_directories
xkeen_info
xkeen_set_info
info_packages
install_packages
echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}XKeen${reset}"
delete_register_xkeen
logs_delete_register_xkeen_info_console
echo -e " Выполняется регистрация новой версии ${yellow}XKeen${reset}"
register_xkeen_list
logs_register_xkeen_list_info_console
register_xkeen_control
logs_register_xkeen_control_info_console
register_xkeen_status
logs_register_xkeen_status_info_console
register_cron_initd
migrate_ports_from_initd
register_xkeen_initd
create_xkeen_cfg
choice_cancel_cron_select=true
update_cron_geofile_task
fixed_register_packages
if pidof xray >/dev/null || pidof mihomo >/dev/null ; then
"$initd_file" restart on >/dev/null 2>&1
fi
delete_tmp
echo
echo -e " Переустановка XKeen ${green}выполнена${reset}"
;;
-g) # Установка баз GeoFile
command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; }
test_connection
test_github
sleep 2
smart_clear
info_geosite
info_geoip
choice_geosite
delete_geosite
install_geosite
sleep 2
smart_clear
choice_geoip
delete_geoip
install_geoip
sleep 2
smart_clear
echo
echo -e " Установка баз GeoFile ${green}выполнена${reset}"
;;
-gips) # Установка GeoIPSET
test_connection
test_github
smart_clear
install_geoipset init
;;
-dgips) # Удаление GeoIPSET
smart_clear
delete_geoipset
;;
-kb) # Резервное копирование XKeen
echo -e " Создание резервной копии ${yellow}XKeen${reset}"
info_version_xkeen
manual_backup="on"
backup_xkeen
;;
-kbr) # Восстановление XKeen из резервной копии
echo -e " Восстановление ${yellow}XKeen${reset} из резервной копии"
restore_backup_xkeen
;;
-xb) # Резервное копирование конфигурации Xray
command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; }
echo -e " Создание резервной копии ${yellow}конфигурации Xray${reset}"
backup_configs_xray
;;
-xbr) # Восстановление конфигурации Xray из резервной копии
command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; }
echo -e " Восстановление ${yellow}конфигурации Xray${reset} из резервной копии"
restore_backup_configs_xray
;;
-mb) # Резервное копирование конфигурации Mihomo
command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Mihomo"; exit 1; }
echo -e " Создание резервной копии ${yellow}конфигурации Mihomo${reset}"
backup_configs_mihomo
;;
-mbr) # Восстановление конфигурации Mihomo из резервной копии
command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Mihomo"; exit 1; }
echo -e " Восстановление ${yellow}конфигурации Mihomo${reset} из резервной копии"
restore_backup_configs_mihomo
;;
-tp) # Показать прослушиваемые порты
echo " Определение прослушиваемых портов"
tests_ports_client
;;
-v|-version) # Показать версию XKeen
echo -e " Версия ${yellow}XKeen $xkeen_current_version $xkeen_build${reset} (время сборки: ${light_blue}$build_timestamp${reset})"
info_xray
info_version_xray
info_mihomo
info_version_mihomo
info_version_yq
if [ -f "$install_dir/xray" ] && grep -q 'name_client="xray"' "$initd_file"; then
echo -e " Ядро проксирования Xray версии ${yellow}$xray_current_version${reset}"
elif [ -f "$install_dir/mihomo" ] && grep -q 'name_client="mihomo"' "$initd_file"; then
echo -e " Ядро проксирования Mihomo версии ${yellow}$mihomo_current_version${reset}"
echo -e " Парсер конфигурационных файлов Yq версии ${yellow}$yq_current_version${reset}"
else
echo -e " Ядро проксирования ${red}не установлено${reset}"
fi
;;
-about) # О программе
smart_clear
about_xkeen
;;
-ad|-donate) # Поддержать разработчиков
smart_clear
author_donate
;;
-af|-feedback) # Обратная связь
smart_clear
author_feedback
;;
-h|-help) # Помощь
smart_clear
help_xkeen
;;
-start) # Запуск XKeen
add_chmod_init
"$initd_file" start on
;;
-stop) # Остановка XKeen
add_chmod_init
"$initd_file" stop
;;
-restart) # Перезапуск XKeen
add_chmod_init
"$initd_file" restart on
;;
-status) # Состояние XKeen
"$initd_file" status
;;
-auto) # Смена режима запуска XKeen
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_autostart_xkeen "$action"
add_chmod_init
;;
-fd) # Смена режима контроля файловых дескрипторов
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_file_descriptors "$action"
;;
-cfd) # Проверка количества открытых файловых дескрипторов
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
check_file_descriptors
;;
-ap) # Добавить порт проксирования
shift
add_ports_donor "$*"
sleep 2
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
;;
-dp) # Удалить порт проксирования
shift
dell_ports_donor "$*"
sleep 2
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
;;
-cp) # Получить список портов проксирования
get_ports_donor
;;
-ape) # Добавить порт-исключение проксирования
shift
add_ports_exclude "$*"
sleep 2
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
;;
-dpe) # Удалить порт-исключение проксирования
shift
dell_ports_exclude "$*"
sleep 2
add_chmod_init
if pidof xray >/dev/null || pidof mihomo >/dev/null; then
"$initd_file" restart on >/dev/null 2>&1
fi
;;
-cpe) # Получить список портов, исключёных из проксирования
get_ports_exclude
;;
-modules) # Obsolete
smart_clear
echo
migration_modules
;;
-delmodules) # Obsolete
smart_clear
echo
remove_modules
;;
-d) # Установка задержки автозапуска в секундах
shift
delay_autostart "$1"
add_chmod_init
;;
-diag) # Диагностика XKeen
location_entware_storage
smart_clear
diagnostic
;;
-channel) # Смена канала обновлений XKeen (Stable/Dev)
smart_clear
choice_channel_xkeen
change_channel_xkeen
;;
-xray) # Смена ядра проксирования на Xray
choice_xray_core
;;
-mihomo) # Смена ядра проксирования на Mihomo
choice_mihomo_core
;;
-ipv6) # Включение/отключение IPv6 в KeeneticOS
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_ipv6_support "$action"
;;
-dns) # Включение/отключение перенаправления DNS в прокси
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
[ -n "$action" ] || warn_proxy_dns
change_proxy_dns "$action"
;;
-pr) # Включение/отключение проксирования трафика Entware
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_proxy_router "$action"
;;
-extmsg) # Включение/отключение расширенных сообщений запуска XKeen
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_extended_msg "$action"
;;
-cbk) # Включение/отключение резервного копирования XKeen при обновлении
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_backup_xkeen "$action"
;;
-aghfix) # Включение/отключение исправления для AdGuard Home
action=$(get_state_arg "$2")
[ -n "$action" ] && shift
smart_clear
change_aghfix_xkeen "$action"
;;
-toff) # Отключение таймаута при меделенной загрузке с GitHub
:
;;
*)
echo -e " Неизвестный ключ: ${red}$1${reset}"
echo -e " Список доступных ключей: ${yellow}xkeen -h${reset}"
;;
esac
shift
done
================================================
FILE: test/README.md
================================================
## XKeen 2.0 Beta
> [!NOTE]
> Это версия из канала разработки. Она регулярно дорабатывается, содержит новейшие функции, возможности и исправления, но может иметь не выявленные ошибки. Если столкнулись с проблемой - обязательно обновитесь командой `xkeen -uk`, возможно ошибка уже известна и исправлена. Если же проблема сохранилась, выполните `xkeen -diag` и покажите диагностический отчёт в телеграм-чате https://t.me/+8Cvh7oVf6cE0MWRi, подробно описав возникшую проблему
### Изменения
- Реализована работа с пользовательскими политиками 1
- Доработан модуль работы с DNS 2
- Реализована работа с IPSET и возможность исключать из проксирования IP-подсети России (параметры `-gips`, `-dgips`) 3
- Добавлена поддержка [DSCP-меток QoS](https://jameszero.net/4509.htm) (`62` - исключение из проксирования, `63` - проксирование)
- Добавлена возможность проксирования трафика Entware (параметр `-pr`) 4
- Режим работы Mixed переименован в Hybrid
- Порт 443 в интерфейсе роутера теперь требуется освобождать только для режима TProxy, пользователям Hybrid (Mixed) режима это делать не обязательно
- На роутерах Keenetic Skipper 4G ( KN-2910) и Keenetic 4G (KN-1212) после установки теперь не требуется подменять бинарник прокси-клиента, сразу устанавливается совместимый
- Версия утилиты yq зафиксирована для стабильности
- Порты проксирования и исключения полностью перенесены в `port_proxying.lst` и `port_exclude.lst`. Параметры `-ap`, `-dp`, `-cp`, `-ape`, `-dpe`, `-cpe` теперь работают только с этими файлам. Переменные `port_donor` и `port_exclude` больше не используются
- Добавлен параметр `-toff` для отключения таймаута загрузок при замедлении GitHub. Пример использования: `xkeen -i -toff`
- Пользовательский прокси для загрузок с GitHub теперь можно задать в параметре `gh_proxy` конфигурационного файла `xkeen.json`
- В файл `01_info_variable.sh` добавлена переменная `curl_extra` для дополнительных параметров, например, для загрузок через socks5 inbound
- DNS-запросы клиентов политик XKeen в журнале AdGuard Home теперь могут отображаться со своими IP-адресами, а не с IP роутера (параметр `-aghfix`)
- Предусмотрен вывод стандартной либо расширенной информации при запуске прокси-клиента (параметр `-extmsg`)
- XKeen переведён на использование актуальных модулей Netfilter из прошивки
- При включении/отключении контроля файловых дескрипторов теперь не требуется перезагружать роутер, достаточно перезапустить XKeen
- Задержка автозапуска XKeen теперь не влияет на запуск остальных пакетов, установленных в Entware
- Интерактивные параметры `-auto`, `-fd`, `-dns`, `-pr`, `-ipv6`, `-extmsg`, `-cbk`, `-aghfix` теперь умеют работать в автоматическом режиме (`-dns on`, `-auto off`,... ), а так же поддерживают перезапуск XKeen (`-dns on -restart`), если это необходимо
- Доработан сценарий установки. Корректное определение режима работы XKeen, не зависящее он имен входящих тегов `redirect` и `tproxy` [@UltraFeed](https://github.com/UltraFeed)
- XKeen теперь корректно работает со встроенной политикой Кинетика "Без доступа в интернет", часто используемой при настройке родительского контроля. При создании расписания, доступ в интернет прекращается и восстанавливается согласно заданных интервалов времени [#53](https://github.com/jameszeroX/XKeen/pull/53) - [@kittylabassistant](https://github.com/kittylabassistant)
- Доработки согласно PR [#32](https://github.com/jameszeroX/XKeen/pull/32) - [@kittylabassistant](https://github.com/kittylabassistant)
- Доработки согласно PR [#33](https://github.com/jameszeroX/XKeen/pull/33), [#34](https://github.com/jameszeroX/XKeen/pull/34), [#35](https://github.com/jameszeroX/XKeen/pull/35), [#36](https://github.com/jameszeroX/XKeen/pull/36), [#37](https://github.com/jameszeroX/XKeen/pull/37), [#38](https://github.com/jameszeroX/XKeen/pull/38), [#39](https://github.com/jameszeroX/XKeen/pull/39), [#40](https://github.com/jameszeroX/XKeen/pull/40), [#41](https://github.com/jameszeroX/XKeen/pull/41), [#42](https://github.com/jameszeroX/XKeen/pull/42), [#43](https://github.com/jameszeroX/XKeen/pull/43), [#44](https://github.com/jameszeroX/XKeen/pull/44), [#45](https://github.com/jameszeroX/XKeen/pull/45), [#46](https://github.com/jameszeroX/XKeen/pull/46), [#47](https://github.com/jameszeroX/XKeen/pull/47), [#48](https://github.com/jameszeroX/XKeen/pull/48), [#49](https://github.com/jameszeroX/XKeen/pull/49), [#50](https://github.com/jameszeroX/XKeen/pull/50), [#51](https://github.com/jameszeroX/XKeen/pull/51), [#52](https://github.com/jameszeroX/XKeen/pull/52) - [@oviron](https://github.com/oviron)
- Удалены неиспользуемые параметры запуска `-rrk`, `-rrx`, `-rrm`, `-drk`, `-drx`, `-drm`
- Рефакторинг кода скриптов
1 В роутинге, используя параметр `source`, вы можете определить разные правила маршрутизации для разных устройств, а пользовательские политики дают возможность задать для них разные порты проксирования, например, для торрент-клиента можно сделать политику с `80,443` портами проксирования, для телефонов политику с портами `80,443,596:599,1400,3478,5222`, а для игровых устройств с более широким набором портов. Для работы с пользовательскими политиками, они должы быть определены в конфигурационном файле `/opt/etc/xkeen/xkeen.json`, а также созданы в интерфейсе роутера с теми же именами. Пользовательские политики подключаются только если в интерфейсе роутера создана дефолтная политика `xkeen`, иначе пользовательские политики игнорируются и проксирование запускается для всех клиентов роутера. В пользовательских политиках, в отличие от политики `xkeen`, не проверяется наличие обязательных портов проксирования `80,443`, и вы можете формировать произвольный их список. Пример конфигурационного файла (допустимы любые имена пользовательских политик):
```
{
"xkeen": {
"gh_proxy": "",
"policy": [
{
"name": "xkeen0",
"port": ""
},
{
"name": "xkeen1",
"port": "80,443,596:599,1400,3478,5222"
},
{
"name": "xkeen2",
"port": "!7777,8888:9999"
}
]
}
}
```
- Политика `xkeen0` - проксирование на всех портах
- Политика `xkeen1` - проксирование на перечисленных портах
- Политика `xkeen2` - проксирование на всех портах, кроме перечисленных
2 При включенном перехвате и проксировании DNS, корректная работа устройств вне политик XKeen возможна только, когда они находятся не в "Политике по умолчанию", а в кастомной политике с произвольным именем и в роутере не игнорируется DNS провайдера, либо добавлен любой внешний не шифрованный DNS (даже при использовании AdGuardHome и отключении прошивочного резолвера командой `opkg dns-override`)
3 XKeen создаёт и использует 3 сета IPv4 и 3 сета IPv6 IPSET
- `user_exclude`, `user_exclude6` - наполняются пользовательскими исключениями IP из файла `ip_exclude.lst`
- `geo_exclude`, `geo_exclude6` - наполняются подсетями России из загружаемых файлов `ru_exclude_ipv4.lst` и `ru_exclude_ipv6.lst`
- `ext_exclude`, `ext_exclude6` - наполняются любым сторонним приложением на ваш выбор, например, AdGuard Home или dnsmasq (см. документацию к этим приложениям). Данная возможность позволяет добавлять в исключения проксирования не IP, а доменные имена. Для совместимости со сторонними приложениями скрипт запуска XKeen переименован в S05xkeen
Файлы `ru_exclude_ipv4.lst` и `ru_exclude_ipv6.lst` загружаются при установке XKeen или командой `xkeen -gips`. Обновление этих файлов выполняется одновременно с геофайлами по планировщику либо вручную запуском `xkeen -ug`
4 Проксирование трафика Entware можно использовать для обновления компонентов XKeen при недоступности GitHub, либо для любых других приложений в Entware, не связанных с XKeen. Для проксирования трафика Entware требуется установка 255 метки на все исходящие подключения кроме blackhole и dns. При использовании ядра Xray добавьте маркировку к каждому подключению в outbounds включая direct:
```
"streamSettings": {
"sockopt": {
"mark": 255
}
}
```
При использовании ядра Mihomo добавьте маркировку глобально:
```
tproxy-port: 1181
log-level: silent
...
routing-mark: 255
```
либо к каждому proxies, proxy-providers и direct подключениям (см. документацию к Mihomo)
---
В связи с добавлением нового компонента IPSET и нового диалогового окна в мастер установки XKeen, рекомендуется для обновления XKeen не использовать штатную возможность по `-uk`, а установить Beta версию поверх предыдущей. В мастере установки при этом можете пропускать уже установленные компоненты, все ваши конфиги и настройки сохранятся
### Рекомендуемый порядок установки/обновления
```
opkg update && opkg upgrade && opkg install curl tar && cd /tmp
sh -c "$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)"
```
### Альтернативный вариант установки/обновления
```bash
opkg update && opkg upgrade && opkg install curl tar && cd /tmp
sh -c "$(curl -sSL https://cdn.jsdelivr.net/gh/jameszeroX/XKeen@main/install.sh)"
```
Штатный механизм обновления тоже работает, но он рекомендуется только для опытных пользователей
### Порядок обновления с предыдущей версии форка XKeen (только для опытных пользователей)
```
xkeen -stop # остановите прокси-клиент
xkeen -channel # переключитесь на канал разработки
xkeen -uk # проверьте и загрузите обновление
xkeen -k # выполните локальную переустановку (пункт 0)
xkeen -gips # установите, если нужно, сеты IPSET
xkeen -ugc # включите автообновление GeoIPSET по планировщику (опционально)
xkeen -start # запустите прокси-клиент
```
Последующие запуски команды `xkeen -uk` в канале разработки каждый раз загружают и обновляют бету XKeen на актуальную версию
================================================
FILE: wiki/DNS-over-VLESS.md
================================================
# DNS-over-VLESS — направляем DNS-трафик через прокси xray
> Источник: [jameszero.net/3398.htm](https://jameszero.net/3398.htm)
Безопасность в интернете с каждым днём становится всё более насущной необходимостью. Утечка личных данных может обойтись слишком дорого, поэтому о своей цифровой неприкосновенности лучше позаботиться заранее. И начать стоит, например, с защиты DNS-запросов. Обычно для этого используются такие протоколы, как **DNS-over-TLS**, **DNS-over-HTTPS** и другие. Я же предлагаю рассмотреть ещё один способ — направить DNS-трафик через прокси-сервер на базе Xray, назовём этот метод **DNS-over-VLESS**.
В сети немало инструкций по настройке самого Xray, но вот проброс DNS-трафика через него раскрыт недостаточно подробно. Попробуем восполнить этот пробел.
## Предварительные условия
Первоначальное условие — у вас уже должен быть настроен и функционировать прокси xray VLESS с XTLS Reality по одной из многочисленных инструкций. Все действия по его "прокачке" выполняем локально на компьютере или роутере, сервер xray не трогаем.
**Замечание от пользователя:**
> Во многих конфигурациях серверов xray есть секция для предотвращения проблем с локальной маршрутизацией, в ней блокируется "geoip:private", куда попадает дефолтный адрес DNS-резолвера - 127.0.0.53. Для нашей задачи данное правило необходимо удалить, либо направить 127.0.0.53 выше этого правила в директ.
## Структура конфигурации
Разобьём единый файл настроек xray **config.json** на файлы по разделам: **inbounds.json**, **outbounds.json**, **dns.json**, **routing.json**. Этих четырёх файлов достаточно для нашей задачи.
Для реализации задуманного необходимо использовать входящее подключение (inbounds), поддерживающее UDP-протокол, например, TProxy.
## Конфигурационные файлы
### inbounds.json
```json
{
"inbounds": [
{
"port": 1181,
"protocol": "dokodemo-door",
"settings": {
"network": "tcp,udp",
"followRedirect": true
},
"sniffing": {
"enabled": true,
"routeOnly": true,
"destOverride": ["http","tls","quic"]
},
"streamSettings": {
"sockopt": {"tproxy": "tproxy"}
},
"tag": "tproxy"
}
]
}
```
### outbounds.json
```json
{
"outbounds": [
{
"protocol": "vless",
"settings": {
"address": "***.***.***.***",
"port": 443,
"id": "****************************",
"encryption": "none",
"flow": "xtls-rprx-vision"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"serverName": "*********",
"publicKey": "****************************",
"shortId": "********",
"spiderX": "/"
},
"sockopt": {
"domainStrategy": "ForceIP"
}
},
"tag": "proxy"
},
{
"protocol": "freedom",
"streamSettings": {
"sockopt": {
"domainStrategy": "ForceIP"
}
},
"tag": "direct"
},
{
"protocol": "dns",
"tag": "dns-out"
}
]
}
```
### dns.json
```json
{
"dns": {
"tag": "dns-in",
"servers": [
"8.8.8.8"
],
"queryStrategy": "UseIP"
}
}
```
### routing.json
```json
{
"routing": {
"rules": [
{
"inboundTag": ["dns-in"],
"outboundTag": "proxy"
},
{
"port": 53,
"outboundTag": "dns-out"
},
{
"domain": [
"browserleaks",
"ip.me"
],
"outboundTag": "proxy"
},
{
"network": "tcp,udp",
"outboundTag": "direct"
}
]
}
}
```
## Проверка работоспособности
Настройка завершена, выполняем проверку.
Если ошибок не допущено, то проксируемый ресурс **browserleaks.com** покажет DNS страны нахождения вашего VPS. Не стоит бояться использования обычных DNS в рассмотренных конфигах, их трафик будет передан на VPS-сервер транспортом VLESS с TLS-шифрованием.
## Результат
В итоге результат получен, **DNS-over-VLESS** настроен и работает. Никто не прослушает ваши DNS-запросы к проксируемым сайтам и не подменит ответы по своему усмотрению. Для дополнительной защиты в интернете обратите внимание на возможность отключения WebRTC и QUIC в браузере.
================================================
FILE: wiki/FAQ.md
================================================
# FAQ по XKeen
> Источник: [jameszero.net/faq-xkeen.htm](https://jameszero.net/faq-xkeen.htm)
## Введение
**XKeen** — это набор скриптов для роутеров Keenetic с прокси-клиентом Xray-core/Mihomo, предназначенный для оптимизации производительности интернет-подключения и обеспечения его безопасности.
**Важно!** Если после обновления или установки XKeen получаете ошибку SIGSEGV с сегментацией, откатите ядро Xray командой `xkeen -ux`.
---
## Часто задаваемые вопросы
### 0. Внезапные проблемы с XKeen
**Q:** Настроил xkeen/xray, всё работало, ничего не трогал. Внезапно прокси упал. Что можно предпринять?
**A:** При внезапных проблемах причина часто в устаревшем ядре Xray. Рекомендуется перейти на форк XKeen или обновить ядро другим способом. Не выбирайте последние версии — они часто требуют отдельной настройки.
Общий порядок действий:
- Если хостер Aeza — обращайтесь к нему, а не на форум
- Роутеры mipsle-архитектуры: не используйте ядро xray 26.3.27
- Проверьте подключение к прокси через WiFi с другого устройства
- Убедитесь в актуальных версиях XKeen и Xray
- Измените uTLS отпечаток на `"fingerprint": "firefox"`
- Подберите другой SNI (маскировочный домен)
- Проверьте галку политики XKeen на провайдере
- Убедитесь, что QUIC не заблокирован
- Проверьте DoH/DoT серверы в роутере
- Отключен ли транзит DNS запросов
- Нет ли ошибок DNS в журнале
- Удалён ли компонент "Протокол IPv6"
- Проверьте параметр `"routeOnly": true` в inbounds
- Отключите частный MAC и приватный DNS на устройствах
- Удалите политику XKeen и запустите для всех клиентов
- Проверьте подключение к другому прокси-серверу
Если ничего не помогло, выполните `xkeen -diag` и прикрепите отчёт в телеграм-чат.
### 1. Обновление с оригинала на форк XKeen
**Q:** Как правильно обновляться с XKeen 1.1.3 на форк?
**A:** Рекомендуется установить форк поверх оригинала. Конфиги Xray сохранятся:
```shell
opkg update && opkg upgrade && opkg install curl tar
curl -OL https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz
tar -xvzf xkeen.tar.gz -C /opt/sbin > /dev/null && rm xkeen.tar.gz
xkeen -i
```
### 2. Различия в замерах скорости
**Q:** Почему при использовании xkeen замеры скорости сильно различаются или низкие?
**A:** Причина в производительности процессора роутера и особенностях работы Xray с потоком xtls-rprx-vision.
**Важно для достоверного результата:**
- Не ограничивайте порты проксирования 80 и 443
- Не используйте роутинг — направьте весь трафик на VPS
Xray не повторно шифрует HTTPS-трафик, но шифрует HTTP-трафик силами процессора. Различные сайты используют разные методики тестирования, поэтому результаты могут не быть корректными.
**Советы для повышения скорости:**
- Выполните оптимизацию сетевого стека с режимом BBR
- Обязательно используйте поток **xtls-rprx-vision**
- Используйте режим TProxy (самый быстрый — Redirect, но без UDP)
- Не тестируйте торрентами — это неправильная практика
### 3. Низкая скорость на бюджетных моделях
**Q:** На KN-3810/KN-3610 после установки XKeen скорость упала до 30-40 Мбит/с.
**A:** В бюджетных моделях установлены процессоры EcoNet (EN****) с очень низкой производительностью при Xray. Исправить нельзя, но можно:
- Ограничить порты проксирования 80 и 443
- Не подключать чрезмерное количество geosite/geoip баз
- Не использовать компонент "Классификация трафика"
### 4. Периодические разрывы соединения
**Q:** Почему при использовании xkeen меня периодически выбрасывает из SSH/RDP/VPN/звонков?
**A:** Xray не предназначен для потокового трафика и может обрывать устаревшие сессии.
**Решения:**
- Добавьте IP-адрес критичного сервера с маской /32 в `/opt/etc/xkeen/ip_exclude.lst`
- Ограничьте порты проксирования в `/opt/etc/xkeen/port_exclude.lst`
- Попробуйте переключиться с Xray на Mihomo (форк XKeen поддерживает оба)
### 5. Невозможно подключиться к серверу с приложения на телефоне
**Q:** Добавил телефон в политику XKeen, но не могу подключить к серверу приложением.
**A:** Вы пытаетесь подключиться к прокси, будучи уже подключённым к прокси. Исключите IP сервера из проксирования, добавив в `/opt/etc/xkeen/ip_exclude.lst` с маской /32.
### 6. Сайт определяет мой провайдерский IP
**Q:** Почему ChatGPT/whoer/dell определяют мой провайдерский IP, хотя ресурс добавлен в роутинг?
**A:** Некоторые ресурсы используют QUIC или WebRTC, через которые возможна утечка IP.
**Решения:**
- Отключите WebRTC и QUIC в браузере
- Заблокируйте UDP 443 правилом роутинга (TProxy/Mixed):
```json
{
"network": "udp",
"port": "443",
"outboundTag": "block"
}
```
- Заблокируйте в межсетевом экране Keenetic
- Некоторые сайты блокируют по языку браузера — переключите на иностранный
### 7. Как заблокировать рекламу на YouTube
**Q:** Как заблокировать рекламу на Youtube?
**A:** Роутингом и DNS этого не сделать — реклама вшита в видеопоток. Помогут только приложения (SmartTube, YouTube ReVanced, AdGuard) или покупка Premium.
### 8. Пропал доступ к SSH после перезагрузки
**Q:** После сбоя пропал доступ к SSH на роутере.
**A:** Перед установкой entware удалите компонент прошивки "Сервер SSH". Если ssh перестал работать:
1. Удалите файл `/opt/var/run/dropbear.pid`
2. Убедитесь в наличии `PORT=22` или `PORT=222` в `/opt/etc/config/dropbear.conf`
3. Перезагрузите роутер или выполните:
```shell
exec sh
exec /opt/etc/init.d/S51dropbear restart
```
**Для предотвращения сбоев** отредактируйте `/opt/etc/config/dropbear.conf`:
```shell
PIDFILE="/var/run/dropbear.pid"
```
### 9. ICMP пинги не проходят через прокси
**Q:** Ping и tracert к ресурсам из роутинга почему-то идут напрямую.
**A:** ICMP пинги работают на Сетевом уровне OSI, а прокси — на Прикладном. Они несовместимы.
### 10. Сайт tmdb не открывается
**Q:** Прописал tmdb.org в роутинг, но сайт всё равно не открывается.
**A:** Ресурс блокирует по GeoDNS. Решения:
- Используйте DNS без EDNS Client Subnet (например, dns.sb)
- В AdGuardHome включите подмену EDNS Client Subnet с IP прокси
- В Keenetic создайте запись для каждого домена с IP прокси-сервера
### 11. Забыл пароль панели 3x-ui (IP 127.0.0.1)
**Q:** Указал IP 127.0.0.1 в панели 3x-ui, теперь не могу зайти.
**A:** Используйте PuTTY с портфорвардингом:
1. SSH → Tunnels: Source port 65345, Destination localhost:65345
2. Нажмите Add и подключитесь
3. В браузере откройте `localhost:65345/путь` (не закрывая PuTTY)
### 12. XKeen не запускается после перезагрузки
**Q:** После перезагрузки XKeen не запускается или работает для всего устройства.
**A:** Entware может стартовать раньше инициализации прошивки. Подберите задержку:
```shell
xkeen -d 30
```
Или добавьте скрипт `/opt/etc/init.d/S99xkeenrestart` для перезапуска после включения интернета.
### 13. AdGuardHome показывает IP роутера вместо клиента
**Q:** В журнале AdGuard все клиенты имеют IP роутера, а не свои.
**A:** Убедитесь в параметре `"routeOnly": true` (Xray) или `"override-destination": false` (Mihomo).
Для XKeen 1.1.3.9 поместите скрипт `aghfix.sh` в `/opt/etc/ndm/netfilter.d/` и сделайте исполняемым:
```shell
chmod +x /opt/etc/ndm/netfilter.d/aghfix.sh
```
Перезагрузите роутер.
### 14. Использование Wireguard/OpenConnect вместо Xray
**Q:** Есть только Wireguard/OpenConnect. Можно ли использовать маршрутизацию Xray для них?
**A:** Да. Определите наименование интерфейса (`ip a`), затем в `outbounds.json` добавьте:
```json
{
"protocol": "freedom",
"streamSettings": {
"sockopt": {
"interface": "nwg0"
}
},
"tag": "newtag"
}
```
В `routing.json` добавьте правило с тегом `newtag`.
### 15. Раздача интернета через XKeen клиентам Wireguard-сервера
**Q:** На роутере Wireguard-сервер. Можно ли раздать через XKeen интернет его клиентам?
**A:** При XKeen для всего устройства — дополнительных настроек нет. При политике найдите номер политики и интерфейса в `startup-config`, затем:
```shell
ip hotspot policy Wireguard0 Policy0
system configuration save
```
Отменить:
```shell
ip hotspot policy Wireguard0 permit
system configuration save
```
### 16. Удалённый доступ к SSH через серый IP
**Q:** Использую KeenDNS. Можно ли получить удалённый доступ к SSH через серый IP?
**A:** Поднимите SSTP VPN-сервер. К нему подключайтесь по домену KeenDNS, после чего SSH роутера доступен по 192.168.1.1:22.
### 17. Разные маршруты для разных устройств в одной политике
**Q:** Можно ли настроить разные маршруты в одной политике XKeen?
**A:** Для WiFi-устройств создайте несколько домашних сетей и добавьте параметр `source` в правила маршрутизации.
Пример — разная маршрутизация для подсетей:
```json
{
"routing": {
"rules": [
{
"source": ["192.168.1.0/24"],
"domain": ["site1.ru", "site2.ru"],
"outboundTag": "vless-reality"
},
{
"source": ["192.168.2.0/24"],
"outboundTag": "vless-reality"
},
{
"network": "tcp,udp",
"outboundTag": "direct"
}
]
}
}
```
Или разные маршруты по IP-адресам устройств:
```json
{
"routing": {
"rules": [
{
"source": ["192.168.1.5"],
"outboundTag": "vless-reality-eu"
},
{
"source": ["192.168.1.6", "192.168.1.7"],
"outboundTag": "vless-reality-us"
}
]
}
}
```
### 18. Резервное копирование и восстановление
**Q:** Как выполнить резервное копирование XKeen?
**A:** Создайте архив:
```shell
opkg update && opkg upgrade && opkg install tar
tar --exclude=entware_backup.tar.gz --exclude=*.pid --warning=no-file-changed -cvzf /opt/entware_backup.tar.gz -C /opt .
```
Для восстановления отформатируйте флешку в ext4, создайте папку `install`, поместите архив, выберите флешку в OPKG.
### 19. Интернет пропадает через несколько часов работы
**Q:** Через часы работы интернет пропадает, хотя `xkeen -status` показывает работу прокси.
**A:** Прокси-клиент исчерпал лимит файловых дескрипторов. Проверьте:
```shell
ls -l /proc/$(pidof xray)/fd | wc -l
```
Форк XKeen с версии 1.1.3.6 контролирует fd. Включите:
```shell
xkeen -fd
```
И перезагрузите роутер.
### 20. Два прокси-сервера для разных сайтов
**Q:** Есть два прокси. Можно ли открывать одни сайты через первый, другие — через второй?
**A:** В `outbounds.json` добавьте оба сервера с разными тегами:
```json
{
"outbounds": [
{
"protocol": "vless",
"settings": {},
"tag": "proxy1"
},
{
"protocol": "vless",
"settings": {},
"tag": "proxy2"
},
{
"protocol": "freedom",
"tag": "direct"
}
]
}
```
В `routing.json` создайте правила:
```json
{
"routing": {
"rules": [
{
"domain": ["site1.ru"],
"outboundTag": "proxy1"
},
{
"domain": ["site2.ru"],
"outboundTag": "proxy2"
},
{
"network": "tcp,udp",
"outboundTag": "direct"
}
]
}
}
```
### 21. Отказоустойчивость прокси
**Q:** Можно ли сделать fallback на второй прокси, если первый недоступен?
**A:** Да, используйте встроенный механизм балансировки Xray. Подробности в статьях сайта об отказоустойчивом прокси-сервере.
### 22. Два провайдера — подключение через один
**Q:** К роутеру подключены два провайдера. Как сделать подключение к прокси только через одного?
**A:** Аналогично пункту 14. В `outbounds.json` добавьте параметр `"interface"` с указанием интерфейса.
Пример для direct:
```json
{
"protocol": "freedom",
"streamSettings": {
"sockopt": {
"interface": "lte_br0"
}
},
"tag": "direct"
}
```
Пример для прокси:
```json
{
"protocol": "vless",
"settings": {},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {},
"sockopt": {
"interface": "lte_br0"
}
},
"tag": "proxy"
}
```
Уточните название интерфейса командой `ifconfig`.
### 23. Не работает голос в Discord/Telegram/WhatsApp
**Q:** Не работают голосовые чаты/пинг 5000 etc.
**A:** Discord использует UDP 50000-50030, Telegram/WhatsApp используют UDP 596:599, 1400, 3478, 5222. Чтобы голос заработал, добавьте в `routing.json` правило для этого порта, если у вас в конце все подключения идут direct:
```json
{
"routing": {
"rules": [
{
"network": "udp",
"port": "50000-50030,596-599,1400,3478,5222",
"outboundTag": "vless-reality"
}
]
}
}
```
Если подключения всё в vless-reality, кроме РФ, то добавить порты в проксирование будет достаточно:
```shell
xkeen -ap 80,443,2053:2096,8443,19200:19400,50000:50030,596:599,1400,3478,5222
```
**Примечание:** Если пинг всё равно высокий или идут обрывы, то есть проблемы с маршрутизацией UDP-трафика - альтернатива:
Плюсы такого решения - можно не добавлять порты проксирования 50000+ и оставить только 80,443 (на socks5 эти ограничения не распространяются), таким образом торренты у вас точно не пойдут в прокси, а может ещё стабильность и пинг в дискорде улучшатся, но это не точно)
После установки Discord, запустите DiscordProxyInstaller.exe он спросит IP роутера и порт socks5-прокси, и Discord станет работать через него. Socks5-прокси, разумеется, должен быть поднят в xray на роутере (не прокси клиент Кинетика, а просто прописан в inbounds.json). для этого добавьте в inbounds.json:
```json
{
"port": 1080,
"protocol": "mixed",
"settings": {"udp": true},
"tag": "socks"
},
```
routing.json:
```json
{
"inboundTag": ["socks"],
"outboundTag": "vless-reality",
"type": "field"
},
```
### 24. Gemini определяет страну РФ
**Q:** Почему Gemini определяет страну РФ?
**Причина:** Если использовать ру аккаунт google через proxy, то google помечает ip как proxy, и определяет страну как РФ. Смена аккаунта не всегда помогает.
**A:** Проксировать gemini через warp-native . Дефолтный Warp xray/3x-ui не всегда отрабатывает корректно. Warp-native устанавливается на VPS.
### 25. Хочу перейти на ядро mihomo, какие rule sets использовать?
**A:** Вот несколько вариантов:
- rule-sets от "zxc-rv"
- rule-sets от "legiz-ru"
### 26. Не работают правила mihomo, что делать?
**A:** - проверяйте корректность настроек
================================================
FILE: wiki/Home.md
================================================
# XKeen
**XKeen** — POSIX-shell утилита для прозрачной маршрутизации сетевого трафика через прокси-движки **Xray** и **Mihomo** на роутерах **Keenetic** / **Netcraze**. Позволяет выборочно направлять TCP/UDP-трафик отдельных клиентов через прокси, не затрагивая остальную сеть.
## Зачем нужен XKeen
- **Обход блокировок** на уровне роутера: одна точка настройки для всех домашних устройств — телефоны, ТВ, консоли, ПК.
- **Гибкая маршрутизация**: разные клиенты могут идти через разные прокси-серверы или напрямую, в зависимости от политик XKeen, IP-адресов и доменов.
- **Защита DNS-запросов**: возможна организация DNS-over-VLESS — DNS-трафик передаётся туннелем Xray, что исключает прослушку и подмену ответов на пути от роутера до прокси. См. [DNS-over-VLESS](DNS-over-VLESS).
- **DSCP-маршрутизация**: проксирование/исключение конкретных приложений по QoS-меткам Windows. См. [Маршрутизация по DSCP](Маршрутизация-по-DSCP).
- **Два ядра в одном инструменте**: переключение между Xray и Mihomo через `xkeen -xray` / `xkeen -mihomo`.
## Поддерживаемые архитектуры
- `arm64-v8a` — старшие модели Keenetic/Netcraze
- `mips32le` — большинство моделей
- `mips32` — старые модели
## Быстрый старт
На роутере под Entware:
```sh
opkg update && opkg upgrade && opkg install curl tar
curl -OL https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz
tar -xvzf xkeen.tar.gz -C /opt/sbin > /dev/null && rm xkeen.tar.gz
xkeen -i
```
Дальше следуйте интерактивному установщику: выбор ядра (Xray / Mihomo / оба), установка geofile, настройка автообновлений.
## Дальнейшие шаги
- **[FAQ](FAQ)** — 22 ответа на самые частые вопросы: проблемы с производительностью, DNS, утечки IP, восстановление после сбоев, маршруты для разных подсетей.
- **[DNS-over-VLESS](DNS-over-VLESS)** — пошаговая настройка проксирования DNS через VLESS.
- **[Маршрутизация по DSCP](Маршрутизация-по-DSCP)** — маршрутизация приложений Windows по QoS-меткам (XKeen 2.0+).
## Поддержка и обратная связь
Команды на роутере:
- `xkeen -h` — встроенная справка по флагам.
- `xkeen -diag` — полная диагностика. **Единственный поддерживаемый канал для отчёта о проблеме.**
- `xkeen -af` — контакты разработчиков.
- `xkeen -ad` — поддержать разработчиков.
================================================
FILE: wiki/_Footer.md
================================================
---
[Сайт автора форка — jameszero.net](https://jameszero.net/) · [Обсуждение на forum.keenetic.ru](https://forum.keenetic.ru/topic/16899-xkeen/)
================================================
FILE: wiki/_Sidebar.md
================================================
**Навигация**
- [Главная](Home)
- [FAQ](FAQ)
- [DNS-over-VLESS](DNS-over-VLESS)
- [Маршрутизация по DSCP](Маршрутизация-по-DSCP)
================================================
FILE: wiki/Маршрутизация-по-DSCP.md
================================================
# Маршрутизация по DSCP-меткам в XKeen
> Источник: [jameszero.net/4509.htm](https://jameszero.net/4509.htm)
В XKeen 2.0 появилась возможность маршрутизации по DSCP-меткам QoS. Это позволяет исключить конкретные приложения из проксирования или направить их трафик через прокси на всех портах, что полезно когда компьютер имеет ограничения на порты 80 и 443.
## Настройка в Windows
### Предварительные требования
Требуется полная редакция Windows с поддержкой Group Policy. В начальных редакциях настройка возможна только через реестр.
### Этап 1: Изменение реестра
Примените следующий твик реестра:
```
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\QoS]
"Do not use NLA"="1"
```
Перезагрузите компьютер после применения.
### Этап 2: Проверка параметров сетевой карты
Убедитесь, что включена опция "Планировщик пакетов QoS" в настройках сетевого адаптера.
### Этап 3: Создание QoS-политики
1. Нажмите Win+R и выполните `gpedit.msc`
2. Откройте "QoS на основе политики"
3. В меню "Действие" выберите "Создать новую политику"
4. Пройдите мастер, указав имя политики, DSCP-метку, приложение, IP-адреса, протоколы и порты
XKeen поддерживает метки 62 (исключение) и 63 (проксирование) по умолчанию, но их можно заменить в переменных стартового скрипта.
После создания политики маршрутизация работает сразу без перезагрузки (достаточно перезапустить приложение).
Каждое приложение требует отдельной политики.