From 3f097263e8e8d4d32f3b24ac4246e93bbab2b7c0 Mon Sep 17 00:00:00 2001 From: anten-ka Date: Fri, 20 Mar 2026 15:21:54 +0300 Subject: [PATCH] WARP Manager v2.0: unified 3X-UI + AmneziaWG with Telegram bot Made-with: Cursor --- README.md | 33 +- warp.sh | 1694 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 1164 insertions(+), 563 deletions(-) diff --git a/README.md b/README.md index 6a39a5f..d5d0830 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,42 @@ -# WARP Manager v1.1 +# WARP Manager v2.0 -**Cloudflare WARP SOCKS5 Proxy для 3X-UI** +**Cloudflare WARP для 3X-UI и AmneziaWG** -Автоматическая установка и управление Cloudflare WARP на VPS-сервере. +Один скрипт — два режима. Автоматическая установка и управление Cloudflare WARP на VPS-сервере. WARP даёт «чистый» IP от Cloudflare, который не заблокирован популярными сервисами (ChatGPT, Netflix, Spotify и др.). > Канал: [YouTube @antenkaru](https://www.youtube.com/@antenkaru) --- +## Два режима работы + +### Режим 3X-UI +``` +Клиент → 3X-UI (Xray) → SOCKS5 (WARP) → Cloudflare → Интернет +``` +Нативный `warp-cli`, SOCKS5-прокси, маршрутизация по доменам в Xray. + +### Режим AmneziaWG +``` +Клиент → AmneziaWG Docker → warp WG interface → Cloudflare → Интернет +``` +WireGuard-интерфейс WARP внутри Docker-контейнера, маршрутизация по клиентам. + +Скрипт автоматически определяет, что установлено на сервере (3X-UI или AmneziaWG Docker), и предлагает соответствующий режим. + +--- + ## Возможности -- Установка Cloudflare WARP в один клик -- Работа в режиме SOCKS5-прокси (только локальный трафик, не затрагивает основной IP сервера) +- Автоопределение режима (3X-UI / AmneziaWG) +- Установка Cloudflare WARP в один клик (оба режима) - Интерактивное SSH-меню с полным управлением - Telegram-бот для удалённого мониторинга и управления -- Автоперезапуск через systemd -- Готовые JSON-конфигурации для 3X-UI +- **3X-UI:** SOCKS5-прокси, JSON-конфигурации, маршрутизация по доменам +- **AmneziaWG:** управление клиентами (добавить/убрать из WARP), WireGuard внутри Docker - Перевыпуск ключа (смена WARP IP) +- Автоперезапуск через systemd - Полное удаление в одну команду --- diff --git a/warp.sh b/warp.sh index 9fa6801..b093032 100644 --- a/warp.sh +++ b/warp.sh @@ -2,18 +2,29 @@ set -o pipefail # ══════════════════════════════════════════════════════════════ -# WARP Manager v1.1 — Cloudflare WARP SOCKS5 Proxy for 3X-UI -# Telegram Bot · Status · Rekey · 3X-UI Config +# WARP Manager v2.0 — Unified 3X-UI + AmneziaWG +# Cloudflare WARP · Telegram Bot · Auto-detect mode # Channel: https://www.youtube.com/@antenkaru # ══════════════════════════════════════════════════════════════ -WARP_VERSION="1.1" +WARP_VERSION="2.0" WARP_DIR="/etc/warp-manager" WARP_CONF="$WARP_DIR/config" WARP_LOG="/var/log/warp-manager.log" BOT_PID_FILE="/var/run/warp_bot.pid" DEFAULT_PORT=40000 +WGCF_VERSION="2.2.30" +WGCF_BIN="/root/wgcf" +WGCF_ACCOUNT="/root/wgcf-account.toml" +WGCF_PROFILE="/root/wgcf-profile.conf" + +AWG_WARP_DIR="/opt/warp" +AWG_WARP_CONF="$AWG_WARP_DIR/warp.conf" +AWG_WARP_CLIENTS="$AWG_WARP_DIR/clients.list" +AWG_MARKER_BEGIN="# --- WARP-MANAGER BEGIN ---" +AWG_MARKER_END="# --- WARP-MANAGER END ---" + VALID_KEYS=( "WARP-PRO-2026-ALPHA" "WARP-PRO-2026-BETA" @@ -22,15 +33,28 @@ VALID_KEYS=( RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m' YELLOW='\033[1;33m'; MAGENTA='\033[0;35m'; WHITE='\033[1;37m' -BLUE='\033[0;34m'; NC='\033[0m' +BLUE='\033[0;34m'; DIM='\033[2m'; NC='\033[0m' SOCKS_PORT="" MY_IP="" BOT_TOKEN="" BOT_CHAT_ID="" LICENSE_KEY="" +MODE="" -# ─── Config ─────────────────────────────────────────────────── +CONTAINER="" +AWG_VPN_CONF="" +AWG_CLIENTS_TABLE="" +AWG_START_SH="" +AWG_SUBNET="" +AWG_WARP_EXIT_IP="" +declare -a AWG_SELECTED_IPS=() +declare -a AWG_CLIENT_IPS=() +declare -A AWG_CLIENT_NAMES=() + +# ═══════════════════════════════════════════════════════════════ +# CONFIG +# ═══════════════════════════════════════════════════════════════ init_config() { mkdir -p "$WARP_DIR" @@ -40,6 +64,8 @@ SOCKS_PORT="40000" BOT_TOKEN="" BOT_CHAT_ID="" LICENSE_KEY="" +MODE="" +CONTAINER="" CONF fi source "$WARP_CONF" @@ -56,61 +82,93 @@ save_config_val() { source "$WARP_CONF" } -# ─── License ────────────────────────────────────────────────── +# ═══════════════════════════════════════════════════════════════ +# MODE DETECTION +# ═══════════════════════════════════════════════════════════════ + +detect_mode() { + source "$WARP_CONF" 2>/dev/null + if [ -n "${MODE:-}" ] && [[ "$MODE" == "3xui" || "$MODE" == "amnezia" ]]; then + return 0 + fi + + local has_docker=0 has_amnezia=0 has_3xui=0 + command -v docker &>/dev/null && has_docker=1 + if [ "$has_docker" -eq 1 ]; then + local awg_ct + awg_ct=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia" | head -1) + [ -n "$awg_ct" ] && has_amnezia=1 + fi + systemctl is-active x-ui &>/dev/null 2>&1 && has_3xui=1 + [ "$has_3xui" -eq 0 ] && command -v x-ui &>/dev/null && has_3xui=1 + + if [ "$has_amnezia" -eq 1 ] && [ "$has_3xui" -eq 1 ]; then + echo -e "\n${CYAN}Обнаружены оба режима:${NC}" + echo -e " ${GREEN}1)${NC} 3X-UI (SOCKS5-прокси)" + echo -e " ${GREEN}2)${NC} AmneziaWG (Docker WireGuard)" + while true; do + read -p "Выберите режим (1/2): " choice + case "$choice" in + 1) MODE="3xui"; break ;; + 2) MODE="amnezia"; break ;; + esac + done + elif [ "$has_amnezia" -eq 1 ]; then + MODE="amnezia" + elif [ "$has_3xui" -eq 1 ]; then + MODE="3xui" + else + echo -e "\n${YELLOW}Не обнаружено ни 3X-UI, ни AmneziaWG Docker.${NC}" + echo -e " ${GREEN}1)${NC} 3X-UI (SOCKS5-прокси для Xray)" + echo -e " ${GREEN}2)${NC} AmneziaWG (Docker WireGuard)" + while true; do + read -p "Выберите режим (1/2): " choice + case "$choice" in + 1) MODE="3xui"; break ;; + 2) MODE="amnezia"; break ;; + esac + done + fi + + save_config_val "MODE" "$MODE" +} + +# ═══════════════════════════════════════════════════════════════ +# LICENSE +# ═══════════════════════════════════════════════════════════════ is_valid_key() { local k="$1" - for vk in "${VALID_KEYS[@]}"; do - [ "$k" = "$vk" ] && return 0 - done + for vk in "${VALID_KEYS[@]}"; do [ "$k" = "$vk" ] && return 0; done return 1 } check_license() { local arg_key="${1:-}" source "$WARP_CONF" 2>/dev/null - - if [ -n "${LICENSE_KEY:-}" ] && is_valid_key "$LICENSE_KEY"; then - return 0 - fi - + if [ -n "${LICENSE_KEY:-}" ] && is_valid_key "$LICENSE_KEY"; then return 0; fi if [ -n "$arg_key" ] && is_valid_key "$arg_key"; then - save_config_val "LICENSE_KEY" "$arg_key" - LICENSE_KEY="$arg_key" - return 0 + save_config_val "LICENSE_KEY" "$arg_key"; LICENSE_KEY="$arg_key"; return 0 fi - - echo "" - echo -e "${RED}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ ⛔ ЛИЦЕНЗИОННЫЙ КЛЮЧ НЕДЕЙСТВИТЕЛЕН ║${NC}" echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" - if [ -n "$arg_key" ]; then - echo -e " ${WHITE}Ключ ${YELLOW}${arg_key}${WHITE} не найден в базе.${NC}" - else - echo -e " ${WHITE}Для установки требуется лицензионный ключ.${NC}" - fi - echo "" - echo -e " ${CYAN}Использование:${NC}" - echo -e " ${WHITE}bash <(curl -sL ...) ${GREEN}ВАШ_КЛЮЧ${NC}" - echo "" - echo -e " ${WHITE}Получить ключ: ${CYAN}https://www.youtube.com/@antenkaru${NC}" - echo "" + [ -n "$arg_key" ] && echo -e " ${WHITE}Ключ ${YELLOW}${arg_key}${WHITE} не найден.${NC}" \ + || echo -e " ${WHITE}Для установки требуется лицензионный ключ.${NC}" + echo -e "\n ${CYAN}Использование:${NC}\n ${WHITE}bash <(curl -sL ...) ${GREEN}ВАШ_КЛЮЧ${NC}" + echo -e "\n ${WHITE}Получить ключ: ${CYAN}https://www.youtube.com/@antenkaru${NC}\n" exit 1 } -# ─── Logging ────────────────────────────────────────────────── +# ═══════════════════════════════════════════════════════════════ +# LOGGING / SYSTEM +# ═══════════════════════════════════════════════════════════════ -log_action() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$WARP_LOG" -} - -# ─── System ─────────────────────────────────────────────────── +log_action() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$WARP_LOG"; } check_root() { - if [ "$EUID" -ne 0 ]; then - echo -e "${RED}[ERROR] Запустите скрипт с правами root!${NC}"; exit 1 - fi + [ "$EUID" -ne 0 ] && { echo -e "${RED}[ERROR] Запустите от root!${NC}"; exit 1; } } check_deps() { @@ -128,51 +186,6 @@ get_my_ip() { MY_IP=$(curl -s4 --max-time 5 ifconfig.me 2>/dev/null || echo "N/A") } -get_warp_ip() { - local warp_ip - warp_ip=$(curl -s4 --max-time 5 --proxy socks5h://127.0.0.1:${SOCKS_PORT} ifconfig.me 2>/dev/null || echo "N/A") - echo "$warp_ip" -} - -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS_ID="$ID" - OS_VERSION="$VERSION_ID" - OS_CODENAME="$VERSION_CODENAME" - else - OS_ID="unknown" - fi -} - -is_warp_installed() { - command -v warp-cli &>/dev/null -} - -is_warp_running() { - local st - st=$(warp-cli --accept-tos status 2>/dev/null) - echo "$st" | grep -qi "status.*connected" && ! echo "$st" | grep -qi "disconnected" -} - -get_warp_status_text() { - if ! is_warp_installed; then - echo "Не установлен" - return - fi - local status - status=$(warp-cli --accept-tos status 2>/dev/null | head -5) - if echo "$status" | grep -qi "disconnected"; then - echo "Отключён" - elif echo "$status" | grep -qi "connected"; then - echo "Подключён" - elif echo "$status" | grep -qi "registration missing"; then - echo "Нет регистрации" - else - echo "Неизвестно" - fi -} - get_system_stats() { local cpu_line load_avg mem_info disk_info uptime_str cpu_usage cpu_line=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || echo "?") @@ -191,365 +204,766 @@ get_system_stats() { echo "$r" } -# ─── Install ────────────────────────────────────────────────── - -install_warp() { - clear - echo -e "\n${CYAN}━━━ Установка Cloudflare WARP ━━━${NC}\n" - - if is_warp_installed; then - echo -e "${YELLOW}WARP уже установлен.${NC}" - echo -e "Используйте меню для управления.\n" - read -p "Нажмите Enter..." - return +detect_os() { + if [ -f /etc/os-release ]; then + . /etc/os-release; OS_ID="$ID"; OS_VERSION="$VERSION_ID"; OS_CODENAME="$VERSION_CODENAME" + else + OS_ID="unknown" fi +} +# ═══════════════════════════════════════════════════════════════ +# 3X-UI BACKEND — WARP via warp-cli (SOCKS5) +# ═══════════════════════════════════════════════════════════════ + +is_warp_installed_3xui() { command -v warp-cli &>/dev/null; } + +is_warp_running_3xui() { + local st; st=$(warp-cli --accept-tos status 2>/dev/null) + echo "$st" | grep -qi "status.*connected" && ! echo "$st" | grep -qi "disconnected" +} + +get_warp_status_3xui() { + if ! is_warp_installed_3xui; then echo "Не установлен"; return; fi + local s; s=$(warp-cli --accept-tos status 2>/dev/null | head -5) + if echo "$s" | grep -qi "disconnected"; then echo "Отключён" + elif echo "$s" | grep -qi "connected"; then echo "Подключён" + elif echo "$s" | grep -qi "registration missing"; then echo "Нет регистрации" + else echo "Неизвестно"; fi +} + +get_warp_ip_3xui() { + curl -s4 --max-time 5 --proxy socks5h://127.0.0.1:${SOCKS_PORT} ifconfig.me 2>/dev/null || echo "N/A" +} + +install_warp_3xui() { + clear; echo -e "\n${CYAN}━━━ Установка Cloudflare WARP (3X-UI) ━━━${NC}\n" + if is_warp_installed_3xui; then + echo -e "${YELLOW}WARP уже установлен.${NC}"; read -p "Enter..."; return + fi detect_os if [[ "$OS_ID" != "ubuntu" && "$OS_ID" != "debian" ]]; then - echo -e "${RED}[ERROR] Поддерживаются только Ubuntu и Debian.${NC}" - echo -e "${WHITE}Ваша ОС: ${YELLOW}${OS_ID} ${OS_VERSION}${NC}" - read -p "Нажмите Enter..." - return + echo -e "${RED}Поддерживаются только Ubuntu и Debian (ваша: ${OS_ID}).${NC}"; read -p "Enter..."; return fi + echo -e "${YELLOW}[1/6]${NC} GPG-ключ Cloudflare..." + curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor -o /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg 2>/dev/null \ + || { echo -e "${RED}Ошибка GPG.${NC}"; read -p "Enter..."; return; } + echo -e "${GREEN} ✓${NC}" - echo -e "${YELLOW}[1/6]${NC} Добавление GPG-ключа Cloudflare..." - curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor -o /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg 2>/dev/null - if [ $? -ne 0 ]; then - echo -e "${RED}[ERROR] Не удалось добавить GPG-ключ.${NC}" - read -p "Нажмите Enter..."; return - fi - echo -e "${GREEN} ✓ GPG-ключ добавлен${NC}" - - echo -e "${YELLOW}[2/6]${NC} Добавление репозитория..." - local codename="$OS_CODENAME" - if [ -z "$codename" ]; then - codename=$(lsb_release -cs 2>/dev/null || echo "focal") - fi + echo -e "${YELLOW}[2/6]${NC} Репозиторий..." + local codename="${OS_CODENAME:-$(lsb_release -cs 2>/dev/null || echo focal)}" echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main" \ > /etc/apt/sources.list.d/cloudflare-client.list - echo -e "${GREEN} ✓ Репозиторий добавлен (${codename})${NC}" + echo -e "${GREEN} ✓ (${codename})${NC}" - echo -e "${YELLOW}[3/6]${NC} Установка пакета cloudflare-warp..." + echo -e "${YELLOW}[3/6]${NC} Установка cloudflare-warp..." export DEBIAN_FRONTEND=noninteractive - apt-get update -y > /dev/null 2>&1 - apt-get install -y cloudflare-warp > /dev/null 2>&1 - if ! command -v warp-cli &>/dev/null; then - echo -e "${RED}[ERROR] Установка не удалась. Проверьте совместимость ОС.${NC}" - echo -e "${WHITE}Попробуйте: ${CYAN}apt-get install cloudflare-warp${NC}" - read -p "Нажмите Enter..."; return - fi - echo -e "${GREEN} ✓ cloudflare-warp установлен${NC}" + apt-get update -y > /dev/null 2>&1; apt-get install -y cloudflare-warp > /dev/null 2>&1 + command -v warp-cli &>/dev/null || { echo -e "${RED}Не удалось установить.${NC}"; read -p "Enter..."; return; } + echo -e "${GREEN} ✓${NC}" - echo -e "${YELLOW}[4/6]${NC} Регистрация аккаунта WARP..." - warp-cli --accept-tos registration new > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED}[ERROR] Регистрация не удалась.${NC}" - read -p "Нажмите Enter..."; return - fi - echo -e "${GREEN} ✓ Аккаунт зарегистрирован${NC}" + echo -e "${YELLOW}[4/6]${NC} Регистрация..." + warp-cli --accept-tos registration new > /dev/null 2>&1 || { echo -e "${RED}Ошибка регистрации.${NC}"; read -p "Enter..."; return; } + echo -e "${GREEN} ✓${NC}" - echo -e "${YELLOW}[5/6]${NC} Настройка режима SOCKS5-прокси..." + echo -e "${YELLOW}[5/6]${NC} SOCKS5-прокси..." warp-cli --accept-tos mode proxy > /dev/null 2>&1 warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 - save_config_val "SOCKS_PORT" "${SOCKS_PORT}" - echo -e "${GREEN} ✓ Режим: SOCKS5 на 127.0.0.1:${SOCKS_PORT}${NC}" + echo -e "${GREEN} ✓ 127.0.0.1:${SOCKS_PORT}${NC}" echo -e "${YELLOW}[6/6]${NC} Подключение..." - warp-cli --accept-tos connect > /dev/null 2>&1 - sleep 3 - - if is_warp_running; then - local wip - wip=$(get_warp_ip) - echo -e "${GREEN} ✓ WARP подключён!${NC}" - echo -e " ${WHITE}WARP IP: ${GREEN}${wip}${NC}" - log_action "INSTALL: WARP installed and connected, port=${SOCKS_PORT}, warp_ip=${wip}" + warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 + if is_warp_running_3xui; then + local wip; wip=$(get_warp_ip_3xui) + echo -e "${GREEN} ✓ WARP IP: ${wip}${NC}" + log_action "3XUI INSTALL: port=${SOCKS_PORT}, warp_ip=${wip}" else - echo -e "${YELLOW} ⚠ WARP установлен, но подключение не подтверждено.${NC}" - echo -e " ${WHITE}Попробуйте: ${CYAN}warp-cli --accept-tos connect${NC}" - log_action "INSTALL: WARP installed, connection unconfirmed" + echo -e "${YELLOW} ⚠ Подключение не подтверждено.${NC}" fi - - echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${WHITE}Для интеграции с 3X-UI используйте пункт меню ${YELLOW}5${WHITE}.${NC}" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - read -p "Нажмите Enter..." + echo -e "\n${WHITE}Интеграция с 3X-UI: пункт меню ${YELLOW}5${NC}" + read -p "Enter..." } -# ─── Start / Stop ───────────────────────────────────────────── - -start_warp() { - if ! is_warp_installed; then - echo -e "\n${RED}WARP не установлен. Сначала выполните установку (п.1).${NC}" - read -p "Нажмите Enter..."; return - fi - if is_warp_running; then - echo -e "\n${YELLOW}WARP уже подключён.${NC}" - read -p "Нажмите Enter..."; return - fi - echo -e "\n${YELLOW}Подключение WARP...${NC}" - warp-cli --accept-tos connect > /dev/null 2>&1 - sleep 3 - if is_warp_running; then - echo -e "${GREEN}[OK] WARP подключён.${NC}" - log_action "START: WARP connected" - else - echo -e "${RED}[ERROR] Не удалось подключить. Проверьте: warp-cli --accept-tos status${NC}" - fi - read -p "Нажмите Enter..." +start_warp_3xui() { + is_warp_installed_3xui || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; } + 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}" + read -p "Enter..." } -stop_warp() { - if ! is_warp_installed; then - echo -e "\n${RED}WARP не установлен.${NC}" - read -p "Нажмите Enter..."; return - fi - if ! is_warp_running; then - echo -e "\n${YELLOW}WARP уже отключён.${NC}" - read -p "Нажмите Enter..."; return - fi - echo -e "\n${YELLOW}Отключение WARP...${NC}" +stop_warp_3xui() { + is_warp_installed_3xui || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; } + is_warp_running_3xui || { echo -e "\n${YELLOW}Уже отключён.${NC}"; read -p "Enter..."; return; } warp-cli --accept-tos disconnect > /dev/null 2>&1 - echo -e "${GREEN}[OK] WARP отключён.${NC}" - log_action "STOP: WARP disconnected" - read -p "Нажмите Enter..." + echo -e "${GREEN}[OK] Отключён.${NC}"; log_action "3XUI STOP" + read -p "Enter..." } -# ─── Status ─────────────────────────────────────────────────── +rekey_warp_3xui() { + is_warp_installed_3xui || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; } + echo -e "\n${CYAN}━━━ Перевыпуск ключа WARP ━━━${NC}\n" + read -p "Продолжить? (y/n): " c; [[ "$c" != "y" ]] && return + echo -e "${YELLOW}[1/4]${NC} Отключение..."; warp-cli --accept-tos disconnect > /dev/null 2>&1; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[2/4]${NC} Удаление регистрации..."; warp-cli --accept-tos registration delete > /dev/null 2>&1; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[3/4]${NC} Новая регистрация..."; warp-cli --accept-tos registration new > /dev/null 2>&1 || { echo -e "${RED}Ошибка.${NC}"; read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[4/4]${NC} Подключение..." + warp-cli --accept-tos mode proxy > /dev/null 2>&1; warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 + warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 + if is_warp_running_3xui; then + local wip; wip=$(get_warp_ip_3xui) + echo -e "${GREEN} ✓ Новый WARP IP: ${wip}${NC}"; log_action "3XUI REKEY: warp_ip=${wip}" + else echo -e "${YELLOW} ⚠ Подключение не подтверждено.${NC}"; fi + read -p "Enter..." +} -show_status() { - clear - echo -e "\n${CYAN}━━━ Статус WARP ━━━${NC}\n" - - if ! is_warp_installed; then - echo -e " ${WHITE}Статус: ${RED}Не установлен${NC}" - echo -e "\n ${WHITE}Установите WARP через пункт меню 1.${NC}" - echo "" - read -p "Нажмите Enter..."; return - fi - - local status_text warp_ip - status_text=$(get_warp_status_text) - - local status_color="$RED" - [[ "$status_text" == "Подключён" ]] && status_color="$GREEN" - [[ "$status_text" == "Отключён" ]] && status_color="$YELLOW" - - echo -e " ${WHITE}Статус: ${status_color}${status_text}${NC}" - echo -e " ${WHITE}Порт SOCKS5: ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}" - echo -e " ${WHITE}Реальный IP: ${GREEN}${MY_IP}${NC}" - - if is_warp_running; then - warp_ip=$(get_warp_ip) - echo -e " ${WHITE}WARP IP: ${GREEN}${warp_ip}${NC}" - fi - - echo "" - echo -e " ${CYAN}── warp-cli status ──${NC}" - warp-cli --accept-tos status 2>/dev/null | while IFS= read -r line; do - echo -e " ${WHITE}$line${NC}" +change_port_3xui() { + is_warp_installed_3xui || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; } + echo -e "\n${CYAN}━━━ Изменение порта ━━━${NC}\n" + echo -e "${WHITE}Текущий: ${GREEN}${SOCKS_PORT}${NC}\n" + local np + while true; do + read -p "Новый порт (1024-65535): " np + [[ "$np" =~ ^[0-9]+$ ]] && (( np >= 1024 && np <= 65535 )) && break + echo -e "${RED}Ошибка.${NC}" done - - echo "" - read -p "Нажмите Enter..." + warp-cli --accept-tos proxy port "$np" > /dev/null 2>&1 + save_config_val "SOCKS_PORT" "$np"; SOCKS_PORT="$np" + echo -e "\n${GREEN}[OK] Порт: ${np}${NC}\n${YELLOW}Обновите порт в 3X-UI!${NC}" + log_action "3XUI PORT: ${np}"; read -p "Enter..." } -# ─── 3X-UI JSON ────────────────────────────────────────────── - show_xui_json() { - clear - echo -e "\n${CYAN}━━━ Конфигурация для 3X-UI ━━━${NC}\n" - echo -e "${WHITE}Добавьте в ${YELLOW}Xray Settings → Outbounds${WHITE} (JSON):${NC}\n" - - echo -e "${GREEN}── 1. Outbound (добавить в массив outbounds) ──${NC}\n" + clear; echo -e "\n${CYAN}━━━ Конфигурация для 3X-UI ━━━${NC}\n" + echo -e "${GREEN}── Outbound ──${NC}\n" cat </dev/null | while IFS= read -r l; do echo -e " ${WHITE}$l${NC}"; done + echo ""; read -p "Enter..." +} - echo -e "\n${CYAN}━━━ Перевыпуск ключа WARP ━━━${NC}\n" - echo -e "${WHITE}Текущая регистрация будет удалена и создана новая.${NC}" - echo -e "${YELLOW}WARP будет временно отключён.${NC}\n" - read -p "Продолжить? (y/n): " confirm - [[ "$confirm" != "y" ]] && return +uninstall_3xui() { + echo -e "\n${YELLOW}Удаление WARP (3X-UI)...${NC}\n" + systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null + rm -f /etc/systemd/system/warp-bot.service; systemctl daemon-reload 2>/dev/null + [ -f "$BOT_PID_FILE" ] && { kill "$(cat "$BOT_PID_FILE")" 2>/dev/null; rm -f "$BOT_PID_FILE"; } + echo -e " ${GREEN}✓${NC} Бот остановлен" + warp-cli --accept-tos disconnect > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} WARP отключён" + warp-cli --accept-tos registration delete > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} Регистрация удалена" + apt-get remove -y cloudflare-warp > /dev/null 2>&1; apt-get autoremove -y > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} Пакет удалён" + rm -f /etc/apt/sources.list.d/cloudflare-client.list /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg + echo -e " ${GREEN}✓${NC} Репозиторий удалён" +} - echo -e "${YELLOW}[1/4] Отключение...${NC}" - warp-cli --accept-tos disconnect > /dev/null 2>&1 - echo -e "${GREEN} ✓${NC}" +# ═══════════════════════════════════════════════════════════════ +# AMNEZIA BACKEND — WARP via wgcf (WireGuard inside Docker) +# ═══════════════════════════════════════════════════════════════ - echo -e "${YELLOW}[2/4] Удаление регистрации...${NC}" - warp-cli --accept-tos registration delete > /dev/null 2>&1 - echo -e "${GREEN} ✓${NC}" - - echo -e "${YELLOW}[3/4] Новая регистрация...${NC}" - warp-cli --accept-tos registration new > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo -e "${RED}[ERROR] Регистрация не удалась.${NC}" - read -p "Нажмите Enter..."; return +awg_pick_container() { + if [ -n "${CONTAINER:-}" ]; then + docker inspect "$CONTAINER" &>/dev/null && return 0 fi - echo -e "${GREEN} ✓${NC}" - - echo -e "${YELLOW}[4/4] Подключение...${NC}" - warp-cli --accept-tos mode proxy > /dev/null 2>&1 - warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 - warp-cli --accept-tos connect > /dev/null 2>&1 - sleep 3 - - if is_warp_running; then - local wip - wip=$(get_warp_ip) - echo -e "${GREEN} ✓ Готово! Новый WARP IP: ${wip}${NC}" - log_action "REKEY: new registration, warp_ip=${wip}" + local -a containers=() + while IFS= read -r name; do + [ -n "$name" ] && containers+=("$name") + done < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia") + if [ ${#containers[@]} -eq 0 ]; then + echo -e "${RED}Не найден Docker-контейнер AmneziaWG.${NC}" + echo -e "${WHITE}Убедитесь, что AmneziaWG запущен через Docker.${NC}" + return 1 + elif [ ${#containers[@]} -eq 1 ]; then + CONTAINER="${containers[0]}" else - echo -e "${YELLOW} ⚠ Регистрация обновлена, подключение не подтверждено.${NC}" - log_action "REKEY: new registration, connection unconfirmed" + echo -e "\n${CYAN}Найдено несколько контейнеров:${NC}" + for i in "${!containers[@]}"; do echo -e " ${GREEN}$((i+1)))${NC} ${containers[$i]}"; done + while true; do + read -p "Выберите (1-${#containers[@]}): " ch + [[ "$ch" =~ ^[0-9]+$ ]] && (( ch >= 1 && ch <= ${#containers[@]} )) && { CONTAINER="${containers[$((ch-1))]}"; break; } + done fi - read -p "Нажмите Enter..." + save_config_val "CONTAINER" "$CONTAINER" + return 0 } -# ─── Change port ────────────────────────────────────────────── - -change_port() { - if ! is_warp_installed; then - echo -e "\n${RED}WARP не установлен.${NC}" - read -p "Нажмите Enter..."; return - fi - - echo -e "\n${CYAN}━━━ Изменение порта SOCKS5 ━━━${NC}\n" - echo -e "${WHITE}Текущий порт: ${GREEN}${SOCKS_PORT}${NC}\n" - - local new_port - while true; do - echo -e "Введите новый порт (1024-65535):" - read -p "> " new_port - if [[ "$new_port" =~ ^[0-9]+$ ]] && (( new_port >= 1024 && new_port <= 65535 )); then - break +awg_load_container_data() { + local vpn_conf="" + for f in /opt/amnezia/awg/wg0.conf /etc/wireguard/wg0.conf /opt/amnezia/wg/wg0.conf; do + if docker exec "$CONTAINER" sh -c "[ -f '$f' ]" 2>/dev/null; then + vpn_conf="$f"; break + fi + done + [ -z "$vpn_conf" ] && { echo -e "${RED}Не найден VPN-конфиг в контейнере.${NC}"; return 1; } + AWG_VPN_CONF="$vpn_conf" + + for f in /opt/amnezia/awg/clientsTable /opt/amnezia/clientsTable /etc/wireguard/clientsTable; do + if docker exec "$CONTAINER" sh -c "[ -f '$f' ]" 2>/dev/null; then + AWG_CLIENTS_TABLE="$f"; break fi - echo -e "${RED}Ошибка: порт должен быть числом от 1024 до 65535.${NC}" done - warp-cli --accept-tos proxy port "$new_port" > /dev/null 2>&1 - save_config_val "SOCKS_PORT" "$new_port" - SOCKS_PORT="$new_port" + for f in /opt/amnezia/start.sh /opt/amnezia/awg/start.sh; do + if docker exec "$CONTAINER" sh -c "[ -f '$f' ]" 2>/dev/null; then + AWG_START_SH="$f"; break + fi + done - echo -e "\n${GREEN}[OK] Порт изменён на ${new_port}.${NC}" - echo -e "${WHITE}SOCKS5: ${CYAN}127.0.0.1:${new_port}${NC}" - echo -e "\n${YELLOW}Не забудьте обновить порт в настройках 3X-UI!${NC}" - log_action "PORT: changed to ${new_port}" - read -p "Нажмите Enter..." + AWG_SUBNET=$(docker exec "$CONTAINER" sh -c "sed -n 's/^Address = \(.*\)$/\1/p' '$AWG_VPN_CONF' | head -1 | cut -d',' -f1" 2>/dev/null | tr -d '\r') + return 0 } -# ─── Uninstall ──────────────────────────────────────────────── - -full_uninstall() { - clear - echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${RED}║ ⚠ ПОЛНОЕ УДАЛЕНИЕ WARP MANAGER ⚠ ║${NC}" - echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - echo -e "${WHITE}Будут удалены:${NC}" - echo -e " ${RED}•${NC} Пакет cloudflare-warp" - echo -e " ${RED}•${NC} Репозиторий и GPG-ключ Cloudflare" - echo -e " ${RED}•${NC} Telegram-бот" - echo -e " ${RED}•${NC} Конфигурация ${WHITE}/etc/warp-manager/${NC}" - echo -e " ${RED}•${NC} Команда ${WHITE}gowarp${NC}" - echo -e " ${RED}•${NC} Логи ${WHITE}/var/log/warp-manager.log${NC}" - echo "" - echo -e "${GREEN}НЕ будет затронуто:${NC}" - echo -e " ${GREEN}•${NC} 3X-UI и Xray (настройки outbound нужно убрать вручную)" - echo -e " ${GREEN}•${NC} Kaskad PRO" - echo -e " ${GREEN}•${NC} Системные пакеты" - echo "" - read -p "$(echo -e "${RED}Удалить WARP Manager полностью? (y/n): ${NC}")" confirm1 - [[ "$confirm1" != "y" ]] && { echo -e "\n${CYAN}Отменено.${NC}"; read -p "Нажмите Enter..."; return; } - - echo "" - echo -e "${YELLOW}Удаление WARP Manager...${NC}\n" - - if systemctl is-active warp-bot &>/dev/null; then - systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null +awg_detect_warp_exit_ip() { + AWG_WARP_EXIT_IP="" + if docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null; then + AWG_WARP_EXIT_IP=$(docker exec "$CONTAINER" sh -c \ + "curl -s --interface warp --connect-timeout 3 https://ifconfig.me 2>/dev/null || true" | tr -d '\r\n') fi +} + +is_warp_installed_awg() { + docker exec "$CONTAINER" sh -c "[ -f '$AWG_WARP_CONF' ]" 2>/dev/null +} + +is_warp_running_awg() { + docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null +} + +get_warp_status_awg() { + if ! is_warp_installed_awg; then echo "Не установлен"; return; fi + is_warp_running_awg && echo "Подключён" || echo "Отключён" +} + +awg_backup() { + local ts; ts=$(date +%Y%m%d-%H%M%S) + docker exec "$CONTAINER" sh -c " + [ -f '$AWG_VPN_CONF' ] && cp '$AWG_VPN_CONF' '${AWG_VPN_CONF}.bak-${ts}' + [ -f '$AWG_CLIENTS_TABLE' ] && cp '$AWG_CLIENTS_TABLE' '${AWG_CLIENTS_TABLE}.bak-${ts}' + [ -f '$AWG_START_SH' ] && cp '$AWG_START_SH' '${AWG_START_SH}.bak-${ts}' + [ -f '$AWG_START_SH' ] && [ ! -f /opt/amnezia/start.sh.final-backup ] && cp '$AWG_START_SH' /opt/amnezia/start.sh.final-backup + " >/dev/null 2>&1 + log_action "AWG BACKUP: $ts" +} + +awg_install_wgcf() { + if [ -x "$WGCF_BIN" ]; then return 0; fi + local arch; arch=$(uname -m) + local wa="" + case "$arch" in + x86_64) wa="amd64" ;; aarch64) wa="arm64" ;; armv7l) wa="armv7" ;; + *) echo -e "${RED}Архитектура не поддерживается: $arch${NC}"; return 1 ;; + esac + wget -q -O "$WGCF_BIN" "https://github.com/ViRb3/wgcf/releases/download/v${WGCF_VERSION}/wgcf_${WGCF_VERSION}_linux_${wa}" \ + || { echo -e "${RED}Не удалось скачать wgcf.${NC}"; return 1; } + chmod +x "$WGCF_BIN" +} + +awg_ensure_account() { + if [ ! -f "$WGCF_ACCOUNT" ]; then + echo -e "${YELLOW}Регистрация WARP через wgcf...${NC}" + echo -e "${WHITE}Если попросит подтверждение — введите ${GREEN}y${NC}" + (cd /root && ./wgcf register) + fi + [ -f "$WGCF_ACCOUNT" ] || { echo -e "${RED}Не создан $WGCF_ACCOUNT${NC}"; return 1; } +} + +awg_generate_profile() { + (cd /root && ./wgcf generate) + [ -f "$WGCF_PROFILE" ] || { echo -e "${RED}Не создан профиль.${NC}"; return 1; } +} + +awg_resolve_endpoint() { + local ep; ep=$(getent ahostsv4 engage.cloudflareclient.com 2>/dev/null | awk 'NR==1{print $1}') + [ -z "$ep" ] && { echo -e "${RED}Не удалось определить IP endpoint.${NC}"; return 1; } + echo "$ep" +} + +awg_build_warp_conf() { + local endpoint_ip="$1" + local pk; pk=$(awk -F' = ' '/^PrivateKey = /{print $2}' "$WGCF_PROFILE") + local pub; pub=$(awk -F' = ' '/^PublicKey = /{print $2}' "$WGCF_PROFILE") + local addr; addr=$(awk -F' = ' '/^Address = /{print $2}' "$WGCF_PROFILE" | cut -d',' -f1) + + docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR'" + docker exec "$CONTAINER" sh -c "cat > '$AWG_WARP_CONF' <<'EOF' +[Interface] +PrivateKey = ${pk} +Address = ${addr} +MTU = 1280 +Table = off + +[Peer] +PublicKey = ${pub} +AllowedIPs = 0.0.0.0/0 +Endpoint = ${endpoint_ip}:2408 +PersistentKeepalive = 25 +EOF +chmod 600 '$AWG_WARP_CONF'" +} + +awg_warp_up() { + docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' >/dev/null 2>&1 || true" + docker exec "$CONTAINER" sh -c "wg-quick up '$AWG_WARP_CONF'" || { echo -e "${RED}wg-quick up не удался.${NC}"; return 1; } + docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" || { echo -e "${RED}Интерфейс warp не поднялся.${NC}"; return 1; } +} + +install_warp_awg() { + clear; echo -e "\n${CYAN}━━━ Установка WARP (AmneziaWG) ━━━${NC}\n" + if is_warp_installed_awg && is_warp_running_awg; then + echo -e "${YELLOW}WARP уже установлен и работает.${NC}"; read -p "Enter..."; return + fi + if is_warp_installed_awg && ! is_warp_running_awg; then + echo -e "${YELLOW}[*] WARP установлен, поднимаю интерфейс...${NC}" + awg_warp_up && echo -e "${GREEN} ✓ warp поднят${NC}" || echo -e "${RED} Ошибка${NC}" + read -p "Enter..."; return + fi + + echo -e "${YELLOW}[1/7]${NC} Бэкап контейнера..." + awg_backup; echo -e "${GREEN} ✓${NC}" + + echo -e "${YELLOW}[2/7]${NC} Скачиваю wgcf..." + awg_install_wgcf || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + + echo -e "${YELLOW}[3/7]${NC} Регистрация WARP..." + awg_ensure_account || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + + echo -e "${YELLOW}[4/7]${NC} Генерация профиля..." + awg_generate_profile || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + + echo -e "${YELLOW}[5/7]${NC} Определение endpoint..." + local ep; ep=$(awg_resolve_endpoint) || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓ ${ep}${NC}" + + echo -e "${YELLOW}[6/7]${NC} Сборка warp.conf в контейнере..." + docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null + awg_build_warp_conf "$ep"; echo -e "${GREEN} ✓${NC}" + + echo -e "${YELLOW}[7/7]${NC} Поднимаю warp-интерфейс..." + awg_warp_up || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + + 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 установлен! Добавьте клиентов через п.5.${NC}" + log_action "AWG INSTALL: endpoint=${ep}, warp_ip=${AWG_WARP_EXIT_IP}" + read -p "Enter..." +} + +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}" + read -p "Enter..." +} + +stop_warp_awg() { + is_warp_running_awg || { echo -e "\n${YELLOW}Уже остановлен.${NC}"; read -p "Enter..."; return; } + docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true" + echo -e "${GREEN}[OK] WARP остановлен.${NC}"; log_action "AWG STOP" + read -p "Enter..." +} + +rekey_warp_awg() { + is_warp_installed_awg || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; } + echo -e "\n${CYAN}━━━ Перевыпуск ключа WARP (AmneziaWG) ━━━${NC}\n" + read -p "Продолжить? (y/n): " c; [[ "$c" != "y" ]] && return + echo -e "${YELLOW}[1/5]${NC} Остановка warp..."; docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[2/5]${NC} Удаление аккаунта..."; rm -f "$WGCF_ACCOUNT"; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[3/5]${NC} Новая регистрация..."; awg_ensure_account || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[4/5]${NC} Генерация профиля..."; awg_generate_profile || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}" + echo -e "${YELLOW}[5/5]${NC} Пересборка и запуск..." + local ep; ep=$(awg_resolve_endpoint) || { read -p "Enter..."; return; } + docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null + awg_build_warp_conf "$ep" + awg_warp_up || { read -p "Enter..."; return; } + 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}" + log_action "AWG REKEY: warp_ip=${AWG_WARP_EXIT_IP}" + read -p "Enter..." +} + +show_status_awg() { + clear; echo -e "\n${CYAN}━━━ Статус WARP (AmneziaWG) ━━━${NC}\n" + echo -e " ${WHITE}Контейнер: ${CYAN}${CONTAINER}${NC}" + echo -e " ${WHITE}Подсеть: ${CYAN}${AWG_SUBNET:-N/A}${NC}" + local st; st=$(get_warp_status_awg) + local sc="$RED"; [[ "$st" == "Подключён" ]] && sc="$GREEN"; [[ "$st" == "Отключён" ]] && sc="$YELLOW" + echo -e " ${WHITE}WARP: ${sc}${st}${NC}" + echo -e " ${WHITE}Реальный IP: ${GREEN}${MY_IP}${NC}" + awg_detect_warp_exit_ip + [ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}" + + awg_load_clients + echo -e "\n ${WHITE}Клиентов в WARP: ${CYAN}${#AWG_SELECTED_IPS[@]}${NC}" + if [ ${#AWG_SELECTED_IPS[@]} -gt 0 ]; then + awg_parse_clients_table + for ip in "${AWG_SELECTED_IPS[@]}"; do + echo -e " ${GREEN}●${NC} $(awg_format_label "$ip")" + done + fi + + if is_warp_running_awg; then + echo -e "\n ${CYAN}── wg show warp ──${NC}" + docker exec "$CONTAINER" sh -c "wg show warp 2>/dev/null" | while IFS= read -r l; do echo -e " ${WHITE}$l${NC}"; done + fi + echo ""; read -p "Enter..." +} + +uninstall_awg() { + echo -e "\n${YELLOW}Удаление WARP (AmneziaWG)...${NC}\n" + systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null + rm -f /etc/systemd/system/warp-bot.service; systemctl daemon-reload 2>/dev/null [ -f "$BOT_PID_FILE" ] && { kill "$(cat "$BOT_PID_FILE")" 2>/dev/null; rm -f "$BOT_PID_FILE"; } - rm -f /etc/systemd/system/warp-bot.service - systemctl daemon-reload 2>/dev/null - echo -e " ${GREEN}✓${NC} Telegram-бот остановлен" + echo -e " ${GREEN}✓${NC} Бот остановлен" - warp-cli --accept-tos disconnect > /dev/null 2>&1 - echo -e " ${GREEN}✓${NC} WARP отключён" + awg_cleanup_rules + docker exec "$CONTAINER" sh -c " + wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true + ip link del warp 2>/dev/null || true + rm -rf '$AWG_WARP_DIR' + " >/dev/null 2>&1 + echo -e " ${GREEN}✓${NC} WARP удалён из контейнера" - warp-cli --accept-tos registration delete > /dev/null 2>&1 - echo -e " ${GREEN}✓${NC} Регистрация удалена" + awg_remove_from_start_sh + if docker exec "$CONTAINER" sh -c '[ -f /opt/amnezia/start.sh.final-backup ]' 2>/dev/null; then + docker exec "$CONTAINER" sh -c " + cp /opt/amnezia/start.sh.final-backup '$AWG_START_SH' 2>/dev/null + chmod +x '$AWG_START_SH' 2>/dev/null + rm -f /opt/amnezia/start.sh.final-backup + " 2>/dev/null + echo -e " ${GREEN}✓${NC} start.sh восстановлен" + fi - apt-get remove -y cloudflare-warp > /dev/null 2>&1 - apt-get autoremove -y > /dev/null 2>&1 - echo -e " ${GREEN}✓${NC} Пакет удалён" + rm -f "$WGCF_BIN" "$WGCF_ACCOUNT" "$WGCF_PROFILE" + echo -e " ${GREEN}✓${NC} wgcf и профили удалены" +} - rm -f /etc/apt/sources.list.d/cloudflare-client.list - rm -f /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg - echo -e " ${GREEN}✓${NC} Репозиторий и ключ удалены" +awg_restart_container() { + echo -e "\n${YELLOW}Перезапуск контейнера ${CONTAINER}...${NC}" + docker restart "$CONTAINER" >/dev/null + local a=0 + while [ "$a" -lt 10 ]; do + docker exec "$CONTAINER" sh -c "true" 2>/dev/null && { echo -e "${GREEN}[OK] Перезапущен.${NC}"; log_action "AWG RESTART"; read -p "Enter..."; return; } + sleep 1; ((a++)) + done + echo -e "${RED}Не удалось за 10с.${NC}"; read -p "Enter..." +} - rm -rf "$WARP_DIR" - rm -f "$WARP_LOG" - echo -e " ${GREEN}✓${NC} Конфигурация и логи удалены" +# ═══════════════════════════════════════════════════════════════ +# AMNEZIA CLIENT MANAGEMENT +# ═══════════════════════════════════════════════════════════════ - rm -f /usr/local/bin/gowarp - echo -e " ${GREEN}✓${NC} Команда gowarp удалена" +awg_load_clients() { + AWG_SELECTED_IPS=() + local raw; raw=$(docker exec "$CONTAINER" sh -c "cat '$AWG_WARP_CLIENTS' 2>/dev/null || true" | tr -d '\r') + if [ -n "$raw" ]; then + while IFS= read -r line; do + line=$(echo "$line" | xargs) + [ -n "$line" ] && AWG_SELECTED_IPS+=("$line") + done <<< "$raw" + fi +} + +awg_save_clients() { + local content="" + for ip in "${AWG_SELECTED_IPS[@]}"; do content+="${ip}"$'\n'; done + docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR' && cat > '$AWG_WARP_CLIENTS' <<'EOF' +${content}EOF +" +} + +awg_parse_clients_table() { + AWG_CLIENT_NAMES=() + [ -z "${AWG_CLIENTS_TABLE:-}" ] && return 0 + local raw; raw=$(docker exec "$CONTAINER" sh -c "cat '$AWG_CLIENTS_TABLE' 2>/dev/null || true" | tr -d '\r') + [ -z "$raw" ] && return 0 + + declare -A key_to_name=() + local pairs; pairs=$(echo "$raw" | awk ' + /"clientId"/ { s=$0; gsub(/.*"clientId"[[:space:]]*:[[:space:]]*"/,"",s); gsub(/".*/,"",s); cid=s } + /"clientName"/ { s=$0; gsub(/.*"clientName"[[:space:]]*:[[:space:]]*"/,"",s); gsub(/".*/,"",s); name=s; + if(cid!=""&&name!="") print cid"|"name }') + if [ -n "$pairs" ]; then + while IFS='|' read -r cid name; do + [ -n "$cid" ] && [ -n "$name" ] && key_to_name["$cid"]="$name" + done <<< "$pairs" + fi + + local peers; peers=$(docker exec "$CONTAINER" sh -c "cat '$AWG_VPN_CONF' 2>/dev/null || true" | tr -d '\r' | awk ' + /^\[Peer\]/ {pk="";ip=""} + /^PublicKey/ {s=$0; sub(/^[^=]*= */,"",s); pk=s} + /^AllowedIPs/ {s=$0; sub(/^[^=]*= */,"",s); ip=s; if(pk!=""&&ip!="") print pk"|"ip}') + if [ -n "$peers" ]; then + while IFS='|' read -r pk ip; do + if [ -n "$pk" ] && [ -n "$ip" ] && [ -n "${key_to_name[$pk]+_}" ]; then + AWG_CLIENT_NAMES["$ip"]="${key_to_name[$pk]}" + AWG_CLIENT_NAMES["${ip%/32}"]="${key_to_name[$pk]}" + fi + done <<< "$peers" + fi +} + +awg_get_name() { + local ip="$1" bare="${1%/32}" + [ -n "${AWG_CLIENT_NAMES[$bare]+_}" ] && { echo "${AWG_CLIENT_NAMES[$bare]}"; return; } + [ -n "${AWG_CLIENT_NAMES[$ip]+_}" ] && { echo "${AWG_CLIENT_NAMES[$ip]}"; return; } + [ -n "${AWG_CLIENT_NAMES[${bare}/32]+_}" ] && { echo "${AWG_CLIENT_NAMES[${bare}/32]}"; return; } +} + +awg_format_label() { + local ip="$1" name; name=$(awg_get_name "$ip") + [ -n "$name" ] && echo "$ip ($name)" || echo "$ip" +} + +awg_get_client_ips() { + AWG_CLIENT_IPS=() + mapfile -t AWG_CLIENT_IPS < <(docker exec "$CONTAINER" sh -c "sed -n 's/^AllowedIPs = \(.*\/32\)$/\1/p' '$AWG_VPN_CONF'" 2>/dev/null | tr -d '\r') +} + +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" + 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))]}") + done + fi echo "" - echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}" - echo -e "${GREEN} WARP Manager полностью удалён.${NC}" - echo -e "${WHITE} Не забудьте убрать outbound \"warp\" из настроек 3X-UI!${NC}" - echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}" + 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")" + else + echo -e " ${DIM}Уже в WARP: $(awg_format_label "$nip")${NC}" + 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 "" - log_action "UNINSTALL: full removal completed" - read -p "Нажмите Enter..." - exit 0 + 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..." +} + +# ═══════════════════════════════════════════════════════════════ +# AMNEZIA RUNTIME RULES & PERSISTENCE +# ═══════════════════════════════════════════════════════════════ + +awg_cleanup_rules() { + docker exec "$CONTAINER" sh -c ' + ip rule | awk "/lookup 100/ {print \$1}" | sed "s/://g" | sort -rn | while read -r pr; do + ip rule del priority "$pr" 2>/dev/null || true + done + iptables -t nat -S POSTROUTING | grep "\-o warp -j MASQUERADE" | while read -r line; do + rule=$(echo "$line" | sed "s/^-A /-D /") + iptables -t nat $rule || true + done + ip route flush table 100 2>/dev/null || true + ' >/dev/null 2>&1 || true +} + +awg_apply_rules() { + awg_cleanup_rules + [ ${#AWG_SELECTED_IPS[@]} -eq 0 ] && return 0 + docker exec "$CONTAINER" sh -c "ip route add default dev warp table 100 2>/dev/null || ip route replace default dev warp table 100" + local prio=100 + for ip in "${AWG_SELECTED_IPS[@]}"; do + docker exec "$CONTAINER" sh -c " + ip rule add from ${ip} table 100 priority ${prio} 2>/dev/null || true + iptables -t nat -C POSTROUTING -s ${ip} -o warp -j MASQUERADE 2>/dev/null || \ + iptables -t nat -I POSTROUTING 1 -s ${ip} -o warp -j MASQUERADE + " + ((prio++)) + done +} + +awg_patch_start_sh() { + [ -z "${AWG_START_SH:-}" ] && return + docker exec "$CONTAINER" sh -c "[ -f /opt/amnezia/start.sh.final-backup ] || cp '$AWG_START_SH' /opt/amnezia/start.sh.final-backup" 2>/dev/null + + local block="${AWG_MARKER_BEGIN} +" + block+="if [ -f '${AWG_WARP_CONF}' ]; then wg-quick up '${AWG_WARP_CONF}' || true; fi +" + if [ ${#AWG_SELECTED_IPS[@]} -gt 0 ]; then + block+="ip route add default dev warp table 100 2>/dev/null || ip route replace default dev warp table 100 2>/dev/null || true +" + local prio=100 + for ip in "${AWG_SELECTED_IPS[@]}"; do + block+="ip rule add from ${ip} table 100 priority ${prio} 2>/dev/null || true +" + block+="iptables -t nat -C POSTROUTING -s ${ip} -o warp -j MASQUERADE 2>/dev/null || iptables -t nat -I POSTROUTING 1 -s ${ip} -o warp -j MASQUERADE +" + ((prio++)) + done + fi + block+="${AWG_MARKER_END}" + + docker exec "$CONTAINER" sh -c " + if grep -qF '${AWG_MARKER_BEGIN}' '$AWG_START_SH' 2>/dev/null; then + sed -i '/${AWG_MARKER_BEGIN//\//\\/}/,/${AWG_MARKER_END//\//\\/}/d' '$AWG_START_SH' + fi + " 2>/dev/null + + docker exec "$CONTAINER" sh -c " + if grep -qF 'tail -f /dev/null' '$AWG_START_SH'; then + tmpf=\$(mktemp) + while IFS= read -r line; do + if echo \"\$line\" | grep -qF 'tail -f /dev/null'; then + cat <<'WBLOCK' +${block} +WBLOCK + fi + echo \"\$line\" + done < '$AWG_START_SH' > \"\$tmpf\" + mv \"\$tmpf\" '$AWG_START_SH'; chmod +x '$AWG_START_SH' + else + cat >> '$AWG_START_SH' <<'WBLOCK' + +${block} +WBLOCK + chmod +x '$AWG_START_SH' + fi + " 2>/dev/null +} + +awg_remove_from_start_sh() { + [ -z "${AWG_START_SH:-}" ] && return + docker exec "$CONTAINER" sh -c " + if grep -qF '${AWG_MARKER_BEGIN}' '$AWG_START_SH' 2>/dev/null; then + sed -i '/${AWG_MARKER_BEGIN//\//\\/}/,/${AWG_MARKER_END//\//\\/}/d' '$AWG_START_SH' + fi + " 2>/dev/null +} + +# ═══════════════════════════════════════════════════════════════ +# UNIFIED WRAPPERS +# ═══════════════════════════════════════════════════════════════ + +get_warp_status() { + [ "$MODE" = "3xui" ] && get_warp_status_3xui || get_warp_status_awg +} + +get_warp_ip() { + if [ "$MODE" = "3xui" ]; then + get_warp_ip_3xui + else + awg_detect_warp_exit_ip; echo "${AWG_WARP_EXIT_IP:-N/A}" + fi +} + +is_warp_running() { + [ "$MODE" = "3xui" ] && is_warp_running_3xui || is_warp_running_awg } # ═══════════════════════════════════════════════════════════════ @@ -595,40 +1009,55 @@ tg_answer_cb() { # ─── Bot keyboards ─────────────────────────────────────────── -kbd_main() { +kbd_main_3xui() { cat <<'JSON' [ - [{"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":"rk"}], - [{"text":"📋 JSON для 3X-UI","callback_data":"js"}], + [{"text":"🔑 Перевыпуск","callback_data":"rk"}], + [{"text":"📋 JSON 3X-UI","callback_data":"js"}], [{"text":"💻 Система","callback_data":"sys"}], [{"text":"🏢 Хостинг","callback_data":"promo"}] ] JSON } +kbd_main_awg() { + cat <<'JSON' +[ + [{"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":"💻 Система","callback_data":"sys"}], + [{"text":"🏢 Хостинг","callback_data":"promo"}] +] +JSON +} + +kbd_main() { [ "$MODE" = "3xui" ] && kbd_main_3xui || kbd_main_awg; } + 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"}]]'; } - -# ─── Bot handlers ──────────────────────────────────────────── +# ─── Bot main menu ─────────────────────────────────────────── bot_main_menu() { local chat_id="$1" msg_id="${2:-}" - local ws wip="" - ws=$(get_warp_status_text) + local ws wip="" extra="" + ws=$(get_warp_status) is_warp_running && wip=" | WARP IP: $(get_warp_ip)" - local text="WARP Manager v${WARP_VERSION}\nСервер: ${MY_IP:-N/A}\nСтатус: ${ws}${wip}\nSOCKS5: 127.0.0.1:${SOCKS_PORT}\n\nВыберите действие:" - local kbd - kbd=$(kbd_main) - if [ -n "$msg_id" ]; then - tg_edit "$chat_id" "$msg_id" "$text" "$kbd" - else - tg_send "$chat_id" "$text" "$kbd" - fi + [ "$MODE" = "amnezia" ] && extra="\nКонтейнер: ${CONTAINER:-N/A}" + [ "$MODE" = "3xui" ] && extra="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" + 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" } +# ─── Bot handlers ──────────────────────────────────────────── + bot_handle_callback() { local chat_id="$1" msg_id="$2" cb_id="$3" data="$4" [ -n "$cb_id" ] && tg_answer_cb "$cb_id" > /dev/null @@ -637,83 +1066,209 @@ bot_handle_callback() { m) bot_main_menu "$chat_id" "$msg_id" ;; st) - local ws wip="" - ws=$(get_warp_status_text) + local ws wip="" extra="" + ws=$(get_warp_status) is_warp_running && wip="\nWARP IP: $(get_warp_ip)" - local raw - raw=$(warp-cli --accept-tos status 2>/dev/null | head -3) - local text="📊 Статус WARP\n\nСтатус: ${ws}\nSOCKS5: 127.0.0.1:${SOCKS_PORT}\nСервер: ${MY_IP:-N/A}${wip}\n\n
${raw}
" - tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" ;; + if [ "$MODE" = "3xui" ]; then + extra="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" + local raw; raw=$(warp-cli --accept-tos status 2>/dev/null | head -3) + extra+="\n\n
${raw}
" + else + extra="\nКонтейнер: ${CONTAINER}\nПодсеть: ${AWG_SUBNET:-N/A}" + awg_load_clients + extra+="\nКлиентов в WARP: ${#AWG_SELECTED_IPS[@]}" + fi + tg_edit "$chat_id" "$msg_id" "📊 Статус WARP\n\nСтатус: ${ws}\nСервер: ${MY_IP:-N/A}${wip}${extra}" "$(kbd_back)" ;; ip) - local wip="N/A" - is_warp_running && wip=$(get_warp_ip) - local text="🌐 IP адреса\n\nРеальный IP: ${MY_IP:-N/A}\nWARP IP: ${wip}\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" - tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" ;; + local wip="N/A"; is_warp_running && wip=$(get_warp_ip) + local t="🌐 IP адреса\n\nРеальный: ${MY_IP:-N/A}\nWARP: ${wip}" + [ "$MODE" = "3xui" ] && t+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" + tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;; on) - if ! is_warp_installed; then - tg_edit "$chat_id" "$msg_id" "❌ WARP не установлен." "$(kbd_back)"; return - fi - if is_warp_running; then - tg_edit "$chat_id" "$msg_id" "✅ WARP уже подключён." "$(kbd_back)"; return - fi - tg_edit "$chat_id" "$msg_id" "⏳ Подключение WARP..." "" - warp-cli --accept-tos connect > /dev/null 2>&1 - sleep 3 - if is_warp_running; then - local wip - wip=$(get_warp_ip) - log_action "BOT: WARP connected" - tg_edit "$chat_id" "$msg_id" "✅ WARP подключён\nWARP IP: ${wip}" "$(kbd_back)" + if [ "$MODE" = "3xui" ]; then + is_warp_installed_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" "⏳ Подключение..." "" + 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)" else - tg_edit "$chat_id" "$msg_id" "❌ Не удалось подключить WARP." "$(kbd_back)" + 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; } + tg_edit "$chat_id" "$msg_id" "⏳ Запуск..." "" + awg_warp_up 2>/dev/null + if is_warp_running_awg; then + awg_detect_warp_exit_ip + tg_edit "$chat_id" "$msg_id" "✅ WARP запущен\nWARP IP: ${AWG_WARP_EXIT_IP:-?}" "$(kbd_back)"; log_action "BOT AWG ON" + else tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; fi fi ;; off) - if ! is_warp_running; then - tg_edit "$chat_id" "$msg_id" "ℹ️ WARP уже отключён." "$(kbd_back)"; return - fi - warp-cli --accept-tos disconnect > /dev/null 2>&1 - log_action "BOT: WARP disconnected" - tg_edit "$chat_id" "$msg_id" "⏹ WARP отключён." "$(kbd_back)" ;; + if [ "$MODE" = "3xui" ]; then + is_warp_running_3xui || { tg_edit "$chat_id" "$msg_id" "ℹ️ Уже отключён." "$(kbd_back)"; return; } + warp-cli --accept-tos disconnect > /dev/null 2>&1 + tg_edit "$chat_id" "$msg_id" "⏹ WARP отключён." "$(kbd_back)"; log_action "BOT 3XUI OFF" + else + is_warp_running_awg || { tg_edit "$chat_id" "$msg_id" "ℹ️ Уже остановлен." "$(kbd_back)"; return; } + docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true" + tg_edit "$chat_id" "$msg_id" "⏹ WARP остановлен." "$(kbd_back)"; log_action "BOT AWG OFF" + fi ;; rk) - tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\nТекущая регистрация будет удалена и создана новая.\nWARP будет временно отключён.\n\nПродолжить?" "$(kbd_rekey_confirm)" ;; + tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\nПродолжить?" "$(kbd_rekey_confirm)" ;; rk_y) - tg_edit "$chat_id" "$msg_id" "⏳ Перевыпуск ключа..." "" - warp-cli --accept-tos disconnect > /dev/null 2>&1 - warp-cli --accept-tos registration delete > /dev/null 2>&1 - warp-cli --accept-tos registration new > /dev/null 2>&1 - warp-cli --accept-tos mode proxy > /dev/null 2>&1 - warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 - warp-cli --accept-tos connect > /dev/null 2>&1 - sleep 3 - if is_warp_running; then - local wip - wip=$(get_warp_ip) - log_action "BOT REKEY: warp_ip=${wip}" - tg_edit "$chat_id" "$msg_id" "✅ Ключ перевыпущен\nНовый WARP IP: ${wip}" "$(kbd_back)" + tg_edit "$chat_id" "$msg_id" "⏳ Перевыпуск..." "" + if [ "$MODE" = "3xui" ]; then + warp-cli --accept-tos disconnect > /dev/null 2>&1 + warp-cli --accept-tos registration delete > /dev/null 2>&1 + warp-cli --accept-tos registration new > /dev/null 2>&1 + warp-cli --accept-tos mode proxy > /dev/null 2>&1 + warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 + warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 + 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 REKEY: ${w}" + else tg_edit "$chat_id" "$msg_id" "⚠️ Перевыпущен, подключение не подтверждено." "$(kbd_back)"; fi else - tg_edit "$chat_id" "$msg_id" "⚠️ Ключ перевыпущен, подключение не подтверждено." "$(kbd_back)" + docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true" + rm -f "$WGCF_ACCOUNT" + (cd /root && ./wgcf register --accept-tos 2>/dev/null && ./wgcf generate 2>/dev/null) + if [ -f "$WGCF_PROFILE" ]; then + local ep; ep=$(awg_resolve_endpoint 2>/dev/null) + if [ -n "$ep" ]; then + docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null + awg_build_warp_conf "$ep" + awg_warp_up 2>/dev/null + awg_load_clients; awg_apply_rules; awg_patch_start_sh + awg_detect_warp_exit_ip + tg_edit "$chat_id" "$msg_id" "✅ Ключ перевыпущен\nWARP IP: ${AWG_WARP_EXIT_IP:-?}" "$(kbd_back)"; log_action "BOT AWG REKEY" + else tg_edit "$chat_id" "$msg_id" "❌ Ошибка endpoint." "$(kbd_back)"; fi + else tg_edit "$chat_id" "$msg_id" "❌ Ошибка генерации профиля." "$(kbd_back)"; fi fi ;; js) - local text="📋 Конфигурация для 3X-UI\n\nOutbound:\n
{\n  \"tag\": \"warp\",\n  \"protocol\": \"socks\",\n  \"settings\": {\n    \"servers\": [{\n      \"address\": \"127.0.0.1\",\n      \"port\": ${SOCKS_PORT}\n    }]\n  }\n}
\n\nRouting rule:\n
{\n  \"outboundTag\": \"warp\",\n  \"domain\": [\n    \"geosite:openai\",\n    \"geosite:netflix\"\n  ]\n}
" - tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" ;; + if [ "$MODE" = "3xui" ]; then + local t="📋 Конфигурация для 3X-UI\n\nOutbound:\n
{\n  \"tag\": \"warp\",\n  \"protocol\": \"socks\",\n  \"settings\": {\n    \"servers\": [{\"address\": \"127.0.0.1\", \"port\": ${SOCKS_PORT}}]\n  }\n}
\n\nRouting:\n
{\"outboundTag\": \"warp\", \"domain\": [\"geosite:openai\",\"geosite:netflix\"]}
" + tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" + else + tg_edit "$chat_id" "$msg_id" "ℹ️ JSON не требуется для AmneziaWG." "$(kbd_back)" + 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" ;; + + ca:*) + local idx="${data#ca:}" + awg_get_client_ips; awg_load_clients + if [ "$idx" = "all" ]; then + AWG_SELECTED_IPS=("${AWG_CLIENT_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") + 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[@]}" ;; + + rc) + if [ "$MODE" != "amnezia" ]; then + tg_edit "$chat_id" "$msg_id" "ℹ️ Только для AmneziaWG." "$(kbd_back)"; return + fi + tg_edit "$chat_id" "$msg_id" "🔄 Перезапуск контейнера..." "" + docker restart "$CONTAINER" >/dev/null 2>&1; sleep 5 + if docker exec "$CONTAINER" sh -c "true" 2>/dev/null; then + tg_edit "$chat_id" "$msg_id" "✅ Контейнер перезапущен." "$(kbd_back)"; log_action "BOT AWG RESTART" + else + tg_edit "$chat_id" "$msg_id" "⚠️ Контейнер не отвечает." "$(kbd_back)" + fi ;; sys) - local s - s=$(get_system_stats) - local ws - ws=$(get_warp_status_text) + local s; s=$(get_system_stats) + local ws; ws=$(get_warp_status) s+="\nWARP: ${ws}" - s+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" + s+="\nРежим: ${MODE}" + [ "$MODE" = "3xui" ] && s+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" + [ "$MODE" = "amnezia" ] && s+="\nКонтейнер: ${CONTAINER:-N/A}" tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;; promo) - local pt="🏢 Хостинг, который работает\n\n🌍 РФ и Европа\n👉 https://vk.cc/ct29NQ\n\nOFF60 — 60% скидка\nantenka20 — +20% (3мес)\nantenka6 — +15% (6мес)\nantenka12 — +5% (12мес)\n\n🇧🇾 Беларусь\n👉 https://vk.cc/cUxAhj\nOFF60 — 60% скидка" + local pt="🏢 Хостинг\n\n🌍 РФ и Европа\n👉 https://vk.cc/ct29NQ\n\nOFF60 — 60%\nantenka20 — +20% (3мес)\n\n🇧🇾 Беларусь\n👉 https://vk.cc/cUxAhj\nOFF60 — 60%" tg_edit "$chat_id" "$msg_id" "$pt" "$(kbd_back)" ;; esac } @@ -723,56 +1278,49 @@ bot_handle_callback() { bot_daemon() { log_action "Bot daemon started (PID $$)"; echo $$ > "$BOT_PID_FILE" source "$WARP_CONF" - [ -z "$BOT_TOKEN" ] && log_action "BOT ERROR: no token" && exit 1 + [ -z "$BOT_TOKEN" ] && { log_action "BOT ERROR: no token"; exit 1; } get_my_ip + [ "$MODE" = "amnezia" ] && [ -n "$CONTAINER" ] && awg_load_container_data 2>/dev/null local offset=0 while true; do local response response=$(curl -s --max-time 35 \ "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=${offset}&timeout=30" 2>/dev/null) [ -z "$response" ] && sleep 2 && continue - local ok - ok=$(echo "$response" | jq -r '.ok // "false"') + local ok; ok=$(echo "$response" | jq -r '.ok // "false"') [ "$ok" != "true" ] && sleep 5 && continue - local cnt - cnt=$(echo "$response" | jq '.result | length') + local cnt; cnt=$(echo "$response" | jq '.result | length') for (( i=0; i /dev/null && continue + [ -n "$BOT_CHAT_ID" ] && [ "$cci" != "$BOT_CHAT_ID" ] && { tg_answer_cb "$cbi" "Нет доступа" > /dev/null; continue; } bot_handle_callback "$cci" "$cmi" "$cbi" "$cbd" else local mci mtx mci=$(echo "$upd" | jq -r '.message.chat.id // empty') 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 - if [ "$mtx" = "/start" ] || [ "$mtx" = "/menu" ]; then - bot_main_menu "$mci" - else - tg_send "$mci" "Используйте /start или /menu" "" > /dev/null - fi + [ -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 fi fi done done } +# ─── Bot menu (SSH) ────────────────────────────────────────── + start_bot() { source "$WARP_CONF" - [ -z "$BOT_TOKEN" ] && echo -e "${RED}Задайте BOT_TOKEN!${NC}" && return - [ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE")" 2>/dev/null && echo -e "${YELLOW}Уже запущен.${NC}" && return + [ -z "$BOT_TOKEN" ] && { echo -e "${RED}Задайте BOT_TOKEN!${NC}"; return; } + [ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE")" 2>/dev/null && { echo -e "${YELLOW}Уже запущен.${NC}"; return; } cat > /etc/systemd/system/warp-bot.service < /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" \ || echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}" } stop_bot() { - systemctl stop warp-bot 2>/dev/null - systemctl disable warp-bot 2>/dev/null - rm -f "$BOT_PID_FILE" - echo -e "${GREEN}[OK] Бот остановлен.${NC}" - log_action "Bot stopped" + systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null; rm -f "$BOT_PID_FILE" + echo -e "${GREEN}[OK] Бот остановлен.${NC}"; log_action "Bot stopped" } bot_menu() { @@ -806,14 +1348,11 @@ bot_menu() { clear; source "$WARP_CONF" 2>/dev/null local bs="${RED}Выкл${NC}" [ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE" 2>/dev/null)" 2>/dev/null && bs="${GREEN}Вкл ($(cat "$BOT_PID_FILE"))${NC}" - local td="нет" - [ -n "${BOT_TOKEN:-}" ] && td="***${BOT_TOKEN: -6}" - + local td="нет"; [ -n "${BOT_TOKEN:-}" ] && td="***${BOT_TOKEN: -6}" echo -e "${CYAN}━━━ Telegram Bot ━━━${NC}" echo -e "Статус: $bs" echo -e "Токен: ${YELLOW}$td${NC}" - echo -e "Chat ID: ${YELLOW}${BOT_CHAT_ID:-нет}${NC}" - echo "" + echo -e "Chat ID: ${YELLOW}${BOT_CHAT_ID:-нет}${NC}\n" echo -e "1) Токен бота" echo -e "2) Chat ID (авто)" echo -e "3) Chat ID (вручную)" @@ -825,13 +1364,11 @@ bot_menu() { 1) echo "Токен:"; read -p "> " t [ -n "$t" ] && save_config_val "BOT_TOKEN" "$t" && BOT_TOKEN="$t" && echo -e "${GREEN}OK${NC}" read -p "Enter..." ;; - 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') + 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}Не удалось получить Chat ID.${NC}" + || echo -e "${RED}Не удалось.${NC}" 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}" @@ -844,7 +1381,7 @@ bot_menu() { } # ═══════════════════════════════════════════════════════════════ -# PROMO +# PROMO / INFO # ═══════════════════════════════════════════════════════════════ show_promo() { @@ -856,48 +1393,66 @@ show_promo() { printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка" "antenka20" "+20% (3мес)" "antenka6" "+15% (6мес)" "antenka12" "+5% (12мес)" echo -e "\n${CYAN}🇧🇾 БЕЛАРУСЬ${NC}\n${WHITE} >>> https://vk.cc/cUxAhj${NC}" printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка" - echo -e "\n${YELLOW}QR-код... (3с)${NC}"; for i in 3 2 1; do echo -ne "$i..."; sleep 1; done; echo "" + echo -e "\n${YELLOW}QR-код...${NC}"; sleep 2 echo -e "\n${WHITE}"; command -v qrencode &>/dev/null && qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ" || echo "Ссылки выше."; echo -e "${NC}" - read -p "Нажмите Enter..." + read -p "Enter..." } -# ═══════════════════════════════════════════════════════════════ -# INFO PAGE -# ═══════════════════════════════════════════════════════════════ - show_info() { clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${MAGENTA}║ 📚 WARP Manager v${WARP_VERSION} — Что это и как работает ║${NC}" + echo -e "${MAGENTA}║ 📚 WARP Manager v${WARP_VERSION} ║${NC}" echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" - echo -e "${CYAN}═══ ЧТО ТАКОЕ CLOUDFLARE WARP ═══${NC}" - echo "" - echo -e "${WHITE} Cloudflare WARP — бесплатный сервис от Cloudflare, который" - echo -e " направляет трафик через глобальную сеть Cloudflare." - echo -e "" - echo -e " Ваш VPS получает «чистый» IP-адрес Cloudflare, который" - echo -e " не заблокирован популярными сервисами.${NC}" - echo "" - echo -e "${CYAN}═══ СХЕМА РАБОТЫ ═══${NC}" - echo "" - echo -e "${WHITE} Клиент → 3X-UI (Xray) → SOCKS5 (WARP) → Cloudflare → Интернет${NC}" - echo "" - echo -e "${GREEN} 1.${NC} Скрипт устанавливает ${YELLOW}cloudflare-warp${NC} на сервер" - echo -e "${GREEN} 2.${NC} WARP работает в режиме ${YELLOW}SOCKS5-прокси${NC} на 127.0.0.1:${SOCKS_PORT}" - echo -e "${GREEN} 3.${NC} В 3X-UI добавляется ${YELLOW}outbound${NC} типа SOCKS → warp" - echo -e "${GREEN} 4.${NC} В маршрутизации выбираете какие сайты идут через WARP" - echo "" - echo -e "${CYAN}═══ ЧТО ПОЛУЧАЕТЕ ═══${NC}" - echo "" - echo -e " ${GREEN}✓${NC} Разблокировка ChatGPT, Netflix, Disney+, Spotify" - echo -e " ${GREEN}✓${NC} Чистый IPv4/IPv6 от Cloudflare" - echo -e " ${GREEN}✓${NC} Стабильный маршрут через сеть Cloudflare" - echo -e " ${GREEN}✓${NC} Управление через Telegram-бот" - echo -e " ${GREEN}✓${NC} Бесплатно (Cloudflare WARP — бесплатный сервис)" - echo "" - echo -e "${MAGENTA}──────────────────────────────────────────────────────────────${NC}" - read -p "Нажмите Enter для продолжения..." + if [ "$MODE" = "3xui" ]; then + echo -e "${CYAN}═══ РЕЖИМ: 3X-UI ═══${NC}\n" + echo -e "${WHITE} Клиент → 3X-UI (Xray) → SOCKS5 (WARP) → Cloudflare → Интернет${NC}\n" + echo -e "${GREEN} 1.${NC} cloudflare-warp установлен нативно" + echo -e "${GREEN} 2.${NC} SOCKS5-прокси на 127.0.0.1:${SOCKS_PORT}" + echo -e "${GREEN} 3.${NC} В 3X-UI: outbound SOCKS → warp" + echo -e "${GREEN} 4.${NC} Маршрутизация по доменам в Xray" + else + echo -e "${CYAN}═══ РЕЖИМ: AmneziaWG ═══${NC}\n" + echo -e "${WHITE} Клиент → AmneziaWG Docker → warp WG → Cloudflare → Интернет${NC}\n" + echo -e "${GREEN} 1.${NC} wgcf генерирует WireGuard-профиль WARP" + echo -e "${GREEN} 2.${NC} WG-интерфейс warp внутри Docker-контейнера" + echo -e "${GREEN} 3.${NC} Маршрутизация per-client через ip rule" + echo -e "${GREEN} 4.${NC} Персистентность через start.sh контейнера" + fi + echo -e "\n${GREEN} ✓${NC} Разблокировка ChatGPT, Netflix, Disney+, Spotify" + echo -e "${GREEN} ✓${NC} Чистый IP от Cloudflare" + echo -e "${GREEN} ✓${NC} Telegram-бот" + echo -e "${GREEN} ✓${NC} Бесплатно (Cloudflare WARP)" + echo ""; read -p "Enter..." +} + +# ═══════════════════════════════════════════════════════════════ +# FULL UNINSTALL +# ═══════════════════════════════════════════════════════════════ + +full_uninstall() { + clear + echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ ⚠ ПОЛНОЕ УДАЛЕНИЕ WARP MANAGER ⚠ ║${NC}" + echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}\n" + echo -e "${WHITE}Режим: ${CYAN}${MODE}${NC}\n" + read -p "$(echo -e "${RED}Удалить полностью? (y/n): ${NC}")" c1 + [[ "$c1" != "y" ]] && return + + [ "$MODE" = "3xui" ] && uninstall_3xui || uninstall_awg + + rm -rf "$WARP_DIR" "$WARP_LOG" + echo -e " ${GREEN}✓${NC} Конфигурация и логи" + rm -f /usr/local/bin/gowarp + echo -e " ${GREEN}✓${NC} Команда gowarp" + + echo -e "\n${GREEN}══════════════════════════════════════════════════════════════${NC}" + echo -e "${GREEN} WARP Manager полностью удалён.${NC}" + [ "$MODE" = "3xui" ] && echo -e "${WHITE} Уберите outbound \"warp\" из 3X-UI!${NC}" + echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}" + log_action "UNINSTALL: full removal ($MODE)" + read -p "Enter..." + exit 0 } # ═══════════════════════════════════════════════════════════════ @@ -907,28 +1462,40 @@ show_info() { show_menu() { while true; do clear - local status_text status_color - status_text=$(get_warp_status_text) - status_color="$RED" - [[ "$status_text" == "Подключён" ]] && status_color="$GREEN" - [[ "$status_text" == "Отключён" ]] && status_color="$YELLOW" + local st sc + st=$(get_warp_status) + sc="$RED"; [[ "$st" == "Подключён" ]] && sc="$GREEN"; [[ "$st" == "Отключён" ]] && sc="$YELLOW" + local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" echo -e "${MAGENTA}******************************************************" - echo " anten-ka · WARP Manager v${WARP_VERSION}" + echo " anten-ka · WARP Manager v${WARP_VERSION} (${mode_label})" echo " YouTube: https://www.youtube.com/@antenkaru" echo -e "******************************************************${NC}" - echo -e "${WHITE}IP сервера: ${GREEN}${MY_IP}${NC} ${WHITE}WARP: ${status_color}${status_text}${NC}" - if is_warp_running; then + echo -e "${WHITE}IP сервера: ${GREEN}${MY_IP}${NC} ${WHITE}WARP: ${sc}${st}${NC}" + if [ "$MODE" = "3xui" ] && is_warp_running; then echo -e "${WHITE}SOCKS5: ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}" fi + if [ "$MODE" = "amnezia" ]; then + echo -e "${WHITE}Контейнер: ${CYAN}${CONTAINER:-N/A}${NC}" + fi echo -e "------------------------------------------------------" echo -e " 1) ${GREEN}Установить WARP${NC}" echo -e " 2) ${CYAN}Запустить WARP${NC}" echo -e " 3) ${YELLOW}Остановить WARP${NC}" - echo -e " 4) 📊 ${WHITE}Статус и конфигурация${NC}" - echo -e " 5) 📋 ${CYAN}JSON для 3X-UI${NC}" - echo -e " 6) 🔑 ${YELLOW}Перевыпуск ключа${NC}" - echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}" + echo -e " 4) 📊 ${WHITE}Статус${NC}" + + if [ "$MODE" = "3xui" ]; then + echo -e " 5) 📋 ${CYAN}JSON для 3X-UI${NC}" + echo -e " 6) 🔑 ${YELLOW}Перевыпуск ключа${NC}" + echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}" + else + echo -e " 5) ${GREEN}➕ Добавить клиентов в WARP${NC}" + echo -e " 6) ${YELLOW}➖ Убрать клиентов из WARP${NC}" + echo -e " 7) 👥 ${WHITE}Показать клиентов${NC}" + echo -e "12) 🔑 ${YELLOW}Перевыпуск ключа${NC}" + echo -e "13) 🔄 ${CYAN}Перезапуск контейнера${NC}" + fi + echo -e " 8) 🤖 ${CYAN}Telegram Bot${NC}" echo -e " 9) ${YELLOW}PROMO${NC}" echo -e "10) ${MAGENTA}📚 Инструкция${NC}" @@ -937,28 +1504,30 @@ show_menu() { echo -e "------------------------------------------------------" read -p "Выбор: " ch case $ch in - 1) install_warp ;; - 2) start_warp ;; - 3) stop_warp ;; - 4) show_status ;; - 5) show_xui_json ;; - 6) rekey_warp ;; - 7) change_port ;; + 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" ] && show_xui_json || awg_add_clients_ssh ;; + 6) [ "$MODE" = "3xui" ] && rekey_warp_3xui || awg_remove_clients_ssh ;; + 7) [ "$MODE" = "3xui" ] && change_port_3xui || awg_show_clients_ssh ;; 8) bot_menu ;; 9) show_promo ;; 10) show_info ;; 11) full_uninstall ;; + 12) [ "$MODE" = "amnezia" ] && rekey_warp_awg ;; + 13) [ "$MODE" = "amnezia" ] && awg_restart_container ;; 0) exit 0 ;; esac done } # ═══════════════════════════════════════════════════════════════ -# STARTUP WITH PROGRESS +# STARTUP # ═══════════════════════════════════════════════════════════════ run_startup() { - local total=6 s=0 + local total=7 s=0 clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" @@ -967,44 +1536,57 @@ run_startup() { echo "" ((s++)) - printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка прав root..." "$s" "$total" + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка root..." "$s" "$total" check_root - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Права root подтверждены \n" "$s" "$total" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} root OK \n" "$s" "$total" ((s++)) - printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка лицензии..." "$s" "$total" - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Лицензия активна \n" "$s" "$total" + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Лицензия..." "$s" "$total" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Лицензия активна \n" "$s" "$total" ((s++)) - printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка зависимостей..." "$s" "$total" + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Зависимости..." "$s" "$total" check_deps - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Зависимости на месте \n" "$s" "$total" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Зависимости OK \n" "$s" "$total" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Установка gowarp..." "$s" "$total" if [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gowarp" ]; then cp -f "$0" "/usr/local/bin/gowarp"; chmod +x "/usr/local/bin/gowarp" fi - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Команда gowarp \n" "$s" "$total" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Команда gowarp \n" "$s" "$total" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение IP..." "$s" "$total" get_my_ip - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} IP: %-25s \n" "$s" "$total" "$MY_IP" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} IP: %-25s \n" "$s" "$total" "$MY_IP" ((s++)) - printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка WARP..." "$s" "$total" - local ws - ws=$(get_warp_status_text) - printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} WARP: %-25s \n" "$s" "$total" "$ws" + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение режима..." "$s" "$total" + detect_mode + local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Режим: %-25s \n" "$s" "$total" "$mode_label" + + ((s++)) + if [ "$MODE" = "amnezia" ]; then + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Docker контейнер..." "$s" "$total" + if awg_pick_container 2>/dev/null; then + awg_load_container_data 2>/dev/null + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Контейнер: %-20s \n" "$s" "$total" "$CONTAINER" + else + printf "\r ${CYAN}[%d/%d]${NC} ${YELLOW}⚠${NC} Контейнер не найден \n" "$s" "$total" + fi + else + printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка WARP..." "$s" "$total" + local ws; ws=$(get_warp_status_3xui) + printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} WARP: %-25s \n" "$s" "$total" "$ws" + fi echo "" local w=40 bar="" for ((i=0; i