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 ![downloads](https://img.shields.io/github/downloads/jameszeroX/Xkeen/${{ env.VERSION }}/total?label=downloads) EOF - name: Create signed tag and release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG_NAME="${{ env.VERSION }}" # Удаляем старый тег если существует git push origin --delete "$TAG_NAME" 2>/dev/null || true git tag -d "$TAG_NAME" 2>/dev/null || true # Создаем подписанный тег git tag -s "$TAG_NAME" -m "Release $TAG_NAME" git push origin "$TAG_NAME" # Создаем релиз gh release create "$TAG_NAME" \ dist/*.tar.gz \ dist/*.tar \ --title "${{ env.VERSION }}" \ --notes-file /tmp/release_notes.md \ ${{ github.event.inputs.prerelease == 'true' && '--prerelease' || '' }} \ --verify-tag - name: Verify signed tag run: | if git tag -v "${{ env.VERSION }}" 2>&1; then echo "✅ Тег ${{ env.VERSION }} успешно подписан и верифицирован!" else echo "⚠️ Предупреждение: Не удалось верифицировать подпись тега" fi ================================================ FILE: .github/workflows/wiki-sync.yaml ================================================ name: Sync GitHub Wiki on: push: branches: - main paths: - 'wiki/**' - '.github/workflows/wiki-sync.yaml' workflow_dispatch: jobs: sync-wiki: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 1 - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} git_user_signingkey: true git_commit_gpgsign: true git_tag_gpgsign: true git_config_global: true - name: Clone wiki repository env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git clone "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git" wiki-repo - name: Sync wiki content run: | rsync -a --delete --exclude='.git' wiki/ wiki-repo/ - name: Commit and push signed wiki update working-directory: wiki-repo run: | git add -A if git diff --staged --quiet; then echo "No wiki changes to commit" exit 0 fi SHORT_SHA="${GITHUB_SHA:0:7}" git commit -S -m "[github-actions] sync wiki from main@${SHORT_SHA}" BRANCH=$(git symbolic-ref --short HEAD) git push origin "$BRANCH" ================================================ FILE: .gitignore ================================================ .claude graphify-out done src/graphify src/output ================================================ FILE: 01_info_variable.sh ================================================ # ------------------------------------- # Цвета # ------------------------------------- green="\033[92m" # Зеленый red="\033[91m" # Красный yellow="\033[93m" # Желтый light_blue="\033[96m" # Голубой italic="\033[3m" # Курсив reset="\033[0m" # Сброс цветов # ------------------------------------- # Директории # ------------------------------------- tmp_dir_global="/opt/tmp" # Временная директория общая tmp_dir="/opt/tmp/xkeen" # Временная директория XKeen xtmp_dir="/opt/tmp/xray" # Временная директория Xray mtmp_dir="/opt/tmp/mihomo" # Временная директория Mihomo xkeen_dir="/opt/sbin/.xkeen" # Директория скриптов XKeen xkeen_cfg="/opt/etc/xkeen" # Директория конфигурации XKeen xkeen_log_dir="/opt/var/log/xkeen" # Директория логов XKeen xray_log_dir="/opt/var/log/xray" # Директория логов Xray initd_dir="/opt/etc/init.d" # Директория init.d pid_dir="/opt/var/run" # Директория pid файлов backups_dir="/opt/backups" # Директория бекапов install_dir="/opt/sbin" # Директория установки geo_dir="/opt/etc/xray/dat" # Директория для dat cron_dir="/opt/var/spool/cron/crontabs" # Директория планировщика cron_file="root" # Файл планировщика install_conf_dir="/opt/etc/xray/configs" # Директория конфигурации Xray mihomo_conf_dir="/opt/etc/mihomo" # Директория конфигурации Mihomo xray_conf_dir="$xkeen_dir/02_install/08_install_configs/02_configs_dir" xkeen_var_file="$xkeen_dir/01_info/01_info_variable.sh" register_dir="/opt/lib/opkg/info" status_file="/opt/lib/opkg/status" os_modules="/lib/modules/$(uname -r)" user_modules="/opt/lib/modules" xkeen_current_version="1.1.3.9" xkeen_build="Stable" build_timestamp="2026-02-07 08:58:11 MSK (fix yq)" # ------------------------------------- # Время # ------------------------------------- existing_content=$(cat "$status_file") installed_size=$(du -s "$install_dir" | cut -f1) source_date_epoch=$(date +%s) current_datetime=$(date "+%d-%b-%y_%H-%M") # ------------------------------------- # IP для проверки доступа в интернет # ------------------------------------- conn_IP1="195.208.4.1" conn_IP2="77.88.44.55" # ------------------------------------- # URL # ------------------------------------- xkeen_api_url="https://api.github.com/repos/jameszeroX/xkeen/releases/latest" # url api для XKeen xkeen_jsd_url="https://data.jsdelivr.com/v1/package/gh/jameszeroX/xkeen" # резервный url api для XKeen xkeen_tar_url="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz" # url для загрузки XKeen xkeen_dev_url="https://raw.githubusercontent.com/jameszeroX/xkeen/main/test/xkeen.tar.gz" # url для загрузки XKeen dev xray_api_url="https://api.github.com/repos/XTLS/Xray-core/releases" # url api для Xray xray_jsd_url="https://data.jsdelivr.com/v1/package/gh/XTLS/Xray-core" # резервный url api для Xray xray_zip_url="https://github.com/XTLS/Xray-core/releases/download" # url для загрузки Xray mihomo_api_url="https://api.github.com/repos/MetaCubeX/mihomo/releases" # url api для Mihomo mihomo_jsd_url="https://data.jsdelivr.com/v1/package/gh/MetaCubeX/mihomo" # резервный url api для Mihomo mihomo_gz_url="https://github.com/MetaCubeX/mihomo/releases/download" # url для загрузки Mihomo yq_dist_url="https://github.com/jameszeroX/yq/releases/latest/download" # url для загрузки Yq gh_proxy1="https://ghfast.top" # 1 прокси для загрузок с GitHub gh_proxy2="https://gh-proxy.com" # 2 прокси для загрузок с GitHub # url для загрузки геофайлов refilter_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geosite.dat" refilterip_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geoip.dat" v2fly_url="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat" v2flyip_url="https://github.com/loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" zkeen_url="https://github.com/jameszeroX/zkeen-domains/releases/latest/download/zkeen.dat" zkeenip_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/zkeenip.dat" # ------------------------------------- # Создание директорий и файлов # ------------------------------------- mkdir -p "$xray_log_dir" || { echo "Ошибка: Не удалось создать директорию $xray_log_dir"; exit 1; } mkdir -p "$initd_dir" || { echo "Ошибка: Не удалось создать директорию $initd_dir"; exit 1; } mkdir -p "$pid_dir" || { echo "Ошибка: Не удалось создать директорию $pid_dir"; exit 1; } mkdir -p "$backups_dir" || { echo "Ошибка: Не удалось создать директорию $backups_dir"; exit 1; } mkdir -p "$install_dir" || { echo "Ошибка: Не удалось создать директорию $install_dir"; exit 1; } mkdir -p "$cron_dir" || { echo "Ошибка: Не удалось создать директорию $cron_dir"; exit 1; } # ------------------------------------- # Журналы # ------------------------------------- xray_access_log="$xray_log_dir/access.log" xray_error_log="$xray_log_dir/error.log" touch "$xray_access_log" || { echo "Ошибка: Не удалось создать файл $xray_access_log"; exit 1; } touch "$xray_error_log" || { echo "Ошибка: Не удалось создать файл $xray_error_log"; exit 1; } ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2026, jameszeroX Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # XKeen 1.1.3.9 > **XKeen** — утилита для выборочной маршрутизации сетевого трафика через прокси‑движки **Xray** и **Mihomo** на роутерах **Keenetic**/**Netcraze**. > Позволяет прозрачно направлять TCP/UDP‑трафик только выбранных клиентов, не затрагивая остальную сеть. --- ## Основные возможности - Выборочная маршрутизация для клиентов в политике доступа в интернет - Сохранение прямого выхода в интернет для остальных клиентов - Маршрутизация без политики для всех клиентов роутера - Поддержка режимов **TProxy**, **Mixed**, **Redirect**, **Other** (socks5/http) - Прозрачное проксирование **TCP** и **UDP** - Поддержка ядер-проксирования **Xray** и **Mihomo** - Совместимость с **KeeneticOS 5+** - Управление через shell и [веб-панели](https://github.com/jameszeroX/XKeen?tab=readme-ov-file#дополнения) сторонних разработчиков XKeen работает полностью на стороне роутера, не меняет настройки клиентов и не требует установки на них дополнительных программ. --- ## Предупреждения > [!WARNING] > Данный материал подготовлен в научно‑технических целях. XKeen предназначен для управления межсетевым экраном роутера Keenetic, защищающим домашнюю сеть. Разработчик не несёт ответственности за иное использование утилиты. Перед применением убедитесь, что ваши действия соответствуют законодательству вашей страны. > [!CAUTION] > В некоторых случаях протокол IPv6 создаёт проблемы при проксировании. В KeeneticOS IPv6 нельзя полностью отключить стандартными средствами. В XKeen реализован альтернативный механизм его отключения, который полностью убирает IPv6‑трафик на роутере. Это **экспериментальная функция** и может привести к некорректной работе отдельных сервисов Keenetic. Используйте её только при необходимости. > [!NOTE] > Установка XKeen гарантируется на внешние USB‑накопители. Установка во внутреннюю память роутера возможна, но требует опыта пользователя. Проблемы, связанные с установкой во внутреннюю память, не считаются ошибками XKeen. --- Данный репозиторий является форком оригинального XKeen с исправлениями, расширенной функциональностью и поддержкой актуальных версий KeeneticOS. ## Ключевые изменения форка ### Исправлено - автозапуск XKeen - сняты ограничения на количество используемых портов ### Добавлено - поддержка **KeeneticOS 5+** - управление IPv6 - поддержка ядра **Mihomo** - быстрое переключение Xray / Mihomo - контроль [файловых дескрипторов](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#контроль-файловых-дескрипторов) - [внешние списки](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#внешние-списки-портов-и-ip) IP и портов - [OffLine](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#offline-установка)‑установка - [Self-Hosted](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки)-прокси для загрузки компонентов ### Удалено - не актуальные и повреждённые геобазы - неиспользуемые конфигурационные файлы - устаревшие параметры запуска и задачи планировщика --- ### Подробное [описание изменений](https://github.com/jameszeroX/XKeen/blob/main/forkinfo.md) --- Список параметров запуска XKeen доступен в справке: ```bash xkeen -h ``` --- ## Порядок установки Требуется роутер **Keenetic**/**Netcraze** с предварительно установленной средой Entware и компонентом `Модули ядра подсистемы Netfilter` ```bash opkg update && opkg upgrade && opkg install curl tar && cd /tmp sh -c "$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)" ``` --- ## Поддержка проекта Форк XKeen, как и оригинал, совершено бесплатен и не имеет каких либо ограничений по использованию. Надеюсь, доработки XKeen, многие из которых я сделал по Вашим просьбам, оказались полезны, так же, как и мои сообщения в [телеграм-чате](https://t.me/+8Cvh7oVf6cE0MWRi). Для меня очень важно понимать, что труд и время потрачены не зря. Буду благодарен за любую Вашу поддержку на развитие проекта: - [CloudTips](https://pay.cloudtips.ru/p/7edb30ec) - [ЮMoney](https://yoomoney.ru/to/41001350776240) - Карта МИР: `2204 1201 2976 4110` - USDT, сеть TRC20: `TQhy1LbuGe3Bz7EVrDYn67ZFLDjDBa2VNX` - USDT, сеть ERC20: `0x6a5DF3b5c67E1f90dF27Ff3bd2a7691Fad234EE2` Уточните актуальность крипто-адресов перед переводом --- ## Дополнения - XKeen UI — https://github.com/zxc-rv/XKeen-UI - XKeen UI — https://github.com/umarcheh001/Xkeen-UI - XKeen UI — https://github.com/fan92rus/xkeen-ui - Генератор Outbound — https://zxc-rv.github.io/XKeen-UI/Outbound_Generator/ - Парсер подписок - https://github.com/tkukushkin/xkeen-subscription-watcher - Парсер подписок — https://github.com/V2as/SubKeen - Mihomo Studio — https://github.com/l-ptrol/mihomo_studio - Конвертер JSON-подписок — https://sngvy.github.io/json-sub-to-outbounds - Mihomo HWID Subscription Installer — https://github.com/dorian6996/Mihomo-HWID-Subscription --- ## Источники и ссылки - Origin XKeen — https://github.com/Skrill0/XKeen - Xray-core — https://github.com/XTLS/Xray-core - Mihomo — https://github.com/MetaCubeX/mihomo - Yq — https://github.com/mikefarah/yq - FAQ — https://jameszero.net/faq-xkeen.htm - Telegram‑чат — https://t.me/+8Cvh7oVf6cE0MWRi ================================================ FILE: configuration.md ================================================ --- ## Внешние списки портов и IP Предусмотрена возможность добавить в конфигурационные файлы XKeen необходимые порты проксирования или исключения из проксирования, а также IP-адреса и подсети, проксирование которых не требуется. Файлы находятся в директории `/opt/etc/xkeen/` - `port_proxying.lst` - порты проксирования, например 80 и 443 - `port_exclude.lst` - порты, которые необходимо исключить из проксирования, например, 3389. - `ip_exclude.lst` - IP-адреса и подсети, которые необходимо исключить из проксирования, например, 77.88.8.8 Каждый порт и IP указываются в этих файлах с новой строки. Пустые строки и строки, начинающиеся со знака комментария `#` игнорируются. Порты проксирования и порты исключенные из проксирования не могут применяться вместе, используйте или то, или другое. Если по ошибке будут заполнены оба списка, то приоритет имеют порты проксирования, а список исключенных портов игнорируется. При чистой установке XKeen создаются шаблоны вышеуказанных файлов с примерами их заполнения. --- ## Контроль файловых дескрипторов В среде entware Keenetic довольно низкий предел файловых дескрипторов (fd) выделяемых на один процесс - 1024. Превышение этого значения приводит к отказу прокси-клиенту создавать новые коннекты. На процессорах arm64 суммарно на все процессы отведено около 50000 fd, а на mips процессорах около 12000 fd (цифры могут отличаться для разных моделей роутеров). Оригинал XKeen выделяет для процесса xray 1 миллион! дескрипторов и если xray попытается их занять, роутер может просто зависнуть. Форк XKeen версии 1.1.3.6+, в зависимости от процессора, устанавливает лимит 40000 fd для arm64 и 10000 fd для остальных процессоров, а при достижении 90% от этой цифры, перезапускает прокси-клиент. В параметрах `arm64_fd` и `other_fd` стартового скрипта XKeen, при необходимости, можно откорректировать лимиты, установив другие значения. Контроль fd может быть полезен для высоконагруженных роутеров с большим количеством клиентов. Для типичного self-use применения XKeen, данный функционал использовать необязательно, поэтому по умолчанию контроль открытых файловых дескрипторов прокси-клиентом отключен (включить/отключить можно командой `xkeen -fd`). Возможностью контролировать открытые файловые дескрипторы следует пользоваться в крайних ситуациях, когда ничего уже не помогает, так как при этом прокси-клиент будет постоянно перезапускаться для сброса дескрипторов. Рекомендуется вместо этого найти и устранить причину. Это может быть устройство или приложение, открывающее множество подключений. --- ## Self-Hosted-прокси для загрузки В базовый конфиг добавлены два GitHub-прокси, через которые возможна загрузка XKeen и его компонентов в случае недоступности GitHub. Если же и они окажутся недоступны, можете установить [Self-Hosted прокси](https://github.com/hunshcn/gh-proxy) на своём сервере и указать его в переменной `gh_proxy1` или `gh_proxy2` файла `/opt/sbin/.xkeen/01_info/01_info_variable.sh` --- ## OffLine-установка Обычная установка XKeen и необходимых компонентов выполняется в OnLine режиме и жёстко привязана к GitHub, а в случае его недоступности будет невозможна. Поэтому в форк дополнительно к способу установки через [Self-Hosted](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки)-прокси добавлен режим OffLine-установки по команде `xkeen -io` Для OffLine-установки необходимо заранее любым способом скачать установочный архив XKeen версии 1.1.3.9+, ядро проксирования [xray](https://github.com/XTLS/Xray-core/releases/latest) и(или) [mihomo](https://github.com/MetaCubeX/mihomo/releases/latest) + парсер yaml-файлов [yq](https://github.com/jameszeroX/yq/releases/latest) подходящей архитектуры. Если планируте использовать xray и геофайлы в роутинге, то загрузите и их. Следующим шагом поместите в папку /opt/sbin/ архив XKeen (не распаковывая) и предварительно извлечённые из архива и при необходимости переименованные в `xray` `mihomo` и `yq` бинарники, затем выполните OffLine-установку командами в ssh-консоли entware Keenetic: ``` cd /opt/sbin tar -xvzf xkeen.tar.gz && rm xkeen.tar.gz xkeen -io # ``` Копирование файлов конфигурации xray, mihomo и необходимых геофайлов в директории /opt/etc/xray/configs, /opt/etc/mihomo, /opt/etc/xray/dat выполните вручную, после чего можете запустить проксирование командой `xkeen -start` При OffLine-установке XKeen не проверяет соответствие архитектуры процессора и бинарников, поэтому выбирайте совместимые бинарники внимательно. Если затрудняетесь в выборе, запустите `xkeen -io` без xray и mihomo в папке /opt/sbin/ и XKeen сообщит, какая архитектура требуется для вашего роутера. При недоступности GitHub, обновление геофайлов по планировщику работать не будет, выполняйте его вручную. Если недоступен не только GitHub, но и [репозиторий Entware](http://bin.entware.net), то перед OffLine установкой XKeen требуется вручную установить недостающие пакеты из следующего списка: ``` curl, tar, lscpu, jq, libc, libssp, librt, libpthread, iptables, ca-bundle, coreutils-uname, coreutils-nohup ``` либо прописать в файл `/opt/etc/opkg.conf` рабочее зеркало репозитория ================================================ FILE: docs/README.md ================================================ # Документация XKeen Этот каталог содержит техническую документацию для разработчиков и контрибьюторов. Если вы пользователь и просто хотите установить XKeen — начните с корневого [`README.md`](../README.md) и [`configuration.md`](../configuration.md). ## Содержание | Документ | О чём | | --- | --- | | [architecture.md](architecture.md) | Точка входа, фазы импорта модулей, режимы проксирования, SSoT-переменные | | [build-and-release.md](build-and-release.md) | GitHub Actions: пакет в Beta-канал, релиз, синхронизация Wiki. Каналы обновлений | | [runtime-paths.md](runtime-paths.md) | Раскладка файлов и каталогов на роутере (`/opt/...`) | | [commands.md](commands.md) | Справочник флагов `xkeen` | | [contributing.md](contributing.md) | Правила правки кода, ограничения POSIX-`sh`, рабочий цикл проверки | ## Связанные документы в корне репозитория - [`README.md`](../README.md) — обзор и установка для пользователей. - [`configuration.md`](../configuration.md) — внешние списки портов/IP, fd-контроль, Self-Hosted прокси, OffLine-установка. - [`forkinfo.md`](../forkinfo.md) — отличия форка от оригинала Skrill0/XKeen. - [`knownissues.md`](../knownissues.md) — известные ограничения. Читать перед триажом багов. - [`test/README.md`](../test/README.md) — release-notes 2.0 Beta, новые параметры и инварианты. ## Wiki Исходники GitHub Wiki лежат в [`../wiki/`](../wiki) и автоматически синхронизируются в `.wiki.git` через workflow `.github/workflows/wiki-sync.yaml`. См. [build-and-release.md](build-and-release.md#workflow-wiki-syncyaml). ================================================ FILE: docs/architecture.md ================================================ # Архитектура XKeen XKeen — POSIX-shell утилита (`sh`, не `bash`) для роутеров Keenetic/Netcraze под Entware. Кода на компилируемых языках нет. Целевые архитектуры — `arm64-v8a`, `mips32le`, `mips32`. Запускается на роутере; в этом репозитории — только исходники и упаковка. ## Точка входа [`scripts/xkeen`](../scripts/xkeen) — монолитный POSIX-`sh` диспетчер (~1450 строк). Парсит флаги через большой `while/case` начиная со строки 119. Каждый флаг — самостоятельная команда. ### Скрытие установочного каталога При первом запуске функция `install_xkeen_rename` ([`scripts/xkeen:7-21`](../scripts/xkeen)) переименовывает `_xkeen/` → `.xkeen/`. **Все runtime-пути в коде ссылаются на `.xkeen`. В репозитории каталог называется `_xkeen` — путать легко.** CI пакует именно `_xkeen`. ### Self-detach Если процесс запущен без TTY (cron, ssh без `-t`, CGI) и команда из `{-start,-stop,-restart}`, `xkeen` форкается через `start-stop-daemon -b` в новую session/pgid с логом в `/opt/var/log/xkeen-detached.log`. Это защищает от обрыва родительской сессии. См. [`scripts/xkeen:43-70`](../scripts/xkeen). Переменная `XKEEN_FOREGROUND=1` отключает детач для скриптов с синхронной семантикой (`xkeen -start && cleanup`). ## Импорт модулей Все модули — это `. file.sh`-импортируемые библиотеки функций. Точка сборки — [`scripts/_xkeen/import.sh`](../scripts/_xkeen/import.sh), которая последовательно тянет `00_*_import.sh` каждой фазы: | Фаза | Каталог | Назначение | | --- | --- | --- | | 01 | [`01_info/`](../scripts/_xkeen/01_info) | Переменные (SSoT — `01_info_variable.sh`), детекция CPU, проверка установленных Xray/Mihomo/geofile, версии, консольный вывод, cron-статус | | 02 | [`02_install/`](../scripts/_xkeen/02_install) | Установка: opkg-пакеты → ядра (Xray, Mihomo) → XKeen → geofile/IPSET → cron → регистрация (`07_install_register/`) → шаблоны конфигов (`08_install_configs/02_configs_dir/`) | | 03 | [`03_delete/`](../scripts/_xkeen/03_delete) | Точечное удаление компонентов + полная деинсталляция (`-remove`) | | 04 | [`04_tools/`](../scripts/_xkeen/04_tools) | Сервис: управление портами, модули ядра, диагностика, задержка автозапуска, интерактивный выбор (`05_tools_choice/`), бэкапы (`06_tools_backups/`), загрузчики через GH-proxy fallback (`07_tools_downloaders/`) | | 05 | [`05_tests/`](../scripts/_xkeen/05_tests) | Runtime-проверки сети, портов, носителя | ## Single Source of Truth: `01_info_variable.sh` Файл [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh) — единственное место, где определены: - Версия и канал: `xkeen_current_version`, `xkeen_build`, `build_timestamp` (последнее — подставляется CI). - Все runtime-каталоги: `xkeen_dir=/opt/sbin/.xkeen`, `xkeen_cfg=/opt/etc/xkeen`, `geo_dir=/opt/etc/xray/dat`, и др. - Все внешние URL: GitHub API для XKeen/Xray/Mihomo, прямые URL архивов, geofile-репозитории. - GitHub-прокси для регионов с ограничениями: `gh_proxy1=https://gh-proxy.com`, `gh_proxy2=https://ghfast.top`. При смене версии или URL правится только этот файл. Релизный workflow перезаписывает в нём только `build_timestamp`. ## GH-proxy fallback Любая загрузка с GitHub в [`04_tools/07_tools_downloaders/`](../scripts/_xkeen/04_tools/07_tools_downloaders) и в корневом [`install.sh`](../install.sh) идёт по цепочке: 1. Прямой URL (например, `github.com/.../xkeen.tar.gz`). 2. `gh_proxy1` префиксом — `https://gh-proxy.com/`. 3. `gh_proxy2` префиксом — `https://ghfast.top/`. С версии 2.0 Beta параметр `gh_proxy` из `/opt/etc/xkeen/xkeen.json` имеет приоритет над встроенными значениями. Маркер `/tmp/toff` (создаётся при запуске с флагом `-toff`) отключает `curl -m 180` на одну сессию — полезно для медленных каналов. ## Режимы проксирования В рантайме определяется один из четырёх режимов: | Режим | Признак | | --- | --- | | TProxy | Inbound с `streamSettings.sockopt.tproxy == "tproxy"` (Xray) или `listeners[].type == "tproxy"` (Mihomo) | | Hybrid | Бывший Mixed — комбинация TProxy + Redirect | | Redirect | Inbound с `sockopt.tproxy == "redirect"`. Самый быстрый, но без UDP | | Other | socks5/http inbound | Определение режима — в [`scripts/_xkeen/02_install/07_install_register/04_register_init.sh`](../scripts/_xkeen/02_install/07_install_register/04_register_init.sh) парсингом конфигов Xray (`tproxy`/`redirect` в `streamSettings.sockopt`) и Mihomo (`tproxy-port`, `listeners[].type == "tproxy"`). **Имена inbound-тегов больше не влияют на режим — исправление 2.0 Beta.** Ранее теги вроде `tproxy-in`/`redirect-in` использовались как fallback, что приводило к ошибкам при кастомных тегах. ## Beta-функции Описаны в [`test/README.md`](../test/README.md). Кратко: - Кастомные политики маршрутизации в `xkeen.json`. - IPSET `ru-exclude` — исключение российских IP из проксирования на уровне ipset. - DSCP-метки 62 (исключение) и 63 (проксирование) — маршрутизация по приоритетам QoS-пакетов. См. также wiki-страницу [Маршрутизация по DSCP](../wiki/Маршрутизация-по-DSCP.md). - Проксирование трафика Entware-пакетов с `routing-mark: 255` (Xray) / `mark: 255` (Mihomo). ================================================ FILE: docs/build-and-release.md ================================================ # Сборка и релиз Локальной сборки нет. Всё делает CI на GitHub Actions. В этом разделе — три workflow-а и две схемы каналов обновлений. ## Workflow-ы ### `package-folder.yaml` [`.github/workflows/package-folder.yaml`](../.github/workflows/package-folder.yaml) | Параметр | Значение | | --- | --- | | Триггер | `push` в `main` с изменениями в `scripts/**`, либо `workflow_dispatch` | | Результат | `test/xkeen.tar.gz` — Beta-канал, упакован из `scripts/*` | | Подпись | GPG-подписанный автокоммит `[github-actions] automated compiling build` | Шаги: 1. Checkout с `fetch-depth: 0`. 2. Импорт GPG-ключа через `crazy-max/ghaction-import-gpg@v7` с `git_config_global: true`. 3. Подмена `build_timestamp="…"` в `scripts/_xkeen/01_info/01_info_variable.sh` на текущее MSK-время. 4. Упаковка: `cd scripts_for_build && find . -type f -o -type l | sed 's|^\./||' | tar -czf .../xkeen.tar.gz -T -`. На верхнем уровне архива — `xkeen` и `_xkeen/`, без вложенного `scripts/`. 5. Перемещение архива в `test/` и подписанный коммит обратно в `main`. **Файл `test/xkeen.tar.gz` — артефакт CI, руками не редактировать.** ### `release.yaml` [`.github/workflows/release.yaml`](../.github/workflows/release.yaml) | Параметр | Значение | | --- | --- | | Триггер | `workflow_dispatch` с входами `version` (string) и `prerelease` (boolean) | | Результат | `dist/xkeen.tar.gz` + `dist/xkeen.tar` + GitHub Release + подписанный GPG-тег | Шаги: 1. Checkout с `fetch-depth: 0`. 2. Импорт GPG-ключа. 3. Подмена `build_timestamp` (как в `package-folder.yaml`). 4. Двойная упаковка: `.tar.gz` (для роутеров с `tar`+gzip) и `.tar` (для альтернативных распаковщиков). 5. Удаление существующего тега, создание подписанного `git tag -s "$VERSION"`, push. 6. `gh release create` с обоими архивами. При `prerelease=true` — флаг `--prerelease`. 7. Верификация подписи `git tag -v`. ### `wiki-sync.yaml` [`.github/workflows/wiki-sync.yaml`](../.github/workflows/wiki-sync.yaml) | Параметр | Значение | | --- | --- | | Триггер | `push` в `main` с изменениями в `wiki/**` или сам workflow, либо `workflow_dispatch` | | Результат | Содержимое `wiki/` синхронизировано в `.wiki.git` подписанным коммитом | Шаги: 1. Checkout главного репо. 2. Импорт GPG-ключа (тот же `crazy-max/ghaction-import-gpg@v7`). 3. Клонирование `.wiki.git` через `https://x-access-token:${GITHUB_TOKEN}@github.com/.wiki.git`. 4. `rsync -a --delete --exclude='.git' wiki/ wiki-repo/` — добавление, обновление, удаление. 5. Подписанный коммит `[github-actions] sync wiki from main@` и push в дефолтную ветку Wiki. Пререкизиты для прода: - В Settings → Features → Wikis: ✅ enabled. - В Wiki создана хотя бы одна страница через UI (иначе `.wiki.git` отдаёт 404). - В Settings → Actions → General → Workflow permissions: `Read and write permissions`. - Secret `GPG_PRIVATE_KEY` (passphrase не используется). ## Каналы обновлений | Канал | Источник | Триггер | | --- | --- | --- | | Stable | GitHub Release с тегом, `xkeen_tar_url` | Прогон `release.yaml` | | Beta | `test/xkeen.tar.gz` в ветке `main`, `xkeen_dev_url` | Любой merge в `main` с изменениями `scripts/**` | На роутере переключение каналов — `xkeen -channel`. Текущая версия и канал хранятся в `01_info_variable.sh` (`xkeen_current_version`, `xkeen_build`). ## Воспроизвести локальную сборку Без CI, для отладки упаковки: ```sh cd scripts && find . -type f -o -type l | sed 's|^\./||' | tar -czf /tmp/xkeen.tar.gz -T - ``` Результат идентичен тому, что генерирует `package-folder.yaml` (за исключением подменённого `build_timestamp`). ================================================ FILE: docs/commands.md ================================================ # Справочник флагов `xkeen` Полный список флагов диспетчера [`scripts/xkeen`](../scripts/xkeen). Извлечён из `help_xkeen()` в [`scripts/_xkeen/about.sh`](../scripts/_xkeen/about.sh). Деструктивные флаги отмечены ⚠ — они интерактивные, требуют подтверждения, не имеют опции «тихого» режима. ## Установка | Флаг | Действие | | --- | --- | | `-i`, `-install` | Полный цикл: XKeen + Xray + GeoFile/GeoIPSET + Mihomo | | `-io` | OffLine-установка XKeen из локальной флешки | | `-toff` | Отключить таймаут `curl` для медленных каналов: `xkeen -i -toff` | ## Переустановка | Флаг | Действие | | --- | --- | | `-k` | XKeen | | `-g` | GeoFile (GeoSite + GeoIP) | | `-gips` | GeoIPSET | ## Обновление | Флаг | Действие | | --- | --- | | `-uk` | XKeen (получает Stable или Beta — см. `-channel`) | | `-ug` | GeoFile/GeoIPSET | | `-ux` | Xray — повышение или понижение версии | | `-um` | Mihomo — повышение или понижение версии | ## Автообновление GeoFile/GeoIPSET (cron-задача) | Флаг | Действие | | --- | --- | | `-ugc` | Создать cron-задачу | | `-dgc` | Удалить cron-задачу | ## Резервные копии | Флаг | Действие | | --- | --- | | `-kb` | Создать резервную копию XKeen | | `-kbr` | Восстановить XKeen из резервной копии | | `-xb` | Создать резервную копию конфигурации Xray | | `-xbr` | Восстановить конфигурацию Xray | | `-mb` | Создать резервную копию конфигурации Mihomo | | `-mbr` | Восстановить конфигурацию Mihomo | ## Удаление ⚠ | Флаг | Действие | | --- | --- | | `-remove` | ⚠ Полная деинсталляция XKeen | | `-dgs` | ⚠ Удалить GeoSite | | `-dgi` | ⚠ Удалить GeoIP | | `-dgips` | ⚠ Удалить GeoIPSET | | `-dx` | ⚠ Удалить Xray | | `-dm` | ⚠ Удалить Mihomo | | `-dk` | ⚠ Удалить XKeen (сохраняет ядра) | ## Порты проксирования | Флаг | Действие | | --- | --- | | `-ap` | Добавить порт | | `-dp` | Удалить порт | | `-cp` | Посмотреть список | ## Порты, исключённые из проксирования | Флаг | Действие | | --- | --- | | `-ape` | Добавить порт-исключение | | `-dpe` | Удалить порт-исключение | | `-cpe` | Посмотреть список | ## Управление прокси-клиентом | Флаг | Действие | | --- | --- | | `-start` | Запуск | | `-stop` | Остановка | | `-restart` | Перезапуск | | `-status` | Статус работы | | `-tp` | Порты, шлюз и протокол прокси-клиента | | `-auto` | Включить / отключить автозапуск | | `-d` | Установить задержку автозапуска: `xkeen -d 30` (секунд) | | `-fd` | Включить / отключить контроль файловых дескрипторов | | `-cfd` | Посчитать количество открытых файловых дескрипторов прокси-клиента | | `-diag` | Выполнить полную диагностику (единственный поддерживаемый канал для отчёта о проблеме) | | `-channel` | Переключить канал обновлений (Stable / Beta) | | `-xray` | Переключить XKeen на ядро Xray | | `-mihomo` | Переключить XKeen на ядро Mihomo | | `-ipv6` | Включить / отключить протокол IPv6 в KeeneticOS | | `-dns` | Включить / отключить перенаправление DNS в прокси | | `-pr` | Включить / отключить проксирование трафика Entware | | `-extmsg` | Включить / отключить расширенные сообщения при запуске | | `-cbk` | Включить / отключить резервное копирование XKeen при обновлении | | `-aghfix` | Включить / отключить отображение клиентов под своими IP в журнале AdGuard Home | ## Информация | Флаг | Действие | | --- | --- | | `-about` | О программе | | `-ad` | Поддержать разработчиков | | `-af` | Обратная связь / контакты | | `-v` | Версия XKeen | | `-h`, `-help` | Показать встроенную справку (`help_xkeen` из `about.sh`) | ## Переменные окружения | Переменная | Значение | Эффект | | --- | --- | --- | | `XKEEN_FOREGROUND` | `1` | Отключает self-detach при запуске без TTY. Использовать в скриптах с синхронной семантикой (`xkeen -start && cleanup`) | | `XKEEN_DETACHED` | `1` | Внутренний маркер — выставляется самим `xkeen` после форка через `start-stop-daemon -b`. Руками не выставлять | ================================================ FILE: docs/contributing.md ================================================ # Правила правки ## Язык — POSIX `sh` Целевая среда — Entware на BusyBox `ash`. **Никаких bash-измов:** | Запрещено | Использовать вместо | | --- | --- | | `[[ … ]]` | `[ … ]` (POSIX `test`) | | `${var,,}`, `${var^^}` | `echo "$var" \| tr 'A-Z' 'a-z'` | | Массивы (`arr=(a b c)`, `${arr[i]}`) | Позиционные параметры, IFS-split строки | | `<<<` (here-string) | `echo "…" \| cmd` или `<< EOF` | | `function name()` | `name()` | | `local var` | Не использовать — `local` не POSIX | | `(( … ))` арифметика | `$(( … ))` или `expr` | | `read -p` | `printf '...'; read var` | Проверка перед PR: ```sh shellcheck scripts/xkeen scripts/_xkeen/**/*.sh ``` ## Пути и URL — только из переменных Все пути и URL определены в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh). **Не хардкодить ни одного `/opt/...` пути и ни одного `https://github.com/...` URL** в других файлах. Если нужен новый путь — добавить переменную в `01_info_variable.sh`. ## Добавление модуля 1. Определить, к какой фазе относится: `01_info/`, `02_install/`, `03_delete/`, `04_tools/`, `05_tests/`. Если новый раздел внутри `02_install/` (например, поддиректория `09_install_X/`) — создать каталог и поместить туда `00__import.sh`. 2. Создать файл `NN_.sh` в нужном каталоге (нумерация — следующая свободная). 3. Подключить через `.` в соответствующем `00_*_import.sh` родительского каталога. 4. Если модуль зависит от других — следить за порядком импорта. ## Добавление новой команды 1. Case-ветка в [`scripts/xkeen`](../scripts/xkeen) в большом `while/case` (начинается со строки 119). 2. Если команда из `{-start, -stop, -restart}` или другая, требующая self-detach в фоне — добавить в проверку на строках 43-48 (`detach_eligible=true`). 3. Описание флага — в `help_xkeen()` функции [`scripts/_xkeen/about.sh`](../scripts/_xkeen/about.sh) под подходящим разделом. 4. Если команда деструктивная — обязательно интерактивное подтверждение перед действием. Не делать «тихие» деструктивные операции. ## Лимиты файловых дескрипторов Значения в стартовом скрипте: `arm64_fd=40000`, `other_fd=10000`. Не править наугад — увеличение влечёт расход RAM, уменьшение — обрывы соединений на пиках. См. также соответствующий раздел в [`configuration.md`](../configuration.md). ## Self-detach Блок в [`scripts/xkeen:43-70`](../scripts/xkeen) — критичный для cron-перезапусков. Без него родитель убивает дочерний процесс по SIGHUP при обрыве ssh-сессии. Трогать только осознанно. ## Проверка перед PR 1. `shellcheck scripts/xkeen scripts/_xkeen/**/*.sh` — нулевая толерантность к новым warning-ам. 2. Деплой архива на тестовый роутер и прогон сценариев: `xkeen -i`, `-start`, `-stop`, `-restart`, `-uk`, `-diag`. 3. Если правились флаги управления (`-ap`, `-dp`, `-ape`, `-dpe`) или режимы проксирования — отдельно прогнать с обоими ядрами (Xray и Mihomo) и в каждом из режимов TProxy/Hybrid/Redirect. 4. `xkeen -diag` — единственный поддерживаемый канал для отчёта о проблеме. ## CI-файлы — не трогать руками - [`.github/workflows/package-folder.yaml`](../.github/workflows/package-folder.yaml) и сам артефакт [`test/xkeen.tar.gz`](../test/xkeen.tar.gz) — генерируются CI. Любые ручные правки будут перезаписаны при следующем push в `main` с изменениями `scripts/**`. - [`.github/workflows/release.yaml`](../.github/workflows/release.yaml) — менять только если действительно меняется процесс релиза. - [`.github/workflows/wiki-sync.yaml`](../.github/workflows/wiki-sync.yaml) — синхронизирует [`wiki/`](../wiki) в GitHub Wiki. Менять только при изменении логики синхронизации. ## Документация - Корневые `README.md`, `configuration.md`, `forkinfo.md`, `knownissues.md` — пользовательская документация. При фичах, затрагивающих пользователя, — обновлять. - [`test/README.md`](../test/README.md) — release-notes 2.0 Beta. При новой Beta-фиче — добавить запись. - [`docs/`](.) — техническая документация для контрибьюторов. При структурных изменениях кода — обновлять `architecture.md` / `runtime-paths.md` / `commands.md`. - [`wiki/`](../wiki) — публичная Wiki для пользователей. Обновления синхронизируются автоматически. ## Каналы и версии - Ветка `main` → Beta-канал, `test/xkeen.tar.gz`, автоматически после push. - GitHub Release с подписанным тегом → Stable-канал. - Версия и канал хранятся в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh): `xkeen_current_version`, `xkeen_build`. ================================================ FILE: docs/runtime-paths.md ================================================ # Раскладка на роутере Все runtime-пути на роутере. В этом репозитории каталог `_xkeen/`; после установки на роутер он переименовывается в `.xkeen/` функцией `install_xkeen_rename`. Все переменные путей определены в [`scripts/_xkeen/01_info/01_info_variable.sh`](../scripts/_xkeen/01_info/01_info_variable.sh) — не хардкодить. ## Исполняемые файлы и модули | Путь | Назначение | | --- | --- | | `/opt/sbin/xkeen` | Диспетчер (исполняемый, монолитный POSIX-sh) | | `/opt/sbin/.xkeen/` | Каталог импортируемых модулей XKeen | | `/opt/sbin/.xkeen/import.sh` | Точка сборки модулей | | `/opt/etc/init.d/S05xkeen` | Init-скрипт XKeen, генерируется `04_register_init.sh` | | `/opt/etc/init.d/S05crond` | Cron-демон, обслуживает автообновления geofile | ## Пользовательский конфиг `/opt/etc/xkeen/` — все настройки, которые правит пользователь. | Файл | Назначение | | --- | --- | | `xkeen.json` | Главный конфиг: `gh_proxy`, политики, расширения 2.0 Beta | | `ip_exclude.lst` | IP/подсети, исключённые из проксирования (с маской `/32` для одиночных адресов) | | `port_proxying.lst` | Порты, направляемые в прокси. С 2.0 Beta — единственный источник, старая `port_donor` упразднена | | `port_exclude.lst` | Порты, исключённые из проксирования. С 2.0 Beta — единственный источник, старая `port_exclude` (как переменная) упразднена | | `ipset/ru_exclude_ipv4.lst` | IPv4-сеты для российских IP — Beta-функция исключения по ipset | | `ipset/ru_exclude_ipv6.lst` | То же для IPv6 | ## Конфиги ядер | Путь | Назначение | | --- | --- | | `/opt/etc/xray/configs/` | Все JSON-конфиги Xray (`inbounds.json`, `outbounds.json`, `routing.json`, `dns.json`) | | `/opt/etc/xray/dat/` | GeoSite (`*.dat`) и GeoIP (`*.dat`) базы | | `/opt/etc/mihomo/` | Конфигурация Mihomo (`config.yaml` и подключаемые) | ## Логи | Путь | Назначение | | --- | --- | | `/opt/var/log/xkeen/` | Логи самого XKeen | | `/opt/var/log/xray/access.log` | Access-лог Xray | | `/opt/var/log/xray/error.log` | Error-лог Xray | | `/opt/var/log/xkeen-detached.log` | Лог фоновых запусков (self-detach из `-start/-stop/-restart` без TTY) | ## Runtime-state | Путь | Назначение | | --- | --- | | `/opt/var/run/` | PID-файлы (`xkeen.pid`, `xray.pid`, `mihomo.pid`) | | `/opt/tmp/xkeen/` | Временная директория XKeen | | `/opt/tmp/xray/`, `/opt/tmp/mihomo/` | Временные директории ядер | | `/opt/backups/` | Архивы резервных копий (флаги `-kb`, `-xb`, `-mb`) | | `/opt/var/spool/cron/crontabs/root` | Cron-задачи (создаются флагом `-ugc`) | ## Хуки в netfilter.d / schedule.d | Путь | Назначение | | --- | --- | | `/opt/etc/ndm/netfilter.d/proxy.sh` | Хук при пересборке правил межсетевого экрана Keenetic — переставляет iptables-правила прокси | | `/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh` | Хук на смену клиентов hotspot — обновляет ipset `xkeen_deny_mac` | ## Маркеры | Файл | Что значит | | --- | --- | | `/tmp/toff` | Маркер сессии: отключает таймаут `curl -m 180`. Создаётся флагом `-toff`, очищается trap-ом INT/TERM | | `/opt/etc/ndm/netfilter.d/aghfix.sh` | Опциональный фикс отображения клиентов в AdGuard Home (флаг `-aghfix`) | ================================================ FILE: forkinfo.md ================================================ ## Сравнение форка с оригинальным XKeen Изменения: - Исправлено добавление портов в исключения (ранее команду `xkeen -ape` нужно было прерывать по ctrl+c) - Исправлена совместная работа режима TProxy и socks5 (ранее Xkeen запускался в Mixed режиме, что приводило к неработоспособности прозрачного проксирования) - Исправлен автозапуск XKeen при старте роутера (ранее XKeen в некоторых случаях не запускался или запускался для всего устройства, а не только для своей политики - [FAQ п.12](https://jameszero.net/faq-xkeen.htm#12)) - Снято техническое ограничение, позволявшее использовать не более 15 портов проксирования и портов исключенных из проксирования - Переработана логика загрузки XKeen, Xray, Mihomo и GeoFile из интернета, уменьшающая вероятность их повреждения - Переработана логика применения правил iptables и ip6tables (ранее XKeen применял все правила, даже при не установленном компоненте IPv6) - Переработана логика добавления и удаления портов проксирования и исключаемых портов - При обновлении геофайлов, добавлении/удалении портов проксирования или портов исключений, а также выполнении других настроек, требующих перезапуск XKeen, прокси-клиент теперь перезапускается если был до этого запущен - При запуске `xkeen -d` без цифрового параметра, теперь отображается информация о текущей задержке автозапуска - При запуске или перезапуске XKeen теперь отображается информация о режиме работы - TProxy, Mixed, Redirect, Other - Не актуальные GeoSite и GeoIP antifilter-community заменены на базы [Re:filter](https://github.com/1andrevich/Re-filter-lists) - Объединены задачи планировщика по обновлению GeoSite и GeoIP. В связи с этим упразднены параметры запуска `-ugs`, `-ugi`, `-ugsc`, `-ugic`, `-dgsc`, `-dgic` - Параметр запуска `-ux` для обновления ядра Xray теперь поддерживает повышение/понижение версии - Корректная деинсталляция xray-core (ранее пакет xray не удалялся при деинсталляции) - Справка (`xkeen -h`) выровнена по табуляции и повышен контраст текста - Скрипт запуска S24xray переименован в S99xkeen - Рефакторинг кода скриптов - Актуализация конфигурационных файлов xray-core Добавлено: - Совместимость с прошивкой KeeneticOS 5+ - Возможность отключить/включить протокол IPv6 в KeeneticOS (параметр запуска `-ipv6`) - Поддержка ядра Mihomo - Возможность сменить ядро проксирования (Xray/Mihomo) параметрами `-xray` и `-mihomo` - При обновлении Xray и Mihomo теперь отображается версия уже установленного в роутере бинарника - Добавлена возможность отключить/включить перехват DNS запросов при соответствующей настройке прокси-клиента (параметр запуска `-dns`) - Поддержка внешних файлов `ip_exclude.lst`, `port_proxying.lst` и `port_exclude.lst` в директории `/opt/etc/xkeen/` для указания IP и портов (проксирования/исключения из проксирования) - Возможность загружать компоненты XKeen через [Self-Hosted прокси](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#self-hosted-прокси-для-загрузки-компонентов) при недоступности GitHub (переменные `gh_proxy(1|2)` в файле `01_info_variable.sh`) - Возможность отключить резервное копирование XKeen при обновлении (переменная `backup` в стартовом скрипте) - Возможность [OffLine установки](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#offline-установка) (параметр `-io`) - Возможность установки GeoIP базы [zkeenip.dat](https://github.com/jameszeroX/zkeen-ip) - Обновление [zkeen.dat](https://github.com/jameszeroX/zkeen-domains) и [zkeenip.dat](https://github.com/jameszeroX/zkeen-ip) по расписанию средствами XKeen - При недоступности GitHub API используется резервный источник релизов для XKeen, Xray и Mihomo - При установке теперь можно выбрать, добавлять ли XKeen в автозагрузку при включении роутера или нет - При пропуске установки Xray, его конфигурационные файлы и геобазы так же пропускаются и не устанавливаются - Mihomo и парсер yaml-файлов Yq устанавливаются и регистрируются в entware, как полноценные ipk-пакеты - Параметр запуска `-remove` для полной деинсталляции XKeen (ранее деинсталляцию нужно было выполнять покомпонентно) - Параметры запуска `-ug` (обновление геофайлов), `-ugc` (управление заданием Cron, обновляющим геофайлы), `-dgc` (удаление задания Cron, обновляющего геофайлы) - Параметр запуска `-um` для обновления/установки ядра Mihomo (поддерживается повышение/понижение версии) - Параметры запуска: `-rrm` (обновить регистрацию Mihomo), `-drm` (удалить регистрацию Mihomo) - Параметр запуска `-dm` для деинсталляции ядра Mihomo - Параметр запуска `-g`, позволяющий переустановить (добавить/удалить) геофайлы для Xray - Параметр запуска `-channel`, позволяющий выбрать канал обновления XKeen между Stable и Dev ветками - Возможность резервного копирования и восстановления конфигурации Mihomo (параметры `-mb`, `-mbr`) - Возможность контролировать число открытых файловых дескрипторов, используемых прокси-клиентом и перезапускать процесс при исчерпании лимита [подробнее](https://github.com/jameszeroX/XKeen/blob/main/configuration.md#контроль-файловых-дескрипторов) Удалено: - Поддержка внешнего файла `/opt/etc/xkeen_exclude.lst` c IP-адресами и подсетями для исключения из проксирования - Возможность установки GeoSite Antizapret (база повреждена в репозитории) - Конфигурационный файл `02_transport.json` (не используется новыми ядрами xray-core) - Запрос на перезапись и сама перезапись конфигурационных файлов Xray, если они уже существуют на момент установки XKeen - Создание резервных копий Xray, так как теперь можно интерактивно установить предыдущую версию ядра параметром `-ux`. В связи с этим упразднены параметры запуска `-xb` и `-xbr` - Логирование процесса установки XKeen в директорию `/opt/var/log/xkeen` (на практике не использовалось) - Задачи планировщика по автообновлению XKeen/Xray. В связи с этим упразднены параметры запуска `-uac`, `-ukc`, `-uxc`, `-dac`, `-dkc` и `-dxc` - Параметры запуска: `-x` (заменён на `-ux`), `-rk` (заменён на `-rrk`), `-rx` (заменён на `-rrx`), `-rc` (не актуален) ================================================ FILE: install.sh ================================================ #!/bin/sh green="\033[92m" red="\033[91m" yellow="\033[93m" light_blue="\033[96m" reset="\033[0m" url_stable="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz" url_beta="https://raw.githubusercontent.com/jameszeroX/XKeen/main/test/xkeen.tar.gz" archive_name="xkeen.tar.gz" release_fix_url="https://raw.githubusercontent.com/jameszeroX/XKeen/main/01_info_variable.sh" clear echo printf " Какую версию ${yellow}XKeen${reset} вы хотите установить?\n\n" printf " 1) Стабильную версию (${light_blue}Stable${reset})\n" printf " 2) Новую Бета-версию (${light_blue}Beta${reset})\n\n" printf " Выберите 1 или 2 [по умолчанию 1]: " read -r version_choice case "$version_choice" in 2) url="$url_beta" echo printf " Выбрана ${light_blue}Бета-версия${reset}\n" ;; *) url="$url_stable" echo printf " Выбрана ${light_blue}Стабильная версия${reset}\n" ;; esac echo get_release_var_file() { if [ -f /opt/sbin/_xkeen/01_info/01_info_variable.sh ]; then printf '%s\n' "/opt/sbin/_xkeen/01_info/01_info_variable.sh" return 0 fi if [ -f /opt/sbin/.xkeen/01_info/01_info_variable.sh ]; then printf '%s\n' "/opt/sbin/.xkeen/01_info/01_info_variable.sh" return 0 fi return 1 } download_xkeen_release() { if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "$url"; then return 0 fi if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "https://gh-proxy.com/$url"; then return 0 fi if curl -fLo "$archive_name" --connect-timeout 10 -m 15 "https://ghfast.top/$url"; then return 0 fi printf " ${red}Ошибка${reset}: не удалось загрузить ${yellow}xkeen.tar.gz${reset}\n" return 1 } download_release_fix() { target_file="$1" if curl -fLo "$target_file" --connect-timeout 10 -m 15 "$release_fix_url"; then return 0 fi if curl -fLo "$target_file" --connect-timeout 10 -m 15 "https://gh-proxy.com/$release_fix_url"; then return 0 fi if curl -fLo "$target_file" --connect-timeout 10 -m 15 "https://ghfast.top/$release_fix_url"; then return 0 fi printf " ${red}Ошибка${reset}: не удалось применить исправление ${yellow}01_info_variable.sh${reset} для релиза ${green}1.1.3.9${reset}\n" return 1 } apply_release_1139_yq_fix() { release_var_file="$(get_release_var_file)" || { printf " ${red}Ошибка${reset}: после распаковки не найден файл ${yellow}01_info_variable.sh${reset}\n" return 1 } release_version=$(sed -n 's/^xkeen_current_version="\([^"]*\)".*/\1/p' "$release_var_file" | head -n 1) release_build=$(sed -n 's/^xkeen_build="\([^"]*\)".*/\1/p' "$release_var_file" | head -n 1) if [ "$release_version" = "1.1.3.9" ] && [ "$release_build" = "Stable" ]; then if ! download_release_fix "$release_var_file"; then return 1 fi fi } if ! download_xkeen_release; then exit 1 fi if ! tar -xzf "$archive_name" -C /opt/sbin; then rm -f "$archive_name" printf " ${red}Ошибка${reset}: не удалось распаковать ${yellow}xkeen.tar.gz${reset}\n" exit 1 fi rm -f "$archive_name" if [ ! -x /opt/sbin/xkeen ]; then printf " ${red}Ошибка${reset}: после распаковки не найден исполняемый файл ${yellow}/opt/sbin/xkeen${reset}\n" exit 1 fi if ! apply_release_1139_yq_fix; then exit 1 fi exec /opt/sbin/xkeen -i ================================================ FILE: knownissues.md ================================================ - При проксировании DNS с помощью XKeen, в профиле "Политика по умолчанию" отсутствует интернет, создайте пользовательсую политкику вместо этого профиля - При подключении к роутеру нескольких провайдеров, XKeen работает через "Основное подключение", даже если в политике XKeen отметить галкой "Резервное" ================================================ FILE: scripts/_xkeen/01_info/00_info_import.sh ================================================ # Импорт информационных модулей # Модуль информации . "$xinfo_dir/01_info_variable.sh" . "$xinfo_dir/02_info_packages.sh" . "$xinfo_dir/03_info_cpu.sh" . "$xinfo_dir/04_info_mihomo.sh" . "$xinfo_dir/04_info_xray.sh" . "$xinfo_dir/05_info_geofile.sh" . "$xinfo_dir/06_info_console.sh" . "$xinfo_dir/07_info_cron.sh" . "$xinfo_dir/08_info_version/00_version_import.sh" ================================================ FILE: scripts/_xkeen/01_info/01_info_variable.sh ================================================ # ------------------------------------- # Цвета # ------------------------------------- green="\033[92m" # Зеленый red="\033[91m" # Красный yellow="\033[93m" # Желтый light_blue="\033[96m" # Голубой italic="\033[3m" # Курсив reset="\033[0m" # Сброс цветов # ------------------------------------- # Информация # ------------------------------------- current_datetime=$(date +"%Y-%m-%d_%H-%M") xkeen_current_version="2.0" xkeen_build="Beta" build_timestamp="" # ------------------------------------- # Директории # ------------------------------------- tmp_dir="/opt/tmp" # Временная директория ktmp_dir="$tmp_dir/xkeen" # Временная директория XKeen xtmp_dir="$tmp_dir/xray" # Временная директория Xray mtmp_dir="$tmp_dir/mihomo" # Временная директория Mihomo install_dir="/opt/sbin" # Директория установки xkeen_dir="$install_dir/.xkeen" # Директория скриптов XKeen xkeen_cfg="/opt/etc/xkeen" # Директория конфигурации XKeen ipset_cfg="$xkeen_cfg/ipset" # Директория IPSET log_dir="/opt/var/log" # Директория логов xray_log_dir="$log_dir/xray" # Директория логов Xray initd_dir="/opt/etc/init.d" # Директория init.d backups_dir="/opt/backups" # Директория бекапов geo_dir="/opt/etc/xray/dat" # Директория для dat cron_dir="/opt/var/spool/cron/crontabs" # Директория планировщика mihomo_conf_dir="/opt/etc/mihomo" # Директория конфигурации Mihomo xray_conf_dir="/opt/etc/xray/configs" # Директория конфигурации Xray xray_conf_smpl="$xkeen_dir/02_install/08_install_configs/02_configs_dir" register_dir="/opt/lib/opkg/info" os_modules="/lib/modules/$(uname -r)" user_modules="/opt/lib/modules" # ------------------------------------- # Файлы # ------------------------------------- xkeen_var_file="$xkeen_dir/01_info/01_info_variable.sh" file_port_proxying="$xkeen_cfg/port_proxying.lst" file_port_exclude="$xkeen_cfg/port_exclude.lst" file_ip_exclude="$xkeen_cfg/ip_exclude.lst" ru_exclude_ipv4="$ipset_cfg/ru_exclude_ipv4.lst" ru_exclude_ipv6="$ipset_cfg/ru_exclude_ipv6.lst" xkeen_config="$xkeen_cfg/xkeen.json" status_file="/opt/lib/opkg/status" initd_file="$initd_dir/S05xkeen" initd_cron="$initd_dir/S05crond" cron_file="root" file_netfilter_hook="/opt/etc/ndm/netfilter.d/proxy.sh" file_schedule_hook="/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh" name_ipset_deny_mac="xkeen_deny_mac" # ------------------------------------- # Ресурсы для проверки доступа в интернет # ------------------------------------- conn_URL="ya.ru" conn_IP1="195.208.4.1" conn_IP2="77.88.44.55" # ------------------------------------- # URL # ------------------------------------- xkeen_api_url="https://api.github.com/repos/jameszeroX/xkeen/releases/latest" # url api для XKeen xkeen_jsd_url="https://data.jsdelivr.com/v1/package/gh/jameszeroX/xkeen" # резервный url api для XKeen xkeen_tar_url="https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz" # url для загрузки XKeen xkeen_dev_url="https://raw.githubusercontent.com/jameszeroX/xkeen/main/test/xkeen.tar.gz" # url для загрузки XKeen dev xray_api_url="https://api.github.com/repos/XTLS/Xray-core/releases" # url api для Xray xray_jsd_url="https://data.jsdelivr.com/v1/package/gh/XTLS/Xray-core" # резервный url api для Xray xray_zip_url="https://github.com/XTLS/Xray-core/releases/download" # url для загрузки Xray mihomo_api_url="https://api.github.com/repos/MetaCubeX/mihomo/releases" # url api для Mihomo mihomo_jsd_url="https://data.jsdelivr.com/v1/package/gh/MetaCubeX/mihomo" # резервный url api для Mihomo mihomo_gz_url="https://github.com/MetaCubeX/mihomo/releases/download" # url для загрузки Mihomo yq_upstream_dist_url="https://github.com/mikefarah/yq/releases/latest/download" # url для загрузки оригинального Yq yq_workaround_dist_url="https://github.com/jameszeroX/yq/releases/latest/download" # url для загрузки рабочего Yq gh_proxy1="https://gh-proxy.com" # 1 прокси для загрузок с GitHub gh_proxy2="https://ghfast.top" # 2 прокси для загрузок с GitHub yq_use_workaround="true" # отключить после исправления issue 2609 (по желанию) yq_workaround_issue_url="https://github.com/mikefarah/yq/issues/2609" # issue с поломанным релизом Yq get_yq_dist_url() { if [ "$yq_use_workaround" = "true" ]; then printf '%s\n' "$yq_workaround_dist_url" else printf '%s\n' "$yq_upstream_dist_url" fi } # url для загрузки геофайлов refilter_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geosite.dat" refilterip_url="https://github.com/1andrevich/Re-filter-lists/releases/latest/download/geoip.dat" v2fly_url="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat" v2flyip_url="https://github.com/loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" zkeen_url="https://github.com/jameszeroX/zkeen-domains/releases/latest/download/zkeen.dat" zkeenip_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/zkeenip.dat" geoipv4_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/ru" geoipv6_url="https://github.com/jameszeroX/zkeen-ip/releases/latest/download/ru6" # ------------------------------------- # Журналы # ------------------------------------- xray_access_log="$xray_log_dir/access.log" xray_error_log="$xray_log_dir/error.log" # ------------------------------------- # Создание директорий и файлов # ------------------------------------- init_directories() { mkdir -p "$xray_log_dir" || { echo "Ошибка: Не удалось создать директорию $xray_log_dir"; exit 1; } mkdir -p "$initd_dir" || { echo "Ошибка: Не удалось создать директорию $initd_dir"; exit 1; } mkdir -p "$backups_dir" || { echo "Ошибка: Не удалось создать директорию $backups_dir"; exit 1; } mkdir -p "$install_dir" || { echo "Ошибка: Не удалось создать директорию $install_dir"; exit 1; } mkdir -p "$cron_dir" || { echo "Ошибка: Не удалось создать директорию $cron_dir"; exit 1; } touch "$xray_access_log" || { echo "Ошибка: Не удалось создать файл $xray_access_log"; exit 1; } touch "$xray_error_log" || { echo "Ошибка: Не удалось создать файл $xray_error_log"; exit 1; } } # Таймаут curl [ -e "/tmp/toff" ] && curl_timeout="" || curl_timeout="-m 180" # Дополнительные параметры curl curl_extra="" ================================================ FILE: scripts/_xkeen/01_info/02_info_packages.sh ================================================ # Кэшируем список установленных пакетов один раз вместо opkg-форка на каждую проверку _packages_cache=$(opkg list-installed 2>/dev/null) # Функция для проверки наличия необходимых пакетов info_packages() { package_name="$1" # Newline-prefix эмулирует якорь "^pkg ", чтобы libc не матчил libcurl case " $_packages_cache" in *" $package_name "*) package_status="installed" ;; *) package_status="not_installed" ;; esac } # Проверка наличия пакета "coreutils-uname" info_packages "coreutils-uname" info_packages_uname=$package_status # Проверка наличия пакета "coreutils-nohup" info_packages "coreutils-nohup" info_packages_nohup=$package_status # Проверка наличия пакета "curl" info_packages "curl" info_packages_curl=$package_status # Проверка наличия пакета "jq" info_packages "jq" info_packages_jq=$package_status # Проверка наличия пакета "libc" info_packages "libc" info_packages_libc=$package_status # Проверка наличия пакета "libssp" info_packages "libssp" info_packages_libssp=$package_status # Проверка наличия пакета "librt" info_packages "librt" info_packages_librt=$package_status # Проверка наличия пакета "libpthread" info_packages "libpthread" info_packages_libpthread=$package_status # Проверка наличия пакета "ca-bundle" info_packages "ca-bundle" info_packages_cabundle=$package_status # Проверка наличия пакета "iptables" info_packages "iptables" info_packages_iptables=$package_status # Проверка наличия пакета "ipset" info_packages "ipset" info_packages_ipset=$package_status ================================================ FILE: scripts/_xkeen/01_info/03_info_cpu.sh ================================================ # Функция для получения информации о процессоре info_cpu() { if command -v opkg >/dev/null 2>&1; then opkg_arch=$(opkg print-architecture | awk '!/all/ {print $2; exit}' | cut -d- -f1) case "$opkg_arch" in *'aarch64'*) architecture='arm64-v8a' ;; *'mipsel'*) architecture='mips32le' ;; *'mips'*) architecture='mips32' ;; *) architecture="$opkg_arch" ;; esac fi # Получение информации о архитектуре из файла состояния (status_file) status_architecture=$(grep -m 1 '^Architecture:' "${status_file}" | awk '{print $2}') # Получение информации о необходимости softfloat банарников [ "$architecture" != "mips32le" ] && echo && return version="$(curl -kfsS "localhost:79/rci/show/version" 2>/dev/null)" case "$version" in *KN-1212*|*KN-2910*) softfloat="true" ;; *) echo; return ;; esac } ================================================ FILE: scripts/_xkeen/01_info/04_info_mihomo.sh ================================================ # Функция для проверки установки Mihomo info_mihomo() { if [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ]; then mihomo_installed="installed" else mihomo_installed="not_installed" fi } ================================================ FILE: scripts/_xkeen/01_info/04_info_xray.sh ================================================ # Функция для проверки установки Xray info_xray() { if [ -f "$install_dir/xray" ]; then xray_installed="installed" else xray_installed="not_installed" fi } ================================================ FILE: scripts/_xkeen/01_info/05_info_geofile.sh ================================================ # Функция для проверки наличия и записи информации о базах GeoSite info_geosite() { update_refilter_geosite=false update_v2fly_geosite=false update_zkeen_geosite=false [ -f "$geo_dir/geosite_refilter.dat" ] && update_refilter_geosite=true [ -f "$geo_dir/geosite_v2fly.dat" ] && update_v2fly_geosite=true [ -f "$geo_dir/geosite_zkeen.dat" ] || [ -f "$geo_dir/zkeen.dat" ] && update_zkeen_geosite=true } # Функция для проверки наличия и записи информации о базах GeoIP info_geoip() { update_refilter_geoip=false update_v2fly_geoip=false update_zkeenip_geoip=false [ -f "$geo_dir/geoip_refilter.dat" ] && update_refilter_geoip=true [ -f "$geo_dir/geoip_v2fly.dat" ] && update_v2fly_geoip=true [ -f "$geo_dir/geoip_zkeenip.dat" ] || [ -f "$geo_dir/zkeenip.dat" ] && update_zkeenip_geoip=true } ================================================ FILE: scripts/_xkeen/01_info/06_info_console.sh ================================================ print_log_status() { local status_code=$1 local success_msg=$2 local error_msg=$3 if [ "$status_code" -eq 0 ]; then echo -e " ${green}Успешно${reset}: $success_msg" else echo -e " ${red}Ошибка${reset}: $error_msg" fi } # Обратная связь в консоль logs_cpu_info_console() { echo -e " Набор инструкций процессора: ${yellow}$architecture${reset}" case "$architecture" in arm64-v8a|mips32le|mips32) echo -e " Процессор ${green}поддерживается${reset} XKeen" ;; *) echo -e " Процессор ${red}не поддерживается${reset} XKeen" ;; esac } logs_delete_configs_info_console() { local deleted_files="" if [ -d "$xray_conf_dir" ]; then deleted_files=$(find "$xray_conf_dir" -maxdepth 1 -name '*.json' -type f) fi if [ -z "$deleted_files" ]; then echo -e " ${green}Успешно${reset}: Все конфигурационные файлы Xray удалены" else echo -e " ${red}Ошибка${reset}: Не удалены следующие конфигурационные файлы:" for file in $deleted_files; do echo -e " $file" done fi } logs_delete_geosite_info_console() { echo -e " ${yellow}Проверка${reset} выполнения операции" # antifilter переименован в refilter в install/delete, имя verification отстало for file in "geosite_refilter.dat" "geosite_v2fly.dat" "geosite_zkeen.dat"; do [ ! -f "$geo_dir/$file" ] print_log_status $? "Файл $file отсутствует в директории '$geo_dir'" "Файл $file не удален" done } logs_delete_geoip_info_console() { echo -e " ${yellow}Проверка${reset} выполнения операции" for file in "geoip_refilter.dat" "geoip_v2fly.dat" "geoip_zkeenip.dat"; do [ ! -f "$geo_dir/$file" ] print_log_status $? "Файл $file отсутствует в директории '$geo_dir'" "Файл $file не удален" done } logs_delete_geoipset_info_console() { echo -e " ${yellow}Проверка${reset} выполнения операции" [ ! -f "$ru_exclude_ipv4" ] print_log_status $? "Файл ru_exclude_ipv4.lst отсутствует в директории '$ipset_cfg'" "Файл ru_exclude_ipv4.lst не удален" [ ! -f "$ru_exclude_ipv6" ] print_log_status $? "Файл ru_exclude_ipv6.lst отсутствует в директории '$ipset_cfg'" "Файл ru_exclude_ipv6.lst не удален" } # Проверки регистрации XKeen logs_register_xkeen_status_info_console() { grep -q "Package: xkeen" "$status_file" print_log_status $? "Запись XKeen найдена в '$status_file'" "Запись XKeen не найдена в '$status_file'" } logs_register_xkeen_control_info_console() { [ -f "$register_dir/xkeen.control" ] print_log_status $? "Файл xkeen.control найден в директории '$register_dir/'" "Файл xkeen.control не найден в директории '$register_dir/'" } logs_register_xkeen_list_info_console() { [ -f "$register_dir/xkeen.list" ] print_log_status $? "Файл xkeen.list найден в директории '$register_dir/'" "Файл xkeen.list не найден в директории '$register_dir/'" } logs_register_xkeen_initd_info_console() { [ -f "$initd_file" ] print_log_status $? "init скрипт XKeen найден в директории '$initd_dir/'" "init скрипт XKeen не найден в директории '$initd_dir/'" } logs_delete_register_xkeen_info_console() { [ ! -f "$register_dir/xkeen.list" ] print_log_status $? "Файл xkeen.list не найден в директории '$register_dir/'" "Файл xkeen.list найден в директории '$register_dir/'" [ ! -f "$register_dir/xkeen.control" ] print_log_status $? "Файл xkeen.control не найден в директории '$register_dir/'" "Файл xkeen.control найден в директории '$register_dir/'" ! grep -q 'Package: xkeen' "$status_file" print_log_status $? "Регистрация пакета xkeen не обнаружена в '$status_file'" "Регистрация пакета xkeen обнаружена в '$status_file'" } # Проверки регистрации Xray logs_register_xray_status_info_console() { grep -q "Package: xray_s" "$status_file" print_log_status $? "Запись Xray найдена в '$status_file'" "Запись Xray не найдена в '$status_file'" } logs_register_xray_control_info_console() { [ -f "$register_dir/xray_s.control" ] print_log_status $? "Файл xray_s.control найден в директории '$register_dir/'" "Файл xray_s.control не найден в директории '$register_dir/'" } logs_register_xray_list_info_console() { [ -f "$register_dir/xray_s.list" ] print_log_status $? "Файл xray_s.list найден в директории '$register_dir/'" "Файл xray_s.list не найден в директории '$register_dir/'" } logs_delete_register_xray_info_console() { [ ! -f "$register_dir/xray_s.list" ] print_log_status $? "Файл xray_s.list не найден в директории '$register_dir/'" "Файл xray_s.list найден в директории '$register_dir/'" [ ! -f "$register_dir/xray_s.control" ] print_log_status $? "Файл xray_s.control не найден в директории '$register_dir/'" "Файл xray_s.control найден в директории '$register_dir/'" ! grep -q 'Package: xray_s' "$status_file" print_log_status $? "Регистрация пакета xray не обнаружена в '$status_file'" "Регистрация пакета xray обнаружена в '$status_file'" } # Проверки регистрации Mihomo logs_register_mihomo_status_info_console() { grep -q "Package: mihomo" "$status_file" print_log_status $? "Запись mihomo найдена в '$status_file'" "Запись mihomo не найдена в '$status_file'" } logs_register_mihomo_control_info_console() { [ -f "$register_dir/mihomo_s.control" ] print_log_status $? "Файл mihomo_s.control найден в директории '$register_dir/'" "Файл mihomo_s.control не найден в директории '$register_dir/'" } logs_register_mihomo_list_info_console() { [ -f "$register_dir/mihomo_s.list" ] print_log_status $? "Файл mihomo_s.list найден в директории '$register_dir/'" "Файл mihomo_s.list не найден в директории '$register_dir/'" } logs_delete_register_mihomo_info_console() { [ ! -f "$register_dir/mihomo_s.list" ] print_log_status $? "Файл mihomo_s.list не найден в директории '$register_dir/'" "Файл mihomo_s.list найден в директории '$register_dir/'" [ ! -f "$register_dir/mihomo_s.control" ] print_log_status $? "Файл mihomo_s.control не найден в директории '$register_dir/'" "Файл mihomo_s.control найден в директории '$register_dir/'" ! grep -q 'Package: mihomo_s' "$status_file" print_log_status $? "Регистрация пакета mihomo не обнаружена в '$status_file'" "Регистрация пакета mihomo обнаружена в '$status_file'" } # Проверки регистрации YQ logs_register_yq_status_info_console() { grep -q "Package: yq" "$status_file" print_log_status $? "Запись yq найдена в '$status_file'" "Запись yq не найдена в '$status_file'" } logs_register_yq_control_info_console() { [ -f "$register_dir/yq_s.control" ] print_log_status $? "Файл yq_s.control найден в директории '$register_dir/'" "Файл yq_s.control не найден в директории '$register_dir/'" } logs_register_yq_list_info_console() { [ -f "$register_dir/yq_s.list" ] print_log_status $? "Файл yq_s.list найден в директории '$register_dir/'" "Файл yq_s.list не найден в директории '$register_dir/'" } logs_delete_register_yq_info_console() { [ ! -f "$register_dir/yq_s.list" ] print_log_status $? "Файл yq_s.list не найден в директории '$register_dir/'" "Файл yq_s.list найден в директории '$register_dir/'" [ ! -f "$register_dir/yq_s.control" ] print_log_status $? "Файл yq_s.control не найден в директории '$register_dir/'" "Файл yq_s.control найден в директории '$register_dir/'" ! grep -q 'Package: yq_s' "$status_file" print_log_status $? "Регистрация пакета yq не обнаружена в '$status_file'" "Регистрация пакета yq обнаружена в '$status_file'" } # Остальные проверки logs_delete_cron_geofile_info_console() { if [ -f "$cron_dir/$cron_file" ]; then ! grep -q "ug" "$cron_dir/$cron_file" print_log_status $? "Задача автоматического обновления GeoFile удалена из cron" "Задача автоматического обновления GeoFile не удалена из cron" fi } ================================================ FILE: scripts/_xkeen/01_info/07_info_cron.sh ================================================ # Проверка наличия задач автоматического обновления в cron info_cron() { # Получаем текущую crontab конфигурацию для пользователя root cron_output=$(crontab -l -u root 2>/dev/null) # Проверяем наличие задачи с ключевым словом "ug" в crontab if echo "$cron_output" | grep -q "ug"; then info_update_geofile_cron="installed" else info_update_geofile_cron="not_installed" fi } ================================================ FILE: scripts/_xkeen/01_info/08_info_version/00_version_import.sh ================================================ # Импорт модулей проверки версий # Модули проверки версий . "$xinfo_dir/08_info_version/01_version_xkeen.sh" . "$xinfo_dir/08_info_version/02_version_mihomo.sh" . "$xinfo_dir/08_info_version/02_version_xray.sh" ================================================ FILE: scripts/_xkeen/01_info/08_info_version/01_version_xkeen.sh ================================================ # Функция для получения версии из xkeen API и сохранения ее в переменной info_version_xkeen() { version=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xkeen_api_url" | jq -r '.tag_name // .name // ""' 2>/dev/null) if [ -z "$version" ]; then echo printf "${red}Нет доступа${reset} к ${yellow}GitHub API${reset}, пробуем ${yellow}jsDelivr${reset}...\n" version=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xkeen_jsd_url" | jq -r '.versions | first' 2>/dev/null) if [ -z "$version" ]; then echo printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n" echo printf "${red}Ошибка${reset}: Не удалось получить версию ни с ${yellow}GitHub${reset}, ни с ${yellow}jsDelivr${reset}\n Проверьте соединение с интернетом или повторите позже\n Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n" echo exit 1 fi fi xkeen_github_version="${version}" } # Функция для сравнения версий XKeen info_compare_xkeen() { if [ "$xkeen_current_version" = "$xkeen_github_version" ]; then info_compare_xkeen="actual" else info_compare_xkeen="update" fi } ================================================ FILE: scripts/_xkeen/01_info/08_info_version/02_version_mihomo.sh ================================================ # Функции для получения информации о версиях Mihomo и Yq info_version_mihomo() { if [ "$mihomo_installed" = "installed" ]; then mihomo_current_version=$("$install_dir/mihomo" -v 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1) mihomo_current_version=${mihomo_current_version:-"unknown"} else mihomo_current_version="unknown" fi } info_version_yq() { if [ "$mihomo_installed" = "installed" ]; then yq_current_version=$("$install_dir/yq" -V 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1) yq_current_version=${yq_current_version:-"unknown"} else yq_current_version="unknown" fi } ================================================ FILE: scripts/_xkeen/01_info/08_info_version/02_version_xray.sh ================================================ # Функция для получения информации о версии Xray info_version_xray() { # Проверяем, установлен ли Xray if [ "$xray_installed" = "installed" ]; then # Если Xray установлен, получаем текущую версию xray_current_version=$("$install_dir/xray" version 2>&1 | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | head -1) xray_current_version=${xray_current_version:-"unknown"} else xray_current_version="unknown" fi } ================================================ FILE: scripts/_xkeen/02_install/00_install_import.sh ================================================ # Импорт модулей установки # Модули установки . "$xinstall_dir/01_install_packages.sh" . "$xinstall_dir/02_install_xray.sh" . "$xinstall_dir/02_install_mihomo.sh" . "$xinstall_dir/03_install_xkeen.sh" . "$xinstall_dir/04_install_geofile.sh" . "$xinstall_dir/05_install_geoipset.sh" . "$xinstall_dir/06_install_cron.sh" . "$xinstall_dir/07_install_register/00_register_import.sh" . "$xinstall_dir/08_install_configs/00_configs_import.sh" ================================================ FILE: scripts/_xkeen/02_install/01_install_packages.sh ================================================ # Установка необходимых пакетов install_packages() { package_status="$1" package_name="$2" if [ "${package_status}" = "not_installed" ]; then opkg install "$package_name" &>/dev/null fi } install_packages "$info_packages_curl" "curl" install_packages "$info_packages_jq" "jq" install_packages "$info_packages_libc" "libc" install_packages "$info_packages_libssp" "libssp" install_packages "$info_packages_librt" "librt" install_packages "$info_packages_iptables" "iptables" install_packages "$info_packages_libpthread" "libpthread" install_packages "$info_packages_ipset" "ipset" install_packages "$info_packages_cabundle" "ca-bundle" install_packages "$info_packages_uname" "coreutils-uname" install_packages "$info_packages_nohup" "coreutils-nohup" ================================================ FILE: scripts/_xkeen/02_install/02_install_mihomo.sh ================================================ # Функция для установки Mihomo install_mihomo() { echo -e " ${yellow}Выполняется установка${reset} Mihomo. Пожалуйста, подождите..." # Определение переменных mihomo_archive="${mtmp_dir}/mihomo.gz" # Проверка наличия архива Mihomo if [ ! -f "${mihomo_archive}" ]; then echo -e " ${red}Ошибка${reset}: Архив Mihomo не найден в '${mtmp_dir}'" return 1 fi if [ -f "$install_dir/mihomo" ]; then mv "$install_dir/mihomo" "$install_dir/mihomo_bak" fi if ! gzip -d "${mihomo_archive}" || [ ! -f "${mtmp_dir}/mihomo" ]; then echo -e " ${red}Ошибка${reset}: Не удалось распаковать архив или файл отсутствует" if [ -f "$install_dir/mihomo_bak" ]; then mv "$install_dir/mihomo_bak" "$install_dir/mihomo" echo -e " ${yellow}Восстановлен${reset} предыдущий бинарник Mihomo" fi rm -f "${mtmp_dir}/mihomo.gz" "${mtmp_dir}/mihomo" return 1 fi if ! mv "${mtmp_dir}/mihomo" "$install_dir/"; then echo -e " ${red}Ошибка${reset}: Не удалось установить Mihomo" [ -f "$install_dir/mihomo_bak" ] && mv "$install_dir/mihomo_bak" "$install_dir/mihomo" return 1 fi chmod +x "$install_dir/mihomo" echo -e " Mihomo ${green}успешно установлен${reset}" return 0 } ================================================ FILE: scripts/_xkeen/02_install/02_install_xray.sh ================================================ # Функция для установки Xray install_xray() { echo -e " ${yellow}Выполняется установка${reset} Xray. Пожалуйста, подождите..." # Определение переменных xray_archive="${xtmp_dir}/xray.zip" # Проверка наличия архива Xray if [ ! -f "${xray_archive}" ]; then echo -e " ${red}Ошибка${reset}: Архив Xray не найден в '${xtmp_dir}'" return 1 fi if [ -f "$install_dir/xray" ]; then mv "$install_dir/xray" "$install_dir/xray_bak" fi # Распаковка архива Xray if [ -d "${xtmp_dir}/xray" ]; then rm -rf "${xtmp_dir}/xray" fi if ! unzip -q "${xray_archive}" -d "${xtmp_dir}/xray"; then echo -e " ${red}Ошибка${reset}: Не удалось распаковать архив" [ -f "$install_dir/xray_bak" ] && mv "$install_dir/xray_bak" "$install_dir/xray" return 1 fi bin_source="${xtmp_dir}/xray/xray" if [ "$softfloat" = "true" ]; then if [ -f "${xtmp_dir}/xray/xray_softfloat" ]; then bin_source="${xtmp_dir}/xray/xray_softfloat" fi fi if [ ! -f "$bin_source" ]; then echo -e " ${red}Ошибка${reset}: Бинарный файл Xray не найден в архиве" if [ -f "$install_dir/xray_bak" ]; then mv "$install_dir/xray_bak" "$install_dir/xray" echo -e " ${yellow}Восстановлен${reset} предыдущий бинарник Xray" fi rm -f "$xray_archive" rm -rf "${xtmp_dir}/xray" return 1 fi mv "$bin_source" "$install_dir/xray" chmod +x "$install_dir/xray" echo -e " Xray ${green}успешно установлен${reset}" rm -f "$xray_archive" rm -rf "${xtmp_dir}/xray" # Фикс для новых ядер xray if [ -d "$xray_conf_dir" ]; then for file in "$xray_conf_dir"/*.json; do [ -f "$file" ] || continue if grep -qE '"transport"\s*:' "$file"; then mv "$file" "${file}.obsolete" fi done fi return 0 } ================================================ FILE: scripts/_xkeen/02_install/03_install_xkeen.sh ================================================ # Функция для установки XKeen install_xkeen() { xkeen_archive="$ktmp_dir/xkeen.tar.gz" # Проверка наличия архива XKeen if [ -f "$xkeen_archive" ]; then # Распаковка архива tar -xzf "$xkeen_archive" -C "$install_dir" xkeen _xkeen # Проверка наличия _xkeen в install_dir и его перемещение if [ -d "$install_dir/_xkeen" ]; then rm -rf "$install_dir/.xkeen" mv "$install_dir/_xkeen" "$install_dir/.xkeen" else echo -e " ${red}Ошибка${reset}: _xkeen не была правильно перенесена" fi # Удаление архива rm "$xkeen_archive" fi [ -d "$log_dir/xkeen" ] && rm -rf "$log_dir/xkeen" } check_keen_mode() { [ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null)" = "1" ] && return 0 keen_mode="unsupported" } ================================================ FILE: scripts/_xkeen/02_install/04_install_geofile.sh ================================================ # Функция для загрузки и обработки геофайлов process_geo_file() { local url="$1" local filename="$2" local display_name="$3" local update_flag="$4" # Защита от path traversal if case "$filename" in */*|*\\*|..|.) true;; *) false;; esac; then printf " ${red}Ошибка${reset}: Недопустимое имя файла %s (path traversal)\n" "$filename" return 1 fi local min_size=24576 # 24 KB printf " Загрузка %s...\n" "$display_name" if ! fetch_with_mirrors "$url" "$geo_dir/$filename" "$min_size"; then case "$_last_error" in size) printf " ${red}Ошибка${reset}: загруженный файл слишком мал (%s bytes) или повреждён\n Невозможно обновить. Оставляем старый файл\n\n" "$_last_size" ;; html_stub) printf " ${red}Ошибка${reset}: получена HTML-страница вместо dat-файла\n Невозможно обновить. Оставляем старый файл\n\n" ;; *) printf " ${red}Ошибка${reset}: не удалось загрузить %s\n" "$display_name" ;; esac return 1 fi if [ "$update_flag" = "true" ]; then printf " %s ${green}успешно обновлён${reset}\n\n" "$display_name" else printf " %s ${green}успешно установлен${reset}\n\n" "$display_name" fi return 0 } # Функция для установки и обновления GeoSite install_geosite() { mkdir -p "$geo_dir" || { echo "Ошибка: Не удалось создать директорию $geo_dir"; exit 1; } local zkeen_datfile="" if [ "$install_zkeen_geosite" = "true" ] || [ "$update_zkeen_geosite" = "true" ]; then zkeen_datfile="geosite_zkeen.dat" if [ -L "$geo_dir/geosite_zkeen.dat" ]; then zkeen_datfile="zkeen.dat" elif [ -L "$geo_dir/zkeen.dat" ]; then zkeen_datfile="geosite_zkeen.dat" elif [ -f "$geo_dir/zkeen.dat" ] && ! [ -f "$geo_dir/geosite_zkeen.dat" ]; then zkeen_datfile="zkeen.dat" fi fi # Параллельная загрузка независимых геофайлов local _pids="" if [ "$install_refilter_geosite" = "true" ] || [ "$update_refilter_geosite" = "true" ]; then process_geo_file "$refilter_url" "geosite_refilter.dat" \ "GeoSite Re:filter" "$update_refilter_geosite" & _pids="$_pids $!" fi if [ "$install_v2fly_geosite" = "true" ] || [ "$update_v2fly_geosite" = "true" ]; then process_geo_file "$v2fly_url" "geosite_v2fly.dat" \ "GeoSite V2Fly" "$update_v2fly_geosite" & _pids="$_pids $!" fi if [ -n "$zkeen_datfile" ]; then process_geo_file "$zkeen_url" "$zkeen_datfile" \ "GeoSite ZKeen" "$update_zkeen_geosite" & _pids="$_pids $!" fi [ -n "$_pids" ] && wait $_pids # Симлинки zkeen после успешной загрузки if [ -n "$zkeen_datfile" ]; then if [ "$zkeen_datfile" = "geosite_zkeen.dat" ]; then rm -f "$geo_dir/zkeen.dat" ln -sf "$geo_dir/geosite_zkeen.dat" "$geo_dir/zkeen.dat" else rm -f "$geo_dir/geosite_zkeen.dat" ln -sf "$geo_dir/zkeen.dat" "$geo_dir/geosite_zkeen.dat" fi fi } # Функция для установки и обновления GeoIP install_geoip() { mkdir -p "$geo_dir" || { echo "Ошибка: Не удалось создать директорию $geo_dir"; exit 1; } local zkeenip_datfile="" if [ "$install_zkeenip_geoip" = "true" ] || [ "$update_zkeenip_geoip" = "true" ]; then zkeenip_datfile="geoip_zkeenip.dat" if [ -L "$geo_dir/geoip_zkeenip.dat" ]; then zkeenip_datfile="zkeenip.dat" elif [ -L "$geo_dir/zkeenip.dat" ]; then zkeenip_datfile="geoip_zkeenip.dat" elif [ -f "$geo_dir/zkeenip.dat" ] && ! [ -f "$geo_dir/geoip_zkeenip.dat" ]; then zkeenip_datfile="zkeenip.dat" fi fi # Параллельная загрузка независимых геофайлов local _pids="" if [ "$install_refilter_geoip" = "true" ] || [ "$update_refilter_geoip" = "true" ]; then process_geo_file "$refilterip_url" "geoip_refilter.dat" \ "GeoIP Re:filter" "$update_refilter_geoip" & _pids="$_pids $!" fi if [ "$install_v2fly_geoip" = "true" ] || [ "$update_v2fly_geoip" = "true" ]; then process_geo_file "$v2flyip_url" "geoip_v2fly.dat" \ "GeoIP V2Fly" "$update_v2fly_geoip" & _pids="$_pids $!" fi if [ -n "$zkeenip_datfile" ]; then process_geo_file "$zkeenip_url" "$zkeenip_datfile" \ "GeoIP ZKeenIP" "$update_zkeenip_geoip" & _pids="$_pids $!" fi [ -n "$_pids" ] && wait $_pids # Симлинки zkeenip после успешной загрузки if [ -n "$zkeenip_datfile" ]; then if [ "$zkeenip_datfile" = "geoip_zkeenip.dat" ]; then rm -f "$geo_dir/zkeenip.dat" ln -sf "$geo_dir/geoip_zkeenip.dat" "$geo_dir/zkeenip.dat" else rm -f "$geo_dir/geoip_zkeenip.dat" ln -sf "$geo_dir/zkeenip.dat" "$geo_dir/geoip_zkeenip.dat" fi fi } ================================================ FILE: scripts/_xkeen/02_install/05_install_geoipset.sh ================================================ # Валидаторы для fetch_with_mirrors: проверяют размер + базовый синтаксис # содержимого (catch HTML-stub и мусор от proxy-error-page). _validate_geoipset_v4() { _validate_default "$1" "$2" || return 1 if ! grep -q "^[0-9]" "$1"; then _last_error="content_v4" return 1 fi return 0 } _validate_geoipset_v6() { _validate_default "$1" "$2" || return 1 if ! grep -q ":" "$1"; then _last_error="content_v6" return 1 fi return 0 } # Функция для установки и обновления GeoIPSET install_geoipset_lst() { mkdir -p "$ipset_cfg" || { echo "Ошибка: Не удалось создать директорию $ipset_cfg"; exit 1; } url="$1" dest_file="$2" display_name="$3" ip_type="$4" printf " Загрузка %s...\n" "$display_name" if [ "$ip_type" = "ipv4" ]; then _validator_name="_validate_geoipset_v4" else _validator_name="_validate_geoipset_v6" fi if ! fetch_with_mirrors "$url" "$dest_file" 0 "$_validator_name"; then case "$_last_error" in html_stub) printf " ${red}Ошибка${reset}: получена HTML-страница вместо списка IP\n Оставляем старый файл\n\n" ;; content_v4) printf " ${red}Ошибка${reset}: %s не содержит корректных IPv4-адресов\n Оставляем старый файл\n\n" "$display_name" ;; content_v6) printf " ${red}Ошибка${reset}: %s не содержит корректных IPv6-адресов\n Оставляем старый файл\n\n" "$display_name" ;; *) printf " ${red}Ошибка${reset}: не удалось загрузить %s\n\n" "$display_name" ;; esac return 1 fi [ "$action" = "init" ] && msg_geoipset="установлен" || msg_geoipset="обновлён" printf " %s ${green}успешно $msg_geoipset${reset}\n\n" "$display_name" return 0 } load_geoipset() { set="$1" file="$2" family="$3" tmp="${set}_tmp" # Заполняем tmp; основной набор подменяется только после успешного restore ipset create "$set" hash:net family "$family" -exist ipset create "$tmp" hash:net family "$family" -exist ipset flush "$tmp" if [ -f "$file" ] && awk '/^[0-9a-fA-F]/ {print "add '"$tmp"' "$1}' "$file" | ipset restore -exist; then ipset swap "$set" "$tmp" fi ipset destroy "$tmp" } install_geoipset() { action="$1" if [ "$action" = "init" ]; then # Без TTY (cron, ssh -T) read получает EOF, default-case крутит while true # бесконечно: процесс висит в R-state с CPU-spin. Дефолтим выбор на "1" # (установить), потому что xkeen -gips из cron это типичный # non-interactive caller, где пользователь явно ожидает установку. if [ ! -t 0 ]; then printf " Не интерактивный режим (нет TTY): автоматическая установка GeoIPSET\n" bypass_cron_geoipset=false else while true; do printf "\n Желаете исключить российские IP-адреса из проксирования?\n\n" printf " 1. Загрузить и установить в исключения IP-подсети России (${yellow}GeoIPSET${reset})\n" printf " 0. Пропустить\n\n" printf " Ваш выбор: " read -r choice case "$choice" in 0) printf " Пропуск установки списков GeoIPSET\n\n" if [ ! -f "$ru_exclude_ipv4" ] && [ ! -f "$ru_exclude_ipv6" ]; then bypass_cron_geoipset=true fi return 0 ;; 1) bypass_cron_geoipset=false break ;; *) printf " Неверный ввод. Пожалуйста, введите 1 или 0.\n" ;; esac done fi fi local do_v4=0 do_v6=0 if ip -4 addr show 2>/dev/null | grep -q "inet " && command -v iptables >/dev/null 2>&1; then if [ "$action" = "init" ] || [ -f "$ru_exclude_ipv4" ]; then do_v4=1 fi fi if ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && command -v ip6tables >/dev/null 2>&1; then if [ "$action" = "init" ] || [ -f "$ru_exclude_ipv6" ]; then do_v6=1 fi fi # Параллельная загрузка независимых списков local _pids="" [ "$do_v4" = "1" ] && { install_geoipset_lst "$geoipv4_url" "$ru_exclude_ipv4" "IPv4 (IPSet)" "ipv4" & _pids="$_pids $!"; } [ "$do_v6" = "1" ] && { install_geoipset_lst "$geoipv6_url" "$ru_exclude_ipv6" "IPv6 (IPSet)" "ipv6" & _pids="$_pids $!"; } [ -n "$_pids" ] && wait $_pids [ "$do_v4" = "1" ] && load_geoipset geo_exclude "$ru_exclude_ipv4" inet [ "$do_v6" = "1" ] && load_geoipset geo_exclude6 "$ru_exclude_ipv6" inet6 } ================================================ FILE: scripts/_xkeen/02_install/06_install_cron.sh ================================================ # Функция для установки задач Cron install_cron() { cron_entry= # Добавление задачи Cron для обновления GeoFile if [ -n "$choice_geofile_cron_time" ]; then cron_entry="$choice_geofile_cron_time $install_dir/xkeen -ug" fi # Если есть записи для задач Cron, то сохраняем их if [ -n "$cron_entry" ] || [ -n "$choice_cancel_cron_select" ]; then cron_file_path="$cron_dir/$cron_file" touch "$cron_file_path" chmod +x "$cron_file_path" if [ -n "$cron_entry" ]; then grep -v "$install_dir/xkeen -ug" "$cron_file_path" > "$cron_file_path.tmp" mv "$cron_file_path.tmp" "$cron_file_path" printf "%s\n" "$cron_entry" >> "$cron_file_path" fi sed -i '/^$/d' "$cron_file_path" fi } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/00_register_common.sh ================================================ # Общие функции для регистрации пакетов в opkg write_opkg_control() { package_name="$1" package_version="$2" package_depends="$3" package_source="$4" package_source_name="$5" package_maintainer="$6" package_description="$7" _installed_size=$(du -s "$install_dir" | cut -f1) _source_date_epoch=$(date +%s) { echo "Package: $package_name" echo "Version: $package_version" [ -n "$package_depends" ] && echo "Depends: $package_depends" echo "Source: $package_source" echo "SourceName: $package_source_name" echo "Section: net" echo "SourceDateEpoch: $_source_date_epoch" echo "Maintainer: $package_maintainer" echo "Architecture: $status_architecture" echo "Installed-Size: $_installed_size" echo "Description: $package_description" } > "$register_dir/$package_name.control" } write_opkg_status() { package_name="$1" package_version="$2" package_depends="$3" status_entry="$(mktemp)" { echo "Package: $package_name" echo "Version: $package_version" [ -n "$package_depends" ] && echo "Depends: $package_depends" echo "Status: install user installed" echo "Architecture: $status_architecture" echo "Installed-Time: $(date +%s)" } > "$status_entry" echo "" >> "$status_file" cat "$status_entry" >> "$status_file" echo "" >> "$status_file" rm -f "$status_entry" sed -i '/^$/{N;/^\n$/D}' "$status_file" } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/00_register_import.sh ================================================ # Импорт модулей регистраций # Модули регистрации . "$xinstall_dir/07_install_register/00_register_common.sh" . "$xinstall_dir/07_install_register/01_register_xray.sh" . "$xinstall_dir/07_install_register/01_register_mihomo.sh" . "$xinstall_dir/07_install_register/02_register_xkeen.sh" . "$xinstall_dir/07_install_register/03_register_cron.sh" ================================================ FILE: scripts/_xkeen/02_install/07_install_register/01_register_mihomo.sh ================================================ # Регистрация Mihomo register_mihomo_list() { cd "$register_dir/" || exit touch mihomo_s.list echo "/opt/sbin/mihomo" >> mihomo_s.list echo "/opt/etc/mihomo/config.yaml" >> mihomo_s.list echo "/opt/etc/mihomo" >> mihomo_s.list } register_mihomo_control() { write_opkg_control \ "mihomo_s" \ "$mihomo_current_version" \ "yq_s" \ "MetaCubeX" \ "mihomo_s" \ "jameszero" \ "A unified platform for anti-censorship." } register_mihomo_status() { write_opkg_status \ "mihomo_s" \ "$mihomo_current_version" \ "yq_s" } register_yq_list() { cd "$register_dir/" || exit touch yq_s.list echo "/opt/sbin/yq" >> yq_s.list } register_yq_control() { write_opkg_control \ "yq_s" \ "$yq_current_version" \ "" \ "mikefarah" \ "yq_s" \ "jameszero" \ "A lightweight and portable command-line YAML, JSON, INI and XML processor." } register_yq_status() { write_opkg_status \ "yq_s" \ "$yq_current_version" \ "" } add_mihomo_config() { if [ -f $install_dir/mihomo ]; then if [ -f "$mihomo_conf_dir/config.yaml" ]; then return 0 elif [ ! -d $mihomo_conf_dir ]; then mkdir $mihomo_conf_dir fi cat << EOF > "$mihomo_conf_dir/config.yaml" tproxy-port: 1181 redir-port: 1182 # Руководство по конфигурации Mihomo - https://wiki.metacubex.one/ru/config/ EOF echo echo " Добавлен шаблон конфигурационного файла Mihomo:" echo -e " ${yellow}config.yaml${reset}" sleep 2 fi } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/01_register_xray.sh ================================================ # Регистрация xray register_xray_control() { write_opkg_control \ "xray_s" \ "$xray_current_version" \ "libc, libssp, librt, libpthread, ca-bundle" \ "XTLS Team" \ "xray_s" \ "Skrill / jameszero" \ "A unified platform for anti-censorship." } register_xray_list() { cd "$register_dir/" || exit touch xray_s.list # Генерация списка файлов find /opt/etc/xray/dat -maxdepth 1 -name "*.dat" -type f | while read -r file; do echo "$file" >> xray_s.list done find /opt/etc/xray/configs -maxdepth 1 -name "*.json" -type f | while read -r file; do echo "$file" >> xray_s.list done find /opt/var/log/xray -maxdepth 1 -name "*.log" -type f | while read -r file; do echo "$file" >> xray_s.list done # Добавление дополнительных путей echo "/opt/var/log/xray" >> xray_s.list echo "/opt/etc/xray/configs" >> xray_s.list echo "/opt/etc/xray/dat" >> xray_s.list echo "/opt/etc/xray" >> xray_s.list echo "/opt/sbin/xray" >> xray_s.list } register_xray_status() { write_opkg_status \ "xray_s" \ "$xray_current_version" \ "libc, libssp, librt, libpthread, ca-bundle" } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/02_register_xkeen.sh ================================================ # Регистрация XKeen # Функция для создания файла xkeen.control register_xkeen_control() { write_opkg_control \ "xkeen" \ "$xkeen_current_version" \ "jq, curl, coreutils-uname, coreutils-nohup, iptables, ipset" \ "Skrill" \ "xkeen" \ "Skrill / jameszero" \ "The platform that makes Xray work." } register_xkeen_list() { cd "$register_dir/" || exit # Создание файла xkeen.list touch xkeen.list # Генерация списка файлов и директорий find "$xkeen_dir" -mindepth 1 | while read -r entry; do echo "$entry" >> xkeen.list done # Добавление дополнительных путей echo "$install_dir/xkeen" >> xkeen.list echo "$xkeen_dir" >> xkeen.list echo "$initd_file" >> xkeen.list echo "$log_dir/xkeen-detached.log" >> xkeen.list } register_xkeen_status() { write_opkg_status \ "xkeen" \ "$xkeen_current_version" \ "jq, curl, coreutils-uname, coreutils-nohup, iptables, ipset" } fixed_register_packages() { awk 'BEGIN {RS=""; ORS="\n\n"} {gsub(/\n\n+/,"\n\n")}1' "$status_file" > tmp_status_file && mv tmp_status_file "$status_file" } register_xkeen_initd() { old_initd_file="${initd_dir}/S24xray" pre_initd_file="${initd_dir}/S99xkeen" old_start_file="${initd_dir}/S99xkeenstart" script_file="${xinstall_dir}/07_install_register/04_register_init.sh" current_datetime=$(date "+%Y-%m-%d_%H-%M-%S") variables_to_extract="name_client name_policy table_id table_mark custom_mark dscp_exclude dscp_proxy ipv4_proxy ipv4_exclude ipv6_proxy ipv6_exclude proxy_dns proxy_router start_attempts check_fd arm64_fd other_fd delay_fd ipv6_support extended_msg backup aghfix" source_main_backup="" source_start_backup="" if [ -f "$initd_file" ]; then source_main_backup="${backups_dir}/${current_datetime}_$(basename "$initd_file")" mv "$initd_file" "$source_main_backup" elif [ -f "$pre_initd_file" ]; then source_main_backup="${backups_dir}/${current_datetime}_$(basename "$pre_initd_file")" mv "$pre_initd_file" "$source_main_backup" elif [ -f "$old_initd_file" ] || [ -f "$old_start_file" ]; then if [ -f "$old_initd_file" ]; then source_main_backup="${backups_dir}/${current_datetime}_$(basename "$old_initd_file")" mv "$old_initd_file" "$source_main_backup" fi if [ -f "$old_start_file" ]; then source_start_backup="${backups_dir}/${current_datetime}_$(basename "$old_start_file")" mv "$old_start_file" "$source_start_backup" fi fi cp "$script_file" "$initd_file" || exit 1 if [ -n "$source_main_backup" ] || [ -n "$source_start_backup" ]; then autostart_val="" start_delay_val="" if [ -n "$source_start_backup" ] && [ -f "$source_start_backup" ]; then autostart_val=$(grep '^autostart=' "$source_start_backup" | head -n 1 | cut -d'=' -f2) start_delay_val=$(grep '^start_delay=' "$source_start_backup" | head -n 1 | cut -d'=' -f2) fi if [ -n "$source_main_backup" ] && [ -f "$source_main_backup" ]; then [ -z "$autostart_val" ] && autostart_val=$(grep '^start_auto=' "$source_main_backup" | head -n 1 | cut -d'=' -f2) [ -z "$start_delay_val" ] && start_delay_val=$(grep '^start_delay=' "$source_main_backup" | head -n 1 | cut -d'=' -f2) fi if [ -n "$autostart_val" ]; then sed -i "s|^start_auto=.*|start_auto=$autostart_val|" "$initd_file" fi if [ -n "$start_delay_val" ]; then sed -i "s|^start_delay=.*|start_delay=$start_delay_val|" "$initd_file" fi if [ -n "$source_main_backup" ] && [ -f "$source_main_backup" ]; then for var in $variables_to_extract; do value=$(grep -m1 "^${var}=" "$source_main_backup") || continue escaped_value=$(printf '%s\n' "$value" | sed 's:[&#/]:\\&:g') position=$(grep -n "^${var}=" "$initd_file" | head -n 1 | cut -d: -f1) [ -n "$position" ] && sed -i "${position}s#.*#${escaped_value}#" "$initd_file" done fi fi chmod +x "$initd_file" if choice_backup_xkeen; then rm -f "$source_main_backup" "$source_start_backup" fi # Пропущенный $ ломал очистку легаси S99xkeenstart при апгрейде с 1.x rm -f "$old_initd_file" "$old_start_file" "$pre_initd_file" } # Миграция скрипта register_xray_initd() { register_xkeen_initd } register_autostart() { : } # Создание конфигурации XKeen create_xkeen_cfg() { mkdir -p "$xkeen_cfg" || { echo "Ошибка: Не удалось создать директорию $xkeen_cfg"; exit 1; } if [ -f "/opt/etc/xkeen_exclude.lst" ] && [ ! -f "$file_ip_exclude" ]; then mv "/opt/etc/xkeen_exclude.lst" "$file_ip_exclude" elif [ ! -f "$file_ip_exclude" ]; then cat << EOF > "$file_ip_exclude" #192.168.0.0/16 #2001:db8::/32 # Добавьте необходимые IP и подсети без комментария # для исключения их из проксирования EOF fi if [ ! -f "$file_port_exclude" ]; then cat << EOF > "$file_port_exclude" # # Одновременно использовать порты проксирования и исключать порты нельзя # Приоритет у портов проксирования EOF fi if [ ! -f "$file_port_proxying" ]; then cat << EOF > "$file_port_proxying" #80 #443 #596:599 # (Раскомментируйте/добавьте по образцу) единичные порты и диапазоны для проскирования EOF fi if [ ! -f "$xkeen_config" ]; then cat << EOF > "$xkeen_config" { } EOF fi } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/03_register_cron.sh ================================================ # Функция для регистрации инициализационного скрипта cron register_cron_initd() { # Проверка наличия пакета cron opkg list-installed 2>/dev/null | grep -q "^cron " && return # Определение переменных s05crond_filename="${current_datetime}_S05crond" required_script_version="0.6" # Получение текущей версии скрипта if [ -e "${initd_cron}" ]; then script_version=$(grep 'version=' "${initd_cron}" | grep -o '[0-9.]\+') fi # Содержимое скрипта script_content='#!/bin/sh # Информация о службе: Запуск / Остановка Cron # version="0.6" green="\\033[32m" red="\\033[31m" yellow="\\033[33m" reset="\\033[0m" cron_initd="/opt/sbin/crond" # Функция для проверки статуса cron cron_status() { if pidof crond > /dev/null; then return 0 # Процесс существует и работает else return 1 # Процесс не существует fi } # Функция для запуска cron start() { if cron_status; then printf " Cron ${yellow}уже запущен${reset}\\n" else $cron_initd -L /dev/null printf " Cron ${green}запущен${reset}\\n" fi } # Функция для остановки cron stop() { if cron_status; then killall crond printf " Cron ${yellow}остановлен${reset}\\n" else printf " Cron ${red}не запущен${reset}\\n" fi } # Функция для перезапуска cron restart() { stop > /dev/null 2>&1 sleep 1 start > /dev/null 2>&1 printf " Cron ${green}перезапущен${reset}\\n" } # Обработка аргументов командной строки case "$1" in start) start ;; stop) stop ;; restart) restart ;; status) if cron_status; then printf " Cron ${green}запущен${reset}\\n" else printf " Cron ${red}не запущен${reset}\\n" fi ;; *) printf " Команды: ${green}start${reset} | ${red}stop${reset} | ${yellow}restart${reset} | status\\n" ;; esac exit 0' # Создание или замена файла, если версия скрипта не соответствует требуемой версии if [ "${script_version}" != "${required_script_version}" ]; then echo -e "${script_content}" > "${initd_cron}" chmod +x "${initd_cron}" fi } # Обновление cron задач update_cron_geofile_task() { if [ -f "$cron_dir/$cron_file" ]; then tmp_file="$cron_dir/${cron_file}.tmp" cp "$cron_dir/$cron_file" "$tmp_file" if [ -z "$choice_cancel_cron_select" ]; then grep -v -e "ug" -e "ux" -e "uk" -e '^\s*$' "$tmp_file" > "$cron_dir/$cron_file" else grep -v -e "ugi" -e "ugs" -e "ux" -e "uk" -e '^\s*$' "$tmp_file" > "$cron_dir/$cron_file" fi fi } ================================================ FILE: scripts/_xkeen/02_install/07_install_register/04_register_init.sh ================================================ #!/bin/sh # Информация о службе: Запуск / Остановка XKeen # Версия: 2.30 # Окружение PATH="/opt/bin:/opt/sbin:/sbin:/bin:/usr/sbin:/usr/bin" # Цвета green="\033[92m" red="\033[91m" yellow="\033[93m" light_blue="\033[96m" reset="\033[0m" # Имена name_client="xray" name_app="XKeen" name_policy="xkeen" name_profile="xkeen" name_chain="xkeen" name_ipset_deny_mac="xkeen_deny_mac" # Директории directory_os_modules="/lib/modules/$(uname -r)" directory_user_modules="/opt/lib/modules" directory_configs_app="/opt/etc/$name_client" directory_xray_config="$directory_configs_app/configs" directory_xray_asset="$directory_configs_app/dat" directory_logs="/opt/var/log" xkeen_cfg="/opt/etc/xkeen" ipset_cfg="$xkeen_cfg/ipset" install_dir="/opt/sbin" # Файлы file_netfilter_hook="/opt/etc/ndm/netfilter.d/proxy.sh" file_schedule_hook="/opt/etc/ndm/schedule.d/00-xkeen-hotspot-sync.sh" log_access="$directory_logs/$name_client/access.log" log_error="$directory_logs/$name_client/error.log" mihomo_config="$directory_configs_app/config.yaml" file_port_proxying="$xkeen_cfg/port_proxying.lst" file_port_exclude="$xkeen_cfg/port_exclude.lst" file_ip_exclude="$xkeen_cfg/ip_exclude.lst" xkeen_config="$xkeen_cfg/xkeen.json" file_pid_fd="/var/run/xkeen_fd.pid" ru_exclude_ipv4="$ipset_cfg/ru_exclude_ipv4.lst" ru_exclude_ipv6="$ipset_cfg/ru_exclude_ipv6.lst" # URL url_server="localhost:79" url_policy="rci/show/ip/policy" url_keenetic_port="rci/ip/http" url_redirect_port="rci/ip/static" url_hotspot="rci/show/ip/hotspot" # Настройки правил iptables table_id="111" table_mark="0x111" table_redirect="nat" table_tproxy="mangle" comment_tag="xkeen_rule" comment="-m comment --comment $comment_tag" custom_mark="" # DSCP-метки dscp_exclude="62" dscp_proxy="63" ipv4_proxy="127.0.0.1" ipv4_exclude="0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 255.255.255.255" ipv6_proxy="::1" ipv6_exclude="::/128 ::1/128 64:ff9b::/96 2001::/32 2002::/16 fd00::/8 ff00::/8 fe80::/10" # Перехват DNS в прокси proxy_dns="off" # Проксирование трафика Entware proxy_router="off" # Настройки запуска start_attempts=10 start_auto="on" start_delay=20 # Контроль файловых дескрипторов check_fd="off" arm64_fd=40000 other_fd=10000 delay_fd=60 # Поддержка IPv6 ipv6_support="on" ## Расширенные сообщения запуска extended_msg="off" ## Резервное копирование XKeen при обновлении backup="on" ## Клиенты XKeen под своими IP в журнале AdGuard Home aghfix="off" # Функции журналирования log_info_router() { logger -p notice -t "$name_app" "$1"; } log_warning_router() { logger -p warning -t "$name_app" "$1"; } log_error_router() { logger -p error -t "$name_app" "$1"; } log_info_terminal() { echo -e "\n${green}Информация${reset}: $1" >&2; } log_warning_terminal() { echo -e "\n${yellow}Предупреждение${reset}: $1" >&2; } log_error_terminal() { echo -e "\n${red}Ошибка${reset}: $1" >&2; exit 1; } print_policy_info() { found="$1" has_custom="$2" ignored_custom="$3" ignore_line="" if [ "$ignored_custom" = "yes" ]; then ignore_line=" Пользовательские политики из '${yellow}xkeen.json${reset}' будут проигнорированы" fi if [ "$extended_msg" != "on" ]; then if [ "$found" = "no" ]; then log_info_terminal " Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line} Прокси будет запущен для всего устройства " fi return fi if [ "$found" = "yes" ]; then if [ "$has_custom" = "yes" ]; then custom_names=$(echo "$user_policies" | cut -d'|' -f1 | tr '\n' ',' | sed 's/,$//; s/,/, /g') policies="${name_policy}, ${custom_names}" detail_list="" if [ -n "$port_donor" ]; then detail_list=" - ${yellow}$name_policy${reset} на портах ${green}${port_donor}${reset}" elif [ -n "$port_exclude" ]; then detail_list=" - ${yellow}$name_policy${reset} на всех портах кроме ${green}${port_exclude}${reset}" else detail_list=" - ${yellow}$name_policy${reset} на всех портах" fi custom_details=$(echo "$user_policies" | while IFS='|' read -r p_name p_mark p_mode p_ports; do if [ "$p_mode" = "include" ]; then echo " - ${yellow}$p_name${reset} на портах ${green}${p_ports}${reset}" elif [ "$p_mode" = "exclude" ]; then echo " - ${yellow}$p_name${reset} на всех портах кроме ${green}${p_ports}${reset}" else echo " - ${yellow}$p_name${reset} на всех портах" fi done) log_info_terminal " Найдены политики '${yellow}${policies}${reset}' Прокси будет запущен для клиентов политик: ${detail_list} ${custom_details} " else if [ -z "$port_donor" ] && [ -z "$port_exclude" ]; then log_info_terminal " Найдена политика '${yellow}$name_policy${reset}' Не определены целевые порты для XKeen Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}' на всех портах " elif [ -n "$port_donor" ]; then log_info_terminal " Найдена политика '${yellow}$name_policy${reset}' Определены целевые порты для XKeen Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}' на портах ${green}${port_donor}${reset} " else log_info_terminal " Найдена политика '${yellow}$name_policy${reset}' Определены порты исключения для XKeen Прокси будет запущен для клиентов политики '${yellow}$name_policy${reset}' на всех портах кроме ${green}${port_exclude}${reset} " fi fi else if [ -n "$port_donor" ]; then log_info_terminal " Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line} Определены целевые порты для XKeen Прокси будет запущен для всех клиентов на портах ${green}${port_donor}${reset} " elif [ -n "$port_exclude" ]; then log_info_terminal " Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line} Определены порты исключения для XKeen Прокси будет запущен для всех клиентов на всех портах кроме ${green}${port_exclude}${reset} " else log_info_terminal " Политика '${yellow}$name_policy${reset}' не найдена в веб-интерфейсе роутера${ignore_line} Не определены целевые порты для XKeen Прокси будет запущен для всех клиентов на всех портах " fi fi } utils="jq curl grep awk sed ipset" [ "$name_client" = "mihomo" ] && utils="$utils yq" for cmd in $utils; do command -v "$cmd" >/dev/null 2>&1 || log_error_terminal "Не найдена необходимая утилита: ${yellow}$cmd${reset}" done log_clean() { [ "$name_client" = "xray" ] && : > "$log_access" && : > "$log_error"; } api_cache_init() { api_policy_json=$(curl -kfsS "${url_server}/${url_policy}" 2>/dev/null) api_port_json=$(curl -kfsS "${url_server}/${url_keenetic_port}" 2>/dev/null) api_static_json=$(curl -kfsS "${url_server}/${url_redirect_port}" 2>/dev/null) } refresh_port_cache() { api_port_json=$(curl -kfsS "${url_server}/${url_keenetic_port}" 2>/dev/null); } json_get_ports() { [ -n "$api_port_json" ] && printf '%s' "$api_port_json" | jq -r '.port, (.ssl.port // empty)' 2>/dev/null; } # Получение портов Keenetic get_keenetic_port() { ports="" ports=$(json_get_ports) case " $ports " in *" 443 "*) return 1 ;; esac if [ -z "$ports" ]; then ndmc -c 'ip http port 8080' >/dev/null 2>&1 ndmc -c 'ip http port 80' >/dev/null 2>&1 ndmc -c 'system configuration save' >/dev/null 2>&1 sleep 2 refresh_port_cache ports=$(json_get_ports) fi [ -n "$ports" ] || return 1 echo "$ports" return 0 } wait_for_webui() { max_wait=10 i=0 while [ "$i" -lt "$max_wait" ]; do pidof nginx >/dev/null 2>&1 && return 0 sleep 1 i=$((i + 1)) done return 1 } apply_ipv6_state() { ipv6_disabled= ipv6_disabled=$(sysctl -n net.ipv6.conf.default.disable_ipv6 2>/dev/null || echo "0") [ "$ipv6_disabled" -eq 1 ] && return 0 [ "$ipv6_support" != "off" ] && return 0 ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" || return 0 wait_for_webui || { log_error_router "Веб-интерфейс недоступен"; return 1; } sleep 5 sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 for dir in /proc/sys/net/ipv6/conf/*; do [ -d "$dir" ] || continue iface="${dir##*/}" case "$iface" in all|ezcfg0|t2s*) continue ;; *) [ -f "$dir/disable_ipv6" ] && echo "1" > "$dir/disable_ipv6" 2>/dev/null ;; esac done sleep 2 if [ "$(sysctl -n net.ipv6.conf.default.disable_ipv6 2>/dev/null)" -eq 1 ]; then log_info_router "Отключение IPv6 выполнено" return 0 fi } get_ipver_support() { ip4_supported=$(ip -4 addr show 2>/dev/null | grep -q "inet " && echo true || echo false) ip6_supported=$(ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && echo true || echo false) iptables_supported=$([ "$ip4_supported" = "true" ] && command -v iptables >/dev/null 2>&1 && echo true || echo false) ip6tables_supported=$([ "$ip6_supported" = "true" ] && command -v ip6tables >/dev/null 2>&1 && echo true || echo false) } strip_json_comments() { sed -e ':a; s:/\*[^*]*\*[^/]*\*/::g; ta' \ -e 's/^[[:space:]]*\/\/.*$//' \ -e 's/[[:space:]]\{1,\}\/\/.*$//' "$@" } # Функция валидации xkeen.json validate_xkeen_json() { [ ! -f "$xkeen_config" ] && return 0 if ! jq -e . "$xkeen_config" >/dev/null 2>&1; then log_error_terminal " Валидация JSON: файл '${yellow}xkeen.json${reset}' содержит синтаксические ошибки Запуск прокси невозможен " fi if ! jq -e '.xkeen.policy[]? | .name' "$xkeen_config" >/dev/null 2>&1; then if jq -e '.xkeen' "$xkeen_config" >/dev/null 2>&1; then log_error_terminal " Файл '${yellow}xkeen.json${reset}' имеет неверную структуру Запуск прокси невозможен " fi fi return 0 } # Функция поиска резервных копий конфигурационных файлов Xray check_xray_backups() { [ "$name_client" != "xray" ] && return 0 # Ищем json-файлы с типичными признаками копий bad_files=$(find "$directory_xray_config" -maxdepth 1 -type f \( -iname "*bak*.json" -o -iname "*old*.json" -o -iname "*copy*.json" -o -iname "*копия*.json" -o -iname "*orig*.json" -o -iname "*save*.json" -o -iname "*temp*.json" -o -iname "*tmp*.json" -o -name "*(*).json" \)) if [ -n "$bad_files" ]; then bad_list=$(printf '%s\n' "$bad_files" | awk -F/ '{print " - " $NF}') log_error_terminal " В директории конфигурации Xray найдены резервные копии: ${light_blue}${bad_list}${reset} Измените расширение резервных копий, например, на ${yellow}.bak${reset} Либо переместите их в поддиректорию Запуск ${yellow}$name_client${reset} ${red}отменен${reset} " fi return 0 } # Функция проверки наличия метки 255 validate_routing_mark() { [ "$proxy_router" != "on" ] && return 0 mark_valid="false" mark_msg="" bad_items="" has_items="false" all_marks_ok="true" if [ "$name_client" = "xray" ]; then mark_msg="mark" for file in "$directory_xray_config"/*.json; do [ -f "$file" ] || continue if strip_json_comments "$file" | jq -e '.outbounds != null' >/dev/null 2>&1; then has_items="true" current_bad=$(strip_json_comments "$file" | jq -r ' .outbounds[]? | select(.protocol != "blackhole" and .protocol != "dns") | select(.streamSettings.sockopt.mark != 255) | (.tag // .protocol) ') if [ -n "$current_bad" ]; then bad_items="${bad_items}${bad_items:+\n}$current_bad" all_marks_ok="false" fi fi done elif [ "$name_client" = "mihomo" ]; then mark_msg="routing-mark" if [ -f "$mihomo_config" ]; then if yq -e '.["routing-mark"] == 255' "$mihomo_config" >/dev/null 2>&1; then mark_valid="true" elif yq -e ' .proxy-providers[]? | select(.override."routing-mark" == 255) ' "$mihomo_config" >/dev/null 2>&1; then mark_valid="true" else if yq -e '.proxies != null' "$mihomo_config" >/dev/null 2>&1; then has_items="true" current_bad=$(yq -r ' .proxies[]? | select(."routing-mark" != 255) | .name ' "$mihomo_config") if [ -n "$current_bad" ]; then bad_items="${bad_items}${bad_items:+\n}$current_bad" all_marks_ok="false" fi fi fi fi fi if [ "$mark_valid" != "true" ]; then if [ "$has_items" = "true" ] && [ "$all_marks_ok" = "true" ]; then mark_valid="true" fi fi if [ "$mark_valid" != "true" ]; then error_details="" if [ -n "$bad_items" ]; then bad_list=$(printf "%b\n" "$bad_items" | awk '!seen[$0]++ {print " - " $0}') if [ "$name_client" = "xray" ]; then error_details=" Подключения без метки: ${light_blue}${bad_list}${reset}" proxy_hint=" Добавьте маркировку во ВСЕ исходящие подключения (кроме blackhole и dns)" else error_details=" Прокси без метки: ${light_blue}${bad_list}${reset}" proxy_hint=" Добавьте в config.yaml маркировку трафика глобально либо в каждое исходящее подключение" fi fi log_warning_terminal " Для проксирования трафика Entware требуется его маркировка В конфигурации ${yellow}$name_client${reset} параметр ${green}$mark_msg: 255${reset} прописан не везде$error_details $proxy_hint Проксирование трафика Entware ${red}отключено${reset} " proxy_router="off" fi return 0 } load_user_ipset_family() { set_name="$1" family="$2" addr_regex="$3" tmp="${set_name}_tmp" # Заполняем tmp; основной набор подменяется только после успешного pipeline ipset create "$set_name" hash:net family "$family" -exist ipset create "$tmp" hash:net family "$family" -exist ipset flush "$tmp" if sed -e 's/\r$//' -e 's/#.*//' -e '/^[[:space:]]*$/d' "$file_ip_exclude" | grep -Eo "$addr_regex" | awk -v s="$tmp" '{print "add "s" "$1}' | ipset restore -exist; then ipset swap "$set_name" "$tmp" fi ipset destroy "$tmp" } # Функция загрузки пользовательских исключений в ipset load_user_ipset() { [ ! -f "$file_ip_exclude" ] && return [ "$iptables_supported" = "true" ] && load_user_ipset_family user_exclude inet '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?' [ "$ip6tables_supported" = "true" ] && load_user_ipset_family user_exclude6 inet6 '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/[0-9]{1,3})?' } # Функция чтения пользовательских портов из файлов read_ports_from_file() { file_ports="$1" [ -f "$file_ports" ] || return sed -e 's/\r$//' -e 's/#.*//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e '/^$/d' "$file_ports" } # Функция обработки, валидации и нормализации списка портов validate_and_clean_ports() { input_ports="$1" mandatory_ports="$2" [ -z "$input_ports" ] && [ -z "$mandatory_ports" ] && return 1 echo "${mandatory_ports}${mandatory_ports:+,}${input_ports}" | tr ',' '\n' | awk ' function is_valid(p) { return p ~ /^[0-9]+$/ && p > 0 && p <= 65535 } { gsub(/[[:space:]]/, "", $0) gsub(/-/, ":", $0) if ($0 == "") next n = split($0, a, ":") if (n == 1) { if (is_valid(a[1])) { print a[1] } } else if (n == 2) { if (is_valid(a[1]) && is_valid(a[2])) { start = a[1] end = a[2] if (start > end) { tmp = start start = end end = tmp } if (start <= end) { print start ":" end } } } } ' | sort -n -u | tr '\n' ',' | sed 's/,$//' } # Функция обработки пользовательских портов process_user_ports() { raw_donor=$(read_ports_from_file "$file_port_proxying") [ -n "$raw_donor" ] && port_donor=$(validate_and_clean_ports "$raw_donor" "80,443") || port_donor="" port_exclude=$(validate_and_clean_ports "$(read_ports_from_file "$file_port_exclude")") if [ -n "$port_donor" ] && [ -n "$port_exclude" ]; then log_warning_terminal " Заданы и порты проксирования, и порты исключения Прокси будет запущен на портах проксирования, порты исключения игнорируются " port_exclude="" fi } # Функция нормализации сторонних политик process_custom_mark() { [ -z "$custom_mark" ] && return clean_mark="" for mark in $(echo "$custom_mark" | tr ',' ' '); do val="${mark#0x}" echo "$val" | grep -Eq '^[0-9a-fA-F]+$' && clean_mark="$clean_mark 0x$val" done custom_mark="${clean_mark# }" } # Проверка статуса прокси-клиента proxy_status() { pidof "$name_client" >/dev/null; } # Поиск конфигураций DNS check_dns_config() { [ "$proxy_dns" != "on" ] && echo "false" && return if [ "$name_client" = "xray" ]; then for file in "$directory_xray_config"/*.json; do [ -f "$file" ] || continue strip_json_comments "$file" | jq -e '.dns.servers? != null' >/dev/null 2>&1 && { echo "true"; return; } done elif [ "$name_client" = "mihomo" ]; then [ -f "$mihomo_config" ] && yq -e '.dns.enable == true' "$mihomo_config" >/dev/null 2>&1 && { echo "true"; return; } fi echo "false" } file_dns=$(check_dns_config) # Кэш списка загруженных модулей; is_module_loaded читает его без форков _loaded_modules="" _refresh_modules_cache() { _loaded_modules=" $(lsmod 2>/dev/null | awk '{print $1}' | tr '\n' ' ') "; } is_module_loaded() { case "$_loaded_modules" in *" $1 "*) return 0 ;; *) return 1 ;; esac } # Загрузка модулей load_modules() { name="${1%.ko}" if ! is_module_loaded "$name"; then for dir in "$directory_os_modules" "$directory_user_modules"; do [ -f "$dir/$1" ] && insmod "$dir/$1" >/dev/null 2>&1 && return done fi } # Обработка модулей и портов get_modules() { _refresh_modules_cache load_modules xt_comment.ko load_modules xt_TPROXY.ko load_modules xt_socket.ko load_modules xt_multiport.ko load_modules xt_dscp.ko _refresh_modules_cache # подхватить только что insmod-нутые модули if ! is_module_loaded xt_comment; then log_error_router "Модуль xt_comment не загружен" log_error_terminal " Модуль '${light_blue}xt_comment${reset}' не загружен Невозможно запустить XKeen без него Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}' " fi if [ "$mode_proxy" = "TProxy" ] || [ "$mode_proxy" = "Hybrid" ]; then for module in xt_TPROXY.ko xt_socket.ko; do if ! is_module_loaded "${module%.ko}"; then proxy_stop log_error_router "Модуль ${module} не загружен" log_error_terminal " Модуль '${light_blue}${module}${reset}' не загружен Невозможно запустить XKeen в режиме ${mode_proxy} без него Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}' " fi done fi if [ -n "$port_donor" ] || [ -n "$port_exclude" ]; then if ! is_module_loaded xt_multiport; then log_warning_router "Модуль xt_multiport не загружен" log_warning_terminal " Модуль '${light_blue}xt_multiport${reset}' не загружен Невозможно использовать выбранные порты без него Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}' Прокси будет запущен на всех портах " port_donor="" port_exclude="" fi fi if [ -n "$dscp_exclude" ] || [ -n "$dscp_proxy" ]; then if ! is_module_loaded xt_dscp; then log_warning_router "Модуль xt_dscp не загружен" log_warning_terminal " Модуль '${light_blue}xt_dscp${reset}' не загружен Работа с DSCP-метками невозможна Установите компонент роутера '${yellow}Модули ядра подсистемы Netfilter${reset}' " dscp_exclude="" dscp_proxy="" fi fi } # Получение transparent inbound'ов Xray _invalidate_inbounds_cache() { rm -f /tmp/xkeen-inbounds-cache; } get_xray_transparent_inbounds() { cache_file="/tmp/xkeen-inbounds-cache" cache_valid=0 if [ -f "$cache_file" ]; then newer=$(find "$directory_xray_config" -maxdepth 1 -name '*.json' -newer "$cache_file" 2>/dev/null | head -n 1) [ -z "$newer" ] && cache_valid=1 fi if [ "$cache_valid" = "1" ]; then cat "$cache_file" return 0 fi cache_tmp="${cache_file}.tmp.$$" { for file in "$directory_xray_config"/*.json; do [ -f "$file" ] || continue strip_json_comments "$file" | jq -r --arg file "$file" ' .inbounds[]? | select( (.protocol == "dokodemo-door" or .protocol == "tunnel") and ((.settings.followRedirect? // false) == true) ) | (.streamSettings.sockopt.tproxy? // "") as $tproxy | select($tproxy == "" or $tproxy == "redirect" or $tproxy == "tproxy") | [ (if $tproxy == "tproxy" then "tproxy" else "redirect" end), (.port // ""), (.settings.network // ""), (.tag // ""), $file ] | @tsv ' 2>/dev/null done } > "$cache_tmp" mv "$cache_tmp" "$cache_file" cat "$cache_file" } get_xray_port_by_mode() { mode="$1" port=$( get_xray_transparent_inbounds | awk -F '\t' -v mode="$mode" ' $1 == mode && $2 != "" { print $2 exit } ' ) echo "$port" } get_xray_network_by_mode() { mode="$1" network=$( get_xray_transparent_inbounds | awk -F '\t' -v mode="$mode" ' function add_networks(value, count, i, item) { gsub(/,/, " ", value) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) if (value == "") { return } count = split(value, items, /[[:space:]]+/) for (i = 1; i <= count; i++) { item = items[i] if (item != "" && !seen[item]++) { order[++order_count] = item } } } $1 == mode { add_networks($3) } END { for (i = 1; i <= order_count; i++) { printf "%s%s", order[i], (i < order_count ? " " : "") } } ' ) echo "$network" } # Получение порта для Redirect get_port_redirect() { if [ "$name_client" = "xray" ]; then port=$(get_xray_port_by_mode "redirect") [ -n "$port" ] && echo "$port" && return 0 elif [ "$name_client" = "mihomo" ]; then port=$(yq eval '.redir-port // ""' "$mihomo_config" 2>/dev/null) if [ -z "$port" ]; then port=$(yq eval '.listeners[] | select(.type == "redir") | .port // ""' "$mihomo_config" 2>/dev/null) fi [ -n "$port" ] && echo "$port" && return 0 else return 1 fi } # Получение порта для TProxy get_port_tproxy() { if [ "$name_client" = "xray" ]; then port=$(get_xray_port_by_mode "tproxy") [ -n "$port" ] && echo "$port" && return 0 elif [ "$name_client" = "mihomo" ]; then port=$(yq eval '.tproxy-port // ""' "$mihomo_config" 2>/dev/null) if [ -z "$port" ]; then port=$(yq eval '.listeners[] | select(.type == "tproxy") | .port // ""' "$mihomo_config" 2>/dev/null) fi [ -n "$port" ] && echo "$port" && return 0 else return 1 fi } # Получение сети для Redirect get_network_redirect() { if [ "$name_client" = "xray" ]; then network=$(get_xray_network_by_mode "redirect") [ -n "$network" ] && echo "$network" && return 0 elif [ "$name_client" = "mihomo" ]; then [ -n "$port_redirect" ] && echo "tcp" && return 0 echo "" && return 0 else return 1 fi } # Получение сети для TProxy get_network_tproxy() { if [ "$name_client" = "xray" ]; then network=$(get_xray_network_by_mode "tproxy") [ -n "$network" ] && echo "$network" && return 0 elif [ "$name_client" = "mihomo" ]; then if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then echo "udp" elif [ -z "$port_redirect" ] && [ -n "$port_tproxy" ]; then echo "tcp udp" else echo "" fi return 0 else return 1 fi } # Получение портов исключения из статических пробросов get_api_exclude_ports() { api_redir_result="" if [ -n "$api_static_json" ]; then api_redir_result=$(echo "$api_static_json" | jq -r ' [ .[] | select(.disable != true) | if has("end-port") then "\(.port):\(.["end-port"])" else .port end | select(. != "80" and . != "443") ] | sort | join(",")') fi echo "$api_redir_result" } # Получение исключенных портов get_port_exclude() { port_exclude_redirect="" port_exclude_result="" port_exclude_redirect=$(get_api_exclude_ports) if [ -n "$port_exclude" ]; then if [ -n "$port_exclude_redirect" ]; then port_exclude_result="$port_exclude,$port_exclude_redirect" else port_exclude_result="$port_exclude" fi else port_exclude_result="$port_exclude_redirect" fi port_exclude_result=$(printf '%s\n' "$port_exclude_result" | tr -dc '0-9,:' | tr -s ',' | sed 's/^,//; s/,$//') echo "$port_exclude_result" } # Получение исключений IPv4 get_exclude_ip4() { [ "$iptables_supported" != "true" ] && return # Получаем провайдерский IPv4 ipv4_eth=$(ip -o route get 195.208.4.1 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p' || \ ip -o route get 77.88.8.8 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p') [ -n "$ipv4_eth" ] && ipv4_eth="${ipv4_eth}/32" echo "${ipv4_eth} ${ipv4_exclude}" | tr ' ' '\n' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/^ //; s/ $//' } # Получение исключений IPv6 get_exclude_ip6() { [ "$ip6tables_supported" != "true" ] && return # Получаем провайдерский IPv6 ipv6_eth=$(ip -o -6 route get 2a0c:a9c7:8::1 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p' || \ ip -o -6 route get 2a02:6b8::feed:0ff 2>/dev/null | sed -n 's/.*src \([^ ]*\).*/\1/p') [ -n "$ipv6_eth" ] && ipv6_eth="${ipv6_eth}/128" echo "${ipv6_eth} ${ipv6_exclude}" | tr ' ' '\n' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/^ //; s/ $//' } # Получение метки политики get_policy_mark() { if [ -n "$api_policy_json" ]; then policy_mark=$(echo "$api_policy_json" | jq -r --arg pname "$name_policy" '.[] | select(.description | ascii_downcase == ($pname | ascii_downcase)) | .mark' 2>/dev/null) fi if [ -n "$policy_mark" ]; then echo "0x${policy_mark}" else echo "" fi } # Атомарная синхронизация ipset xkeen_deny_mac с текущим состоянием hotspot API. # Идемпотентна: создаёт основной набор при первом вызове, в дальнейшем # наполняет tmp-набор и делает ipset swap. Вызывается на старте XKeen и # на каждой netfilter.d/schedule.d-инвокации — это даёт динамику без # `xkeen -restart` при работе Keenetic-расписаний (родительский контроль). sync_deny_mac_ipset() { command -v ipset >/dev/null 2>&1 || return 0 ipset create "$name_ipset_deny_mac" hash:mac -exist 2>/dev/null || return 0 _xkeen_deny_tmp="${name_ipset_deny_mac}_tmp" ipset create "$_xkeen_deny_tmp" hash:mac -exist 2>/dev/null ipset flush "$_xkeen_deny_tmp" >/dev/null 2>&1 _xkeen_hotspot_json=$(curl -kfsS "${url_server}/${url_hotspot}" 2>/dev/null) if [ -n "$_xkeen_hotspot_json" ]; then printf '%s' "$_xkeen_hotspot_json" | jq -r ' ((.host // . // []) | (if type == "array" then .[] else . end)) | select((.access // "") == "deny" and (.mac // "") != "") | .mac ' 2>/dev/null | tr '[:lower:]' '[:upper:]' | while IFS= read -r _xkeen_mac; do [ -n "$_xkeen_mac" ] && ipset add "$_xkeen_deny_tmp" "$_xkeen_mac" -exist 2>/dev/null done fi ipset swap "$_xkeen_deny_tmp" "$name_ipset_deny_mac" 2>/dev/null ipset destroy "$_xkeen_deny_tmp" 2>/dev/null unset _xkeen_deny_tmp _xkeen_hotspot_json _xkeen_mac } # Получаем пользовательские политики get_user_policies() { [ ! -f "$xkeen_config" ] && return jq -r '.xkeen.policy[]? | "\(.name)|\(.port // "")" ' "$xkeen_config" 2>/dev/null } # Проверка на конфликт имен политик check_policy_name_conflict() { if [ -f "$xkeen_config" ]; then conflict=$(jq -r --arg main "$name_policy" '.xkeen.policy[] | select((.name | ascii_downcase) == ($main | ascii_downcase)) | .name' "$xkeen_config" 2>/dev/null | head -n 1) if [ -n "$conflict" ]; then log_error_router "Ошибка конфигурации: Имя политики в xkeen.json совпадает с зарезервированным" log_error_terminal " В файле '${yellow}xkeen.json${reset}' найдена политика с именем '${red}${conflict}${reset}' Это имя зарезервировано основной службой XKeen Переименуйте пользовательскую политику в json-файле Запуск ${yellow}$name_client${reset} ${red}отменен${reset} " fi fi } # Получаем порты пользовательских политик resolve_user_policies() { [ -f "$xkeen_config" ] && [ -n "$api_policy_json" ] || return api_exclude_ports=$(get_api_exclude_ports) # Получаем сопоставленные политики одним вызовом jq matched_policies=$(printf '%s' "$api_policy_json" | jq -r --argjson user_cfg "$(cat "$xkeen_config")" ' ($user_cfg.xkeen.policy // []) as $up | .[] as $api | $up[] | select( (.name | ascii_downcase) == ($api.description | ascii_downcase) ) | "\(.name)|\($api.mark)|\(.port // "")" ' 2>/dev/null) [ -z "$matched_policies" ] && return # Обрабатываем каждую политику в одном цикле echo "$matched_policies" | while IFS='|' read -r pname mark pports; do if [ -z "$pports" ]; then # Порты не указаны -> режим "all" (все порты) if [ -n "$api_exclude_ports" ]; then echo "${pname}|${mark}|exclude|${api_exclude_ports}" else echo "${pname}|${mark}|all|" fi else case "$pports" in !*) mode="exclude"; ports="${pports#!}" [ -n "$api_exclude_ports" ] && ports="${ports:+$ports,}$api_exclude_ports" ;; *) mode="include"; ports="$pports" if [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ]; then case ",$ports," in *,53,*) ;; *) ports="53,$ports" ;; esac fi ;; esac clean_ports=$(validate_and_clean_ports "$ports") [ -n "$clean_ports" ] && echo "${pname}|${mark}|${mode}|${clean_ports}" fi done } # Получение режима прокси-клиента get_mode_proxy() { if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then mode_proxy="Hybrid" elif [ -n "$port_tproxy" ]; then mode_proxy="TProxy" elif [ -n "$port_redirect" ]; then mode_proxy="Redirect" else mode_proxy="Other" fi echo "$mode_proxy" } # Настройка брандмауэра configure_firewall() { : > "$file_netfilter_hook" # Pre-evaluate dynamic variables val_exclude_ip6="$(get_exclude_ip6)" val_exclude_ip4="$(get_exclude_ip4)" cat > "$file_netfilter_hook" <<'EOL' #!/bin/sh # XKeen: Auto-generated file. DO NOT EDIT! [ -f /tmp/xkeen_ready ] || exit 0 EOL # Securely inject variables into the script inject_var() { local name="$1" local val="$2" local safe_val safe_val="${val//\'/\'\\\'\'}" printf "%s='%s'\n" "$name" "$safe_val" >> "$file_netfilter_hook" } inject_var name_client "$name_client" inject_var name_profile "$name_profile" inject_var mode_proxy "$mode_proxy" inject_var network_redirect "$network_redirect" inject_var network_tproxy "$network_tproxy" inject_var networks "$networks" inject_var name_chain "$name_chain" inject_var port_redirect "$port_redirect" inject_var port_tproxy "$port_tproxy" inject_var port_donor "$port_donor" inject_var port_exclude "$port_exclude" inject_var policy_mark "$policy_mark" inject_var comment_tag "$comment_tag" inject_var comment "$comment" inject_var custom_mark "$custom_mark" inject_var dscp_exclude "$dscp_exclude" inject_var dscp_proxy "$dscp_proxy" inject_var user_policies "$user_policies" inject_var table_redirect "$table_redirect" inject_var table_tproxy "$table_tproxy" inject_var table_mark "$table_mark" inject_var table_id "$table_id" inject_var file_dns "$file_dns" inject_var proxy_dns "$proxy_dns" inject_var proxy_router "$proxy_router" inject_var directory_os_modules "$directory_os_modules" inject_var directory_user_modules "$directory_user_modules" inject_var directory_configs_app "$directory_configs_app" inject_var directory_xray_config "$directory_xray_config" inject_var directory_xray_asset "$directory_xray_asset" inject_var iptables_supported "$iptables_supported" inject_var ip6tables_supported "$ip6tables_supported" inject_var arm64_fd "$arm64_fd" inject_var other_fd "$other_fd" inject_var aghfix "$aghfix" inject_var ipv6_proxy "$ipv6_proxy" inject_var ipv4_proxy "$ipv4_proxy" inject_var val_exclude_ip6 "$val_exclude_ip6" inject_var val_exclude_ip4 "$val_exclude_ip4" inject_var name_ipset_deny_mac "$name_ipset_deny_mac" inject_var url_server "$url_server" inject_var url_hotspot "$url_hotspot" cat >> "$file_netfilter_hook" <<'EOL' # Перезапуск скрипта restart_script() { exec /bin/sh "$0" "$@" } if pidof "$name_client" >/dev/null; then # Динамическая синхронизация ipset с deny-MAC из hotspot API. # Закрывает обход built-in политики «Без доступа в интернет» при включенном # проксировании: PREROUTING на эти MAC делает RETURN до TPROXY, пакет идёт # в FORWARD, где штатно дропается NDM-цепочкой _NDM_HOTSPOT_FWD. # Хук перезапускается NDM при netfilter rewrite, schedule.d дёргает этот же # скрипт на start/stop расписаний — список MAC всегда актуален. _xkeen_sync_deny_mac_ipset() { command -v ipset >/dev/null 2>&1 || return 0 ipset create "$name_ipset_deny_mac" hash:mac -exist 2>/dev/null || return 0 _tmp="${name_ipset_deny_mac}_tmp" ipset create "$_tmp" hash:mac -exist 2>/dev/null ipset flush "$_tmp" >/dev/null 2>&1 _hjson=$(curl -kfsS "${url_server}/${url_hotspot}" 2>/dev/null) if [ -n "$_hjson" ]; then printf '%s' "$_hjson" | jq -r ' ((.host // . // []) | (if type == "array" then .[] else . end)) | select((.access // "") == "deny" and (.mac // "") != "") | .mac ' 2>/dev/null | tr '[:lower:]' '[:upper:]' | while IFS= read -r _m; do [ -n "$_m" ] && ipset add "$_tmp" "$_m" -exist 2>/dev/null done fi ipset swap "$_tmp" "$name_ipset_deny_mac" 2>/dev/null ipset destroy "$_tmp" 2>/dev/null } _xkeen_sync_deny_mac_ipset # Аккумулируем правила в строки, применяем атомарно одним # iptables-restore --noflush на (family, table) в _xkeen_apply. # Сохраняем семантику старого ipt() для всех существующих helper'ов. _xkeen_v4_nat_rules="" _xkeen_v4_mangle_rules="" _xkeen_v6_nat_rules="" _xkeen_v6_mangle_rules="" ipt() { [ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && return 0 [ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && return 0 case "$1" in -A|-I|-D) _line=$* case "${family}_${table}" in iptables_nat) _xkeen_v4_nat_rules="${_xkeen_v4_nat_rules}${_line} " ;; iptables_mangle) _xkeen_v4_mangle_rules="${_xkeen_v4_mangle_rules}${_line} " ;; ip6tables_nat) _xkeen_v6_nat_rules="${_xkeen_v6_nat_rules}${_line} " ;; ip6tables_mangle) _xkeen_v6_mangle_rules="${_xkeen_v6_mangle_rules}${_line} " ;; esac return 0 ;; *) # Прочие операции (-F, -X) - в реальный iptables. if [ "$family" = "iptables" ]; then iptables -w -t "$table" "$@" else ip6tables -w -t "$table" "$@" fi return $? ;; esac } # Применяет аккумулированные правила одной таблицы атомарно через # iptables-restore --noflush. Custom chain $name_chain flush'ится # объявлением ":$name_chain -" перед добавлением новых правил. _xkeen_apply_table() { _family="$1" _table="$2" _rules_var="$3" eval "_rules=\${$_rules_var}" [ -z "$_rules" ] && return 0 # Удаляем устаревшие xkeen-tagged правила из built-in/system chain'ов # (PREROUTING, OUTPUT, _NDM_HOTSPOT_DNSREDIR), правила из самой $name_chain # игнорируются - там ":chain -" в blob их сам flush'ит. save_cmd="" [ "$_family" = "iptables" ] && [ "$iptables_supported" = "true" ] && save_cmd="iptables-save" [ "$_family" = "ip6tables" ] && [ "$ip6tables_supported" = "true" ] && save_cmd="ip6tables-save" [ -z "$save_cmd" ] && { _deletes=""; return; } _deletes=$($save_cmd -t "$_table" 2>/dev/null | awk \ -v tag="$comment_tag" \ -v c1="$name_chain" \ -v c2="${name_chain}_out" ' index($0, tag) && $1 == "-A" && $2 != c1 && $2 != c2 { sub(/^-A /, "-D ") print } ') { printf '*%s\n' "$_table" printf ':%s -\n' "$name_chain" [ "$proxy_router" = "on" ] && printf ':%s_out -\n' "$name_chain" [ -n "$_deletes" ] && printf '%s\n' "$_deletes" printf '%s' "$_rules" printf 'COMMIT\n' } | if [ "$_family" = "iptables" ]; then iptables-restore --noflush else ip6tables-restore --noflush fi } _xkeen_apply() { [ "$iptables_supported" = "true" ] && _xkeen_apply_table iptables nat _xkeen_v4_nat_rules || true [ "$iptables_supported" = "true" ] && _xkeen_apply_table iptables mangle _xkeen_v4_mangle_rules || true [ "$ip6tables_supported" = "true" ] && _xkeen_apply_table ip6tables nat _xkeen_v6_nat_rules || true [ "$ip6tables_supported" = "true" ] && _xkeen_apply_table ip6tables mangle _xkeen_v6_mangle_rules || true } # Добавление правил-исключений add_exclude_rules() { chain="$1" for exclude in $exclude_list; do if [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && [ "$chain" != "${name_chain}_out" ]; then case "$exclude" in 10.0.0.0/8|172.16.0.0/12|192.168.0.0/16|fd00::/8|fe80::/10) if [ "$table" = "mangle" ] && [ "$mode_proxy" = "Hybrid" ]; then ipt -A "$chain" -d "$exclude" -p tcp --dport 53 $comment -j RETURN >/dev/null 2>&1 ipt -A "$chain" -d "$exclude" -p udp ! --dport 53 $comment -j RETURN >/dev/null 2>&1 elif [ "$table" = "nat" ] && [ "$mode_proxy" = "Hybrid" ]; then ipt -A "$chain" -d "$exclude" -p tcp ! --dport 53 $comment -j RETURN >/dev/null 2>&1 ipt -A "$chain" -d "$exclude" -p udp --dport 53 $comment -j RETURN >/dev/null 2>&1 elif [ "$table" = "mangle" ] && [ "$mode_proxy" = "TProxy" ]; then ipt -A "$chain" -d "$exclude" -p tcp ! --dport 53 $comment -j RETURN >/dev/null 2>&1 ipt -A "$chain" -d "$exclude" -p udp ! --dport 53 $comment -j RETURN >/dev/null 2>&1 fi ;; esac else ipt -A "$chain" -d "$exclude" $comment -j RETURN >/dev/null 2>&1 fi done } add_ipset_exclude() { base_set="$1" set_type="${2:-hash:net}" if [ "$family" = "ip6tables" ]; then set_name="${base_set}6" ipset_family="inet6" else set_name="$base_set" ipset_family="inet" fi ipset create "$set_name" "$set_type" family "$ipset_family" -exist || return ipt -I "$chain" 1 -m set --match-set "$set_name" dst $comment -j RETURN >/dev/null 2>&1 } # Добавление правил iptables add_ipt_rule() { family="$1" table="$2" chain="$3" shift 3 [ "$family" = "iptables" ] && [ "$iptables_supported" = "false" ] && return [ "$family" = "ip6tables" ] && [ "$ip6tables_supported" = "false" ] && return # Custom chain создаётся/flush'ится одной строкой ":$name_chain -" в blob, # поэтому ни -nL guard, ни -N не нужны - всегда заполняем body. add_exclude_rules "$chain" if [ "$table" = "$table_tproxy" ]; then if [ "$mode_proxy" = "Hybrid" ]; then set -- -p udp -m conntrack --ctstate ESTABLISHED,RELATED $comment -j CONNMARK --restore-mark else set -- -m conntrack --ctstate ESTABLISHED,RELATED $comment -j CONNMARK --restore-mark fi ipt -I "$chain" 1 "$@" >/dev/null 2>&1 fi case "$mode_proxy" in Hybrid) if [ "$table" = "$table_redirect" ]; then ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1 add_ipset_exclude ext_exclude hash:ip add_ipset_exclude geo_exclude hash:net add_ipset_exclude user_exclude hash:net ipt -A "$chain" -p tcp $comment -j REDIRECT --to-port "$port_redirect" >/dev/null 2>&1 else ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1 add_ipset_exclude ext_exclude hash:ip add_ipset_exclude geo_exclude hash:net add_ipset_exclude user_exclude hash:net ipt -A "$chain" -p udp -m socket --transparent $comment -j MARK --set-mark "$table_mark" >/dev/null 2>&1 ipt -A "$chain" -p udp -m mark ! --mark 0 $comment -j CONNMARK --save-mark >/dev/null 2>&1 ipt -A "$chain" -p udp $comment -j TPROXY --on-ip "$proxy_ip" --on-port "$port_tproxy" --tproxy-mark "$table_mark" >/dev/null 2>&1 fi ;; TProxy) ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1 for net in $network_tproxy; do add_ipset_exclude ext_exclude hash:ip add_ipset_exclude geo_exclude hash:net add_ipset_exclude user_exclude hash:net ipt -A "$chain" -p "$net" -m socket --transparent $comment -j MARK --set-mark "$table_mark" >/dev/null 2>&1 ipt -A "$chain" -p "$net" -m mark ! --mark 0 $comment -j CONNMARK --save-mark >/dev/null 2>&1 ipt -A "$chain" -p "$net" $comment -j TPROXY --on-ip "$proxy_ip" --on-port "$port_tproxy" --tproxy-mark "$table_mark" >/dev/null 2>&1 done ;; Redirect) ipt -I "$chain" 1 -m conntrack --ctstate DNAT $comment -j RETURN >/dev/null 2>&1 add_ipset_exclude ext_exclude hash:ip add_ipset_exclude geo_exclude hash:net add_ipset_exclude user_exclude hash:net for net in $network_redirect; do ipt -A "$chain" -p "$net" $comment -j REDIRECT --to-port "$port_redirect" >/dev/null 2>&1 done ;; *) exit 0 ;; esac if [ -n "$dscp_exclude" ]; then for dscp in $dscp_exclude; do ipt -I "$chain" -m dscp --dscp "$dscp" $comment -j RETURN >/dev/null 2>&1 done fi } # Настройка таблицы маршрутов configure_route() { ip_version="$1" # Определяем таблицу маршрутизации if [ -n "$policy_mark" ]; then policy_table=$(ip rule show | awk -v policy="$policy_mark" '$0 ~ policy && /lookup/ && !/blackhole/ {print $(NF); exit}') fi source_table="${policy_table:-main}" # Проверяем есть ли default маршрут check_default() { if [ "$ip_version" = "6" ] && ! ip -6 route show default 2>/dev/null | grep -q .; then return 0 fi if [ "$source_table" = "main" ]; then ip -"$ip_version" route show default 2>/dev/null | grep -q '^default' else ip -"$ip_version" route show table all 2>/dev/null | grep -E "^[[:space:]]*default .* table $policy_table([[:space:]]|$)" | grep -vq 'unreachable' >/dev/null fi } attempts=0 max_attempts=4 until check_default; do attempts=$((attempts + 1)) if [ "$attempts" -ge "$max_attempts" ]; then [ "$ip_version" = "4" ] && touch "/tmp/noinet" return 1 fi sleep 1 done [ "$ip_version" = "4" ] && rm -f "/tmp/noinet" ip -"$ip_version" rule del fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1 || true ip -"$ip_version" route flush table "$table_id" >/dev/null 2>&1 || true ip -"$ip_version" route add local default dev lo table "$table_id" >/dev/null 2>&1 || true ip -"$ip_version" rule add fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1 || true # Копируем маршруты ip -"$ip_version" route show table "$source_table" 2>/dev/null | while read -r route_line; do case "$route_line" in default*|unreachable*|blackhole*) continue ;; *) ip -"$ip_version" route add table "$table_id" $route_line >/dev/null 2>&1 || true ;; esac done return 0 } # Создание множественных правил multiport add_multiport_rules() { family="$1" table="$2" net="$3" mark="$4" ports="$5" target="$6" [ -z "$ports" ] && return num_ports=$(echo "$ports" | tr ',' '\n' | wc -l) i=1 while [ "$i" -le "$num_ports" ]; do end=$((i + 6)) chunk=$(echo "$ports" | tr ',' '\n' | sed -n "${i},${end}p" | tr '\n' ',' | sed 's/,$//') [ -z "$chunk" ] && break if [ -n "$mark" ]; then set -- -m connmark --mark "$mark" -m conntrack ! --ctstate INVALID -p "$net" -m multiport --dports "$chunk" $comment -j "$target" else set -- -m conntrack ! --ctstate INVALID -p "$net" -m multiport --dports "$chunk" $comment -j "$target" fi ipt -A PREROUTING "$@" >/dev/null 2>&1 i=$((i + 7)) done } # Добавление цепочек PREROUTING add_prerouting() { family="$1" table="$2" # MAC-bypass для built-in «Без доступа в интернет»: RETURN из PREROUTING # до xkeen-jumps, пакет минует TPROXY/REDIRECT/MARK и попадает в FORWARD, # где NDM-цепочка _NDM_HOTSPOT_FWD его дропнет штатно. -m mac --mac-source # видит L2-MAC только для устройств в одном broadcast-домене с роутером # (LAN/Wi-Fi/guest-bridge); за L3-VLAN правило безвредно неактивно. ipt -I PREROUTING 1 -m set --match-set "$name_ipset_deny_mac" src $comment -j RETURN >/dev/null 2>&1 for net in $networks; do if [ "$mode_proxy" = "Hybrid" ]; then [ "$table" = "nat" ] && [ "$net" != "tcp" ] && continue [ "$table" = "mangle" ] && [ "$net" != "udp" ] && continue fi if [ "$mode_proxy" = "TProxy" ]; then proto_match="" else proto_match="-p $net" fi for dscp in $dscp_proxy; do set -- -m conntrack ! --ctstate INVALID $proto_match -m dscp --dscp "$dscp" $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 done if [ "$proxy_router" = "on" ]; then set -- -i lo -m mark --mark "$table_mark" $proto_match $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 fi # Пользовательские политики из xkeen.json # Heredoc вместо echo|while - while должен исполниться в parent shell, # чтобы аккумуляторы _xkeen_*_rules в ipt() модифицировались в нужном scope. while IFS='|' read -r pname pmark pmode pports; do [ -z "$pmark" ] && continue pmark=$(echo "$pmark" | tr -d ' \r\n') pmode=$(echo "$pmode" | tr -d ' \r\n') pports=$(echo "$pports" | tr -d ' \r\n') if [ "$pmode" = "all" ]; then set -- -m connmark --mark 0x"$pmark" -m conntrack ! --ctstate INVALID $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 elif [ "$pmode" = "include" ]; then add_multiport_rules "$family" "$table" "$net" "0x$pmark" "$pports" "$name_chain" elif [ "$pmode" = "exclude" ]; then add_multiport_rules "$family" "$table" "$net" "0x$pmark" "$pports" "RETURN" set -- -m connmark --mark 0x"$pmark" -m conntrack ! --ctstate INVALID -p "$net" $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 fi done </dev/null 2>&1 else # Политика xkeen, когда порты не указаны (проксирование на всех портах) set -- -m connmark --mark "$policy_mark" -m conntrack ! --ctstate INVALID $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 fi # НЕТ политики xkeen else # заданы порты проксирования if [ -n "$port_donor" ]; then add_multiport_rules "$family" "$table" "$net" "" "$port_donor" "$name_chain" # заданы порты исключения elif [ -n "$port_exclude" ]; then add_multiport_rules "$family" "$table" "$net" "" "$port_exclude" "RETURN" set -- -m conntrack ! --ctstate INVALID -p "$net" $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 # Если нет ни xkeen, ни пользовательских политик -> перехватываем всё else set -- -m conntrack ! --ctstate INVALID $comment -j "$name_chain" ipt -A PREROUTING "$@" >/dev/null 2>&1 fi fi done } # Добавление цепочек для проксирования трафика Entware add_output() { family="$1" table="$2" [ "$proxy_router" != "on" ] && return out_chain="${name_chain}_out" # ":${name_chain}_out -" в blob создаст/flush'ит chain атомарно, # body заполняется всегда. orig_chain="$chain" chain="$out_chain" ipt -A "$out_chain" -o lo $comment -j RETURN >/dev/null 2>&1 ipt -A "$out_chain" -m mark --mark 255 $comment -j RETURN >/dev/null 2>&1 add_exclude_rules "$out_chain" add_ipset_exclude ext_exclude hash:ip add_ipset_exclude geo_exclude hash:net add_ipset_exclude user_exclude hash:net chain="$orig_chain" for net in $networks; do if [ "$mode_proxy" = "Hybrid" ]; then [ "$table" = "nat" ] && [ "$net" != "tcp" ] && continue [ "$table" = "mangle" ] && [ "$net" != "udp" ] && continue fi if [ "$mode_proxy" = "TProxy" ]; then proto_match="" else proto_match="-p $net" fi set -- -m conntrack ! --ctstate INVALID $proto_match $comment -j "$out_chain" ipt -A OUTPUT "$@" >/dev/null 2>&1 if [ "$table" = "$table_redirect" ]; then set -- -p "$net" $comment -j REDIRECT --to-port "$port_redirect" ipt -A "$out_chain" "$@" >/dev/null 2>&1 elif [ "$table" = "$table_tproxy" ]; then set -- -p "$net" $comment -j MARK --set-mark "$table_mark" ipt -A "$out_chain" "$@" >/dev/null 2>&1 fi done } dns_redir() { family="$1" table="nat" [ "$aghfix" != "on" ] && return [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && return all_marks="" [ -n "$policy_mark" ] && all_marks="$policy_mark" [ -n "$custom_mark" ] && all_marks="$custom_mark $all_marks" if [ -n "$user_policies" ]; then user_marks=$(echo "$user_policies" | awk -F'|' '{if ($2 != "") print "0x"$2}') all_marks="$all_marks $user_marks" fi for mark in $all_marks; do mark=$(echo "$mark" | tr -d ' \r\n') [ -z "$mark" ] && continue for proto in udp tcp; do set -- -p "$proto" -m mark --mark "$mark" -m pkttype --pkt-type unicast -m "$proto" --dport 53 $comment -j REDIRECT --to-ports 53 ipt -I _NDM_HOTSPOT_DNSREDIR "$@" >/dev/null 2>&1 done done } if [ -n "$port_donor" ] || [ -n "$port_exclude" ]; then [ "$file_dns" = "true" ] && [ "$proxy_dns" = "on" ] && [ -n "$port_donor" ] && port_donor="53,$port_donor" fi for family in iptables ip6tables; do [ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && continue [ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && continue if [ "$family" = "ip6tables" ]; then exclude_list="$val_exclude_ip6" proxy_ip="$ipv6_proxy" configure_route 6 else exclude_list="$val_exclude_ip4" proxy_ip="$ipv4_proxy" configure_route 4 fi if [ -n "$port_redirect" ] && [ -n "$port_tproxy" ]; then for table in "$table_tproxy" "$table_redirect"; do add_ipt_rule "$family" "$table" "$name_chain" add_prerouting "$family" "$table" add_output "$family" "$table" done elif [ -z "$port_redirect" ] && [ -n "$port_tproxy" ]; then table="$table_tproxy" add_ipt_rule "$family" "$table" "$name_chain" add_prerouting "$family" "$table" add_output "$family" "$table" elif [ -n "$port_redirect" ] && [ -z "$port_tproxy" ]; then table="$table_redirect" add_ipt_rule "$family" "$table" "$name_chain" add_prerouting "$family" "$table" add_output "$family" "$table" fi dns_redir "$family" done # Атомарно применяем все аккумулированные правила одним # iptables-restore --noflush per (family, table). _xkeen_apply else [ -f "/tmp/xkeen_starting.lock" ] && exit 0 touch "/tmp/xkeen_starting.lock" . "/opt/sbin/.xkeen/01_info/03_info_cpu.sh" status_file="/opt/lib/opkg/status" info_cpu fd_limit="$other_fd" [ "$architecture" = "arm64-v8a" ] && fd_limit="$arm64_fd" ulimit -SHn "$fd_limit" case "$name_client" in xray) export XRAY_LOCATION_CONFDIR="$directory_xray_config" export XRAY_LOCATION_ASSET="$directory_xray_asset" "$name_client" run >/dev/null 2>&1 & ;; mihomo) export CLASH_HOME_DIR="$directory_configs_app" "$name_client" >/dev/null 2>&1 & ;; esac _probe=0 while [ "$_probe" -lt 60 ]; do pidof "$name_client" >/dev/null 2>&1 && break _probe=$((_probe + 1)) usleep 100000 done unset _probe rm -f "/tmp/xkeen_starting.lock" if pidof "$name_client" >/dev/null; then restart_script "$@" else exit 1 fi fi EOL sed -i '1,2!{/^[[:space:]]*#/d; /^[[:space:]]*$/d}' "$file_netfilter_hook" chmod 700 "$file_netfilter_hook" # Schedule.d-хук: NDM вызывает scripts/schedule.d при start/stop расписаний # (родительский контроль). Хук дёргает netfilter.d/proxy.sh, который # ре-синхронизирует ipset deny-MAC из актуального hotspot API. mkdir -p "$(dirname "$file_schedule_hook")" 2>/dev/null cat > "$file_schedule_hook" <<'SCHEDULE_EOL' #!/bin/sh # XKeen: re-sync deny MAC ipset on schedule start/stop. Auto-generated. DO NOT EDIT! [ "$1" = "start" ] || [ "$1" = "stop" ] || exit 0 [ -x /opt/etc/ndm/netfilter.d/proxy.sh ] && /opt/etc/ndm/netfilter.d/proxy.sh SCHEDULE_EOL chmod 755 "$file_schedule_hook" sh "$file_netfilter_hook" } # Удаление правил iptables clean_firewall() { [ -f "$file_netfilter_hook" ] && : > "$file_netfilter_hook" get_ipver_support for family in iptables ip6tables; do [ "$family" = "iptables" ] && [ "$iptables_supported" != "true" ] && continue [ "$family" = "ip6tables" ] && [ "$ip6tables_supported" != "true" ] && continue if "$family" -w -t nat -nL _NDM_HOTSPOT_DNSREDIR >/dev/null 2>&1; then "$family" -w -t nat -S _NDM_HOTSPOT_DNSREDIR | grep -E -- "$comment_tag" | sed 's/^-A /-D /' | while IFS= read -r rule; do [ -n "$rule" ] && "$family" -w -t nat $rule >/dev/null 2>&1 done fi done clean_run() { family="$1" table="$2" name_chain="$3" for sys_chain in PREROUTING OUTPUT; do "$family" -w -t "$table" -S "$sys_chain" 2>/dev/null | grep -E -- "$comment_tag" | sed 's/^-A /-D /' | while IFS= read -r rule; do [ -n "$rule" ] && "$family" -w -t "$table" $rule >/dev/null 2>&1 done done if "$family" -w -t "$table" -nL "$name_chain" >/dev/null 2>&1; then "$family" -w -t "$table" -F "$name_chain" >/dev/null 2>&1 "$family" -w -t "$table" -X "$name_chain" >/dev/null 2>&1 fi out_chain="${name_chain}_out" if "$family" -w -t "$table" -nL "$out_chain" >/dev/null 2>&1; then "$family" -w -t "$table" -F "$out_chain" >/dev/null 2>&1 "$family" -w -t "$table" -X "$out_chain" >/dev/null 2>&1 fi } for family in iptables ip6tables; do for chain in nat mangle; do clean_run "$family" "$chain" "$name_chain" done done if command -v ip >/dev/null 2>&1; then for family in 4 6; do while ip -"$family" rule del fwmark "$table_mark" lookup "$table_id" >/dev/null 2>&1; do :; done ip -"$family" route flush table "$table_id" >/dev/null 2>&1 || true done fi # Очистка и удаление списков ipset if command -v ipset >/dev/null 2>&1; then for set in geo_exclude geo_exclude6 user_exclude user_exclude6 "$name_ipset_deny_mac"; do ipset flush "$set" >/dev/null 2>&1 ipset destroy "$set" >/dev/null 2>&1 done fi # Schedule.d-hook идемпотентно перегенерируется в configure_firewall, # на остановке убираем чтобы NDM не дёргал мёртвый netfilter.d/proxy.sh. [ -f "$file_schedule_hook" ] && rm -f "$file_schedule_hook" } # Мониторинг файловых дескрипторов monitor_fd() { while true; do client_pid=$(pidof "$name_client" | awk '{print $1}') if [ -n "$client_pid" ] && [ -d "/proc/$client_pid/fd" ]; then limit=$(awk '/Max open files/ {print $4}' "/proc/$client_pid/limits") set -- /proc/$client_pid/fd/* [ -e "$1" ] || set -- current=$# if [ "$limit" -gt 0 ] && [ "$current" -gt $((limit * 90 / 100)) ]; then log_warning_router "$name_client открыл $current из $limit файловых дескрипторов, инициирован перезапуск" rm -f "$file_pid_fd" fd_out=true proxy_stop proxy_start "on" exit 0 fi fi sleep "$delay_fd" done } load_ipset() { set="$1" file="$2" family="$3" tmp="${set}_tmp" # Заполняем tmp; основной набор подменяется только после успешного restore ipset create "$set" hash:net family "$family" -exist ipset create "$tmp" hash:net family "$family" -exist ipset flush "$tmp" if [ -f "$file" ] && sed -e 's/\r$//' -e 's/#.*//' -e '/^[[:space:]]*$/d' "$file" | awk '{print "add '"$tmp"' "$1}' | ipset restore -exist; then ipset swap "$set" "$tmp" fi ipset destroy "$tmp" } apply_fd_limit() { fd_limit="$other_fd" [ "$architecture" = "arm64-v8a" ] && fd_limit="$arm64_fd" ulimit -SHn "$fd_limit" } cleanup_fd_monitor() { [ -f "$file_pid_fd" ] || return 0 kill "$(cat "$file_pid_fd")" 2>/dev/null rm -f "$file_pid_fd" } missing_files_template=' '"${light_blue}"'Отсутствуют исполняемые файлы:'"${reset}"' '"${yellow}"'%b'"${reset}"' '"${green}"'Возможные причины:'"${reset}"' • XKeen установлен во внутреннюю память и на ней недостаточно места • У файла отсутствуют права на выполнение '"${green}"'Рекомендуемые действия:'"${reset}"' • Переустановите XKeen на внешний накопитель • Скопируйте недостающий файл вручную и сделайте исполняемым ' check_binary() { file="$1" path="$install_dir/$file" if [ ! -f "$path" ] || [ ! -x "$path" ]; then return 1 fi check_cmd="version" [ "$file" = "xray" ] && check_cmd="version" [ "$file" = "yq" ] && check_cmd="--version" [ "$file" = "mihomo" ] && check_cmd="-v" if ! "$file" $check_cmd >/dev/null 2>&1; then log_error_router "Бинарный файл $file аварийно остановлен" log_error_terminal " Бинарный файл ${yellow}$file${reset} аварийно остановлен ${red}Файл повреждён или несовместим с процессором${reset} вашего роутера Установите другую версию ${yellow}$file${reset} " fi return 0 } info_health_binary() { missing_files="" add_to_missing() { file_name="$1" prefix=" - " if [ -z "$missing_files" ]; then missing_files="${prefix}${yellow}${file_name}${reset}" else missing_files="${missing_files}\n ${prefix}${yellow}${file_name}${reset}" fi } case "$name_client" in xray) if ! check_binary xray; then add_to_missing "xray"; fi ;; mihomo) for file in mihomo yq; do if ! check_binary "$file"; then add_to_missing "$file"; fi done ;; esac if [ -n "$missing_files" ]; then log_error_terminal "$(printf "$missing_files_template" "$missing_files")" fi } # Очистка при аварийной остановке прокси-клиента emergency_clear() { rm -f "/tmp/xkeen_ready" cleanup_fd_monitor clean_firewall } # Запуск прокси-клиента proxy_start() { start_manual="$1" if [ "$start_manual" = "on" ] || [ "$start_auto" = "on" ]; then _invalidate_inbounds_cache apply_ipv6_state get_ipver_support info_health_binary validate_xkeen_json check_policy_name_conflict check_xray_backups validate_routing_mark log_clean api_cache_init sync_deny_mac_ipset process_user_ports process_custom_mark port_redirect=$(get_port_redirect) network_redirect=$(get_network_redirect) port_tproxy=$(get_port_tproxy) network_tproxy=$(get_network_tproxy) mode_proxy=$(get_mode_proxy) if [ "$mode_proxy" != "Other" ]; then policy_mark=$(get_policy_mark) if [ -n "$policy_mark" ]; then user_policies=$(resolve_user_policies) if [ -n "$user_policies" ]; then print_policy_info "yes" "yes" else print_policy_info "yes" "no" fi else raw_user_policies=$(get_user_policies) ignored_custom="no" if [ -n "$raw_user_policies" ]; then ignored_custom="yes" fi print_policy_info "no" "no" "$ignored_custom" user_policies="" fi networks=$(printf '%s\n' $network_redirect $network_tproxy | tr ',' ' ' | tr -s ' ' '\n' | sort -u | tr '\n' ' ') networks=${networks% } if [ -n "$policy_mark" ] && [ -z "$port_donor" ]; then port_exclude=$(get_port_exclude) fi if ! proxy_status && { [ -n "$port_donor" ] || [ -n "$port_exclude" ] || [ "$mode_proxy" = "TProxy" ] || [ "$mode_proxy" = "Hybrid" ]; }; then get_modules fi if [ "$mode_proxy" = "TProxy" ]; then keenetic_ssl="$(get_keenetic_port)" || { proxy_stop log_error_router "Порт 443 занят сервисами Keenetic" log_error_terminal " Необходимый для режима ${light_blue}TProxy${reset} ${red}443 порт занят${reset} сервисами Keenetic Освободите его на странице 'Пользователи и доступ' веб-интерфейса роутера " } fi fi if proxy_status; then echo -e " Прокси-клиент уже ${green}запущен${reset}" # Marker до configure_firewall: тот завершается `sh proxy.sh`, # gate в хуке читает /tmp/xkeen_ready. touch "/tmp/xkeen_ready" [ "$mode_proxy" != "Other" ] && configure_firewall if [ "$start_manual" = "on" ]; then log_error_terminal "Не удалось запустить ${yellow}$name_client${reset}, так как он уже запущен" else log_info_router "Прокси-клиент успешно запущен в режиме $mode_proxy" rm -f "/tmp/xkeen_coldstart.lock" fi else log_info_router "Инициирован запуск прокси-клиента" attempt=1 . "/opt/sbin/.xkeen/01_info/03_info_cpu.sh" status_file="/opt/lib/opkg/status" info_cpu while [ "$attempt" -le "$start_attempts" ]; do case "$name_client" in xray) export XRAY_LOCATION_CONFDIR="$directory_xray_config" export XRAY_LOCATION_ASSET="$directory_xray_asset" find "$directory_xray_config" -maxdepth 1 -name '._*.json' -type f -delete apply_fd_limit if [ -n "$fd_out" ]; then nohup "$name_client" run >/dev/null 2>&1 & unset fd_out else "$name_client" run & fi ;; mihomo) export CLASH_HOME_DIR="$directory_configs_app" apply_fd_limit if [ -n "$fd_out" ]; then nohup "$name_client" >/dev/null 2>&1 & unset fd_out else "$name_client" & fi ;; *) log_error_terminal "Неизвестный прокси-клиент: ${yellow}$name_client${reset}" ;; esac _probe_attempt=0 while [ "$_probe_attempt" -lt 60 ]; do proxy_status && break _probe_attempt=$((_probe_attempt + 1)) usleep 50000 done unset _probe_attempt if proxy_status; then # См. alive-branch: marker до configure_firewall. touch "/tmp/xkeen_ready" [ "$mode_proxy" != "Other" ] && configure_firewall _pids="" [ "$iptables_supported" = "true" ] && [ -f "$ru_exclude_ipv4" ] && { load_ipset geo_exclude "$ru_exclude_ipv4" inet & _pids="$_pids $!"; } [ "$ip6tables_supported" = "true" ] && [ -f "$ru_exclude_ipv6" ] && { load_ipset geo_exclude6 "$ru_exclude_ipv6" inet6 & _pids="$_pids $!"; } load_user_ipset & _pids="$_pids $!" [ -n "$_pids" ] && wait $_pids unset _pids echo -e " Прокси-клиент ${green}запущен${reset} в режиме ${light_blue}${mode_proxy}${reset}" ( # Даём ядру прокси время полностью инициализироваться # Это защищает от ситуаций, когда xray/mihomo # успевает создать PID, но затем аварийно завершается, # например, из-за битой конфигурации sleep 3 if ! proxy_status; then echo echo -e " Прокси-клиент ${red}аварийно завершился${reset}" echo -e " ${green}Выполняется очистка${reset} правил прозрачного проксирования" log_error_router "Прокси-клиент аварийно завершился после запуска" emergency_clear printf '\n~ # ' fi ) & if [ -n "$api_policy_json" ]; then if echo "$api_policy_json" | jq --arg policy "$name_policy" -e 'any(.[]; .description | ascii_downcase == $policy)' > /dev/null; then if [ -e "/tmp/noinet" ]; then echo echo -e " У политики ${yellow}$name_policy${reset} ${red}нет доступа в интернет${reset}" echo " Проверьте, установлена ли галка на подключении к провайдеру" fi fi fi [ "$mode_proxy" = "Other" ] && echo -e " Функция прозрачного прокси ${red}не активна${reset}. Направляйте соединения на ${yellow}${name_client}${reset} вручную" log_info_router "Прокси-клиент успешно запущен в режиме $mode_proxy" rm -f "/tmp/xkeen_coldstart.lock" if [ "$check_fd" = "on" ]; then cleanup_fd_monitor monitor_fd & echo $! > "$file_pid_fd" log_info_router "Запущен контроль файловых дескрипторов $name_client" fi return 0 fi attempt=$((attempt + 1)) done echo -e " ${red}Не удалось запустить${reset} прокси-клиент" log_error_terminal "Не удалось запустить прокси-клиент" fi else clean_firewall fi } # Активная проба готовности окружения вместо sleep $start_delay. # Ждём ndmc, default route и insmod-ability xt_TPROXY (deps ndm # подгружает асинхронно уже после ndmc-ready). $start_delay сохранён # как safety cap (FAQ #12). wait_for_ready() { _max=$(( ${start_delay:-60} * 2 )) _attempt=0 _probe_ko="$directory_os_modules/xt_TPROXY.ko" while [ "$_attempt" -lt "$_max" ]; do if ndmc -c "show version" >/dev/null 2>&1 \ && ip route show default 2>/dev/null | grep -q '^default'; then # .ko отсутствует (не TProxy/Hybrid), уже загружен, либо insmod удался if [ ! -f "$_probe_ko" ] \ || grep -q '^xt_TPROXY ' /proc/modules 2>/dev/null \ || insmod "$_probe_ko" >/dev/null 2>&1; then return 0 fi fi usleep 500000 _attempt=$((_attempt + 1)) done return 0 } # Остановка прокси-клиента proxy_stop() { rm -f "/tmp/xkeen_ready" if ! proxy_status; then echo -e " Прокси-клиент ${red}не запущен${reset}" cleanup_fd_monitor else [ -f "/tmp/xkeen_coldstart.lock" ] || log_info_router "Инициирована остановка прокси-клиента" cleanup_fd_monitor attempt=1 while [ "$attempt" -le "$start_attempts" ]; do clean_firewall killall -q "$name_client" 2>/dev/null _stop_attempt=0 while [ "$_stop_attempt" -lt 30 ]; do pidof "$name_client" >/dev/null 2>&1 || break _stop_attempt=$((_stop_attempt + 1)) usleep 50000 done unset _stop_attempt if pidof "$name_client" >/dev/null 2>&1; then killall -q -9 "$name_client" 2>/dev/null usleep 200000 fi if ! proxy_status; then echo -e " Прокси-клиент ${red}остановлен${reset}" [ -f "/tmp/xkeen_coldstart.lock" ] || log_info_router "Прокси-клиент успешно остановлен" rm -f "/tmp/xkeen_coldstart.lock" return 0 fi attempt=$((attempt + 1)) done echo -e " Прокси-клиент ${red}не удалось остановить${reset}" log_error_terminal "Не удалось остановить прокси-клиент" fi } # Менеджер команд case "$1" in start) ipset create ext_exclude hash:ip family inet -exist ipset create ext_exclude6 hash:ip family inet6 -exist if [ -z "$2" ]; then [ "$start_auto" != "on" ] && exit 0 log_info_router "Подготовка к запуску прокси-клиента" nohup "$0" cold_start >/dev/null 2>&1 & touch "/tmp/xkeen_coldstart.lock" exit 0 fi proxy_start "$2" ;; stop) proxy_stop ;; status) if proxy_status; then mode_proxy="" if [ -f "$file_netfilter_hook" ]; then mode_proxy=$(grep '^mode_proxy=' "$file_netfilter_hook" | awk -F"=" '{print $2}' | tr -d "'" 2>/dev/null) fi [ -z "$mode_proxy" ] && mode_proxy="Other" echo -e " Прокси-клиент ${yellow}$name_client${reset} ${green}запущен${reset} в режиме ${light_blue}$mode_proxy${reset}" else echo -e " Прокси-клиент ${red}не запущен${reset}" fi ;; restart) proxy_stop; proxy_start "$2" ;; cold_start) # Re-spawn в чистый S05xkeen: sh-функции (wait_for_ready) не # наследуются через nohup sh -c, поэтому пробу зовём отсюда. wait_for_ready proxy_start "" ;; *) echo -e " Команды: ${green}start${reset} | ${red}stop${reset} | ${yellow}restart${reset} | status" ;; esac exit 0 ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/00_configs_import.sh ================================================ # Импорт модулей конфигураций # Модуль конфигурации . "$xinstall_dir/08_install_configs/01_configs_install.sh" ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/01_configs_install.sh ================================================ # Функция для установки файлов конфигурации Xray install_configs() { if [ ! -d "$xray_conf_dir" ]; then mkdir -p "$xray_conf_dir" fi if ls "$xray_conf_dir"/*.json >/dev/null 2>&1; then return 0 fi xray_files="$xray_conf_smpl"/*.json for file in $xray_files; do filename=$(basename "$file") cp "$file" "$xray_conf_dir/" echo " Добавлен шаблон конфигурационного файла Xray:" echo -e " ${yellow}$filename${reset}" sleep 1 done } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/01_log.json ================================================ { "log": { "access": "/opt/var/log/xray/access.log", "error": "/opt/var/log/xray/error.log", "dnsLog": true, "loglevel": "none" } } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/02_dns.json ================================================ { // Пример настройки - https://jameszero.net/3398.htm } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/03_inbounds.json ================================================ { "inbounds": [ { "port": 1181, "protocol": "tunnel", "settings": { "network": "tcp", "followRedirect": true }, "sniffing": { "enabled": true, "routeOnly": true, "destOverride": ["http","tls"] }, "tag": "redirect" }, { "port": 1181, "protocol": "tunnel", "settings": { "network": "udp", "followRedirect": true }, "streamSettings": { "sockopt": {"tproxy": "tproxy"} }, "sniffing": { "enabled": true, "routeOnly": true, "destOverride": ["quic"] }, "tag": "tproxy" } ] } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/04_outbounds.json ================================================ { // Создайте файл по ссылке https://zxc-rv.github.io/XKeen-UI/Outbound_Generator/ } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/05_routing.json ================================================ { // Создайте файл по ссылке https://xray-routing-generator.netlify.app } ================================================ FILE: scripts/_xkeen/02_install/08_install_configs/02_configs_dir/06_policy.json ================================================ { "policy": { "levels": { "0": { "uplinkOnly": 0, "downlinkOnly": 0 } } } } ================================================ FILE: scripts/_xkeen/03_delete/00_delete_import.sh ================================================ # Импорт модулей удаления # Модули удаления . "$xdelete_dir/01_delete_geofile.sh" . "$xdelete_dir/02_delete_geoipset.sh" . "$xdelete_dir/03_delete_cron.sh" . "$xdelete_dir/04_delete_configs.sh" . "$xdelete_dir/05_delete_register.sh" . "$xdelete_dir/06_delete_tmp.sh" ================================================ FILE: scripts/_xkeen/03_delete/01_delete_geofile.sh ================================================ # Функция для удаления выбранных файлов GeoSite delete_geosite() { [ "$choice_delete_geosite_refilter_select" = "true" ] && rm -f "$geo_dir/geosite_refilter.dat" [ "$choice_delete_geosite_v2fly_select" = "true" ] && rm -f "$geo_dir/geosite_v2fly.dat" [ "$choice_delete_geosite_zkeen_select" = "true" ] && rm -f "$geo_dir/"geosite_zkeen.dat "$geo_dir/"zkeen.dat } # Функция для удаления всех файлов GeoSite delete_geosite_key() { rm -f "$geo_dir/geosite_refilter.dat" \ "$geo_dir/geosite_v2fly.dat" \ "$geo_dir/geosite_zkeen.dat" \ "$geo_dir/zkeen.dat" } # Функция для удаления выбранных файлов GeoIP delete_geoip() { [ "$choice_delete_geoip_refilter_select" = "true" ] && rm -f "$geo_dir/geoip_refilter.dat" [ "$choice_delete_geoip_v2fly_select" = "true" ] && rm -f "$geo_dir/geoip_v2fly.dat" [ "$choice_delete_geoip_zkeenip_select" = "true" ] && rm -f "$geo_dir/geoip_zkeenip.dat" "$geo_dir/zkeenip.dat" } # Функция для удаления всех файлов GeoIP delete_geoip_key() { rm -f "$geo_dir/geoip_refilter.dat" \ "$geo_dir/geoip_v2fly.dat" \ "$geo_dir/geoip_zkeenip.dat" \ "$geo_dir/zkeenip.dat" } ================================================ FILE: scripts/_xkeen/03_delete/02_delete_geoipset.sh ================================================ # Функция для удаления GeoIPSET delete_geoipset() { while true; do printf "\n Желаете удалить российские IP-адреса из исключений проксирования?\n\n" printf " 1. Да. Загруженные файлы подсетей будут удалены, а списки очищены\n" printf " 0. Нет. Отмена удаления\n\n" printf " Ваш выбор: " read -r choice case "$choice" in 0) echo printf " Отмена удаления списков GeoIPSET.\n\n" return 0 ;; 1) echo break ;; *) printf " Неверный ввод. Пожалуйста, введите 1 или 0.\n" ;; esac done ipset flush geo_exclude 2>/dev/null ipset flush geo_exclude6 2>/dev/null [ -f "$ru_exclude_ipv4" ] && rm -f "$ru_exclude_ipv4" 2>/dev/null [ -f "$ru_exclude_ipv6" ] && rm -f "$ru_exclude_ipv6" 2>/dev/null # [ -d "$ipset_cfg" ] && rm -rf "$ipset_cfg" printf " Списки исключений GeoIPSET ${green}успешно удалены${reset}\n\n" return 0 } delete_geoipset_key() { ipset flush geo_exclude 2>/dev/null ipset flush geo_exclude6 2>/dev/null [ -f "$ru_exclude_ipv4" ] && rm -f "$ru_exclude_ipv4" 2>/dev/null [ -f "$ru_exclude_ipv6" ] && rm -f "$ru_exclude_ipv6" 2>/dev/null # [ -d "$ipset_cfg" ] && rm -rf "$ipset_cfg" } ================================================ FILE: scripts/_xkeen/03_delete/03_delete_cron.sh ================================================ # Функция для удаления cron задачи для GeoFile delete_cron_geofile() { if [ -f "$cron_dir/$cron_file" ]; then tmp_file="$cron_dir/${cron_file}.tmp" cp "$cron_dir/$cron_file" "$tmp_file" grep -v "ug" "$tmp_file" | grep -v '^\s*$' > "$cron_dir/$cron_file" fi } ================================================ FILE: scripts/_xkeen/03_delete/04_delete_configs.sh ================================================ # Удаление всех конфигураций Xray delete_configs() { if [ -d "$xray_conf_dir" ]; then find "$xray_conf_dir" -maxdepth 1 -name '*.json' -type f -delete fi } ================================================ FILE: scripts/_xkeen/03_delete/05_delete_register.sh ================================================ # Удаление регистрации Xray delete_register_xray() { # Удаляем соответствующие записи из файла статуса opkg sed -i -e '/Package: xray_s/,/Installed-Time:/d' "/opt/lib/opkg/status" # Удаляем файлы регистрации, если они существуют if [ -f "$register_dir/xray_s.control" ] || [ -f "$register_dir/xray_s.list" ]; then rm -f "$register_dir/xray_s.control" "$register_dir/xray_s.list" fi } # Удаление регистрации Mihomo delete_register_mihomo() { # Удаляем соответствующие записи из файла статуса opkg sed -i -e '/Package: mihomo_s/,/Installed-Time:/d' "/opt/lib/opkg/status" sed -i -e '/Package: yq_s/,/Installed-Time:/d' "/opt/lib/opkg/status" # Удаляем файлы регистрации, если они существуют if [ -f "$register_dir/mihomo_s.control" ] || [ -f "$register_dir/mihomo_s.list" ]; then rm -f "$register_dir/mihomo_s.control" "$register_dir/mihomo_s.list" fi if [ -f "$register_dir/yq_s.control" ] || [ -f "$register_dir/yq_s.list" ]; then rm -f "$register_dir/yq_s.control" "$register_dir/yq_s.list" fi } # Удаление регистрации XKeen delete_register_xkeen() { # Удаляем соответствующие записи из файла статуса opkg sed -i -e '/Package: xkeen/,/Installed-Time:/d' "/opt/lib/opkg/status" # Удаляем файлы регистрации, если они существуют if [ -f "$register_dir/xkeen.control" ] || [ -f "$register_dir/xkeen.list" ]; then rm -f "$register_dir/xkeen.control" "$register_dir/xkeen.list" fi } ================================================ FILE: scripts/_xkeen/03_delete/06_delete_tmp.sh ================================================ # Удаление временных файлов и директорий delete_tmp() { [ -d "$ktmp_dir" ] && rm -rf "$ktmp_dir" [ -d "$xtmp_dir" ] && rm -rf "$xtmp_dir" [ -d "$mtmp_dir" ] && rm -rf "$mtmp_dir" [ -f "$cron_dir/root.tmp" ] && rm -f "$cron_dir/root.tmp" [ -f "$register_dir/new_entry.txt" ] && rm -f "$register_dir/new_entry.txt" [ -f "$install_dir/xray_bak" ] && rm -f "$install_dir/xray_bak" [ -f "$install_dir/mihomo_bak" ] && rm -f "$install_dir/mihomo_bak" [ -f "/tmp/xkrun" ] && rm -f "/tmp/xkrun" [ -f "/tmp/toff" ] && rm -f "/tmp/toff" if ! pidof xray >/dev/null && ! pidof mihomo >/dev/null ; then [ -f "$file_netfilter_hook" ] && rm "$file_netfilter_hook" [ -f "$file_schedule_hook" ] && rm "$file_schedule_hook" if command -v ipset >/dev/null 2>&1; then ipset flush "$name_ipset_deny_mac" >/dev/null 2>&1 ipset destroy "$name_ipset_deny_mac" >/dev/null 2>&1 fi fi echo echo -e " Очистка временных файлов ${green}выполнена${reset}" } delete_all() { echo echo -e " Удалить резервные копии и пользовательские настройки?" echo -e " ${yellow}$backups_dir${reset}" echo -e " ${yellow}$xkeen_cfg${reset}" echo echo " 1. Да, удалить" echo " 0. Нет, оставить" echo while true; do read -r -p " Ваш выбор: " choice case "$choice" in 1) [ -d "$backups_dir" ] && rm -rf "$backups_dir" [ -d "$xkeen_cfg" ] && rm -rf "$xkeen_cfg" return 0 ;; 0) return 0 ;; *) echo -e " ${red}Некорректный ввод${reset}" ;; esac done } ================================================ FILE: scripts/_xkeen/04_tools/00_tools_import.sh ================================================ # Дополнительные инструменты . "$xtools_dir/01_tools_ports.sh" . "$xtools_dir/02_tools_modules.sh" . "$xtools_dir/03_tools_diagnostic.sh" . "$xtools_dir/04_tools_delay.sh" # Модуль выбора . "$xtools_dir/05_tools_choice/00_choice_import.sh" # Модуль резервного копирования . "$xtools_dir/06_tools_backups/00_backups_import.sh" # Модуль загрузок . "$xtools_dir/07_tools_downloaders/00_downloaders_import.sh" ================================================ FILE: scripts/_xkeen/04_tools/01_tools_ports.sh ================================================ read_ports_file() { file="$1" [ -f "$file" ] || return sed 's/\r$//' "$file" | \ sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | \ grep -v '^#' | \ grep -v '^$' | \ sed 's/-/:/g' | \ grep -E '^[0-9]+(:[0-9]+)?$' | \ tr '\n' ',' | \ sed 's/,$//' } write_ports_file() { file="$1" ports="$2" tmpfile=$(mktemp) echo "# XKeen ports list" > "$tmpfile" echo "$ports" | tr ',' '\n' >> "$tmpfile" mv "$tmpfile" "$file" } ports_conflict_check() { file1="$1" file2="$2" ports1=$(read_ports_file "$file1") ports2=$(read_ports_file "$file2") if [ -n "$ports1" ] && [ -n "$ports2" ]; then return 0 fi return 1 } data_is_updated_donor() { file=$1 new_ports=$2 current_ports=$( awk -F= '/^port_donor/{print $2; exit}' "$file" \ | tr -d '"' ) if [ "$current_ports" = "$new_ports" ]; then return 0 else return 1 fi } data_is_updated_excluded() { file=$1 new_ports=$2 current_ports=$( awk -F= '/^port_exclude/{print $2; exit}' "$file" \ | tr -d '"' ) if [ "$current_ports" = "$new_ports" ]; then return 0 else return 1 fi } normalize_ports() { echo "$1" | tr ',' '\n' | awk ' function valid(p) { return (p ~ /^[0-9]+$/ && p >= 0 && p <= 65535) } { gsub(/[[:space:]]/, "") if ($0 == "") next gsub(/-/, ":") n = split($0, a, ":") if (n == 1) { if (valid(a[1])) ports[a[1]] } else if (n == 2) { if (valid(a[1]) && valid(a[2])) { start = a[1] end = a[2] if (start > end) { tmp = start start = end end = tmp } ports[start ":" end] } } } END { for (p in ports) print p } ' | sort -n | tr '\n' ',' | sed 's/,$//' } ensure_web_ports() { ports="$1" echo "$ports" | tr ',' '\n' | grep -qx "80" || ports="$ports,80" echo "$ports" | tr ',' '\n' | grep -qx "443" || ports="$ports,443" normalize_ports "$ports" } add_ports_donor() { [ -z "$1" ] && { echo -e " ${red}Ошибка${reset}: список портов не может быть пустым" return 1 } if ports_conflict_check "$file_port_proxying" "$file_port_exclude"; then echo -e " ${yellow}Внимание${reset}: вы добавляете порты проксирования, но уже заданы порты исключения Приоритет у портов проксирования, порты исключения будут проигнорированы" fi new_ports=$(normalize_ports "$1") current_ports=$(read_ports_file "$file_port_proxying") current_ports=$(normalize_ports "$current_ports") if [ -n "$current_ports" ]; then all_ports=$(echo "$current_ports,$new_ports" | tr ',' '\n' | sort -n -u | tr '\n' ',' | sed 's/,$//') else all_ports="$new_ports" fi all_ports=$(ensure_web_ports "$all_ports") write_ports_file "$file_port_proxying" "$all_ports" echo -e " ${green}Порты проксирования обновлены${reset}" } dell_ports_donor() { ports_to_del=$(normalize_ports "$1") current_ports=$(read_ports_file "$file_port_proxying") [ -z "$current_ports" ] && { echo -e " ${yellow}Файл пуст${reset}" return } if [ -z "$ports_to_del" ]; then > "$file_port_proxying" echo -e " ${green}Все порты удалены${reset}" return fi new_ports="$current_ports" for port in $(echo "$ports_to_del" | tr ',' '\n'); do new_ports=$(echo "$new_ports" | tr ',' '\n' | grep -vFx "$port" | tr '\n' ',' | sed 's/,$//') done write_ports_file "$file_port_proxying" "$new_ports" echo -e " ${green}Порты удалены${reset}" } add_ports_exclude() { [ -z "$1" ] && { echo -e " ${red}Ошибка${reset}: список портов не может быть пустым" return 1 } if ports_conflict_check "$file_port_proxying" "$file_port_exclude"; then echo -e " ${yellow}Внимание${reset}: вы добавляете порты исключения, но уже заданы порты проксирования Приоритет у портов проксирования, порты исключения будут проигнорированы" fi new_ports=$(normalize_ports "$1") current_ports=$(read_ports_file "$file_port_exclude") current_ports=$(normalize_ports "$current_ports") if [ -n "$current_ports" ]; then all_ports=$(echo "$current_ports,$new_ports" | tr ',' '\n' | sort -n -u | tr '\n' ',' | sed 's/,$//') else all_ports="$new_ports" fi write_ports_file "$file_port_exclude" "$all_ports" echo -e " ${green}Порты исключения обновлены${reset}" } dell_ports_exclude() { ports_to_del=$(normalize_ports "$1") current_ports=$(read_ports_file "$file_port_exclude") [ -z "$current_ports" ] && { echo -e " ${yellow}Файл пуст${reset}" return } if [ -z "$ports_to_del" ]; then > "$file_port_exclude" echo -e " ${green}Все исключения удалены${reset}" return fi new_ports="$current_ports" for port in $(echo "$ports_to_del" | tr ',' '\n'); do new_ports=$(echo "$new_ports" | tr ',' '\n' | grep -vFx "$port" | tr '\n' ',' | sed 's/,$//') done write_ports_file "$file_port_exclude" "$new_ports" echo -e " ${green}Порты исключения удалены${reset}" } # Получить список портов проксирования get_ports_donor() { ports=$(read_ports_file "$file_port_proxying") if [ -z "$ports" ]; then echo -e " Прокси-клиент работает ${yellow}на всех портах${reset}" else echo "$ports" | tr ',' '\n' | sed 's/^/ /' fi } # Получить список портов, исключённых из проксирования get_ports_exclude() { ports=$(read_ports_file "$file_port_exclude") if [ -z "$ports" ]; then echo -e " Нет портов исключённых из проксирования" else echo "$ports" | tr ',' '\n' | sed 's/^/ /' fi } migrate_ports_from_initd() { legacy_initd="" for f in "/opt/etc/init.d/S99xkeen" "/opt/etc/init.d/S24xray"; do [ -f "$f" ] && { legacy_initd="$f"; break; } done [ -n "$legacy_initd" ] || return # Читаем старые значения port_donor_val=$( awk -F= '/^port_donor=/{print $2; exit}' "$legacy_initd" | tr -d '"' ) port_exclude_val=$( awk -F= '/^port_exclude=/{print $2; exit}' "$legacy_initd" | tr -d '"' ) port_donor_val=$(normalize_ports "$port_donor_val") port_exclude_val=$(normalize_ports "$port_exclude_val") migrated=0 # Миграция port_donor if [ -n "$port_donor_val" ]; then current_proxy=$(normalize_ports "$(read_ports_file "$file_port_proxying")") combined=$(normalize_ports "$current_proxy,$port_donor_val") if [ "$combined" != "$current_proxy" ]; then tmpfile=$(mktemp) echo "# XKeen port proxying list (migrated)" > "$tmpfile" echo "$combined" | tr ',' '\n' >> "$tmpfile" mv "$tmpfile" "$file_port_proxying" fi fi # Миграция port_exclude if [ -n "$port_exclude_val" ]; then current_exclude=$(normalize_ports "$(read_ports_file "$file_port_exclude")") combined=$(normalize_ports "$current_exclude,$port_exclude_val") if [ "$combined" != "$current_exclude" ]; then tmpfile=$(mktemp) echo "# XKeen port exclude list (migrated)" > "$tmpfile" echo "$combined" | tr ',' '\n' >> "$tmpfile" mv "$tmpfile" "$file_port_exclude" fi fi } ================================================ FILE: scripts/_xkeen/04_tools/02_tools_modules.sh ================================================ show_deprecation_warning() { echo -e " ${red}Внимание!${reset} Команда устарела и удалена из XKeen" echo -e " Компонент '${yellow}Модули ядра подсистемы Netfilter${reset}' обязателен" echo } migration_modules() { show_deprecation_warning && return } remove_modules() { show_deprecation_warning && return } ================================================ FILE: scripts/_xkeen/04_tools/03_tools_diagnostic.sh ================================================ diagnostic() { # Установка пути к файлу diagnostic diagnostic="/opt/diagnostic.txt" if pidof "xray" >/dev/null; then name_client=xray elif pidof "mihomo" >/dev/null; then name_client=mihomo else echo echo -e " Диагностика возможна только при работающем ${yellow}XKeen${reset}" echo -e " Запустите ${yellow}XKeen${reset} командой '${green}xkeen -start${reset}'" exit 1 fi ip4_supported=$(ip -4 addr show | grep -q "inet " && echo true || echo false) ip6_supported=$(ip -6 addr show | grep -q "inet6 fe80::" && echo true || echo false) iptables_supported=$([ "$ip4_supported" = "true" ] && command -v iptables >/dev/null 2>&1 && echo true || echo false) ip6tables_supported=$([ "$ip6_supported" = "true" ] && command -v ip6tables >/dev/null 2>&1 && echo true || echo false) echo echo " Выполняется диагностика. Пожалуйста, подождите..." # Очищаем файл diagnostic перед записью новых данных > "$diagnostic" chmod 600 "$diagnostic" # Функция записи заголовка write_header() { echo "-------------------------" >> "$diagnostic" echo -e "$1" >> "$diagnostic" echo "-------------------------" >> "$diagnostic" echo >> "$diagnostic" } # Функция логирования блоков log_block() { write_header "$1" cat >> "$diagnostic" echo >> "$diagnostic"; echo >> "$diagnostic" } # Функция маскировки чувствительных данных в конфигах Xray mask_xray_sensitive_data() { sed -E \ -e 's/("(id|uuid|password|user|pass|auth|secretKey|preSharedKey)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g' \ -e 's/("(address|host|serverName|sni|path|token|spiderX)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g' \ -e 's/("(publicKey|privateKey|shortId|mldsa65Verify|encryption)")[[:space:]]*:[[:space:]]*"?[^",[:space:]]+"?(,?)/\1: "***MASKED***"\3/g' } # Функция маскировки чувствительных данных в конфигах Mihomo mask_mihomo_sensitive_data() { sed -E \ -e 's/^([[:space:]]*(- )?(password|username|uuid|pre-shared-key|private-key|private-key-passphrase):).*/\1 ***MASKED***/i' \ -e 's/^([[:space:]]*(- )?(server|servername|sni|host|query-server-name|external-controller):).*/\1 ***MASKED***/i' \ -e 's/^([[:space:]]*(- )?(url|path|certificate|config|public-key|short-id|client-id|auth-str):).*/\1 ***MASKED***/i' \ -e 's/^([[:space:]]*(- )?(obfs-password|encryption|token|secret|psk):).*/\1 ***MASKED***/i' } # Функция логирования файлов log_file() { local file="$1" local title="$2" if [ -f "$file" ]; then cat "$file" | log_block "$title" else echo "Файл $file не найден" | log_block "$title" fi } # Функция дампа iptables/ip6tables dump_tables() { local cmd="$1" local ver="$2" for chain in PREROUTING xkeen xkeen_out OUTPUT; do $cmd -w -t nat -nvL "$chain" 2>&1 | log_block "Результат таблицы NAT цепи $chain $ver" $cmd -w -t mangle -nvL "$chain" 2>&1 | log_block "Результат таблицы MANGLE цепи $chain $ver" done $cmd -w -t nat -nvL "_NDM_HOTSPOT_DNSREDIR" 2>&1 | log_block "Результат таблицы NAT цепи _NDM_HOTSPOT_DNSREDIR $ver" } # Сбор данных write_header "XKeen работает на ядре ${name_client}\nи установлен ${entware_storage}" { echo "Поддержка IPv4 - $ip4_supported" echo "Поддержка IPv6 - $ip6_supported" echo echo "Поддержка iptables - $iptables_supported" echo "Поддержка ip6tables - $ip6tables_supported" } | log_block "Доступность IPv4 и IPv6" [ "$iptables_supported" = "true" ] && dump_tables "iptables" "IPv4" [ "$ip6tables_supported" = "true" ] && dump_tables "ip6tables" "IPv6" if command -v ipset >/dev/null 2>&1; then sets=$(ipset list -n 2>/dev/null | grep -v '^_NDM_' | grep -v '^_UPNP') if [ -n "$sets" ]; then echo "$sets" | { total=0 while read -r set; do count=$(ipset save "$set" 2>/dev/null | grep -c '^add') printf "%-30s %s\n" "$set" "$count" total=$((total + count)) done echo echo "Всего записей во всех списках: $total" } | log_block "Списки ipset и количество записей в каждом" fi fi log_file "/opt/etc/ndm/netfilter.d/proxy.sh" "Содержимое файла /opt/etc/ndm/netfilter.d/proxy.sh" curl -kfsS "localhost:79/rci/ip/http/ssl" | jq -r '.port' | log_block "Проверка использования SSL порта" curl -kfsS "localhost:79/rci/show/ip/policy" | jq -r '.[] | select(.description | ascii_downcase == "xkeen")' | log_block "Данные о политике доступа" ip rule show | log_block "Результат команды ip rule show" ip route show table main | log_block "Результат команды ip route show table main" curl -kfsS "localhost:79/rci/show/version" | jq -r '.title, .model, .region' | log_block "Данные из localhost:79/rci/show/version" { if [ "${name_client}" = "xray" ]; then xray version; else mihomo -v; fi echo echo "Открыто файловых дескрипторов:" ls "/proc/$(pidof ${name_client})/fd" | wc -l echo "Лимит файловых дескрипторов:" grep 'Max open files' "/proc/$(pidof ${name_client})/limits" | awk '{print $4}' } | log_block "Версия $name_client и файловые дескрипторы" echo "Версия XKeen $xkeen_current_version $xkeen_build (время сборки: $build_timestamp)" | log_block "Версия XKeen" [ -f "$xkeen_config" ] && log_file "$xkeen_config" "Файл xkeen.json" if [ "${name_client}" = "xray" ] && [ -d "$xray_conf_dir" ]; then ls -p "$xray_conf_dir" | log_block "Содержимое директории configs" for conf in dns inbounds routing outbounds; do file=$(ls "$xray_conf_dir"/*${conf}*.json 2>/dev/null | head -n 1) if [ -n "$file" ]; then write_header "Содержимое файла $file" mask_xray_sensitive_data < "$file" >> "$diagnostic" echo >> "$diagnostic"; echo >> "$diagnostic" fi done fi if [ "${name_client}" = "mihomo" ]; then for conf_file in "$mihomo_conf_dir/config.yaml" "$mihomo_conf_dir/config.yml"; do if [ -f "$conf_file" ]; then write_header "Содержимое файла $conf_file" mask_mihomo_sensitive_data < "$conf_file" >> "$diagnostic" echo >> "$diagnostic"; echo >> "$diagnostic" fi done fi echo echo -e " Диагностика ${green}выполнена${reset}" echo -e " Отправьте файл '${yellow}$diagnostic${reset}' в телеграм-чат ${yellow}XKeen${reset}, подробно описав возникшую проблему" echo echo -e " ${red}Примечание${reset}: Диагностика не проверяет доступ к прокси-серверу, правильность заполнения конфигов" echo -e " и настройки роутера/сервера. Она проверяет ${green}только${reset} корректность инициализации ${yellow}XKeen${reset} в роутере" } ================================================ FILE: scripts/_xkeen/04_tools/04_tools_delay.sh ================================================ get_current_delay() { awk -F= '/^[[:space:]]*start_delay=/{print $2; exit}' "$1" | tr -d '[:space:]"' } delay_autostart() { new_delay="$1" if [ ! -f "$initd_file" ]; then echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}" return 1 fi current_delay=$(get_current_delay "$initd_file") if [ -z "$new_delay" ]; then echo -e " Текущая задержка автозапуска XKeen ${yellow}${current_delay} секунд(ы)${reset}" return 0 fi case "$new_delay" in ''|*[!0-9]*) echo -e " ${red}Ошибка${reset}" echo " Новая задержка должна быть числом" return 1 ;; esac if [ "$current_delay" = "$new_delay" ]; then echo " Обновление задержки автозапуска XKeen не требуется" return 0 fi tmpfile=$(mktemp) || return 1 awk -v d="$new_delay" ' /^[[:space:]]*start_delay=/ && !done { sub(/=.*/, "=" d) done=1 } {print} ' "$initd_file" > "$tmpfile" && mv "$tmpfile" "$initd_file" if [ "$(get_current_delay "$initd_file")" = "$new_delay" ]; then echo -e " Установлена задержка автозапуска XKeen ${yellow}${new_delay} секунд(ы)${reset}" else echo -e " ${red}Ошибка${reset}: не удалось обновить параметр" return 1 fi } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/00_choice_import.sh ================================================ # Импорт модулей выбора пользователя # Модули выбора . "$xtools_dir/05_tools_choice/01_choice_cores.sh" . "$xtools_dir/05_tools_choice/02_choice_xkeen.sh" . "$xtools_dir/05_tools_choice/03_choice_geofile.sh" . "$xtools_dir/05_tools_choice/04_choice_input.sh" . "$xtools_dir/05_tools_choice/05_choice_cron/00_cron_import.sh" ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/01_choice_cores.sh ================================================ # Запрос на добавление ядер проксирования choice_add_proxy_cores() { while true; do echo echo -e " Выберите ${yellow}ядро проксирования${reset} для загрузки и установки:" echo echo " 1. Xray" echo " 2. Mihomo" echo " 3. Xray + Mihomo" echo echo " 0. Пропустить загрузку ядра проксирования, если оно уже установлено" echo valid_input=true add_xray=false add_mihomo=false while true; do read -r -p " Ваш выбор: " proxy_choice proxy_choice=$(echo "$proxy_choice" | sed 's/,/, /g') if echo "$proxy_choice" | grep -qE '^[0-3]$'; then break else echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов" fi done case "$proxy_choice" in 1) add_xray=true ;; 2) add_mihomo=true ;; 3) add_xray=true add_mihomo=true ;; 0) add_xray=false add_mihomo=false ;; *) echo -e " ${red}Некорректный ввод${reset}" valid_input=false ;; esac [ "$valid_input" = "true" ] && break done } # Смена ядра проксирования на Xray choice_xray_core() { command -v xray >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: Ядро Xray не установлено. Выполните установку командой ${yellow}xkeen -ux${reset}"; exit 1; } if [ -f "$initd_file" ]; then if grep -q 'name_client="xray"' $initd_file; then echo -e " Смена ядра ${red}не выполнена${reset}. Устройство уже работает на ядре ${yellow}Xray${reset}" elif grep -q 'name_client="mihomo"' $initd_file; then if pidof "mihomo" >/dev/null; then $initd_file stop fi sed -i 's/name_client="mihomo"/name_client="xray"/' $initd_file add_chmod_init echo -e " ${green}Выполнена${reset} смена ядра на ${yellow}Xray${reset}" echo -e " Настройте конфигурацию по пути '${yellow}$xray_conf_dir/${reset}'" echo -e " И запустите проксирование командой ${yellow}xkeen -start${reset}" else echo -e " Произошла ${red}ошибка${reset} при смене ядра проксирования" fi else echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}" return 1 fi } # Смена ядра проксирования на Mihomo choice_mihomo_core() { command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: Ядро Mihomo не установлено. Выполните установку командой ${yellow}xkeen -um${reset}"; exit 1; } command -v yq >/dev/null 2>&1 || { echo -e " ${red}Ошибка${reset}: не установлен парсер конфигурационных файлов Mihomo - ${yellow}Yq${reset}"; exit 1; } if [ -f "$initd_file" ]; then if grep -q 'name_client="mihomo"' $initd_file; then echo -e " Смена ядра ${red}не выполнена${reset}. Устройство уже работает на ядре ${yellow}Mihomo${reset}" elif [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ] && grep -q 'name_client="xray"' $initd_file; then if pidof "xray" >/dev/null; then $initd_file stop fi sed -i 's/name_client="xray"/name_client="mihomo"/' $initd_file add_chmod_init echo -e " ${green}Выполнена${reset} смена ядра на ${yellow}Mihomo${reset}" echo -e " Настройте конфигурацию по пути '${yellow}$mihomo_conf_dir/${reset}'" echo -e " И запустите проксирование командой ${yellow}xkeen -start${reset}" else echo -e " Произошла ${red}ошибка${reset} при смене ядра проксирования" fi else echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}" return 1 fi } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/02_choice_xkeen.sh ================================================ # Запрос на смену канала обновлений XKeen (Stable/Dev) choice_channel_xkeen() { echo echo -e " Текущий канал обновлений ${yellow}XKeen${reset}:" if [ "$xkeen_build" = "Stable" ]; then echo -e " Стабильная версия (${green}Stable${reset})" echo echo " 1. Переключиться на канал разработки" echo " 0. Остаться на стабильной версии" else echo -e " Версия в разработке (${green}$xkeen_build${reset})" echo echo " 1. Переключиться на стабильную версию" echo " 0. Остаться на версии разработки" fi echo while true; do read -r -p " Ваш выбор: " choice if echo "$choice" | grep -qE '^[0-1]$'; then case "$choice" in 1) if [ "$xkeen_build" = "Stable" ]; then choice_build="Dev" else choice_build="Stable" fi return 0 ;; 0) echo " Остаёмся на текущей ветке XKeen" return 0 ;; esac else echo -e " ${red}Некорректный ввод${reset}" fi done } change_channel_xkeen() { echo if [ "$choice_build" = "Stable" ]; then sed -i 's/^xkeen_build="[^"]*"/xkeen_build="Stable"/' "$xkeen_var_file" if grep -q '^xkeen_build="Stable"$' "$xkeen_var_file"; then echo -e " Канал получения обновлений ${yellow}XKeen${reset} переключен на ${green}стабильную ветку${reset}" else echo -e " ${red}Возникла ошибка${reset} при переключении канала обновлений" unset choice_build fi elif [ "$choice_build" = "Dev" ]; then sed -i 's/xkeen_build="Stable"/xkeen_build="Dev"/' $xkeen_var_file if grep -q '^xkeen_build="Dev"$' "$xkeen_var_file"; then echo -e " Канал получения обновлений ${yellow}XKeen${reset} переключен на ${green}ветку разработки${reset}" else echo -e " ${red}Возникла ошибка${reset} при переключении канала обновлений" unset choice_build fi fi if [ -n "$choice_build" ]; then echo echo -e " Командой ${green}xkeen -uk${reset} вы можете обновить ${yellow}XKeen${reset} до последней версии в выбраной ветке" fi } change_ipv6_support() { ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::" && ip6_supported="true" || ip6_supported="false" if [ "$1" = "on" ]; then [ "$ip6_supported" = "true" ] && return 0 desired_state="on" elif [ "$1" = "off" ]; then [ "$ip6_supported" = "false" ] && return 0 desired_state="off" else echo echo -e " Текущее состояние IPv6 в ${yellow}KeeneticOS${reset}:" if [ "$ip6_supported" = "true" ]; then echo -e " IPv6 ${green}включён${reset}" echo echo " 1. Отключить IPv6" echo " 0. Оставить без изменений" desired_state="off" else echo -e " IPv6 ${green}отключён${reset}" echo echo " 1. Включить IPv6" echo " 0. Оставить без изменений" desired_state="on" fi echo while true; do read -r -p " Ваш выбор: " choice if echo "$choice" | grep -qE '^[0-1]$'; then case "$choice" in 0) return 0 ;; 1) break ;; esac else echo -e " ${red}Некорректный ввод${reset}" fi done fi if [ -f "$initd_file" ]; then sed -i "s/ipv6_support=\"[a-z]*\"/ipv6_support=\"$desired_state\"/" "$initd_file" if [ "$desired_state" = "off" ]; then sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 for dir in /proc/sys/net/ipv6/conf/*; do [ -d "$dir" ] || continue iface="${dir##*/}" case "$iface" in all|ezcfg0|t2s*) continue ;; *) [ -f "$dir/disable_ipv6" ] && echo "1" > "$dir/disable_ipv6" 2>/dev/null ;; esac done else sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 fi # Перезапуск прокси-клиента, если запущен if pidof xray >/dev/null || pidof mihomo >/dev/null; then echo -e " ${yellow}Выполняется${reset}. Пожалуйста, подождите..." "$initd_file" restart on >/dev/null 2>&1 fi # Проверка и вывод результата if [ "$desired_state" = "off" ]; then if ! ip -6 addr show 2>/dev/null | grep -q "inet6 fe80::"; then echo -e " Поддержка IPv6 в KeeneticOS ${green}отключена${reset}" echo -e " ${red}Дополнительно убедитесь, что IPv6 отключен в веб-интерфейсе роутера${reset}" else echo -e " ${red}Ошибка${reset} при выключении IPv6" fi else if [ "$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null)" -eq 0 ]; then echo -e " Поддержка IPv6 в KeeneticOS ${green}включена${reset}" else echo -e " ${red}Ошибка${reset} при включении IPv6" fi fi else echo -e " ${red}Ошибка${reset}: Не найден файл автозапуска ${yellow}S05xkeen${reset}" return 1 fi } choice_backup_xkeen() { [ -f "$initd_file" ] || return 1 backup_value=$(awk -F= '/^[[:space:]]*backup[[:space:]]*=/ { gsub(/"| /,"",$2); print tolower($2); exit }' "$initd_file") [ "$backup_value" = "off" ] } choice_autostart_xkeen() { if [ -f "$initd_file" ] && grep -q 'start_auto="off"' "$initd_file"; then return 1 fi if choice_menu \ "Добавить ${yellow}XKeen${reset} в автозагрузку при включении роутера?" \ "Да" \ "Нет"; then echo -e " Автозагрузка XKeen ${green}включена${reset}" return 0 else bypass_autostart_msg="yes" change_autostart_xkeen unset bypass_autostart_msg return 0 fi } choice_redownload_xkeen() { if choice_menu \ "Выберите вариант переустановки ${yellow}XKeen${reset}" \ "Загрузить дистрибутив XKeen из интернета" \ "Локальная переустановка XKeen"; then redownload_xkeen="yes" fi } choice_remove() { if choice_menu \ "Вы действительно хотите ${red}удалить ${choice_for_remove}${reset}?" \ "Да, хочу удалить" \ "Нет, передумал(а)"; then return 0 else exit 0 fi } check_file_descriptors() { pid="" if pid=$(pidof xray | awk '{print $1}') && [ -n "$pid" ]; then name_client="xray" elif pid=$(pidof mihomo | awk '{print $1}') && [ -n "$pid" ]; then name_client="mihomo" else echo -e "\n Команда работает только при работающем ${yellow}XKeen${reset}" return 1 fi fd_count=$(ls /proc/"$pid"/fd | wc -l) maxfd=$(grep 'Max open files' "/proc/$pid/limits" | awk '{print $4}') echo -e "\n Прокси-клиент ${light_blue}$name_client${reset} открыл файловых дескрипторов - ${green}$fd_count${reset}" echo -e " Лимит файловых дескрипторов для вашего роутера - ${green}$maxfd${reset}" echo -e "\n При высоких значениях открытых файловых дескрипторов," echo -e " можете включить их контроль командой ${yellow}xkeen -fd${reset}" } warn_proxy_dns() { echo echo -e " ${red}Внимание!${reset} Значение данного параметра без соответствующих настроек прокси-клиента ${green}игнорируется${reset}" } change_proxy_dns() { toggle_param "proxy_dns" "перехвата DNS" "restart" "$1" } change_autostart_xkeen() { toggle_param "start_auto" "автозапуска XKeen" "none" "$1" } change_file_descriptors() { toggle_param "check_fd" "контроля файловых дескрипторов" "restart" "$1" } change_proxy_router() { toggle_param "proxy_router" "проксирования трафика Entware" "restart" "$1" } change_extended_msg() { toggle_param "extended_msg" "расширенных сообщений при запуcке XKeen" "none" "$1" } change_backup_xkeen() { toggle_param "backup" "резервного копирования XKeen при обновлении" "none" "$1" } change_aghfix_xkeen() { toggle_param "aghfix" "отображения клиентов XKeen под своими IP в журнале AaGuard Home" "restart" "$1" } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/03_choice_geofile.sh ================================================ choice_geodata() { type="$1" type_name="$2" src3="$3" src3_name="$4" var_bypass="$5" has_missing_bases=false has_updatable_bases=false for source in refilter v2fly "$src3"; do var="update_${source}_${type}" msg_var="update_${source}_${type}_msg" if [ "$(eval echo \$$var)" = "false" ]; then has_missing_bases=true else eval "$msg_var=true" has_updatable_bases=true fi done while true; do eval "install_refilter_${type}=false" eval "install_v2fly_${type}=false" eval "install_${src3}_${type}=false" eval "update_refilter_${type}=false" eval "update_v2fly_${type}=false" eval "update_${src3}_${type}=false" eval "choice_delete_${type}_refilter_select=false" eval "choice_delete_${type}_v2fly_select=false" eval "choice_delete_${type}_${src3}_select=false" invalid_choice=false echo echo -e " Выберите номер или номера действий через пробел для ${yellow}${type_name}${reset}" echo [ "$has_missing_bases" = true ] && echo " 1. Установить отсутствующие и обновить установленные ${type_name}" || echo -e " 1. ${italic}Все доступные ${type_name} установлены${reset}" [ "$has_updatable_bases" = true ] && echo " 2. Обновить установленные ${type_name}" || echo -e " 2. ${italic}Нет доступных ${type_name} для обновления${reset}" [ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && refilter_choice="Обновить" || refilter_choice="Установить" [ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && v2fly_choice="Обновить" || v2fly_choice="Установить" [ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && src3_choice="Обновить" || src3_choice="Установить" echo " 3. $refilter_choice Re:filter" echo " 4. $v2fly_choice v2fly" echo " 5. $src3_choice ${src3_name}" echo echo " 0. Пропустить" [ "$has_updatable_bases" = true ] && echo && echo " 6. Удалить установленные ${type_name}" echo valid_input=true while true; do read -r -p " Ваш выбор: " data_choices data_choices=$(echo "$data_choices" | sed 's/,/, /g') if echo "$data_choices" | grep -qE '^[0-6 ]+$'; then break else echo -e " ${red}Некорректный ввод.${reset} Пожалуйста, выберите снова" fi done for choice in $data_choices; do case "$choice" in 1) if [ "$has_missing_bases" = "false" ]; then echo -e " Все ${type_name} ${green}уже установлены${reset}" if input_concordance_list "Вы хотите обновить их?"; then eval "update_refilter_${type}=true" eval "update_v2fly_${type}=true" eval "update_${src3}_${type}=true" else invalid_choice=true fi else [ "$(eval echo \$update_refilter_${type}_msg)" != "true" ] && eval "install_refilter_${type}=true" [ "$(eval echo \$update_v2fly_${type}_msg)" != "true" ] && eval "install_v2fly_${type}=true" [ "$(eval echo \$update_${src3}_${type}_msg)" != "true" ] && eval "install_${src3}_${type}=true" [ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && eval "update_refilter_${type}=true" [ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && eval "update_v2fly_${type}=true" [ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && eval "update_${src3}_${type}=true" fi ;; 2) if [ "$has_updatable_bases" = "false" ]; then echo -e " ${red}Нет установленных ${type_name}${reset} для обновления" if input_concordance_list "Вы хотите установить их?"; then eval "install_refilter_${type}=true" eval "install_v2fly_${type}=true" eval "install_${src3}_${type}=true" else invalid_choice=true fi else [ "$(eval echo \$update_refilter_${type}_msg)" = "true" ] && eval "update_refilter_${type}=true" [ "$(eval echo \$update_v2fly_${type}_msg)" = "true" ] && eval "update_v2fly_${type}=true" [ "$(eval echo \$update_${src3}_${type}_msg)" = "true" ] && eval "update_${src3}_${type}=true" fi ;; 3) [ "$(eval echo \$update_refilter_${type}_msg)" != "true" ] && eval "install_refilter_${type}=true" || eval "update_refilter_${type}=true" ;; 4) [ "$(eval echo \$update_v2fly_${type}_msg)" != "true" ] && eval "install_v2fly_${type}=true" || eval "update_v2fly_${type}=true" ;; 5) [ "$(eval echo \$update_${src3}_${type}_msg)" != "true" ] && eval "install_${src3}_${type}=true" || eval "update_${src3}_${type}=true" ;; 6) if [ "$has_updatable_bases" = "false" ]; then echo -e " ${red}Нет установленных ${type_name} для удаления${reset}. Выберите другой пункт" invalid_choice=true else eval "choice_delete_${type}_refilter_select=true" eval "choice_delete_${type}_v2fly_select=true" eval "choice_delete_${type}_${src3}_select=true" fi ;; 0) echo " Выполнен пропуск установки / обновления ${type_name}" if [ "$has_updatable_bases" = "true" ]; then eval "$var_bypass=false" else eval "$var_bypass=true" fi return ;; *) echo -e " ${red}Некорректный ввод.${reset} Пожалуйста, выберите снова" invalid_choice=true ;; esac done [ "$invalid_choice" = true ] && continue install_list="" update_list="" delete_list="" [ "$(eval echo \$install_refilter_${type})" = "true" ] && install_list="$install_list ${yellow}Re:filter${reset}," [ "$(eval echo \$install_v2fly_${type})" = "true" ] && install_list="$install_list ${yellow}v2fly${reset}," [ "$(eval echo \$install_${src3}_${type})" = "true" ] && install_list="$install_list ${yellow}${src3_name}${reset}," [ "$(eval echo \$update_refilter_${type})" = "true" ] && update_list="$update_list ${yellow}Re:filter${reset}," [ "$(eval echo \$update_v2fly_${type})" = "true" ] && update_list="$update_list ${yellow}v2fly${reset}," [ "$(eval echo \$update_${src3}_${type})" = "true" ] && update_list="$update_list ${yellow}${src3_name}${reset}," [ "$(eval echo \$choice_delete_${type}_refilter_select)" = "true" ] && delete_list="$delete_list ${yellow}Re:filter${reset}," [ "$(eval echo \$choice_delete_${type}_v2fly_select)" = "true" ] && delete_list="$delete_list ${yellow}v2fly${reset}," [ "$(eval echo \$choice_delete_${type}_${src3}_select)" = "true" ] && delete_list="$delete_list ${yellow}${src3_name}${reset}," if [ -n "$install_list" ]; then echo -e " Устанавливаются следующие ${type_name}: ${install_list%,}" fi if [ -n "$update_list" ]; then echo -e " Обновляются следующие ${type_name}: ${update_list%,}" fi if [ -n "$delete_list" ]; then echo -e " Удаляются следующие ${type_name}: ${delete_list%,}" fi break done if [ -z "$install_list" ] && [ -z "$update_list" ] && [ -z "$delete_list" ]; then eval "$var_bypass=true" else eval "$var_bypass=false" fi } choice_geosite() { choice_geodata "geosite" "GeoSite" "zkeen" "ZKeen" "bypass_cron_geosite" } choice_geoip() { choice_geodata "geoip" "GeoIP" "zkeenip" "ZKeenIP" "bypass_cron_geoip" } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/04_choice_input.sh ================================================ # Функция для выбора пользователя между "Да" и "Нет" с номерами 0 и 1 input_concordance_list() { prompt_message=" $1" error_message=" ${yellow}Пожалуйста, выберите вариант, введя номер 0 (Нет) или 1 (Да)${reset}" echo echo -e "$prompt_message" echo " 0. Нет" echo " 1. Да" while true; do echo read -r -p " Введите номер: " user_input case "$user_input" in 0) return 1 ;; 1) return 0 ;; *) echo echo -e " $error_message" continue ;; esac done } toggle_param() { param="$1" description="$2" restart_needed="$3" force_state="$4" echo if [ ! -f "$initd_file" ]; then echo -e " ${red}Ошибка${reset}: Не найден файл ${yellow}S05xkeen${reset}" return 1 fi current_state=$(grep -m 1 -E "^[[:space:]]*$param=" "$initd_file" | cut -d'=' -f2 | tr -d '"[:space:]') if [ "$force_state" = "on" ] || [ "$force_state" = "off" ]; then if [ "$current_state" = "$force_state" ]; then if [ "$current_state" = "on" ]; then echo -e " Состояние ${description} уже ${green}включено${reset}" else echo -e " Состояние ${description} уже ${red}отключено${reset}" fi [ "$apply" = "restart" ] && echo return 0 fi desired_state="$force_state" elif [ "$bypass_autostart_msg" = "yes" ]; then if [ "$current_state" = "on" ]; then desired_state="off" else desired_state="on" fi else echo -e " Текущее состояние ${description}:" if [ "$current_state" = "on" ]; then echo -e " ${green}Включено${reset}" echo echo " 1. Отключить" echo " 0. Оставить без изменений" desired_state="off" else echo -e " ${red}Отключено${reset}" echo echo " 1. Включить" echo " 0. Оставить без изменений" desired_state="on" fi echo while true; do read -r -p " Ваш выбор: " choice case "$choice" in 0) return 0 ;; 1) break ;; *) echo -e " ${red}Некорректный ввод${reset}" ;; esac done fi if awk -v param="$param" -v value="$desired_state" ' !found && $0 ~ "^[[:space:]]*" param "=" { sub(/"[^"]*"/, "\"" value "\"") found=1 } {print} ' "$initd_file" > "$initd_file.tmp" && mv "$initd_file.tmp" "$initd_file"; then [ "$bypass_autostart_msg" = "yes" ] && return 0 if [ "$desired_state" = "on" ]; then echo -e " Новое состояние ${description} ${green}включено${reset}" else echo -e " Новое состояние ${description} ${red}отключено${reset}" fi if [ "$restart_needed" = "reboot" ]; then echo echo -e " ${yellow}Перезагрузите роутер для применения изменений${reset}" elif [ "$restart_needed" = "restart" ] && [ "$apply" != "restart" ]; then echo echo -e " ${yellow}Перезапустите XKeen для применения изменений${reset}" fi add_chmod_init else echo echo -e " ${red}Ошибка${reset} при изменении параметра $param" return 1 fi } choice_menu() { title="$1" option_yes="$2" option_no="$3" echo [ -n "$title" ] && echo -e " $title" echo echo " 1. $option_yes" echo " 0. $option_no" echo while true; do read -r -p " Ваш выбор: " choice case "$choice" in 1) return 0 ;; 0) return 1 ;; *) echo -e " ${red}Некорректный ввод${reset}" ;; esac done } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/00_cron_import.sh ================================================ # Импорт модулей вопросов cron # Модули вопросов cron . "$xtools_dir/05_tools_choice/05_choice_cron/01_cron_status.sh" . "$xtools_dir/05_tools_choice/05_choice_cron/02_cron_time.sh" ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/01_cron_status.sh ================================================ # Определение статуса для задач cron get_existing_cron_time() { crontab -l 2>/dev/null | grep 'xkeen -ug' | head -n1 | awk '{print $1,$2,$3,$4,$5}' } format_cron_time() { cron="$1" minute=$(echo "$cron" | awk '{print $1}') hour=$(echo "$cron" | awk '{print $2}') dow=$(echo "$cron" | awk '{print $5}') formatted_hour=$(printf "%02d" "$hour") formatted_minute=$(printf "%02d" "$minute") case "$dow" in "*") day="Ежедневно" ;; 1) day="Понедельник" ;; 2) day="Вторник" ;; 3) day="Среда" ;; 4) day="Четверг" ;; 5) day="Пятница" ;; 6) day="Суббота" ;; 0) day="Воскресенье" ;; *) day="Неизвестно" ;; esac echo "$day в $formatted_hour:$formatted_minute" } choice_update_cron() { has_updatable_cron_tasks=false [ "$info_update_geofile_cron" = "installed" ] && has_updatable_cron_tasks=true existing_cron=$(get_existing_cron_time) if [ -n "$existing_cron" ]; then echo echo -e " Время обновления ${yellow}геофайлов${reset} установлено на: ${green}$(format_cron_time "$existing_cron")${reset}" fi while true; do choice_cancel_cron_select=false choice_geofile_cron_select=false choice_delete_all_cron_select=false invalid_choice=false echo echo -e " Выберите номер действия для автообновления ${yellow}GeoFile/GeoIPSET${reset}" echo [ "$info_update_geofile_cron" != "installed" ] && geofile_choice="Включить" || geofile_choice="Обновить" echo " 1. $geofile_choice задачу" echo " 0. Пропустить" [ "$has_updatable_cron_tasks" = true ] && echo && echo " 2. Выключить автообновление" echo while true; do read -r -p " Ваш выбор: " update_choices update_choices=$(echo "$update_choices" | sed 's/,/, /g') if echo "$update_choices" | grep -qE '^[0-2]$'; then break else echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов" fi done for choice in $update_choices; do case "$choice" in 1) choice_geofile_cron_select=true if [ "$info_update_geofile_cron" = "installed" ]; then echo -e " ${yellow}Будет выполнено${reset} обновление задачи GeoFile/GeoIPSET" else echo -e " ${yellow}Будет выполнено${reset} включение задачи GeoFile/GeoIPSET" fi ;; 0) choice_cancel_cron_select=true echo " Выполнен пропуск настройки автообновления" return ;; 2) if [ "$has_updatable_cron_tasks" = true ]; then delete_cron_geofile echo -e " Автообновление баз GeoFile/GeoIPSET ${green}выключено${reset}" else echo -e " ${red}Автообновление баз GeoFile/GeoIPSET не включено${reset}. Выберите другой пункт" invalid_choice=true fi ;; *) echo -e " ${red}Некорректный ввод${reset}" invalid_choice=true ;; esac done [ "$invalid_choice" = true ] || break done } ================================================ FILE: scripts/_xkeen/04_tools/05_tools_choice/05_choice_cron/02_cron_time.sh ================================================ # Определение времени для задач cron choice_cron_time() { [ "$choice_geofile_cron_select" = true ] || return echo echo -e " Время автоматического обновления ${yellow}геофайлов${reset}:" echo echo " Выберите день" echo " 0. Отмена" echo " 1. Понедельник" echo " 2. Вторник" echo " 3. Среда" echo " 4. Четверг" echo " 5. Пятница" echo " 6. Суббота" echo " 7. Воскресенье" echo " 8. Ежедневно" echo while :; do read -r -p " Ваш выбор: " day_choice echo "$day_choice" | grep -qE '^[0-8]$' && break echo -e " ${red}Некорректный номер действия.${reset} Пожалуйста, выберите снова" done [ "$day_choice" -eq 0 ] && { echo -e " Включение автоматического обновления ${yellow}геофайлов${reset} отменено." return } echo while :; do read -r -p " Выберите час (0-23): " hour case "$hour" in ''|*[!0-9]*) ;; *) [ "$hour" -ge 0 ] && [ "$hour" -le 23 ] && break ;; esac echo -e " ${red}Некорректный час.${reset} Пожалуйста, попробуйте снова" done while :; do read -r -p " Выберите минуту (0-59): " minute case "$minute" in ''|*[!0-9]*) ;; *) [ "$minute" -ge 0 ] && [ "$minute" -le 59 ] && break ;; esac echo -e " ${red}Некорректные минуты.${reset} Пожалуйста, попробуйте снова" done if [ "$day_choice" -eq 8 ]; then cron_expression="$minute $hour * * *" day_name="Ежедневно" else case "$day_choice" in 1) dow=1; day_name="Понедельник" ;; 2) dow=2; day_name="Вторник" ;; 3) dow=3; day_name="Среда" ;; 4) dow=4; day_name="Четверг" ;; 5) dow=5; day_name="Пятница" ;; 6) dow=6; day_name="Суббота" ;; 7) dow=0; day_name="Воскресенье" ;; esac cron_expression="$minute $hour * * $dow" fi formatted_hour=$(printf "%02d" "$hour") formatted_minute=$(printf "%02d" "$minute") echo echo -e " Выбранное время обновления ${yellow}геофайлов${reset}: $day_name в $formatted_hour:$formatted_minute" choice_geofile_cron_time="$cron_expression" } ================================================ FILE: scripts/_xkeen/04_tools/06_tools_backups/00_backups_import.sh ================================================ # Импорт модулей резервного копирования # Модули резервного копирования . "$xtools_dir/06_tools_backups/01_backups_xkeen.sh" . "$xtools_dir/06_tools_backups/02_backups_configs_xray.sh" . "$xtools_dir/06_tools_backups/02_backups_configs_mihomo.sh" ================================================ FILE: scripts/_xkeen/04_tools/06_tools_backups/01_backups_xkeen.sh ================================================ # Создание резервной копии XKeen backup_xkeen() { if choice_backup_xkeen && [ -z "$manual_backup" ]; then return 0 fi backup_filename="${current_datetime}_xkeen_v${xkeen_current_version}" backup_dir="${backups_dir}/${backup_filename}" mkdir -p "$backup_dir" # Копирование файлов. Проверяем успех всей операции копирования if cp -r "$install_dir/.xkeen" "$install_dir/xkeen" "$backup_dir/"; then # Переименование скрытой директории для удобства хранения mv "$backup_dir/.xkeen" "$backup_dir/_xkeen" echo -e " Резервная копия XKeen создана: ${yellow}${backup_filename}${reset}" else echo -e " ${red}Ошибка${reset} при создании резервной копии XKeen" fi } # Восстановление XKeen из резервной копии restore_backup_xkeen() { latest_backup_dir="" for entry in "$backups_dir"/*xkeen*; do if [ -d "$entry" ]; then latest_backup_dir="$entry" fi done if [ -n "$latest_backup_dir" ]; then # Используем временную директорию для безопасности при восстановлении # Чтобы не удалить старый .xkeen, пока не убедимся, что копия цела if cp -r "$latest_backup_dir/_xkeen" "$install_dir/" && \ cp -f "$latest_backup_dir/xkeen" "$install_dir/"; then rm -rf "${install_dir:?}/.xkeen" mv "$install_dir/_xkeen" "$install_dir/.xkeen" echo -e " XKeen ${green}успешно восстановлен${reset} из: $(basename "$latest_backup_dir")" else echo -e " ${red}Ошибка:${reset} Не удалось скопировать файлы из резервной копии" fi else echo -e " ${red}Ошибка:${reset} Подходящая резервная копия XKeen не найдена" fi } ================================================ FILE: scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_mihomo.sh ================================================ backup_configs_mihomo() { backup_filename="${current_datetime}_configs_mihomo" backup_configs_dir="$backups_dir/$backup_filename" mkdir -p "$backup_configs_dir" if cp -r "$mihomo_conf_dir"/* "$backup_configs_dir/"; then echo -e " Резервная копия конфигурации Mihomo создана: ${yellow}$backup_filename${reset}" else echo -e " ${red}Ошибка${reset} при создании резервной копии конфигураций Mihomo" fi } restore_backup_configs_mihomo() { latest_backup="" for entry in "$backups_dir"/*_configs_mihomo; do if [ -e "$entry" ]; then latest_backup="$entry" fi done if [ -n "$latest_backup" ]; then rm -rf "${mihomo_conf_dir:?}"/* if cp -r "$latest_backup"/* "$mihomo_conf_dir/"; then echo -e " Конфигурация Mihomo ${green}успешно восстановлена${reset} из: $(basename "$latest_backup")" else echo -e " ${red}Ошибка${reset} при восстановлении файлов" fi else echo -e " ${red}Ошибка:${reset} Резервные копии не найдены в $backups_dir" fi } ================================================ FILE: scripts/_xkeen/04_tools/06_tools_backups/02_backups_configs_xray.sh ================================================ backup_configs_xray() { backup_filename="${current_datetime}_configs_xray" backup_configs_dir="$backups_dir/$backup_filename" mkdir -p "$backup_configs_dir" if cp -r "$xray_conf_dir"/* "$backup_configs_dir/"; then echo -e " Резервная копия конфигурации Xray создана: ${yellow}$backup_filename${reset}" else echo -e " ${red}Ошибка${reset} при создании резервной копии конфигураций Xray" fi } restore_backup_configs_xray() { latest_backup="" for entry in "$backups_dir"/*_configs_xray; do if [ -e "$entry" ]; then latest_backup="$entry" fi done if [ -n "$latest_backup" ]; then rm -rf "${xray_conf_dir:?}"/* if cp -r "$latest_backup"/* "$xray_conf_dir/"; then echo -e " Конфигурация Xray ${green}успешно восстановлена${reset} из: $(basename "$latest_backup")" else echo -e " ${red}Ошибка${reset} при восстановлении файлов" fi else echo -e " ${red}Ошибка:${reset} Резервные копии не найдены в $backups_dir" fi } ================================================ FILE: scripts/_xkeen/04_tools/07_tools_downloaders/00_downloaders_import.sh ================================================ # Импорт модулей загрузки # fetch_with_mirrors / probe_with_mirrors уже подгружены из xkeen/import.sh # (раньше install-модуля, который тоже их вызывает). # Модули загрузки . "$xtools_dir/07_tools_downloaders/01_downloaders_xray.sh" . "$xtools_dir/07_tools_downloaders/01_downloaders_mihomo.sh" . "$xtools_dir/07_tools_downloaders/02_donwloaders_xkeen.sh" ================================================ FILE: scripts/_xkeen/04_tools/07_tools_downloaders/00_fetch_with_mirrors.sh ================================================ # Загрузка с per-call mirror-fallback'ом. # # Заменяет паттерн "test_github -> один gh_proxy на сессию -> один curl # без fallback'а". Старый flow ломается когда выбранный mirror транзиентно # падает между test_github и фактической загрузкой: curl получает 5xx или # таймаут, caller тихо return 1, geo-файлы устаревают. # # fetch_with_mirrors пробует префиксы по очереди (gh_proxy_user # exclusive, иначе direct + gh_proxy1 + gh_proxy2), кэширует удачный # выбор на TTL_SEC, валидирует ответ (HTTP-код + min-size + HTML-stub # detect). После неудачного вызова caller может прочитать причину из # глобальных переменных _last_error / _last_size (для fetch) и # _last_http (для probe), чтобы напечатать осмысленное сообщение. _mirror_cache="/tmp/.xkeen_mirror_cache" _mirror_ttl=60 _DIRECT_TOKEN="__direct__" # Чтение закэшированного префикса. stdout = префикс ("" для direct), # rc = 0 если кэш свежий, 1 если просрочен/garbage/отсутствует. _mirror_cache_read() { [ -r "$_mirror_cache" ] || return 1 _cache_ts="" _cache_pfx="" IFS=' ' read -r _cache_ts _cache_pfx < "$_mirror_cache" 2>/dev/null || return 1 case "$_cache_ts" in ''|*[!0-9]*) return 1 ;; esac _cache_now=$(date +%s 2>/dev/null) || return 1 [ $((_cache_now - _cache_ts)) -lt "$_mirror_ttl" ] || return 1 [ "$_cache_pfx" = "$_DIRECT_TOKEN" ] && _cache_pfx="" printf '%s' "$_cache_pfx" return 0 } # Сохранение удачного префикса в кэш. $1 = "" для direct, иначе url. _mirror_cache_write() { _w_pfx="$1" [ -z "$_w_pfx" ] && _w_pfx="$_DIRECT_TOKEN" printf '%s %s\n' "$(date +%s)" "$_w_pfx" > "$_mirror_cache" 2>/dev/null } # Список префиксов для попыток, по одному на строку, в порядке приоритета. # Используется token __direct__ для direct GitHub (пустая строка ломала # бы heredoc-итерацию). _mirror_order() { if [ -n "$gh_proxy_user" ]; then printf '%s\n' "${gh_proxy_user%/}" return fi _order_cached_set=0 if _order_cached=$(_mirror_cache_read); then _order_cached_set=1 printf '%s\n' "${_order_cached:-$_DIRECT_TOKEN}" fi # direct и дефолтные mirror'ы, пропуская тот что уже в кэше if [ "$_order_cached_set" = "0" ] || [ -n "$_order_cached" ]; then printf '%s\n' "$_DIRECT_TOKEN" fi if [ -n "$gh_proxy1" ] && [ "$_order_cached" != "${gh_proxy1%/}" ]; then printf '%s\n' "${gh_proxy1%/}" fi if [ -n "$gh_proxy2" ] && [ "$_order_cached" != "${gh_proxy2%/}" ]; then printf '%s\n' "${gh_proxy2%/}" fi } # Дефолтный валидатор для скачанного файла. # $1 = path, $2 = min_size (байт, 0 = без проверки размера). # Сетит _last_error и _last_size для caller-сообщений. # # HTML-stub detect: cloudflare challenge, jsdelivr "429: Too Many # Requests", proxy-error 404-page под HTTP 200. Маркеры якорные (^...) # чтобы не словить false-positive на байтах в gzip/zip/ELF метадате. _validate_default() { _v_f="$1" _v_min="${2:-0}" _last_error="" _last_size=0 if [ ! -s "$_v_f" ]; then _last_error="curl_failed" return 1 fi _last_size=$(wc -c < "$_v_f" 2>/dev/null | tr -d ' ') if [ "$_v_min" -gt 0 ]; then [ -n "$_last_size" ] && [ "$_last_size" -ge "$_v_min" ] || { _last_error="size" return 1 } fi if head -c 100 "$_v_f" 2>/dev/null | grep -iqE '^( [min_size] [validator] # # Качает в через цепочку префиксов, валидирует. # Атомарная замена: запись в "${dest}.tmp.$$" + mv. # # Возврат: 0 на успех, 1 на полный провал (все попытки failed/invalid). # При rc != 0: _last_error содержит причину последней неудачи # (curl_failed / size / html_stub), _last_size содержит размер файла # при size-fail. fetch_with_mirrors() { _fwm_url="$1" _fwm_dest="$2" _fwm_min="${3:-0}" _fwm_validator="${4:-_validate_default}" _fwm_tmp="${_fwm_dest}.tmp.$$" _fwm_winner="" _last_error="" _last_size=0 rm -f "$_fwm_tmp" _fwm_orders=$(_mirror_order) while IFS= read -r _fwm_prefix; do [ "$_fwm_prefix" = "$_DIRECT_TOKEN" ] && _fwm_prefix="" if [ -n "$_fwm_prefix" ]; then _fwm_fetch="$_fwm_prefix/$_fwm_url" else _fwm_fetch="$_fwm_url" fi if eval curl $curl_extra --connect-timeout 10 $curl_timeout \ -fL -o "$_fwm_tmp" "$_fwm_fetch" >/dev/null 2>&1; then if "$_fwm_validator" "$_fwm_tmp" "$_fwm_min"; then _fwm_winner="$_fwm_prefix" break fi else _last_error="curl_failed" fi rm -f "$_fwm_tmp" done < # # HEAD-probe (с fallback на range-byte для mirror'ов которые не разрешают # HEAD и отдают 405). Используется в xray/mihomo downloader'ах для # быстрой проверки "существует ли такая версия" перед полной загрузкой. # # Возврат: 0 на 2xx; 2 если все попытки получили 4xx (definitive miss, # например пользователь ввёл неверную версию); 1 на прочие транзиентные # ошибки. _last_http содержит HTTP-код последней значимой попытки (для # error сообщений caller'а), _last_curl_rc содержит exit-код curl # последней попытки (28 = таймаут, остальные см. man curl). probe_with_mirrors() { _pwm_url="$1" _pwm_attempts=0 _pwm_fail_4xx=0 _last_http="" _last_curl_rc=0 _pwm_orders=$(_mirror_order) while IFS= read -r _pwm_prefix; do [ "$_pwm_prefix" = "$_DIRECT_TOKEN" ] && _pwm_prefix="" if [ -n "$_pwm_prefix" ]; then _pwm_probe="$_pwm_prefix/$_pwm_url" else _pwm_probe="$_pwm_url" fi _pwm_attempts=$((_pwm_attempts + 1)) _pwm_code=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout \ -I -s -L -w '%{http_code}' -o /dev/null "$_pwm_probe" 2>/dev/null) _last_curl_rc=$? if [ "$_pwm_code" = "405" ]; then _pwm_code=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout \ -s -L -r 0-0 -w '%{http_code}' -o /dev/null "$_pwm_probe" 2>/dev/null) _last_curl_rc=$? fi _last_http="$_pwm_code" case "$_pwm_code" in 2[0-9][0-9]) _mirror_cache_write "$_pwm_prefix" return 0 ;; 40[0-9]) _pwm_fail_4xx=$((_pwm_fail_4xx + 1)) ;; esac done </dev/null | jq -r '.[] | select(.prerelease == false) | .tag_name' | head -n 8) if [ -z "$RELEASE_TAGS" ]; then echo printf " ${red}Нет доступа${reset} к ${yellow}GitHub API${reset}. Пробуем ${yellow}jsDelivr${reset}...\n" # Получаем список релизов через jsDelivr RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$mihomo_jsd_url" 2>/dev/null | jq -r '.versions[]' | head -n 8) if [ -z "$RELEASE_TAGS" ]; then echo printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n" echo printf " ${red}Ошибка${reset}: Не удалось получить список релизов ни через ${yellow}GitHub API${reset}, ни через ${yellow}jsDelivr${reset}\n Проверьте соединение с интернетом или повторите позже\n Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n" echo exit 1 fi echo printf " Список релизов получен с использованием ${yellow}jsDelivr${reset}:\n" USE_JSDELIVR="true" else echo printf " Список релизов получен с использованием ${yellow}GitHub API${reset}:\n" fi while true; do echo echo "$RELEASE_TAGS" | awk '{printf " %2d. %s\n", NR, $0}' echo echo " 9. Ручной ввод версии" echo echo " 0. Пропустить загрузку Mihomo" printf "\n Введите порядковый номер релиза (0 - пропустить, 9 - ручной ввод): " read -r choice case "$choice" in [0-9]) ;; *) printf " ${red}Некорректный${reset} ввод. Пожалуйста, введите число\n" sleep 1 continue ;; esac if [ "$choice" = "0" ]; then bypass_mihomo="true" printf " Загрузка Mihomo ${yellow}пропущена${reset}\n" return fi if [ "$choice" = "9" ]; then printf " Введите версию Mihomo для загрузки (например: v1.19.6): " read -r version_selected if [ -z "$version_selected" ]; then printf " ${red}Ошибка${reset}: Версия не может быть пустой\n" sleep 1 continue fi version_selected=$(echo "$version_selected" | sed 's/^v//') version_selected="v$version_selected" else version_selected=$(echo "$RELEASE_TAGS" | awk -v line="$choice" 'NR == line {print $0; exit}') if [ -z "$version_selected" ]; then printf " Выбранный номер ${red}вне диапазона.${reset} Пожалуйста, попробуйте снова\n" sleep 1 continue fi if [ "$USE_JSDELIVR" = "true" ]; then version_selected="v$version_selected" fi fi VERSION_ARG="$version_selected" URL_BASE="${mihomo_gz_url}/$VERSION_ARG" yq_download_base_url="$(get_yq_dist_url)" case $architecture in "arm64-v8a") download_url="$URL_BASE/mihomo-linux-arm64-$VERSION_ARG.gz" download_yq="$yq_download_base_url/yq_linux_arm64" ;; "mips32le") if [ "$softfloat" = "true" ]; then download_url="$URL_BASE/mihomo-linux-mipsle-softfloat-$VERSION_ARG.gz" else download_url="$URL_BASE/mihomo-linux-mipsle-hardfloat-$VERSION_ARG.gz" fi download_yq="$yq_download_base_url/yq_linux_mipsle" ;; "mips32") download_url="$URL_BASE/mihomo-linux-mips-hardfloat-$VERSION_ARG.gz" download_yq="$yq_download_base_url/yq_linux_mips" ;; *) download_url= download_yq= ;; esac if [ -z "$download_url" ] || [ -z "$download_yq" ]; then printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Mihomo\n" exit 1 fi filename=$(basename "$download_url") extension="${filename##*.}" mkdir -p "$mtmp_dir" yq_available="false" printf " ${yellow}Проверка${reset} доступности версии $version_selected...\n" probe_with_mirrors "$download_url" _rc=$? case "$_rc" in 0) printf " Файл ${green}доступен${reset}\n" ;; 2) case "$_last_http" in 403) printf " ${red}Доступ запрещен${reset} (403)\n" ;; 404) printf " Файл ${red}не найден${reset} (404)\n" ;; *) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;; esac printf " ${red}Ошибка${reset}: Версия Mihomo $version_selected недоступна\n" continue ;; *) if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT printf " ${red}Таймаут${reset} при проверке\n" elif [ "$_last_curl_rc" != "0" ]; then printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc" elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" else printf " ${red}Нет соединения${reset}\n" fi printf " ${red}Ошибка${reset}: Версия Mihomo $version_selected недоступна\n" continue ;; esac printf " ${yellow}Выполняется загрузка${reset} парсера конфигурационных файлов Mihomo - Yq\n" if probe_with_mirrors "$download_yq"; then if fetch_with_mirrors "$download_yq" "$install_dir/yq" 1024; then chmod +x "$install_dir/yq" yq_available="true" printf " Yq ${green}успешно загружен и установлен${reset}\n" else printf " ${red}Ошибка${reset}: Не удалось загрузить Yq\n" fi else printf " ${yellow}Предупреждение${reset}: Yq недоступен для загрузки, продолжение без него\n" fi printf " ${yellow}Выполняется загрузка${reset} выбранной версии Mihomo\n" if [ "$yq_available" != "true" ] && [ -x "$install_dir/yq" ]; then yq_available="true" printf " ${yellow}Используется${reset} уже установленный Yq\n" fi if [ "$yq_available" != "true" ]; then printf " ${red}Ошибка${reset}: Для работы Mihomo требуется Yq. Установка прервана\n" return 1 fi if ! fetch_with_mirrors "$download_url" "$mtmp_dir/mihomo.$extension" 1024; then printf " ${red}Ошибка${reset}: Не удалось загрузить Mihomo $version_selected\n" continue fi printf " Mihomo ${green}успешно загружен${reset}\n" return 0 done } ================================================ FILE: scripts/_xkeen/04_tools/07_tools_downloaders/01_downloaders_xray.sh ================================================ # Загрузка Xray download_xray() { USE_JSDELIVR="" printf " ${green}Запрос информации${reset} о релизах ${yellow}Xray${reset}\n" # Получаем список релизов через GitHub API RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "${xray_api_url}?per_page=50" 2>/dev/null | jq -r '.[] | select(.prerelease == false) | .tag_name' | head -n 8) if [ -z "$RELEASE_TAGS" ]; then echo printf " ${red}Нет доступа${reset} к ${yellow}GitHub API${reset}. Пробуем ${yellow}jsDelivr${reset}...\n" # Получаем список релизов через jsDelivr RELEASE_TAGS=$(eval curl $curl_extra --connect-timeout 10 $curl_timeout -s "$xray_jsd_url" 2>/dev/null | jq -r '.versions[]' | head -n 8) if [ -z "$RELEASE_TAGS" ]; then echo printf " ${red}Нет доступа${reset} к ${yellow}jsDelivr${reset}\n" echo printf " ${red}Ошибка${reset}: Не удалось получить список релизов ни через ${yellow}GitHub API${reset}, ни через ${yellow}jsDelivr${reset}\n Проверьте соединение с интернетом или повторите позже\n Если ошибка сохраняется, воспользуйтесь возможностью OffLine установки:\n https://github.com/jameszeroX/XKeen/blob/main/OffLine_install.md\n" echo exit 1 fi echo printf " Список релизов получен с использованием ${yellow}jsDelivr${reset}:\n" USE_JSDELIVR="true" else echo printf " Список релизов получен с использованием ${yellow}GitHub API${reset}:\n" fi if [ "$autoinstall_mode" = "true" ]; then version_selected=$(echo "$RELEASE_TAGS" | head -1) [ "$USE_JSDELIVR" = "true" ] && version_selected="v$version_selected" printf " ${green}Авто-режим${reset}: выбрана последняя версия ${yellow}%s${reset}\n" "$version_selected" VERSION_ARG="$version_selected" URL_BASE="${xray_zip_url}/$VERSION_ARG" case $architecture in "arm64-v8a") download_url="$URL_BASE/Xray-linux-arm64-v8a.zip" ;; "mips32le") download_url="$URL_BASE/Xray-linux-mips32le.zip" ;; "mips32") download_url="$URL_BASE/Xray-linux-mips32.zip" ;; *) download_url= ;; esac if [ -z "$download_url" ]; then printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Xray\n" exit 1 fi filename=$(basename "$download_url") extension="${filename##*.}" mkdir -p "$xtmp_dir" printf " ${yellow}Проверка${reset} доступности версии %s...\n" "$version_selected" probe_with_mirrors "$download_url" _rc=$? case "$_rc" in 0) printf " Файл ${green}доступен${reset}\n" ;; 2) case "$_last_http" in 403) printf " ${red}Доступ запрещен${reset} (403)\n" ;; 404) printf " Файл ${red}не найден${reset} (404)\n" ;; *) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;; esac printf " ${red}Ошибка${reset}: Версия %s недоступна\n" "$version_selected" exit 1 ;; *) if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT printf " ${red}Таймаут${reset} при проверке\n" elif [ "$_last_curl_rc" != "0" ]; then printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc" elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" else printf " ${red}Нет соединения${reset}\n" fi printf " ${red}Ошибка${reset}: Версия %s недоступна\n" "$version_selected" exit 1 ;; esac printf " ${yellow}Выполняется загрузка${reset} последней версии Xray\n" if ! fetch_with_mirrors "$download_url" "$xtmp_dir/xray.$extension" 1024; then printf " ${red}Ошибка${reset}: Не удалось загрузить Xray %s\n" "$version_selected" exit 1 fi printf " Xray ${green}успешно загружен${reset}\n" return 0 fi while true; do echo echo "$RELEASE_TAGS" | awk '{printf " %2d. %s\n", NR, $0}' echo echo " 9. Ручной ввод версии" echo echo " 0. Пропустить загрузку Xray" printf "\n Введите порядковый номер релиза (0 - пропустить, 9 - ручной ввод): " read -r choice case "$choice" in [0-9]) ;; *) printf " ${red}Некорректный${reset} ввод. Пожалуйста, введите число\n" sleep 1 continue ;; esac if [ "$choice" = "0" ]; then bypass_xray="true" printf " Загрузка Xray ${yellow}пропущена${reset}\n" return fi if [ "$choice" = "9" ]; then printf " Введите версию Xray для загрузки (например: v25.4.30): " read -r version_selected if [ -z "$version_selected" ]; then printf " ${red}Ошибка${reset}: Версия не может быть пустой\n" sleep 1 continue fi version_selected=$(echo "$version_selected" | sed 's/^v//') version_selected="v$version_selected" else version_selected=$(echo "$RELEASE_TAGS" | awk -v line="$choice" 'NR == line {print $0; exit}') if [ -z "$version_selected" ]; then printf " Выбранный номер ${red}вне диапазона.${reset} Пожалуйста, попробуйте снова\n" sleep 1 continue fi if [ "$USE_JSDELIVR" = "true" ]; then version_selected="v$version_selected" fi fi VERSION_ARG="$version_selected" URL_BASE="${xray_zip_url}/$VERSION_ARG" case $architecture in "arm64-v8a") download_url="$URL_BASE/Xray-linux-arm64-v8a.zip" ;; "mips32le") download_url="$URL_BASE/Xray-linux-mips32le.zip" ;; "mips32") download_url="$URL_BASE/Xray-linux-mips32.zip" ;; *) download_url= ;; esac if [ -z "$download_url" ]; then printf " ${red}Ошибка${reset}: Не удалось получить URL для загрузки Xray\n" exit 1 fi filename=$(basename "$download_url") extension="${filename##*.}" mkdir -p "$xtmp_dir" printf " ${yellow}Проверка${reset} доступности версии $version_selected...\n" probe_with_mirrors "$download_url" _rc=$? case "$_rc" in 0) printf " Файл ${green}доступен${reset}\n" ;; 2) case "$_last_http" in 403) printf " ${red}Доступ запрещен${reset} (403)\n" ;; 404) printf " Файл ${red}не найден${reset} (404)\n" ;; *) printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" ;; esac printf " ${red}Ошибка${reset}: Версия $version_selected недоступна\n" continue ;; *) if [ "$_last_curl_rc" = "28" ]; then # curl OPERATION_TIMEDOUT printf " ${red}Таймаут${reset} при проверке\n" elif [ "$_last_curl_rc" != "0" ]; then printf " ${red}Ошибка curl (%s)${reset} при проверке\n" "$_last_curl_rc" elif [ -n "$_last_http" ] && [ "$_last_http" != "000" ]; then printf " ${yellow}Проблема с доступом${reset} (HTTP: %s)\n" "$_last_http" else printf " ${red}Нет соединения${reset}\n" fi printf " ${red}Ошибка${reset}: Версия $version_selected недоступна\n" continue ;; esac printf " ${yellow}Выполняется загрузка${reset} выбранной версии Xray\n" if ! fetch_with_mirrors "$download_url" "$xtmp_dir/xray.$extension" 1024; then printf " ${red}Ошибка${reset}: Не удалось загрузить Xray $version_selected\n" continue fi printf " Xray ${green}успешно загружен${reset}\n" return 0 done } ================================================ FILE: scripts/_xkeen/04_tools/07_tools_downloaders/02_donwloaders_xkeen.sh ================================================ # Загрузка XKeen download_xkeen() { mkdir -p "$ktmp_dir" printf " ${yellow}Выполняется загрузка${reset} XKeen\n" if ! fetch_with_mirrors "$xkeen_tar_url" "$ktmp_dir/xkeen.tar.gz" 1024; then printf " ${red}Ошибка${reset}: Не удалось загрузить XKeen\n" exit 1 fi printf " XKeen ${green}успешно загружен${reset}\n" return 0 } download_xkeen_dev() { xkeen_tar_url="$xkeen_dev_url" download_xkeen } ================================================ FILE: scripts/_xkeen/05_tests/00_tests_import.sh ================================================ # Импорт модулей тестирования # Модули тестирования . "$xtests_dir/01_tests_connected.sh" . "$xtests_dir/02_tests_xports.sh" . "$xtests_dir/03_tests_storage.sh" ================================================ FILE: scripts/_xkeen/05_tests/01_tests_connected.sh ================================================ # Функция проверки доступности интернета test_connection() { nslookup "$conn_URL" >/dev/null 2>&1 && return 0 curl -Is --connect-timeout 1 "$conn_URL" >/dev/null && return 0 ping -c 1 -W 1 "$conn_IP1" >/dev/null 2>&1 && return 0 ping -c 1 -W 1 "$conn_IP2" >/dev/null 2>&1 && return 0 printf " ${red}Отсутствует${reset} интернет-соединение\n" exit 1 } # Функция загрузки download_with_check() { url="$1" output_file="$2" min_size="${3:-50000}" eval curl $curl_extra --connect-timeout 5 -m 15 -y 1000 -Y 5 -s -L "$url" -o "$output_file" 2>/dev/null if [ -f "$output_file" ]; then size=$(wc -c < "$output_file" 2>/dev/null || echo 0) if [ "$size" -gt "$min_size" ]; then return 0 fi fi rm -f "$output_file" 2>/dev/null return 1 } # Функция проверки доступности Entware test_entware() { printf " ${yellow}Проверка доступности${reset} репозитория Entware. Подождите, пожалуйста...\n" repo_url=$(awk '/^src/ {print $3; exit}' /opt/etc/opkg.conf 2>/dev/null) if [ -z "$repo_url" ]; then printf " ${red}Не удалось${reset} определить используемый репозиторий Entware\n" exit 1 fi repo_url="$repo_url/Packages.gz" tmp_file="/tmp/pkg_check_$$" if download_with_check "$repo_url" "$tmp_file"; then printf " Репозиторий Entware ${green}доступен${reset}. Продолжаем...\n" opkg update >/dev/null 2>&1 info_packages install_packages rm -f "$tmp_file" 2>/dev/null return 0 else printf " Репозиторий Entware ${red}недоступен${reset}\n" printf " Укажите рабочее зеркало репозитория в файле ${yellow}/opt/etc/opkg.conf${reset}\n" exit 1 fi } # Функция определения пользовательского прокси для GitHub get_user_proxy() { gh_proxy_user="" [ ! -f "$xkeen_config" ] && return 1 if command -v jq >/dev/null 2>&1; then gh_proxy_user=$(jq -r '.xkeen.gh_proxy // empty' "$xkeen_config" 2>/dev/null) fi if [ -z "$gh_proxy_user" ]; then gh_proxy_user=$(sed -n 's/.*"gh_proxy": *"\([^"]*\)".*/\1/p' "$xkeen_config" | xargs 2>/dev/null) fi [ "$gh_proxy_user" = "null" ] && gh_proxy_user="" } # Функция проверки доступности GitHub. # Тонкая обёртка над probe_with_mirrors: идентичная философия (пара URL # через одну цепочку префиксов, exclusive gh_proxy_user, exit 1 на # полную недоступность), но один engine fallback в helper'е вместо # дублирования логики direct/proxy1/proxy2 здесь. test_github() { [ -n "$_gh_probed" ] && return 0 get_user_proxy printf " ${yellow}Проверка доступности${reset} GitHub. Подождите, пожалуйста...\n" # Pair probe: github.com (releases) + raw.githubusercontent.com # (для dev-обновления). Разные CDN, разный uptime, проверяем оба. if probe_with_mirrors "$xkeen_tar_url" && probe_with_mirrors "$xkeen_dev_url"; then _gh_probed=1 if [ -n "$gh_proxy_user" ]; then printf " GitHub ${green}доступен через ваш прокси${reset}: ${yellow}$gh_proxy_user${reset}. Продолжаем...\n" elif [ -r /tmp/.xkeen_mirror_cache ] && grep -q "__direct__" /tmp/.xkeen_mirror_cache 2>/dev/null; then printf " GitHub ${green}доступен${reset}. Продолжаем...\n" else printf " GitHub ${green}доступен через прокси${reset}. Продолжаем...\n" fi return 0 fi if [ -n "$gh_proxy_user" ]; then printf " ${red}Ошибка${reset}: Указанный вами прокси $gh_proxy_user недоступен\n" else printf " ${red}Ошибка${reset}: GitHub недоступен\n" fi exit 1 } ================================================ FILE: scripts/_xkeen/05_tests/02_tests_xports.sh ================================================ # Определение на каких портах слушает ядро прокси tests_ports_client() { if pidof "xray" >/dev/null; then name_client=xray elif pidof "mihomo" >/dev/null; then name_client=mihomo else echo echo " Определение портов прослушивания возможно только при работающем XKeen" echo " Запустите XKeen командой 'xkeen -start'" exit 1 fi listening_ports_tcp= listening_ports_udp= output=" $name_client ${green}слушает${reset}" listening_ports_tcp=$(netstat -ltunp | grep "$name_client" | grep "tcp") listening_ports_udp=$(netstat -ltunp | grep "$name_client" | grep "udp") if [ -n "$listening_ports_tcp" ] || [ -n "$listening_ports_udp" ]; then printed=false IFS=' ' for line in $listening_ports_tcp $listening_ports_udp; do gateway= port= protocol= if [ -n "$(echo "$line" | grep "tcp")" ]; then protocol="TCP" fi if [ -n "$(echo "$line" | grep "udp")" ]; then if [ -n "$protocol" ]; then protocol="$protocol и UDP" else protocol="UDP" fi fi full_address=$(echo "$line" | awk '{print $4}') if echo "$full_address" | grep -q '^:::[0-9]'; then # Если IPv4 отображается как :::port gateway="0.0.0.0" port=$(echo "$full_address" | awk -F':::' '{print $2}') elif echo "$full_address" | grep -q '^\[::\]'; then # Явный IPv6 [::]:port gateway="[::]" port=$(echo "$full_address" | awk -F'\\]:' '{print $2}') elif echo "$full_address" | grep -q '\\]:'; then # Обычный IPv6 [addr]:port gateway=$(echo "$full_address" | awk -F'\\]:' '{print $1}')"]" port=$(echo "$full_address" | awk -F'\\]:' '{print $2}') elif echo "$full_address" | grep -q ':'; then # Обычный IPv4 gateway=$(echo "$full_address" | cut -d':' -f1) port=$(echo "$full_address" | cut -d':' -f2) fi if [ "$printed" = false ]; then printf "%b\n" "$output" printed=true fi printf "\n %bШлюз%b %s\n %bПорт%b %s\n %bПротокол%b %s\n" \ "$italic" "$reset" "$gateway" \ "$italic" "$reset" "$port" \ "$italic" "$reset" "$protocol" done else printf "%b\n" " $name_client ${red}не слушает${reset} на каких-либо портах" fi } ================================================ FILE: scripts/_xkeen/05_tests/03_tests_storage.sh ================================================ # Определение места установки Entware location_entware_storage() { mount_point=$(mount | grep 'on /opt ') device=$(echo "$mount_point" | awk '{print $1}') if echo "$device" | grep -q "^/dev/sd"; then entware_storage="на внешний USB-накопитель" elif echo "$device" | grep -q "^/dev/ubi"; then entware_storage="во внутреннюю память роутера" preinstall_warn="true" else entware_storage="на неидентифицированный носитель информации" fi } preinstall_warn() { if [ -n "$preinstall_warn" ]; then echo echo -e " ${red}Внимание${reset}: Инициирована установка XKeen $entware_storage" echo " Убедитесь, что на ней достаточно свободного места. Сбой при такой" echo " установке не является проблемой XKeen и багрепорт не будет рассмотрен" echo -e " XKeen ${green}рекомендуется${reset} устанавливать на внешний ${green}USB-накопитель${reset}" echo echo " 1. Продолжить установку $entware_storage" echo " 2. Выйти из установщика" echo while true; do read -p " Выберите действие: " choice case $choice in 1) clear break ;; 2) echo echo -e " ${red}Установка отменена${reset}" exit 0 ;; *) echo -e " ${red}Некорректный ввод.${reset} Выберите один из предложенных вариантов" ;; esac done fi } ================================================ FILE: scripts/_xkeen/about.sh ================================================ about_xkeen() { echo printf " Утилита ${green}XKeen${reset} предназначена для управления межсетевым\n экраном роутера ${yellow}Keenetic${reset}, защищающим домашнюю сеть.\n Разработчики ${red}не несут ответственности${reset} за использование\n ${green}XKeen${reset} вне прямого назначения. Перед использованием убедитесь,\n что ваши действия соответствуют законодательству вашей страны.\n Использование ${green}XKeen${reset} в противоправных целях ${red}строго запрещено${reset}.\n" } author_donate() { echo echo " Выберите удобный для Вас способ:" echo echo -e " Поддержать автора оригинального XKeen (${green}Skrill0${reset})" echo " 1. Т-Банк" echo " 2. DonationAlerts/ЮMoney" echo " 3. Crypto" echo echo -e " Поддержать разработчика форка XKeen (${green}jameszero${reset})" echo " 4. Карта МИР" echo " 5. CloudTips/ЮMoney" echo " 6. Crypto" echo echo " 0. Отмена" echo while true; do read -r -p " Ваш выбор: " choice case "$choice" in 1) echo echo -e " ${yellow}Прямая ссылка${reset}" echo " https://www.tbank.ru/rm/krasilnikova.alina18/G4Z9433893" echo echo -e " ${yellow}Номер карты${reset}" echo " 2200 7008 8716 3128" echo return 0 ;; 2) echo echo -e " ${yellow}Прямая ссылка DonationAlerts${reset}" echo " https://www.donationalerts.com/r/skrill0" echo echo -e " ${yellow}Прямая ссылка ЮMoney${reset}" echo " https://yoomoney.ru/to/410018052017678" echo echo -e " ${yellow}Номер ЮMoney-кошелька${reset}" echo " 4100 1805 201 7678" echo return 0 ;; 3) echo echo -e " ${yellow}USDT${reset}, TRC20" echo " tsc6emx5khk4cpyfkwj7dusybokravxs3m" echo echo -e " ${yellow}USDT${reset}, ERC20 и BEP20" echo " 0x4a0369a762e3a23cc08f0bbbf39e169a647a5661" echo echo -e " ${light_blue}Уточните актуальность реквизитов перед переводом${reset}" echo return 0 ;; 4) echo echo -e " ${yellow}Карта МИР${reset} ЮMoney" echo " 2204 1201 2976 4110" echo return 0 ;; 5) echo echo -e " ${yellow}Прямая ссылка CloudTips${reset}" echo " https://pay.cloudtips.ru/p/7edb30ec" echo echo -e " ${yellow}Прямая ссылка ЮMoney${reset}" echo " https://yoomoney.ru/to/41001350776240" echo echo -e " ${yellow}Номер ЮMoney-кошелька${reset}" echo " 4100 1350 7762 40" echo return 0 ;; 6) echo echo -e " ${yellow}USDT${reset}, TRC20" echo " TQhy1LbuGe3Bz7EVrDYn67ZFLDjDBa2VNX" echo echo -e " ${yellow}USDT${reset}, ERC20" echo " 0x6a5DF3b5c67E1f90dF27Ff3bd2a7691Fad234EE2" echo echo -e " ${light_blue}Уточните актуальность реквизитов перед переводом${reset}" echo return 0 ;; 0) echo echo -e " ${yellow}Спасибо${reset}, что ознакомились с возможностью поддержать разработчиков" echo return 0 ;; *) echo -e " ${red}Некорректный ввод${reset}" ;; esac done } author_feedback() { echo echo -e " ${green}Контакты разработчиков${reset}" echo echo -e " ${light_blue}Автор оригинального XKeen${reset}:" echo -e " ${yellow}Профиль на форуме keenetic${reset}:" echo " https://forum.keenetic.ru/profile/73583-skrill0" echo -e " ${yellow}e-mail${reset}:" echo " alinajoeyone@gmail.com" echo -e " ${yellow}telegram${reset}:" echo " @Skrill_zerro" echo -e " ${yellow}telegram помощника${reset}:" echo " @skride" echo echo -e " ${light_blue}Разработчик форка XKeen${reset}:" echo -e " ${yellow}Профиль на форуме keenetic${reset}:" echo " https://forum.keenetic.ru/profile/20945-jameszero" echo -e " ${yellow}e-mail${reset}:" echo " admin@jameszero.net" echo -e " ${yellow}telegram${reset}:" echo " @jameszero" echo -e " ${yellow}сайт${reset}:" echo " https://jameszero.net" echo -e " ${yellow}GitHub${reset}:" echo " https://github.com/jameszeroX" echo echo -e " Предоставленные выше контакты предназначены ${green}для личной переписки${reset}, а ${red}не для консультаций${reset}" echo " Возникающие вопросы по XKeen, задавайте в телеграм-чате https://t.me/+8Cvh7oVf6cE0MWRi" } help_xkeen() { echo echo -e "${yellow}Установка${reset}" echo -e " -i ${italic} Основной режим установки XKeen + Xray + GeoFile/GeoIPSET + Mihomo${reset}" echo -e " -io ${italic} OffLine установка XKeen${reset}" echo -e " -toff ${italic} Отключение таймаута при меделенной загрузке с GitHub (xkeen -i -toff)${reset}" echo echo -e "${green}Переустановка${reset}" echo -e " -k ${italic} XKeen${reset}" echo -e " -g ${italic} GeoFile${reset}" echo -e " -gips ${italic} GeoIPSET${reset}" echo echo -e "${yellow}Обновление${reset}" echo -e " -uk ${italic} XKeen${reset}" echo -e " -ug ${italic} GeoFile/GeoIPSET${reset}" echo -e " -ux ${italic} Xray (повышение/понижение версии)${reset}" echo -e " -um ${italic} Mihomo (повышение/понижение версии)${reset}" echo echo -e "${yellow}Запланированная здача автообновления GeoFile/GeoIPSET${reset}" echo -e " -ugc ${italic} Создание${reset}" echo -e " -dgc ${italic} Удаление${reset}" echo echo -e "${green}Резервная копия XKeen${reset}" echo -e " -kb ${italic} Создание${reset}" echo -e " -kbr ${italic} Восстановление${reset}" echo echo -e "${green}Резервная копия конфигурации Xray${reset}" echo -e " -xb ${italic} Создание${reset}" echo -e " -xbr ${italic} Восстановление${reset}" echo echo -e "${green}Резервная копия конфигурации Mihomo${reset}" echo -e " -mb ${italic} Создание${reset}" echo -e " -mbr ${italic} Восстановление${reset}" echo echo -e "${red}Удаление${reset}" echo -e " -remove ${italic} Полная деинсталляция XKeen${reset}" echo -e " -dgs ${italic} GeoSite${reset}" echo -e " -dgi ${italic} GeoIP${reset}" echo -e " -dgips ${italic} GeoIPSET${reset}" echo -e " -dx ${italic} Xray${reset}" echo -e " -dm ${italic} Mihomo${reset}" echo -e " -dk ${italic} XKeen${reset}" echo echo -e "${green}Порты проксирования${reset}" echo -e " -ap ${italic} Добавить${reset}" echo -e " -dp ${italic} Удалить${reset}" echo -e " -cp ${italic} Посмотреть${reset}" echo echo -e "${green}Порты, исключённые из проксирования${reset}" echo -e " -ape ${italic} Добавить${reset}" echo -e " -dpe ${italic} Удалить${reset}" echo -e " -cpe ${italic} Посмотреть${reset}" echo echo -e "${light_blue}Управление прокси-клиентом${reset}" echo -e " -start ${italic} Запуск${reset}" echo -e " -stop ${italic} Остановка${reset}" echo -e " -restart${italic} Перезапуск${reset}" echo -e " -status ${italic} Статус работы${reset}" echo -e " -tp ${italic} Порты, шлюз и протокол прокси-клиента${reset}" echo -e " -auto ${italic} Включить | Отключить автозапуск прокси-клиента${reset}" echo -e " -d ${italic} Установить задержку автозапуска прокси-клиента${reset}" echo -e " -fd ${italic} Включить | Отключить контроль файловых дескрипторов прокси-клиента${reset}" echo -e " -cfd ${italic} Проверить количество файловых дескрипторов открытых прокси-клиентом${reset}" echo -e " -diag ${italic} Выполнить диагностику${reset}" echo -e " -channel${italic} Переключить канал получения обновлений XKeen (Stable/Dev версия)${reset}" echo -e " -xray ${italic} Переключить XKeen на ядро Xray${reset}" echo -e " -mihomo ${italic} Переключить XKeen на ядро Mihomo${reset}" echo -e " -ipv6 ${italic} Включить | Отключить протокол IPv6 в KeeneticOS${reset}" echo -e " -dns ${italic} Включить | Отключить перенаправление DNS в прокси${reset}" echo -e " -pr ${italic} Включить | Отключить проксирование трафика Entware${reset}" echo -e " -extmsg ${italic} Включить | Отключить расширенные сообщения при запуске XKeen${reset}" echo -e " -cbk ${italic} Включить | Отключить резервное копирование XKeen при обновлении${reset}" echo -e " -aghfix ${italic} Включить | Отключить отображение клиентов XKeen под своими IP в журнале AdGuard Home${reset}" echo echo -e "${light_blue}Информация${reset}" echo -e " -about ${italic} О программе${reset}" echo -e " -ad ${italic} Поддержать разработчиков${reset}" echo -e " -af ${italic} Обратная связь${reset}" echo -e " -v ${italic} Версия XKeen${reset}" } ================================================ FILE: scripts/_xkeen/import.sh ================================================ # Импорт основных модулей и определение их путей script_dir="$(cd "$(dirname "$0")" && pwd)" xinfo_dir="$script_dir/.xkeen/01_info" xinstall_dir="$script_dir/.xkeen/02_install" xdelete_dir="$script_dir/.xkeen/03_delete" xtools_dir="$script_dir/.xkeen/04_tools" xtests_dir="$script_dir/.xkeen/05_tests" main_dir="$script_dir/.xkeen" # Модуль информации . "$xinfo_dir/00_info_import.sh" # Mirror-fallback helper. Загружается до install-модуля, потому что # install-модуль (04_install_geofile, 05_install_geoipset) вызывает # fetch_with_mirrors напрямую. . "$xtools_dir/07_tools_downloaders/00_fetch_with_mirrors.sh" # Модуль установки . "$xinstall_dir/00_install_import.sh" # Модуль удаления . "$xdelete_dir/00_delete_import.sh" # Модуль инструментария . "$xtools_dir/00_tools_import.sh" # Модуль тестирования . "$xtests_dir/00_tests_import.sh" # Модуль справки . "$main_dir/about.sh" ================================================ FILE: scripts/xkeen ================================================ #!/bin/sh # Определение директории, где находится xkeen script_dir="$(cd "$(dirname "$0")" && pwd)" # Скрываем основную директорию xkeen install_xkeen_rename() { source_dir="_xkeen" target_dir=".xkeen" source_path="$script_dir/$source_dir" target_path="$script_dir/$target_dir" if [ -d "$source_path" ]; then if [ -d "$target_path" ]; then rm -rf "$target_path" 2>/dev/null fi mv "$source_path" "$target_path" fi rm "/opt/root/install.sh" 2>/dev/null } install_xkeen_rename add_chmod_init() { [ -f "$initd_file" ] && chmod +x "$initd_file" } # Подсчет количества логических команд (параметров, начинающихся с - или --) LOGICAL_CMD_COUNT=0 for arg in "$@"; do case "$arg" in -*) LOGICAL_CMD_COUNT=$((LOGICAL_CMD_COUNT + 1)) ;; esac done # Функция умной очистки экрана smart_clear() { # Очищаем экран, только если запущена одна логическая команда if [ "$LOGICAL_CMD_COUNT" -le 1 ]; then clear fi } for arg in "$@"; do [ "$arg" = "-toff" ] && touch "/tmp/toff" [ "$arg" = "-restart" ] && apply="restart" case "$arg" in -restart|-start|-stop) detach_eligible=true ;; esac done # Self-detach в новую session/pgid когда вызвали без TTY (ssh без -t, cron, CGI) # чтобы форсированный обрыв caller-сессии не убивал начальные проверки. # Подробности в commit-message. XKEEN_FOREGROUND=1 отключает детач для callers # требующих синхронной семантики (скрипты с && cleanup). if [ -n "$detach_eligible" ] && [ -z "$XKEEN_DETACHED" ] \ && [ ! -t 0 ] && [ ! -t 1 ] && [ -z "$XKEEN_FOREGROUND" ]; then export XKEEN_DETACHED=1 log_dir="/opt/var/log" [ -d "$log_dir" ] || mkdir -p "$log_dir" log="$log_dir/xkeen-detached.log" { echo echo "=== $(date '+%F %T') $0 $* ===" } >>"$log" start-stop-daemon -S -b -n "xkdtch.$$" \ -x "$0" -- "$@" \ >>"$log" 2>&1 echo " Запуск в фоновом режиме, лог: $log" >&2 exit 0 fi get_state_arg() { if [ "$1" = "on" ] || [ "$1" = "off" ]; then echo "$1" fi } # Импортируем модули . "$script_dir/.xkeen/import.sh" # Маркер /tmp/toff отключает curl_timeout — снимаем при прерывании trap 'rm -f /tmp/toff; exit 1' INT TERM xkeen_info() { # Проверяем версию XKeen info_version_xkeen # Проверяем актуальность XKeen info_compare_xkeen } xkeen_set_info() { # Определяем архитектуру процессора info_cpu # Проверяем установку Xray info_xray # Проверяем установку Mihomo info_mihomo # Проверяем установленные базы GeoSite info_geosite # Проверяем установленные базы GeoiIP info_geoip # Проверяем статус автообновления info_cron # Проверяем версию Xray info_version_xray # Проверяем версию Mihomo и Yq info_version_mihomo info_version_yq } while [ $# -gt 0 ]; do case "$1" in -i|-install) # Запуск полного цикла установки test_connection test_entware test_github sleep 2 smart_clear echo check_keen_mode if [ -n "$keen_mode" ]; then echo -e " ${red}Ошибка${reset}: Установка XKeen возможна только на Keenetic в режиме ${green}роутера${reset}" exit 1 fi echo -e " Запуск полного цикла установки ${yellow}XKeen${reset}" init_directories xkeen_info xkeen_set_info logs_cpu_info_console case "$architecture" in arm64-v8a|mips32le|mips32) ;; *) exit 1 ;; esac location_entware_storage preinstall_warn choice_add_proxy_cores if [ "$xray_installed" != "installed" ] && [ "$mihomo_installed" != "installed" ] && [ "$add_xray" = "false" ] && [ "$add_mihomo" = "false" ]; then echo -e " ${red}Невозможно установить${reset} XKeen без ядра проксирования" exit 1 fi if [ "$add_xray" = "true" ]; then smart_clear echo download_xray else command -v xray >/dev/null 2>&1 || bypass_xray="true" fi if [ -z "$bypass_xray" ]; then install_xray info_xray fi if [ "$xray_installed" = "installed" ]; then smart_clear # Устанавливаем GeoSite choice_geosite delete_geosite install_geosite sleep 2 smart_clear # Устанавливаем GeoIP choice_geoip delete_geoip install_geoip sleep 2 fi smart_clear # Устанавливаем GeoIPSET install_geoipset init sleep 2 if [ "$bypass_cron_geosite" = "false" ] || [ "$bypass_cron_geoip" = "false" ] || [ "$bypass_cron_geoipset" = "false" ]; then smart_clear # Настраиваем автоматические обновления info_cron choice_update_cron update_cron_geofile_task smart_clear choice_cron_time install_cron sleep 2 fi if [ "$xray_installed" = "installed" ]; then smart_clear echo install_configs fi # Создаем init для cron "$initd_dir/S05crond" stop >/dev/null 2>&1 [ -e "$initd_dir/S05crond" ] && rm -f "$initd_dir/S05crond" register_cron_initd "$initd_dir/S05crond" start >/dev/null 2>&1 if [ -z "$bypass_xray" ]; then info_version_xray delete_register_xray echo echo -e " Выполняется регистрация ${yellow}Xray${reset}" register_xray_list logs_register_xray_list_info_console register_xray_control logs_register_xray_control_info_console register_xray_status logs_register_xray_status_info_console sleep 2 fi if [ "$add_mihomo" = "true" ]; then smart_clear echo if ! download_mihomo; then bypass_mihomo="true" fi else command -v mihomo >/dev/null 2>&1 || bypass_mihomo="true" fi if [ -z "$bypass_mihomo" ]; then install_mihomo info_mihomo fi if [ "$mihomo_installed" = "installed" ]; then add_mihomo_config fi if [ -z "$bypass_mihomo" ]; then info_version_mihomo info_version_yq delete_register_mihomo echo echo -e " Выполняется регистрация ${yellow}Mihomo${reset}" register_mihomo_list logs_register_mihomo_list_info_console register_mihomo_control logs_register_mihomo_control_info_console register_mihomo_status logs_register_mihomo_status_info_console register_yq_list logs_register_yq_list_info_console register_yq_control logs_register_yq_control_info_console register_yq_status logs_register_yq_status_info_console sleep 2 fi if [ "$mihomo_installed" != "installed" ] && [ "$xray_installed" != "installed" ]; then echo echo -e " ${red}Ошибка${reset}: Установка прервана. Не установлено ни одного ядра проксирования (Xray или Mihomo)" exit 1 fi if [ "$xray_installed" = "installed" ] || [ "$mihomo_installed" = "installed" ]; then delete_register_xkeen smart_clear echo echo -e " Выполняется регистрация ${yellow}XKeen${reset}" register_xkeen_list logs_register_xkeen_list_info_console register_xkeen_control logs_register_xkeen_control_info_console register_xkeen_status logs_register_xkeen_status_info_console fixed_register_packages migrate_ports_from_initd register_xkeen_initd create_xkeen_cfg sleep 2 smart_clear choice_autostart_xkeen fi if [ "$xray_installed" != "installed" ] && [ "$mihomo_installed" = "installed" ]; then if [ -f "$install_dir/mihomo" ] && [ -f "$install_dir/yq" ] && grep -q 'name_client="xray"' "$initd_file"; then sed -i 's/name_client="xray"/name_client="mihomo"/' "$initd_file" fi elif [ "$xray_installed" = "installed" ] && [ "$mihomo_installed" != "installed" ]; then if [ -f "$install_dir/xray" ] && grep -q 'name_client="mihomo"' "$initd_file"; then sed -i 's/name_client="mihomo"/name_client="xray"/' "$initd_file" fi fi add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null ; then "$initd_file" restart on >/dev/null 2>&1 fi # Удаляем временные файлы delete_tmp sleep 2 smart_clear echo echo -e " ${green}Установка XKeen завершена!${reset}" if [ "$xray_installed" = "installed" ] || [ "$mihomo_installed" = "installed" ]; then if grep -q 'name_client="xray"' "$initd_file"; then echo -e " 1. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'" echo -e " 2. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 3. ${green}Enjoy!${reset}" echo elif grep -q 'name_client="mihomo"' "$initd_file"; then echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'" echo -e " 2. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 3. ${green}Enjoy!${reset}" echo fi fi if [ "$xray_installed" = "installed" ] && [ "$mihomo_installed" = "installed" ]; then if grep -q 'name_client="xray"' "$initd_file"; then echo -e " Если хотите переключить XKeen на ядро ${yellow}Mihomo${reset}" echo echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'" echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -mihomo${reset}" echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 4. ${green}Enjoy!${reset}" elif grep -q 'name_client="mihomo"' "$initd_file"; then echo -e " Если хотите переключить XKeen на ядро ${yellow}Xray${reset}" echo echo -e " 1. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'" echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -xray${reset}" echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 4. ${green}Enjoy!${reset}" fi echo fi echo -e " Для вывода Справки выполните ${yellow}xkeen -h${reset}" ;; -io) # Установка XKeen OffLine smart_clear echo check_keen_mode if [ -n "$keen_mode" ]; then echo -e " ${red}Ошибка${reset}: Установка XKeen возможна только на Keenetic в режиме ${green}роутера${reset}" exit 1 fi echo " Установка XKeen OffLine" init_directories xkeen_set_info logs_cpu_info_console case "$architecture" in arm64-v8a|mips32le|mips32) ;; *) exit 1 ;; esac if [ -f "$install_dir/xray" ]; then chmod +x "$install_dir/xray" elif [ -f "$install_dir/mihomo" ]; then chmod +x "$install_dir/mihomo" if [ -f "$install_dir/yq" ]; then chmod +x "$install_dir/yq" else echo -e " ${red}Не найден${reset} парсер конфигурационных файлов Mihomo - Yq" exit 1 fi else smart_clear echo echo -e " ${red}Не найдено ядро проксирования xray или mihomo${reset}" echo echo -e " Если планируете использовать ядро xray, поместите бинарник ${yellow}xray${reset}\n архитектуры ${green}$architecture${reset} в директорию /opt/sbin/ и начните установку снова" echo -e " Страница загрузок xray: ${yellow}https://github.com/XTLS/Xray-core/releases/latest${reset}" echo echo -e " Если планируете использовать ядро mihomo, поместите бинарники ${yellow}mihomo${reset} и ${yellow}yq${reset}\n архитектуры ${green}$architecture${reset} в директорию /opt/sbin/ и начните установку снова" echo -e " Страница загрузок mihomo: ${yellow}https://github.com/MetaCubeX/mihomo/releases/latest${reset}" echo -e " Страница загрузок yq: ${yellow}https://github.com/mikefarah/yq/releases/latest${reset}" echo exit 1 fi if [ -f "$install_dir/xray" ]; then install_configs if [ ! -d "$geo_dir" ]; then mkdir -p "$geo_dir" fi smart_clear delete_register_xray info_xray info_version_xray echo echo -e " Выполняется регистрация ${yellow}Xray${reset}" register_xray_list logs_register_xray_list_info_console register_xray_control logs_register_xray_control_info_console register_xray_status logs_register_xray_status_info_console fi if [ -f "$install_dir/mihomo" ]; then add_mihomo_config delete_register_mihomo info_mihomo info_version_mihomo info_version_yq echo echo -e " Выполняется регистрация ${yellow}Mihomo${reset}" register_mihomo_list logs_register_mihomo_list_info_console register_mihomo_control logs_register_mihomo_control_info_console register_mihomo_status logs_register_mihomo_status_info_console register_yq_list logs_register_yq_list_info_console register_yq_control logs_register_yq_control_info_console register_yq_status logs_register_yq_status_info_console sleep 2 fi smart_clear delete_register_xkeen echo echo -e " Выполняется регистрация ${yellow}XKeen${reset}" register_xkeen_list logs_register_xkeen_list_info_console register_xkeen_control logs_register_xkeen_control_info_console register_xkeen_status logs_register_xkeen_status_info_console migrate_ports_from_initd register_xkeen_initd create_xkeen_cfg fixed_register_packages smart_clear choice_autostart_xkeen add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null ; then "$initd_file" restart on >/dev/null 2>&1 fi # Удаляем временные файлы delete_tmp sleep 2 smart_clear echo echo -e " ${green}Установка XKeen завершена!${reset}" echo echo -e " Для использования ядра '${yellow}$Xray${reset}'" echo -e " 1. Поместите необходимые геофайлы в директорию '${yellow}$geo_dir/${reset}'" echo -e " 2. Настройте конфигурацию Xray по пути '${yellow}$xray_conf_dir/${reset}'" echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 4. ${green}Enjoy!${reset}" echo echo -e " Для использования ядра ${yellow}Mihomo${reset}" echo -e " 1. Настройте конфигурацию Mihomo в файле '${yellow}$mihomo_conf_dir/config.yaml${reset}'" echo -e " 2. Переключите ядро проксирования командой ${yellow}xkeen -mihomo${reset}" echo -e " 3. Запустите XKeen командой ${yellow}xkeen -start${reset}" echo -e " 4. ${green}Enjoy!${reset}" echo echo -e " Для вывода Справки выполните ${yellow}xkeen -h${reset}" ;; -ug) # Обновление баз GeoFile/GeoIPSET test_connection test_github sleep 2 smart_clear echo echo " Обновление установленных баз GeoFile/GeoIPSET" info_geosite info_geoip if [ "$update_refilter_geosite" = "true" ] || \ [ "$update_v2fly_geosite" = "true" ] || \ [ "$update_zkeen_geosite" = "true" ] || \ [ "$update_refilter_geoip" = "true" ] || \ [ "$update_v2fly_geoip" = "true" ] || \ [ "$update_zkeenip_geoip" = "true" ]; then echo install_geosite install_geoip install_geoipset update if pidof xray >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi echo -e " Обновление установленных баз GeoFile/GeoIPSET ${green}завершено${reset}" else echo -e " ${red}Не обнаружены${reset} базы GeoFile/GeoIPSET для обновления" fi ;; -uk) # Обновление XKeen test_connection test_github sleep 2 smart_clear echo echo " Проверка обновлений XKeen" xkeen_info xkeen_set_info if [ "$xkeen_build" != "Stable" ]; then download_func="download_xkeen_dev" else if [ "$info_compare_xkeen" = "actual" ]; then echo " Нет доступных обновлений XKeen" exit 0 else echo -e " Найдена новая версия ${yellow}XKeen${reset}" download_func="download_xkeen" fi fi backup_xkeen "$download_func" install_xkeen # Перезапуск скрипта grep -E "^\s*-uk_post_update\s*\)" "$0" > /dev/null && exec sh "$0" -uk_post_update ;; -uk_post_update) . "/opt/sbin/.xkeen/import.sh" init_directories xkeen_info xkeen_set_info info_packages install_packages echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}XKeen${reset}" delete_register_xkeen logs_delete_register_xkeen_info_console echo -e " Выполняется регистрация новой версии ${yellow}XKeen${reset}" register_xkeen_list logs_register_xkeen_list_info_console register_xkeen_control logs_register_xkeen_control_info_console register_xkeen_status logs_register_xkeen_status_info_console register_cron_initd migrate_ports_from_initd register_xkeen_initd create_xkeen_cfg choice_cancel_cron_select=true update_cron_geofile_task fixed_register_packages if pidof xray >/dev/null || pidof mihomo >/dev/null ; then "$initd_file" restart on >/dev/null 2>&1 fi delete_tmp echo -e " Обновление XKeen ${green}выполнено${reset}" ;; -ux) # Обновление или установка ядра Xray test_connection test_github sleep 2 . "/opt/sbin/.xkeen/01_info/03_info_cpu.sh" status_file="/opt/lib/opkg/status" info_cpu smart_clear info_xray info_version_xray [ "$xray_installed" = "installed" ] && echo -e " В роутере установлен Xray версии ${yellow}$xray_current_version${reset}" && echo download_xray if [ -z "$bypass_xray" ]; then install_xray if [ "$xray_installed" = "installed" ]; then echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}Xray${reset}" delete_register_xray logs_delete_register_xray_info_console info_version_xray echo -e " Выполняется регистрация новой версии ${yellow}Xray${reset}" register_xray_list logs_register_xray_list_info_console register_xray_control logs_register_xray_control_info_console register_xray_status logs_register_xray_status_info_console sleep 2 if pidof xray >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi echo echo -e " Обновление ядра Xray ${green}выполнено${reset}" else xray_installed="installed" info_version_xray if [ -f "$install_dir/xray" ]; then install_configs if [ ! -d "$geo_dir" ]; then mkdir -p "$geo_dir" fi delete_register_xray echo echo -e " Выполняется регистрация ${yellow}Xray${reset}" register_xray_list logs_register_xray_list_info_console register_xray_control logs_register_xray_control_info_console register_xray_status logs_register_xray_status_info_console sleep 2 smart_clear echo echo -e " Установка ядра ${yellow}Xray${reset} ${green}выполнена${reset}" fi fi fi fixed_register_packages delete_tmp ;; -um) # Обновление или установка ядра Mihomo test_connection test_github sleep 2 . "/opt/sbin/.xkeen/01_info/03_info_cpu.sh" status_file="/opt/lib/opkg/status" info_cpu smart_clear info_mihomo info_version_mihomo mihomo_was_installed="$mihomo_installed" [ "$mihomo_installed" = "installed" ] && echo -e " В роутере установлен Mihomo версии ${yellow}$mihomo_current_version${reset}" && echo if ! download_mihomo; then delete_tmp exit 1 fi if [ -z "$bypass_mihomo" ]; then install_mihomo info_mihomo if [ "$mihomo_installed" != "installed" ]; then delete_tmp echo -e " ${red}Ошибка${reset}: Mihomo не установлен, так как отсутствует обязательный Yq" exit 1 elif [ "$mihomo_was_installed" = "installed" ]; then echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}Mihomo${reset}" delete_register_mihomo logs_delete_register_mihomo_info_console logs_delete_register_yq_info_console info_version_mihomo info_version_yq echo -e " Выполняется регистрация новой версии ${yellow}Mihomo${reset}" register_mihomo_list logs_register_mihomo_list_info_console register_mihomo_control logs_register_mihomo_control_info_console register_mihomo_status logs_register_mihomo_status_info_console register_yq_list logs_register_yq_list_info_console register_yq_control logs_register_yq_control_info_console register_yq_status logs_register_yq_status_info_console if pidof mihomo >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi echo echo -e " Обновление ядра ${yellow}Mihomo${reset} ${green}выполнено${reset}" else info_version_mihomo info_version_yq add_mihomo_config delete_register_mihomo echo echo -e " Выполняется регистрация ${yellow}Mihomo${reset}" register_mihomo_list logs_register_mihomo_list_info_console register_mihomo_control logs_register_mihomo_control_info_console register_mihomo_status logs_register_mihomo_status_info_console register_yq_list logs_register_yq_list_info_console register_yq_control logs_register_yq_control_info_console register_yq_status logs_register_yq_status_info_console sleep 2 smart_clear echo echo -e " Установка ядра ${yellow}Mihomo${reset} ${green}выполнена${reset}" fi fi fixed_register_packages delete_tmp ;; -ugc) # Создать или изменить существующюю задачу автообновления баз GeoFile/GeoIPSET info_cron smart_clear echo -e " Создание или изменение задачи автообновления баз ${yellow}GeoFile/GeoIPSET${reset}" choice_update_cron update_cron_geofile_task choice_cron_time install_cron delete_tmp echo -e " Создание или изменение задачи автообновления баз GeoFile/GeoIPSET ${green}выполнено${reset}" ;; -ri) # Пересоздать файл автозапуска XKeen smart_clear "$initd_file" stop >/dev/null 2>&1 [ -e "$initd_file" ] && rm -f "$initd_file" echo -e " Создание файла автозапуска ${yellow}XKeen${reset}" sleep 1 migrate_ports_from_initd register_xkeen_initd logs_register_xkeen_initd_info_console echo echo -e " Создание файла автозапуска XKeen ${green}выполнено${reset}" echo -e " Если конфигурация настроена, то можете запустить проксирование командой '${yellow}xkeen -start${reset}'" ;; -dgc) # Удалить задачу автообновления баз GeoFile/GeoIPSET smart_clear echo choice_for_remove="задачу автообновления баз GeoFile/GeoIPSET" choice_remove info_cron smart_clear echo echo -e " Удаление задачи автообновления баз ${yellow}GeoFile/GeoIPSET${reset}" delete_cron_geofile logs_delete_cron_geofile_info_console delete_tmp echo -e " Удаление задачи автообновления баз GeoFile/GeoIPSET ${green}выполнено${reset}" ;; -dx) # Удалить Xray smart_clear echo choice_for_remove="Xray" choice_remove smart_clear echo command -v xray >/dev/null 2>&1 || { echo -e " Xray ${red}не установлен${reset}"; exit 1; } echo -e " Удаление ${yellow}Xray${reset}" "$initd_file" stop >/dev/null 2>&1 opkg remove xray_s rm -f "$install_dir/xray" echo echo -e " Удаление ${yellow}конфигурационных файлов Xray${reset}" delete_configs logs_delete_configs_info_console echo echo -e " Удаление Xray ${green}выполнено${reset}" ;; -dm) # Удалить Mihomo smart_clear echo choice_for_remove="Mihomo" choice_remove smart_clear echo command -v mihomo >/dev/null 2>&1 || { echo -e " Mihomo ${red}не установлен${reset}"; exit 1; } echo -e " Удаление ${yellow}Mihomo${reset}" "$initd_file" stop >/dev/null 2>&1 opkg remove mihomo_s opkg remove yq_s rm -f "$install_dir/mihomo" "$install_dir/yq" rm -rf "$mihomo_conf_dir" echo echo -e " Удаление Mihomo ${green}выполнено${reset}" ;; -dk) # Удалить XKeen smart_clear echo choice_for_remove="XKeen" choice_remove smart_clear echo echo -e " Удаление ${yellow}XKeen${reset}" opkg remove xkeen delete_tmp smart_clear echo delete_all smart_clear echo echo -e " Удаление XKeen ${green}выполнено${reset}" echo echo -e " Установить ${yellow}XKeen${reset} заново можно командами:" echo echo -e " ${green}opkg update && opkg upgrade && opkg install curl tar && cd /tmp${reset}" echo -e " ${green}sh -c \"\$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)\"${reset}" ;; -dgi) # Удалить GeoIP smart_clear echo choice_for_remove="GeoIP" choice_remove smart_clear echo echo -e " Удаление всех баз ${yellow}GeoIP${reset}" delete_geoip_key logs_delete_geoip_info_console echo echo -e " Удаление всех баз GeoIP ${green}выполнено${reset}" ;; -dgs) # Удалить GeoSite smart_clear echo choice_for_remove="GeoSite" choice_remove smart_clear echo echo -e " Удаление всех баз ${yellow}GeoSite${reset}" delete_geosite_key logs_delete_geosite_info_console echo echo -e " Удаление всех баз GeoSite ${green}выполнено${reset}" ;; -remove) # Полная деинсталляция XKeen smart_clear choice_for_remove="XKeen полностью со всеми зависимостями" choice_remove info_cron smart_clear echo echo -e " Удаление задачи автообновления баз ${yellow}GeoFile${reset}" delete_cron_geofile logs_delete_cron_geofile_info_console echo echo -e " Удаление задачи автообновления баз GeoFile ${green}выполнено${reset}" sleep 2 # Удаление GeoSite's smart_clear echo echo -e " Удаление всех баз ${yellow}GeoSite${reset}" delete_geosite_key logs_delete_geosite_info_console echo -e " Удаление всех баз GeoSite ${green}выполнено${reset}" sleep 2 # Удаление GeoIP's smart_clear echo echo -e " Удаление всех баз ${yellow}GeoIP${reset}" delete_geoip_key logs_delete_geoip_info_console echo -e " Удаление всех баз GeoIP ${green}выполнено${reset}" sleep 2 # Удаление GeoIPSET smart_clear echo echo -e " Удаление ${yellow}GeoIPSET${reset}" delete_geoipset_key logs_delete_geoipset_info_console echo -e " Удаление GeoIPSET ${green}выполнено${reset}" sleep 2 # Удаление файлов конфигурации Xray smart_clear echo echo -e " Удаление ${yellow}конфигурационных файлов Xray${reset}" delete_configs logs_delete_configs_info_console echo echo -e " Удаление конфигурационных файлов Xray ${green}выполнено${reset}" sleep 2 # Удаление Xray smart_clear echo echo -e " ${yellow}Удаление${reset} Xray" "$initd_file" stop >/dev/null 2>&1 opkg remove xray_s rm -f "$install_dir/xray" rm -rf "/opt/etc/xray" echo echo -e " Удаление Xray ${green}выполнено${reset}" sleep 2 # Удаление Mihomo smart_clear echo echo -e " ${yellow}Удаление${reset} Mihomo" opkg remove mihomo_s opkg remove yq_s rm -f "$install_dir/mihomo" "$install_dir/yq" rm -rf "$mihomo_conf_dir" echo echo -e " Удаление Mihomo ${green}выполнено${reset}" sleep 2 # Удаление XKeen smart_clear echo echo -e " Удаление ${yellow}XKeen${reset}" opkg remove xkeen delete_tmp smart_clear delete_all smart_clear echo echo -e " Полная деинсталляция ${yellow}XKeen${reset} и всех зависимостей ${green}выполнена${reset}" echo echo -e " Установить ${yellow}XKeen${reset} заново можно командами:" echo echo -e " ${green}opkg update && opkg upgrade && opkg install curl tar && cd /tmp${reset}" echo -e " ${green}sh -c \"\$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)\"${reset}" ;; -k) # Переустановка XKeen . "/opt/sbin/.xkeen/01_info/03_info_cpu.sh" status_file="/opt/lib/opkg/status" xkeen_info xkeen_set_info smart_clear echo echo -e " Переустановка ${yellow}XKeen${reset}" choice_redownload_xkeen if [ -n "$redownload_xkeen" ]; then if [ "$xkeen_build" = "Stable" ]; then download_func="download_xkeen" else download_func="download_xkeen_dev" fi test_connection test_entware test_github sleep 2 "$download_func" else opkg update >/dev/null 2>&1 info_packages install_packages fi echo install_xkeen # Перезапуск скрипта grep -E "^\s*-k_post_install\s*\)" "$0" > /dev/null && exec sh "$0" -k_post_install ;; -k_post_install) . "/opt/sbin/.xkeen/import.sh" init_directories xkeen_info xkeen_set_info info_packages install_packages echo -e " Выполняется отмена регистрации предыдущей версии ${yellow}XKeen${reset}" delete_register_xkeen logs_delete_register_xkeen_info_console echo -e " Выполняется регистрация новой версии ${yellow}XKeen${reset}" register_xkeen_list logs_register_xkeen_list_info_console register_xkeen_control logs_register_xkeen_control_info_console register_xkeen_status logs_register_xkeen_status_info_console register_cron_initd migrate_ports_from_initd register_xkeen_initd create_xkeen_cfg choice_cancel_cron_select=true update_cron_geofile_task fixed_register_packages if pidof xray >/dev/null || pidof mihomo >/dev/null ; then "$initd_file" restart on >/dev/null 2>&1 fi delete_tmp echo echo -e " Переустановка XKeen ${green}выполнена${reset}" ;; -g) # Установка баз GeoFile command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; } test_connection test_github sleep 2 smart_clear info_geosite info_geoip choice_geosite delete_geosite install_geosite sleep 2 smart_clear choice_geoip delete_geoip install_geoip sleep 2 smart_clear echo echo -e " Установка баз GeoFile ${green}выполнена${reset}" ;; -gips) # Установка GeoIPSET test_connection test_github smart_clear install_geoipset init ;; -dgips) # Удаление GeoIPSET smart_clear delete_geoipset ;; -kb) # Резервное копирование XKeen echo -e " Создание резервной копии ${yellow}XKeen${reset}" info_version_xkeen manual_backup="on" backup_xkeen ;; -kbr) # Восстановление XKeen из резервной копии echo -e " Восстановление ${yellow}XKeen${reset} из резервной копии" restore_backup_xkeen ;; -xb) # Резервное копирование конфигурации Xray command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; } echo -e " Создание резервной копии ${yellow}конфигурации Xray${reset}" backup_configs_xray ;; -xbr) # Восстановление конфигурации Xray из резервной копии command -v xray >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Xray"; exit 1; } echo -e " Восстановление ${yellow}конфигурации Xray${reset} из резервной копии" restore_backup_configs_xray ;; -mb) # Резервное копирование конфигурации Mihomo command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Mihomo"; exit 1; } echo -e " Создание резервной копии ${yellow}конфигурации Mihomo${reset}" backup_configs_mihomo ;; -mbr) # Восстановление конфигурации Mihomo из резервной копии command -v mihomo >/dev/null 2>&1 || { echo -e " ${red}Не обнаружено${reset} ядро проксирования Mihomo"; exit 1; } echo -e " Восстановление ${yellow}конфигурации Mihomo${reset} из резервной копии" restore_backup_configs_mihomo ;; -tp) # Показать прослушиваемые порты echo " Определение прослушиваемых портов" tests_ports_client ;; -v|-version) # Показать версию XKeen echo -e " Версия ${yellow}XKeen $xkeen_current_version $xkeen_build${reset} (время сборки: ${light_blue}$build_timestamp${reset})" info_xray info_version_xray info_mihomo info_version_mihomo info_version_yq if [ -f "$install_dir/xray" ] && grep -q 'name_client="xray"' "$initd_file"; then echo -e " Ядро проксирования Xray версии ${yellow}$xray_current_version${reset}" elif [ -f "$install_dir/mihomo" ] && grep -q 'name_client="mihomo"' "$initd_file"; then echo -e " Ядро проксирования Mihomo версии ${yellow}$mihomo_current_version${reset}" echo -e " Парсер конфигурационных файлов Yq версии ${yellow}$yq_current_version${reset}" else echo -e " Ядро проксирования ${red}не установлено${reset}" fi ;; -about) # О программе smart_clear about_xkeen ;; -ad|-donate) # Поддержать разработчиков smart_clear author_donate ;; -af|-feedback) # Обратная связь smart_clear author_feedback ;; -h|-help) # Помощь smart_clear help_xkeen ;; -start) # Запуск XKeen add_chmod_init "$initd_file" start on ;; -stop) # Остановка XKeen add_chmod_init "$initd_file" stop ;; -restart) # Перезапуск XKeen add_chmod_init "$initd_file" restart on ;; -status) # Состояние XKeen "$initd_file" status ;; -auto) # Смена режима запуска XKeen action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_autostart_xkeen "$action" add_chmod_init ;; -fd) # Смена режима контроля файловых дескрипторов action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_file_descriptors "$action" ;; -cfd) # Проверка количества открытых файловых дескрипторов action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear check_file_descriptors ;; -ap) # Добавить порт проксирования shift add_ports_donor "$*" sleep 2 add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi ;; -dp) # Удалить порт проксирования shift dell_ports_donor "$*" sleep 2 add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi ;; -cp) # Получить список портов проксирования get_ports_donor ;; -ape) # Добавить порт-исключение проксирования shift add_ports_exclude "$*" sleep 2 add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi ;; -dpe) # Удалить порт-исключение проксирования shift dell_ports_exclude "$*" sleep 2 add_chmod_init if pidof xray >/dev/null || pidof mihomo >/dev/null; then "$initd_file" restart on >/dev/null 2>&1 fi ;; -cpe) # Получить список портов, исключёных из проксирования get_ports_exclude ;; -modules) # Obsolete smart_clear echo migration_modules ;; -delmodules) # Obsolete smart_clear echo remove_modules ;; -d) # Установка задержки автозапуска в секундах shift delay_autostart "$1" add_chmod_init ;; -diag) # Диагностика XKeen location_entware_storage smart_clear diagnostic ;; -channel) # Смена канала обновлений XKeen (Stable/Dev) smart_clear choice_channel_xkeen change_channel_xkeen ;; -xray) # Смена ядра проксирования на Xray choice_xray_core ;; -mihomo) # Смена ядра проксирования на Mihomo choice_mihomo_core ;; -ipv6) # Включение/отключение IPv6 в KeeneticOS action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_ipv6_support "$action" ;; -dns) # Включение/отключение перенаправления DNS в прокси action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear [ -n "$action" ] || warn_proxy_dns change_proxy_dns "$action" ;; -pr) # Включение/отключение проксирования трафика Entware action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_proxy_router "$action" ;; -extmsg) # Включение/отключение расширенных сообщений запуска XKeen action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_extended_msg "$action" ;; -cbk) # Включение/отключение резервного копирования XKeen при обновлении action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_backup_xkeen "$action" ;; -aghfix) # Включение/отключение исправления для AdGuard Home action=$(get_state_arg "$2") [ -n "$action" ] && shift smart_clear change_aghfix_xkeen "$action" ;; -toff) # Отключение таймаута при меделенной загрузке с GitHub : ;; *) echo -e " Неизвестный ключ: ${red}$1${reset}" echo -e " Список доступных ключей: ${yellow}xkeen -h${reset}" ;; esac shift done ================================================ FILE: test/README.md ================================================ ## XKeen 2.0 Beta > [!NOTE] > Это версия из канала разработки. Она регулярно дорабатывается, содержит новейшие функции, возможности и исправления, но может иметь не выявленные ошибки. Если столкнулись с проблемой - обязательно обновитесь командой `xkeen -uk`, возможно ошибка уже известна и исправлена. Если же проблема сохранилась, выполните `xkeen -diag` и покажите диагностический отчёт в телеграм-чате https://t.me/+8Cvh7oVf6cE0MWRi, подробно описав возникшую проблему ### Изменения - Реализована работа с пользовательскими политиками 1 - Доработан модуль работы с DNS 2 - Реализована работа с IPSET и возможность исключать из проксирования IP-подсети России (параметры `-gips`, `-dgips`) 3 - Добавлена поддержка [DSCP-меток QoS](https://jameszero.net/4509.htm) (`62` - исключение из проксирования, `63` - проксирование) - Добавлена возможность проксирования трафика Entware (параметр `-pr`) 4 - Режим работы Mixed переименован в Hybrid - Порт 443 в интерфейсе роутера теперь требуется освобождать только для режима TProxy, пользователям Hybrid (Mixed) режима это делать не обязательно - На роутерах Keenetic Skipper 4G ( KN-2910) и Keenetic 4G (KN-1212) после установки теперь не требуется подменять бинарник прокси-клиента, сразу устанавливается совместимый - Версия утилиты yq зафиксирована для стабильности - Порты проксирования и исключения полностью перенесены в `port_proxying.lst` и `port_exclude.lst`. Параметры `-ap`, `-dp`, `-cp`, `-ape`, `-dpe`, `-cpe` теперь работают только с этими файлам. Переменные `port_donor` и `port_exclude` больше не используются - Добавлен параметр `-toff` для отключения таймаута загрузок при замедлении GitHub. Пример использования: `xkeen -i -toff` - Пользовательский прокси для загрузок с GitHub теперь можно задать в параметре `gh_proxy` конфигурационного файла `xkeen.json` - В файл `01_info_variable.sh` добавлена переменная `curl_extra` для дополнительных параметров, например, для загрузок через socks5 inbound - DNS-запросы клиентов политик XKeen в журнале AdGuard Home теперь могут отображаться со своими IP-адресами, а не с IP роутера (параметр `-aghfix`) - Предусмотрен вывод стандартной либо расширенной информации при запуске прокси-клиента (параметр `-extmsg`) - XKeen переведён на использование актуальных модулей Netfilter из прошивки - При включении/отключении контроля файловых дескрипторов теперь не требуется перезагружать роутер, достаточно перезапустить XKeen - Задержка автозапуска XKeen теперь не влияет на запуск остальных пакетов, установленных в Entware - Интерактивные параметры `-auto`, `-fd`, `-dns`, `-pr`, `-ipv6`, `-extmsg`, `-cbk`, `-aghfix` теперь умеют работать в автоматическом режиме (`-dns on`, `-auto off`,... ), а так же поддерживают перезапуск XKeen (`-dns on -restart`), если это необходимо - Доработан сценарий установки. Корректное определение режима работы XKeen, не зависящее он имен входящих тегов `redirect` и `tproxy` [@UltraFeed](https://github.com/UltraFeed) - XKeen теперь корректно работает со встроенной политикой Кинетика "Без доступа в интернет", часто используемой при настройке родительского контроля. При создании расписания, доступ в интернет прекращается и восстанавливается согласно заданных интервалов времени [#53](https://github.com/jameszeroX/XKeen/pull/53) - [@kittylabassistant](https://github.com/kittylabassistant) - Доработки согласно PR [#32](https://github.com/jameszeroX/XKeen/pull/32) - [@kittylabassistant](https://github.com/kittylabassistant) - Доработки согласно PR [#33](https://github.com/jameszeroX/XKeen/pull/33), [#34](https://github.com/jameszeroX/XKeen/pull/34), [#35](https://github.com/jameszeroX/XKeen/pull/35), [#36](https://github.com/jameszeroX/XKeen/pull/36), [#37](https://github.com/jameszeroX/XKeen/pull/37), [#38](https://github.com/jameszeroX/XKeen/pull/38), [#39](https://github.com/jameszeroX/XKeen/pull/39), [#40](https://github.com/jameszeroX/XKeen/pull/40), [#41](https://github.com/jameszeroX/XKeen/pull/41), [#42](https://github.com/jameszeroX/XKeen/pull/42), [#43](https://github.com/jameszeroX/XKeen/pull/43), [#44](https://github.com/jameszeroX/XKeen/pull/44), [#45](https://github.com/jameszeroX/XKeen/pull/45), [#46](https://github.com/jameszeroX/XKeen/pull/46), [#47](https://github.com/jameszeroX/XKeen/pull/47), [#48](https://github.com/jameszeroX/XKeen/pull/48), [#49](https://github.com/jameszeroX/XKeen/pull/49), [#50](https://github.com/jameszeroX/XKeen/pull/50), [#51](https://github.com/jameszeroX/XKeen/pull/51), [#52](https://github.com/jameszeroX/XKeen/pull/52) - [@oviron](https://github.com/oviron) - Удалены неиспользуемые параметры запуска `-rrk`, `-rrx`, `-rrm`, `-drk`, `-drx`, `-drm` - Рефакторинг кода скриптов 1 В роутинге, используя параметр `source`, вы можете определить разные правила маршрутизации для разных устройств, а пользовательские политики дают возможность задать для них разные порты проксирования, например, для торрент-клиента можно сделать политику с `80,443` портами проксирования, для телефонов политику с портами `80,443,596:599,1400,3478,5222`, а для игровых устройств с более широким набором портов. Для работы с пользовательскими политиками, они должы быть определены в конфигурационном файле `/opt/etc/xkeen/xkeen.json`, а также созданы в интерфейсе роутера с теми же именами. Пользовательские политики подключаются только если в интерфейсе роутера создана дефолтная политика `xkeen`, иначе пользовательские политики игнорируются и проксирование запускается для всех клиентов роутера. В пользовательских политиках, в отличие от политики `xkeen`, не проверяется наличие обязательных портов проксирования `80,443`, и вы можете формировать произвольный их список. Пример конфигурационного файла (допустимы любые имена пользовательских политик): ``` { "xkeen": { "gh_proxy": "", "policy": [ { "name": "xkeen0", "port": "" }, { "name": "xkeen1", "port": "80,443,596:599,1400,3478,5222" }, { "name": "xkeen2", "port": "!7777,8888:9999" } ] } } ``` - Политика `xkeen0` - проксирование на всех портах - Политика `xkeen1` - проксирование на перечисленных портах - Политика `xkeen2` - проксирование на всех портах, кроме перечисленных 2 При включенном перехвате и проксировании DNS, корректная работа устройств вне политик XKeen возможна только, когда они находятся не в "Политике по умолчанию", а в кастомной политике с произвольным именем и в роутере не игнорируется DNS провайдера, либо добавлен любой внешний не шифрованный DNS (даже при использовании AdGuardHome и отключении прошивочного резолвера командой `opkg dns-override`) 3 XKeen создаёт и использует 3 сета IPv4 и 3 сета IPv6 IPSET - `user_exclude`, `user_exclude6` - наполняются пользовательскими исключениями IP из файла `ip_exclude.lst` - `geo_exclude`, `geo_exclude6` - наполняются подсетями России из загружаемых файлов `ru_exclude_ipv4.lst` и `ru_exclude_ipv6.lst` - `ext_exclude`, `ext_exclude6` - наполняются любым сторонним приложением на ваш выбор, например, AdGuard Home или dnsmasq (см. документацию к этим приложениям). Данная возможность позволяет добавлять в исключения проксирования не IP, а доменные имена. Для совместимости со сторонними приложениями скрипт запуска XKeen переименован в S05xkeen Файлы `ru_exclude_ipv4.lst` и `ru_exclude_ipv6.lst` загружаются при установке XKeen или командой `xkeen -gips`. Обновление этих файлов выполняется одновременно с геофайлами по планировщику либо вручную запуском `xkeen -ug` 4 Проксирование трафика Entware можно использовать для обновления компонентов XKeen при недоступности GitHub, либо для любых других приложений в Entware, не связанных с XKeen. Для проксирования трафика Entware требуется установка 255 метки на все исходящие подключения кроме blackhole и dns. При использовании ядра Xray добавьте маркировку к каждому подключению в outbounds включая direct: ``` "streamSettings": { "sockopt": { "mark": 255 } } ``` При использовании ядра Mihomo добавьте маркировку глобально: ``` tproxy-port: 1181 log-level: silent ... routing-mark: 255 ``` либо к каждому proxies, proxy-providers и direct подключениям (см. документацию к Mihomo) --- В связи с добавлением нового компонента IPSET и нового диалогового окна в мастер установки XKeen, рекомендуется для обновления XKeen не использовать штатную возможность по `-uk`, а установить Beta версию поверх предыдущей. В мастере установки при этом можете пропускать уже установленные компоненты, все ваши конфиги и настройки сохранятся ### Рекомендуемый порядок установки/обновления ``` opkg update && opkg upgrade && opkg install curl tar && cd /tmp sh -c "$(curl -sSL https://raw.githubusercontent.com/jameszeroX/XKeen/main/install.sh)" ``` ### Альтернативный вариант установки/обновления ```bash opkg update && opkg upgrade && opkg install curl tar && cd /tmp sh -c "$(curl -sSL https://cdn.jsdelivr.net/gh/jameszeroX/XKeen@main/install.sh)" ``` Штатный механизм обновления тоже работает, но он рекомендуется только для опытных пользователей ### Порядок обновления с предыдущей версии форка XKeen (только для опытных пользователей) ``` xkeen -stop # остановите прокси-клиент xkeen -channel # переключитесь на канал разработки xkeen -uk # проверьте и загрузите обновление xkeen -k # выполните локальную переустановку (пункт 0) xkeen -gips # установите, если нужно, сеты IPSET xkeen -ugc # включите автообновление GeoIPSET по планировщику (опционально) xkeen -start # запустите прокси-клиент ``` Последующие запуски команды `xkeen -uk` в канале разработки каждый раз загружают и обновляют бету XKeen на актуальную версию ================================================ FILE: wiki/DNS-over-VLESS.md ================================================ # DNS-over-VLESS — направляем DNS-трафик через прокси xray > Источник: [jameszero.net/3398.htm](https://jameszero.net/3398.htm) Безопасность в интернете с каждым днём становится всё более насущной необходимостью. Утечка личных данных может обойтись слишком дорого, поэтому о своей цифровой неприкосновенности лучше позаботиться заранее. И начать стоит, например, с защиты DNS-запросов. Обычно для этого используются такие протоколы, как **DNS-over-TLS**, **DNS-over-HTTPS** и другие. Я же предлагаю рассмотреть ещё один способ — направить DNS-трафик через прокси-сервер на базе Xray, назовём этот метод **DNS-over-VLESS**. В сети немало инструкций по настройке самого Xray, но вот проброс DNS-трафика через него раскрыт недостаточно подробно. Попробуем восполнить этот пробел. ## Предварительные условия Первоначальное условие — у вас уже должен быть настроен и функционировать прокси xray VLESS с XTLS Reality по одной из многочисленных инструкций. Все действия по его "прокачке" выполняем локально на компьютере или роутере, сервер xray не трогаем. **Замечание от пользователя:** > Во многих конфигурациях серверов xray есть секция для предотвращения проблем с локальной маршрутизацией, в ней блокируется "geoip:private", куда попадает дефолтный адрес DNS-резолвера - 127.0.0.53. Для нашей задачи данное правило необходимо удалить, либо направить 127.0.0.53 выше этого правила в директ. ## Структура конфигурации Разобьём единый файл настроек xray **config.json** на файлы по разделам: **inbounds.json**, **outbounds.json**, **dns.json**, **routing.json**. Этих четырёх файлов достаточно для нашей задачи. Для реализации задуманного необходимо использовать входящее подключение (inbounds), поддерживающее UDP-протокол, например, TProxy. ## Конфигурационные файлы ### inbounds.json ```json { "inbounds": [ { "port": 1181, "protocol": "dokodemo-door", "settings": { "network": "tcp,udp", "followRedirect": true }, "sniffing": { "enabled": true, "routeOnly": true, "destOverride": ["http","tls","quic"] }, "streamSettings": { "sockopt": {"tproxy": "tproxy"} }, "tag": "tproxy" } ] } ``` ### outbounds.json ```json { "outbounds": [ { "protocol": "vless", "settings": { "address": "***.***.***.***", "port": 443, "id": "****************************", "encryption": "none", "flow": "xtls-rprx-vision" }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "serverName": "*********", "publicKey": "****************************", "shortId": "********", "spiderX": "/" }, "sockopt": { "domainStrategy": "ForceIP" } }, "tag": "proxy" }, { "protocol": "freedom", "streamSettings": { "sockopt": { "domainStrategy": "ForceIP" } }, "tag": "direct" }, { "protocol": "dns", "tag": "dns-out" } ] } ``` ### dns.json ```json { "dns": { "tag": "dns-in", "servers": [ "8.8.8.8" ], "queryStrategy": "UseIP" } } ``` ### routing.json ```json { "routing": { "rules": [ { "inboundTag": ["dns-in"], "outboundTag": "proxy" }, { "port": 53, "outboundTag": "dns-out" }, { "domain": [ "browserleaks", "ip.me" ], "outboundTag": "proxy" }, { "network": "tcp,udp", "outboundTag": "direct" } ] } } ``` ## Проверка работоспособности Настройка завершена, выполняем проверку. Если ошибок не допущено, то проксируемый ресурс **browserleaks.com** покажет DNS страны нахождения вашего VPS. Не стоит бояться использования обычных DNS в рассмотренных конфигах, их трафик будет передан на VPS-сервер транспортом VLESS с TLS-шифрованием. ## Результат В итоге результат получен, **DNS-over-VLESS** настроен и работает. Никто не прослушает ваши DNS-запросы к проксируемым сайтам и не подменит ответы по своему усмотрению. Для дополнительной защиты в интернете обратите внимание на возможность отключения WebRTC и QUIC в браузере. ================================================ FILE: wiki/FAQ.md ================================================ # FAQ по XKeen > Источник: [jameszero.net/faq-xkeen.htm](https://jameszero.net/faq-xkeen.htm) ## Введение **XKeen** — это набор скриптов для роутеров Keenetic с прокси-клиентом Xray-core/Mihomo, предназначенный для оптимизации производительности интернет-подключения и обеспечения его безопасности. **Важно!** Если после обновления или установки XKeen получаете ошибку SIGSEGV с сегментацией, откатите ядро Xray командой `xkeen -ux`. --- ## Часто задаваемые вопросы ### 0. Внезапные проблемы с XKeen **Q:** Настроил xkeen/xray, всё работало, ничего не трогал. Внезапно прокси упал. Что можно предпринять? **A:** При внезапных проблемах причина часто в устаревшем ядре Xray. Рекомендуется перейти на форк XKeen или обновить ядро другим способом. Не выбирайте последние версии — они часто требуют отдельной настройки. Общий порядок действий: - Если хостер Aeza — обращайтесь к нему, а не на форум - Роутеры mipsle-архитектуры: не используйте ядро xray 26.3.27 - Проверьте подключение к прокси через WiFi с другого устройства - Убедитесь в актуальных версиях XKeen и Xray - Измените uTLS отпечаток на `"fingerprint": "firefox"` - Подберите другой SNI (маскировочный домен) - Проверьте галку политики XKeen на провайдере - Убедитесь, что QUIC не заблокирован - Проверьте DoH/DoT серверы в роутере - Отключен ли транзит DNS запросов - Нет ли ошибок DNS в журнале - Удалён ли компонент "Протокол IPv6" - Проверьте параметр `"routeOnly": true` в inbounds - Отключите частный MAC и приватный DNS на устройствах - Удалите политику XKeen и запустите для всех клиентов - Проверьте подключение к другому прокси-серверу Если ничего не помогло, выполните `xkeen -diag` и прикрепите отчёт в телеграм-чат. ### 1. Обновление с оригинала на форк XKeen **Q:** Как правильно обновляться с XKeen 1.1.3 на форк? **A:** Рекомендуется установить форк поверх оригинала. Конфиги Xray сохранятся: ```shell opkg update && opkg upgrade && opkg install curl tar curl -OL https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz tar -xvzf xkeen.tar.gz -C /opt/sbin > /dev/null && rm xkeen.tar.gz xkeen -i ``` ### 2. Различия в замерах скорости **Q:** Почему при использовании xkeen замеры скорости сильно различаются или низкие? **A:** Причина в производительности процессора роутера и особенностях работы Xray с потоком xtls-rprx-vision. **Важно для достоверного результата:** - Не ограничивайте порты проксирования 80 и 443 - Не используйте роутинг — направьте весь трафик на VPS Xray не повторно шифрует HTTPS-трафик, но шифрует HTTP-трафик силами процессора. Различные сайты используют разные методики тестирования, поэтому результаты могут не быть корректными. **Советы для повышения скорости:** - Выполните оптимизацию сетевого стека с режимом BBR - Обязательно используйте поток **xtls-rprx-vision** - Используйте режим TProxy (самый быстрый — Redirect, но без UDP) - Не тестируйте торрентами — это неправильная практика ### 3. Низкая скорость на бюджетных моделях **Q:** На KN-3810/KN-3610 после установки XKeen скорость упала до 30-40 Мбит/с. **A:** В бюджетных моделях установлены процессоры EcoNet (EN****) с очень низкой производительностью при Xray. Исправить нельзя, но можно: - Ограничить порты проксирования 80 и 443 - Не подключать чрезмерное количество geosite/geoip баз - Не использовать компонент "Классификация трафика" ### 4. Периодические разрывы соединения **Q:** Почему при использовании xkeen меня периодически выбрасывает из SSH/RDP/VPN/звонков? **A:** Xray не предназначен для потокового трафика и может обрывать устаревшие сессии. **Решения:** - Добавьте IP-адрес критичного сервера с маской /32 в `/opt/etc/xkeen/ip_exclude.lst` - Ограничьте порты проксирования в `/opt/etc/xkeen/port_exclude.lst` - Попробуйте переключиться с Xray на Mihomo (форк XKeen поддерживает оба) ### 5. Невозможно подключиться к серверу с приложения на телефоне **Q:** Добавил телефон в политику XKeen, но не могу подключить к серверу приложением. **A:** Вы пытаетесь подключиться к прокси, будучи уже подключённым к прокси. Исключите IP сервера из проксирования, добавив в `/opt/etc/xkeen/ip_exclude.lst` с маской /32. ### 6. Сайт определяет мой провайдерский IP **Q:** Почему ChatGPT/whoer/dell определяют мой провайдерский IP, хотя ресурс добавлен в роутинг? **A:** Некоторые ресурсы используют QUIC или WebRTC, через которые возможна утечка IP. **Решения:** - Отключите WebRTC и QUIC в браузере - Заблокируйте UDP 443 правилом роутинга (TProxy/Mixed): ```json { "network": "udp", "port": "443", "outboundTag": "block" } ``` - Заблокируйте в межсетевом экране Keenetic - Некоторые сайты блокируют по языку браузера — переключите на иностранный ### 7. Как заблокировать рекламу на YouTube **Q:** Как заблокировать рекламу на Youtube? **A:** Роутингом и DNS этого не сделать — реклама вшита в видеопоток. Помогут только приложения (SmartTube, YouTube ReVanced, AdGuard) или покупка Premium. ### 8. Пропал доступ к SSH после перезагрузки **Q:** После сбоя пропал доступ к SSH на роутере. **A:** Перед установкой entware удалите компонент прошивки "Сервер SSH". Если ssh перестал работать: 1. Удалите файл `/opt/var/run/dropbear.pid` 2. Убедитесь в наличии `PORT=22` или `PORT=222` в `/opt/etc/config/dropbear.conf` 3. Перезагрузите роутер или выполните: ```shell exec sh exec /opt/etc/init.d/S51dropbear restart ``` **Для предотвращения сбоев** отредактируйте `/opt/etc/config/dropbear.conf`: ```shell PIDFILE="/var/run/dropbear.pid" ``` ### 9. ICMP пинги не проходят через прокси **Q:** Ping и tracert к ресурсам из роутинга почему-то идут напрямую. **A:** ICMP пинги работают на Сетевом уровне OSI, а прокси — на Прикладном. Они несовместимы. ### 10. Сайт tmdb не открывается **Q:** Прописал tmdb.org в роутинг, но сайт всё равно не открывается. **A:** Ресурс блокирует по GeoDNS. Решения: - Используйте DNS без EDNS Client Subnet (например, dns.sb) - В AdGuardHome включите подмену EDNS Client Subnet с IP прокси - В Keenetic создайте запись для каждого домена с IP прокси-сервера ### 11. Забыл пароль панели 3x-ui (IP 127.0.0.1) **Q:** Указал IP 127.0.0.1 в панели 3x-ui, теперь не могу зайти. **A:** Используйте PuTTY с портфорвардингом: 1. SSH → Tunnels: Source port 65345, Destination localhost:65345 2. Нажмите Add и подключитесь 3. В браузере откройте `localhost:65345/путь` (не закрывая PuTTY) ### 12. XKeen не запускается после перезагрузки **Q:** После перезагрузки XKeen не запускается или работает для всего устройства. **A:** Entware может стартовать раньше инициализации прошивки. Подберите задержку: ```shell xkeen -d 30 ``` Или добавьте скрипт `/opt/etc/init.d/S99xkeenrestart` для перезапуска после включения интернета. ### 13. AdGuardHome показывает IP роутера вместо клиента **Q:** В журнале AdGuard все клиенты имеют IP роутера, а не свои. **A:** Убедитесь в параметре `"routeOnly": true` (Xray) или `"override-destination": false` (Mihomo). Для XKeen 1.1.3.9 поместите скрипт `aghfix.sh` в `/opt/etc/ndm/netfilter.d/` и сделайте исполняемым: ```shell chmod +x /opt/etc/ndm/netfilter.d/aghfix.sh ``` Перезагрузите роутер. ### 14. Использование Wireguard/OpenConnect вместо Xray **Q:** Есть только Wireguard/OpenConnect. Можно ли использовать маршрутизацию Xray для них? **A:** Да. Определите наименование интерфейса (`ip a`), затем в `outbounds.json` добавьте: ```json { "protocol": "freedom", "streamSettings": { "sockopt": { "interface": "nwg0" } }, "tag": "newtag" } ``` В `routing.json` добавьте правило с тегом `newtag`. ### 15. Раздача интернета через XKeen клиентам Wireguard-сервера **Q:** На роутере Wireguard-сервер. Можно ли раздать через XKeen интернет его клиентам? **A:** При XKeen для всего устройства — дополнительных настроек нет. При политике найдите номер политики и интерфейса в `startup-config`, затем: ```shell ip hotspot policy Wireguard0 Policy0 system configuration save ``` Отменить: ```shell ip hotspot policy Wireguard0 permit system configuration save ``` ### 16. Удалённый доступ к SSH через серый IP **Q:** Использую KeenDNS. Можно ли получить удалённый доступ к SSH через серый IP? **A:** Поднимите SSTP VPN-сервер. К нему подключайтесь по домену KeenDNS, после чего SSH роутера доступен по 192.168.1.1:22. ### 17. Разные маршруты для разных устройств в одной политике **Q:** Можно ли настроить разные маршруты в одной политике XKeen? **A:** Для WiFi-устройств создайте несколько домашних сетей и добавьте параметр `source` в правила маршрутизации. Пример — разная маршрутизация для подсетей: ```json { "routing": { "rules": [ { "source": ["192.168.1.0/24"], "domain": ["site1.ru", "site2.ru"], "outboundTag": "vless-reality" }, { "source": ["192.168.2.0/24"], "outboundTag": "vless-reality" }, { "network": "tcp,udp", "outboundTag": "direct" } ] } } ``` Или разные маршруты по IP-адресам устройств: ```json { "routing": { "rules": [ { "source": ["192.168.1.5"], "outboundTag": "vless-reality-eu" }, { "source": ["192.168.1.6", "192.168.1.7"], "outboundTag": "vless-reality-us" } ] } } ``` ### 18. Резервное копирование и восстановление **Q:** Как выполнить резервное копирование XKeen? **A:** Создайте архив: ```shell opkg update && opkg upgrade && opkg install tar tar --exclude=entware_backup.tar.gz --exclude=*.pid --warning=no-file-changed -cvzf /opt/entware_backup.tar.gz -C /opt . ``` Для восстановления отформатируйте флешку в ext4, создайте папку `install`, поместите архив, выберите флешку в OPKG. ### 19. Интернет пропадает через несколько часов работы **Q:** Через часы работы интернет пропадает, хотя `xkeen -status` показывает работу прокси. **A:** Прокси-клиент исчерпал лимит файловых дескрипторов. Проверьте: ```shell ls -l /proc/$(pidof xray)/fd | wc -l ``` Форк XKeen с версии 1.1.3.6 контролирует fd. Включите: ```shell xkeen -fd ``` И перезагрузите роутер. ### 20. Два прокси-сервера для разных сайтов **Q:** Есть два прокси. Можно ли открывать одни сайты через первый, другие — через второй? **A:** В `outbounds.json` добавьте оба сервера с разными тегами: ```json { "outbounds": [ { "protocol": "vless", "settings": {}, "tag": "proxy1" }, { "protocol": "vless", "settings": {}, "tag": "proxy2" }, { "protocol": "freedom", "tag": "direct" } ] } ``` В `routing.json` создайте правила: ```json { "routing": { "rules": [ { "domain": ["site1.ru"], "outboundTag": "proxy1" }, { "domain": ["site2.ru"], "outboundTag": "proxy2" }, { "network": "tcp,udp", "outboundTag": "direct" } ] } } ``` ### 21. Отказоустойчивость прокси **Q:** Можно ли сделать fallback на второй прокси, если первый недоступен? **A:** Да, используйте встроенный механизм балансировки Xray. Подробности в статьях сайта об отказоустойчивом прокси-сервере. ### 22. Два провайдера — подключение через один **Q:** К роутеру подключены два провайдера. Как сделать подключение к прокси только через одного? **A:** Аналогично пункту 14. В `outbounds.json` добавьте параметр `"interface"` с указанием интерфейса. Пример для direct: ```json { "protocol": "freedom", "streamSettings": { "sockopt": { "interface": "lte_br0" } }, "tag": "direct" } ``` Пример для прокси: ```json { "protocol": "vless", "settings": {}, "streamSettings": { "network": "raw", "security": "reality", "realitySettings": {}, "sockopt": { "interface": "lte_br0" } }, "tag": "proxy" } ``` Уточните название интерфейса командой `ifconfig`. ### 23. Не работает голос в Discord/Telegram/WhatsApp **Q:** Не работают голосовые чаты/пинг 5000 etc. **A:** Discord использует UDP 50000-50030, Telegram/WhatsApp используют UDP 596:599, 1400, 3478, 5222. Чтобы голос заработал, добавьте в `routing.json` правило для этого порта, если у вас в конце все подключения идут direct: ```json { "routing": { "rules": [ { "network": "udp", "port": "50000-50030,596-599,1400,3478,5222", "outboundTag": "vless-reality" } ] } } ``` Если подключения всё в vless-reality, кроме РФ, то добавить порты в проксирование будет достаточно: ```shell xkeen -ap 80,443,2053:2096,8443,19200:19400,50000:50030,596:599,1400,3478,5222 ``` **Примечание:** Если пинг всё равно высокий или идут обрывы, то есть проблемы с маршрутизацией UDP-трафика - альтернатива: Плюсы такого решения - можно не добавлять порты проксирования 50000+ и оставить только 80,443 (на socks5 эти ограничения не распространяются), таким образом торренты у вас точно не пойдут в прокси, а может ещё стабильность и пинг в дискорде улучшатся, но это не точно) После установки Discord, запустите DiscordProxyInstaller.exe он спросит IP роутера и порт socks5-прокси, и Discord станет работать через него. Socks5-прокси, разумеется, должен быть поднят в xray на роутере (не прокси клиент Кинетика, а просто прописан в inbounds.json). для этого добавьте в inbounds.json: ```json { "port": 1080, "protocol": "mixed", "settings": {"udp": true}, "tag": "socks" }, ``` routing.json: ```json { "inboundTag": ["socks"], "outboundTag": "vless-reality", "type": "field" }, ``` ### 24. Gemini определяет страну РФ **Q:** Почему Gemini определяет страну РФ? **Причина:** Если использовать ру аккаунт google через proxy, то google помечает ip как proxy, и определяет страну как РФ. Смена аккаунта не всегда помогает. **A:** Проксировать gemini через warp-native . Дефолтный Warp xray/3x-ui не всегда отрабатывает корректно. Warp-native устанавливается на VPS. ### 25. Хочу перейти на ядро mihomo, какие rule sets использовать? **A:** Вот несколько вариантов: - rule-sets от "zxc-rv" - rule-sets от "legiz-ru" ### 26. Не работают правила mihomo, что делать? **A:** - проверяйте корректность настроек ================================================ FILE: wiki/Home.md ================================================ # XKeen **XKeen** — POSIX-shell утилита для прозрачной маршрутизации сетевого трафика через прокси-движки **Xray** и **Mihomo** на роутерах **Keenetic** / **Netcraze**. Позволяет выборочно направлять TCP/UDP-трафик отдельных клиентов через прокси, не затрагивая остальную сеть. ## Зачем нужен XKeen - **Обход блокировок** на уровне роутера: одна точка настройки для всех домашних устройств — телефоны, ТВ, консоли, ПК. - **Гибкая маршрутизация**: разные клиенты могут идти через разные прокси-серверы или напрямую, в зависимости от политик XKeen, IP-адресов и доменов. - **Защита DNS-запросов**: возможна организация DNS-over-VLESS — DNS-трафик передаётся туннелем Xray, что исключает прослушку и подмену ответов на пути от роутера до прокси. См. [DNS-over-VLESS](DNS-over-VLESS). - **DSCP-маршрутизация**: проксирование/исключение конкретных приложений по QoS-меткам Windows. См. [Маршрутизация по DSCP](Маршрутизация-по-DSCP). - **Два ядра в одном инструменте**: переключение между Xray и Mihomo через `xkeen -xray` / `xkeen -mihomo`. ## Поддерживаемые архитектуры - `arm64-v8a` — старшие модели Keenetic/Netcraze - `mips32le` — большинство моделей - `mips32` — старые модели ## Быстрый старт На роутере под Entware: ```sh opkg update && opkg upgrade && opkg install curl tar curl -OL https://github.com/jameszeroX/XKeen/releases/latest/download/xkeen.tar.gz tar -xvzf xkeen.tar.gz -C /opt/sbin > /dev/null && rm xkeen.tar.gz xkeen -i ``` Дальше следуйте интерактивному установщику: выбор ядра (Xray / Mihomo / оба), установка geofile, настройка автообновлений. ## Дальнейшие шаги - **[FAQ](FAQ)** — 22 ответа на самые частые вопросы: проблемы с производительностью, DNS, утечки IP, восстановление после сбоев, маршруты для разных подсетей. - **[DNS-over-VLESS](DNS-over-VLESS)** — пошаговая настройка проксирования DNS через VLESS. - **[Маршрутизация по DSCP](Маршрутизация-по-DSCP)** — маршрутизация приложений Windows по QoS-меткам (XKeen 2.0+). ## Поддержка и обратная связь Команды на роутере: - `xkeen -h` — встроенная справка по флагам. - `xkeen -diag` — полная диагностика. **Единственный поддерживаемый канал для отчёта о проблеме.** - `xkeen -af` — контакты разработчиков. - `xkeen -ad` — поддержать разработчиков. ================================================ FILE: wiki/_Footer.md ================================================ --- [Сайт автора форка — jameszero.net](https://jameszero.net/) · [Обсуждение на forum.keenetic.ru](https://forum.keenetic.ru/topic/16899-xkeen/) ================================================ FILE: wiki/_Sidebar.md ================================================ **Навигация** - [Главная](Home) - [FAQ](FAQ) - [DNS-over-VLESS](DNS-over-VLESS) - [Маршрутизация по DSCP](Маршрутизация-по-DSCP) ================================================ FILE: wiki/Маршрутизация-по-DSCP.md ================================================ # Маршрутизация по DSCP-меткам в XKeen > Источник: [jameszero.net/4509.htm](https://jameszero.net/4509.htm) В XKeen 2.0 появилась возможность маршрутизации по DSCP-меткам QoS. Это позволяет исключить конкретные приложения из проксирования или направить их трафик через прокси на всех портах, что полезно когда компьютер имеет ограничения на порты 80 и 443. ## Настройка в Windows ### Предварительные требования Требуется полная редакция Windows с поддержкой Group Policy. В начальных редакциях настройка возможна только через реестр. ### Этап 1: Изменение реестра Примените следующий твик реестра: ``` Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\QoS] "Do not use NLA"="1" ``` Перезагрузите компьютер после применения. ### Этап 2: Проверка параметров сетевой карты Убедитесь, что включена опция "Планировщик пакетов QoS" в настройках сетевого адаптера. ### Этап 3: Создание QoS-политики 1. Нажмите Win+R и выполните `gpedit.msc` 2. Откройте "QoS на основе политики" 3. В меню "Действие" выберите "Создать новую политику" 4. Пройдите мастер, указав имя политики, DSCP-метку, приложение, IP-адреса, протоколы и порты XKeen поддерживает метки 62 (исключение) и 63 (проксирование) по умолчанию, но их можно заменить в переменных стартового скрипта. После создания политики маршрутизация работает сразу без перезагрузки (достаточно перезапустить приложение). Каждое приложение требует отдельной политики.