├── LICENSE ├── README.md ├── README_RU.md ├── adh └── AdGuardHome.yaml ├── bot └── reverse_proxy_bot.py ├── database ├── x-ui.db └── x-ui.db_old ├── media ├── reverse_proxy_manager.png ├── reverse_proxy_manager_RU.png ├── xui.png ├── xui_rp_install.png └── xui_rp_install_RU.png ├── other ├── check_domain_ip.sh ├── flag_final.sh ├── reverse_proxy_server.sh └── x-ui.gpg ├── reverse_proxy.sh ├── reverse_proxy_bot.sh ├── reverse_proxy_random_site.sh └── warp └── xui-rp-warp.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 cortez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REVERSE_PROXY ([Russian](/README_RU.md)) 2 |

Image

3 | 4 | ----- 5 | 6 | ### Server using NGINX reverse proxy 7 | This script is designed for quick and easy setup of a reverse proxy server using NGINX. In this setup, all incoming requests are processed by NGINX, and the server functions as a reverse proxy server only if the request contains the correct path (URI). This enhances security and improves access control management. 8 | 9 | > [!IMPORTANT] 10 | > This script has been tested in a KVM virtualization environment. You will need your own domain, which needs to be bound to Cloudflare for it to work correctly. It is recommended to run the script as root on a freshly installed system. 11 | 12 | > [!NOTE] 13 | > The script is configured according to routing rules for users in Russia. 14 | 15 | ### Supported Operating Systems: 16 | 17 | | **Ubuntu** | **Debian** | **CentOS** | 18 | |------------------|-----------------|-----------------| 19 | | 24.04 LTS | 12 bookworm | Stream 9 | 20 | | 22.04 LTS | 11 bullseye | Stream 8 | 21 | | 20.04 LTS | 10 buster | 7 | 22 | 23 | ----- 24 | 25 | ### Setting up cloudflare 26 | 1. Upgrade the system and reboot the server. 27 | 2. Configure Cloudflare: 28 | - Bind your domain to Cloudflare. 29 | - Add the following DNS records: 30 | 31 | SERVER 1 32 | | Type | Name | Content | Proxy status | 33 | | ----- | ---------------- | ---------------- | ------------- | 34 | | A | example.com | your_server_ip | DNS only | 35 | | CNAME | www | example.com | DNS only | 36 | 37 | SERVER 2 38 | | Type | Name | Content | Proxy status | 39 | | ----- | ---------------- | ---------------- | ------------- | 40 | | A | nl.example.com | your_server_ip | DNS only | 41 | | CNAME | www.nl | nl.example.com | DNS only | 42 | 43 | 3. SSL/TLS settings in Cloudflare: 44 | - Go to SSL/TLS > Overview and select Full for the Configure option. 45 | - Set the Minimum TLS Version to TLS 1.3. 46 | - Enable TLS 1.3 (true) under Edge Certificates. 47 | 48 | ----- 49 | 50 | ### Includes: 51 | 52 | 1. Proxy server configuration: 53 | - Support for automatic configuration updates through subscription and JSON subscription with the ability to convert to formats for popular applications. 54 | - You must enable MUX (multiplexing TCP connections) in each client application 55 | - gRPC-TLS 56 | - XHTTP-TLS 57 | - HTTPUpgrade-TLS 58 | - Websocket-TLS 59 | - The user “flow”: “xtls-rprx-vision” must be enabled 60 | - TCP-REALITY (Steal oneself) (disconnection will result in loss of access) 61 | - TCP-TLS 62 | - Important: it is recommended to choose one suitable connection type and use it for optimal performance. You can disable all incoming connections except the one marked as STEAL. Disabling STEAL will result in losing access to the web interface, as this connection type is used for proxy management access. 63 | 2. Configuring NGINX reverse proxy on port 443. 64 | 3. Providing security: 65 | - Automatic system updates via unattended-upgrades. 66 | - Configuring Cloudflare SSL certificates with automatic updates to secure connections. 67 | - Configuring WARP to protect traffic. 68 | - Configuring UFW (Uncomplicated Firewall) for access control. 69 | - Configuring SSH, to provide the minimum required security. 70 | - Disabling IPv6 to prevent possible vulnerabilities. 71 | - Encrypting DNS queries using systemd-resolved (DoT) or AdGuard Home (Dot, DoH). 72 | - Selecting a random website template from an array. 73 | 4. Enabling BBR - improving the performance of TCP connections. 74 | 5. Optional extras: 75 | - Installing and configuring Node Exporter for system performance monitoring and integrating with Prometheus and Grafana for real-time metrics visualization. 76 | - Setting up Shell In A Box for secure, web-based SSH access to the server. 77 | - Updated SSH welcome message (motd) with useful system information, service status, and available updates. 78 | - VNStat integration for traffic monitoring, with the ability to get statistics by time. 79 | 80 | ----- 81 | 82 | ### REVERSE_PROXY_MANAGER: 83 | 84 |

Image

85 | 86 | ----- 87 | 88 | ### Help message of the script: 89 | ``` 90 | Usage: reverse-proxy [-u|--utils ] [-d|--dns ] [-a|--addu ] 91 | [-r|--autoupd ] [-b|--bbr ] [-i|--ipv6 ] [-w|--warp ] 92 | [-c|--cert ] [-m|--mon ] [-l|--shell ] [-n|--nginx ] 93 | [-p|--panel ] [--custom ] [-f|--firewall ] [-s|--ssh ] 94 | [-t|--tgbot ] [-g|--generate ] [-x|--skip-check ] [-o|--subdomain ] 95 | [--update] [-h|--help]" 96 | 97 | -u, --utils Additional utilities (default: true) 98 | -d, --dns DNS encryption (default: true) 99 | -a, --addu User addition (default: true) 100 | -r, --autoupd Automatic updates (default: true) 101 | -b, --bbr BBR (TCP Congestion Control) (default: true) 102 | -i, --ipv6 Disable IPv6 support (default: true) 103 | -w, --warp Warp (default: false) 104 | -c, --cert Certificate issuance for domain (default: true) 105 | -m, --mon Monitoring services (node_exporter) (default: false) 106 | -l, --shell Shell In A Box installation (default: false) 107 | -n, --nginx NGINX installation (default: true) 108 | -p, --panel Panel installation for user management (default: true) 109 | --custom Custom JSON subscription (default: true) 110 | -f, --firewall Firewall configuration (default: true) 111 | -s, --ssh SSH access (default: true) 112 | -t, --tgbot Telegram bot integration (default: false) 113 | -g, --generate Generate a random string for configuration (default: true) 114 | -x, --skip-check Disable the check functionality (default: false) 115 | -o, --subdomain Support for subdomains (default: false) 116 | --update Update version of Reverse-proxy manager 117 | -h, --help Display this help message 118 | ``` 119 | 120 | ### Installation of REVERSE_PROXY: 121 | 122 | To begin configuring the server, simply run the following command in a terminal: 123 | ```sh 124 | bash <(curl -Ls https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/refs/heads/main/reverse_proxy.sh) 125 | ``` 126 | 127 | ### Installing a random template for the website: 128 | ```sh 129 | bash <(curl -Ls https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/refs/heads/main/reverse_proxy_random_site.sh) 130 | ``` 131 | 132 | The script will then prompt you for the necessary configuration information: 133 | 134 |

Image

135 | 136 | ### Note: 137 | - Once the configuration is complete, the script will display all the necessary links and login information for the administration panel. 138 | - All configurations can be modified as needed due to the flexibility of the settings. 139 | 140 | ----- 141 | 142 | > [!IMPORTANT] 143 | > This repository is intended solely for educational purposes and for studying the principles of reverse proxy servers and network security. The script demonstrates the setup of a proxy server using NGINX for reverse proxy, traffic management, and attack protection. 144 | > 145 | >We strongly remind you that using this tool to bypass network restrictions or censorship is illegal in certain countries that have laws regulating the use of technologies to circumvent internet restrictions. 146 | > 147 | >This project is not intended for use in ways that violate information protection laws or interfere with censorship mechanisms. We take no responsibility for any legal consequences arising from the use of this script. 148 | > 149 | >Use this tool/script only for demonstration purposes, as an example of reverse proxy operation and data protection. We strongly recommend removing the script after reviewing it. Further use is at your own risk. 150 | > 151 | >If you are unsure whether the use of this tool or its components violates the laws of your country, refrain from interacting with this tool. 152 | 153 | ----- 154 | 155 | ## Stargazers over time 156 | [![Stargazers over time](https://starchart.cc/cortez24rus/xui-reverse-proxy.svg?variant=adaptive)](https://starchart.cc/cortez24rus/xui-reverse-proxy) 157 | -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 | # REVERSE_PROXY ([English](/README.md)) 2 |

Image

3 | 4 | ----- 5 | 6 | ### Сервер с использованием реверс-прокси NGINX 7 | Этот скрипт предназначен для быстрой и простой настройки обратного прокси-сервера с использованием NGINX. В данном варианте все входящие запросы обрабатываются NGINX, а сервер работает как обратный прокси-сервер только при условии, что запрос содержит правильный путь (URI). Это повышает безопасность и улучшает управление доступом. 8 | 9 | > [!IMPORTANT] 10 | > Этот скрипт был протестирован в среде виртуализации KVM. Для корректной работы вам потребуется собственный домен, который` необходимо привязать к Cloudflare. Скрипт рекомендуется запускать с правами root на свежеустановленной системе. 11 | 12 | > [!NOTE] 13 | > Скрипт настроен с учётом специфики маршрутизации для пользователей из России. 14 | 15 | ### Поддерживаемые операционные системы: 16 | 17 | | **Ubuntu** | **Debian** | **CentOS** | 18 | |------------------|-----------------|------------------| 19 | | 24.04 LTS | 12 bookworm | Stream 9 | 20 | | 22.04 LTS | 11 bullseye | Stream 8 | 21 | | 20.04 LTS | 10 buster | 7 | 22 | 23 | ----- 24 | 25 | ### Настройка cloudflare 26 | 1. Обновите систему и перезагрузите сервер. 27 | 2. Настройте Cloudflare: 28 | - Привяжите ваш домен к Cloudflare. 29 | - Добавьте следующие DNS записи: 30 | 31 | Сервер #1 32 | | Type | Name | Content | Proxy status | 33 | | ----- | ---------------- | ---------------- | ------------- | 34 | | A | example.com | your_server_ip | DNS only | 35 | | CNAME | www | example.com | DNS only | 36 | 37 | Сервер #2 38 | | Type | Name | Content | Proxy status | 39 | | ----- | ---------------- | ---------------- | ------------- | 40 | | A | nl.example.com | your_server_ip | DNS only | 41 | | CNAME | www.nl | nl.example.com | DNS only | 42 | 43 | 3. Настройки SSL/TLS в Cloudflare: 44 | - Перейдите в раздел SSL/TLS > Overview и выберите Full для параметра Configure. 45 | - Установите Minimum TLS Version на TLS 1.3. 46 | - Включите TLS 1.3 (true) в разделе Edge Certificates. 47 | 48 | ----- 49 | 50 | ### REVERSE_PROXY_MANAGER: 51 | 52 |

Image

