mirror of
https://github.com/anten-ka/go_warp_pro.git
synced 2026-05-19 20:36:01 +00:00
1613 lines
79 KiB
Bash
1613 lines
79 KiB
Bash
#!/bin/bash
|
||
set -o pipefail
|
||
|
||
# ══════════════════════════════════════════════════════════════
|
||
# WARP Manager v2.0 — Unified 3X-UI + AmneziaWG
|
||
# Cloudflare WARP · Telegram Bot · Auto-detect mode
|
||
# Channel: https://www.youtube.com/@antenkaru
|
||
# ══════════════════════════════════════════════════════════════
|
||
|
||
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"
|
||
"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=""
|
||
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"
|
||
if [ ! -f "$WARP_CONF" ]; then
|
||
cat > "$WARP_CONF" <<'CONF'
|
||
SOCKS_PORT="40000"
|
||
BOT_TOKEN=""
|
||
BOT_CHAT_ID=""
|
||
LICENSE_KEY=""
|
||
MODE=""
|
||
CONTAINER=""
|
||
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
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
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
|
||
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+="<b>📊 Системная информация</b>\n\n"
|
||
r+="<b>Uptime:</b> ${uptime_str}\n"
|
||
r+="<b>CPU:</b> ${cpu_line} ядер | ${cpu_usage}%\n"
|
||
r+="<b>Load:</b> ${load_avg}\n"
|
||
r+="<b>RAM:</b> ${mem_info}\n"
|
||
r+="<b>Disk /:</b> ${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}5${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
|
||
is_warp_running_3xui && echo -e "${GREEN}[OK] Подключён.${NC}" && log_action "3XUI START" \
|
||
|| echo -e "${RED}Ошибка подключения.${NC}"
|
||
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_xui_json() {
|
||
clear; echo -e "\n${CYAN}━━━ Конфигурация для 3X-UI ━━━${NC}\n"
|
||
echo -e "${GREEN}── Outbound ──${NC}\n"
|
||
cat <<EOF
|
||
{
|
||
"tag": "warp",
|
||
"protocol": "socks",
|
||
"settings": {
|
||
"servers": [{"address": "127.0.0.1", "port": ${SOCKS_PORT}}]
|
||
}
|
||
}
|
||
EOF
|
||
echo -e "\n${GREEN}── Routing Rule ──${NC}\n"
|
||
cat <<EOF
|
||
{
|
||
"outboundTag": "warp",
|
||
"domain": ["geosite:openai","geosite:netflix","geosite:disney","geosite:spotify","domain:claude.ai"]
|
||
}
|
||
EOF
|
||
echo -e "\n${WHITE}SOCKS5: ${GREEN}127.0.0.1:${SOCKS_PORT}${NC}"
|
||
is_warp_running_3xui && echo -e "${WHITE}WARP IP: ${GREEN}$(get_warp_ip_3xui)${NC}"
|
||
echo ""; read -p "Enter..."
|
||
}
|
||
|
||
show_status_3xui() {
|
||
clear; echo -e "\n${CYAN}━━━ Статус WARP (3X-UI) ━━━${NC}\n"
|
||
if ! is_warp_installed_3xui; then
|
||
echo -e " ${RED}Не установлен.${NC} Установите через п.1."; echo ""; read -p "Enter..."; return
|
||
fi
|
||
local st; st=$(get_warp_status_3xui)
|
||
local sc="$RED"; [[ "$st" == "Подключён" ]] && sc="$GREEN"; [[ "$st" == "Отключён" ]] && sc="$YELLOW"
|
||
echo -e " ${WHITE}Статус: ${sc}${st}${NC}"
|
||
echo -e " ${WHITE}Порт SOCKS5: ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}"
|
||
echo -e " ${WHITE}Реальный IP: ${GREEN}${MY_IP}${NC}"
|
||
is_warp_running_3xui && echo -e " ${WHITE}WARP IP: ${GREEN}$(get_warp_ip_3xui)${NC}"
|
||
echo -e "\n ${CYAN}── warp-cli status ──${NC}"
|
||
warp-cli --accept-tos status 2>/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_pick_container() {
|
||
if [ -n "${CONTAINER:-}" ]; then
|
||
docker inspect "$CONTAINER" &>/dev/null && return 0
|
||
fi
|
||
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 "\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
|
||
save_config_val "CONTAINER" "$CONTAINER"
|
||
return 0
|
||
}
|
||
|
||
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
|
||
done
|
||
|
||
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
|
||
|
||
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
|
||
}
|
||
|
||
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; 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"; }
|
||
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_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+="${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 ""
|
||
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 ""
|
||
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
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 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":"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"}]]'; }
|
||
|
||
# ─── 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)"
|
||
[ "$MODE" = "amnezia" ] && extra="\nКонтейнер: <code>${CONTAINER:-N/A}</code>"
|
||
[ "$MODE" = "3xui" ] && extra="\nSOCKS5: <code>127.0.0.1:${SOCKS_PORT}</code>"
|
||
local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
|
||
local text="<b>WARP Manager v${WARP_VERSION}</b> (${mode_label})\nСервер: <code>${MY_IP:-N/A}</code>\nСтатус: <b>${ws}</b>${wip}${extra}\n\nВыберите:"
|
||
local 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
|
||
|
||
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: <code>$(get_warp_ip)</code>"
|
||
if [ "$MODE" = "3xui" ]; then
|
||
extra="\nSOCKS5: <code>127.0.0.1:${SOCKS_PORT}</code>"
|
||
local raw; raw=$(warp-cli --accept-tos status 2>/dev/null | head -3)
|
||
extra+="\n\n<pre>${raw}</pre>"
|
||
else
|
||
extra="\nКонтейнер: <code>${CONTAINER}</code>\nПодсеть: <code>${AWG_SUBNET:-N/A}</code>"
|
||
awg_load_clients
|
||
extra+="\nКлиентов в WARP: <b>${#AWG_SELECTED_IPS[@]}</b>"
|
||
fi
|
||
tg_edit "$chat_id" "$msg_id" "📊 <b>Статус WARP</b>\n\nСтатус: <b>${ws}</b>\nСервер: <code>${MY_IP:-N/A}</code>${wip}${extra}" "$(kbd_back)" ;;
|
||
|
||
ip)
|
||
local wip="N/A"; is_warp_running && wip=$(get_warp_ip)
|
||
local t="🌐 <b>IP адреса</b>\n\n<b>Реальный:</b> <code>${MY_IP:-N/A}</code>\n<b>WARP:</b> <code>${wip}</code>"
|
||
[ "$MODE" = "3xui" ] && t+="\n<b>SOCKS5:</b> <code>127.0.0.1:${SOCKS_PORT}</code>"
|
||
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;;
|
||
|
||
on)
|
||
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" "✅ <b>Подключён</b>\nWARP IP: <code>${w}</code>" "$(kbd_back)"; log_action "BOT 3XUI ON"; } \
|
||
|| tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"
|
||
else
|
||
is_warp_installed_awg || { tg_edit "$chat_id" "$msg_id" "❌ Не установлен." "$(kbd_back)"; return; }
|
||
is_warp_running_awg && { tg_edit "$chat_id" "$msg_id" "✅ Уже работает." "$(kbd_back)"; return; }
|
||
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" "✅ <b>WARP запущен</b>\nWARP IP: <code>${AWG_WARP_EXIT_IP:-?}</code>" "$(kbd_back)"; log_action "BOT AWG ON"
|
||
else tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; fi
|
||
fi ;;
|
||
|
||
off)
|
||
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" "⏹ <b>WARP отключён.</b>" "$(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" "⏹ <b>WARP остановлен.</b>" "$(kbd_back)"; log_action "BOT AWG OFF"
|
||
fi ;;
|
||
|
||
rk)
|
||
tg_edit "$chat_id" "$msg_id" "🔑 <b>Перевыпуск ключа</b>\n\nПродолжить?" "$(kbd_rekey_confirm)" ;;
|
||
|
||
rk_y)
|
||
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" "✅ <b>Ключ перевыпущен</b>\nWARP IP: <code>${w}</code>" "$(kbd_back)"; log_action "BOT 3XUI REKEY: ${w}"
|
||
else tg_edit "$chat_id" "$msg_id" "⚠️ Перевыпущен, подключение не подтверждено." "$(kbd_back)"; fi
|
||
else
|
||
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
|
||
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" "✅ <b>Ключ перевыпущен</b>\nWARP IP: <code>${AWG_WARP_EXIT_IP:-?}</code>" "$(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)
|
||
if [ "$MODE" = "3xui" ]; then
|
||
local t="📋 <b>Конфигурация для 3X-UI</b>\n\n<b>Outbound:</b>\n<pre>{\n \"tag\": \"warp\",\n \"protocol\": \"socks\",\n \"settings\": {\n \"servers\": [{\"address\": \"127.0.0.1\", \"port\": ${SOCKS_PORT}}]\n }\n}</pre>\n\n<b>Routing:</b>\n<pre>{\"outboundTag\": \"warp\", \"domain\": [\"geosite:openai\",\"geosite:netflix\"]}</pre>"
|
||
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="👥 <b>Клиенты в WARP</b> (${#AWG_SELECTED_IPS[@]})\n\n"
|
||
if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then
|
||
t+="<i>Нет клиентов.</i>"
|
||
else
|
||
for ip in "${AWG_SELECTED_IPS[@]}"; do
|
||
local name; name=$(awg_get_name "$ip")
|
||
t+="● <code>${ip}</code>"
|
||
[ -n "$name" ] && t+=" ($name)"
|
||
t+="\n"
|
||
done
|
||
fi
|
||
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;;
|
||
|
||
cla)
|
||
awg_get_client_ips; awg_parse_clients_table; awg_load_clients
|
||
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" "➕ <b>Добавить в WARP:</b>" "$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" "➖ <b>Убрать из WARP:</b>" "$kbd" ;;
|
||
|
||
cr:*)
|
||
local idx="${data#cr:}"
|
||
awg_load_clients
|
||
if [ "$idx" = "all" ]; then
|
||
AWG_SELECTED_IPS=()
|
||
else
|
||
[[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_SELECTED_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; }
|
||
local -a new=()
|
||
for i in "${!AWG_SELECTED_IPS[@]}"; do
|
||
[ "$i" -ne "$idx" ] && new+=("${AWG_SELECTED_IPS[$i]}")
|
||
done
|
||
AWG_SELECTED_IPS=("${new[@]+"${new[@]}"}")
|
||
fi
|
||
awg_save_clients; awg_apply_rules; awg_patch_start_sh
|
||
tg_edit "$chat_id" "$msg_id" "✅ Обновлено. Клиентов: ${#AWG_SELECTED_IPS[@]}" "$(kbd_back)"
|
||
log_action "BOT AWG REMOVE: ${#AWG_SELECTED_IPS[@]}" ;;
|
||
|
||
rc)
|
||
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)
|
||
s+="\n<b>WARP:</b> ${ws}"
|
||
s+="\n<b>Режим:</b> ${MODE}"
|
||
[ "$MODE" = "3xui" ] && s+="\n<b>SOCKS5:</b> 127.0.0.1:${SOCKS_PORT}"
|
||
[ "$MODE" = "amnezia" ] && s+="\n<b>Контейнер:</b> ${CONTAINER:-N/A}"
|
||
tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;;
|
||
|
||
promo)
|
||
local pt="<b>🏢 Хостинг</b>\n\n<b>🌍 РФ и Европа</b>\n👉 https://vk.cc/ct29NQ\n\n<code>OFF60</code> — 60%\n<code>antenka20</code> — +20% (3мес)\n\n<b>🇧🇾 Беларусь</b>\n👉 https://vk.cc/cUxAhj\n<code>OFF60</code> — 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
|
||
[ "$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"')
|
||
[ "$ok" != "true" ] && sleep 5 && continue
|
||
local cnt; cnt=$(echo "$response" | jq '.result | length')
|
||
for (( i=0; i<cnt; i++ )); do
|
||
local upd; upd=$(echo "$response" | jq ".result[$i]")
|
||
local uid; uid=$(echo "$upd" | jq -r '.update_id')
|
||
offset=$((uid + 1))
|
||
local cbd; cbd=$(echo "$upd" | jq -r '.callback_query.data // empty')
|
||
if [ -n "$cbd" ]; then
|
||
local cbi cci cmi
|
||
cbi=$(echo "$upd" | jq -r '.callback_query.id')
|
||
cci=$(echo "$upd" | jq -r '.callback_query.message.chat.id')
|
||
cmi=$(echo "$upd" | jq -r '.callback_query.message.message_id')
|
||
[ -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: <code>$mci</code>" "" > /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; }
|
||
cat > /etc/systemd/system/warp-bot.service <<EOF
|
||
[Unit]
|
||
Description=WARP Manager Telegram Bot
|
||
After=network.target
|
||
[Service]
|
||
Type=simple
|
||
ExecStart=/usr/local/bin/gowarp --bot-daemon
|
||
Restart=always
|
||
RestartSec=5
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
systemctl daemon-reload; systemctl enable warp-bot > /dev/null 2>&1; systemctl start warp-bot; sleep 1
|
||
systemctl is-active warp-bot &>/dev/null && echo -e "${GREEN}[OK] Бот запущен.${NC}" && log_action "Bot started" \
|
||
|| echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}"
|
||
}
|
||
|
||
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"
|
||
}
|
||
|
||
bot_menu() {
|
||
while true; do
|
||
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}"
|
||
echo -e "${CYAN}━━━ Telegram Bot ━━━${NC}"
|
||
echo -e "Статус: $bs"
|
||
echo -e "Токен: ${YELLOW}$td${NC}"
|
||
echo -e "Chat ID: ${YELLOW}${BOT_CHAT_ID:-нет}${NC}\n"
|
||
echo -e "1) Токен бота"
|
||
echo -e "2) Chat ID (авто)"
|
||
echo -e "3) Chat ID (вручную)"
|
||
echo -e "4) ${GREEN}Запустить${NC}"
|
||
echo -e "5) ${RED}Остановить${NC}"
|
||
echo -e "0) Назад"
|
||
read -p "Выбор: " ch
|
||
case $ch in
|
||
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')
|
||
[ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && BOT_CHAT_ID="$c" && echo -e "${GREEN}Chat ID: $c${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}"
|
||
read -p "Enter..." ;;
|
||
4) start_bot; read -p "Enter..." ;;
|
||
5) stop_bot; 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 [ "$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
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# MAIN MENU
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
show_menu() {
|
||
while true; do
|
||
clear
|
||
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 -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 [ "$MODE" = "3xui" ] && is_warp_running; then
|
||
echo -e " ${WHITE}SOCKS5:${NC} ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}"
|
||
fi
|
||
if [ "$MODE" = "amnezia" ] && [ -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 [ "$MODE" = "3xui" ]; then
|
||
echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}"
|
||
echo -e " 6) 📋 ${CYAN}JSON конфиг для 3X-UI${NC}"
|
||
echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}"
|
||
fi
|
||
|
||
if [ "$MODE" = "amnezia" ]; then
|
||
echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}"
|
||
echo -e " 6) ${GREEN}➕ Добавить клиентов в WARP${NC}"
|
||
echo -e " 7) ${YELLOW}➖ Убрать клиентов из WARP${NC}"
|
||
echo -e " 8) 👥 ${WHITE}Показать клиентов в WARP${NC}"
|
||
echo -e " 9) 🔄 ${CYAN}Перезапуск контейнера${NC}"
|
||
fi
|
||
|
||
echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}"
|
||
echo -e " 10) 🤖 ${CYAN}Настройка и управление ботом${NC}"
|
||
|
||
echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}"
|
||
echo -e " 11) ${YELLOW}PROMO${NC}"
|
||
echo -e " 12) ${MAGENTA}📚 Инструкция${NC}"
|
||
echo -e " 13) ${RED}⚠ Полное удаление${NC}"
|
||
echo -e " 0) Выход"
|
||
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
|
||
read -p " Выбор: " ch
|
||
|
||
case $ch in
|
||
1) [ "$MODE" = "3xui" ] && install_warp_3xui || install_warp_awg ;;
|
||
2) [ "$MODE" = "3xui" ] && start_warp_3xui || start_warp_awg ;;
|
||
3) [ "$MODE" = "3xui" ] && stop_warp_3xui || stop_warp_awg ;;
|
||
4) [ "$MODE" = "3xui" ] && show_status_3xui || show_status_awg ;;
|
||
5) [ "$MODE" = "3xui" ] && rekey_warp_3xui || rekey_warp_awg ;;
|
||
6) [ "$MODE" = "3xui" ] && show_xui_json || awg_add_clients_ssh ;;
|
||
7) [ "$MODE" = "3xui" ] && change_port_3xui || awg_remove_clients_ssh ;;
|
||
8) [ "$MODE" = "amnezia" ] && awg_show_clients_ssh ;;
|
||
9) [ "$MODE" = "amnezia" ] && awg_restart_container ;;
|
||
10) bot_menu ;;
|
||
11) show_promo ;;
|
||
12) show_info ;;
|
||
13) full_uninstall ;;
|
||
0) exit 0 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# STARTUP
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
run_startup() {
|
||
local total=7 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"
|
||
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"
|
||
|
||
((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"
|
||
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<w; i++)); do bar+="█"; done
|
||
echo -e " ${CYAN}[${GREEN}${bar}${CYAN}]${NC} ${GREEN}100%${NC}"
|
||
echo -e "\n ${GREEN}✅ WARP Manager v${WARP_VERSION} (${mode_label}) готов!${NC}\n"
|
||
sleep 2
|
||
|
||
show_promo
|
||
show_info
|
||
show_menu
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# ENTRY POINT
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
case "${1:-}" in
|
||
--bot-daemon) init_config; bot_daemon ;;
|
||
*) init_config; check_license "${1:-}"; run_startup ;;
|
||
esac
|