v2.1: unified client toggle, 3X-UI SOCKS5 submenu, fix 14 bugs across 3 audit cycles

Made-with: Cursor
This commit is contained in:
anten-ka
2026-03-20 16:29:48 +03:00
parent 9c41c93507
commit 82b1a38592
2 changed files with 200 additions and 247 deletions

View File

@@ -40,7 +40,7 @@ WireGuard-интерфейс WARP внутри Docker-контейнера Amnez
| JSON-конфиги для Xray | ✅ | — | | JSON-конфиги для Xray | ✅ | — |
| Маршрутизация по доменам | ✅ | — | | Маршрутизация по доменам | ✅ | — |
| Изменение порта SOCKS5 | ✅ | — | | Изменение порта SOCKS5 | ✅ | — |
| Управление клиентами (add/remove) | — | ✅ | | Управление клиентами (переключатели ✅/☐ в одном меню) | — | ✅ |
| WireGuard внутри Docker | — | ✅ | | WireGuard внутри Docker | — | ✅ |
| Перезапуск контейнера | — | ✅ | | Перезапуск контейнера | — | ✅ |
@@ -249,31 +249,30 @@ curl -s --proxy socks5h://127.0.0.1:40000 ifconfig.me
- Поднимет WG-интерфейс `warp` внутри контейнера - Поднимет WG-интерфейс `warp` внутри контейнера
- Покажет WARP IP - Покажет WARP IP
### Шаг 3. Добавление клиентов в WARP ### Шаг 3. Включение клиентов в WARP
После установки WARP нужно указать, **каких клиентов** пускать через Cloudflare. После установки WARP нужно указать, **каких клиентов** пускать через Cloudflare.
**Через SSH-меню:** **Через SSH-меню:**
1. Выберите пункт **6** Добавить клиентов в WARP 1. Выберите пункт **6**переключение клиентов WARP (единое меню)
2. Вы увидите список всех клиентов AmneziaWG с их IP и именами 2. Вы увидите список всех клиентов AmneziaWG с их IP и именами; напротив каждого — **✅** (уже в WARP) или **☐** (не в WARP)
3. Введите номер клиента, которого хотите добавить, или `all` для всех 3. Введите номер клиента, чтобы переключить состояние (в WARP или вне WARP)
4. Скрипт: 4. Скрипт:
- Добавит `ip rule` для маршрутизации трафика клиента через WARP - Добавит или снимет `ip rule` для маршрутизации трафика клиента через WARP
- Настроит `iptables` NAT для корректного выхода через WG-интерфейс - Настроит или уберёт `iptables` NAT для корректного выхода через WG-интерфейс
- Пропишет правила в `start.sh` контейнера (персистентность) - Пропишет правила в `start.sh` контейнера (персистентность)
**Через Telegram-бот:** **Через Telegram-бот:**
1. Нажмите **Клиенты**** Добавить** 1. Нажмите **👥 Клиенты WARP** — откроется тот же список-переключатель
2. Выберите клиента из списка (✅ = уже в WARP, = можно добавить) 2. Напротив клиентов отображаются **✅** (в WARP) и **☐** (вне WARP); выбор пункта переключает состояние
3. Или нажмите **Все в WARP** для добавления всех
### Шаг 4. Проверка ### Шаг 4. Проверка
1. Подключитесь к VPN через приложение Amnezia 1. Подключитесь к VPN через приложение Amnezia
2. Откройте [https://whoer.net](https://whoer.net) или [https://ifconfig.me](https://ifconfig.me) 2. Откройте [https://whoer.net](https://whoer.net) или [https://ifconfig.me](https://ifconfig.me)
3. Если ваш клиент добавлен в WARP — вы увидите **IP Cloudflare** 3. Если ваш клиент включён в WARP — вы увидите **IP Cloudflare**
4. Если клиент не в WARP — вы увидите **обычный IP сервера** 4. Если клиент не в WARP — вы увидите **обычный IP сервера**
Проверка через SSH (внутри контейнера): Проверка через SSH (внутри контейнера):
@@ -284,17 +283,17 @@ docker exec <имя_контейнера> curl -s --interface warp ifconfig.me
### Управление клиентами ### Управление клиентами
В SSH и в боте используется **одно меню переключателей**: у каждого клиента видно **✅** (трафик через WARP) или **☐** (напрямую); действие по пункту меняет состояние.
| Действие | SSH-меню | Telegram-бот | | Действие | SSH-меню | Telegram-бот |
|----------|----------|--------------| |----------|----------|--------------|
| Добавить в WARP | пункт **6** | Клиенты → Добавить | | Включить/выключить клиента в WARP (список ✅/☐) | пункт **6** | **👥 Клиенты WARP** |
| Убрать из WARP | пункт **7** | Клиенты → Убрать |
| Список клиентов в WARP | пункт **8** | Клиенты |
| Перевыпуск ключа | пункт **5** | 🔑 Перевыпуск ключа | | Перевыпуск ключа | пункт **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` 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` 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 ## Меню WARP Manager
Меню организовано по секциям. Нумерация пунктов 15 и 1013 одинакова в обоих режимах, а секция 69 адаптируется. Меню организовано по секциям. Пункты **15** (WARP) и **811** (бот, промо, инструкция, удаление) одинаковы в обоих режимах; пункты **67** зависят от режима (3X-UI или AmneziaWG).
### Общая секция — WARP-ключ (оба режима) ### Общая секция — WARP-ключ (оба режима)
@@ -336,26 +335,24 @@ docker exec <имя_контейнера> curl -s --interface warp ifconfig.me
| Пункт | Действие | | Пункт | Действие |
|-------|----------| |-------|----------|
| 6 | JSON конфиг для 3X-UI | | 6 | Настройки SOCKS5 / JSON для 3X-UI |
| 7 | Изменить порт SOCKS5 | | 7 | Изменить порт SOCKS5 |
### Секция AmneziaWG (только в режиме AmneziaWG) ### Секция AmneziaWG (только в режиме AmneziaWG)
| Пункт | Действие | | Пункт | Действие |
|-------|----------| |-------|----------|
| 6 | Добавить клиентов в WARP | | 6 | Переключение клиентов WARP (единое меню, ✅ / ☐) |
| 7 | Убрать клиентов из WARP | | 7 | Перезапуск контейнера |
| 8 | Показать клиентов в WARP |
| 9 | Перезапуск контейнера |
### Общая секция — бот и прочее (оба режима) ### Общая секция — бот и прочее (оба режима)
| Пункт | Действие | | Пункт | Действие |
|-------|----------| |-------|----------|
| 10 | Telegram Bot (настройка и управление) | | 8 | Telegram Bot (настройка и управление) |
| 11 | PROMO | | 9 | PROMO |
| 12 | Инструкция | | 10 | Инструкция |
| 13 | Полное удаление | | 11 | Полное удаление |
| 0 | Выход | | 0 | Выход |
--- ---
@@ -370,7 +367,7 @@ WARP Manager включает встроенный Telegram-бот для уда
2. Отправьте `/newbot` 2. Отправьте `/newbot`
3. Задайте имя и username бота 3. Задайте имя и username бота
4. Скопируйте полученный **токен** 4. Скопируйте полученный **токен**
5. На сервере выполните `gowarp` → пункт **10** (Telegram Bot) 5. На сервере выполните `gowarp` → пункт **8** (Telegram Bot)
6. Выберите **1) Токен бота** — вставьте токен 6. Выберите **1) Токен бота** — вставьте токен
7. Выберите **2) Chat ID (авто)**: 7. Выберите **2) Chat ID (авто)**:
- Откройте вашего бота в Telegram - Откройте вашего бота в Telegram
@@ -405,9 +402,7 @@ WARP Manager включает встроенный Telegram-бот для уда
| ▶️ Запустить | Поднять WG-интерфейс WARP | | ▶️ Запустить | Поднять WG-интерфейс WARP |
| ⏹ Остановить | Опустить WG-интерфейс WARP | | ⏹ Остановить | Опустить WG-интерфейс WARP |
| 🔑 Перевыпуск ключа | Пересоздать WARP-профиль | | 🔑 Перевыпуск ключа | Пересоздать WARP-профиль |
| 👥 Клиенты | Список клиентов в WARP | | 👥 Клиенты WARP | Единое меню переключателей: список клиентов с **✅** (в WARP) и **☐** (вне WARP) |
| Добавить | Добавить клиента в WARP (выбор из списка) |
| Убрать | Убрать клиента из WARP |
| 🔄 Контейнер | Перезапуск Docker-контейнера AmneziaWG | | 🔄 Контейнер | Перезапуск Docker-контейнера AmneziaWG |
| 🖥 Система | CPU, RAM, диск, uptime | | 🖥 Система | CPU, RAM, диск, uptime |
| 🏷 Хостинг | Промо-материалы | | 🏷 Хостинг | Промо-материалы |
@@ -453,7 +448,7 @@ docker exec <контейнер> cat /opt/warp/warp.conf
docker exec <контейнер> ip link show warp docker exec <контейнер> ip link show warp
``` ```
Попробуйте перевыпустить ключ (пункт 5) или перезапустить контейнер (пункт 9). Попробуйте перевыпустить ключ (пункт 5) или перезапустить контейнер (пункт 7).
### Сайт всё ещё заблокирован после настройки (3X-UI) ### Сайт всё ещё заблокирован после настройки (3X-UI)
@@ -464,9 +459,9 @@ docker exec <контейнер> ip link show warp
### Клиент не видит WARP IP (AmneziaWG) ### Клиент не видит WARP IP (AmneziaWG)
1. Убедитесь, что клиент **добавлен** в WARP (`gowarp` → пункт 8) 1. Убедитесь, что клиент **включён** в WARP (`gowarp` → пункт 6, в списке должна быть **✅**)
2. Проверьте, что интерфейс `warp` поднят (`gowarp` → пункт 4) 2. Проверьте, что интерфейс `warp` поднят (`gowarp` → пункт 4)
3. Перезапустите контейнер (`gowarp` → пункт 9) 3. Перезапустите контейнер (`gowarp` → пункт 7)
4. Переподключитесь к VPN на клиентском устройстве 4. Переподключитесь к VPN на клиентском устройстве
### Как сменить WARP IP ### Как сменить WARP IP
@@ -492,7 +487,7 @@ docker exec <контейнер> ip link show warp
gowarp gowarp
``` ```
Выберите пункт **13** — Полное удаление. Выберите пункт **11** — Полное удаление.
### Что удаляется (3X-UI) ### Что удаляется (3X-UI)