53 | 54 | ----- 55 | 56 | ### Включает в себя: 57 | 58 | 1. Конфигурация прокси сервера: 59 | - Поддержка автоматического обновления конфигураций через подписку и JSON подписку с возможностью конвертации в форматы для популярных приложений. 60 | - Необходимо включать MUX (мультиплексирование TCP соединений) в каждом клиентском приложении 61 | - gRPC-TLS 62 | - XHTTP-TLS 63 | - HTTPUpgrade-TLS 64 | - Websocket-TLS 65 | - Необходимо включать у пользователя "flow": "xtls-rprx-vision" 66 | - TCP-REALITY (Steal oneself) (отключение приведет к потере доступа) 67 | - TCP-TLS 68 | - Важно: рекомендуется выбрать один подходящий тип подключения и использовать его для оптимальной работы. Вы можете отключить все входящие соединения, кроме того, которое отмечено как STEAL. Отключение STEAL приведет к потере доступа к веб-интерфейсу, так как этот тип соединения используется для доступа к управлению прокси. 69 | 2. Настройку обратного прокси NGINX на порт 443. 70 | 3. Обеспечение безопасности: 71 | - Автоматические обновления системы через unattended-upgrades. 72 | - Настройка SSL сертификатов Cloudflare с автоматическим обновлением для защиты соединений. 73 | - Настройка WARP для защиты трафика. 74 | - Настройка UFW (Uncomplicated Firewall) для управления доступом. 75 | - Настройка SSH, для обеспечения минимально необходимой безопасности. 76 | - Отключение IPv6 для предотвращения возможных уязвимостей. 77 | - Шифрование DNS-запросов с использованием systemd-resolved (Dot) или AdGuard Home (Dot, DoH). 78 | - Выбор случайного шаблона веб-сайта из массива. 79 | 4. Включение BBR — улучшение производительности TCP-соединений. 80 | 5. Дополнительные опции: 81 | - Настройка Shell In A Box для безопасного доступа к серверу через SSH через веб-интерфейс. 82 | - Установка и настройка Node Exporter для мониторинга производительности системы с интеграцией с Prometheus и Grafana для визуализации метрик в реальном времени. 83 | - Обновленное приветственное сообщение (motd) при подключении через SSH, содержащее полезную информацию о системе, состоянии сервисов и доступных обновлениях. 84 | - Интеграция VNStat для мониторинга трафика, с возможностью получения статистики по времени. 85 | ----- 86 | 87 | ### Сообщение помощи для скрипта: 88 | ``` 89 | 90 | Использование: reverse_proxy [-u|--utils ] [-d|--dns ] [-a|--addu ] 91 | [-r|--autoupd ] [-b|--bbr ] [-i|--ipv6 ] [-w|--warp ] 92 | [-c|--cert ] [-m|--mon ] [-l|--shell ] [-n|--nginx ] 93 | [-p|--panel ] [--custom ] [-f|--firewall ] [-s|--ssh ] 94 | [-t|--tgbot ] [-g|--generate ] [-x|--skip-check ] [-o|--subdomain ] 95 | [--update] [-h|--help]" 96 | 97 | -u, --utils Дополнительные утилиты (по умолчанию: true) 98 | -d, --dns Шифрование DNS (по умолчанию: true) 99 | -a, --addu Добавление пользователя (по умолчанию: true) 100 | -r, --autoupd Автоматические обновления (по умолчанию: true) 101 | -b, --bbr BBR (управление перегрузкой TCP) (по умолчанию: true) 102 | -i, --ipv6 Отключить поддержку IPv6 (по умолчанию: true) 103 | -w, --warp Warp (по умолчанию: false) 104 | -c, --cert Выпуск сертификатов для домена (по умолчанию: true) 105 | -m, --mon Сервисы мониторинга (node_exporter) (по умолчанию: false) 106 | -l, --shell Установка Shell In A Box (по умолчанию: false) 107 | -n, --nginx Установка NGINX (по умолчанию: true) 108 | -p, --panel Установка панели для управления пользователями (по умолчанию: true) 109 | --custom Кастомная JSON-подписка (по умолчанию: true) 110 | -f, --firewall Настройка файрвола (по умолчанию: true) 111 | -s, --ssh SSH доступ (по умолчанию: true) 112 | -t, --tgbot Интеграция Telegram бота (по умолчанию: false) 113 | -g, --generate Генерация случайных путей для конфигурации (по умолчанию: true) 114 | -x, --skip-check Отключение проверки (по умолчанию: false) 115 | -o, --subdomain Поддержка поддоменов (по умолчанию: false) 116 | --update Обновить версию Reverse-proxy manager 117 | -h, --help Показать это сообщение помощи 118 | ``` 119 | 120 | ----- 121 | 122 | ### Установка REVERSE_PROXY: 123 | 124 | Для начала настройки сервера выполните следующую команду в терминале: 125 | ```sh 126 | bash <(curl -Ls https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/refs/heads/main/reverse_proxy.sh) 127 | ``` 128 | 129 | ### Установка случайного шаблона для веб-сайта: 130 | ```sh 131 | bash <(curl -Ls https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/refs/heads/main/reverse_proxy_random_site.sh) 132 | ``` 133 | 134 | Скрипт запросит у вас необходимую конфигурационную информацию: 135 | 136 |

Image

