#!/bin/bash set -o pipefail # ══════════════════════════════════════════════════════════════ # WARP Manager v2.2 — Unified 3X-UI + AmneziaWG # Cloudflare WARP · Telegram Bot · Auto-detect mode # Channel: https://www.youtube.com/@antenkaru # ══════════════════════════════════════════════════════════════ WARP_VERSION="2.2" 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" "WARP-PRO-2026-GAMMA" ) 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'; DIM='\033[2m'; NC='\033[0m' SOCKS_PORT="" MY_IP="" BOT_TOKEN="" BOT_CHAT_ID="" LICENSE_KEY="" MODE="" CONTAINER="" CONTAINERS="" AWG_VPN_CONF="" AWG_VPN_IF="" AWG_VPN_QUICK_CMD="" 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=() declare -a AWG_ALL_CONTAINERS=() # ═══════════════════════════════════════════════════════════════ # CONFIG # ═══════════════════════════════════════════════════════════════ init_config() { mkdir -p "$WARP_DIR" if [ ! -f "$WARP_CONF" ]; then cat > "$WARP_CONF" <<'CONF' SOCKS_PORT="40000" BOT_TOKEN="" BOT_CHAT_ID="" LICENSE_KEY="" MODE="" CONTAINER="" CONTAINERS="" CONF fi source "$WARP_CONF" SOCKS_PORT="${SOCKS_PORT:-$DEFAULT_PORT}" } save_config_val() { local key="$1" value="$2" if grep -q "^${key}=" "$WARP_CONF" 2>/dev/null; then sed -i "s|^${key}=.*|${key}=\"${value}\"|" "$WARP_CONF" else echo "${key}=\"${value}\"" >> "$WARP_CONF" fi source "$WARP_CONF" } # ═══════════════════════════════════════════════════════════════ # MODE DETECTION # ═══════════════════════════════════════════════════════════════ has_3xui_mode() { [[ "$MODE" == "3xui" || "$MODE" == "both" ]]; } has_awg_mode() { [[ "$MODE" == "amnezia" || "$MODE" == "both" ]]; } detect_mode() { source "$WARP_CONF" 2>/dev/null if [ -n "${MODE:-}" ] && [[ "$MODE" == "3xui" || "$MODE" == "amnezia" || "$MODE" == "both" ]]; 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 -E '^amnezia-awg2$|^amnezia-awg$' | head -1) [ -z "$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 MODE="both" 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)" echo -e " ${GREEN}3)${NC} Оба режима" while true; do read -p "Выберите режим (1/2/3): " choice case "$choice" in 1) MODE="3xui"; break ;; 2) MODE="amnezia"; break ;; 3) MODE="both"; 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 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 "$arg_key" ] && is_valid_key "$arg_key"; then save_config_val "LICENSE_KEY" "$arg_key"; LICENSE_KEY="$arg_key"; return 0 fi echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ ⛔ ЛИЦЕНЗИОННЫЙ КЛЮЧ НЕДЕЙСТВИТЕЛЕН ║${NC}" echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${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 / SYSTEM # ═══════════════════════════════════════════════════════════════ log_action() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$WARP_LOG"; } check_root() { [ "$EUID" -ne 0 ] && { echo -e "${RED}[ERROR] Запустите от root!${NC}"; exit 1; } } check_deps() { for cmd in jq curl; do if ! command -v "$cmd" &>/dev/null; then export DEBIAN_FRONTEND=noninteractive apt-get update -y > /dev/null 2>&1 apt-get install -y jq curl > /dev/null 2>&1 break fi done } get_my_ip() { MY_IP=$(curl -s4 --max-time 5 ifconfig.me 2>/dev/null || echo "N/A") } 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 "?") load_avg=$(cat /proc/loadavg 2>/dev/null | awk '{print $1, $2, $3}') mem_info=$(free -m 2>/dev/null | awk '/^Mem:/ {printf "%d/%dMB (%.1f%%)", $3, $2, $3/$2*100}') disk_info=$(df -h / 2>/dev/null | awk 'NR==2 {printf "%s/%s (%s)", $3, $2, $5}') uptime_str=$(uptime -p 2>/dev/null || uptime | sed 's/.*up /up /' | sed 's/,.*load.*//') cpu_usage=$(awk '/^cpu / {u=$2+$4; t=$2+$3+$4+$5+$6+$7+$8; if(t>0) printf "%.1f", u/t*100; else print "0"}' /proc/stat 2>/dev/null) local r="" r+="📊 Системная информация\n\n" r+="Uptime: ${uptime_str}\n" r+="CPU: ${cpu_line} ядер | ${cpu_usage}%\n" r+="Load: ${load_avg}\n" r+="RAM: ${mem_info}\n" r+="Disk /: ${disk_info}\n" echo "$r" } 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}Поддерживаются только 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}[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 "${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 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-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-прокси..." warp-cli --accept-tos mode proxy > /dev/null 2>&1 warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1 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_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} ⚠ Подключение не подтверждено.${NC}" fi echo -e "\n${WHITE}Настройка 3X-UI: п.${YELLOW}6${WHITE} (JSON, инструкция, порт)${NC}" 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 if is_warp_running_3xui; then echo -e "${GREEN}[OK] Подключён.${NC}"; log_action "3XUI START" else echo -e "${RED}Ошибка подключения.${NC}" fi read -p "Enter..." } 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] Отключён.${NC}"; log_action "3XUI STOP" read -p "Enter..." } 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..." } 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 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..." } show_3xui_menu() { while true; do clear echo -e "\n${CYAN}━━━ Настройки SOCKS5 для 3X-UI ━━━${NC}\n" echo -e " ${WHITE}SOCKS5-прокси:${NC} ${GREEN}127.0.0.1:${SOCKS_PORT}${NC}" if is_warp_running_3xui; then echo -e " ${WHITE}WARP IP:${NC} ${GREEN}$(get_warp_ip_3xui)${NC}" fi echo -e "\n 1) 📋 Показать JSON Outbound и Routing" echo -e " 2) 📖 Пошаговая инструкция для 3X-UI" echo -e " 3) 🔧 Изменить порт SOCKS5 (сейчас: ${SOCKS_PORT})" echo -e " 0) ⬅️ Назад" echo "" read -p " Выбор: " ch case $ch in 1) show_3xui_json ;; 2) show_3xui_guide ;; 3) change_port_3xui ;; 0) return ;; esac done } show_3xui_json() { clear; echo -e "\n${CYAN}━━━ JSON для 3X-UI ━━━${NC}\n" echo -e "${WHITE}Добавьте в ${YELLOW}Xray Settings → Outbounds${WHITE} (JSON):${NC}\n" echo -e "${GREEN}── 1. Outbound (добавить в массив outbounds) ──${NC}\n" cat </dev/null | while IFS= read -r l; do echo -e " ${WHITE}$l${NC}"; done echo ""; read -p "Enter..." } 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} Репозиторий удалён" } # ═══════════════════════════════════════════════════════════════ # AMNEZIA BACKEND — WARP via wgcf (WireGuard inside Docker) # ═══════════════════════════════════════════════════════════════ awg_discover_containers() { AWG_ALL_CONTAINERS=() mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^amnezia-awg2$|^amnezia-awg$' || true) if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia" || true) fi if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then echo -e "${RED}Контейнеры amnezia-awg / amnezia-awg2 не найдены.${NC}" return 1 fi CONTAINERS="${AWG_ALL_CONTAINERS[*]}" save_config_val "CONTAINERS" "$CONTAINERS" CONTAINER="${AWG_ALL_CONTAINERS[0]}" save_config_val "CONTAINER" "$CONTAINER" return 0 } awg_pick_container() { if [ -n "${CONTAINER:-}" ]; then docker exec "$CONTAINER" sh -c "true" 2>/dev/null && return 0 CONTAINER="" fi awg_discover_containers || return 1 if [ ${#AWG_ALL_CONTAINERS[@]} -eq 1 ]; then CONTAINER="${AWG_ALL_CONTAINERS[0]}" fi save_config_val "CONTAINER" "$CONTAINER" return 0 } awg_select_container() { awg_discover_containers 2>/dev/null if [ ${#AWG_ALL_CONTAINERS[@]} -le 1 ]; then CONTAINER="${AWG_ALL_CONTAINERS[0]:-}" return 0 fi echo -e "\n${CYAN}Выберите контейнер:${NC}" local i=1 for c in "${AWG_ALL_CONTAINERS[@]}"; do local has_warp="нет" docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null && has_warp="${GREEN}да${NC}" echo -e " ${GREEN}$i)${NC} $c [WARP: $has_warp]" ((i++)) done echo -e " ${DIM}0) Отмена${NC}" while true; do read -p " > " ch [ "$ch" = "0" ] && return 1 if [[ "$ch" =~ ^[0-9]+$ ]] && (( ch >= 1 && ch <= ${#AWG_ALL_CONTAINERS[@]} )); then CONTAINER="${AWG_ALL_CONTAINERS[$((ch-1))]}" save_config_val "CONTAINER" "$CONTAINER" awg_load_container_data return 0 fi done } awg_load_container_data() { if [ "$CONTAINER" = "amnezia-awg2" ]; then AWG_VPN_CONF="/opt/amnezia/awg/awg0.conf" AWG_VPN_IF="awg0" AWG_VPN_QUICK_CMD="awg-quick" else AWG_VPN_CONF="/opt/amnezia/awg/wg0.conf" AWG_VPN_IF="wg0" AWG_VPN_QUICK_CMD="wg-quick" fi AWG_CLIENTS_TABLE="/opt/amnezia/awg/clientsTable" AWG_START_SH="/opt/amnezia/start.sh" docker exec "$CONTAINER" sh -c "[ -f '$AWG_VPN_CONF' ]" 2>/dev/null || { for f in /opt/amnezia/awg/wg0.conf /opt/amnezia/awg/awg0.conf /etc/wireguard/wg0.conf; do if docker exec "$CONTAINER" sh -c "[ -f '$f' ]" 2>/dev/null; then AWG_VPN_CONF="$f" break fi done } docker exec "$CONTAINER" sh -c "[ -f '$AWG_VPN_CONF' ]" 2>/dev/null || { echo -e "${RED}Не найден конфиг VPN в контейнере: $AWG_VPN_CONF${NC}" return 1 } AWG_SUBNET=$(docker exec "$CONTAINER" sh -c "sed -n 's/^Address = \(.*\)$/\1/p' '$AWG_VPN_CONF' | head -n1 | cut -d',' -f1" 2>/dev/null | tr -d '\r') return 0 } 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}" (cd /root && yes | ./wgcf register 2>/dev/null) fi [ -f "$WGCF_ACCOUNT" ] || { echo -e "${RED}Не создан $WGCF_ACCOUNT${NC}"; return 1; } } awg_generate_profile() { (cd /root && yes | ./wgcf generate 2>/dev/null) [ -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 pub addr pk=$(awk -F' = ' '/^PrivateKey = /{print $2}' "$WGCF_PROFILE") pub=$(awk -F' = ' '/^PublicKey = /{print $2}' "$WGCF_PROFILE") addr=$(awk -F' = ' '/^Address = /{print $2}' "$WGCF_PROFILE" | cut -d',' -f1) docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR'" docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null docker exec "$CONTAINER" sh -c "cat > '$AWG_WARP_CONF' <<'WARPEOF' [Interface] PrivateKey = ${pk} Address = ${addr} MTU = 1280 Table = off [Peer] PublicKey = ${pub} AllowedIPs = 0.0.0.0/0 Endpoint = ${endpoint_ip}:2408 PersistentKeepalive = 25 WARPEOF 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 в контейнере..." 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 установлен! Управление клиентами — п.6.${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}" if awg_warp_up; then echo -e "${GREEN}[OK] WARP подключён.${NC}"; log_action "AWG START" else echo -e "${RED}Ошибка.${NC}" fi 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; } 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} ✓ Готово!${NC}" [ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}Новый WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}" log_action "AWG REKEY: warp_ip=${AWG_WARP_EXIT_IP}" read -p "Enter..." } 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"; } echo -e " ${GREEN}✓${NC} Бот остановлен" 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 удалён из контейнера" 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 rm -f "$WGCF_BIN" "$WGCF_ACCOUNT" "$WGCF_PROFILE" echo -e " ${GREEN}✓${NC} wgcf и профили удалены" } awg_check_containers() { clear; echo -e "\n${CYAN}━━━ Контейнеры AmneziaWG ━━━${NC}\n" local saved_container="${CONTAINER:-}" awg_discover_containers 2>/dev/null # discover persists CONTAINER=first match; restore user's choice (or "") on disk CONTAINER="$saved_container" save_config_val "CONTAINER" "$saved_container" if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then echo -e " ${RED}Контейнеры не найдены.${NC}" read -p " Enter..."; return fi printf " ${WHITE}%-20s %-10s %-10s %-12s${NC}\n" "Контейнер" "Статус" "WARP" "Клиентов" echo -e " ${CYAN}────────────────────────────────────────────────────${NC}" for c in "${AWG_ALL_CONTAINERS[@]}"; do local status="Running" docker exec "$c" sh -c "true" 2>/dev/null || status="${RED}Down${NC}" [ "$status" = "Running" ] && status="${GREEN}Running${NC}" local has_warp="${DIM}Нет${NC}" local client_info="—" if docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null; then local warp_up="${YELLOW}Выкл${NC}" docker exec "$c" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null && warp_up="${GREEN}Вкл${NC}" has_warp="$warp_up" local cnt; cnt=$(docker exec "$c" sh -c "cat /opt/warp/clients.list 2>/dev/null | grep -c '.' || echo 0" | tr -d '\r') client_info="${cnt}" fi local marker="" [ "$c" = "${CONTAINER:-}" ] && marker=" ${CYAN}*${NC}" printf " %-20s %-22b %-22b %-12s%b\n" "$c" "$status" "$has_warp" "$client_info" "$marker" done echo -e "\n ${DIM}* = текущий контейнер${NC}" echo ""; read -p " Enter..." } 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..." } # ═══════════════════════════════════════════════════════════════ # AMNEZIA CLIENT MANAGEMENT # ═══════════════════════════════════════════════════════════════ 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="${content}${ip}"$'\n' done docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR' && cat > '$AWG_WARP_CLIENTS' <<'CLEOF' ${content}CLEOF " } awg_parse_clients_table() { AWG_CLIENT_NAMES=() 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 id_name_pairs id_name_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 "$id_name_pairs" ]; then while IFS='|' read -r cid name; do [ -n "$cid" ] && [ -n "$name" ] && key_to_name["$cid"]="$name" done <<< "$id_name_pairs" fi local conf_peers conf_peers=$(docker exec "$CONTAINER" sh -c "cat '$AWG_VPN_CONF' 2>/dev/null || true" | tr -d '\r' | awk ' /^\[Peer\]/ { pubkey=""; ip="" } /^PublicKey/ { s = $0 sub(/^[^=]*= */, "", s) pubkey = s } /^AllowedIPs/ { s = $0 sub(/^[^=]*= */, "", s) ip = s if (pubkey != "" && ip != "") { print pubkey "|" ip } }') if [ -n "$conf_peers" ]; then while IFS='|' read -r pubkey ip; do if [ -n "$pubkey" ] && [ -n "$ip" ] && [ -n "${key_to_name[$pubkey]+_}" ]; then local name="${key_to_name[$pubkey]}" AWG_CLIENT_NAMES["$ip"]="$name" local bare="${ip%/32}" AWG_CLIENT_NAMES["$bare"]="$name" fi done <<< "$conf_peers" fi return 0 } 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[[:space:]]*=[[:space:]]*\(.*\/32\)[[:space:]]*$/\1/p' '$AWG_VPN_CONF'" 2>/dev/null | tr -d '\r') if [ "${#AWG_CLIENT_IPS[@]}" -eq 0 ]; then mapfile -t AWG_CLIENT_IPS < <(docker exec "$CONTAINER" sh -c "awk '/^\[Peer\]/,/^$/' '$AWG_VPN_CONF' | sed -n 's/^AllowedIPs[[:space:]]*=[[:space:]]*//p'" 2>/dev/null | tr -d '\r' | grep '/32') fi } awg_toggle_clients_ssh() { if [ ${#AWG_ALL_CONTAINERS[@]} -gt 1 ]; then awg_select_container || return fi awg_get_client_ips; awg_parse_clients_table; awg_load_clients if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then echo -e "\n ${RED}Нет клиентов в конфиге VPN.${NC}" read -p "Enter..."; return fi local -a pending_ips=() for ip in "${AWG_SELECTED_IPS[@]}"; do pending_ips+=("$ip"); done while true; do local pending_set=" ${pending_ips[*]+"${pending_ips[*]}"} " clear; echo -e "\n${CYAN}━━━ Управление клиентами WARP ━━━${NC}" [ ${#AWG_ALL_CONTAINERS[@]} -gt 1 ] && echo -e " ${DIM}Контейнер: ${CONTAINER}${NC}" echo -e " ${DIM}Нажмите номер чтобы вкл/выкл WARP для клиента${NC}\n" local i=1 warp_count=0 for ip in "${AWG_CLIENT_IPS[@]}"; do local label; label=$(awg_format_label "$ip") if [[ "$pending_set" == *" $ip "* ]]; then echo -e " ${GREEN} $i) ✅ $label${NC}" ((warp_count++)) else echo -e " ${WHITE} $i)${NC} ☐ $label" fi ((i++)) done echo "" echo -e " ${WHITE}Через WARP: ${CYAN}${warp_count}${NC} из ${#AWG_CLIENT_IPS[@]}" echo -e "${CYAN}──────────────────────────────────────────────────────${NC}" echo -e " ${GREEN}all${NC}) Включить всех ${YELLOW}none${NC}) Выключить всех" echo -e " ${GREEN}ok${NC}) Применить ${DIM}0${NC}) Отмена (без изменений)" echo "" read -p " > " answer case "$answer" in 0|"") return ;; all) pending_ips=("${AWG_CLIENT_IPS[@]}") ;; none) pending_ips=() ;; ok) AWG_SELECTED_IPS=("${pending_ips[@]+"${pending_ips[@]}"}") echo -e "\n${YELLOW} Применяю правила...${NC}" awg_save_clients; awg_apply_rules; awg_patch_start_sh echo -e "${GREEN} ✓ Правила сохранены${NC}" echo -e "\n${YELLOW} Перезапуск контейнера ${CONTAINER}...${NC}" docker restart "$CONTAINER" >/dev/null 2>&1 local a=0 while [ "$a" -lt 15 ]; do docker exec "$CONTAINER" sh -c "true" 2>/dev/null && break sleep 1; ((a++)) done if docker exec "$CONTAINER" sh -c "true" 2>/dev/null; then echo -e "${GREEN} ✓ Контейнер перезапущен${NC}" else echo -e "${RED} ⚠ Контейнер не отвечает${NC}" fi log_action "AWG CLIENTS APPLIED: ${#AWG_SELECTED_IPS[@]} in WARP, container restarted" read -p " Enter..."; return ;; *) 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 [[ "$pending_set" == *" $ip "* ]]; then local -a tmp=() for eip in "${pending_ips[@]}"; do [ "$eip" != "$ip" ] && tmp+=("$eip") done pending_ips=("${tmp[@]+"${tmp[@]}"}") else pending_ips+=("$ip") fi fi done ;; esac done } # ═══════════════════════════════════════════════════════════════ # 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 iptables -t mangle -S FORWARD 2>/dev/null | grep "\-o warp.*TCPMSS" | while read -r line; do rule=$(echo "$line" | sed "s/^-A /-D /") iptables -t mangle $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 2>/dev/null || true" docker exec "$CONTAINER" sh -c " iptables -t mangle -C FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || \ iptables -t mangle -A FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || true " 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 warp_block="" warp_block+="${AWG_MARKER_BEGIN}"$'\n' warp_block+=""$'\n' warp_block+="if [ -f '${AWG_WARP_CONF}' ]; then"$'\n' warp_block+=" wg-quick up '${AWG_WARP_CONF}' || true"$'\n' warp_block+=" sleep 3"$'\n' warp_block+="fi"$'\n' warp_block+=""$'\n' if [ ${#AWG_SELECTED_IPS[@]} -gt 0 ]; then warp_block+="ip route add default dev warp table 100 2>/dev/null || ip route replace default dev warp table 100 2>/dev/null || true"$'\n' warp_block+="iptables -t mangle -C FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || iptables -t mangle -A FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || true"$'\n' warp_block+=""$'\n' local prio=100 for ip in "${AWG_SELECTED_IPS[@]}"; do warp_block+="ip rule add from ${ip} table 100 priority ${prio} 2>/dev/null || true"$'\n' warp_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"$'\n' ((prio++)) done fi warp_block+=""$'\n' warp_block+="${AWG_MARKER_END}" docker exec "$CONTAINER" sh -c " if grep -qF '${AWG_MARKER_BEGIN}' '$AWG_START_SH'; then sed -i '/# --- WARP-MANAGER BEGIN ---/,/# --- WARP-MANAGER 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 tmpfile=\$(mktemp) while IFS= read -r line; do if echo \"\$line\" | grep -qF 'tail -f /dev/null'; then cat <<'WARPBLOCK' ${warp_block} WARPBLOCK fi echo \"\$line\" done < '$AWG_START_SH' > \"\$tmpfile\" mv \"\$tmpfile\" '$AWG_START_SH' chmod +x '$AWG_START_SH' else cat >> '$AWG_START_SH' <<'WARPBLOCK' ${warp_block} WARPBLOCK 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 '/# --- WARP-MANAGER BEGIN ---/,/# --- WARP-MANAGER END ---/d' '$AWG_START_SH' fi " 2>/dev/null } # ═══════════════════════════════════════════════════════════════ # UNIFIED WRAPPERS # ═══════════════════════════════════════════════════════════════ get_warp_status() { if [ "$MODE" = "both" ]; then local s3 sa s3=$(get_warp_status_3xui); sa=$(get_warp_status_awg) echo "3X-UI: ${s3} | AWG: ${sa}" elif [ "$MODE" = "3xui" ]; then get_warp_status_3xui else get_warp_status_awg fi } get_warp_ip() { if [ "$MODE" = "both" ]; then local ip3 ipa ip3=$(get_warp_ip_3xui) awg_detect_warp_exit_ip; ipa="${AWG_WARP_EXIT_IP:-N/A}" echo "3X-UI: ${ip3} | AWG: ${ipa}" elif [ "$MODE" = "3xui" ]; then get_warp_ip_3xui else awg_detect_warp_exit_ip; echo "${AWG_WARP_EXIT_IP:-N/A}" fi } is_warp_running() { if [ "$MODE" = "both" ]; then is_warp_running_3xui 2>/dev/null || is_warp_running_awg 2>/dev/null elif [ "$MODE" = "3xui" ]; then is_warp_running_3xui else is_warp_running_awg fi } # ═══════════════════════════════════════════════════════════════ # TELEGRAM BOT # ═══════════════════════════════════════════════════════════════ tg_api() { curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/$1" \ -H "Content-Type: application/json" -d "$2" 2>/dev/null } tg_send() { local chat_id="$1" text keyboard="${3:-}" text=$(printf '%b' "$2") local payload if [ -n "$keyboard" ]; then payload=$(jq -n --arg c "$chat_id" --arg t "$text" --argjson k "$keyboard" \ '{chat_id:$c, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}') else payload=$(jq -n --arg c "$chat_id" --arg t "$text" \ '{chat_id:$c, text:$t, parse_mode:"HTML"}') fi tg_api "sendMessage" "$payload" } tg_edit() { local chat_id="$1" msg_id="$2" text keyboard="${4:-}" text=$(printf '%b' "$3") local payload if [ -n "$keyboard" ]; then payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" --argjson k "$keyboard" \ '{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}') else payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" \ '{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML"}') fi tg_api "editMessageText" "$payload" } tg_answer_cb() { tg_api "answerCallbackQuery" "{\"callback_query_id\":\"$1\",\"text\":\"${2:-}\"}" } # ─── Bot keyboards ─────────────────────────────────────────── kbd_main_3xui() { cat <<'JSON' [ [{"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":"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":"rc"}], [{"text":"💻 Система","callback_data":"sys"}], [{"text":"🏢 Хостинг","callback_data":"promo"}] ] JSON } kbd_main_both() { cat <<'JSON' [ [{"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":"👥 Клиенты AWG","callback_data":"cl"}], [{"text":"🔄 Контейнер","callback_data":"rc"}], [{"text":"💻 Система","callback_data":"sys"}], [{"text":"🏢 Хостинг","callback_data":"promo"}] ] JSON } kbd_main() { if [ "$MODE" = "both" ]; then kbd_main_both elif [ "$MODE" = "3xui" ]; then kbd_main_3xui else kbd_main_awg; fi } kbd_back() { echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]'; } kbd_rekey_confirm() { echo '[[{"text":"✅ Да","callback_data":"rk_y"}],[{"text":"⬅️ Отмена","callback_data":"m"}]]'; } # ─── Bot main menu ─────────────────────────────────────────── bot_main_menu() { local chat_id="$1" msg_id="${2:-}" local ws wip="" extra="" ws=$(get_warp_status) is_warp_running && wip=" | WARP IP: $(get_warp_ip)" if has_3xui_mode; then extra+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi if has_awg_mode; then extra+="\nКонтейнер: ${CONTAINER:-N/A}"; fi local mode_label="3X-UI" [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" [ "$MODE" = "both" ] && mode_label="3X-UI + AWG" 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) if [ -n "$msg_id" ]; then tg_edit "$chat_id" "$msg_id" "$text" "$kbd" else tg_send "$chat_id" "$text" "$kbd" fi } # ─── 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 case "$data" in m) bot_main_menu "$chat_id" "$msg_id" ;; st) local ws wip="" extra="" ws=$(get_warp_status) is_warp_running && wip="\nWARP IP: $(get_warp_ip)" if has_3xui_mode; then extra+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}" local raw; raw=$(warp-cli --accept-tos status 2>/dev/null | head -3) [ -n "$raw" ] && extra+="\n\n
${raw}
" fi if has_awg_mode; then extra+="\nКонтейнер: ${CONTAINER:-N/A}\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 t="🌐 IP адреса\n\nРеальный: ${MY_IP:-N/A}\nWARP: ${wip}" if has_3xui_mode; then t+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;; on) tg_edit "$chat_id" "$msg_id" "⏳ Запуск..." "" local result="" if has_3xui_mode && is_warp_installed_3xui; then warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3 if is_warp_running_3xui; then result+="✅ 3X-UI: $(get_warp_ip_3xui)\n"; log_action "BOT 3XUI ON" else result+="❌ 3X-UI: ошибка\n"; fi fi if has_awg_mode && is_warp_installed_awg; then awg_warp_up 2>/dev/null if is_warp_running_awg; then awg_detect_warp_exit_ip result+="✅ AWG: ${AWG_WARP_EXIT_IP:-?}\n"; log_action "BOT AWG ON" else result+="❌ AWG: ошибка\n"; fi fi [ -z "$result" ] && result="❌ WARP не установлен." tg_edit "$chat_id" "$msg_id" "Запуск WARP\n\n${result}" "$(kbd_back)" ;; off) local result="" if has_3xui_mode && is_warp_running_3xui 2>/dev/null; then warp-cli --accept-tos disconnect > /dev/null 2>&1 result+="⏹ 3X-UI отключён\n"; log_action "BOT 3XUI OFF" fi if has_awg_mode && is_warp_running_awg 2>/dev/null; then docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true" result+="⏹ AWG остановлен\n"; log_action "BOT AWG OFF" fi [ -z "$result" ] && result="ℹ️ Уже отключён." tg_edit "$chat_id" "$msg_id" "Остановка WARP\n\n${result}" "$(kbd_back)" ;; rk) tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\nПродолжить?" "$(kbd_rekey_confirm)" ;; rk_y) tg_edit "$chat_id" "$msg_id" "⏳ Перевыпуск..." "" local result="" if has_3xui_mode; 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) result+="✅ 3X-UI: ${w}\n"; log_action "BOT 3XUI REKEY: ${w}" else result+="⚠️ 3X-UI: не подтверждено\n"; fi fi if has_awg_mode; then docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true" rm -f "$WGCF_ACCOUNT" (cd /root && yes | ./wgcf register 2>/dev/null && yes | ./wgcf generate 2>/dev/null) if [ -f "$WGCF_PROFILE" ]; then local ep; ep=$(awg_resolve_endpoint 2>/dev/null) if [ -n "$ep" ]; then 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 result+="✅ AWG: ${AWG_WARP_EXIT_IP:-?}\n"; log_action "BOT AWG REKEY" else result+="❌ AWG: ошибка endpoint\n"; fi else result+="❌ AWG: ошибка профиля\n"; fi fi tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\n${result}" "$(kbd_back)" ;; js) if has_3xui_mode; 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_get_client_ips; awg_parse_clients_table; awg_load_clients local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " local t="👥 Клиенты WARP (${#AWG_SELECTED_IPS[@]} из ${#AWG_CLIENT_IPS[@]})\n\n" local kbd="" if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then t+="Нет клиентов в конфиге VPN." kbd='[[{"text":"⬅️ Меню","callback_data":"m"}]]' else kbd="[" local first=1 for i in "${!AWG_CLIENT_IPS[@]}"; do local ip="${AWG_CLIENT_IPS[$i]}" local name; name=$(awg_get_name "$ip") local label="${ip}" [ -n "$name" ] && label="$name" if [[ "$warp_set" == *" $ip "* ]]; then t+="✅ ${ip}" [ -n "$name" ] && t+=" ($name)" label="✅ ${label}" else t+="☐ ${ip}" [ -n "$name" ] && t+=" ($name)" label="☐ ${label}" fi t+="\n" local safe_label; safe_label=$(echo "$label" | sed 's/["\\]/\\&/g; s/\n//g') [ "$first" -eq 0 ] && kbd+="," kbd+="[{\"text\":\"${safe_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" ;; ct:*) local idx="${data#ct:}" awg_get_client_ips; awg_load_clients if [ "$idx" = "all" ]; then AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}") elif [ "$idx" = "none" ]; then AWG_SELECTED_IPS=() else [[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; } local ip="${AWG_CLIENT_IPS[$idx]}" local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} " if [[ "$warp_set" == *" $ip "* ]]; then local -a tmp=() for eip in "${AWG_SELECTED_IPS[@]}"; do [ "$eip" != "$ip" ] && tmp+=("$eip") done AWG_SELECTED_IPS=("${tmp[@]+"${tmp[@]}"}") else AWG_SELECTED_IPS+=("$ip") fi fi awg_save_clients; awg_apply_rules; awg_patch_start_sh tg_edit "$chat_id" "$msg_id" "⏳ Применяю и перезапускаю контейнер..." "" docker restart "$CONTAINER" >/dev/null 2>&1 local _a=0; while [ "$_a" -lt 15 ]; do docker exec "$CONTAINER" sh -c "true" 2>/dev/null && break; sleep 1; ((_a++)); done log_action "BOT AWG TOGGLE: ${#AWG_SELECTED_IPS[@]}, container restarted" bot_handle_callback "$chat_id" "$msg_id" "" "cl" ;; rc) if ! has_awg_mode; 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) s+="\nWARP: ${ws}" s+="\nРежим: ${MODE}" if has_3xui_mode; then s+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi if has_awg_mode; then s+="\nКонтейнер: ${CONTAINER:-N/A}"; fi tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;; promo) local pt="🏢 Хостинг со скидкой до -60%\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% скидка" tg_edit "$chat_id" "$msg_id" "$pt" "$(kbd_back)" ;; esac } # ─── Bot daemon ─────────────────────────────────────────────── 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; } get_my_ip if has_awg_mode && [ -n "$CONTAINER" ]; then awg_load_container_data 2>/dev/null; fi local offset=0 while true; do bot_poll_cycle done } bot_poll_cycle() { 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 && return local header header=$(jq -r '[.ok // "false", (.result | length)] | @tsv' <<< "$response" 2>/dev/null) local ok cnt IFS=$'\t' read -r ok cnt <<< "$header" [ "$ok" != "true" ] && sleep 5 && return [ "$cnt" -eq 0 ] 2>/dev/null && return local updates_tsv updates_tsv=$(jq -r ' .result[] | [ .update_id, (.callback_query.data // ""), (.callback_query.id // ""), (.callback_query.message.chat.id // ""), (.callback_query.message.message_id // ""), (.message.chat.id // ""), (.message.text // "") ] | @tsv ' <<< "$response" 2>/dev/null) while IFS=$'\t' read -r uid cbd cbi cci cmi mci mtx; do [ -z "$uid" ] && continue offset=$((uid + 1)) if [ -n "$cbd" ]; then if ! is_bot_admin "$cci"; then tg_answer_cb "$cbi" "Нет доступа" > /dev/null; continue; fi bot_handle_callback "$cci" "$cmi" "$cbi" "$cbd" elif [ -n "$mci" ] && [ -n "$mtx" ]; then if ! is_bot_admin "$mci"; then tg_send "$mci" "⛔ Нет доступа.\nChat ID: $mci" "" > /dev/null; continue; fi if [[ "$mtx" == "/start" || "$mtx" == "/menu" ]]; then bot_main_menu "$mci" else tg_send "$mci" "Используйте /start или /menu" "" > /dev/null fi fi done <<< "$updates_tsv" } # ─── 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; } cat > /etc/systemd/system/warp-bot.service < /dev/null 2>&1; systemctl start warp-bot; sleep 1 if systemctl is-active warp-bot &>/dev/null; then echo -e "${GREEN}[OK] Бот запущен.${NC}"; log_action "Bot started" else echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}" fi } stop_bot() { 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" } is_bot_admin() { local id="$1" [ -z "${BOT_CHAT_ID:-}" ] && return 0 local admin for admin in $BOT_CHAT_ID; do [ "$admin" = "$id" ] && return 0 done return 1 } bot_add_admin() { local new_id="$1" [ -z "$new_id" ] && return 1 if ! is_bot_admin "$new_id"; then if [ -n "${BOT_CHAT_ID:-}" ]; then BOT_CHAT_ID="${BOT_CHAT_ID} ${new_id}" else BOT_CHAT_ID="$new_id" fi save_config_val "BOT_CHAT_ID" "$BOT_CHAT_ID" fi return 0 } bot_remove_admin() { local rm_id="$1" local -a new_list=() for admin in $BOT_CHAT_ID; do [ "$admin" != "$rm_id" ] && new_list+=("$admin") done BOT_CHAT_ID="${new_list[*]+"${new_list[*]}"}" save_config_val "BOT_CHAT_ID" "$BOT_CHAT_ID" } bot_auto_chatid() { systemctl stop warp-bot 2>/dev/null sleep 1 local flush_resp last_uid flush_resp=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=-1&limit=1") last_uid=$(echo "$flush_resp" | jq -r '.result[-1].update_id // empty' 2>/dev/null) if [ -n "$last_uid" ]; then curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=$((last_uid + 1))&limit=1" >/dev/null 2>&1 fi echo -e "${YELLOW} Отправьте боту 3 любых сообщения в Telegram.${NC}" echo -e "${YELLOW} Затем нажмите Enter.${NC}" read -p " > " _ local resp; resp=$(curl -s --max-time 10 "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=10") local c; c=$(echo "$resp" | jq -r '[.result[].message.chat.id // empty] | map(select(. != "")) | unique | first // empty' 2>/dev/null) if [ -n "$c" ] && [[ "$c" =~ ^[0-9-]+$ ]]; then bot_add_admin "$c" echo -e " ${GREEN}✓ Chat ID: $c (добавлен как админ)${NC}" return 0 else echo -e " ${RED}✗ Не удалось определить Chat ID.${NC}" echo -e "" echo -e " ${WHITE}Получите Chat ID вручную:${NC}" echo -e " ${CYAN}1.${NC} Откройте ${GREEN}@userinfobot${NC} в Telegram" echo -e " ${CYAN}2.${NC} Нажмите /start" echo -e " ${CYAN}3.${NC} Скопируйте число ${WHITE}Id: XXXXXXXX${NC}" echo -e " ${CYAN}4.${NC} Впишите через меню бота (п. Добавить админа)" return 1 fi } bot_wizard() { clear echo -e "\n${CYAN}━━━ Настройка Telegram-бота (быстрый старт) ━━━${NC}\n" echo -e "${YELLOW}Шаг 1/3.${NC} Введите токен бота" echo -e "${DIM} Получить: @BotFather → /newbot → скопировать токен${NC}\n" read -p " Токен: " t if [ -z "$t" ]; then echo -e " ${RED}Отменено.${NC}"; read -p "Enter..."; return; fi save_config_val "BOT_TOKEN" "$t"; BOT_TOKEN="$t" echo -e " ${GREEN}✓ Токен сохранён${NC}\n" echo -e "${YELLOW}Шаг 2/3.${NC} Определение Chat ID (авто)" if ! bot_auto_chatid; then echo -e "\n ${YELLOW}Chat ID не определён — бот будет доступен всем.${NC}" echo -e " ${WHITE}Можно задать вручную позже через меню бота (п.3).${NC}" fi echo "" echo -e "${YELLOW}Шаг 3/3.${NC} Запуск бота" start_bot echo "" if systemctl is-active warp-bot &>/dev/null; then echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN} ✅ Бот настроен и запущен!${NC}" echo -e "${GREEN} Откройте бота в Telegram и нажмите /start${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" else echo -e "${YELLOW} ⚠ Бот может не запуститься. Проверьте:${NC}" echo -e " ${WHITE}journalctl -u warp-bot --no-pager -n 10${NC}" fi read -p " Enter..." } bot_show_admins() { echo -e "\n ${CYAN}Администраторы бота:${NC}" if [ -z "${BOT_CHAT_ID:-}" ]; then echo -e " ${YELLOW}Нет (бот доступен всем)${NC}" else local i=1 for admin in $BOT_CHAT_ID; do echo -e " ${GREEN}$i)${NC} $admin" ((i++)) done fi echo "" } bot_menu() { source "$WARP_CONF" 2>/dev/null if [ -z "${BOT_TOKEN:-}" ]; then bot_wizard return fi while true; do clear; source "$WARP_CONF" 2>/dev/null local bs="${RED}Выкл${NC}" systemctl is-active warp-bot &>/dev/null && bs="${GREEN}Вкл${NC}" local td="***${BOT_TOKEN: -6}" local admin_count=0 [ -n "${BOT_CHAT_ID:-}" ] && admin_count=$(echo "$BOT_CHAT_ID" | wc -w) echo -e "\n${CYAN}━━━ Telegram Bot ━━━${NC}\n" echo -e " Статус: $bs" echo -e " Токен: ${YELLOW}$td${NC}" echo -e " Админов: ${YELLOW}${admin_count}${NC}\n" echo -e " ${CYAN}── Управление ──${NC}" echo -e " 1) ${GREEN}Запустить${NC}" echo -e " 2) ${RED}Остановить${NC}" echo -e " 3) Изменить токен" echo -e "\n ${CYAN}── Администраторы ──${NC}" echo -e " 4) Показать админов" echo -e " 5) Добавить админа (авто — по сообщению)" echo -e " 6) Добавить админа (вручную — Chat ID)" echo -e " 7) Убрать админа" echo -e "\n 0) Назад" echo "" read -p " Выбор: " ch case $ch in 1) start_bot; read -p " Enter..." ;; 2) stop_bot; read -p " Enter..." ;; 3) echo " Токен:"; read -p " > " t [ -n "$t" ] && save_config_val "BOT_TOKEN" "$t" && BOT_TOKEN="$t" && echo -e " ${GREEN}OK${NC}" read -p " Enter..." ;; 4) bot_show_admins; read -p " Enter..." ;; 5) bot_auto_chatid; read -p " Enter..." ;; 6) echo -e "\n ${WHITE}Как узнать Chat ID: откройте ${GREEN}@userinfobot${WHITE} → /start → скопируйте Id${NC}" echo " Chat ID:"; read -p " > " c if [ -n "$c" ] && [[ "$c" =~ ^[0-9-]+$ ]]; then bot_add_admin "$c" echo -e " ${GREEN}✓ Админ $c добавлен${NC}" else echo -e " ${RED}Некорректный ID${NC}" fi read -p " Enter..." ;; 7) if [ -z "${BOT_CHAT_ID:-}" ]; then echo -e "\n ${YELLOW}Нет админов.${NC}"; read -p " Enter..."; continue fi bot_show_admins echo " Номер для удаления (0 — отмена):" read -p " > " num if [ "$num" != "0" ] && [[ "$num" =~ ^[0-9]+$ ]]; then local -a admins=($BOT_CHAT_ID) if (( num >= 1 && num <= ${#admins[@]} )); then local rm_id="${admins[$((num-1))]}" bot_remove_admin "$rm_id" echo -e " ${GREEN}✓ Админ $rm_id удалён${NC}" fi fi read -p " Enter..." ;; 0) return ;; esac done } # ═══════════════════════════════════════════════════════════════ # PROMO / INFO # ═══════════════════════════════════════════════════════════════ show_promo() { clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ║${NC}" echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" echo -e "\n${CYAN}🌍 РФ И ЕВРОПА${NC}\n${WHITE} >>> https://vk.cc/ct29NQ${NC}" 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-код...${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..." } show_info() { clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${MAGENTA}║ 📚 WARP Manager v${WARP_VERSION} ║${NC}" echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" if has_3xui_mode; 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" echo "" fi if has_awg_mode; then 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 if has_3xui_mode; then uninstall_3xui; fi if has_awg_mode; then uninstall_awg; fi 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}" has_3xui_mode && echo -e "${WHITE} Уберите outbound \"warp\" из 3X-UI!${NC}" echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}" log_action "UNINSTALL: full removal ($MODE)" read -p "Enter..." exit 0 } # ═══════════════════════════════════════════════════════════════ # MAIN MENU # ═══════════════════════════════════════════════════════════════ show_menu() { while true; do clear local st sc st=$(get_warp_status) sc="$RED"; [[ "$st" == *"Подключён"* ]] && sc="$GREEN"; [[ "$st" == *"Отключён"* && "$st" != *"Подключён"* ]] && sc="$YELLOW" local mode_label="3X-UI" [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" [ "$MODE" = "both" ] && mode_label="3X-UI + AmneziaWG" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗" echo -e "║ anten-ka · WARP Manager v${WARP_VERSION} ║" echo -e "║ YouTube: https://www.youtube.com/@antenkaru ║" echo -e "╚══════════════════════════════════════════════════════╝${NC}" echo -e " ${WHITE}IP сервера:${NC} ${GREEN}${MY_IP}${NC} ${WHITE}Режим:${NC} ${CYAN}${mode_label}${NC}" echo -e " ${WHITE}WARP:${NC} ${sc}${st}${NC}" if has_3xui_mode && is_warp_running_3xui 2>/dev/null; then echo -e " ${WHITE}SOCKS5:${NC} ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}" fi if has_awg_mode && [ -n "${CONTAINER:-}" ]; then echo -e " ${WHITE}Контейнер:${NC} ${CYAN}${CONTAINER}${NC}" fi echo -e "\n${CYAN}── WARP-ключ ──────────────────────────────────────────${NC}" echo -e " 1) ${GREEN}Установить WARP${NC}" echo -e " 2) ${CYAN}Запустить WARP${NC}" echo -e " 3) ${YELLOW}Остановить WARP${NC}" echo -e " 4) 📊 Статус" echo -e " 5) 🔑 ${YELLOW}Перевыпуск ключа${NC}" if has_3xui_mode; then echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}" echo -e " 6) 📋 ${CYAN}Настройки SOCKS5 / JSON / Инструкция${NC}" fi if has_awg_mode; then echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}" echo -e " 7) 👥 ${GREEN}Управление клиентами WARP${NC}" fi echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}" echo -e " 8) 🤖 ${CYAN}Настройка и управление ботом${NC}" echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}" echo -e " 9) ${YELLOW}PROMO${NC}" echo -e " 10) ${MAGENTA}📚 Инструкция${NC}" if has_awg_mode; then echo -e " 11) 🔍 ${WHITE}Проверить контейнеры${NC}" fi echo -e " 12) ${RED}⚠ Полное удаление${NC}" echo -e " 0) Выход" echo -e "${CYAN}──────────────────────────────────────────────────────${NC}" read -p " Выбор: " ch case $ch in 1) if has_3xui_mode; then install_warp_3xui; fi if has_awg_mode; then install_warp_awg; fi ;; 2) if has_3xui_mode; then start_warp_3xui; fi if has_awg_mode; then start_warp_awg; fi ;; 3) if has_3xui_mode; then stop_warp_3xui; fi if has_awg_mode; then stop_warp_awg; fi ;; 4) if has_3xui_mode; then show_status_3xui; fi if has_awg_mode; then show_status_awg; fi ;; 5) if has_3xui_mode; then rekey_warp_3xui; fi if has_awg_mode; then rekey_warp_awg; fi ;; 6) has_3xui_mode && show_3xui_menu ;; 7) has_awg_mode && awg_toggle_clients_ssh ;; 8) bot_menu ;; 9) show_promo ;; 10) show_info ;; 11) has_awg_mode && awg_check_containers ;; 12) full_uninstall ;; 0) exit 0 ;; esac done } # ═══════════════════════════════════════════════════════════════ # STARTUP # ═══════════════════════════════════════════════════════════════ run_startup() { local total=6 s=0 clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${MAGENTA}║ WARP Manager v${WARP_VERSION} — Загрузка ║${NC}" echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка root..." "$s" "$total" check_root 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" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Зависимости..." "$s" "$total" check_deps printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Зависимости OK \n" "$s" "$total" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Установка gowarp..." "$s" "$total" local upgrade_msg="установлена" if [ -f "/usr/local/bin/gowarp" ] && [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gowarp" ]; then upgrade_msg="обновлена (v${WARP_VERSION})" fi 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 %s \n" "$s" "$total" "$upgrade_msg" ((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" ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение режима..." "$s" "$total" detect_mode local mode_label="3X-UI" [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG" [ "$MODE" = "both" ] && mode_label="3X-UI + AmneziaWG" has_awg_mode && ((total++)) has_3xui_mode && ((total++)) printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Режим: %-25s \n" "$s" "$total" "$mode_label" if has_awg_mode; then ((s++)) 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 fi if has_3xui_mode; then ((s++)) printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка WARP (3X-UI)..." "$s" "$total" local ws; ws=$(get_warp_status_3xui) printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} 3X-UI WARP: %-20s \n" "$s" "$total" "$ws" fi echo "" local w=40 bar="" for ((i=0; i