Showing preview only (456K chars total). Download the full file or copy to clipboard to get everything.
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`
<sup>Уточните актуальность крипто-адресов перед переводом</sup>
---
## Дополнения
- 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) и автоматически синхронизируются в `<repo>.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/<github_url>`.
3. `gh_proxy2` префиксом — `https://ghfast.top/<github_url>`.
С версии 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/` синхронизировано в `<repo>.wiki.git` подписанным коммитом |
Шаги:
1. Checkout главного репо.
2. Импорт GPG-ключа (тот же `crazy-max/ghaction-import-gpg@v7`).
3. Клонирование `<repo>.wiki.git` через `https://x-access-token:${GITHUB_TOKEN}@github.com/<repo>.wiki.git`.
4. `rsync -a --delete --exclude='.git' wiki/ wiki-repo/` — добавление, обновление, удаление.
5. Подписанный коммит `[github-actions] sync wiki from main@<short-sha>` и push в дефолтную ветку Wiki.
Пререкизиты для прода:
- В Settings → Features → Wikis: ✅ enabled.
- В Wiki создана хотя бы одна страница через UI (иначе `<repo>.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_<phase>_import.sh`.
2. Создать файл `NN_<purpose>.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 <<USER_POLICIES_EOF
$user_policies
USER_POLICIES_EOF
# Политика xkeen (стандартная)
if [ -n "$policy_mark" ]; then
# заданы порты проксирования
if [ -n "$port_donor" ]; then
add_multiport_rules "$family" "$table" "$net" "$policy_mark" "$port_donor" "$name_chain"
# заданы порты исключения
elif [ -n "$port_exclude" ]; then
add_multiport_rules "$family" "$table" "$net" "$policy_mark" "$port_exclude" "RETURN"
set -- -m connmark --mark "$policy_mark" -m conntrack ! --ctstate INVALID -p "$net" $comment -j "$name_chain"
ipt -A PREROUTING "$@" >/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 "
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
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (463K chars).
[
{
"path": ".gitattributes",
"chars": 170,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Unix files that are always LF\n*.md text eol=lf\n*.sh"
},
{
"path": ".github/workflows/package-folder.yaml",
"chars": 1719,
"preview": "name: Create Test build to `main/test/` folder\n\non:\n push:\n branches:\n - main\n paths:\n - 'scripts/**'\n "
},
{
"path": ".github/workflows/release.yaml",
"chars": 3160,
"preview": "name: Create Release\n\non:\n workflow_dispatch:\n inputs:\n version:\n description: 'Version number (e.g., 1."
},
{
"path": ".github/workflows/wiki-sync.yaml",
"chars": 1428,
"preview": "name: Sync GitHub Wiki\n\non:\n push:\n branches:\n - main\n paths:\n - 'wiki/**'\n - '.github/workflows/w"
},
{
"path": ".gitignore",
"chars": 50,
"preview": ".claude\ngraphify-out\ndone\nsrc/graphify\nsrc/output\n"
},
{
"path": "01_info_variable.sh",
"chars": 5112,
"preview": "# -------------------------------------\n# Цвета\n# -------------------------------------\ngreen=\"\\033[92m\"\t# Зеленый\nred=\""
},
{
"path": "LICENSE",
"chars": 1497,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2026, jameszeroX\n\nRedistribution and use in source and binary forms, with or without"
},
{
"path": "README.md",
"chars": 5176,
"preview": "# XKeen 1.1.3.9\n\n> **XKeen** — утилита для выборочной маршрутизации сетевого трафика через прокси‑движки **Xray** и **Mi"
},
{
"path": "configuration.md",
"chars": 5147,
"preview": "---\n\n## Внешние списки портов и IP\n\nПредусмотрена возможность добавить в конфигурационные файлы XKeen необходимые порты "
},
{
"path": "docs/README.md",
"chars": 1558,
"preview": "# Документация XKeen\n\nЭтот каталог содержит техническую документацию для разработчиков и контрибьюторов. Если вы пользов"
},
{
"path": "docs/architecture.md",
"chars": 5109,
"preview": "# Архитектура XKeen\n\nXKeen — POSIX-shell утилита (`sh`, не `bash`) для роутеров Keenetic/Netcraze под Entware. Кода на к"
},
{
"path": "docs/build-and-release.md",
"chars": 3587,
"preview": "# Сборка и релиз\n\nЛокальной сборки нет. Всё делает CI на GitHub Actions. В этом разделе — три workflow-а и две схемы кан"
},
{
"path": "docs/commands.md",
"chars": 3823,
"preview": "# Справочник флагов `xkeen`\n\nПолный список флагов диспетчера [`scripts/xkeen`](../scripts/xkeen). Извлечён из `help_xkee"
},
{
"path": "docs/contributing.md",
"chars": 4464,
"preview": "# Правила правки\n\n## Язык — POSIX `sh`\n\nЦелевая среда — Entware на BusyBox `ash`. **Никаких bash-измов:**\n\n| Запрещено |"
},
{
"path": "docs/runtime-paths.md",
"chars": 3110,
"preview": "# Раскладка на роутере\n\nВсе runtime-пути на роутере. В этом репозитории каталог `_xkeen/`; после установки на роутер он "
},
{
"path": "forkinfo.md",
"chars": 5972,
"preview": "## Сравнение форка с оригинальным XKeen\n\nИзменения:\n- Исправлено добавление портов в исключения (ранее команду `xkeen -a"
},
{
"path": "install.sh",
"chars": 3459,
"preview": "#!/bin/sh\n\ngreen=\"\\033[92m\"\nred=\"\\033[91m\"\nyellow=\"\\033[93m\"\nlight_blue=\"\\033[96m\"\nreset=\"\\033[0m\"\n\nurl_stable=\"https://"
},
{
"path": "knownissues.md",
"chars": 305,
"preview": "- При проксировании DNS с помощью XKeen, в профиле \"Политика по умолчанию\" отсутствует интернет, создайте пользовательсу"
},
{
"path": "scripts/_xkeen/01_info/00_info_import.sh",
"chars": 369,
"preview": "# Импорт информационных модулей\n\n# Модуль информации\n. \"$xinfo_dir/01_info_variable.sh\"\n. \"$xinfo_dir/02_info_packages.s"
},
{
"path": "scripts/_xkeen/01_info/01_info_variable.sh",
"chars": 6277,
"preview": "# -------------------------------------\n# Цвета\n# -------------------------------------\ngreen=\"\\033[92m\"\t# Зеленый\nred=\""
},
{
"path": "scripts/_xkeen/01_info/02_info_packages.sh",
"chars": 1538,
"preview": "# Кэшируем список установленных пакетов один раз вместо opkg-форка на каждую проверку\n_packages_cache=$(opkg list-instal"
},
{
"path": "scripts/_xkeen/01_info/03_info_cpu.sh",
"chars": 919,
"preview": "# Функция для получения информации о процессоре\ninfo_cpu() {\n if command -v opkg >/dev/null 2>&1; then\n opkg_a"
},
{
"path": "scripts/_xkeen/01_info/04_info_mihomo.sh",
"chars": 223,
"preview": "# Функция для проверки установки Mihomo\n\ninfo_mihomo() {\n if [ -f \"$install_dir/mihomo\" ] && [ -f \"$install_dir/yq\" ]"
},
{
"path": "scripts/_xkeen/01_info/04_info_xray.sh",
"chars": 185,
"preview": "# Функция для проверки установки Xray\n\ninfo_xray() {\n if [ -f \"$install_dir/xray\" ]; then\n xray_installed=\"ins"
},
{
"path": "scripts/_xkeen/01_info/05_info_geofile.sh",
"chars": 838,
"preview": "# Функция для проверки наличия и записи информации о базах GeoSite\ninfo_geosite() {\n update_refilter_geosite=false\n "
},
{
"path": "scripts/_xkeen/01_info/06_info_console.sh",
"chars": 7956,
"preview": "print_log_status() {\n local status_code=$1\n local success_msg=$2\n local error_msg=$3\n\n if [ \"$status_code\" -"
},
{
"path": "scripts/_xkeen/01_info/07_info_cron.sh",
"chars": 414,
"preview": "# Проверка наличия задач автоматического обновления в cron\ninfo_cron() {\n # Получаем текущую crontab конфигурацию для"
},
{
"path": "scripts/_xkeen/01_info/08_info_version/00_version_import.sh",
"chars": 211,
"preview": "# Импорт модулей проверки версий\n\n# Модули проверки версий\n. \"$xinfo_dir/08_info_version/01_version_xkeen.sh\"\n. \"$xinfo_"
},
{
"path": "scripts/_xkeen/01_info/08_info_version/01_version_xkeen.sh",
"chars": 1327,
"preview": "# Функция для получения версии из xkeen API и сохранения ее в переменной\ninfo_version_xkeen() {\n version=$(eval curl "
},
{
"path": "scripts/_xkeen/01_info/08_info_version/02_version_mihomo.sh",
"chars": 692,
"preview": "# Функции для получения информации о версиях Mihomo и Yq\ninfo_version_mihomo() {\n if [ \"$mihomo_installed\" = \"install"
},
{
"path": "scripts/_xkeen/01_info/08_info_version/02_version_xray.sh",
"chars": 462,
"preview": "# Функция для получения информации о версии Xray\ninfo_version_xray() {\n\n # Проверяем, установлен ли Xray\n if [ \"$x"
},
{
"path": "scripts/_xkeen/02_install/00_install_import.sh",
"chars": 438,
"preview": "# Импорт модулей установки\n\n# Модули установки\n. \"$xinstall_dir/01_install_packages.sh\"\n. \"$xinstall_dir/02_install_xray"
},
{
"path": "scripts/_xkeen/02_install/01_install_packages.sh",
"chars": 776,
"preview": "# Установка необходимых пакетов\ninstall_packages() {\n package_status=\"$1\"\n package_name=\"$2\"\n\n if [ \"${package_"
},
{
"path": "scripts/_xkeen/02_install/02_install_mihomo.sh",
"chars": 1317,
"preview": "# Функция для установки Mihomo\ninstall_mihomo() {\n echo -e \" ${yellow}Выполняется установка${reset} Mihomo. Пожалуйс"
},
{
"path": "scripts/_xkeen/02_install/02_install_xray.sh",
"chars": 1954,
"preview": "# Функция для установки Xray\ninstall_xray() {\n echo -e \" ${yellow}Выполняется установка${reset} Xray. Пожалуйста, по"
},
{
"path": "scripts/_xkeen/02_install/03_install_xkeen.sh",
"chars": 820,
"preview": "# Функция для установки XKeen\ninstall_xkeen() {\n xkeen_archive=\"$ktmp_dir/xkeen.tar.gz\"\n\n # Проверка наличия архив"
},
{
"path": "scripts/_xkeen/02_install/04_install_geofile.sh",
"chars": 5033,
"preview": "# Функция для загрузки и обработки геофайлов\nprocess_geo_file() {\n local url=\"$1\"\n local filename=\"$2\"\n local d"
},
{
"path": "scripts/_xkeen/02_install/05_install_geoipset.sh",
"chars": 4915,
"preview": "# Валидаторы для fetch_with_mirrors: проверяют размер + базовый синтаксис\n# содержимого (catch HTML-stub и мусор от prox"
},
{
"path": "scripts/_xkeen/02_install/06_install_cron.sh",
"chars": 792,
"preview": "# Функция для установки задач Cron\ninstall_cron() {\n cron_entry=\n\n # Добавление задачи Cron для обновления GeoFile"
},
{
"path": "scripts/_xkeen/02_install/07_install_register/00_register_common.sh",
"chars": 1526,
"preview": "# Общие функции для регистрации пакетов в opkg\n\nwrite_opkg_control() {\n package_name=\"$1\"\n package_version=\"$2\"\n "
},
{
"path": "scripts/_xkeen/02_install/07_install_register/00_register_import.sh",
"chars": 347,
"preview": "# Импорт модулей регистраций\n\t\n# Модули регистрации\n. \"$xinstall_dir/07_install_register/00_register_common.sh\"\n. \"$xins"
},
{
"path": "scripts/_xkeen/02_install/07_install_register/01_register_mihomo.sh",
"chars": 1659,
"preview": "# Регистрация Mihomo\n\nregister_mihomo_list() {\n cd \"$register_dir/\" || exit\n touch mihomo_s.list\n echo \"/opt/sb"
},
{
"path": "scripts/_xkeen/02_install/07_install_register/01_register_xray.sh",
"chars": 1227,
"preview": "# Регистрация xray\nregister_xray_control() {\n write_opkg_control \\\n \"xray_s\" \\\n \"$xray_current_version\""
},
{
"path": "scripts/_xkeen/02_install/07_install_register/02_register_xkeen.sh",
"chars": 5581,
"preview": "# Регистрация XKeen\n\n# Функция для создания файла xkeen.control\nregister_xkeen_control() {\n write_opkg_control \\\n "
},
{
"path": "scripts/_xkeen/02_install/07_install_register/03_register_cron.sh",
"chars": 2707,
"preview": "# Функция для регистрации инициализационного скрипта cron\nregister_cron_initd() {\n # Проверка наличия пакета cron\n "
},
{
"path": "scripts/_xkeen/02_install/07_install_register/04_register_init.sh",
"chars": 77550,
"preview": "#!/bin/sh\n\n# Информация о службе: Запуск / Остановка XKeen\n# Версия: 2.30\n\n# Окружение\nPATH=\"/opt/bin:/opt/sbin:/sbin:/b"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/00_configs_import.sh",
"chars": 113,
"preview": "# Импорт модулей конфигураций\n\t\n# Модуль конфигурации\n. \"$xinstall_dir/08_install_configs/01_configs_install.sh\"\n"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/01_configs_install.sh",
"chars": 514,
"preview": "# Функция для установки файлов конфигурации Xray\ninstall_configs() {\n if [ ! -d \"$xray_conf_dir\" ]; then\n mkdi"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/01_log.json",
"chars": 151,
"preview": "{\n \"log\": {\n \"access\": \"/opt/var/log/xray/access.log\",\n \"error\": \"/opt/var/log/xray/error.log\",\n \"dnsLog\": tru"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/02_dns.json",
"chars": 56,
"preview": "{\n// Пример настройки - https://jameszero.net/3398.htm\n}"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/03_inbounds.json",
"chars": 671,
"preview": "{\n \"inbounds\": [\n {\n \"port\": 1181,\n \"protocol\": \"tunnel\",\n \"settings\": {\n \"network\": \"tcp\",\n "
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/04_outbounds.json",
"chars": 84,
"preview": "{\n// Создайте файл по ссылке https://zxc-rv.github.io/XKeen-UI/Outbound_Generator/\n}"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/05_routing.json",
"chars": 73,
"preview": "{\n// Создайте файл по ссылке https://xray-routing-generator.netlify.app\n}"
},
{
"path": "scripts/_xkeen/02_install/08_install_configs/02_configs_dir/06_policy.json",
"chars": 115,
"preview": "{\n \"policy\": {\n \"levels\": {\n \"0\": {\n \"uplinkOnly\": 0,\n \"downlinkOnly\": 0\n }\n }\n }\n}"
},
{
"path": "scripts/_xkeen/03_delete/00_delete_import.sh",
"chars": 268,
"preview": "# Импорт модулей удаления\n\n# Модули удаления\n. \"$xdelete_dir/01_delete_geofile.sh\"\n. \"$xdelete_dir/02_delete_geoipset.sh"
},
{
"path": "scripts/_xkeen/03_delete/01_delete_geofile.sh",
"chars": 1186,
"preview": "# Функция для удаления выбранных файлов GeoSite\ndelete_geosite() {\n [ \"$choice_delete_geosite_refilter_select\" = \"tru"
},
{
"path": "scripts/_xkeen/03_delete/02_delete_geoipset.sh",
"chars": 1405,
"preview": "# Функция для удаления GeoIPSET\ndelete_geoipset() {\n while true; do\n printf \"\\n Желаете удалить российские IP"
},
{
"path": "scripts/_xkeen/03_delete/03_delete_cron.sh",
"chars": 291,
"preview": "# Функция для удаления cron задачи для GeoFile\ndelete_cron_geofile() {\n if [ -f \"$cron_dir/$cron_file\" ]; then\n "
},
{
"path": "scripts/_xkeen/03_delete/04_delete_configs.sh",
"chars": 173,
"preview": "# Удаление всех конфигураций Xray\n\ndelete_configs() {\n if [ -d \"$xray_conf_dir\" ]; then\n find \"$xray_conf_dir\""
},
{
"path": "scripts/_xkeen/03_delete/05_delete_register.sh",
"chars": 1499,
"preview": "# Удаление регистрации Xray\ndelete_register_xray() {\n # Удаляем соответствующие записи из файла статуса opkg\n sed "
},
{
"path": "scripts/_xkeen/03_delete/06_delete_tmp.sh",
"chars": 1750,
"preview": "# Удаление временных файлов и директорий\ndelete_tmp() {\n [ -d \"$ktmp_dir\" ] && rm -rf \"$ktmp_dir\"\n [ -d \"$xtmp_dir"
},
{
"path": "scripts/_xkeen/04_tools/00_tools_import.sh",
"chars": 410,
"preview": "\n# Дополнительные инструменты\n. \"$xtools_dir/01_tools_ports.sh\"\n. \"$xtools_dir/02_tools_modules.sh\"\n. \"$xtools_dir/03_to"
},
{
"path": "scripts/_xkeen/04_tools/01_tools_ports.sh",
"chars": 7605,
"preview": "read_ports_file() {\n file=\"$1\"\n\n [ -f \"$file\" ] || return\n\n sed 's/\\r$//' \"$file\" | \\\n sed 's/^[[:space:]]*/"
},
{
"path": "scripts/_xkeen/04_tools/02_tools_modules.sh",
"chars": 329,
"preview": "show_deprecation_warning() {\n echo -e \" ${red}Внимание!${reset} Команда устарела и удалена из XKeen\"\n echo -e \" "
},
{
"path": "scripts/_xkeen/04_tools/03_tools_diagnostic.sh",
"chars": 7243,
"preview": "diagnostic() {\n # Установка пути к файлу diagnostic\n diagnostic=\"/opt/diagnostic.txt\"\n\n if pidof \"xray\" >/dev/n"
},
{
"path": "scripts/_xkeen/04_tools/04_tools_delay.sh",
"chars": 1349,
"preview": "get_current_delay() {\n awk -F= '/^[[:space:]]*start_delay=/{print $2; exit}' \"$1\" | tr -d '[:space:]\"'\n}\n\ndelay_autos"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/00_choice_import.sh",
"chars": 326,
"preview": "# Импорт модулей выбора пользователя\n\n# Модули выбора\n. \"$xtools_dir/05_tools_choice/01_choice_cores.sh\"\n. \"$xtools_dir/"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/01_choice_cores.sh",
"chars": 4038,
"preview": "# Запрос на добавление ядер проксирования\nchoice_add_proxy_cores() {\n while true; do\n echo\n echo -e \" "
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/02_choice_xkeen.sh",
"chars": 8529,
"preview": "# Запрос на смену канала обновлений XKeen (Stable/Dev)\nchoice_channel_xkeen() {\n echo\n echo -e \" Текущий канал об"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/03_choice_geofile.sh",
"chars": 8634,
"preview": "choice_geodata() {\n type=\"$1\"\n type_name=\"$2\"\n src3=\"$3\"\n src3_name=\"$4\"\n var_bypass=\"$5\"\n\n has_missin"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/04_choice_input.sh",
"chars": 3918,
"preview": "# Функция для выбора пользователя между \"Да\" и \"Нет\" с номерами 0 и 1\ninput_concordance_list() {\n prompt_message=\" $"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/00_cron_import.sh",
"chars": 183,
"preview": "# Импорт модулей вопросов cron\n\n# Модули вопросов cron\n. \"$xtools_dir/05_tools_choice/05_choice_cron/01_cron_status.sh\"\n"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/01_cron_status.sh",
"chars": 3521,
"preview": "# Определение статуса для задач cron\nget_existing_cron_time() {\n crontab -l 2>/dev/null | grep 'xkeen -ug' | head -n1"
},
{
"path": "scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/02_cron_time.sh",
"chars": 2270,
"preview": "# Определение времени для задач cron\nchoice_cron_time() {\n [ \"$choice_geofile_cron_select\" = true ] || return\n\n ec"
},
{
"path": "scripts/_xkeen/04_tools/06_tools_backups/00_backups_import.sh",
"chars": 247,
"preview": "# Импорт модулей резервного копирования\n\n# Модули резервного копирования\n. \"$xtools_dir/06_tools_backups/01_backups_xkee"
},
{
"path": "scripts/_xkeen/04_tools/06_tools_backups/01_backups_xkeen.sh",
"chars": 1734,
"preview": "# Создание резервной копии XKeen\nbackup_xkeen() {\n if choice_backup_xkeen && [ -z \"$manual_backup\" ]; then\n re"
},
{
"path": "scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_mihomo.sh",
"chars": 1094,
"preview": "backup_configs_mihomo() {\n backup_filename=\"${current_datetime}_configs_mihomo\"\n backup_configs_dir=\"$backups_dir/"
},
{
"path": "scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_xray.sh",
"chars": 1074,
"preview": "backup_configs_xray() {\n backup_filename=\"${current_datetime}_configs_xray\"\n backup_configs_dir=\"$backups_dir/$bac"
},
{
"path": "scripts/_xkeen/04_tools/07_tools_downloaders/00_downloaders_import.sh",
"chars": 357,
"preview": "# Импорт модулей загрузки\n\n# fetch_with_mirrors / probe_with_mirrors уже подгружены из xkeen/import.sh\n# (раньше install"
},
{
"path": "scripts/_xkeen/04_tools/07_tools_downloaders/00_fetch_with_mirrors.sh",
"chars": 6915,
"preview": "# Загрузка с per-call mirror-fallback'ом.\n#\n# Заменяет паттерн \"test_github -> один gh_proxy на сессию -> один curl\n# бе"
},
{
"path": "scripts/_xkeen/04_tools/07_tools_downloaders/01_downloaders_mihomo.sh",
"chars": 7435,
"preview": "# Загрузка Mihomo\ndownload_mihomo() {\n USE_JSDELIVR=\"\"\n printf \" ${green}Запрос информации${reset} о релизах ${ye"
},
{
"path": "scripts/_xkeen/04_tools/07_tools_downloaders/01_downloaders_xray.sh",
"chars": 8523,
"preview": "# Загрузка Xray\ndownload_xray() {\n USE_JSDELIVR=\"\"\n printf \" ${green}Запрос информации${reset} о релизах ${yellow"
},
{
"path": "scripts/_xkeen/04_tools/07_tools_downloaders/02_donwloaders_xkeen.sh",
"chars": 447,
"preview": "# Загрузка XKeen\ndownload_xkeen() {\n mkdir -p \"$ktmp_dir\"\n printf \" ${yellow}Выполняется загрузка${reset} XKeen\\n"
},
{
"path": "scripts/_xkeen/05_tests/00_tests_import.sh",
"chars": 161,
"preview": "# Импорт модулей тестирования\n\n# Модули тестирования\n. \"$xtests_dir/01_tests_connected.sh\"\n. \"$xtests_dir/02_tests_xport"
},
{
"path": "scripts/_xkeen/05_tests/01_tests_connected.sh",
"chars": 3643,
"preview": "# Функция проверки доступности интернета\ntest_connection() {\n nslookup \"$conn_URL\" >/dev/null 2>&1 && return 0\n cu"
},
{
"path": "scripts/_xkeen/05_tests/02_tests_xports.sh",
"chars": 2740,
"preview": "# Определение на каких портах слушает ядро прокси\ntests_ports_client() {\n\n if pidof \"xray\" >/dev/null; then\n n"
},
{
"path": "scripts/_xkeen/05_tests/03_tests_storage.sh",
"chars": 1535,
"preview": "# Определение места установки Entware\nlocation_entware_storage() {\n mount_point=$(mount | grep 'on /opt ')\n device"
},
{
"path": "scripts/_xkeen/about.sh",
"chars": 9808,
"preview": "about_xkeen() {\n echo\n printf \" Утилита ${green}XKeen${reset} предназначена для управления межсетевым\\n экраном "
},
{
"path": "scripts/_xkeen/import.sh",
"chars": 890,
"preview": "# Импорт основных модулей и определение их путей\n\nscript_dir=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nxinfo_dir=\"$script_dir/.xk"
},
{
"path": "scripts/xkeen",
"chars": 46925,
"preview": "#!/bin/sh\n\n# Определение директории, где находится xkeen\nscript_dir=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\n# Скрываем основну"
},
{
"path": "test/README.md",
"chars": 9702,
"preview": "## XKeen 2.0 Beta\n\n> [!NOTE]\n> Это версия из канала разработки. Она регулярно дорабатывается, содержит новейшие функции,"
},
{
"path": "wiki/DNS-over-VLESS.md",
"chars": 4295,
"preview": "# DNS-over-VLESS — направляем DNS-трафик через прокси xray\n\n> Источник: [jameszero.net/3398.htm](https://jameszero.net/3"
},
{
"path": "wiki/FAQ.md",
"chars": 14354,
"preview": "# FAQ по XKeen\n\n> Источник: [jameszero.net/faq-xkeen.htm](https://jameszero.net/faq-xkeen.htm)\n\n## Введение\n\n**XKeen** —"
},
{
"path": "wiki/Home.md",
"chars": 2241,
"preview": "# XKeen\n\n**XKeen** — POSIX-shell утилита для прозрачной маршрутизации сетевого трафика через прокси-движки **Xray** и **"
},
{
"path": "wiki/_Footer.md",
"chars": 147,
"preview": "---\n\n[Сайт автора форка — jameszero.net](https://jameszero.net/) · [Обсуждение на forum.keenetic.ru](https://forum.keene"
},
{
"path": "wiki/_Sidebar.md",
"chars": 130,
"preview": "**Навигация**\n\n- [Главная](Home)\n- [FAQ](FAQ)\n- [DNS-over-VLESS](DNS-over-VLESS)\n- [Маршрутизация по DSCP](Маршрутизация"
},
{
"path": "wiki/Маршрутизация-по-DSCP.md",
"chars": 1469,
"preview": "# Маршрутизация по DSCP-меткам в XKeen\n\n> Источник: [jameszero.net/4509.htm](https://jameszero.net/4509.htm)\n\nВ XKeen 2."
}
]
About this extraction
This page contains the full source code of the jameszeroX/XKeen GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (356.9 KB), approximately 107.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.