/dev/null | while IFS= read -r l; do echo -e " ${WHITE}$l${NC}"; done
echo ""; read -p "Enter..."
}
uninstall_3xui() {
echo -e "\n${YELLOW}Удаление WARP (3X-UI)...${NC}\n"
systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null
rm -f /etc/systemd/system/warp-bot.service; systemctl daemon-reload 2>/dev/null
[ -f "$BOT_PID_FILE" ] && { kill "$(cat "$BOT_PID_FILE")" 2>/dev/null; rm -f "$BOT_PID_FILE"; }
echo -e " ${GREEN}✓${NC} Бот остановлен"
warp-cli --accept-tos disconnect > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} WARP отключён"
warp-cli --accept-tos registration delete > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} Регистрация удалена"
apt-get remove -y cloudflare-warp > /dev/null 2>&1; apt-get autoremove -y > /dev/null 2>&1; echo -e " ${GREEN}✓${NC} Пакет удалён"
rm -f /etc/apt/sources.list.d/cloudflare-client.list /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
echo -e " ${GREEN}✓${NC} Репозиторий удалён"
}
# ═══════════════════════════════════════════════════════════════
# AMNEZIA BACKEND — WARP via wgcf (WireGuard inside Docker)
# ═══════════════════════════════════════════════════════════════
awg_discover_containers() {
AWG_ALL_CONTAINERS=()
mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^amnezia-awg2$|^amnezia-awg$' || true)
if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then
mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia" || true)
fi
if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then
echo -e "${RED}Контейнеры amnezia-awg / amnezia-awg2 не найдены.${NC}"
return 1
fi
CONTAINERS="${AWG_ALL_CONTAINERS[*]}"
save_config_val "CONTAINERS" "$CONTAINERS"
return 0
}
awg_pick_container() {
awg_discover_containers || return 1
if [ -n "${CONTAINER:-}" ]; then
docker exec "$CONTAINER" sh -c "true" 2>/dev/null && return 0
fi
CONTAINER="${AWG_ALL_CONTAINERS[0]}"
save_config_val "CONTAINER" "$CONTAINER"
return 0
}
awg_select_container() {
awg_discover_containers 2>/dev/null || return 1
if [ ${#AWG_ALL_CONTAINERS[@]} -eq 1 ]; then
CONTAINER="${AWG_ALL_CONTAINERS[0]}"
awg_load_container_data
return 0
fi
echo -e "\n${CYAN}Выберите контейнер:${NC}"
local i=1
for c in "${AWG_ALL_CONTAINERS[@]}"; do
local has_warp="${DIM}нет${NC}"
docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null && has_warp="${GREEN}да${NC}"
local clients="-"
local cnt; cnt=$(docker exec "$c" sh -c "cat /opt/warp/clients.list 2>/dev/null | grep -c '.' || echo 0" 2>/dev/null | tr -d '\r')
[ "$cnt" != "0" ] && clients="$cnt"
local marker=""
[ "$c" = "${CONTAINER:-}" ] && marker=" ${CYAN}*${NC}"
echo -e " ${GREEN}$i)${NC} $c [WARP: $has_warp | Клиентов: $clients]${marker}"
((i++))
done
echo -e " ${DIM}0) Отмена${NC}"
while true; do
read -p " > " ch
[ "$ch" = "0" ] && return 1
if [[ "$ch" =~ ^[0-9]+$ ]] && (( ch >= 1 && ch <= ${#AWG_ALL_CONTAINERS[@]} )); then
CONTAINER="${AWG_ALL_CONTAINERS[$((ch-1))]}"
save_config_val "CONTAINER" "$CONTAINER"
awg_load_container_data
return 0
fi
done
}
awg_load_container_data() {
if [ "$CONTAINER" = "amnezia-awg2" ]; then
AWG_VPN_CONF="/opt/amnezia/awg/awg0.conf"
AWG_VPN_IF="awg0"
AWG_VPN_QUICK_CMD="awg-quick"
else
AWG_VPN_CONF="/opt/amnezia/awg/wg0.conf"
AWG_VPN_IF="wg0"
AWG_VPN_QUICK_CMD="wg-quick"
fi
AWG_CLIENTS_TABLE="/opt/amnezia/awg/clientsTable"
AWG_START_SH="/opt/amnezia/start.sh"
docker exec "$CONTAINER" sh -c "[ -f '$AWG_VPN_CONF' ]" 2>/dev/null || {
for f in /opt/amnezia/awg/wg0.conf /opt/amnezia/awg/awg0.conf /etc/wireguard/wg0.conf; do
if docker exec "$CONTAINER" sh -c "[ -f '$f' ]" 2>/dev/null; then
AWG_VPN_CONF="$f"
break
fi
done
}
docker exec "$CONTAINER" sh -c "[ -f '$AWG_VPN_CONF' ]" 2>/dev/null || {
echo -e "${RED}Не найден конфиг VPN в контейнере: $AWG_VPN_CONF${NC}"
return 1
}
AWG_SUBNET=$(docker exec "$CONTAINER" sh -c "sed -n 's/^Address = \(.*\)$/\1/p' '$AWG_VPN_CONF' | head -n1 | cut -d',' -f1" 2>/dev/null | tr -d '\r')
return 0
}
awg_detect_warp_exit_ip() {
AWG_WARP_EXIT_IP=""
if docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null; then
AWG_WARP_EXIT_IP=$(docker exec "$CONTAINER" sh -c \
"curl -s --interface warp --connect-timeout 3 https://ifconfig.me 2>/dev/null || true" | tr -d '\r\n')
fi
}
is_warp_installed_awg() {
docker exec "$CONTAINER" sh -c "[ -f '$AWG_WARP_CONF' ]" 2>/dev/null
}
is_warp_running_awg() {
docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null
}
get_warp_status_awg() {
if ! is_warp_installed_awg; then echo "Не установлен"; return; fi
is_warp_running_awg && echo "Подключён" || echo "Отключён"
}
awg_backup() {
local ts; ts=$(date +%Y%m%d-%H%M%S)
docker exec "$CONTAINER" sh -c "
[ -f '$AWG_VPN_CONF' ] && cp '$AWG_VPN_CONF' '${AWG_VPN_CONF}.bak-${ts}'
[ -f '$AWG_CLIENTS_TABLE' ] && cp '$AWG_CLIENTS_TABLE' '${AWG_CLIENTS_TABLE}.bak-${ts}'
[ -f '$AWG_START_SH' ] && cp '$AWG_START_SH' '${AWG_START_SH}.bak-${ts}'
[ -f '$AWG_START_SH' ] && [ ! -f /opt/amnezia/start.sh.final-backup ] && cp '$AWG_START_SH' /opt/amnezia/start.sh.final-backup
" >/dev/null 2>&1
log_action "AWG BACKUP: $ts"
}
awg_install_wgcf() {
if [ -x "$WGCF_BIN" ]; then return 0; fi
local arch; arch=$(uname -m)
local wa=""
case "$arch" in
x86_64) wa="amd64" ;; aarch64) wa="arm64" ;; armv7l) wa="armv7" ;;
*) echo -e "${RED}Архитектура не поддерживается: $arch${NC}"; return 1 ;;
esac
wget -q -O "$WGCF_BIN" "https://github.com/ViRb3/wgcf/releases/download/v${WGCF_VERSION}/wgcf_${WGCF_VERSION}_linux_${wa}" \
|| { echo -e "${RED}Не удалось скачать wgcf.${NC}"; return 1; }
chmod +x "$WGCF_BIN"
}
awg_ensure_account() {
if [ ! -f "$WGCF_ACCOUNT" ]; then
echo -e "${YELLOW}Регистрация WARP через wgcf...${NC}"
(cd /root && yes | ./wgcf register 2>/dev/null)
fi
[ -f "$WGCF_ACCOUNT" ] || { echo -e "${RED}Не создан $WGCF_ACCOUNT${NC}"; return 1; }
}
awg_generate_profile() {
(cd /root && yes | ./wgcf generate 2>/dev/null)
[ -f "$WGCF_PROFILE" ] || { echo -e "${RED}Не создан профиль.${NC}"; return 1; }
}
awg_resolve_endpoint() {
local ep; ep=$(getent ahostsv4 engage.cloudflareclient.com 2>/dev/null | awk 'NR==1{print $1}')
[ -z "$ep" ] && { echo -e "${RED}Не удалось определить IP endpoint.${NC}"; return 1; }
echo "$ep"
}
awg_build_warp_conf() {
local endpoint_ip="$1"
local pk pub addr
pk=$(awk -F' = ' '/^PrivateKey = /{print $2}' "$WGCF_PROFILE")
pub=$(awk -F' = ' '/^PublicKey = /{print $2}' "$WGCF_PROFILE")
addr=$(awk -F' = ' '/^Address = /{print $2}' "$WGCF_PROFILE" | cut -d',' -f1)
docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR'"
docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null
docker exec "$CONTAINER" sh -c "cat > '$AWG_WARP_CONF' <<'WARPEOF'
[Interface]
PrivateKey = ${pk}
Address = ${addr}
MTU = 1280
Table = off
[Peer]
PublicKey = ${pub}
AllowedIPs = 0.0.0.0/0
Endpoint = ${endpoint_ip}:2408
PersistentKeepalive = 25
WARPEOF
chmod 600 '$AWG_WARP_CONF'"
}
awg_warp_up() {
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' >/dev/null 2>&1 || true"
docker exec "$CONTAINER" sh -c "wg-quick up '$AWG_WARP_CONF'" || { echo -e "${RED}wg-quick up не удался.${NC}"; return 1; }
docker exec "$CONTAINER" sh -c "ip addr show warp >/dev/null 2>&1" || { echo -e "${RED}Интерфейс warp не поднялся.${NC}"; return 1; }
}
install_warp_awg() {
clear; echo -e "\n${CYAN}━━━ Установка WARP (AmneziaWG) ━━━${NC}\n"
awg_discover_containers 2>/dev/null || { read -p "Enter..."; return; }
echo -e "${YELLOW}[1/5]${NC} Скачиваю wgcf..."
awg_install_wgcf || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[2/5]${NC} Регистрация WARP..."
awg_ensure_account || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[3/5]${NC} Генерация профиля..."
awg_generate_profile || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[4/5]${NC} Определение endpoint..."
local ep; ep=$(awg_resolve_endpoint) || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓ ${ep}${NC}"
echo -e "${YELLOW}[5/5]${NC} Установка на контейнеры...\n"
local saved_container="${CONTAINER:-}"
for c in "${AWG_ALL_CONTAINERS[@]}"; do
CONTAINER="$c"
awg_load_container_data 2>/dev/null || continue
echo -e " ${CYAN}► ${c}${NC}"
if docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null; then
if docker exec "$c" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null; then
echo -e " ${GREEN}✓ Уже установлен и работает${NC}"
else
echo -e " ${YELLOW}Поднимаю интерфейс...${NC}"
awg_warp_up 2>/dev/null && echo -e " ${GREEN}✓ warp поднят${NC}" || echo -e " ${RED}✗ Ошибка${NC}"
fi
else
awg_backup 2>/dev/null
awg_build_warp_conf "$ep"
awg_warp_up 2>/dev/null && echo -e " ${GREEN}✓ WARP установлен${NC}" || echo -e " ${RED}✗ Ошибка${NC}"
fi
awg_detect_warp_exit_ip
[ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}"
done
CONTAINER="$saved_container"
echo -e "\n${GREEN}Готово! Управление клиентами — п.7.${NC}"
log_action "AWG INSTALL: ${#AWG_ALL_CONTAINERS[@]} containers, endpoint=${ep}"
read -p "Enter..."
}
start_warp_awg() {
is_warp_installed_awg || { echo -e "\n${RED}WARP не установлен (п.1).${NC}"; read -p "Enter..."; return; }
is_warp_running_awg && { echo -e "\n${YELLOW}Уже работает.${NC}"; read -p "Enter..."; return; }
echo -e "\n${YELLOW}Поднимаю warp...${NC}"
if awg_warp_up; then
echo -e "${GREEN}[OK] WARP подключён.${NC}"; log_action "AWG START"
else
echo -e "${RED}Ошибка.${NC}"
fi
read -p "Enter..."
}
stop_warp_awg() {
is_warp_running_awg || { echo -e "\n${YELLOW}Уже остановлен.${NC}"; read -p "Enter..."; return; }
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"
echo -e "${GREEN}[OK] WARP остановлен.${NC}"; log_action "AWG STOP"
read -p "Enter..."
}
rekey_warp_awg() {
is_warp_installed_awg || { echo -e "\n${RED}WARP не установлен.${NC}"; read -p "Enter..."; return; }
echo -e "\n${CYAN}━━━ Перевыпуск ключа WARP (AmneziaWG) ━━━${NC}\n"
read -p "Продолжить? (y/n): " c; [[ "$c" != "y" ]] && return
echo -e "${YELLOW}[1/5]${NC} Остановка warp..."; docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[2/5]${NC} Удаление аккаунта..."; rm -f "$WGCF_ACCOUNT"; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[3/5]${NC} Новая регистрация..."; awg_ensure_account || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[4/5]${NC} Генерация профиля..."; awg_generate_profile || { read -p "Enter..."; return; }; echo -e "${GREEN} ✓${NC}"
echo -e "${YELLOW}[5/5]${NC} Пересборка и запуск..."
local ep; ep=$(awg_resolve_endpoint) || { read -p "Enter..."; return; }
awg_build_warp_conf "$ep"
awg_warp_up || { read -p "Enter..."; return; }
awg_apply_rules
awg_patch_start_sh
awg_detect_warp_exit_ip
echo -e "${GREEN} ✓ Готово!${NC}"
[ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}Новый WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}"
log_action "AWG REKEY: warp_ip=${AWG_WARP_EXIT_IP}"
read -p "Enter..."
}
show_status_awg() {
clear; echo -e "\n${CYAN}━━━ Статус WARP (AmneziaWG) ━━━${NC}\n"
echo -e " ${WHITE}Контейнер: ${CYAN}${CONTAINER}${NC}"
echo -e " ${WHITE}Подсеть: ${CYAN}${AWG_SUBNET:-N/A}${NC}"
local st; st=$(get_warp_status_awg)
local sc="$RED"; [[ "$st" == "Подключён" ]] && sc="$GREEN"; [[ "$st" == "Отключён" ]] && sc="$YELLOW"
echo -e " ${WHITE}WARP: ${sc}${st}${NC}"
echo -e " ${WHITE}Реальный IP: ${GREEN}${MY_IP}${NC}"
awg_detect_warp_exit_ip
[ -n "$AWG_WARP_EXIT_IP" ] && echo -e " ${WHITE}WARP IP: ${GREEN}${AWG_WARP_EXIT_IP}${NC}"
awg_load_clients
echo -e "\n ${WHITE}Клиентов в WARP: ${CYAN}${#AWG_SELECTED_IPS[@]}${NC}"
if [ ${#AWG_SELECTED_IPS[@]} -gt 0 ]; then
awg_parse_clients_table
for ip in "${AWG_SELECTED_IPS[@]}"; do
echo -e " ${GREEN}●${NC} $(awg_format_label "$ip")"
done
fi
if is_warp_running_awg; then
echo -e "\n ${CYAN}── wg show warp ──${NC}"
docker exec "$CONTAINER" sh -c "wg show warp 2>/dev/null" | while IFS= read -r l; do echo -e " ${WHITE}$l${NC}"; done
fi
echo ""; read -p "Enter..."
}
uninstall_awg() {
echo -e "\n${YELLOW}Удаление WARP (AmneziaWG)...${NC}\n"
systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null
rm -f /etc/systemd/system/warp-bot.service; systemctl daemon-reload 2>/dev/null
[ -f "$BOT_PID_FILE" ] && { kill "$(cat "$BOT_PID_FILE")" 2>/dev/null; rm -f "$BOT_PID_FILE"; }
echo -e " ${GREEN}✓${NC} Бот остановлен"
awg_cleanup_rules
docker exec "$CONTAINER" sh -c "
wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true
ip link del warp 2>/dev/null || true
rm -rf '$AWG_WARP_DIR'
" >/dev/null 2>&1
echo -e " ${GREEN}✓${NC} WARP удалён из контейнера"
awg_remove_from_start_sh
if docker exec "$CONTAINER" sh -c '[ -f /opt/amnezia/start.sh.final-backup ]' 2>/dev/null; then
docker exec "$CONTAINER" sh -c "
cp /opt/amnezia/start.sh.final-backup '$AWG_START_SH' 2>/dev/null
chmod +x '$AWG_START_SH' 2>/dev/null
rm -f /opt/amnezia/start.sh.final-backup
" 2>/dev/null
echo -e " ${GREEN}✓${NC} start.sh восстановлен"
fi
rm -f "$WGCF_BIN" "$WGCF_ACCOUNT" "$WGCF_PROFILE"
echo -e " ${GREEN}✓${NC} wgcf и профили удалены"
}
awg_check_containers() {
clear; echo -e "\n${CYAN}━━━ Контейнеры AmneziaWG ━━━${NC}\n"
awg_discover_containers 2>/dev/null
if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then
echo -e " ${RED}Контейнеры не найдены.${NC}"
read -p " Enter..."; return
fi
printf " ${WHITE}%-20s %-10s %-10s %-12s${NC}\n" "Контейнер" "Статус" "WARP" "Клиентов"
echo -e " ${CYAN}────────────────────────────────────────────────────${NC}"
for c in "${AWG_ALL_CONTAINERS[@]}"; do
local status="Running"
docker exec "$c" sh -c "true" 2>/dev/null || status="${RED}Down${NC}"
[ "$status" = "Running" ] && status="${GREEN}Running${NC}"
local has_warp="${DIM}Нет${NC}"
local client_info="—"
if docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null; then
local warp_up="${YELLOW}Выкл${NC}"
docker exec "$c" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null && warp_up="${GREEN}Вкл${NC}"
has_warp="$warp_up"
local cnt; cnt=$(docker exec "$c" sh -c "cat /opt/warp/clients.list 2>/dev/null | grep -c '.' || echo 0" | tr -d '\r')
client_info="${cnt}"
fi
local marker=""
[ "$c" = "${CONTAINER:-}" ] && marker=" ${CYAN}*${NC}"
printf " %-20s %-22b %-22b %-12s%b\n" "$c" "$status" "$has_warp" "$client_info" "$marker"
done
echo -e "\n ${DIM}* = текущий контейнер${NC}"
echo ""; read -p " Enter..."
}
awg_restart_container() {
echo -e "\n${YELLOW}Перезапуск контейнера ${CONTAINER}...${NC}"
docker restart "$CONTAINER" >/dev/null
local a=0
while [ "$a" -lt 10 ]; do
docker exec "$CONTAINER" sh -c "true" 2>/dev/null && { echo -e "${GREEN}[OK] Перезапущен.${NC}"; log_action "AWG RESTART"; read -p "Enter..."; return; }
sleep 1; ((a++))
done
echo -e "${RED}Не удалось за 10с.${NC}"; read -p "Enter..."
}
# ═══════════════════════════════════════════════════════════════
# AMNEZIA CLIENT MANAGEMENT
# ═══════════════════════════════════════════════════════════════
awg_load_clients() {
AWG_SELECTED_IPS=()
local raw; raw=$(docker exec "$CONTAINER" sh -c "cat '$AWG_WARP_CLIENTS' 2>/dev/null || true" | tr -d '\r')
if [ -n "$raw" ]; then
while IFS= read -r line; do
line=$(echo "$line" | xargs)
[ -n "$line" ] && AWG_SELECTED_IPS+=("$line")
done <<< "$raw"
fi
}
awg_save_clients() {
local content=""
for ip in "${AWG_SELECTED_IPS[@]}"; do
content="${content}${ip}"$'\n'
done
docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR' && cat > '$AWG_WARP_CLIENTS' <<'CLEOF'
${content}CLEOF
"
}
awg_parse_clients_table() {
AWG_CLIENT_NAMES=()
local raw
raw=$(docker exec "$CONTAINER" sh -c "cat '$AWG_CLIENTS_TABLE' 2>/dev/null || true" | tr -d '\r')
[ -z "$raw" ] && return 0
declare -A key_to_name=()
local id_name_pairs
id_name_pairs=$(echo "$raw" | awk '
/"clientId"/ {
s = $0
gsub(/.*"clientId"[[:space:]]*:[[:space:]]*"/, "", s)
gsub(/".*/, "", s)
cid = s
}
/"clientName"/ {
s = $0
gsub(/.*"clientName"[[:space:]]*:[[:space:]]*"/, "", s)
gsub(/".*/, "", s)
name = s
if (cid != "" && name != "") {
print cid "|" name
}
}')
if [ -n "$id_name_pairs" ]; then
while IFS='|' read -r cid name; do
[ -n "$cid" ] && [ -n "$name" ] && key_to_name["$cid"]="$name"
done <<< "$id_name_pairs"
fi
local conf_peers
conf_peers=$(docker exec "$CONTAINER" sh -c "cat '$AWG_VPN_CONF' 2>/dev/null || true" | tr -d '\r' | awk '
/^\[Peer\]/ { pubkey=""; ip="" }
/^PublicKey/ {
s = $0
sub(/^[^=]*= */, "", s)
pubkey = s
}
/^AllowedIPs/ {
s = $0
sub(/^[^=]*= */, "", s)
ip = s
if (pubkey != "" && ip != "") {
print pubkey "|" ip
}
}')
if [ -n "$conf_peers" ]; then
while IFS='|' read -r pubkey ip; do
if [ -n "$pubkey" ] && [ -n "$ip" ] && [ -n "${key_to_name[$pubkey]+_}" ]; then
local name="${key_to_name[$pubkey]}"
AWG_CLIENT_NAMES["$ip"]="$name"
local bare="${ip%/32}"
AWG_CLIENT_NAMES["$bare"]="$name"
fi
done <<< "$conf_peers"
fi
return 0
}
awg_get_name() {
local ip="$1" bare="${1%/32}"
[ -n "${AWG_CLIENT_NAMES[$bare]+_}" ] && { echo "${AWG_CLIENT_NAMES[$bare]}"; return; }
[ -n "${AWG_CLIENT_NAMES[$ip]+_}" ] && { echo "${AWG_CLIENT_NAMES[$ip]}"; return; }
[ -n "${AWG_CLIENT_NAMES[${bare}/32]+_}" ] && { echo "${AWG_CLIENT_NAMES[${bare}/32]}"; return; }
}
awg_format_label() {
local ip="$1" name; name=$(awg_get_name "$ip")
[ -n "$name" ] && echo "$ip ($name)" || echo "$ip"
}
awg_get_client_ips() {
AWG_CLIENT_IPS=()
mapfile -t AWG_CLIENT_IPS < <(docker exec "$CONTAINER" sh -c "sed -n 's/^AllowedIPs[[:space:]]*=[[:space:]]*\(.*\/32\)[[:space:]]*$/\1/p' '$AWG_VPN_CONF'" 2>/dev/null | tr -d '\r')
if [ "${#AWG_CLIENT_IPS[@]}" -eq 0 ]; then
mapfile -t AWG_CLIENT_IPS < <(docker exec "$CONTAINER" sh -c "awk '/^\[Peer\]/,/^$/' '$AWG_VPN_CONF' | sed -n 's/^AllowedIPs[[:space:]]*=[[:space:]]*//p'" 2>/dev/null | tr -d '\r' | grep '/32')
fi
}
awg_toggle_clients_ssh() {
awg_select_container || return
awg_get_client_ips; awg_parse_clients_table; awg_load_clients
if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then
echo -e "\n ${RED}Нет клиентов в конфиге VPN.${NC}"
read -p "Enter..."; return
fi
local -a pending_ips=()
for ip in "${AWG_SELECTED_IPS[@]}"; do pending_ips+=("$ip"); done
while true; do
local pending_set=" ${pending_ips[*]+"${pending_ips[*]}"} "
clear; echo -e "\n${CYAN}━━━ Управление клиентами WARP ━━━${NC}"
[ ${#AWG_ALL_CONTAINERS[@]} -gt 1 ] && echo -e " ${DIM}Контейнер: ${CONTAINER}${NC}"
echo -e " ${DIM}Нажмите номер чтобы вкл/выкл WARP для клиента${NC}\n"
local i=1 warp_count=0
for ip in "${AWG_CLIENT_IPS[@]}"; do
local label; label=$(awg_format_label "$ip")
if [[ "$pending_set" == *" $ip "* ]]; then
echo -e " ${GREEN} $i) ✅ $label${NC}"
((warp_count++))
else
echo -e " ${WHITE} $i)${NC} ☐ $label"
fi
((i++))
done
echo ""
echo -e " ${WHITE}Через WARP: ${CYAN}${warp_count}${NC} из ${#AWG_CLIENT_IPS[@]}"
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
echo -e " ${GREEN}all${NC}) Включить всех ${YELLOW}none${NC}) Выключить всех"
echo -e " ${GREEN}ok${NC}) Применить ${DIM}0${NC}) Отмена (без изменений)"
echo ""
read -p " > " answer
case "$answer" in
0|"")
return ;;
all)
pending_ips=("${AWG_CLIENT_IPS[@]}") ;;
none)
pending_ips=() ;;
ok)
AWG_SELECTED_IPS=("${pending_ips[@]+"${pending_ips[@]}"}")
echo -e "\n${YELLOW} Применяю правила...${NC}"
awg_save_clients; awg_apply_rules; awg_patch_start_sh
echo -e "${GREEN} ✓ Правила сохранены${NC}"
echo -e "\n${YELLOW} Перезапуск контейнера ${CONTAINER}...${NC}"
docker restart "$CONTAINER" >/dev/null 2>&1
local a=0
while [ "$a" -lt 15 ]; do
docker exec "$CONTAINER" sh -c "true" 2>/dev/null && break
sleep 1; ((a++))
done
if docker exec "$CONTAINER" sh -c "true" 2>/dev/null; then
echo -e "${GREEN} ✓ Контейнер перезапущен${NC}"
else
echo -e "${RED} ⚠ Контейнер не отвечает${NC}"
fi
log_action "AWG CLIENTS APPLIED: ${#AWG_SELECTED_IPS[@]} in WARP, container restarted"
read -p " Enter..."; return ;;
*)
IFS=',' read -ra parts <<< "$answer"
for p in "${parts[@]}"; do
p=$(echo "$p" | xargs)
if [[ "$p" =~ ^[0-9]+$ ]] && (( p >= 1 && p <= ${#AWG_CLIENT_IPS[@]} )); then
local ip="${AWG_CLIENT_IPS[$((p-1))]}"
if [[ "$pending_set" == *" $ip "* ]]; then
local -a tmp=()
for eip in "${pending_ips[@]}"; do
[ "$eip" != "$ip" ] && tmp+=("$eip")
done
pending_ips=("${tmp[@]+"${tmp[@]}"}")
else
pending_ips+=("$ip")
fi
fi
done ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# AMNEZIA RUNTIME RULES & PERSISTENCE
# ═══════════════════════════════════════════════════════════════
awg_cleanup_rules() {
docker exec "$CONTAINER" sh -c '
ip rule | awk "/lookup 100/ {print \$1}" | sed "s/://g" | sort -rn | while read -r pr; do
ip rule del priority "$pr" 2>/dev/null || true
done
iptables -t nat -S POSTROUTING | grep "\-o warp -j MASQUERADE" | while read -r line; do
rule=$(echo "$line" | sed "s/^-A /-D /")
iptables -t nat $rule || true
done
iptables -t mangle -S FORWARD 2>/dev/null | grep "\-o warp.*TCPMSS" | while read -r line; do
rule=$(echo "$line" | sed "s/^-A /-D /")
iptables -t mangle $rule || true
done
ip route flush table 100 2>/dev/null || true
' >/dev/null 2>&1 || true
}
awg_apply_rules() {
awg_cleanup_rules
[ ${#AWG_SELECTED_IPS[@]} -eq 0 ] && return 0
docker exec "$CONTAINER" sh -c "ip route add default dev warp table 100 2>/dev/null || ip route replace default dev warp table 100 2>/dev/null || true"
docker exec "$CONTAINER" sh -c "
iptables -t mangle -C FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || \
iptables -t mangle -A FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || true
"
local prio=100
for ip in "${AWG_SELECTED_IPS[@]}"; do
docker exec "$CONTAINER" sh -c "
ip rule add from ${ip} table 100 priority ${prio} 2>/dev/null || true
iptables -t nat -C POSTROUTING -s ${ip} -o warp -j MASQUERADE 2>/dev/null || \
iptables -t nat -I POSTROUTING 1 -s ${ip} -o warp -j MASQUERADE
"
((prio++))
done
}
awg_patch_start_sh() {
[ -z "${AWG_START_SH:-}" ] && return
docker exec "$CONTAINER" sh -c "[ -f /opt/amnezia/start.sh.final-backup ] || cp '$AWG_START_SH' /opt/amnezia/start.sh.final-backup" 2>/dev/null
local warp_block=""
warp_block+="${AWG_MARKER_BEGIN}"$'\n'
warp_block+=""$'\n'
warp_block+="if [ -f '${AWG_WARP_CONF}' ]; then"$'\n'
warp_block+=" wg-quick up '${AWG_WARP_CONF}' || true"$'\n'
warp_block+=" sleep 3"$'\n'
warp_block+="fi"$'\n'
warp_block+=""$'\n'
if [ ${#AWG_SELECTED_IPS[@]} -gt 0 ]; then
warp_block+="ip route add default dev warp table 100 2>/dev/null || ip route replace default dev warp table 100 2>/dev/null || true"$'\n'
warp_block+="iptables -t mangle -C FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || iptables -t mangle -A FORWARD -o warp -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 2>/dev/null || true"$'\n'
warp_block+=""$'\n'
local prio=100
for ip in "${AWG_SELECTED_IPS[@]}"; do
warp_block+="ip rule add from ${ip} table 100 priority ${prio} 2>/dev/null || true"$'\n'
warp_block+="iptables -t nat -C POSTROUTING -s ${ip} -o warp -j MASQUERADE 2>/dev/null || iptables -t nat -I POSTROUTING 1 -s ${ip} -o warp -j MASQUERADE"$'\n'
((prio++))
done
fi
warp_block+=""$'\n'
warp_block+="${AWG_MARKER_END}"
docker exec "$CONTAINER" sh -c "
if grep -qF '${AWG_MARKER_BEGIN}' '$AWG_START_SH'; then
sed -i '/# --- WARP-MANAGER BEGIN ---/,/# --- WARP-MANAGER END ---/d' '$AWG_START_SH'
fi
" 2>/dev/null
docker exec "$CONTAINER" sh -c "
if grep -qF 'tail -f /dev/null' '$AWG_START_SH'; then
tmpfile=\$(mktemp)
while IFS= read -r line; do
if echo \"\$line\" | grep -qF 'tail -f /dev/null'; then
cat <<'WARPBLOCK'
${warp_block}
WARPBLOCK
fi
echo \"\$line\"
done < '$AWG_START_SH' > \"\$tmpfile\"
mv \"\$tmpfile\" '$AWG_START_SH'
chmod +x '$AWG_START_SH'
else
cat >> '$AWG_START_SH' <<'WARPBLOCK'
${warp_block}
WARPBLOCK
chmod +x '$AWG_START_SH'
fi
" 2>/dev/null
}
awg_remove_from_start_sh() {
[ -z "${AWG_START_SH:-}" ] && return
docker exec "$CONTAINER" sh -c "
if grep -qF '${AWG_MARKER_BEGIN}' '$AWG_START_SH' 2>/dev/null; then
sed -i '/# --- WARP-MANAGER BEGIN ---/,/# --- WARP-MANAGER END ---/d' '$AWG_START_SH'
fi
" 2>/dev/null
}
# ═══════════════════════════════════════════════════════════════
# UNIFIED WRAPPERS
# ═══════════════════════════════════════════════════════════════
get_warp_status() {
if [ "$MODE" = "both" ]; then
local s3 sa
s3=$(get_warp_status_3xui); sa=$(get_warp_status_awg)
echo "3X-UI: ${s3} | AWG: ${sa}"
elif [ "$MODE" = "3xui" ]; then
get_warp_status_3xui
else
get_warp_status_awg
fi
}
get_warp_ip() {
if [ "$MODE" = "both" ]; then
local ip3 ipa
ip3=$(get_warp_ip_3xui)
awg_detect_warp_exit_ip; ipa="${AWG_WARP_EXIT_IP:-N/A}"
echo "3X-UI: ${ip3} | AWG: ${ipa}"
elif [ "$MODE" = "3xui" ]; then
get_warp_ip_3xui
else
awg_detect_warp_exit_ip; echo "${AWG_WARP_EXIT_IP:-N/A}"
fi
}
is_warp_running() {
if [ "$MODE" = "both" ]; then
is_warp_running_3xui 2>/dev/null || is_warp_running_awg 2>/dev/null
elif [ "$MODE" = "3xui" ]; then
is_warp_running_3xui
else
is_warp_running_awg
fi
}
# ═══════════════════════════════════════════════════════════════
# TELEGRAM BOT
# ═══════════════════════════════════════════════════════════════
tg_api() {
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/$1" \
-H "Content-Type: application/json" -d "$2" 2>/dev/null
}
tg_send() {
local chat_id="$1" text keyboard="${3:-}"
text=$(printf '%b' "$2")
local payload
if [ -n "$keyboard" ]; then
payload=$(jq -n --arg c "$chat_id" --arg t "$text" --argjson k "$keyboard" \
'{chat_id:$c, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}')
else
payload=$(jq -n --arg c "$chat_id" --arg t "$text" \
'{chat_id:$c, text:$t, parse_mode:"HTML"}')
fi
tg_api "sendMessage" "$payload"
}
tg_edit() {
local chat_id="$1" msg_id="$2" text keyboard="${4:-}"
text=$(printf '%b' "$3")
local payload
if [ -n "$keyboard" ]; then
payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" --argjson k "$keyboard" \
'{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}')
else
payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" \
'{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML"}')
fi
tg_api "editMessageText" "$payload"
}
tg_answer_cb() {
tg_api "answerCallbackQuery" "{\"callback_query_id\":\"$1\",\"text\":\"${2:-}\"}"
}
# ─── Bot keyboards ───────────────────────────────────────────
kbd_main_3xui() {
cat <<'JSON'
[
[{"text":"📊 Статус","callback_data":"st"},{"text":"🌐 IP","callback_data":"ip"}],
[{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}],
[{"text":"🔑 Перевыпуск","callback_data":"rk"}],
[{"text":"📋 JSON 3X-UI","callback_data":"js"}],
[{"text":"💻 Система","callback_data":"sys"}],
[{"text":"🏢 Хостинг","callback_data":"promo"}]
]
JSON
}
kbd_main_awg() {
cat <<'JSON'
[
[{"text":"📊 Статус","callback_data":"st"},{"text":"🌐 IP","callback_data":"ip"}],
[{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}],
[{"text":"🔑 Перевыпуск","callback_data":"rk"}],
[{"text":"👥 Клиенты WARP","callback_data":"cl"},{"text":"🔄 Контейнер","callback_data":"rc"}],
[{"text":"💻 Система","callback_data":"sys"}],
[{"text":"🏢 Хостинг","callback_data":"promo"}]
]
JSON
}
kbd_main_both() {
cat <<'JSON'
[
[{"text":"📊 Статус","callback_data":"st"},{"text":"🌐 IP","callback_data":"ip"}],
[{"text":"▶️ Запустить","callback_data":"on"},{"text":"⏹ Остановить","callback_data":"off"}],
[{"text":"🔑 Перевыпуск","callback_data":"rk"}],
[{"text":"📋 JSON 3X-UI","callback_data":"js"},{"text":"👥 Клиенты AWG","callback_data":"cl"}],
[{"text":"🔄 Контейнер","callback_data":"rc"}],
[{"text":"💻 Система","callback_data":"sys"}],
[{"text":"🏢 Хостинг","callback_data":"promo"}]
]
JSON
}
kbd_main() {
if [ "$MODE" = "both" ]; then kbd_main_both
elif [ "$MODE" = "3xui" ]; then kbd_main_3xui
else kbd_main_awg; fi
}
kbd_back() { echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]'; }
kbd_rekey_confirm() { echo '[[{"text":"✅ Да","callback_data":"rk_y"}],[{"text":"⬅️ Отмена","callback_data":"m"}]]'; }
# ─── Bot main menu ───────────────────────────────────────────
bot_main_menu() {
local chat_id="$1" msg_id="${2:-}"
local ws wip="" extra=""
ws=$(get_warp_status)
is_warp_running && wip=" | WARP IP: $(get_warp_ip)"
if has_3xui_mode; then extra+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi
if has_awg_mode; then extra+="\nКонтейнер: ${CONTAINER:-N/A}"; fi
local mode_label="3X-UI"
[ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
[ "$MODE" = "both" ] && mode_label="3X-UI + AWG"
local text="WARP Manager v${WARP_VERSION} (${mode_label})\nСервер: ${MY_IP:-N/A}\nСтатус: ${ws}${wip}${extra}\n\nВыберите:"
local kbd; kbd=$(kbd_main)
if [ -n "$msg_id" ]; then
tg_edit "$chat_id" "$msg_id" "$text" "$kbd"
else
tg_send "$chat_id" "$text" "$kbd"
fi
}
# ─── Bot handlers ────────────────────────────────────────────
bot_handle_callback() {
local chat_id="$1" msg_id="$2" cb_id="$3" data="$4"
[ -n "$cb_id" ] && tg_answer_cb "$cb_id" > /dev/null
case "$data" in
m) bot_main_menu "$chat_id" "$msg_id" ;;
st)
local ws wip="" extra=""
ws=$(get_warp_status)
is_warp_running && wip="\nWARP IP: $(get_warp_ip)"
if has_3xui_mode; then
extra+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"
local raw; raw=$(warp-cli --accept-tos status 2>/dev/null | head -3)
[ -n "$raw" ] && extra+="\n\n${raw}"
fi
if has_awg_mode; then
extra+="\nКонтейнер: ${CONTAINER:-N/A}\nПодсеть: ${AWG_SUBNET:-N/A}"
awg_load_clients
extra+="\nКлиентов в WARP: ${#AWG_SELECTED_IPS[@]}"
fi
tg_edit "$chat_id" "$msg_id" "📊 Статус WARP\n\nСтатус: ${ws}\nСервер: ${MY_IP:-N/A}${wip}${extra}" "$(kbd_back)" ;;
ip)
local wip="N/A"; is_warp_running && wip=$(get_warp_ip)
local t="🌐 IP адреса\n\nРеальный: ${MY_IP:-N/A}\nWARP: ${wip}"
if has_3xui_mode; then t+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;;
on)
tg_edit "$chat_id" "$msg_id" "⏳ Запуск..." ""
local result=""
if has_3xui_mode && is_warp_installed_3xui; then
warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3
if is_warp_running_3xui; then
result+="✅ 3X-UI: $(get_warp_ip_3xui)\n"; log_action "BOT 3XUI ON"
else result+="❌ 3X-UI: ошибка\n"; fi
fi
if has_awg_mode && is_warp_installed_awg; then
awg_warp_up 2>/dev/null
if is_warp_running_awg; then
awg_detect_warp_exit_ip
result+="✅ AWG: ${AWG_WARP_EXIT_IP:-?}\n"; log_action "BOT AWG ON"
else result+="❌ AWG: ошибка\n"; fi
fi
[ -z "$result" ] && result="❌ WARP не установлен."
tg_edit "$chat_id" "$msg_id" "Запуск WARP\n\n${result}" "$(kbd_back)" ;;
off)
local result=""
if has_3xui_mode && is_warp_running_3xui 2>/dev/null; then
warp-cli --accept-tos disconnect > /dev/null 2>&1
result+="⏹ 3X-UI отключён\n"; log_action "BOT 3XUI OFF"
fi
if has_awg_mode && is_warp_running_awg 2>/dev/null; then
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"
result+="⏹ AWG остановлен\n"; log_action "BOT AWG OFF"
fi
[ -z "$result" ] && result="ℹ️ Уже отключён."
tg_edit "$chat_id" "$msg_id" "Остановка WARP\n\n${result}" "$(kbd_back)" ;;
rk)
tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\nПродолжить?" "$(kbd_rekey_confirm)" ;;
rk_y)
tg_edit "$chat_id" "$msg_id" "⏳ Перевыпуск..." ""
local result=""
if has_3xui_mode; then
warp-cli --accept-tos disconnect > /dev/null 2>&1
warp-cli --accept-tos registration delete > /dev/null 2>&1
warp-cli --accept-tos registration new > /dev/null 2>&1
warp-cli --accept-tos mode proxy > /dev/null 2>&1
warp-cli --accept-tos proxy port "${SOCKS_PORT}" > /dev/null 2>&1
warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3
if is_warp_running_3xui; then
local w; w=$(get_warp_ip_3xui)
result+="✅ 3X-UI: ${w}\n"; log_action "BOT 3XUI REKEY: ${w}"
else result+="⚠️ 3X-UI: не подтверждено\n"; fi
fi
if has_awg_mode; then
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"
rm -f "$WGCF_ACCOUNT"
(cd /root && yes | ./wgcf register 2>/dev/null && yes | ./wgcf generate 2>/dev/null)
if [ -f "$WGCF_PROFILE" ]; then
local ep; ep=$(awg_resolve_endpoint 2>/dev/null)
if [ -n "$ep" ]; then
awg_build_warp_conf "$ep"
awg_warp_up 2>/dev/null
awg_load_clients; awg_apply_rules; awg_patch_start_sh
awg_detect_warp_exit_ip
result+="✅ AWG: ${AWG_WARP_EXIT_IP:-?}\n"; log_action "BOT AWG REKEY"
else result+="❌ AWG: ошибка endpoint\n"; fi
else result+="❌ AWG: ошибка профиля\n"; fi
fi
tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\n${result}" "$(kbd_back)" ;;
js)
if has_3xui_mode; then
local t="📋 Конфигурация для 3X-UI\n\nOutbound:\n{\n \"tag\": \"warp\",\n \"protocol\": \"socks\",\n \"settings\": {\n \"servers\": [{\"address\": \"127.0.0.1\", \"port\": ${SOCKS_PORT}}]\n }\n}\n\nRouting:\n{\"outboundTag\": \"warp\", \"domain\": [\"geosite:openai\",\"geosite:netflix\"]}"
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)"
else
tg_edit "$chat_id" "$msg_id" "ℹ️ JSON не требуется для AmneziaWG." "$(kbd_back)"
fi ;;
cl)
awg_get_client_ips; awg_parse_clients_table; awg_load_clients
local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
local t="👥 Клиенты WARP (${#AWG_SELECTED_IPS[@]} из ${#AWG_CLIENT_IPS[@]})\n\n"
local kbd=""
if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then
t+="Нет клиентов в конфиге VPN."
kbd='[[{"text":"⬅️ Меню","callback_data":"m"}]]'
else
kbd="["
local first=1
for i in "${!AWG_CLIENT_IPS[@]}"; do
local ip="${AWG_CLIENT_IPS[$i]}"
local name; name=$(awg_get_name "$ip")
local label="${ip}"
[ -n "$name" ] && label="$name"
if [[ "$warp_set" == *" $ip "* ]]; then
t+="✅ ${ip}"
[ -n "$name" ] && t+=" ($name)"
label="✅ ${label}"
else
t+="☐ ${ip}"
[ -n "$name" ] && t+=" ($name)"
label="☐ ${label}"
fi
t+="\n"
local safe_label; safe_label=$(echo "$label" | sed 's/["\\]/\\&/g; s/\n//g')
[ "$first" -eq 0 ] && kbd+=","
kbd+="[{\"text\":\"${safe_label}\",\"callback_data\":\"ct:${i}\"}]"
first=0
done
kbd+=",[{\"text\":\"✅ Все\",\"callback_data\":\"ct:all\"},{\"text\":\"☐ Никого\",\"callback_data\":\"ct:none\"}]"
kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]"
fi
tg_edit "$chat_id" "$msg_id" "$t" "$kbd" ;;
ct:*)
local idx="${data#ct:}"
awg_get_client_ips; awg_load_clients
if [ "$idx" = "all" ]; then
AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}")
elif [ "$idx" = "none" ]; then
AWG_SELECTED_IPS=()
else
[[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; }
local ip="${AWG_CLIENT_IPS[$idx]}"
local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
if [[ "$warp_set" == *" $ip "* ]]; then
local -a tmp=()
for eip in "${AWG_SELECTED_IPS[@]}"; do
[ "$eip" != "$ip" ] && tmp+=("$eip")
done
AWG_SELECTED_IPS=("${tmp[@]+"${tmp[@]}"}")
else
AWG_SELECTED_IPS+=("$ip")
fi
fi
awg_save_clients; awg_apply_rules; awg_patch_start_sh
tg_edit "$chat_id" "$msg_id" "⏳ Применяю и перезапускаю контейнер..." ""
docker restart "$CONTAINER" >/dev/null 2>&1
local _a=0; while [ "$_a" -lt 15 ]; do docker exec "$CONTAINER" sh -c "true" 2>/dev/null && break; sleep 1; ((_a++)); done
log_action "BOT AWG TOGGLE: ${#AWG_SELECTED_IPS[@]}, container restarted"
bot_handle_callback "$chat_id" "$msg_id" "" "cl" ;;
rc)
if ! has_awg_mode; then
tg_edit "$chat_id" "$msg_id" "ℹ️ Только для AmneziaWG." "$(kbd_back)"; return
fi
tg_edit "$chat_id" "$msg_id" "🔄 Перезапуск контейнера..." ""
docker restart "$CONTAINER" >/dev/null 2>&1; sleep 5
if docker exec "$CONTAINER" sh -c "true" 2>/dev/null; then
tg_edit "$chat_id" "$msg_id" "✅ Контейнер перезапущен." "$(kbd_back)"; log_action "BOT AWG RESTART"
else
tg_edit "$chat_id" "$msg_id" "⚠️ Контейнер не отвечает." "$(kbd_back)"
fi ;;
sys)
local s; s=$(get_system_stats)
local ws; ws=$(get_warp_status)
s+="\nWARP: ${ws}"
s+="\nРежим: ${MODE}"
if has_3xui_mode; then s+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"; fi
if has_awg_mode; then s+="\nКонтейнер: ${CONTAINER:-N/A}"; fi
tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;;
promo)
local pt="🏢 Хостинг со скидкой до -60%\n\n🌍 РФ и Европа\n👉 https://vk.cc/ct29NQ\n\nOFF60 — 60% скидка\nantenka20 — +20% к балансу (3мес)\nantenka6 — +15% к балансу (6мес)\nantenka12 — +5% к балансу (12мес)\n\n🇧🇾 Беларусь\n👉 https://vk.cc/cUxAhj\nOFF60 — 60% скидка"
tg_edit "$chat_id" "$msg_id" "$pt" "$(kbd_back)" ;;
esac
}
# ─── Bot daemon ───────────────────────────────────────────────
bot_daemon() {
log_action "Bot daemon started (PID $$)"; echo $$ > "$BOT_PID_FILE"
source "$WARP_CONF"
[ -z "$BOT_TOKEN" ] && { log_action "BOT ERROR: no token"; exit 1; }
get_my_ip
if has_awg_mode && [ -n "$CONTAINER" ]; then awg_load_container_data 2>/dev/null; fi
local offset=0
while true; do
bot_poll_cycle
done
}
bot_poll_cycle() {
local response
response=$(curl -s --max-time 35 \
"https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=${offset}&timeout=30" 2>/dev/null)
[ -z "$response" ] && sleep 2 && return
local header
header=$(jq -r '[.ok // "false", (.result | length)] | @tsv' <<< "$response" 2>/dev/null)
local ok cnt
IFS=$'\t' read -r ok cnt <<< "$header"
[ "$ok" != "true" ] && sleep 5 && return
[ "$cnt" -eq 0 ] 2>/dev/null && return
local updates_tsv
updates_tsv=$(jq -r '
.result[] | [
.update_id,
(.callback_query.data // ""),
(.callback_query.id // ""),
(.callback_query.message.chat.id // ""),
(.callback_query.message.message_id // ""),
(.message.chat.id // ""),
(.message.text // "")
] | @tsv
' <<< "$response" 2>/dev/null)
while IFS=$'\t' read -r uid cbd cbi cci cmi mci mtx; do
[ -z "$uid" ] && continue
offset=$((uid + 1))
if [ -n "$cbd" ]; then
if ! is_bot_admin "$cci"; then tg_answer_cb "$cbi" "Нет доступа" > /dev/null; continue; fi
bot_handle_callback "$cci" "$cmi" "$cbi" "$cbd"
elif [ -n "$mci" ] && [ -n "$mtx" ]; then
if ! is_bot_admin "$mci"; then tg_send "$mci" "⛔ Нет доступа.\nChat ID: $mci" "" > /dev/null; continue; fi
if [[ "$mtx" == "/start" || "$mtx" == "/menu" ]]; then
bot_main_menu "$mci"
else
tg_send "$mci" "Используйте /start или /menu" "" > /dev/null
fi
fi
done <<< "$updates_tsv"
}
# ─── Bot menu (SSH) ──────────────────────────────────────────
start_bot() {
source "$WARP_CONF"
[ -z "$BOT_TOKEN" ] && { echo -e "${RED}Задайте BOT_TOKEN!${NC}"; return; }
[ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE")" 2>/dev/null && { echo -e "${YELLOW}Уже запущен.${NC}"; return; }
cat > /etc/systemd/system/warp-bot.service < /dev/null 2>&1; systemctl start warp-bot; sleep 1
if systemctl is-active warp-bot &>/dev/null; then
echo -e "${GREEN}[OK] Бот запущен.${NC}"; log_action "Bot started"
else
echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}"
fi
}
stop_bot() {
systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null; rm -f "$BOT_PID_FILE"
echo -e "${GREEN}[OK] Бот остановлен.${NC}"; log_action "Bot stopped"
}
is_bot_admin() {
local id="$1"
[ -z "${BOT_CHAT_ID:-}" ] && return 0
local admin
for admin in $BOT_CHAT_ID; do
[ "$admin" = "$id" ] && return 0
done
return 1
}
bot_add_admin() {
local new_id="$1"
[ -z "$new_id" ] && return 1
if ! is_bot_admin "$new_id"; then
if [ -n "${BOT_CHAT_ID:-}" ]; then
BOT_CHAT_ID="${BOT_CHAT_ID} ${new_id}"
else
BOT_CHAT_ID="$new_id"
fi
save_config_val "BOT_CHAT_ID" "$BOT_CHAT_ID"
fi
return 0
}
bot_remove_admin() {
local rm_id="$1"
local -a new_list=()
for admin in $BOT_CHAT_ID; do
[ "$admin" != "$rm_id" ] && new_list+=("$admin")
done
BOT_CHAT_ID="${new_list[*]+"${new_list[*]}"}"
save_config_val "BOT_CHAT_ID" "$BOT_CHAT_ID"
}
bot_auto_chatid() {
systemctl stop warp-bot 2>/dev/null
sleep 1
local flush_resp last_uid
flush_resp=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=-1&limit=1")
last_uid=$(echo "$flush_resp" | jq -r '.result[-1].update_id // empty' 2>/dev/null)
if [ -n "$last_uid" ]; then
curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=$((last_uid + 1))&limit=1" >/dev/null 2>&1
fi
echo -e "${YELLOW} Отправьте боту 3 любых сообщения в Telegram.${NC}"
echo -e "${YELLOW} Затем нажмите Enter.${NC}"
read -p " > " _
local resp; resp=$(curl -s --max-time 10 "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=10")
local c; c=$(echo "$resp" | jq -r '[.result[].message.chat.id // empty] | map(select(. != "")) | unique | first // empty' 2>/dev/null)
if [ -n "$c" ] && [[ "$c" =~ ^[0-9-]+$ ]]; then
bot_add_admin "$c"
echo -e " ${GREEN}✓ Chat ID: $c (добавлен как админ)${NC}"
return 0
else
echo -e " ${RED}✗ Не удалось определить Chat ID.${NC}"
echo -e ""
echo -e " ${WHITE}Получите Chat ID вручную:${NC}"
echo -e " ${CYAN}1.${NC} Откройте ${GREEN}@userinfobot${NC} в Telegram"
echo -e " ${CYAN}2.${NC} Нажмите /start"
echo -e " ${CYAN}3.${NC} Скопируйте число ${WHITE}Id: XXXXXXXX${NC}"
echo -e " ${CYAN}4.${NC} Впишите через меню бота (п. Добавить админа)"
return 1
fi
}
bot_wizard() {
clear
echo -e "\n${CYAN}━━━ Настройка Telegram-бота (быстрый старт) ━━━${NC}\n"
echo -e "${YELLOW}Шаг 1/3.${NC} Введите токен бота"
echo -e "${DIM} Получить: @BotFather → /newbot → скопировать токен${NC}\n"
read -p " Токен: " t
if [ -z "$t" ]; then echo -e " ${RED}Отменено.${NC}"; read -p "Enter..."; return; fi
save_config_val "BOT_TOKEN" "$t"; BOT_TOKEN="$t"
echo -e " ${GREEN}✓ Токен сохранён${NC}\n"
echo -e "${YELLOW}Шаг 2/3.${NC} Определение Chat ID (авто)"
if ! bot_auto_chatid; then
echo -e "\n ${YELLOW}Chat ID не определён — бот будет доступен всем.${NC}"
echo -e " ${WHITE}Можно задать вручную позже через меню бота (п.3).${NC}"
fi
echo ""
echo -e "${YELLOW}Шаг 3/3.${NC} Запуск бота"
start_bot
echo ""
if systemctl is-active warp-bot &>/dev/null; then
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} ✅ Бот настроен и запущен!${NC}"
echo -e "${GREEN} Откройте бота в Telegram и нажмите /start${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
else
echo -e "${YELLOW} ⚠ Бот может не запуститься. Проверьте:${NC}"
echo -e " ${WHITE}journalctl -u warp-bot --no-pager -n 10${NC}"
fi
read -p " Enter..."
}
bot_show_admins() {
echo -e "\n ${CYAN}Администраторы бота:${NC}"
if [ -z "${BOT_CHAT_ID:-}" ]; then
echo -e " ${YELLOW}Нет (бот доступен всем)${NC}"
else
local i=1
for admin in $BOT_CHAT_ID; do
echo -e " ${GREEN}$i)${NC} $admin"
((i++))
done
fi
echo ""
}
bot_menu() {
source "$WARP_CONF" 2>/dev/null
if [ -z "${BOT_TOKEN:-}" ]; then
bot_wizard
return
fi
while true; do
clear; source "$WARP_CONF" 2>/dev/null
local bs="${RED}Выкл${NC}"
systemctl is-active warp-bot &>/dev/null && bs="${GREEN}Вкл${NC}"
local td="***${BOT_TOKEN: -6}"
local admin_count=0
[ -n "${BOT_CHAT_ID:-}" ] && admin_count=$(echo "$BOT_CHAT_ID" | wc -w)
echo -e "\n${CYAN}━━━ Telegram Bot ━━━${NC}\n"
echo -e " Статус: $bs"
echo -e " Токен: ${YELLOW}$td${NC}"
echo -e " Админов: ${YELLOW}${admin_count}${NC}\n"
echo -e " ${CYAN}── Управление ──${NC}"
echo -e " 1) ${GREEN}Запустить${NC}"
echo -e " 2) ${RED}Остановить${NC}"
echo -e " 3) Изменить токен"
echo -e "\n ${CYAN}── Администраторы ──${NC}"
echo -e " 4) Показать админов"
echo -e " 5) Добавить админа (авто — по сообщению)"
echo -e " 6) Добавить админа (вручную — Chat ID)"
echo -e " 7) Убрать админа"
echo -e "\n 0) Назад"
echo ""
read -p " Выбор: " ch
case $ch in
1) start_bot; read -p " Enter..." ;;
2) stop_bot; read -p " Enter..." ;;
3) echo " Токен:"; read -p " > " t
[ -n "$t" ] && save_config_val "BOT_TOKEN" "$t" && BOT_TOKEN="$t" && echo -e " ${GREEN}OK${NC}"
read -p " Enter..." ;;
4) bot_show_admins; read -p " Enter..." ;;
5) bot_auto_chatid; read -p " Enter..." ;;
6) echo -e "\n ${WHITE}Как узнать Chat ID: откройте ${GREEN}@userinfobot${WHITE} → /start → скопируйте Id${NC}"
echo " Chat ID:"; read -p " > " c
if [ -n "$c" ] && [[ "$c" =~ ^[0-9-]+$ ]]; then
bot_add_admin "$c"
echo -e " ${GREEN}✓ Админ $c добавлен${NC}"
else
echo -e " ${RED}Некорректный ID${NC}"
fi
read -p " Enter..." ;;
7) if [ -z "${BOT_CHAT_ID:-}" ]; then
echo -e "\n ${YELLOW}Нет админов.${NC}"; read -p " Enter..."; continue
fi
bot_show_admins
echo " Номер для удаления (0 — отмена):"
read -p " > " num
if [ "$num" != "0" ] && [[ "$num" =~ ^[0-9]+$ ]]; then
local -a admins=($BOT_CHAT_ID)
if (( num >= 1 && num <= ${#admins[@]} )); then
local rm_id="${admins[$((num-1))]}"
bot_remove_admin "$rm_id"
echo -e " ${GREEN}✓ Админ $rm_id удалён${NC}"
fi
fi
read -p " Enter..." ;;
0) return ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# PROMO / INFO
# ═══════════════════════════════════════════════════════════════
show_promo() {
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo -e "\n${CYAN}🌍 РФ И ЕВРОПА${NC}\n${WHITE} >>> https://vk.cc/ct29NQ${NC}"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка" "antenka20" "+20% (3мес)" "antenka6" "+15% (6мес)" "antenka12" "+5% (12мес)"
echo -e "\n${CYAN}🇧🇾 БЕЛАРУСЬ${NC}\n${WHITE} >>> https://vk.cc/cUxAhj${NC}"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка"
echo -e "\n${YELLOW}QR-код...${NC}"; sleep 2
echo -e "\n${WHITE}"; command -v qrencode &>/dev/null && qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ" || echo "Ссылки выше."; echo -e "${NC}"
read -p "Enter..."
}
show_info() {
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ 📚 WARP Manager v${WARP_VERSION} ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
if has_3xui_mode; then
echo -e "${CYAN}═══ 3X-UI ═══${NC}\n"
echo -e "${WHITE} Клиент → 3X-UI (Xray) → SOCKS5 (WARP) → Cloudflare → Интернет${NC}\n"
echo -e "${GREEN} 1.${NC} cloudflare-warp установлен нативно"
echo -e "${GREEN} 2.${NC} SOCKS5-прокси на 127.0.0.1:${SOCKS_PORT}"
echo -e "${GREEN} 3.${NC} В 3X-UI: outbound SOCKS → warp"
echo -e "${GREEN} 4.${NC} Маршрутизация по доменам в Xray"
echo ""
fi
if has_awg_mode; then
echo -e "${CYAN}═══ AmneziaWG ═══${NC}\n"
echo -e "${WHITE} Клиент → AmneziaWG Docker → warp WG → Cloudflare → Интернет${NC}\n"
echo -e "${GREEN} 1.${NC} wgcf генерирует WireGuard-профиль WARP"
echo -e "${GREEN} 2.${NC} WG-интерфейс warp внутри Docker-контейнера"
echo -e "${GREEN} 3.${NC} Маршрутизация per-client через ip rule"
echo -e "${GREEN} 4.${NC} Персистентность через start.sh контейнера"
fi
echo -e "\n${GREEN} ✓${NC} Разблокировка ChatGPT, Netflix, Disney+, Spotify"
echo -e "${GREEN} ✓${NC} Чистый IP от Cloudflare"
echo -e "${GREEN} ✓${NC} Telegram-бот"
echo -e "${GREEN} ✓${NC} Бесплатно (Cloudflare WARP)"
echo ""; read -p "Enter..."
}
# ═══════════════════════════════════════════════════════════════
# FULL UNINSTALL
# ═══════════════════════════════════════════════════════════════
full_uninstall() {
clear
echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ⚠ ПОЛНОЕ УДАЛЕНИЕ WARP MANAGER ⚠ ║${NC}"
echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}\n"
echo -e "${WHITE}Режим: ${CYAN}${MODE}${NC}\n"
read -p "$(echo -e "${RED}Удалить полностью? (y/n): ${NC}")" c1
[[ "$c1" != "y" ]] && return
if has_3xui_mode; then uninstall_3xui; fi
if has_awg_mode; then uninstall_awg; fi
rm -rf "$WARP_DIR" "$WARP_LOG"
echo -e " ${GREEN}✓${NC} Конфигурация и логи"
rm -f /usr/local/bin/gowarp
echo -e " ${GREEN}✓${NC} Команда gowarp"
echo -e "\n${GREEN}══════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} WARP Manager полностью удалён.${NC}"
has_3xui_mode && echo -e "${WHITE} Уберите outbound \"warp\" из 3X-UI!${NC}"
echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}"
log_action "UNINSTALL: full removal ($MODE)"
read -p "Enter..."
exit 0
}
# ═══════════════════════════════════════════════════════════════
# MAIN MENU
# ═══════════════════════════════════════════════════════════════
show_menu() {
while true; do
clear
local st sc
st=$(get_warp_status)
sc="$RED"; [[ "$st" == *"Подключён"* ]] && sc="$GREEN"; [[ "$st" == *"Отключён"* && "$st" != *"Подключён"* ]] && sc="$YELLOW"
local mode_label="3X-UI"
[ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
[ "$MODE" = "both" ] && mode_label="3X-UI + AmneziaWG"
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗"
echo -e "║ anten-ka · WARP Manager v${WARP_VERSION} ║"
echo -e "║ YouTube: https://www.youtube.com/@antenkaru ║"
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
echo -e " ${WHITE}IP сервера:${NC} ${GREEN}${MY_IP}${NC} ${WHITE}Режим:${NC} ${CYAN}${mode_label}${NC}"
echo -e " ${WHITE}WARP:${NC} ${sc}${st}${NC}"
if has_3xui_mode && is_warp_running_3xui 2>/dev/null; then
echo -e " ${WHITE}SOCKS5:${NC} ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}"
fi
if has_awg_mode && [ -n "${CONTAINER:-}" ]; then
echo -e " ${WHITE}Контейнер:${NC} ${CYAN}${CONTAINER}${NC}"
fi
echo -e "\n${CYAN}── WARP-ключ ──────────────────────────────────────────${NC}"
echo -e " 1) ${GREEN}Установить WARP${NC}"
echo -e " 2) ${CYAN}Запустить WARP${NC}"
echo -e " 3) ${YELLOW}Остановить WARP${NC}"
echo -e " 4) 📊 Статус"
echo -e " 5) 🔑 ${YELLOW}Перевыпуск ключа${NC}"
if has_3xui_mode; then
echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}"
echo -e " 6) 📋 ${CYAN}Настройки SOCKS5 / JSON / Инструкция${NC}"
fi
if has_awg_mode; then
echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}"
echo -e " 7) 👥 ${GREEN}Управление клиентами WARP${NC}"
echo -e " 8) 🔍 ${WHITE}Проверить контейнеры${NC}"
fi
echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}"
echo -e " 9) 🤖 ${CYAN}Настройка и управление ботом${NC}"
echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}"
echo -e " 10) ${YELLOW}PROMO${NC}"
echo -e " 11) ${MAGENTA}📚 Инструкция${NC}"
echo -e " 12) ${RED}⚠ Полное удаление${NC}"
echo -e " 0) Выход"
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
read -p " Выбор: " ch
case $ch in
1) if has_3xui_mode; then install_warp_3xui; fi
if has_awg_mode; then install_warp_awg; fi ;;
2) if has_3xui_mode; then start_warp_3xui; fi
if has_awg_mode; then start_warp_awg; fi ;;
3) if has_3xui_mode; then stop_warp_3xui; fi
if has_awg_mode; then stop_warp_awg; fi ;;
4) if has_3xui_mode; then show_status_3xui; fi
if has_awg_mode; then show_status_awg; fi ;;
5) if has_3xui_mode; then rekey_warp_3xui; fi
if has_awg_mode; then rekey_warp_awg; fi ;;
6) has_3xui_mode && show_3xui_menu ;;
7) has_awg_mode && awg_toggle_clients_ssh ;;
8) has_awg_mode && awg_check_containers ;;
9) bot_menu ;;
10) show_promo ;;
11) show_info ;;
12) full_uninstall ;;
0) exit 0 ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# STARTUP
# ═══════════════════════════════════════════════════════════════
run_startup() {
local total=6 s=0
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ WARP Manager v${WARP_VERSION} — Загрузка ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка root..." "$s" "$total"
check_root
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} root OK \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Лицензия..." "$s" "$total"
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Лицензия активна \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Зависимости..." "$s" "$total"
check_deps
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Зависимости OK \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Установка gowarp..." "$s" "$total"
local upgrade_msg="установлена"
if [ -f "/usr/local/bin/gowarp" ] && [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gowarp" ]; then
upgrade_msg="обновлена (v${WARP_VERSION})"
fi
if [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gowarp" ]; then
cp -f "$0" "/usr/local/bin/gowarp"; chmod +x "/usr/local/bin/gowarp"
fi
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Команда gowarp %s \n" "$s" "$total" "$upgrade_msg"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение IP..." "$s" "$total"
get_my_ip
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} IP: %-25s \n" "$s" "$total" "$MY_IP"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение режима..." "$s" "$total"
detect_mode
local mode_label="3X-UI"
[ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
[ "$MODE" = "both" ] && mode_label="3X-UI + AmneziaWG"
has_awg_mode && ((total++))
has_3xui_mode && ((total++))
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Режим: %-25s \n" "$s" "$total" "$mode_label"
if has_awg_mode; then
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Docker контейнер..." "$s" "$total"
if awg_pick_container 2>/dev/null; then
awg_load_container_data 2>/dev/null
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Контейнер: %-20s \n" "$s" "$total" "$CONTAINER"
else
printf "\r ${CYAN}[%d/%d]${NC} ${YELLOW}⚠${NC} Контейнер не найден \n" "$s" "$total"
fi
fi
if has_3xui_mode; then
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка WARP (3X-UI)..." "$s" "$total"
local ws; ws=$(get_warp_status_3xui)
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} 3X-UI WARP: %-20s \n" "$s" "$total" "$ws"
fi
echo ""
local w=40 bar=""
for ((i=0; i