382
warp.sh
View File

@@ -283,7 +283,7 @@ install_warp_3xui() {
else else
echo -e "${YELLOW} ⚠ Подключение не подтверждено.${NC}" echo -e "${YELLOW} ⚠ Подключение не подтверждено.${NC}"
fi fi
echo -e "\n${WHITE}Интеграция с 3X-UI: пункт меню ${YELLOW}5${NC}" echo -e "\n${WHITE}Настройки SOCKS5 и JSON: пункт меню ${YELLOW}6${NC}"
read -p "Enter..." read -p "Enter..."
} }
@@ -292,8 +292,11 @@ start_warp_3xui() {
is_warp_running_3xui && { echo -e "\n${YELLOW}Уже подключён.${NC}"; read -p "Enter..."; return; } is_warp_running_3xui && { echo -e "\n${YELLOW}Уже подключён.${NC}"; read -p "Enter..."; return; }
echo -e "\n${YELLOW}Подключение...${NC}" echo -e "\n${YELLOW}Подключение...${NC}"
warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3
is_warp_running_3xui && echo -e "${GREEN}[OK] Подключён.${NC}" && log_action "3XUI START" \ if is_warp_running_3xui; then
|| echo -e "${RED}Ошибка подключения.${NC}" echo -e "${GREEN}[OK] Подключён.${NC}"; log_action "3XUI START"
else
echo -e "${RED}Ошибка подключения.${NC}"
fi
read -p "Enter..." read -p "Enter..."
} }
@@ -613,7 +616,7 @@ install_warp_awg() {
awg_detect_warp_exit_ip awg_detect_warp_exit_ip
[ -n "$AWG_WARP_EXIT_IP" ] && echo -e "\n ${WHITE}WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}" [ -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}" log_action "AWG INSTALL: endpoint=${ep}, warp_ip=${AWG_WARP_EXIT_IP}"
read -p "Enter..." 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_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; } is_warp_running_awg && { echo -e "\n${YELLOW}Уже работает.${NC}"; read -p "Enter..."; return; }
echo -e "\n${YELLOW}Поднимаю warp...${NC}" echo -e "\n${YELLOW}Поднимаю warp...${NC}"
awg_warp_up && echo -e "${GREEN}[OK] WARP подключён.${NC}" && log_action "AWG START" \ if awg_warp_up; then
|| echo -e "${RED}Ошибка.${NC}" echo -e "${GREEN}[OK] WARP подключён.${NC}"; log_action "AWG START"
else
echo -e "${RED}Ошибка.${NC}"
fi
read -p "Enter..." read -p "Enter..."
} }
@@ -649,8 +655,8 @@ rekey_warp_awg() {
awg_apply_rules awg_apply_rules
awg_patch_start_sh awg_patch_start_sh
awg_detect_warp_exit_ip awg_detect_warp_exit_ip
echo -e "${GREEN} ✓ Готово!" echo -e "${GREEN} ✓ Готово!${NC}"
[ -n "$AWG_WARP_EXIT_IP" ] && echo -e " Новый WARP IP: ${AWG_WARP_EXIT_IP}${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}" log_action "AWG REKEY: warp_ip=${AWG_WARP_EXIT_IP}"
read -p "Enter..." read -p "Enter..."
} }
@@ -828,105 +834,72 @@ awg_get_client_ips() {
fi fi
} }
awg_add_clients_ssh() { awg_toggle_clients_ssh() {
awg_get_client_ips; awg_parse_clients_table; awg_load_clients while true; do
local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " awg_get_client_ips; awg_parse_clients_table; awg_load_clients
clear; echo -e "\n${CYAN}━━━ Добавить клиентов в WARP ━━━${NC}\n" local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then echo -e "${RED}Нет клиентов.${NC}"; read -p "Enter..."; return; fi
local i=1 clear; echo -e "\n${CYAN}━━━ Клиенты WARP ━━━${NC}\n"
for ip in "${AWG_CLIENT_IPS[@]}"; do
local label; label=$(awg_format_label "$ip") if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then
if [[ "$warp_set" == *" $ip "* ]]; then echo -e " ${RED}Нет клиентов в конфиге VPN.${NC}"
echo -e " ${GREEN}$i) $label [WARP]${NC}" read -p "Enter..."; return
else
echo " $i) $label"
fi fi
((i++))
done
echo " all) все клиенты"
echo -e " ${DIM}0) Отмена${NC}\n"
read -p "Номер (через запятую) или all: " answer
[ -z "$answer" ] || [ "$answer" = "0" ] && return
local -a new_ips=() local i=1 warp_count=0
if [ "$answer" = "all" ]; then for ip in "${AWG_CLIENT_IPS[@]}"; do
new_ips=("${AWG_CLIENT_IPS[@]}") local label; label=$(awg_format_label "$ip")
else if [[ "$warp_set" == *" $ip "* ]]; then
IFS=',' read -ra parts <<< "$answer" echo -e " ${GREEN}$i) ✅ $label${NC}"
for p in "${parts[@]}"; do ((warp_count++))
p=$(echo "$p" | xargs) else
[[ "$p" =~ ^[0-9]+$ ]] && (( p >= 1 && p <= ${#AWG_CLIENT_IPS[@]} )) && new_ips+=("${AWG_CLIENT_IPS[$((p-1))]}") echo -e " $i) ☐ $label"
fi
((i++))
done done
fi
echo "" echo ""
for nip in "${new_ips[@]}"; do echo -e " ${WHITE}В WARP: ${CYAN}${warp_count}${NC} из ${#AWG_CLIENT_IPS[@]}"
local found=0 echo ""
for eip in "${AWG_SELECTED_IPS[@]}"; do [ "$nip" = "$eip" ] && found=1 && break; done echo -e " ${DIM}Введите номер для переключения (через запятую)${NC}"
if [ "$found" -eq 0 ]; then echo -e " ${DIM}all) все в WARP | none) убрать всех | 0) назад${NC}"
AWG_SELECTED_IPS+=("$nip") echo ""
echo -e " ${GREEN}+${NC} $(awg_format_label "$nip")" 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 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 fi
done 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() { 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() { get_warp_ip() {
@@ -1042,7 +1015,7 @@ get_warp_ip() {
} }
is_warp_running() { 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":"st"},{"text":"🌐 IP","callback_data":"ip"}],
[{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}], [{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}],
[{"text":"🔑 Перевыпуск","callback_data":"rk"}], [{"text":"🔑 Перевыпуск","callback_data":"rk"}],
[{"text":"👥 Клиенты WARP","callback_data":"cl"},{"text":" Добавить","callback_data":"cla"}], [{"text":"👥 Клиенты WARP","callback_data":"cl"}],
[{"text":" Убрать","callback_data":"clr"},{"text":"🔄 Контейнер","callback_data":"rc"}], [{"text":"🔄 Контейнер","callback_data":"rc"}],
[{"text":"💻 Система","callback_data":"sys"}], [{"text":"💻 Система","callback_data":"sys"}],
[{"text":"🏢 Хостинг","callback_data":"promo"}] [{"text":"🏢 Хостинг","callback_data":"promo"}]
] ]
JSON 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_back() { echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]'; }
kbd_rekey_confirm() { echo '[[{"text":"✅ Да","callback_data":"rk_y"}],[{"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 mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
local text="<b>WARP Manager v${WARP_VERSION}</b> (${mode_label})\nСервер: <code>${MY_IP:-N/A}</code>\nСтатус: <b>${ws}</b>${wip}${extra}\n\nВыберите:" local text="<b>WARP Manager v${WARP_VERSION}</b> (${mode_label})\nСервер: <code>${MY_IP:-N/A}</code>\nСтатус: <b>${ws}</b>${wip}${extra}\n\nВыберите:"
local kbd; kbd=$(kbd_main) 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 ──────────────────────────────────────────── # ─── Bot handlers ────────────────────────────────────────────
@@ -1171,8 +1148,12 @@ bot_handle_callback() {
is_warp_running_3xui && { tg_edit "$chat_id" "$msg_id" "✅ Уже подключён." "$(kbd_back)"; return; } is_warp_running_3xui && { tg_edit "$chat_id" "$msg_id" "✅ Уже подключён." "$(kbd_back)"; return; }
tg_edit "$chat_id" "$msg_id" "⏳ Подключение..." "" tg_edit "$chat_id" "$msg_id" "⏳ Подключение..." ""
warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 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" "✅ <b>Подключён</b>\nWARP IP: <code>${w}</code>" "$(kbd_back)"; log_action "BOT 3XUI ON"; } \ if is_warp_running_3xui; then
|| tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)" local w; w=$(get_warp_ip_3xui)
tg_edit "$chat_id" "$msg_id" "✅ <b>Подключён</b>\nWARP IP: <code>${w}</code>" "$(kbd_back)"; log_action "BOT 3XUI ON"
else
tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"
fi
else else
is_warp_installed_awg || { tg_edit "$chat_id" "$msg_id" "Не установлен." "$(kbd_back)"; return; } 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; } is_warp_running_awg && { tg_edit "$chat_id" "$msg_id" "✅ Уже работает." "$(kbd_back)"; return; }
@@ -1237,93 +1218,64 @@ bot_handle_callback() {
fi ;; fi ;;
cl) cl)
awg_load_clients; awg_parse_clients_table
local t="👥 <b>Клиенты в WARP</b> (${#AWG_SELECTED_IPS[@]})\n\n"
if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then
t+="<i>Нет клиентов.</i>"
else
for ip in "${AWG_SELECTED_IPS[@]}"; do
local name; name=$(awg_get_name "$ip")
t+="● <code>${ip}</code>"
[ -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 awg_get_client_ips; awg_parse_clients_table; awg_load_clients
local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
local kbd="[" local t="👥 <b>Клиенты WARP</b> (${#AWG_SELECTED_IPS[@]} из ${#AWG_CLIENT_IPS[@]})\n\n"
local first=1 local kbd=""
for i in "${!AWG_CLIENT_IPS[@]}"; do if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then
local ip="${AWG_CLIENT_IPS[$i]}" t+="<i>Нет клиентов в конфиге VPN.</i>"
local name; name=$(awg_get_name "$ip") kbd='[[{"text":"⬅️ Меню","callback_data":"m"}]]'
local label="${ip}" else
[ -n "$name" ] && label="$name" kbd="["
local in_warp=0 local first=1
[[ "$warp_set" == *" $ip "* ]] && in_warp=1 for i in "${!AWG_CLIENT_IPS[@]}"; do
[ "$in_warp" -eq 1 ] && label="$label" || label=" $label" local ip="${AWG_CLIENT_IPS[$i]}"
[ "$first" -eq 0 ] && kbd+="," local name; name=$(awg_get_name "$ip")
kbd+="[{\"text\":\"${label}\",\"callback_data\":\"ca:${i}\"}]" local label="${ip}"
first=0 [ -n "$name" ] && label="$name"
done if [[ "$warp_set" == *" $ip "* ]]; then
kbd+=",[{\"text\":\"Все в WARP\",\"callback_data\":\"ca:all\"}]" t+="✅ <code>${ip}</code>"
kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" [ -n "$name" ] && t+=" ($name)"
tg_edit "$chat_id" "$msg_id" " <b>Добавить в WARP:</b>" "$kbd" ;; label="${label}"
else
t+="☐ <code>${ip}</code>"
[ -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:*) ct:*)
local idx="${data#ca:}" local idx="${data#ct:}"
awg_get_client_ips; awg_load_clients awg_get_client_ips; awg_load_clients
if [ "$idx" = "all" ]; then if [ "$idx" = "all" ]; then
AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}") AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}")
elif [ "$idx" = "none" ]; then
AWG_SELECTED_IPS=()
else else
[[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; } [[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; }
local ip="${AWG_CLIENT_IPS[$idx]}" local ip="${AWG_CLIENT_IPS[$idx]}"
local found=0 local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
for e in "${AWG_SELECTED_IPS[@]}"; do [ "$e" = "$ip" ] && found=1 && break; done if [[ "$warp_set" == *" $ip "* ]]; then
[ "$found" -eq 0 ] && AWG_SELECTED_IPS+=("$ip") 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 fi
awg_save_clients; awg_apply_rules; awg_patch_start_sh 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 TOGGLE: ${#AWG_SELECTED_IPS[@]}"
log_action "BOT AWG ADD: ${#AWG_SELECTED_IPS[@]}" ;; bot_handle_callback "$chat_id" "$msg_id" "" "cl" ;;
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" " <b>Убрать из WARP:</b>" "$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[@]}" ;;
rc) rc)
if [ "$MODE" != "amnezia" ]; then if [ "$MODE" != "amnezia" ]; then
@@ -1387,7 +1339,11 @@ bot_daemon() {
mtx=$(echo "$upd" | jq -r '.message.text // empty') mtx=$(echo "$upd" | jq -r '.message.text // empty')
if [ -n "$mci" ] && [ -n "$mtx" ]; then if [ -n "$mci" ] && [ -n "$mtx" ]; then
[ -n "$BOT_CHAT_ID" ] && [ "$mci" != "$BOT_CHAT_ID" ] && { tg_send "$mci" "⛔ Нет доступа.\nChat ID: <code>$mci</code>" "" > /dev/null; continue; } [ -n "$BOT_CHAT_ID" ] && [ "$mci" != "$BOT_CHAT_ID" ] && { tg_send "$mci" "⛔ Нет доступа.\nChat ID: <code>$mci</code>" "" > /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
fi fi
done done
@@ -1413,8 +1369,11 @@ RestartSec=5
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
systemctl daemon-reload; systemctl enable warp-bot > /dev/null 2>&1; systemctl start warp-bot; sleep 1 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" \ if systemctl is-active warp-bot &>/dev/null; then
|| echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}" echo -e "${GREEN}[OK] Бот запущен.${NC}"; log_action "Bot started"
else
echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}"
fi
} }
stop_bot() { stop_bot() {
@@ -1446,8 +1405,11 @@ bot_menu() {
2) [ -z "${BOT_TOKEN:-}" ] && echo -e "${RED}Сначала токен!${NC}" && read -p "" && continue 2) [ -z "${BOT_TOKEN:-}" ] && echo -e "${RED}Сначала токен!${NC}" && read -p "" && continue
echo -e "${YELLOW}Отправьте боту сообщение, нажмите Enter.${NC}"; read -p "" 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') 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}" \ if [ -n "$c" ]; then
|| echo -e "${RED}Не удалось.${NC}" 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..." ;; read -p "Enter..." ;;
3) echo "Chat ID:"; read -p "> " c 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}" [ -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 read -p "$(echo -e "${RED}Удалить полностью? (y/n): ${NC}")" c1
[[ "$c1" != "y" ]] && return [[ "$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" rm -rf "$WARP_DIR" "$WARP_LOG"
echo -e " ${GREEN}${NC} Конфигурация и логи" echo -e " ${GREEN}${NC} Конфигурация и логи"
@@ -1568,43 +1530,39 @@ show_menu() {
if [ "$MODE" = "3xui" ]; then if [ "$MODE" = "3xui" ]; then
echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}" 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}" echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}"
fi fi
if [ "$MODE" = "amnezia" ]; then if [ "$MODE" = "amnezia" ]; then
echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}" echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}"
echo -e " 6) ${GREEN} Добавить клиентов в WARP${NC}" echo -e " 6) 👥 ${GREEN}Управление клиентами WARP${NC}"
echo -e " 7) ${YELLOW} Убрать клиентов из WARP${NC}" echo -e " 7) 🔄 ${CYAN}Перезапуск контейнера${NC}"
echo -e " 8) 👥 ${WHITE}Показать клиентов в WARP${NC}"
echo -e " 9) 🔄 ${CYAN}Перезапуск контейнера${NC}"
fi fi
echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}" echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}"
echo -e " 10) 🤖 ${CYAN}Настройка и управление ботом${NC}" echo -e " 8) 🤖 ${CYAN}Настройка и управление ботом${NC}"
echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}" echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}"
echo -e " 11) ${YELLOW}PROMO${NC}" echo -e " 9) ${YELLOW}PROMO${NC}"
echo -e " 12) ${MAGENTA}📚 Инструкция${NC}" echo -e " 10) ${MAGENTA}📚 Инструкция${NC}"
echo -e " 13) ${RED}⚠ Полное удаление${NC}" echo -e " 11) ${RED}⚠ Полное удаление${NC}"
echo -e " 0) Выход" echo -e " 0) Выход"
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}" echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
read -p " Выбор: " ch read -p " Выбор: " ch
case $ch in case $ch in
1) [ "$MODE" = "3xui" ] && install_warp_3xui || install_warp_awg ;; 1) if [ "$MODE" = "3xui" ]; then install_warp_3xui; else install_warp_awg; fi ;;
2) [ "$MODE" = "3xui" ] && start_warp_3xui || start_warp_awg ;; 2) if [ "$MODE" = "3xui" ]; then start_warp_3xui; else start_warp_awg; fi ;;
3) [ "$MODE" = "3xui" ] && stop_warp_3xui || stop_warp_awg ;; 3) if [ "$MODE" = "3xui" ]; then stop_warp_3xui; else stop_warp_awg; fi ;;
4) [ "$MODE" = "3xui" ] && show_status_3xui || show_status_awg ;; 4) if [ "$MODE" = "3xui" ]; then show_status_3xui; else show_status_awg; fi ;;
5) [ "$MODE" = "3xui" ] && rekey_warp_3xui || rekey_warp_awg ;; 5) if [ "$MODE" = "3xui" ]; then rekey_warp_3xui; else rekey_warp_awg; fi ;;
6) [ "$MODE" = "3xui" ] && show_xui_json || awg_add_clients_ssh ;; 6) if [ "$MODE" = "3xui" ]; then show_xui_json; else awg_toggle_clients_ssh; fi ;;
7) [ "$MODE" = "3xui" ] && change_port_3xui || awg_remove_clients_ssh ;; 7) if [ "$MODE" = "3xui" ]; then change_port_3xui; else awg_restart_container; fi ;;
8) [ "$MODE" = "amnezia" ] && awg_show_clients_ssh ;; 8) bot_menu ;;
9) [ "$MODE" = "amnezia" ] && awg_restart_container ;; 9) show_promo ;;
10) bot_menu ;; 10) show_info ;;
11) show_promo ;; 11) full_uninstall ;;
12) show_info ;;
13) full_uninstall ;;
0) exit 0 ;; 0) exit 0 ;;
esac esac
done done