Files
go_warp_pro/warp.sh

1693 lines
81 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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_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=()
# ═══════════════════════════════════════════════════════════════
# 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 -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
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}Реальный IP: ${GREEN}${MY_IP}${NC}"
is_warp_running_3xui && echo -e " ${WHITE}WARP IP: ${GREEN}$(get_warp_ip_3xui)${NC}"
echo -e "\n${CYAN}── Настройки SOCKS5-прокси ──${NC}\n"
echo -e " ${WHITE}Адрес:${NC} ${GREEN}127.0.0.1${NC}"
echo -e " ${WHITE}Порт:${NC} ${GREEN}${SOCKS_PORT}${NC}"
echo -e " ${WHITE}Прокси:${NC} ${CYAN}socks5h://127.0.0.1:${SOCKS_PORT}${NC}"
echo -e "\n${CYAN}── JSON Outbound для 3X-UI (скопируйте в панель) ──${NC}\n"
echo -e "${GREEN}"
cat <<EOF
{
"tag": "warp",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": ${SOCKS_PORT}
}
]
}
}
EOF
echo -e "${NC}"
echo -e "${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 exec "$CONTAINER" sh -c "true" 2>/dev/null && return 0
CONTAINER=""
fi
local -a containers=()
mapfile -t containers < <(docker ps --format '{{.Names}}' | grep -E '^amnezia-awg2$|^amnezia-awg$' 2>/dev/null || true)
if [ ${#containers[@]} -eq 0 ]; then
mapfile -t containers < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia" || true)
fi
if [ ${#containers[@]} -eq 0 ]; then
echo -e "${RED}Контейнеры amnezia-awg / amnezia-awg2 не найдены.${NC}"
echo -e "${WHITE}Убедитесь, что AmneziaWG запущен через Docker.${NC}"
return 1
elif [ ${#containers[@]} -eq 1 ]; then
CONTAINER="${containers[0]}"
else
echo -e "\n${CYAN}Доступные контейнеры:${NC}"
local i=1
for c in "${containers[@]}"; do echo -e " ${GREEN}$i)${NC} $c"; ((i++)); done
echo -e " ${DIM}0) Отмена${NC}"
while true; do
read -p "Выберите контейнер: " ch
[ "$ch" = "0" ] && return 1
[[ "$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() {
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}"
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; }
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=()
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_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 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+="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+=""$'\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() {
[ "$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% (ес)\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% (ес)" "antenka6" "+15% (ес)" "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