137 | 138 | ### Примечание: 139 | - После завершения настройки скрипт отобразит все необходимые ссылки и данные для входа в административную панель. 140 | - Все конфигурации можно будет изменять по мере необходимости, благодаря гибкости настроек. 141 | 142 | ----- 143 | 144 | > [!IMPORTANT] 145 | > Этот репозиторий предназначен исключительно для образовательных целей и для изучения принципов работы обратных прокси-серверов и сетевой безопасности. Скрипт демонстрирует настройку прокси-сервера с использованием NGINX для реверс-прокси, управления трафиком и защиты от атак. 146 | > 147 | > Мы настоятельно напоминаем, что использование этого инструмента с целью обхода сетевых блокировок или цензуры является незаконным в ряде стран, где существуют законы, регулирующие использование технологий для обхода ограничений в интернете. 148 | > 149 | > Данный проект не предназначен для использования в целях, нарушающих законы о защите информации или вмешивающихся в механизмы цензуры. Мы не несем ответственности за возможные юридические последствия, связанные с использованием этого скрипта. 150 | > 151 | >Используйте этот инструмент/скрипт исключительно в демонстрационных целях, в качестве примера работы обратного прокси и защиты данных. Настоятельно рекомендуем удалить скрипт после ознакомления. Дальнейшее использование на ваш страх и риск. 152 | > 153 | >Если вы не уверены, нарушает ли использование данного инструмента или его компонентов законодательство вашей страны- откажитесь от любого взаимодействия с данным инструментом. 154 | 155 | ----- 156 | 157 | ## Количество звезд по времени 158 | [![Stargazers over time](https://starchart.cc/cortez24rus/xui-reverse-proxy.svg?variant=adaptive)](https://starchart.cc/cortez24rus/xui-reverse-proxy) 159 | -------------------------------------------------------------------------------- /adh/AdGuardHome.yaml: -------------------------------------------------------------------------------- 1 | http: 2 | pprof: 3 | port: 6060 4 | enabled: false 5 | address: 127.0.0.1:8081 6 | session_ttl: 720h 7 | users: 8 | - name: username 9 | password: hash 10 | auth_attempts: 5 11 | block_auth_min: 15 12 | http_proxy: "" 13 | language: "" 14 | theme: auto 15 | dns: 16 | bind_hosts: 17 | - 0.0.0.0 18 | port: 53 19 | anonymize_client_ip: false 20 | ratelimit: 20 21 | ratelimit_subnet_len_ipv4: 24 22 | ratelimit_subnet_len_ipv6: 56 23 | ratelimit_whitelist: [] 24 | refuse_any: true 25 | upstream_dns: 26 | - https://dns.cloudflare.com/dns-query 27 | - https://dns.google/dns-query 28 | - https://dns10.quad9.net/dns-query 29 | - tls://one.one.one.one 30 | - tls://dns.google 31 | - tls://dns10.quad9.net 32 | upstream_dns_file: "" 33 | bootstrap_dns: 34 | - 9.9.9.10 35 | - 149.112.112.10 36 | - 2620:fe::10 37 | - 2620:fe::fe:10 38 | fallback_dns: [] 39 | upstream_mode: parallel 40 | fastest_timeout: 1s 41 | allowed_clients: [] 42 | disallowed_clients: [] 43 | blocked_hosts: 44 | - version.bind 45 | - id.server 46 | - hostname.bind 47 | trusted_proxies: 48 | - 127.0.0.0/8 49 | - ::1/128 50 | cache_size: 4194304 51 | cache_ttl_min: 0 52 | cache_ttl_max: 0 53 | cache_optimistic: false 54 | bogus_nxdomain: [] 55 | aaaa_disabled: false 56 | enable_dnssec: false 57 | edns_client_subnet: 58 | custom_ip: "" 59 | enabled: false 60 | use_custom: false 61 | max_goroutines: 300 62 | handle_ddr: true 63 | ipset: [] 64 | ipset_file: "" 65 | bootstrap_prefer_ipv6: false 66 | upstream_timeout: 10s 67 | private_networks: [] 68 | use_private_ptr_resolvers: true 69 | local_ptr_upstreams: [] 70 | use_dns64: false 71 | dns64_prefixes: [] 72 | serve_http3: false 73 | use_http3_upstreams: false 74 | serve_plain_dns: true 75 | hostsfile_enabled: true 76 | tls: 77 | enabled: true 78 | server_name: domain_temp # Укажите имя сервера 79 | force_https: true 80 | port_https: 443 81 | port_dns_over_tls: 853 82 | port_dns_over_quic: 853 83 | port_dnscrypt: 0 84 | dnscrypt_config_file: "" 85 | allow_unencrypted_doh: false # Запрещаем неоплачиваемый DoH 86 | certificate_chain: fullchain.pem 87 | private_key: privkey.pem 88 | certificate_path: fullchain.pem 89 | private_key_path: privkey.pem 90 | strict_sni_check: false 91 | querylog: 92 | dir_path: "" 93 | ignored: [] 94 | interval: 2160h 95 | size_memory: 1000 96 | enabled: true 97 | file_enabled: true 98 | statistics: 99 | dir_path: "" 100 | ignored: [] 101 | interval: 24h 102 | enabled: true 103 | filters: 104 | - enabled: true 105 | url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt 106 | name: AdGuard DNS filter 107 | id: 1 108 | - enabled: false 109 | url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt 110 | name: AdAway Default Blocklist 111 | id: 2 112 | - enabled: true 113 | url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_33.txt 114 | name: Steven Black's List 115 | id: 1725212203 116 | whitelist_filters: [] 117 | user_rules: [] 118 | dhcp: 119 | enabled: false 120 | interface_name: "" 121 | local_domain_name: lan 122 | dhcpv4: 123 | gateway_ip: "" 124 | subnet_mask: "" 125 | range_start: "" 126 | range_end: "" 127 | lease_duration: 86400 128 | icmp_timeout_msec: 1000 129 | options: [] 130 | dhcpv6: 131 | range_start: "" 132 | lease_duration: 86400 133 | ra_slaac_only: false 134 | ra_allow_slaac: false 135 | filtering: 136 | blocking_ipv4: "" 137 | blocking_ipv6: "" 138 | blocked_services: 139 | schedule: 140 | time_zone: Local 141 | ids: [] 142 | protection_disabled_until: null 143 | safe_search: 144 | enabled: false 145 | bing: true 146 | duckduckgo: true 147 | google: true 148 | pixabay: true 149 | yandex: true 150 | youtube: true 151 | blocking_mode: default 152 | parental_block_host: family-block.dns.adguard.com 153 | safebrowsing_block_host: standard-block.dns.adguard.com 154 | rewrites: [] 155 | safebrowsing_cache_size: 1048576 156 | safesearch_cache_size: 1048576 157 | parental_cache_size: 1048576 158 | cache_time: 30 159 | filters_update_interval: 24 160 | blocked_response_ttl: 10 161 | filtering_enabled: true 162 | parental_enabled: false 163 | safebrowsing_enabled: false 164 | protection_enabled: true 165 | clients: 166 | runtime_sources: 167 | whois: true 168 | arp: true 169 | rdns: true 170 | dhcp: true 171 | hosts: true 172 | persistent: [] 173 | log: 174 | enabled: true 175 | file: "" 176 | max_backups: 0 177 | max_size: 100 178 | max_age: 3 179 | compress: false 180 | local_time: false 181 | verbose: false 182 | os: 183 | group: "" 184 | user: "" 185 | rlimit_nofile: 0 186 | schema_version: 28 187 | -------------------------------------------------------------------------------- /bot/reverse_proxy_bot.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import json 3 | import uuid 4 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update 5 | from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler 6 | from telegram.ext import filters 7 | from datetime import datetime, timedelta 8 | 9 | # Чтение данных из конфигурационного файла 10 | CONFIG_FILE = '/usr/local/reverse_proxy/reverse_proxy_bot_config.json' 11 | 12 | def load_config(): 13 | try: 14 | with open(CONFIG_FILE, 'r') as f: 15 | config = json.load(f) 16 | except FileNotFoundError: 17 | print(f"Файл конфигурации {CONFIG_FILE} не найден!") 18 | exit(1) 19 | except json.JSONDecodeError: 20 | print("Ошибка в формате конфигурационного файла!") 21 | exit(1) 22 | return config 23 | 24 | config = load_config() 25 | 26 | # Вводные данные 27 | DB_PATH = '/etc/x-ui/x-ui.db' 28 | BOT_TOKEN = config['BOT_TOKEN'] 29 | BOT_AID = config['BOT_AID'] 30 | NAME_MENU = config['NAME_MENU'] 31 | 32 | # Функция для подключения к базе данных 33 | def get_db_connection(): 34 | conn = sqlite3.connect(DB_PATH) 35 | conn.row_factory = sqlite3.Row 36 | return conn 37 | 38 | # Функция для получения всех remark с up и down 39 | def get_inbounds_remarks(): 40 | connection = sqlite3.connect(DB_PATH) 41 | cursor = connection.cursor() 42 | 43 | cursor.execute("SELECT remark, up, down, enable FROM inbounds") 44 | remarks = cursor.fetchall() 45 | 46 | connection.close() 47 | return [(remark, up, down, enable) for remark, up, down, enable in remarks] 48 | 49 | # Функция для получения всех существующих ID 50 | def get_all_ids(): 51 | conn = sqlite3.connect(DB_PATH) 52 | cursor = conn.cursor() 53 | cursor.execute("SELECT id, settings FROM inbounds") 54 | result = cursor.fetchall() 55 | conn.close() 56 | return [(id, json.loads(settings)) for id, settings in result] 57 | 58 | # Функция для получения всех пользователей 59 | def get_all_users(): 60 | all_ids = get_all_ids() 61 | users = [] 62 | for _, settings in all_ids: 63 | for client in settings.get('clients', []): 64 | users.append(client['subId']) # Добавляем subId пользователя в список 65 | return list(set(users)) # Удаляем дубликаты 66 | 67 | # Функция для добавления пользователя 68 | def add_user_to_all_ids(name): 69 | all_ids = get_all_ids() # Предполагается, что эта функция возвращает все id и соответствующие настройки 70 | 71 | for id, settings in all_ids: 72 | # Получаем remark для текущего id 73 | conn = sqlite3.connect(DB_PATH) 74 | cursor = conn.cursor() 75 | cursor.execute("SELECT remark FROM inbounds WHERE id = ?", (id,)) 76 | remark = cursor.fetchone()[0] # Получаем значение remark 77 | conn.close() 78 | 79 | # Используем имя для формирования email с remark 80 | email = f"{name}{remark}" # Формируем email на основе имени пользователя и remark 81 | 82 | # Генерируем уникальный UUID для id 83 | new_id = str(uuid.uuid4()) 84 | 85 | # Округляем до следующего часа 86 | next_hour = (datetime.now() + timedelta(hours=1)).replace(minute=0, second=0, microsecond=0) 87 | 88 | # Добавляем два дня к следующему часу 89 | expiry_time = int((next_hour + timedelta(days=2)).timestamp() * 1000) 90 | 91 | new_client = { 92 | "id": new_id, # Генерируем уникальный id 93 | "flow": "", 94 | "email": email, # Email теперь на основе имени и remark 95 | "limitIp": 2, 96 | "totalGB": 0, 97 | "expiryTime": expiry_time, # Текущее время + 2 дня в Unix формате 98 | "enable": True, 99 | "tgId": "", 100 | "subId": name, # Используем введённое имя как subId 101 | "reset": 30 102 | } 103 | 104 | settings['clients'].append(new_client) 105 | 106 | # Обновляем настройки в таблице inbounds 107 | conn = sqlite3.connect(DB_PATH) 108 | cursor = conn.cursor() 109 | cursor.execute("UPDATE inbounds SET settings = ? WHERE id = ?", (json.dumps(settings), id)) 110 | conn.commit() 111 | 112 | # Добавляем нового клиента в таблицу client_traffics 113 | cursor.execute(''' 114 | INSERT INTO client_traffics (inbound_id, enable, email, up, down, expiry_time, total, reset) 115 | VALUES (?, ?, ?, ?, ?, ?, ?, ?) 116 | ''', (id, 1, email, 0, 0, expiry_time, 0, 30)) # Замените значения по необходимости 117 | 118 | conn.commit() 119 | conn.close() 120 | 121 | # Функция для обновления значения enable в базе данных 122 | def toggle_enable(remark): 123 | connection = sqlite3.connect(DB_PATH) 124 | cursor = connection.cursor() 125 | 126 | cursor.execute("SELECT enable FROM inbounds WHERE remark = ?", (remark,)) 127 | current_value = cursor.fetchone() 128 | 129 | if current_value: 130 | new_value = 1 if current_value[0] == 0 else 0 131 | cursor.execute("UPDATE inbounds SET enable = ? WHERE remark = ?", (new_value, remark)) 132 | connection.commit() 133 | 134 | connection.close() 135 | return new_value 136 | 137 | # Функция для удаления пользователя по subId 138 | def remove_user_from_all_ids(subId): 139 | all_ids = get_all_ids() 140 | 141 | for id, settings in all_ids: 142 | # Фильтруем клиентов, исключая удаляемого 143 | clients_to_remove = [client for client in settings['clients'] if client['subId'] == subId] 144 | settings['clients'] = [client for client in settings['clients'] if client['subId'] != subId] 145 | 146 | # Удаляем пользователей из client_traffics 147 | conn = sqlite3.connect(DB_PATH) 148 | cursor = conn.cursor() 149 | 150 | # Удаляем записи из client_traffics по email 151 | for client in clients_to_remove: 152 | email = client['email'] 153 | cursor.execute("DELETE FROM client_traffics WHERE email = ?", (email,)) 154 | 155 | # Обновляем inbounds 156 | cursor.execute("UPDATE inbounds SET settings = ? WHERE id = ?", (json.dumps(settings), id)) 157 | conn.commit() 158 | conn.close() 159 | 160 | # Функция для получения информации о пользователях из базы данных 161 | def get_users_info(): 162 | conn = get_db_connection() 163 | cursor = conn.cursor() 164 | 165 | # Получение значения suburl 166 | cursor.execute("SELECT value FROM settings WHERE key = 'subURI'") 167 | suburl_row = cursor.fetchone() 168 | suburl = suburl_row[0] if suburl_row else "" # Убедимся, что значение suburl получено 169 | 170 | # Запрос для получения всех данных из таблицы inbounds 171 | cursor.execute("SELECT settings FROM inbounds") 172 | inbounds = cursor.fetchall() 173 | 174 | user_traffic = {} # Используем словарь для хранения трафика пользователей 175 | 176 | # Обработка данных 177 | for inbound in inbounds: 178 | settings = json.loads(inbound['settings']) 179 | for client in settings.get('clients', []): 180 | sub_id = client.get('subId') 181 | email = client.get('email') 182 | if sub_id: 183 | # Запрос для получения трафика пользователя 184 | cursor.execute("SELECT up, down FROM client_traffics WHERE email = ?", (email,)) 185 | traffic = cursor.fetchone() 186 | up_traffic = traffic[0] / (1024 ** 3) if traffic and traffic[0] is not None else 0 # в гигабайтах 187 | down_traffic = traffic[1] / (1024 ** 3) if traffic and traffic[1] is not None else 0 # в гигабайтах 188 | 189 | # Если sub_id уже есть в словаре, суммируем трафик 190 | if sub_id in user_traffic: 191 | user_traffic[sub_id]['up'] += up_traffic 192 | user_traffic[sub_id]['down'] += down_traffic 193 | else: 194 | # Иначе добавляем нового пользователя в словарь 195 | user_traffic[sub_id] = { 196 | 'up': up_traffic, 197 | 'down': down_traffic, 198 | 'subscription_link': f"🔗 {suburl}{sub_id}" if suburl else f"/{sub_id}" 199 | } 200 | conn.close() # Закрываем соединение после завершения всех операций 201 | 202 | # Форматируем вывод 203 | user_lines = [] 204 | for sub_id, traffic_info in user_traffic.items(): 205 | user_lines.append(f"👤 {sub_id} - 🔼 Up {traffic_info['up']:.2f} GB / 🔽 Down {traffic_info['down']:.2f} GB\n{traffic_info['subscription_link']}") 206 | 207 | return "\n\n".join(user_lines) if user_lines else "No users" 208 | 209 | def calculate_total_traffic(): 210 | connection = sqlite3.connect(DB_PATH) 211 | cursor = connection.cursor() 212 | 213 | cursor.execute("SELECT up, down FROM inbounds") 214 | total_up = total_down = 0 215 | 216 | for up, down in cursor.fetchall(): 217 | total_up += up 218 | total_down += down 219 | 220 | connection.close() 221 | # Переводим значения в гигабайты 222 | total_up_gb = total_up / (1024 ** 3) 223 | total_down_gb = total_down / (1024 ** 3) 224 | 225 | return total_up_gb, total_down_gb 226 | 227 | # Функция для обработки команды /start 228 | async def start_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 229 | user_id = update.message.from_user.id if update.message else update.callback_query.from_user.id 230 | 231 | # Проверяем, является ли пользователь администратором 232 | if user_id != BOT_AID: 233 | await (update.message.reply_text("Access denied") if update.message else update.callback_query.edit_message_text("Access denied")) 234 | return 235 | # Сразу открываем основное меню 236 | keyboard = [ 237 | [InlineKeyboardButton("📬 Inbounds", callback_data='inbounds')], 238 | [InlineKeyboardButton("🫂 User menu", callback_data='user_menu')] 239 | ] 240 | reply_markup = InlineKeyboardMarkup(keyboard) 241 | if update.message: 242 | await update.message.reply_text(NAME_MENU, reply_markup=reply_markup) 243 | else: 244 | await update.callback_query.edit_message_text(NAME_MENU, reply_markup=reply_markup) 245 | 246 | # Функция для обработки нажатия кнопок 247 | async def show_inbounds_menu(query): 248 | total_up, total_down = calculate_total_traffic() 249 | remarks = get_inbounds_remarks() 250 | if remarks: 251 | header = f"📬 Inbounds 📬\n🔼 Total Up {total_up:.2f} GB / 🔽 Total Down {total_down:.2f} GB\n" 252 | keyboard = [ 253 | [InlineKeyboardButton( 254 | f"{remark} - {up / (1024 ** 3):.2f} GB / {down / (1024 ** 3):.2f} GB {'🟢' if enable == 1 else '⭕️'}", 255 | callback_data=f"select_{remark}" 256 | )] 257 | for remark, up, down, enable in remarks 258 | ] 259 | keyboard.append([InlineKeyboardButton("🔙 Return", callback_data='start_menu')]) 260 | reply_markup = InlineKeyboardMarkup(keyboard) 261 | await query.edit_message_text(header, reply_markup=reply_markup) 262 | else: 263 | await query.edit_message_text("No inbounds available") 264 | 265 | async def button_click(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 266 | query = update.callback_query 267 | if query.from_user.id != BOT_AID: 268 | await query.answer("Access denied", show_alert=True) 269 | return 270 | 271 | if query.data == 'user_menu': 272 | await show_user_menu(query) 273 | 274 | elif query.data == 'show_users': 275 | users_info = get_users_info() 276 | keyboard = [ 277 | [InlineKeyboardButton("🔙 Return", callback_data='user_menu')] 278 | ] 279 | reply_markup = InlineKeyboardMarkup(keyboard) 280 | await query.edit_message_text(text=f"🚦 Traffic / 💵 Subscription\n\n{users_info}", reply_markup=reply_markup) 281 | 282 | elif query.data == 'inbounds': 283 | await show_inbounds_menu(query) 284 | 285 | elif query.data.startswith("select_"): 286 | remark = query.data.split("select_")[1] 287 | toggle_enable(remark) 288 | await show_inbounds_menu(query) 289 | 290 | elif query.data == 'start_menu': 291 | await start_menu(update, context) 292 | 293 | elif query.data == 'add_user': 294 | await query.message.reply_text("Please enter a username to add") 295 | context.user_data['action'] = 'add_user' 296 | 297 | elif query.data == 'delete_user': 298 | users = get_all_users() 299 | if users: 300 | await show_delete_user_menu(query, users) 301 | else: 302 | await query.edit_message_text("No users available") 303 | 304 | elif query.data.startswith('remove_'): 305 | subId = query.data.split('_')[1] 306 | remove_user_from_all_ids(subId) 307 | users = get_all_users() 308 | await show_delete_user_menu(query, users) 309 | 310 | elif query.data == 'list_users': 311 | await list_users(update, context) 312 | 313 | elif query.data.startswith("toggle_"): 314 | await toggle_user_enable(update, context) 315 | 316 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup 317 | 318 | async def list_users(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 319 | all_ids = get_all_ids() 320 | users_keyboard = [] 321 | row = [] 322 | seen_sub_ids = set() # Множество для отслеживания уже добавленных subId 323 | 324 | for id, settings in all_ids: 325 | for client in settings.get('clients', []): 326 | sub_id = client.get('subId') 327 | enable_status = client.get('enable', False) 328 | emoji = "🟢" if enable_status else "⭕️" 329 | 330 | # Проверяем, был ли sub_id уже добавлен 331 | if sub_id not in seen_sub_ids: 332 | seen_sub_ids.add(sub_id) # Добавляем sub_id в множество 333 | 334 | # Добавляем кнопку в строку 335 | row.append(InlineKeyboardButton(f"{sub_id} {emoji}", callback_data=f"toggle_{sub_id}")) 336 | 337 | # Если в строке 2 кнопки, добавляем ее в клавиатуру и очищаем 338 | if len(row) == 2: 339 | users_keyboard.append(row) 340 | row = [] 341 | 342 | # Добавляем последнюю строку, если она не пуста 343 | if row: 344 | users_keyboard.append(row) 345 | 346 | # Кнопка возврата внизу 347 | users_keyboard.append([InlineKeyboardButton("🔙 Return", callback_data='user_menu')]) 348 | 349 | reply_markup = InlineKeyboardMarkup(users_keyboard) 350 | await update.callback_query.edit_message_text("🔄 Switch User Status", reply_markup=reply_markup) 351 | 352 | 353 | # Изменение состояния enable при нажатии на кнопку 354 | async def toggle_user_enable(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 355 | sub_id = update.callback_query.data.split("toggle_")[1] 356 | 357 | # Выводим subId для отладки 358 | print(f"Toggle request for subId: {sub_id}") 359 | 360 | connection = sqlite3.connect(DB_PATH) 361 | cursor = connection.cursor() 362 | 363 | # Получаем текущие настройки для subId 364 | cursor.execute("SELECT settings FROM inbounds WHERE id = ?", (sub_id,)) 365 | result = cursor.fetchone() 366 | 367 | if result: 368 | settings = json.loads(result[0]) 369 | found = False 370 | 371 | for client in settings['clients']: 372 | # Сравниваем subId для поиска 373 | if client['subId'] == sub_id: 374 | client['enable'] = not client['enable'] # Переключаем состояние enable 375 | found = True 376 | break 377 | 378 | if found: 379 | # Обновляем настройки в базе данных 380 | cursor.execute("UPDATE inbounds SET settings = ? WHERE id = ?", (json.dumps(settings), sub_id)) 381 | connection.commit() 382 | await update.callback_query.answer(f"Статус пользователя {sub_id} изменен на {'включен' if client['enable'] else 'выключен'}") 383 | else: 384 | await update.callback_query.answer("Пользователь не найден в списке клиентов.") 385 | else: 386 | await update.callback_query.answer("Пользователь не найден в базе данных.") 387 | 388 | connection.close() 389 | 390 | # Обновляем список пользователей 391 | await list_users(update, context) 392 | 393 | # Изменение состояния enable при нажатии на кнопку 394 | async def toggle_user_enable(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 395 | sub_id = update.callback_query.data.split("toggle_")[1] 396 | 397 | # Выводим subId для отладки 398 | print(f"Toggle request for subId: {sub_id}") 399 | 400 | connection = sqlite3.connect(DB_PATH) 401 | cursor = connection.cursor() 402 | 403 | # Получаем текущие настройки для subId 404 | cursor.execute("SELECT id, settings FROM inbounds") 405 | all_ids = cursor.fetchall() 406 | 407 | for id, settings_json in all_ids: 408 | settings = json.loads(settings_json) 409 | 410 | # Находим клиента с соответствующим subId 411 | for client in settings.get('clients', []): 412 | if client.get('subId') == sub_id: 413 | # Переключаем состояние enable 414 | client['enable'] = not client.get('enable', False) 415 | print(f"Toggling enable for subId: {sub_id} to {client['enable']}") 416 | 417 | # Обновляем настройки в базе данных 418 | cursor.execute("UPDATE inbounds SET settings = ? WHERE id = ?", (json.dumps(settings), id)) 419 | connection.commit() 420 | break # Выходим из цикла после изменения 421 | 422 | connection.close() 423 | 424 | # Обновляем меню пользователей 425 | await list_users(update, context) 426 | 427 | async def show_user_menu(query): 428 | keyboard = [ 429 | [InlineKeyboardButton("✅ Add user", callback_data='add_user')], 430 | [InlineKeyboardButton("❌ Delete user", callback_data='delete_user')], 431 | [InlineKeyboardButton("🔄 Switch User Status", callback_data='list_users')], 432 | [InlineKeyboardButton("🚦 Traffic / 💵 Subscription", callback_data='show_users')], 433 | [InlineKeyboardButton("🔙 Return", callback_data='start_menu')] 434 | ] 435 | reply_markup = InlineKeyboardMarkup(keyboard) 436 | await query.edit_message_text("🫂 User menu 🫂", reply_markup=reply_markup) 437 | 438 | async def show_delete_user_menu(query, users): 439 | keyboard = [] 440 | for i, user in enumerate(users): 441 | # Добавляем пользователей в две колонки 442 | if i % 2 == 0: 443 | keyboard.append([InlineKeyboardButton(user, callback_data=f'remove_{user}')]) 444 | else: 445 | keyboard[-1].append(InlineKeyboardButton(user, callback_data=f'remove_{user}')) 446 | keyboard.append([InlineKeyboardButton("🔙 Return", callback_data='user_menu')]) 447 | reply_markup = InlineKeyboardMarkup(keyboard) 448 | await query.edit_message_text("❌ Select the user to delete ❌", reply_markup=reply_markup) 449 | 450 | async def message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): 451 | action = context.user_data.get('action') 452 | if action == 'add_user': 453 | name = update.message.text 454 | add_user_to_all_ids(name) 455 | await update.message.reply_text(f"User {name} added") 456 | context.user_data['action'] = None 457 | 458 | if __name__ == '__main__': 459 | application = ApplicationBuilder().token(BOT_TOKEN).build() 460 | 461 | # Регистрация обработчиков команд и сообщений 462 | application.add_handler(CommandHandler("start", start_menu)) 463 | application.add_handler(CallbackQueryHandler(button_click)) 464 | application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, message_handler)) 465 | 466 | # Запуск бота 467 | application.run_polling() 468 | -------------------------------------------------------------------------------- /database/x-ui.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/database/x-ui.db -------------------------------------------------------------------------------- /database/x-ui.db_old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/database/x-ui.db_old -------------------------------------------------------------------------------- /media/reverse_proxy_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/media/reverse_proxy_manager.png -------------------------------------------------------------------------------- /media/reverse_proxy_manager_RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/media/reverse_proxy_manager_RU.png -------------------------------------------------------------------------------- /media/xui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/media/xui.png -------------------------------------------------------------------------------- /media/xui_rp_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/media/xui_rp_install.png -------------------------------------------------------------------------------- /media/xui_rp_install_RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cortez24rus/xui-reverse-proxy/cbfcfd33b7b14fdd331007c61bd79b0518d5706e/media/xui_rp_install_RU.png -------------------------------------------------------------------------------- /other/check_domain_ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################### 4 | ### Obtaining your external IP address 5 | ################################### 6 | check_ip() { 7 | IP4_REGEX="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" 8 | 9 | IP4=$(ip route get 8.8.8.8 2>/dev/null | grep -Po -- 'src \K\S*') 10 | 11 | if [[ ! $IP4 =~ $IP4_REGEX ]]; then 12 | IP4=$(curl -s --max-time 5 ipinfo.io/ip 2>/dev/null) 13 | fi 14 | 15 | if [[ ! $IP4 =~ $IP4_REGEX ]]; then 16 | echo "Не удалось получить внешний IP." 17 | return 1 18 | fi 19 | echo "$IP4" 20 | } 21 | 22 | ################################### 23 | ### Obtaining a domain IP address 24 | ################################### 25 | get_domain_ips() { 26 | IPS=($(dig @1.1.1.1 +short "$DOMAIN")) 27 | echo "${IPS[@]}" 28 | } 29 | 30 | ################################### 31 | ### Checking if the IP is in range 32 | ################################### 33 | ip_in_range() { 34 | local IP=$1 35 | local RANGE=$2 36 | ipcalc -n -c "$RANGE" | grep -q "$IP" 37 | } 38 | 39 | ################################### 40 | ### IP checks 41 | ################################### 42 | check_ip_in_cloudflare() { 43 | DOMAIN_IP=$1 44 | CLOUDFLARE_IPS=($(curl -s https://www.cloudflare.com/ips-v4)) 45 | 46 | for RANGE in "${CLOUDFLARE_IPS[@]}"; do 47 | if ip_in_range "$DOMAIN_IP" "$RANGE"; then 48 | return 0 49 | fi 50 | done 51 | return 1 52 | } 53 | 54 | ################################### 55 | ### Domain address verification 56 | ################################### 57 | check_domain_ip() { 58 | local MY_IP 59 | local DOMAIN_IPS 60 | MY_IP=$(check_ip) 61 | DOMAIN_IPS=($(get_domain_ips)) 62 | 63 | if [[ $? -ne 0 ]]; then 64 | echo " Не удалось получить внешний IP, завершение выполнения" 65 | exit 1 66 | fi 67 | 68 | echo " IP-адреса домена $DOMAIN: ${DOMAIN_IPS[@]}" 69 | 70 | if echo "${DOMAIN_IPS[@]}" | grep -qw "$MY_IP"; then 71 | echo " Ваш IP совпадает с одним из IP домена $DOMAIN (Status Dns only)" 72 | echo 73 | return 0 74 | fi 75 | 76 | for IP in "${DOMAIN_IPS[@]}"; do 77 | if check_ip_in_cloudflare "$IP"; then 78 | echo " IP-адрес $IP входит в диапазоны Cloudflare (Status Proxied)" 79 | echo 80 | return 0 81 | fi 82 | done 83 | 84 | echo " Ни один из IP-адресов ${DOMAIN_IPS[@]} не входит в диапазоны Cloudflare." 85 | echo "|-------------------------------------------------------------------------|" 86 | exit 1 87 | } 88 | 89 | final() { 90 | echo " Отработал на ура" 91 | } 92 | 93 | main() { 94 | apt install -y ipcalc > /dev/null 2>&1 95 | echo 96 | echo "|-------------------------------------------------------------------------|" 97 | DOMAIN="$@" 98 | check_domain_ip 99 | final 100 | echo "|-------------------------------------------------------------------------|" 101 | echo 102 | } 103 | 104 | main "$@" 105 | -------------------------------------------------------------------------------- /other/flag_final.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -A defaults 4 | declare -A args 5 | defaults_file="/usr/local/xui-rp/reinstall_defaults.conf" 6 | 7 | # Функция для отображения справки 8 | show_help() { 9 | echo "" 10 | echo "Usage: $0 [-f|--utils ] [-d|--dns ] [-a|--addu ] [-r|--autoupd ]" 11 | echo " [-b|--bbr ] [-i|--ipv6 ] [-w|--warp ] [-c|--cert ]" 12 | echo " [-m|--mon ] [-n|--nginx ] [-p|--panel ] [-u|--ufw ]" 13 | echo " [-s|--ssh ] [-t|--tgbot ] [-h|--help]" 14 | echo "" 15 | echo " -f, --utils Enable or disable utilities (default: ${defaults[utils]})" 16 | echo " -d, --dns Enable or disable DNS encryption (default: ${defaults[dns]})" 17 | echo " -a, --addu Enable or disable user addition (default: ${defaults[addu]})" 18 | echo " -r, --autoupd Enable or disable automatic updates (default: ${defaults[autoupd]})" 19 | echo " -b, --bbr Enable or disable BBR (default: ${defaults[bbr]})" 20 | echo " -i, --ipv6 Enable or disable IPv6 (default: ${defaults[ipv6]})" 21 | echo " -w, --warp Enable or disable Warp (default: ${defaults[warp]})" 22 | echo " -c, --cert Enable or disable certificate issuance (default: ${defaults[cert]})" 23 | echo " -m, --mon Enable or disable Monitoring (default: ${defaults[mon]})" 24 | echo " -n, --nginx Enable or disable NGINX installation (default: ${defaults[nginx]})" 25 | echo " -p, --panel Enable or disable panel installation (default: ${defaults[panel]})" 26 | echo " -u, --ufw Enable or disable UFW (default: ${defaults[ufw]})" 27 | echo " -s, --ssh Enable or disable SSH (default: ${defaults[ssh]})" 28 | echo " -t, --tgbot Enable or disable Telegram bot (default: ${defaults[tgbot]})" 29 | echo " -h, --help Display this help message" 30 | echo "" 31 | exit 0 32 | } 33 | 34 | # Функция для чтения значений из файла 35 | read_defaults_from_file() { 36 | if [[ -f $defaults_file ]]; then 37 | # Чтение и выполнение строк из файла 38 | while IFS= read -r line; do 39 | # Пропускаем пустые строки и комментарии 40 | [[ -z "$line" || "$line" =~ ^# ]] && continue 41 | eval "$line" 42 | done < $defaults_file 43 | else 44 | # Если файл не найден, используем значения по умолчанию 45 | defaults[utils]=true 46 | defaults[dns]=true 47 | defaults[addu]=true 48 | defaults[autoupd]=true 49 | defaults[bbr]=true 50 | defaults[ipv6]=true 51 | defaults[warp]=true 52 | defaults[cert]=true 53 | defaults[mon]=false 54 | defaults[nginx]=true 55 | defaults[panel]=true 56 | defaults[ufw]=true 57 | defaults[ssh]=true 58 | defaults[tgbot]=false 59 | fi 60 | } 61 | 62 | # Функция для записи значений в файл 63 | write_defaults_to_file() { 64 | cat > ${defaults_file}<] [-d|--dns ] [-a|--addu ]" 225 | echo " [-r|--autoupd ] [-b|--bbr ] [-i|--ipv6 ] [-w|--warp ]" 226 | echo " [-c|--cert ] [-m|--mon ] [-n|--nginx ] [-p|--panel ]" 227 | echo " [-f|--firewall ] [-s|--ssh ] [-t|--tgbot ] [-g|--generate ]" 228 | echo " [-x|--skip-check ] [-h|--help]" 229 | echo 230 | echo " -u, --utils Additional utilities (default: ${defaults[utils]})" 231 | echo " Дополнительные утилиты" 232 | echo " -d, --dns DNS encryption (default: ${defaults[dns]})" 233 | echo " Шифрование DNS" 234 | echo " -a, --addu User addition (default: ${defaults[addu]})" 235 | echo " Добавление пользователя" 236 | echo " -r, --autoupd Automatic updates (default: ${defaults[autoupd]})" 237 | echo " Автоматические обновления" 238 | echo " -b, --bbr BBR (TCP Congestion Control) (default: ${defaults[bbr]})" 239 | echo " BBR (управление перегрузкой TCP)" 240 | echo " -i, --ipv6 Disable IPv6 support (default: ${defaults[ipv6]})" 241 | echo " Отключить поддержку IPv6 " 242 | echo " -w, --warp WARP (default: ${defaults[warp]})" 243 | echo " WARP" 244 | echo " -c, --cert Certificate issuance for domain (default: ${defaults[cert]})" 245 | echo " Выпуск сертификатов для домена" 246 | echo " -m, --mon Monitoring services (node_exporter) (default: ${defaults[mon]})" 247 | echo " Сервисы мониторинга (node_exporter)" 248 | echo " -n, --nginx NGINX installation (default: ${defaults[nginx]})" 249 | echo " Установка NGINX" 250 | echo " -p, --panel Panel installation for user management (default: ${defaults[panel]})" 251 | echo " Установка панели для управления пользователями" 252 | echo " -f, --firewall Firewall configuration (default: ${defaults[firewall]})" 253 | echo " Настройка файрвола" 254 | echo " -s, --ssh SSH access (default: ${defaults[ssh]})" 255 | echo " SSH доступ" 256 | echo " -t, --tgbot Telegram bot integration (default: ${defaults[tgbot]})" 257 | echo " Интеграция Telegram бота" 258 | echo " -g, --generate Generate a random string for configuration (default: ${defaults[generate]})" 259 | echo " Генерация случайных путей для конфигурации" 260 | echo " -x, --skip-check Disable the check functionality (default: ${defaults[skip-check]})" 261 | echo " Отключение проверки" 262 | echo " -h, --help Display this help message " 263 | echo " Показать это сообщение помощи" 264 | echo 265 | exit 0 266 | } 267 | 268 | ################################### 269 | ### Reading values ​​from file 270 | ################################### 271 | read_defaults_from_file() { 272 | if [[ -f $defaults_file ]]; then 273 | # Чтение и выполнение строк из файла 274 | while IFS= read -r line; do 275 | # Пропускаем пустые строки и комментарии 276 | [[ -z "$line" || "$line" =~ ^# ]] && continue 277 | eval "$line" 278 | done < $defaults_file 279 | else 280 | # Если файл не найден, используем значения по умолчанию 281 | defaults[utils]=true 282 | defaults[dns]=true 283 | defaults[addu]=true 284 | defaults[autoupd]=true 285 | defaults[bbr]=true 286 | defaults[ipv6]=true 287 | defaults[warp]=true 288 | defaults[cert]=true 289 | defaults[mon]=false 290 | defaults[nginx]=true 291 | defaults[panel]=true 292 | defaults[firewall]=true 293 | defaults[ssh]=true 294 | defaults[tgbot]=false 295 | defaults[generate]=true 296 | defaults[skip-check]=false 297 | fi 298 | } 299 | 300 | ################################### 301 | ### Writing values ​​to a file 302 | ################################### 303 | write_defaults_to_file() { 304 | cat > ${defaults_file}< >(tee -a "$LOGFILE") 2>&1 489 | } 490 | 491 | ################################### 492 | ### Language selection 493 | ################################### 494 | select_language() { 495 | L=E 496 | hint " $(text 0) \n" # Показывает информацию о доступных языках 497 | reading " $(text 1) " LANGUAGE # Запрашивает выбор языка 498 | 499 | # Устанавливаем язык в зависимости от выбора 500 | case "$LANGUAGE" in 501 | 1) L=E ;; # Если выбран английский 502 | 2) L=R ;; # Если выбран русский 503 | # 3) L=C ;; # Если выбран китайский 504 | # 4) L=F ;; # Если выбран персидский 505 | *) L=E ;; # По умолчанию — английский 506 | esac 507 | } 508 | 509 | ################################### 510 | ### Checking the operating system 511 | ################################### 512 | check_operating_system() { 513 | if [ -s /etc/os-release ]; then 514 | SYS="$(grep -i pretty_name /etc/os-release | cut -d \" -f2)" 515 | elif [ -x "$(type -p hostnamectl)" ]; then 516 | SYS="$(hostnamectl | grep -i system | cut -d : -f2)" 517 | elif [ -x "$(type -p lsb_release)" ]; then 518 | SYS="$(lsb_release -sd)" 519 | elif [ -s /etc/lsb-release ]; then 520 | SYS="$(grep -i description /etc/lsb-release | cut -d \" -f2)" 521 | elif [ -s /etc/redhat-release ]; then 522 | SYS="$(grep . /etc/redhat-release)" 523 | elif [ -s /etc/issue ]; then 524 | SYS="$(grep . /etc/issue | cut -d '\' -f1 | sed '/^[ ]*$/d')" 525 | fi 526 | 527 | REGEX=("debian" "ubuntu" "centos|red hat|kernel|alma|rocky") 528 | RELEASE=("Debian" "Ubuntu" "CentOS") 529 | EXCLUDE=("---") 530 | MAJOR=("10" "20" "7") 531 | PACKAGE_UPDATE=("apt -y update" "apt -y update" "yum -y update --skip-broken") 532 | PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install") 533 | PACKAGE_UNINSTALL=("apt -y autoremove" "apt -y autoremove" "yum -y autoremove") 534 | 535 | for int in "${!REGEX[@]}"; do 536 | [[ "${SYS,,}" =~ ${REGEX[int]} ]] && SYSTEM="${RELEASE[int]}" && break 537 | done 538 | 539 | # Проверка на кастомизированные системы от различных производителей 540 | if [ -z "$SYSTEM" ]; then 541 | [ -x "$(type -p yum)" ] && int=2 && SYSTEM='CentOS' || error " $(text 5) " 542 | fi 543 | 544 | # Определение основной версии Linux 545 | MAJOR_VERSION=$(sed "s/[^0-9.]//g" <<< "$SYS" | cut -d. -f1) 546 | 547 | # Сначала исключаем системы, указанные в EXCLUDE, затем для оставшихся делаем сравнение по основной версии 548 | for ex in "${EXCLUDE[@]}"; do [[ ! "${SYS,,}" =~ $ex ]]; done && 549 | [[ "$MAJOR_VERSION" -lt "${MAJOR[int]}" ]] && error " $(text 71) " 550 | } 551 | 552 | ################################### 553 | ### Checking and installing dependencies 554 | ################################### 555 | check_dependencies() { 556 | # Зависимости, необходимые для трех основных систем 557 | [ "${SYSTEM}" = 'CentOS' ] && ${PACKAGE_INSTALL[int]} vim-common epel-release 558 | DEPS_CHECK=("ping" "wget" "curl" "systemctl" "ip" "sudo") 559 | DEPS_INSTALL=("iputils-ping" "wget" "curl" "systemctl" "iproute2" "sudo") 560 | 561 | for g in "${!DEPS_CHECK[@]}"; do 562 | [ ! -x "$(type -p ${DEPS_CHECK[g]})" ] && [[ ! "${DEPS[@]}" =~ "${DEPS_INSTALL[g]}" ]] && DEPS+=(${DEPS_INSTALL[g]}) 563 | done 564 | 565 | if [ "${#DEPS[@]}" -ge 1 ]; then 566 | info "\n $(text 72) ${DEPS[@]} \n" 567 | ${PACKAGE_UPDATE[int]} 568 | ${PACKAGE_INSTALL[int]} ${DEPS[@]} 569 | else 570 | info "\n $(text 73) \n" 571 | fi 572 | } 573 | 574 | ################################### 575 | ### Root check 576 | ################################### 577 | check_root() { 578 | if [[ $EUID -ne 0 ]]; then 579 | error " $(text 8) " 580 | fi 581 | } 582 | 583 | ################################### 584 | ### Banner 585 | ################################### 586 | banner_1() { 587 | echo 588 | echo " █░█ █░░█ ░▀░ ░░ █▀▀█ █▀▀ ▀█░█▀ █▀▀ █▀▀█ █▀▀ █▀▀ ░░ █▀▀█ █▀▀█ █▀▀█ █░█ █░░█ " 589 | echo " ▄▀▄ █░░█ ▀█▀ ▀▀ █▄▄▀ █▀▀ ░█▄█░ █▀▀ █▄▄▀ ▀▀█ █▀▀ ▀▀ █░░█ █▄▄▀ █░░█ ▄▀▄ █▄▄█ " 590 | echo " ▀░▀ ░▀▀▀ ▀▀▀ ░░ ▀░▀▀ ▀▀▀ ░░▀░░ ▀▀▀ ▀░▀▀ ▀▀▀ ▀▀▀ ░░ █▀▀▀ ▀░▀▀ ▀▀▀▀ ▀░▀ ▄▄▄█ " 591 | echo 592 | echo 593 | } 594 | 595 | ################################### 596 | ### Installation request 597 | ################################### 598 | start_installation() { 599 | warning " $(text 5) " 600 | echo 601 | info " $(text 6) " 602 | warning " apt-get update && apt-get full-upgrade -y && reboot " 603 | echo 604 | reading " $(text 8) " ANSWER_START 605 | case "${ANSWER_START,,}" in 606 | y|"") 607 | ;; 608 | *) 609 | error " $(text 9) " 610 | ;; 611 | esac 612 | } 613 | 614 | ################################### 615 | ### Obtaining your external IP address 616 | ################################### 617 | check_ip() { 618 | IP4_REGEX="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" 619 | 620 | IP4=$(ip route get 8.8.8.8 2>/dev/null | grep -Po -- 'src \K\S*') 621 | 622 | if [[ ! $IP4 =~ $IP4_REGEX ]]; then 623 | IP4=$(curl -s --max-time 5 ipinfo.io/ip 2>/dev/null) 624 | fi 625 | 626 | if [[ ! $IP4 =~ $IP4_REGEX ]]; then 627 | echo "Не удалось получить внешний IP." 628 | return 1 629 | fi 630 | } 631 | 632 | ################################### 633 | ### Request and response from Cloudflare API 634 | ################################### 635 | get_test_response() { 636 | testdomain=$(echo "${DOMAIN}" | rev | cut -d '.' -f 1-2 | rev) 637 | 638 | if [[ "$CFTOKEN" =~ [A-Z] ]]; then 639 | test_response=$(curl --silent --request GET --url https://api.cloudflare.com/client/v4/zones --header "Authorization: Bearer ${CFTOKEN}" --header "Content-Type: application/json") 640 | else 641 | test_response=$(curl --silent --request GET --url https://api.cloudflare.com/client/v4/zones --header "X-Auth-Key: ${CFTOKEN}" --header "X-Auth-Email: ${EMAIL}" --header "Content-Type: application/json") 642 | fi 643 | } 644 | 645 | ################################### 646 | ### Domain validation in cloudflare 647 | ################################### 648 | check_cf_token() { 649 | while ! echo "$test_response" | grep -qE "\"${testdomain}\"|\"#dns_records:edit\"|\"#dns_records:read\"|\"#zone:read\""; do 650 | local temp_domain 651 | DOMAIN="" 652 | SUBDOMAIN="" 653 | 654 | while [[ -z "$temp_domain" ]]; do 655 | reading " $(text 13) " temp_domain 656 | echo 657 | done 658 | 659 | temp_domain=$(echo "$temp_domain" | sed -E 's/^https?:\/\///' | sed -E 's/(:[0-9]+)?(\/[a-zA-Z0-9_\-\/]+)?$//') 660 | 661 | if [[ "$temp_domain" =~ ${regex[domain]} ]]; then 662 | SUBDOMAIN="$temp_domain" # Весь домен сохраняем в SUBDOMAIN 663 | DOMAIN="${BASH_REMATCH[2]}" # Извлекаем домен второго уровня 664 | else 665 | DOMAIN="$temp_domain" # Если это домен второго уровня, то просто сохраняем 666 | SUBDOMAIN="www.$temp_domain" # Для домена второго уровня подставляем www в SUBDOMAIN 667 | fi 668 | 669 | while [[ -z $EMAIL ]]; do 670 | reading " $(text 15) " EMAIL 671 | echo 672 | done 673 | 674 | while [[ -z $CFTOKEN ]]; do 675 | reading " $(text 16) " CFTOKEN 676 | done 677 | 678 | [[ ${args[skip-check]} == "false" ]] && get_test_response 679 | info " $(text 17) " 680 | done 681 | } 682 | 683 | ################################### 684 | ### Processing paths with a loop 685 | ################################### 686 | validate_path() { 687 | local VARIABLE_NAME="$1" 688 | local PATH_VALUE 689 | 690 | # Проверка на пустое значение 691 | while true; do 692 | case "$VARIABLE_NAME" in 693 | CDNGRPC) 694 | reading " $(text 20) " PATH_VALUE 695 | ;; 696 | CDNSPLIT) 697 | reading " $(text 21) " PATH_VALUE 698 | ;; 699 | CDNHTTPU) 700 | reading " $(text 22) " PATH_VALUE 701 | ;; 702 | CDNWS) 703 | reading " $(text 23) " PATH_VALUE 704 | ;; 705 | METRICS) 706 | reading " $(text 24) " PATH_VALUE 707 | ;; 708 | ADGUARDPATH) 709 | reading " $(text 25) " PATH_VALUE 710 | ;; 711 | WEB_BASE_PATH) 712 | reading " $(text 26) " PATH_VALUE 713 | ;; 714 | SUB_PATH) 715 | reading " $(text 27) " PATH_VALUE 716 | ;; 717 | SUB_JSON_PATH) 718 | reading " $(text 28) " PATH_VALUE 719 | ;; 720 | esac 721 | 722 | if [[ -z "$PATH_VALUE" ]]; then 723 | warning " $(text 29) " 724 | echo 725 | elif [[ $PATH_VALUE =~ ['{}\$/\\'] ]]; then 726 | warning " $(text 30) " 727 | echo 728 | else 729 | break 730 | fi 731 | done 732 | 733 | # Экранируем пробелы в пути 734 | local ESCAPED_PATH=$(echo "$PATH_VALUE" | sed 's/ /\\ /g') 735 | 736 | # Присваиваем значение переменной 737 | case "$VARIABLE_NAME" in 738 | CDNGRPC) 739 | export CDNGRPC="$ESCAPED_PATH" 740 | ;; 741 | CDNSPLIT) 742 | export CDNSPLIT="$ESCAPED_PATH" 743 | ;; 744 | CDNHTTPU) 745 | export CDNHTTPU="$ESCAPED_PATH" 746 | ;; 747 | CDNWS) 748 | export CDNWS="$ESCAPED_PATH" 749 | ;; 750 | METRICS) 751 | export METRICS="$ESCAPED_PATH" 752 | ;; 753 | ADGUARDPATH) 754 | export ADGUARDPATH="$ESCAPED_PATH" 755 | ;; 756 | WEB_BASE_PATH) 757 | export WEB_BASE_PATH="$ESCAPED_PATH" 758 | ;; 759 | SUB_PATH) 760 | export SUB_PATH="$ESCAPED_PATH" 761 | ;; 762 | SUB_JSON_PATH) 763 | export SUB_JSON_PATH="$ESCAPED_PATH" 764 | ;; 765 | esac 766 | } 767 | 768 | ################################### 769 | ### DNS Selection 770 | ################################### 771 | choise_dns () { 772 | while true; do 773 | hint " $(text 31) \n" && reading " $(text 1) " CHOISE_DNS 774 | case $CHOISE_DNS in 775 | 1) 776 | info " $(text 32) " 777 | break 778 | ;; 779 | 2) 780 | info " $(text 25) " 781 | if [[ ${args[generate]} == "true" ]]; then 782 | ADGUARDPATH=$(eval ${generate[path]}) 783 | else 784 | echo 785 | tilda "$(text 10)" 786 | validate_path ADGUARDPATH 787 | fi 788 | echo 789 | break 790 | ;; 791 | *) 792 | info " $(text 33) " 793 | ;; 794 | esac 795 | done 796 | } 797 | 798 | ################################### 799 | ### Data entry 800 | ################################### 801 | data_entry() { 802 | tilda "$(text 10)" 803 | # reading " $(text 70) " SECRET_PASSWORD 804 | 805 | # tilda "$(text 10)" 806 | 807 | reading " $(text 11) " USERNAME 808 | echo 809 | reading " $(text 12) " PASSWORD 810 | [[ ${args[addu]} == "true" ]] && add_user 811 | 812 | check_cf_token 813 | 814 | tilda "$(text 10)" 815 | 816 | choise_dns 817 | 818 | # reading " $(text 19) " REALITY 819 | 820 | if [[ ${args[generate]} == "true" ]]; then 821 | # echo 822 | # CDNGRPC=$(eval ${generate[path]}) 823 | # CDNSPLIT=$(eval ${generate[path]}) 824 | # CDNHTTPU=$(eval ${generate[path]}) 825 | # CDNWS=$(eval ${generate[path]}) 826 | WEB_BASE_PATH=$(eval ${generate[path]}) 827 | SUB_PATH=$(eval ${generate[path]}) 828 | SUB_JSON_PATH=$(eval ${generate[path]}) 829 | else 830 | # validate_path CDNGRPC 831 | # echo 832 | # validate_path CDNSPLIT 833 | # echo 834 | # validate_path CDNHTTPU 835 | # echo 836 | # validate_path CDNWS 837 | # tilda "$(text 10)" 838 | validate_path WEB_BASE_PATH 839 | echo 840 | validate_path SUB_PATH 841 | echo 842 | validate_path SUB_JSON_PATH 843 | fi 844 | 845 | if [[ ${args[mon]} == "true" ]]; then 846 | if [[ ${args[generate]} == "true" ]]; then 847 | METRICS=$(eval ${generate[path]}) 848 | else 849 | echo 850 | validate_path METRICS 851 | fi 852 | fi 853 | 854 | if [[ ${args[ssh]} == "true" ]]; then 855 | tilda "$(text 10)" 856 | reading " $(text 54) " ANSWER_SSH 857 | if [[ "${ANSWER_SSH,,}" == "y" ]]; then 858 | info " $(text 48) " 859 | out_data " $(text 49) " 860 | echo 861 | out_data " $(text 50) " 862 | out_data " $(text 51) " 863 | echo 864 | out_data " $(text 52)" "type \$env:USERPROFILE\.ssh\id_rsa.pub | ssh -p 22 ${USERNAME}@${IP4} \"cat >> ~/.ssh/authorized_keys\"" 865 | out_data " $(text 53)" "ssh-copy-id -p 22 ${USERNAME}@${IP4}" 866 | echo 867 | 868 | # Цикл проверки наличия ключей 869 | while true; do 870 | if [[ -s "/home/${USERNAME}/.ssh/authorized_keys" || -s "/root/.ssh/authorized_keys" ]]; then 871 | info " $(text 56) " # Ключи найдены 872 | SSH_OK=true 873 | break 874 | else 875 | warning " $(text 55) " # Ключи отсутствуют 876 | echo 877 | reading " $(text 54) " ANSWER_SSH 878 | if [[ "${ANSWER_SSH,,}" != "y" ]]; then 879 | warning " $(text 9) " # Настройка отменена 880 | SSH_OK=false 881 | break 882 | fi 883 | fi 884 | done 885 | tilda "$(text 10)" 886 | else 887 | warning " $(text 9) " # Настройка пропущена 888 | SSH_OK=false 889 | fi 890 | fi 891 | 892 | if [[ ${args[tgbot]} == "true" ]]; then 893 | reading " $(text 35) " ADMIN_ID 894 | echo 895 | reading " $(text 34) " BOT_TOKEN 896 | tilda "$(text 10)" 897 | fi 898 | 899 | SUB_URI=https://${DOMAIN}/${SUB_PATH}/ 900 | SUB_JSON_URI=https://${DOMAIN}/${SUB_JSON_PATH}/ 901 | } 902 | 903 | ################################### 904 | ### Install NGINX 905 | ################################### 906 | nginx_gpg() { 907 | case "$SYSTEM" in 908 | Debian) 909 | ${PACKAGE_INSTALL[int]} debian-archive-keyring 910 | curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ 911 | | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null 912 | gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg 913 | echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ 914 | http://nginx.org/packages/debian `lsb_release -cs` nginx" \ 915 | | tee /etc/apt/sources.list.d/nginx.list 916 | echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ 917 | | tee /etc/apt/preferences.d/99nginx 918 | ;; 919 | 920 | Ubuntu) 921 | ${PACKAGE_INSTALL[int]} ubuntu-keyring 922 | curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ 923 | | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null 924 | gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg 925 | echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ 926 | http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ 927 | | tee /etc/apt/sources.list.d/nginx.list 928 | echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ 929 | | tee /etc/apt/preferences.d/99nginx 930 | ;; 931 | 932 | CentOS|Fedora) 933 | ${PACKAGE_INSTALL[int]} yum-utils 934 | cat < /etc/yum.repos.d/nginx.repo 935 | [nginx-stable] 936 | name=nginx stable repo 937 | baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/ 938 | gpgcheck=1 939 | enabled=1 940 | gpgkey=https://nginx.org/keys/nginx_signing.key 941 | module_hotfixes=true 942 | 943 | [nginx-mainline] 944 | name=nginx mainline repo 945 | baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/ 946 | gpgcheck=1 947 | enabled=0 948 | gpgkey=https://nginx.org/keys/nginx_signing.key 949 | module_hotfixes=true 950 | EOL 951 | ;; 952 | esac 953 | ${PACKAGE_UPDATE[int]} 954 | ${PACKAGE_INSTALL[int]} nginx 955 | systemctl daemon-reload 956 | systemctl start nginx 957 | systemctl enable nginx 958 | systemctl restart nginx 959 | systemctl status nginx --no-pager 960 | } 961 | 962 | ################################### 963 | ### Installing packages 964 | ################################### 965 | installation_of_utilities() { 966 | info " $(text 36) " 967 | case "$SYSTEM" in 968 | Debian|Ubuntu) 969 | DEPS_PACK_CHECK=("jq" "ufw" "zip" "wget" "gpg" "nano" "cron" "sqlite3" "certbot" "openssl" "netstat" "htpasswd" "update-ca-certificates" "add-apt-repository" "unattended-upgrades" "certbot-dns-cloudflare") 970 | DEPS_PACK_INSTALL=("jq" "ufw" "zip" "wget" "gnupg2" "nano" "cron" "sqlite3" "certbot" "openssl" "net-tools" "apache2-utils" "ca-certificates" "software-properties-common" "unattended-upgrades" "python3-certbot-dns-cloudflare") 971 | 972 | for g in "${!DEPS_PACK_CHECK[@]}"; do 973 | [ ! -x "$(type -p ${DEPS_PACK_CHECK[g]})" ] && [[ ! "${DEPS_PACK[@]}" =~ "${DEPS_PACK_INSTALL[g]}" ]] && DEPS_PACK+=(${DEPS_PACK_INSTALL[g]}) 974 | done 975 | 976 | if [ "${#DEPS_PACK[@]}" -ge 1 ]; then 977 | info " $(text 77) ": ${DEPS_PACK[@]} 978 | ${PACKAGE_UPDATE[int]} 979 | ${PACKAGE_INSTALL[int]} ${DEPS_PACK[@]} 980 | else 981 | info " $(text 78) " 982 | fi 983 | ;; 984 | 985 | 986 | CentOS|Fedora) 987 | DEPS_PACK_CHECK=("jq" "zip" "tar" "wget" "gpg" "nano" "crontab" "sqlite3" "openssl" "netstat" "nslookup" "htpasswd" "certbot" "update-ca-certificates" "certbot-dns-cloudflare") 988 | DEPS_PACK_INSTALL=("jq" "zip" "tar" "wget" "gnupg2" "nano" "cronie" "sqlite" "openssl" "net-tools" "bind-utils" "httpd-tools" "certbot" "ca-certificates" "python3-certbot-dns-cloudflare") 989 | 990 | for g in "${!DEPS_PACK_CHECK[@]}"; do 991 | [ ! -x "$(type -p ${DEPS_PACK_CHECK[g]})" ] && [[ ! "${DEPS_PACK[@]}" =~ "${DEPS_PACK_INSTALL[g]}" ]] && DEPS_PACK+=(${DEPS_PACK_INSTALL[g]}) 992 | done 993 | 994 | if [ "${#DEPS_PACK[@]}" -ge 1 ]; then 995 | info " $(text 77) ": ${DEPS_PACK[@]} 996 | ${PACKAGE_UPDATE[int]} 997 | ${PACKAGE_INSTALL[int]} ${DEPS_PACK[@]} 998 | else 999 | info " $(text 78) " 1000 | fi 1001 | ;; 1002 | esac 1003 | 1004 | nginx_gpg 1005 | ${PACKAGE_INSTALL[int]} systemd-resolved 1006 | tilda "$(text 10)" 1007 | } 1008 | 1009 | ################################### 1010 | ### DNS Systemd-resolved 1011 | ################################### 1012 | dns_systemd_resolved() { 1013 | tee /etc/systemd/resolved.conf <> /etc/apt/apt.conf.d/50unattended-upgrades 1136 | echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true | debconf-set-selections 1137 | dpkg-reconfigure -f noninteractive unattended-upgrades 1138 | systemctl restart unattended-upgrades 1139 | ;; 1140 | 1141 | CentOS|Fedora) 1142 | cat > /etc/dnf/automatic.conf <> /etc/sysctl.conf 1169 | fi 1170 | 1171 | if [[ ! "$(sysctl net.ipv4.tcp_congestion_control)" == *"bbr" ]]; then 1172 | echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.conf 1173 | fi 1174 | sysctl -p 1175 | } 1176 | 1177 | ################################### 1178 | ### Disabling IPv6 1179 | ################################### 1180 | disable_ipv6() { 1181 | info " $(text 42) " 1182 | interface_name=$(ifconfig -s | awk 'NR==2 {print $1}') 1183 | if [[ ! "$(sysctl net.ipv6.conf.all.disable_ipv6)" == *"= 1" ]]; then 1184 | echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf 1185 | fi 1186 | 1187 | if [[ ! "$(sysctl net.ipv6.conf.default.disable_ipv6)" == *"= 1" ]]; then 1188 | echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf 1189 | fi 1190 | if [[ ! "$(sysctl net.ipv6.conf.lo.disable_ipv6)" == *"= 1" ]]; then 1191 | echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf 1192 | fi 1193 | 1194 | if [[ ! "$(sysctl net.ipv6.conf.$interface_name.disable_ipv6)" == *"= 1" ]]; then 1195 | echo "net.ipv6.conf.$interface_name.disable_ipv6 = 1" >> /etc/sysctl.conf 1196 | fi 1197 | sysctl -p 1198 | tilda "$(text 10)" 1199 | } 1200 | 1201 | ################################### 1202 | ### WARP 1203 | ################################### 1204 | warp() { 1205 | info " $(text 43) " 1206 | 1207 | mkdir -p /usr/local/reverse_proxy/ 1208 | mkdir -p /etc/systemd/system/warp-svc.service.d 1209 | cd /usr/local/reverse_proxy/ 1210 | 1211 | case "$SYSTEM" in 1212 | Debian|Ubuntu) 1213 | while ! wget --progress=dot:mega --timeout=30 --tries=10 --retry-connrefused "https://pkg.cloudflareclient.com/pool/$(grep "VERSION_CODENAME=" /etc/os-release | cut -d "=" -f 2)/main/c/cloudflare-warp/cloudflare-warp_2024.6.497-1_amd64.deb"; do 1214 | warning " $(text 38) " 1215 | sleep 3 1216 | done 1217 | apt install -y ./cloudflare-warp_2024.6.497-1_amd64.deb 1218 | ;; 1219 | 1220 | CentOS|Fedora) 1221 | while ! wget --progress=dot:mega --timeout=30 --tries=10 --retry-connrefused "https://pkg.cloudflareclient.com/rpm/x86_64/cloudflare-warp-2024.6.497-1.x86_64.rpm"; do 1222 | warning " $(text 38) " 1223 | sleep 3 1224 | done 1225 | sudo yum localinstall -y cloudflare-warp-2024.6.497-1.x86_64.rpm 1226 | ;; 1227 | esac 1228 | 1229 | rm -rf cloudflare-warp_* 1230 | cd ~ 1231 | 1232 | cat > /etc/systemd/system/warp-svc.service.d/override.conf < ${CF_CREDENTIALS_PATH} < ${CF_CREDENTIALS_PATH} <> /etc/letsencrypt/renewal/${DOMAIN}.conf 1301 | # echo "" 1302 | # openssl dhparam -out /etc/nginx/dhparam.pem 2048 1303 | # else 1304 | # echo "renew_hook = cat /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /etc/letsencrypt/live/${DOMAIN}/privkey.pem > /etc/haproxy/certs/${DOMAIN}.pem && systemctl restart haproxy" >> /etc/letsencrypt/renewal/${DOMAIN}.conf 1305 | # echo "" 1306 | # openssl dhparam -out /etc/haproxy/dhparam.pem 2048 1307 | # fi 1308 | } 1309 | 1310 | ################################### 1311 | ### Node exporter 1312 | ################################### 1313 | monitoring() { 1314 | info " $(text 66) " 1315 | bash <(curl -Ls https://github.com/cortez24rus/grafana-prometheus/raw/refs/heads/main/prometheus_node_exporter.sh) 1316 | 1317 | COMMENT_METRIC="location /${METRICS} { 1318 | auth_basic \"Restricted Content\"; 1319 | auth_basic_user_file /etc/nginx/.htpasswd; 1320 | proxy_pass http://127.0.0.1:9100/metrics; 1321 | proxy_set_header Host \$host; 1322 | proxy_set_header X-Real-IP \$remote_addr; 1323 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 1324 | proxy_set_header X-Forwarded-Proto \$scheme; 1325 | break; 1326 | }" 1327 | 1328 | tilda "$(text 10)" 1329 | } 1330 | 1331 | ################################### 1332 | ### NGINX 1333 | ################################### 1334 | nginx_setup() { 1335 | info " $(text 45) " 1336 | 1337 | mkdir -p /etc/nginx/stream-enabled/ 1338 | mkdir -p /etc/nginx/conf.d/ 1339 | rm -rf /etc/nginx/conf.d/default.conf 1340 | touch /etc/nginx/.htpasswd 1341 | htpasswd -nb "$USERNAME" "$PASSWORD" > /etc/nginx/.htpasswd 1342 | openssl dhparam -out /etc/nginx/dhparam.pem 2048 1343 | 1344 | case "$SYSTEM" in 1345 | Debian|Ubuntu) 1346 | USERNGINX="www-data" 1347 | ;; 1348 | 1349 | CentOS|Fedora) 1350 | USERNGINX="nginx" 1351 | ;; 1352 | esac 1353 | 1354 | nginx_conf 1355 | stream_conf 1356 | local_conf 1357 | random_site 1358 | 1359 | systemctl daemon-reload 1360 | systemctl restart nginx 1361 | nginx -s reload 1362 | 1363 | tilda "$(text 10)" 1364 | } 1365 | 1366 | ################################### 1367 | ### http conf 1368 | ################################### 1369 | nginx_conf() { 1370 | cat > /etc/nginx/nginx.conf < /etc/nginx/stream-enabled/stream.conf < /etc/nginx/conf.d/local.conf <|\.\.\.|\.\.\/|\/\/\/)"){set \$hack 1;} 1512 | error_page 400 402 403 500 501 502 503 504 =404 /404; 1513 | proxy_intercept_errors on; 1514 | 1515 | if (\$host = ${IP4}) { 1516 | return 444; 1517 | } 1518 | # PANEL 1519 | location /${WEB_BASE_PATH} { 1520 | if (\$hack = 1) {return 404;} 1521 | proxy_redirect off; 1522 | proxy_set_header Host \$host; 1523 | proxy_set_header X-Real-IP \$remote_addr; 1524 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 1525 | proxy_set_header X-Real-IP \$remote_addr; 1526 | proxy_set_header Range \$http_range; 1527 | proxy_set_header If-Range \$http_if_range; 1528 | proxy_pass http://127.0.0.1:36075/${WEB_BASE_PATH}; 1529 | break; 1530 | } 1531 | # SUB 1532 | location /${SUB_PATH} { 1533 | if (\$hack = 1) {return 404;} 1534 | proxy_redirect off; 1535 | proxy_set_header Host \$host; 1536 | proxy_set_header X-Real-IP \$remote_addr; 1537 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 1538 | proxy_pass http://127.0.0.1:36074/${SUB_PATH}; 1539 | break; 1540 | } 1541 | # SUB JSON 1542 | location /${SUB_JSON_PATH} { 1543 | if (\$hack = 1) {return 404;} 1544 | proxy_redirect off; 1545 | proxy_set_header Host \$host; 1546 | proxy_set_header X-Real-IP \$remote_addr; 1547 | proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; 1548 | proxy_pass http://127.0.0.1:36074/${SUB_JSON_PATH}; 1549 | break; 1550 | } 1551 | # Node Exporter 1552 | ${COMMENT_METRIC} 1553 | # Adguard Home 1554 | ${COMMENT_AGH} 1555 | } 1556 | EOF 1557 | } 1558 | 1559 | ################################### 1560 | ### Selecting a random site 1561 | ################################### 1562 | random_site() { 1563 | info " $(text 79) " 1564 | mkdir -p /var/www/html/ /usr/local/reverse_proxy/ 1565 | 1566 | cd /usr/local/reverse_proxy/ || echo "Не удалось перейти в /usr/local/reverse_proxy/" 1567 | 1568 | if [[ ! -d "simple-web-templates-main" ]]; then 1569 | while ! wget -q --progress=dot:mega --timeout=30 --tries=10 --retry-connrefused "https://github.com/cortez24rus/simple-web-templates/archive/refs/heads/main.zip"; do 1570 | warning " $(text 38) " 1571 | sleep 3 1572 | done 1573 | unzip -q main.zip &>/dev/null && rm -f main.zip 1574 | fi 1575 | 1576 | cd simple-web-templates-main || echo "Не удалось перейти в папку с шаблонами" 1577 | 1578 | rm -rf assets ".gitattributes" "README.md" "_config.yml" 1579 | 1580 | RandomHTML=$(ls -d */ | shuf -n1) # Обновил для выбора случайного подкаталога 1581 | info " $(text 80) ${RandomHTML}" 1582 | 1583 | # Если шаблон существует, копируем его в /var/www/html 1584 | if [[ -d "${RandomHTML}" && -d "/var/www/html/" ]]; then 1585 | echo "Копируем шаблон в /var/www/html/..." 1586 | rm -rf /var/www/html/* # Очищаем старую папку 1587 | cp -a "${RandomHTML}/." /var/www/html/ || echo "Ошибка при копировании шаблона" 1588 | else 1589 | echo "Ошибка при извлечении шаблона!" 1590 | fi 1591 | 1592 | cd ~ 1593 | } 1594 | 1595 | ################################### 1596 | ### Key generation 1597 | ################################### 1598 | generate_keys() { 1599 | # Генерация пары ключей X25519 с использованием xray 1600 | local KEY_PAIR=$(/usr/local/x-ui/bin/xray-linux-amd64 x25519) 1601 | local PRIVATE_KEY=$(echo "$KEY_PAIR" | grep "Private key:" | awk '{print $3}') 1602 | local PUBLIC_KEY=$(echo "$KEY_PAIR" | grep "Public key:" | awk '{print $3}') 1603 | 1604 | # Возвращаем ключи в виде строки, разделенной пробелом 1605 | echo "$PRIVATE_KEY $PUBLIC_KEY" 1606 | } 1607 | 1608 | ################################### 1609 | ### Settings reality (Steal Oneself) 1610 | ################################### 1611 | settings_steal() { 1612 | read PRIVATE_KEY0 PUBLIC_KEY0 <<< "$(generate_keys)" 1613 | STREAM_SETTINGS_STEAL=$(cat < /dev/null 2>&1 1783 | 1784 | settings_steal 1785 | settings_xtls 1786 | sniffing_inbounds 1787 | database_change 1788 | 1789 | x-ui stop 1790 | 1791 | rm -rf /etc/x-ui/x-ui.db.backup 1792 | [ -f /etc/x-ui/x-ui.db ] && mv /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db.backup 1793 | mv x-ui.db /etc/x-ui/ 1794 | 1795 | x-ui start 1796 | echo -e "20\n1" | x-ui > /dev/null 2>&1 1797 | tilda "$(text 10)" 1798 | } 1799 | 1800 | ################################### 1801 | ### Firewall 1802 | ################################### 1803 | enabling_security() { 1804 | info " $(text 47) " 1805 | BLOCK_ZONE_IP=$(echo ${IP4} | cut -d '.' -f 1-3).0/22 1806 | 1807 | case "$SYSTEM" in 1808 | Debian|Ubuntu ) 1809 | ufw --force reset 1810 | ufw allow 22/tcp 1811 | ufw allow 443/tcp 1812 | ufw insert 1 deny from "$BLOCK_ZONE_IP" 1813 | ufw --force enable 1814 | ;; 1815 | 1816 | CentOS|Fedora ) 1817 | systemctl enable --now firewalld 1818 | firewall-cmd --permanent --zone=public --add-port=22/tcp 1819 | firewall-cmd --permanent --zone=public --add-port=80/tcp 1820 | firewall-cmd --permanent --zone=public --add-port=443/tcp 1821 | firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4' source address='$BLOCK_ZONE_IP' reject" 1822 | firewall-cmd --reload 1823 | ;; 1824 | esac 1825 | 1826 | tilda "$(text 10)" 1827 | } 1828 | 1829 | ################################### 1830 | ### SSH 1831 | ################################### 1832 | ssh_setup() { 1833 | if [[ "${ANSWER_SSH,,}" == "y" ]]; then 1834 | info " $(text 48) " 1835 | sed -i -e " 1836 | s/#Port/Port/g; 1837 | s/Port 22/Port 22/g; 1838 | s/#PermitRootLogin/PermitRootLogin/g; 1839 | s/PermitRootLogin yes/PermitRootLogin prohibit-password/g; 1840 | s/#PubkeyAuthentication/PubkeyAuthentication/g; 1841 | s/PubkeyAuthentication no/PubkeyAuthentication yes/g; 1842 | s/#PasswordAuthentication/PasswordAuthentication/g; 1843 | s/PasswordAuthentication yes/PasswordAuthentication no/g; 1844 | s/#PermitEmptyPasswords/PermitEmptyPasswords/g; 1845 | s/PermitEmptyPasswords yes/PermitEmptyPasswords no/g; 1846 | " /etc/ssh/sshd_config 1847 | 1848 | # Настройка баннера 1849 | cat > /etc/motd </dev/null 18 | systemctl stop reverse_proxy_bot.service >/dev/null 19 | systemctl daemon-reload >/dev/null 20 | 21 | # Создание директории и виртуального окружения 22 | mkdir -p /usr/local/reverse_proxy/ 23 | python3 -m venv /usr/local/reverse_proxy/reverse_proxy_env || { echo "Ошибка создания виртуального окружения"; exit 1; } 24 | 25 | # Путь к конфигурационному файлу 26 | CONFIG_FILE="/usr/local/reverse_proxy/reverse_proxy_bot_config.json" 27 | 28 | # Создаем или обновляем конфигурационный файл 29 | cat > $CONFIG_FILE < /etc/systemd/system/reverse_proxy_bot.service </dev/null && rm -f main.zip 37 | fi 38 | 39 | cd simple-web-templates-main || { msg_err "Не удалось перейти в папку с шаблонами"; exit 1; } 40 | 41 | msg_inf "Удаляем ненужные файлы..." 42 | rm -rf assets ".gitattributes" "README.md" "_config.yml" 43 | 44 | RandomHTML=$(ls -d */ | shuf -n1) # Обновил для выбора случайного подкаталога 45 | msg_inf "Random template name: ${RandomHTML}" 46 | 47 | # Если шаблон существует, копируем его в /var/www/html 48 | if [[ -d "${RandomHTML}" && -d "/var/www/html/" ]]; then 49 | msg_inf "Копируем шаблон в /var/www/html/..." 50 | rm -rf /var/www/html/* # Очищаем старую папку 51 | cp -a "${RandomHTML}/." /var/www/html/ || { msg_err "Ошибка при копировании шаблона"; exit 1; } 52 | msg_ok "Шаблон успешно извлечен и установлен!" 53 | else 54 | msg_err "Ошибка при извлечении шаблона!" 55 | exit 1 56 | fi 57 | 58 | cd ~ || { msg_err "Не удалось вернуться в домашнюю директорию"; exit 1; } 59 | -------------------------------------------------------------------------------- /warp/xui-rp-warp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Установка ключа и репозитория Cloudflare WARP 4 | # curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg 5 | # echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(grep "VERSION_CODENAME=" /etc/os-release | cut -d "=" -f 2) main" | tee /etc/apt/sources.list.d/cloudflare-client.list 6 | # apt-get update && apt-get install cloudflare-warp -y 7 | 8 | export DEBIAN_FRONTEND=noninteractive 9 | 10 | mkdir -p /usr/local/xui-rp/ 11 | 12 | echo "Попытка скачать пакет..." 13 | while ! wget --progress=dot:mega --timeout=30 --tries=10 --retry-connrefused "https://pkg.cloudflareclient.com/pool/$(grep "VERSION_CODENAME=" /etc/os-release | cut -d "=" -f 2)/main/c/cloudflare-warp/cloudflare-warp_2024.6.497-1_amd64.deb" -O /usr/local/xui-rp/cloudflare-warp_2024.6.497-1_amd64.deb; do 14 | echo "Не удалось скачать. Повторная попытка через 3 секунды..." 15 | sleep 3 16 | done 17 | echo "Скачивание завершено успешно." 18 | 19 | cd /usr/local/xui-rp/ 20 | apt install -y ./cloudflare-warp_2024.6.497-1_amd64.deb 21 | rm -rf cloudflare-warp_* 22 | cd ~/ 23 | 24 | # Создание директории для конфигурации 25 | mkdir -p /etc/systemd/system/warp-svc.service.d 26 | 27 | # Настройка уровня логирования 28 | cat > /etc/systemd/system/warp-svc.service.d/override.conf <