From 82b1a38592a2b5d0f98373f4583159876bf9d823 Mon Sep 17 00:00:00 2001 From: anten-ka Date: Fri, 20 Mar 2026 16:29:48 +0300 Subject: [PATCH] v2.1: unified client toggle, 3X-UI SOCKS5 submenu, fix 14 bugs across 3 audit cycles Made-with: Cursor --- README.md | 65 +++++----- warp.sh | 382 ++++++++++++++++++++++++------------------------------ 2 files changed, 200 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index 8fe97f6..5283c19 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ WireGuard-интерфейс WARP внутри Docker-контейнера Amnez | JSON-конфиги для Xray | ✅ | — | | Маршрутизация по доменам | ✅ | — | | Изменение порта SOCKS5 | ✅ | — | -| Управление клиентами (add/remove) | — | ✅ | +| Управление клиентами (переключатели ✅/☐ в одном меню) | — | ✅ | | WireGuard внутри Docker | — | ✅ | | Перезапуск контейнера | — | ✅ | @@ -249,31 +249,30 @@ curl -s --proxy socks5h://127.0.0.1:40000 ifconfig.me - Поднимет WG-интерфейс `warp` внутри контейнера - Покажет WARP IP -### Шаг 3. Добавление клиентов в WARP +### Шаг 3. Включение клиентов в WARP После установки WARP нужно указать, **каких клиентов** пускать через Cloudflare. **Через SSH-меню:** -1. Выберите пункт **6** — ➕ Добавить клиентов в WARP -2. Вы увидите список всех клиентов AmneziaWG с их IP и именами -3. Введите номер клиента, которого хотите добавить, или `all` для всех +1. Выберите пункт **6** — переключение клиентов WARP (единое меню) +2. Вы увидите список всех клиентов AmneziaWG с их IP и именами; напротив каждого — **✅** (уже в WARP) или **☐** (не в WARP) +3. Введите номер клиента, чтобы переключить состояние (в WARP или вне WARP) 4. Скрипт: - - Добавит `ip rule` для маршрутизации трафика клиента через WARP - - Настроит `iptables` NAT для корректного выхода через WG-интерфейс + - Добавит или снимет `ip rule` для маршрутизации трафика клиента через WARP + - Настроит или уберёт `iptables` NAT для корректного выхода через WG-интерфейс - Пропишет правила в `start.sh` контейнера (персистентность) **Через Telegram-бот:** -1. Нажмите **Клиенты** → **➕ Добавить** -2. Выберите клиента из списка (✅ = уже в WARP, ➕ = можно добавить) -3. Или нажмите **✅ Все в WARP** для добавления всех +1. Нажмите **👥 Клиенты WARP** — откроется тот же список-переключатель +2. Напротив клиентов отображаются **✅** (в WARP) и **☐** (вне WARP); выбор пункта переключает состояние ### Шаг 4. Проверка 1. Подключитесь к VPN через приложение Amnezia 2. Откройте [https://whoer.net](https://whoer.net) или [https://ifconfig.me](https://ifconfig.me) -3. Если ваш клиент добавлен в WARP — вы увидите **IP Cloudflare** +3. Если ваш клиент включён в WARP — вы увидите **IP Cloudflare** 4. Если клиент не в WARP — вы увидите **обычный IP сервера** Проверка через SSH (внутри контейнера): @@ -284,17 +283,17 @@ docker exec <имя_контейнера> curl -s --interface warp ifconfig.me ### Управление клиентами +В SSH и в боте используется **одно меню переключателей**: у каждого клиента видно **✅** (трафик через WARP) или **☐** (напрямую); действие по пункту меняет состояние. + | Действие | SSH-меню | Telegram-бот | |----------|----------|--------------| -| Добавить в WARP | пункт **6** | Клиенты → ➕ Добавить | -| Убрать из WARP | пункт **7** | Клиенты → ➖ Убрать | -| Список клиентов в WARP | пункт **8** | Клиенты | +| Включить/выключить клиента в WARP (список ✅/☐) | пункт **6** | **👥 Клиенты WARP** | | Перевыпуск ключа | пункт **5** | 🔑 Перевыпуск ключа | -| Перезапуск контейнера | пункт **9** | 🔄 Контейнер | +| Перезапуск контейнера | пункт **7** | 🔄 Контейнер | -### Что происходит при добавлении клиента +### Что происходит при включении клиента в WARP -Когда вы добавляете клиента (например, `10.8.1.2`) в WARP, скрипт: +Когда вы переводите клиента в WARP (например, `10.8.1.2`), скрипт: 1. Создаёт **ip rule**: `ip rule add from 10.8.1.2 table 51820` 2. Добавляет **iptables NAT**: `iptables -t nat -A POSTROUTING -s 10.8.1.2 -o warp -j MASQUERADE` @@ -320,7 +319,7 @@ docker exec <имя_контейнера> curl -s --interface warp ifconfig.me ## Меню WARP Manager -Меню организовано по секциям. Нумерация пунктов 1–5 и 10–13 одинакова в обоих режимах, а секция 6–9 адаптируется. +Меню организовано по секциям. Пункты **1–5** (WARP) и **8–11** (бот, промо, инструкция, удаление) одинаковы в обоих режимах; пункты **6–7** зависят от режима (3X-UI или AmneziaWG). ### Общая секция — WARP-ключ (оба режима) @@ -336,26 +335,24 @@ docker exec <имя_контейнера> curl -s --interface warp ifconfig.me | Пункт | Действие | |-------|----------| -| 6 | JSON конфиг для 3X-UI | +| 6 | Настройки SOCKS5 / JSON для 3X-UI | | 7 | Изменить порт SOCKS5 | ### Секция AmneziaWG (только в режиме AmneziaWG) | Пункт | Действие | |-------|----------| -| 6 | ➕ Добавить клиентов в WARP | -| 7 | ➖ Убрать клиентов из WARP | -| 8 | Показать клиентов в WARP | -| 9 | Перезапуск контейнера | +| 6 | Переключение клиентов WARP (единое меню, ✅ / ☐) | +| 7 | Перезапуск контейнера | ### Общая секция — бот и прочее (оба режима) | Пункт | Действие | |-------|----------| -| 10 | Telegram Bot (настройка и управление) | -| 11 | PROMO | -| 12 | Инструкция | -| 13 | Полное удаление | +| 8 | Telegram Bot (настройка и управление) | +| 9 | PROMO | +| 10 | Инструкция | +| 11 | Полное удаление | | 0 | Выход | --- @@ -370,7 +367,7 @@ WARP Manager включает встроенный Telegram-бот для уда 2. Отправьте `/newbot` 3. Задайте имя и username бота 4. Скопируйте полученный **токен** -5. На сервере выполните `gowarp` → пункт **10** (Telegram Bot) +5. На сервере выполните `gowarp` → пункт **8** (Telegram Bot) 6. Выберите **1) Токен бота** — вставьте токен 7. Выберите **2) Chat ID (авто)**: - Откройте вашего бота в Telegram @@ -405,9 +402,7 @@ WARP Manager включает встроенный Telegram-бот для уда | ▶️ Запустить | Поднять WG-интерфейс WARP | | ⏹ Остановить | Опустить WG-интерфейс WARP | | 🔑 Перевыпуск ключа | Пересоздать WARP-профиль | -| 👥 Клиенты | Список клиентов в WARP | -| ➕ Добавить | Добавить клиента в WARP (выбор из списка) | -| ➖ Убрать | Убрать клиента из WARP | +| 👥 Клиенты WARP | Единое меню переключателей: список клиентов с **✅** (в WARP) и **☐** (вне WARP) | | 🔄 Контейнер | Перезапуск Docker-контейнера AmneziaWG | | 🖥 Система | CPU, RAM, диск, uptime | | 🏷 Хостинг | Промо-материалы | @@ -453,7 +448,7 @@ docker exec <контейнер> cat /opt/warp/warp.conf docker exec <контейнер> ip link show warp ``` -Попробуйте перевыпустить ключ (пункт 5) или перезапустить контейнер (пункт 9). +Попробуйте перевыпустить ключ (пункт 5) или перезапустить контейнер (пункт 7). ### Сайт всё ещё заблокирован после настройки (3X-UI) @@ -464,9 +459,9 @@ docker exec <контейнер> ip link show warp ### Клиент не видит WARP IP (AmneziaWG) -1. Убедитесь, что клиент **добавлен** в WARP (`gowarp` → пункт 8) +1. Убедитесь, что клиент **включён** в WARP (`gowarp` → пункт 6, в списке должна быть **✅**) 2. Проверьте, что интерфейс `warp` поднят (`gowarp` → пункт 4) -3. Перезапустите контейнер (`gowarp` → пункт 9) +3. Перезапустите контейнер (`gowarp` → пункт 7) 4. Переподключитесь к VPN на клиентском устройстве ### Как сменить WARP IP @@ -492,7 +487,7 @@ docker exec <контейнер> ip link show warp gowarp ``` -Выберите пункт **13** — Полное удаление. +Выберите пункт **11** — Полное удаление. ### Что удаляется (3X-UI) diff --git a/warp.sh b/warp.sh index 2c77009..dae5f5c 100644 --- a/warp.sh +++ b/warp.sh @@ -283,7 +283,7 @@ install_warp_3xui() { else echo -e "${YELLOW} ⚠ Подключение не подтверждено.${NC}" fi - echo -e "\n${WHITE}Интеграция с 3X-UI: пункт меню ${YELLOW}5${NC}" + echo -e "\n${WHITE}Настройки SOCKS5 и JSON: пункт меню ${YELLOW}6${NC}" read -p "Enter..." } @@ -292,8 +292,11 @@ start_warp_3xui() { is_warp_running_3xui && { echo -e "\n${YELLOW}Уже подключён.${NC}"; read -p "Enter..."; return; } echo -e "\n${YELLOW}Подключение...${NC}" warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 - is_warp_running_3xui && echo -e "${GREEN}[OK] Подключён.${NC}" && log_action "3XUI START" \ - || echo -e "${RED}Ошибка подключения.${NC}" + if is_warp_running_3xui; then + echo -e "${GREEN}[OK] Подключён.${NC}"; log_action "3XUI START" + else + echo -e "${RED}Ошибка подключения.${NC}" + fi read -p "Enter..." } @@ -613,7 +616,7 @@ install_warp_awg() { awg_detect_warp_exit_ip [ -n "$AWG_WARP_EXIT_IP" ] && echo -e "\n ${WHITE}WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}" - echo -e "\n${GREEN}WARP установлен! Добавьте клиентов через п.6.${NC}" + echo -e "\n${GREEN}WARP установлен! Управление клиентами — п.6.${NC}" log_action "AWG INSTALL: endpoint=${ep}, warp_ip=${AWG_WARP_EXIT_IP}" read -p "Enter..." } @@ -622,8 +625,11 @@ start_warp_awg() { is_warp_installed_awg || { echo -e "\n${RED}WARP не установлен (п.1).${NC}"; read -p "Enter..."; return; } is_warp_running_awg && { echo -e "\n${YELLOW}Уже работает.${NC}"; read -p "Enter..."; return; } echo -e "\n${YELLOW}Поднимаю warp...${NC}" - awg_warp_up && echo -e "${GREEN}[OK] WARP подключён.${NC}" && log_action "AWG START" \ - || echo -e "${RED}Ошибка.${NC}" + if awg_warp_up; then + echo -e "${GREEN}[OK] WARP подключён.${NC}"; log_action "AWG START" + else + echo -e "${RED}Ошибка.${NC}" + fi read -p "Enter..." } @@ -649,8 +655,8 @@ rekey_warp_awg() { awg_apply_rules awg_patch_start_sh awg_detect_warp_exit_ip - echo -e "${GREEN} ✓ Готово!" - [ -n "$AWG_WARP_EXIT_IP" ] && echo -e " Новый WARP IP: ${AWG_WARP_EXIT_IP}${NC}" + echo -e "${GREEN} ✓ Готово!${NC}" + [ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}Новый WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}" log_action "AWG REKEY: warp_ip=${AWG_WARP_EXIT_IP}" read -p "Enter..." } @@ -828,105 +834,72 @@ awg_get_client_ips() { fi } -awg_add_clients_ssh() { - awg_get_client_ips; awg_parse_clients_table; awg_load_clients - local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " - clear; echo -e "\n${CYAN}━━━ Добавить клиентов в WARP ━━━${NC}\n" - if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then echo -e "${RED}Нет клиентов.${NC}"; read -p "Enter..."; return; fi - local i=1 - for ip in "${AWG_CLIENT_IPS[@]}"; do - local label; label=$(awg_format_label "$ip") - if [[ "$warp_set" == *" $ip "* ]]; then - echo -e " ${GREEN}$i) $label [WARP]${NC}" - else - echo " $i) $label" +awg_toggle_clients_ssh() { + while true; do + awg_get_client_ips; awg_parse_clients_table; awg_load_clients + local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " + + clear; echo -e "\n${CYAN}━━━ Клиенты WARP ━━━${NC}\n" + + if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then + echo -e " ${RED}Нет клиентов в конфиге VPN.${NC}" + read -p "Enter..."; return fi - ((i++)) - done - echo " all) все клиенты" - echo -e " ${DIM}0) Отмена${NC}\n" - read -p "Номер (через запятую) или all: " answer - [ -z "$answer" ] || [ "$answer" = "0" ] && return - local -a new_ips=() - if [ "$answer" = "all" ]; then - new_ips=("${AWG_CLIENT_IPS[@]}") - else - IFS=',' read -ra parts <<< "$answer" - for p in "${parts[@]}"; do - p=$(echo "$p" | xargs) - [[ "$p" =~ ^[0-9]+$ ]] && (( p >= 1 && p <= ${#AWG_CLIENT_IPS[@]} )) && new_ips+=("${AWG_CLIENT_IPS[$((p-1))]}") + local i=1 warp_count=0 + for ip in "${AWG_CLIENT_IPS[@]}"; do + local label; label=$(awg_format_label "$ip") + if [[ "$warp_set" == *" $ip "* ]]; then + echo -e " ${GREEN}$i) ✅ $label${NC}" + ((warp_count++)) + else + echo -e " $i) ☐ $label" + fi + ((i++)) done - fi - echo "" - for nip in "${new_ips[@]}"; do - local found=0 - for eip in "${AWG_SELECTED_IPS[@]}"; do [ "$nip" = "$eip" ] && found=1 && break; done - if [ "$found" -eq 0 ]; then - AWG_SELECTED_IPS+=("$nip") - echo -e " ${GREEN}+${NC} $(awg_format_label "$nip")" + echo "" + echo -e " ${WHITE}В WARP: ${CYAN}${warp_count}${NC} из ${#AWG_CLIENT_IPS[@]}" + echo "" + echo -e " ${DIM}Введите номер для переключения (через запятую)${NC}" + echo -e " ${DIM}all) все в WARP | none) убрать всех | 0) назад${NC}" + echo "" + read -p " Выбор: " answer + + [ -z "$answer" ] || [ "$answer" = "0" ] && return + + local changed=0 + if [ "$answer" = "all" ]; then + AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}") + changed=1 + elif [ "$answer" = "none" ]; then + AWG_SELECTED_IPS=() + changed=1 else - echo -e " ${DIM}Уже в WARP: $(awg_format_label "$nip")${NC}" + IFS=',' read -ra parts <<< "$answer" + for p in "${parts[@]}"; do + p=$(echo "$p" | xargs) + if [[ "$p" =~ ^[0-9]+$ ]] && (( p >= 1 && p <= ${#AWG_CLIENT_IPS[@]} )); then + local ip="${AWG_CLIENT_IPS[$((p-1))]}" + if [[ "$warp_set" == *" $ip "* ]]; then + local -a tmp=() + for eip in "${AWG_SELECTED_IPS[@]}"; do + [ "$eip" != "$ip" ] && tmp+=("$eip") + done + AWG_SELECTED_IPS=("${tmp[@]+"${tmp[@]}"}") + else + AWG_SELECTED_IPS+=("$ip") + fi + changed=1 + fi + done + fi + + if [ "$changed" -eq 1 ]; then + awg_save_clients; awg_apply_rules; awg_patch_start_sh + log_action "AWG TOGGLE: ${#AWG_SELECTED_IPS[@]} clients in WARP" fi done - - awg_save_clients; awg_apply_rules; awg_patch_start_sh - echo -e "\n${GREEN}Готово. Правила применены.${NC}" - log_action "AWG ADD CLIENTS: ${#AWG_SELECTED_IPS[@]} total" - read -p "Enter..." -} - -awg_remove_clients_ssh() { - awg_load_clients - if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then echo -e "\n${YELLOW}Нет клиентов в WARP.${NC}"; read -p "Enter..."; return; fi - awg_parse_clients_table - clear; echo -e "\n${CYAN}━━━ Убрать клиентов из WARP ━━━${NC}\n" - local i=1 - for ip in "${AWG_SELECTED_IPS[@]}"; do echo " $i) $(awg_format_label "$ip")"; ((i++)); done - echo " all) убрать всех" - echo -e " ${DIM}0) Отмена${NC}\n" - read -p "Номер (через запятую) или all: " answer - [ -z "$answer" ] || [ "$answer" = "0" ] && return - - local -a remove_ips=() - if [ "$answer" = "all" ]; then - remove_ips=("${AWG_SELECTED_IPS[@]}") - else - IFS=',' read -ra parts <<< "$answer" - for p in "${parts[@]}"; do - p=$(echo "$p" | xargs) - [[ "$p" =~ ^[0-9]+$ ]] && (( p >= 1 && p <= ${#AWG_SELECTED_IPS[@]} )) && remove_ips+=("${AWG_SELECTED_IPS[$((p-1))]}") - done - fi - - echo "" - local -a new_list=() - for eip in "${AWG_SELECTED_IPS[@]}"; do - local removing=0 - for rip in "${remove_ips[@]}"; do [ "$eip" = "$rip" ] && removing=1 && break; done - if [ "$removing" -eq 0 ]; then new_list+=("$eip") - else echo -e " ${RED}-${NC} $(awg_format_label "$eip")"; fi - done - AWG_SELECTED_IPS=("${new_list[@]+"${new_list[@]}"}") - awg_save_clients; awg_apply_rules; awg_patch_start_sh - echo -e "\n${GREEN}Готово.${NC}" - log_action "AWG REMOVE CLIENTS: ${#AWG_SELECTED_IPS[@]} remaining" - read -p "Enter..." -} - -awg_show_clients_ssh() { - awg_load_clients; awg_parse_clients_table - clear; echo -e "\n${CYAN}━━━ Клиенты в WARP ━━━${NC}\n" - if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then - echo -e " ${YELLOW}Нет клиентов в WARP.${NC}" - else - echo -e " ${WHITE}Всего: ${CYAN}${#AWG_SELECTED_IPS[@]}${NC}\n" - for ip in "${AWG_SELECTED_IPS[@]}"; do - echo -e " ${GREEN}●${NC} $(awg_format_label "$ip")" - done - fi - echo ""; read -p "Enter..." } # ═══════════════════════════════════════════════════════════════ @@ -1030,7 +1003,7 @@ awg_remove_from_start_sh() { # ═══════════════════════════════════════════════════════════════ get_warp_status() { - [ "$MODE" = "3xui" ] && get_warp_status_3xui || get_warp_status_awg + if [ "$MODE" = "3xui" ]; then get_warp_status_3xui; else get_warp_status_awg; fi } get_warp_ip() { @@ -1042,7 +1015,7 @@ get_warp_ip() { } is_warp_running() { - [ "$MODE" = "3xui" ] && is_warp_running_3xui || is_warp_running_awg + if [ "$MODE" = "3xui" ]; then is_warp_running_3xui; else is_warp_running_awg; fi } # ═══════════════════════════════════════════════════════════════ @@ -1107,15 +1080,15 @@ kbd_main_awg() { [{"text":"📊 Статус","callback_data":"st"},{"text":"🌐 IP","callback_data":"ip"}], [{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}], [{"text":"🔑 Перевыпуск","callback_data":"rk"}], - [{"text":"👥 Клиенты WARP","callback_data":"cl"},{"text":"➕ Добавить","callback_data":"cla"}], - [{"text":"➖ Убрать","callback_data":"clr"},{"text":"🔄 Контейнер","callback_data":"rc"}], + [{"text":"👥 Клиенты WARP","callback_data":"cl"}], + [{"text":"🔄 Контейнер","callback_data":"rc"}], [{"text":"💻 Система","callback_data":"sys"}], [{"text":"🏢 Хостинг","callback_data":"promo"}] ] JSON } -kbd_main() { [ "$MODE" = "3xui" ] && kbd_main_3xui || kbd_main_awg; } +kbd_main() { if [ "$MODE" = "3xui" ]; then kbd_main_3xui; else kbd_main_awg; fi; } kbd_back() { echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]'; } kbd_rekey_confirm() { echo '[[{"text":"✅ Да","callback_data":"rk_y"}],[{"text":"⬅️ Отмена","callback_data":"m"}]]'; } @@ -1132,7 +1105,11 @@ bot_main_menu() { local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" local text="WARP Manager v${WARP_VERSION} (${mode_label})\nСервер: ${MY_IP:-N/A}\nСтатус: ${ws}${wip}${extra}\n\nВыберите:" local kbd; kbd=$(kbd_main) - [ -n "$msg_id" ] && tg_edit "$chat_id" "$msg_id" "$text" "$kbd" || tg_send "$chat_id" "$text" "$kbd" + if [ -n "$msg_id" ]; then + tg_edit "$chat_id" "$msg_id" "$text" "$kbd" + else + tg_send "$chat_id" "$text" "$kbd" + fi } # ─── Bot handlers ──────────────────────────────────────────── @@ -1171,8 +1148,12 @@ bot_handle_callback() { is_warp_running_3xui && { tg_edit "$chat_id" "$msg_id" "✅ Уже подключён." "$(kbd_back)"; return; } tg_edit "$chat_id" "$msg_id" "⏳ Подключение..." "" warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 - is_warp_running_3xui && { local w; w=$(get_warp_ip_3xui); tg_edit "$chat_id" "$msg_id" "✅ Подключён\nWARP IP: ${w}" "$(kbd_back)"; log_action "BOT 3XUI ON"; } \ - || tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)" + if is_warp_running_3xui; then + local w; w=$(get_warp_ip_3xui) + tg_edit "$chat_id" "$msg_id" "✅ Подключён\nWARP IP: ${w}" "$(kbd_back)"; log_action "BOT 3XUI ON" + else + tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)" + fi else is_warp_installed_awg || { tg_edit "$chat_id" "$msg_id" "❌ Не установлен." "$(kbd_back)"; return; } is_warp_running_awg && { tg_edit "$chat_id" "$msg_id" "✅ Уже работает." "$(kbd_back)"; return; } @@ -1237,93 +1218,64 @@ bot_handle_callback() { fi ;; cl) - awg_load_clients; awg_parse_clients_table - local t="👥 Клиенты в WARP (${#AWG_SELECTED_IPS[@]})\n\n" - if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then - t+="Нет клиентов." - else - for ip in "${AWG_SELECTED_IPS[@]}"; do - local name; name=$(awg_get_name "$ip") - t+="● ${ip}" - [ -n "$name" ] && t+=" ($name)" - t+="\n" - done - fi - tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;; - - cla) awg_get_client_ips; awg_parse_clients_table; awg_load_clients local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " - local kbd="[" - local first=1 - for i in "${!AWG_CLIENT_IPS[@]}"; do - local ip="${AWG_CLIENT_IPS[$i]}" - local name; name=$(awg_get_name "$ip") - local label="${ip}" - [ -n "$name" ] && label="$name" - local in_warp=0 - [[ "$warp_set" == *" $ip "* ]] && in_warp=1 - [ "$in_warp" -eq 1 ] && label="✅ $label" || label="➕ $label" - [ "$first" -eq 0 ] && kbd+="," - kbd+="[{\"text\":\"${label}\",\"callback_data\":\"ca:${i}\"}]" - first=0 - done - kbd+=",[{\"text\":\"✅ Все в WARP\",\"callback_data\":\"ca:all\"}]" - kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" - tg_edit "$chat_id" "$msg_id" "➕ Добавить в WARP:" "$kbd" ;; + local t="👥 Клиенты WARP (${#AWG_SELECTED_IPS[@]} из ${#AWG_CLIENT_IPS[@]})\n\n" + local kbd="" + if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then + t+="Нет клиентов в конфиге VPN." + kbd='[[{"text":"⬅️ Меню","callback_data":"m"}]]' + else + kbd="[" + local first=1 + for i in "${!AWG_CLIENT_IPS[@]}"; do + local ip="${AWG_CLIENT_IPS[$i]}" + local name; name=$(awg_get_name "$ip") + local label="${ip}" + [ -n "$name" ] && label="$name" + if [[ "$warp_set" == *" $ip "* ]]; then + t+="✅ ${ip}" + [ -n "$name" ] && t+=" ($name)" + label="✅ ${label}" + else + t+="☐ ${ip}" + [ -n "$name" ] && t+=" ($name)" + label="☐ ${label}" + fi + t+="\n" + [ "$first" -eq 0 ] && kbd+="," + kbd+="[{\"text\":\"${label}\",\"callback_data\":\"ct:${i}\"}]" + first=0 + done + kbd+=",[{\"text\":\"✅ Все\",\"callback_data\":\"ct:all\"},{\"text\":\"☐ Никого\",\"callback_data\":\"ct:none\"}]" + kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" + fi + tg_edit "$chat_id" "$msg_id" "$t" "$kbd" ;; - ca:*) - local idx="${data#ca:}" + ct:*) + local idx="${data#ct:}" awg_get_client_ips; awg_load_clients if [ "$idx" = "all" ]; then AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}") + elif [ "$idx" = "none" ]; then + AWG_SELECTED_IPS=() else [[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; } local ip="${AWG_CLIENT_IPS[$idx]}" - local found=0 - for e in "${AWG_SELECTED_IPS[@]}"; do [ "$e" = "$ip" ] && found=1 && break; done - [ "$found" -eq 0 ] && AWG_SELECTED_IPS+=("$ip") + local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " + if [[ "$warp_set" == *" $ip "* ]]; then + local -a tmp=() + for eip in "${AWG_SELECTED_IPS[@]}"; do + [ "$eip" != "$ip" ] && tmp+=("$eip") + done + AWG_SELECTED_IPS=("${tmp[@]+"${tmp[@]}"}") + else + AWG_SELECTED_IPS+=("$ip") + fi fi awg_save_clients; awg_apply_rules; awg_patch_start_sh - tg_edit "$chat_id" "$msg_id" "✅ Обновлено. Клиентов: ${#AWG_SELECTED_IPS[@]}" "$(kbd_back)" - log_action "BOT AWG ADD: ${#AWG_SELECTED_IPS[@]}" ;; - - clr) - awg_load_clients; awg_parse_clients_table - if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then - tg_edit "$chat_id" "$msg_id" "ℹ️ Нет клиентов в WARP." "$(kbd_back)"; return - fi - local kbd="[" - local first=1 - for i in "${!AWG_SELECTED_IPS[@]}"; do - local ip="${AWG_SELECTED_IPS[$i]}" - local name; name=$(awg_get_name "$ip") - local label="${ip}" - [ -n "$name" ] && label="$name" - [ "$first" -eq 0 ] && kbd+="," - kbd+="[{\"text\":\"❌ ${label}\",\"callback_data\":\"cr:${i}\"}]" - first=0 - done - kbd+=",[{\"text\":\"❌ Убрать всех\",\"callback_data\":\"cr:all\"}]" - kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" - tg_edit "$chat_id" "$msg_id" "➖ Убрать из WARP:" "$kbd" ;; - - cr:*) - local idx="${data#cr:}" - awg_load_clients - if [ "$idx" = "all" ]; then - AWG_SELECTED_IPS=() - else - [[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_SELECTED_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; } - local -a new=() - for i in "${!AWG_SELECTED_IPS[@]}"; do - [ "$i" -ne "$idx" ] && new+=("${AWG_SELECTED_IPS[$i]}") - done - AWG_SELECTED_IPS=("${new[@]+"${new[@]}"}") - fi - awg_save_clients; awg_apply_rules; awg_patch_start_sh - tg_edit "$chat_id" "$msg_id" "✅ Обновлено. Клиентов: ${#AWG_SELECTED_IPS[@]}" "$(kbd_back)" - log_action "BOT AWG REMOVE: ${#AWG_SELECTED_IPS[@]}" ;; + log_action "BOT AWG TOGGLE: ${#AWG_SELECTED_IPS[@]}" + bot_handle_callback "$chat_id" "$msg_id" "" "cl" ;; rc) if [ "$MODE" != "amnezia" ]; then @@ -1387,7 +1339,11 @@ bot_daemon() { mtx=$(echo "$upd" | jq -r '.message.text // empty') if [ -n "$mci" ] && [ -n "$mtx" ]; then [ -n "$BOT_CHAT_ID" ] && [ "$mci" != "$BOT_CHAT_ID" ] && { tg_send "$mci" "⛔ Нет доступа.\nChat ID: $mci" "" > /dev/null; continue; } - [[ "$mtx" == "/start" || "$mtx" == "/menu" ]] && bot_main_menu "$mci" || tg_send "$mci" "Используйте /start или /menu" "" > /dev/null + if [[ "$mtx" == "/start" || "$mtx" == "/menu" ]]; then + bot_main_menu "$mci" + else + tg_send "$mci" "Используйте /start или /menu" "" > /dev/null + fi fi fi done @@ -1413,8 +1369,11 @@ RestartSec=5 WantedBy=multi-user.target EOF systemctl daemon-reload; systemctl enable warp-bot > /dev/null 2>&1; systemctl start warp-bot; sleep 1 - systemctl is-active warp-bot &>/dev/null && echo -e "${GREEN}[OK] Бот запущен.${NC}" && log_action "Bot started" \ - || echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}" + if systemctl is-active warp-bot &>/dev/null; then + echo -e "${GREEN}[OK] Бот запущен.${NC}"; log_action "Bot started" + else + echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}" + fi } stop_bot() { @@ -1446,8 +1405,11 @@ bot_menu() { 2) [ -z "${BOT_TOKEN:-}" ] && echo -e "${RED}Сначала токен!${NC}" && read -p "" && continue echo -e "${YELLOW}Отправьте боту сообщение, нажмите Enter.${NC}"; read -p "" local c; c=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=1&offset=-1" | jq -r '.result[0].message.chat.id // empty') - [ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && BOT_CHAT_ID="$c" && echo -e "${GREEN}Chat ID: $c${NC}" \ - || echo -e "${RED}Не удалось.${NC}" + if [ -n "$c" ]; then + save_config_val "BOT_CHAT_ID" "$c"; BOT_CHAT_ID="$c"; echo -e "${GREEN}Chat ID: $c${NC}" + else + echo -e "${RED}Не удалось.${NC}" + fi read -p "Enter..." ;; 3) echo "Chat ID:"; read -p "> " c [ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && BOT_CHAT_ID="$c" && echo -e "${GREEN}OK${NC}" @@ -1518,7 +1480,7 @@ full_uninstall() { read -p "$(echo -e "${RED}Удалить полностью? (y/n): ${NC}")" c1 [[ "$c1" != "y" ]] && return - [ "$MODE" = "3xui" ] && uninstall_3xui || uninstall_awg + if [ "$MODE" = "3xui" ]; then uninstall_3xui; else uninstall_awg; fi rm -rf "$WARP_DIR" "$WARP_LOG" echo -e " ${GREEN}✓${NC} Конфигурация и логи" @@ -1568,43 +1530,39 @@ show_menu() { if [ "$MODE" = "3xui" ]; then echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}" - echo -e " 6) 📋 ${CYAN}JSON конфиг для 3X-UI${NC}" + echo -e " 6) 📋 ${CYAN}Настройки SOCKS5 / JSON для 3X-UI${NC}" echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}" fi if [ "$MODE" = "amnezia" ]; then echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}" - echo -e " 6) ${GREEN}➕ Добавить клиентов в WARP${NC}" - echo -e " 7) ${YELLOW}➖ Убрать клиентов из WARP${NC}" - echo -e " 8) 👥 ${WHITE}Показать клиентов в WARP${NC}" - echo -e " 9) 🔄 ${CYAN}Перезапуск контейнера${NC}" + echo -e " 6) 👥 ${GREEN}Управление клиентами WARP${NC}" + echo -e " 7) 🔄 ${CYAN}Перезапуск контейнера${NC}" fi echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}" - echo -e " 10) 🤖 ${CYAN}Настройка и управление ботом${NC}" + echo -e " 8) 🤖 ${CYAN}Настройка и управление ботом${NC}" echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}" - echo -e " 11) ${YELLOW}PROMO${NC}" - echo -e " 12) ${MAGENTA}📚 Инструкция${NC}" - echo -e " 13) ${RED}⚠ Полное удаление${NC}" + echo -e " 9) ${YELLOW}PROMO${NC}" + echo -e " 10) ${MAGENTA}📚 Инструкция${NC}" + echo -e " 11) ${RED}⚠ Полное удаление${NC}" echo -e " 0) Выход" echo -e "${CYAN}──────────────────────────────────────────────────────${NC}" read -p " Выбор: " ch case $ch in - 1) [ "$MODE" = "3xui" ] && install_warp_3xui || install_warp_awg ;; - 2) [ "$MODE" = "3xui" ] && start_warp_3xui || start_warp_awg ;; - 3) [ "$MODE" = "3xui" ] && stop_warp_3xui || stop_warp_awg ;; - 4) [ "$MODE" = "3xui" ] && show_status_3xui || show_status_awg ;; - 5) [ "$MODE" = "3xui" ] && rekey_warp_3xui || rekey_warp_awg ;; - 6) [ "$MODE" = "3xui" ] && show_xui_json || awg_add_clients_ssh ;; - 7) [ "$MODE" = "3xui" ] && change_port_3xui || awg_remove_clients_ssh ;; - 8) [ "$MODE" = "amnezia" ] && awg_show_clients_ssh ;; - 9) [ "$MODE" = "amnezia" ] && awg_restart_container ;; - 10) bot_menu ;; - 11) show_promo ;; - 12) show_info ;; - 13) full_uninstall ;; + 1) if [ "$MODE" = "3xui" ]; then install_warp_3xui; else install_warp_awg; fi ;; + 2) if [ "$MODE" = "3xui" ]; then start_warp_3xui; else start_warp_awg; fi ;; + 3) if [ "$MODE" = "3xui" ]; then stop_warp_3xui; else stop_warp_awg; fi ;; + 4) if [ "$MODE" = "3xui" ]; then show_status_3xui; else show_status_awg; fi ;; + 5) if [ "$MODE" = "3xui" ]; then rekey_warp_3xui; else rekey_warp_awg; fi ;; + 6) if [ "$MODE" = "3xui" ]; then show_xui_json; else awg_toggle_clients_ssh; fi ;; + 7) if [ "$MODE" = "3xui" ]; then change_port_3xui; else awg_restart_container; fi ;; + 8) bot_menu ;; + 9) show_promo ;; + 10) show_info ;; + 11) full_uninstall ;; 0) exit 0 ;; esac done