/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Контейнер: ${CONTAINER:-N/A}"
[ "$MODE" = "3xui" ] && extra="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"
local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
local text="WARP Manager v${WARP_VERSION} (${mode_label})\nСервер: ${MY_IP:-N/A}\nСтатус: ${ws}${wip}${extra}\n\nВыберите:"
local kbd; kbd=$(kbd_main)
[ -n "$msg_id" ] && tg_edit "$chat_id" "$msg_id" "$text" "$kbd" || tg_send "$chat_id" "$text" "$kbd"
}
# ─── Bot handlers ────────────────────────────────────────────
bot_handle_callback() {
local chat_id="$1" msg_id="$2" cb_id="$3" data="$4"
[ -n "$cb_id" ] && tg_answer_cb "$cb_id" > /dev/null
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 [ "$MODE" = "3xui" ]; then
extra="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"
local raw; raw=$(warp-cli --accept-tos status 2>/dev/null | head -3)
extra+="\n\n${raw}"
else
extra="\nКонтейнер: ${CONTAINER}\nПодсеть: ${AWG_SUBNET:-N/A}"
awg_load_clients
extra+="\nКлиентов в WARP: ${#AWG_SELECTED_IPS[@]}"
fi
tg_edit "$chat_id" "$msg_id" "📊 Статус WARP\n\nСтатус: ${ws}\nСервер: ${MY_IP:-N/A}${wip}${extra}" "$(kbd_back)" ;;
ip)
local wip="N/A"; is_warp_running && wip=$(get_warp_ip)
local t="🌐 IP адреса\n\nРеальный: ${MY_IP:-N/A}\nWARP: ${wip}"
[ "$MODE" = "3xui" ] && t+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;;
on)
if [ "$MODE" = "3xui" ]; then
is_warp_installed_3xui || { tg_edit "$chat_id" "$msg_id" "❌ Не установлен." "$(kbd_back)"; return; }
is_warp_running_3xui && { tg_edit "$chat_id" "$msg_id" "✅ Уже подключён." "$(kbd_back)"; return; }
tg_edit "$chat_id" "$msg_id" "⏳ Подключение..." ""
warp-cli --accept-tos connect > /dev/null 2>&1; sleep 3
is_warp_running_3xui && { local w; w=$(get_warp_ip_3xui); tg_edit "$chat_id" "$msg_id" "✅ Подключён\nWARP IP: ${w}" "$(kbd_back)"; log_action "BOT 3XUI ON"; } \
|| tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"
else
is_warp_installed_awg || { tg_edit "$chat_id" "$msg_id" "❌ Не установлен." "$(kbd_back)"; return; }
is_warp_running_awg && { tg_edit "$chat_id" "$msg_id" "✅ Уже работает." "$(kbd_back)"; return; }
tg_edit "$chat_id" "$msg_id" "⏳ Запуск..." ""
awg_warp_up 2>/dev/null
if is_warp_running_awg; then
awg_detect_warp_exit_ip
tg_edit "$chat_id" "$msg_id" "✅ WARP запущен\nWARP IP: ${AWG_WARP_EXIT_IP:-?}" "$(kbd_back)"; log_action "BOT AWG ON"
else tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; fi
fi ;;
off)
if [ "$MODE" = "3xui" ]; then
is_warp_running_3xui || { tg_edit "$chat_id" "$msg_id" "ℹ️ Уже отключён." "$(kbd_back)"; return; }
warp-cli --accept-tos disconnect > /dev/null 2>&1
tg_edit "$chat_id" "$msg_id" "⏹ WARP отключён." "$(kbd_back)"; log_action "BOT 3XUI OFF"
else
is_warp_running_awg || { tg_edit "$chat_id" "$msg_id" "ℹ️ Уже остановлен." "$(kbd_back)"; return; }
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"
tg_edit "$chat_id" "$msg_id" "⏹ WARP остановлен." "$(kbd_back)"; log_action "BOT AWG OFF"
fi ;;
rk)
tg_edit "$chat_id" "$msg_id" "🔑 Перевыпуск ключа\n\nПродолжить?" "$(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" "✅ Ключ перевыпущен\nWARP IP: ${w}" "$(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" "✅ Ключ перевыпущен\nWARP IP: ${AWG_WARP_EXIT_IP:-?}" "$(kbd_back)"; log_action "BOT AWG REKEY"
else tg_edit "$chat_id" "$msg_id" "❌ Ошибка endpoint." "$(kbd_back)"; fi
else tg_edit "$chat_id" "$msg_id" "❌ Ошибка генерации профиля." "$(kbd_back)"; fi
fi ;;
js)
if [ "$MODE" = "3xui" ]; then
local t="📋 Конфигурация для 3X-UI\n\nOutbound:\n{\n \"tag\": \"warp\",\n \"protocol\": \"socks\",\n \"settings\": {\n \"servers\": [{\"address\": \"127.0.0.1\", \"port\": ${SOCKS_PORT}}]\n }\n}\n\nRouting:\n{\"outboundTag\": \"warp\", \"domain\": [\"geosite:openai\",\"geosite:netflix\"]}"
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)"
else
tg_edit "$chat_id" "$msg_id" "ℹ️ JSON не требуется для AmneziaWG." "$(kbd_back)"
fi ;;
cl)
awg_load_clients; awg_parse_clients_table
local t="👥 Клиенты в WARP (${#AWG_SELECTED_IPS[@]})\n\n"
if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then
t+="Нет клиентов."
else
for ip in "${AWG_SELECTED_IPS[@]}"; do
local name; name=$(awg_get_name "$ip")
t+="● ${ip}"
[ -n "$name" ] && t+=" ($name)"
t+="\n"
done
fi
tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_back)" ;;
cla)
awg_get_client_ips; awg_parse_clients_table; awg_load_clients
local warp_set=" ${AWG_SELECTED_IPS[*]+"${AWG_SELECTED_IPS[*]}"} "
local kbd="["
local first=1
for i in "${!AWG_CLIENT_IPS[@]}"; do
local ip="${AWG_CLIENT_IPS[$i]}"
local name; name=$(awg_get_name "$ip")
local label="${ip}"
[ -n "$name" ] && label="$name"
local in_warp=0
[[ "$warp_set" == *" $ip "* ]] && in_warp=1
[ "$in_warp" -eq 1 ] && label="✅ $label" || label="➕ $label"
[ "$first" -eq 0 ] && kbd+=","
kbd+="[{\"text\":\"${label}\",\"callback_data\":\"ca:${i}\"}]"
first=0
done
kbd+=",[{\"text\":\"✅ Все в WARP\",\"callback_data\":\"ca:all\"}]"
kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]"
tg_edit "$chat_id" "$msg_id" "➕ Добавить в WARP:" "$kbd" ;;
ca:*)
local idx="${data#ca:}"
awg_get_client_ips; awg_load_clients
if [ "$idx" = "all" ]; then
AWG_SELECTED_IPS=("${AWG_CLIENT_IPS[@]}")
else
[[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_CLIENT_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; }
local ip="${AWG_CLIENT_IPS[$idx]}"
local found=0
for e in "${AWG_SELECTED_IPS[@]}"; do [ "$e" = "$ip" ] && found=1 && break; done
[ "$found" -eq 0 ] && AWG_SELECTED_IPS+=("$ip")
fi
awg_save_clients; awg_apply_rules; awg_patch_start_sh
tg_edit "$chat_id" "$msg_id" "✅ Обновлено. Клиентов: ${#AWG_SELECTED_IPS[@]}" "$(kbd_back)"
log_action "BOT AWG ADD: ${#AWG_SELECTED_IPS[@]}" ;;
clr)
awg_load_clients; awg_parse_clients_table
if [ ${#AWG_SELECTED_IPS[@]} -eq 0 ]; then
tg_edit "$chat_id" "$msg_id" "ℹ️ Нет клиентов в WARP." "$(kbd_back)"; return
fi
local kbd="["
local first=1
for i in "${!AWG_SELECTED_IPS[@]}"; do
local ip="${AWG_SELECTED_IPS[$i]}"
local name; name=$(awg_get_name "$ip")
local label="${ip}"
[ -n "$name" ] && label="$name"
[ "$first" -eq 0 ] && kbd+=","
kbd+="[{\"text\":\"❌ ${label}\",\"callback_data\":\"cr:${i}\"}]"
first=0
done
kbd+=",[{\"text\":\"❌ Убрать всех\",\"callback_data\":\"cr:all\"}]"
kbd+=",[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]"
tg_edit "$chat_id" "$msg_id" "➖ Убрать из WARP:" "$kbd" ;;
cr:*)
local idx="${data#cr:}"
awg_load_clients
if [ "$idx" = "all" ]; then
AWG_SELECTED_IPS=()
else
[[ "$idx" =~ ^[0-9]+$ ]] && (( idx < ${#AWG_SELECTED_IPS[@]} )) || { tg_edit "$chat_id" "$msg_id" "❌ Ошибка." "$(kbd_back)"; return; }
local -a new=()
for i in "${!AWG_SELECTED_IPS[@]}"; do
[ "$i" -ne "$idx" ] && new+=("${AWG_SELECTED_IPS[$i]}")
done
AWG_SELECTED_IPS=("${new[@]+"${new[@]}"}")
fi
awg_save_clients; awg_apply_rules; awg_patch_start_sh
tg_edit "$chat_id" "$msg_id" "✅ Обновлено. Клиентов: ${#AWG_SELECTED_IPS[@]}" "$(kbd_back)"
log_action "BOT AWG REMOVE: ${#AWG_SELECTED_IPS[@]}" ;;
rc)
if [ "$MODE" != "amnezia" ]; then
tg_edit "$chat_id" "$msg_id" "ℹ️ Только для AmneziaWG." "$(kbd_back)"; return
fi
tg_edit "$chat_id" "$msg_id" "🔄 Перезапуск контейнера..." ""
docker restart "$CONTAINER" >/dev/null 2>&1; sleep 5
if docker exec "$CONTAINER" sh -c "true" 2>/dev/null; then
tg_edit "$chat_id" "$msg_id" "✅ Контейнер перезапущен." "$(kbd_back)"; log_action "BOT AWG RESTART"
else
tg_edit "$chat_id" "$msg_id" "⚠️ Контейнер не отвечает." "$(kbd_back)"
fi ;;
sys)
local s; s=$(get_system_stats)
local ws; ws=$(get_warp_status)
s+="\nWARP: ${ws}"
s+="\nРежим: ${MODE}"
[ "$MODE" = "3xui" ] && s+="\nSOCKS5: 127.0.0.1:${SOCKS_PORT}"
[ "$MODE" = "amnezia" ] && s+="\nКонтейнер: ${CONTAINER:-N/A}"
tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;;
promo)
local pt="🏢 Хостинг\n\n🌍 РФ и Европа\n👉 https://vk.cc/ct29NQ\n\nOFF60 — 60%\nantenka20 — +20% (3мес)\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
[ "$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 /dev/null; continue; }
bot_handle_callback "$cci" "$cmi" "$cbi" "$cbd"
else
local mci mtx
mci=$(echo "$upd" | jq -r '.message.chat.id // empty')
mtx=$(echo "$upd" | jq -r '.message.text // empty')
if [ -n "$mci" ] && [ -n "$mtx" ]; then
[ -n "$BOT_CHAT_ID" ] && [ "$mci" != "$BOT_CHAT_ID" ] && { tg_send "$mci" "⛔ Нет доступа.\nChat ID: $mci" "" > /dev/null; continue; }
[[ "$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 < /dev/null 2>&1; systemctl start warp-bot; sleep 1
systemctl is-active warp-bot &>/dev/null && echo -e "${GREEN}[OK] Бот запущен.${NC}" && log_action "Bot started" \
|| echo -e "${RED}[ERROR] journalctl -u warp-bot${NC}"
}
stop_bot() {
systemctl stop warp-bot 2>/dev/null; systemctl disable warp-bot 2>/dev/null; rm -f "$BOT_PID_FILE"
echo -e "${GREEN}[OK] Бот остановлен.${NC}"; log_action "Bot stopped"
}
bot_menu() {
while true; do
clear; source "$WARP_CONF" 2>/dev/null
local bs="${RED}Выкл${NC}"
[ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE" 2>/dev/null)" 2>/dev/null && bs="${GREEN}Вкл ($(cat "$BOT_PID_FILE"))${NC}"
local td="нет"; [ -n "${BOT_TOKEN:-}" ] && td="***${BOT_TOKEN: -6}"
echo -e "${CYAN}━━━ Telegram Bot ━━━${NC}"
echo -e "Статус: $bs"
echo -e "Токен: ${YELLOW}$td${NC}"
echo -e "Chat ID: ${YELLOW}${BOT_CHAT_ID:-нет}${NC}\n"
echo -e "1) Токен бота"
echo -e "2) Chat ID (авто)"
echo -e "3) Chat ID (вручную)"
echo -e "4) ${GREEN}Запустить${NC}"
echo -e "5) ${RED}Остановить${NC}"
echo -e "0) Назад"
read -p "Выбор: " ch
case $ch in
1) echo "Токен:"; read -p "> " t
[ -n "$t" ] && save_config_val "BOT_TOKEN" "$t" && BOT_TOKEN="$t" && echo -e "${GREEN}OK${NC}"
read -p "Enter..." ;;
2) [ -z "${BOT_TOKEN:-}" ] && echo -e "${RED}Сначала токен!${NC}" && read -p "" && continue
echo -e "${YELLOW}Отправьте боту сообщение, нажмите Enter.${NC}"; read -p ""
local c; c=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=1&offset=-1" | jq -r '.result[0].message.chat.id // empty')
[ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && BOT_CHAT_ID="$c" && echo -e "${GREEN}Chat ID: $c${NC}" \
|| echo -e "${RED}Не удалось.${NC}"
read -p "Enter..." ;;
3) echo "Chat ID:"; read -p "> " c
[ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && BOT_CHAT_ID="$c" && echo -e "${GREEN}OK${NC}"
read -p "Enter..." ;;
4) start_bot; read -p "Enter..." ;;
5) stop_bot; read -p "Enter..." ;;
0) return ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# PROMO / INFO
# ═══════════════════════════════════════════════════════════════
show_promo() {
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo -e "\n${CYAN}🌍 РФ И ЕВРОПА${NC}\n${WHITE} >>> https://vk.cc/ct29NQ${NC}"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка" "antenka20" "+20% (3мес)" "antenka6" "+15% (6мес)" "antenka12" "+5% (12мес)"
echo -e "\n${CYAN}🇧🇾 БЕЛАРУСЬ${NC}\n${WHITE} >>> https://vk.cc/cUxAhj${NC}"
printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка"
echo -e "\n${YELLOW}QR-код...${NC}"; sleep 2
echo -e "\n${WHITE}"; command -v qrencode &>/dev/null && qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ" || echo "Ссылки выше."; echo -e "${NC}"
read -p "Enter..."
}
show_info() {
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ 📚 WARP Manager v${WARP_VERSION} ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
if [ "$MODE" = "3xui" ]; then
echo -e "${CYAN}═══ РЕЖИМ: 3X-UI ═══${NC}\n"
echo -e "${WHITE} Клиент → 3X-UI (Xray) → SOCKS5 (WARP) → Cloudflare → Интернет${NC}\n"
echo -e "${GREEN} 1.${NC} cloudflare-warp установлен нативно"
echo -e "${GREEN} 2.${NC} SOCKS5-прокси на 127.0.0.1:${SOCKS_PORT}"
echo -e "${GREEN} 3.${NC} В 3X-UI: outbound SOCKS → warp"
echo -e "${GREEN} 4.${NC} Маршрутизация по доменам в Xray"
else
echo -e "${CYAN}═══ РЕЖИМ: AmneziaWG ═══${NC}\n"
echo -e "${WHITE} Клиент → AmneziaWG Docker → warp WG → Cloudflare → Интернет${NC}\n"
echo -e "${GREEN} 1.${NC} wgcf генерирует WireGuard-профиль WARP"
echo -e "${GREEN} 2.${NC} WG-интерфейс warp внутри Docker-контейнера"
echo -e "${GREEN} 3.${NC} Маршрутизация per-client через ip rule"
echo -e "${GREEN} 4.${NC} Персистентность через start.sh контейнера"
fi
echo -e "\n${GREEN} ✓${NC} Разблокировка ChatGPT, Netflix, Disney+, Spotify"
echo -e "${GREEN} ✓${NC} Чистый IP от Cloudflare"
echo -e "${GREEN} ✓${NC} Telegram-бот"
echo -e "${GREEN} ✓${NC} Бесплатно (Cloudflare WARP)"
echo ""; read -p "Enter..."
}
# ═══════════════════════════════════════════════════════════════
# FULL UNINSTALL
# ═══════════════════════════════════════════════════════════════
full_uninstall() {
clear
echo -e "\n${RED}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ⚠ ПОЛНОЕ УДАЛЕНИЕ WARP MANAGER ⚠ ║${NC}"
echo -e "${RED}╚══════════════════════════════════════════════════════════════╝${NC}\n"
echo -e "${WHITE}Режим: ${CYAN}${MODE}${NC}\n"
read -p "$(echo -e "${RED}Удалить полностью? (y/n): ${NC}")" c1
[[ "$c1" != "y" ]] && return
[ "$MODE" = "3xui" ] && uninstall_3xui || uninstall_awg
rm -rf "$WARP_DIR" "$WARP_LOG"
echo -e " ${GREEN}✓${NC} Конфигурация и логи"
rm -f /usr/local/bin/gowarp
echo -e " ${GREEN}✓${NC} Команда gowarp"
echo -e "\n${GREEN}══════════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} WARP Manager полностью удалён.${NC}"
[ "$MODE" = "3xui" ] && echo -e "${WHITE} Уберите outbound \"warp\" из 3X-UI!${NC}"
echo -e "${GREEN}══════════════════════════════════════════════════════════════${NC}"
log_action "UNINSTALL: full removal ($MODE)"
read -p "Enter..."
exit 0
}
# ═══════════════════════════════════════════════════════════════
# MAIN MENU
# ═══════════════════════════════════════════════════════════════
show_menu() {
while true; do
clear
local st sc
st=$(get_warp_status)
sc="$RED"; [[ "$st" == "Подключён" ]] && sc="$GREEN"; [[ "$st" == "Отключён" ]] && sc="$YELLOW"
local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗"
echo -e "║ anten-ka · WARP Manager v${WARP_VERSION} ║"
echo -e "║ YouTube: https://www.youtube.com/@antenkaru ║"
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
echo -e " ${WHITE}IP сервера:${NC} ${GREEN}${MY_IP}${NC} ${WHITE}Режим:${NC} ${CYAN}${mode_label}${NC}"
echo -e " ${WHITE}WARP:${NC} ${sc}${st}${NC}"
if [ "$MODE" = "3xui" ] && is_warp_running; then
echo -e " ${WHITE}SOCKS5:${NC} ${CYAN}127.0.0.1:${SOCKS_PORT}${NC}"
fi
if [ "$MODE" = "amnezia" ] && [ -n "${CONTAINER:-}" ]; then
echo -e " ${WHITE}Контейнер:${NC} ${CYAN}${CONTAINER}${NC}"
fi
echo -e "\n${CYAN}── WARP-ключ ──────────────────────────────────────────${NC}"
echo -e " 1) ${GREEN}Установить WARP${NC}"
echo -e " 2) ${CYAN}Запустить WARP${NC}"
echo -e " 3) ${YELLOW}Остановить WARP${NC}"
echo -e " 4) 📊 Статус"
echo -e " 5) 🔑 ${YELLOW}Перевыпуск ключа${NC}"
if [ "$MODE" = "3xui" ]; then
echo -e "\n${CYAN}── 3X-UI ──────────────────────────────────────────────${NC}"
echo -e " 6) 📋 ${CYAN}JSON конфиг для 3X-UI${NC}"
echo -e " 7) 🔧 ${WHITE}Изменить порт SOCKS5${NC}"
fi
if [ "$MODE" = "amnezia" ]; then
echo -e "\n${CYAN}── AmneziaWG ──────────────────────────────────────────${NC}"
echo -e " 6) ${GREEN}➕ Добавить клиентов в WARP${NC}"
echo -e " 7) ${YELLOW}➖ Убрать клиентов из WARP${NC}"
echo -e " 8) 👥 ${WHITE}Показать клиентов в WARP${NC}"
echo -e " 9) 🔄 ${CYAN}Перезапуск контейнера${NC}"
fi
echo -e "\n${CYAN}── Telegram-бот ───────────────────────────────────────${NC}"
echo -e " 10) 🤖 ${CYAN}Настройка и управление ботом${NC}"
echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}"
echo -e " 11) ${YELLOW}PROMO${NC}"
echo -e " 12) ${MAGENTA}📚 Инструкция${NC}"
echo -e " 13) ${RED}⚠ Полное удаление${NC}"
echo -e " 0) Выход"
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
read -p " Выбор: " ch
case $ch in
1) [ "$MODE" = "3xui" ] && install_warp_3xui || install_warp_awg ;;
2) [ "$MODE" = "3xui" ] && start_warp_3xui || start_warp_awg ;;
3) [ "$MODE" = "3xui" ] && stop_warp_3xui || stop_warp_awg ;;
4) [ "$MODE" = "3xui" ] && show_status_3xui || show_status_awg ;;
5) [ "$MODE" = "3xui" ] && rekey_warp_3xui || rekey_warp_awg ;;
6) [ "$MODE" = "3xui" ] && show_xui_json || awg_add_clients_ssh ;;
7) [ "$MODE" = "3xui" ] && change_port_3xui || awg_remove_clients_ssh ;;
8) [ "$MODE" = "amnezia" ] && awg_show_clients_ssh ;;
9) [ "$MODE" = "amnezia" ] && awg_restart_container ;;
10) bot_menu ;;
11) show_promo ;;
12) show_info ;;
13) full_uninstall ;;
0) exit 0 ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# STARTUP
# ═══════════════════════════════════════════════════════════════
run_startup() {
local total=7 s=0
clear; echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ WARP Manager v${WARP_VERSION} — Загрузка ║${NC}"
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка root..." "$s" "$total"
check_root
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} root OK \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Лицензия..." "$s" "$total"
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Лицензия активна \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Зависимости..." "$s" "$total"
check_deps
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Зависимости OK \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Установка gowarp..." "$s" "$total"
if [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gowarp" ]; then
cp -f "$0" "/usr/local/bin/gowarp"; chmod +x "/usr/local/bin/gowarp"
fi
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Команда gowarp \n" "$s" "$total"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение IP..." "$s" "$total"
get_my_ip
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} IP: %-25s \n" "$s" "$total" "$MY_IP"
((s++))
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Определение режима..." "$s" "$total"
detect_mode
local mode_label="3X-UI"; [ "$MODE" = "amnezia" ] && mode_label="AmneziaWG"
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Режим: %-25s \n" "$s" "$total" "$mode_label"
((s++))
if [ "$MODE" = "amnezia" ]; then
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Docker контейнер..." "$s" "$total"
if awg_pick_container 2>/dev/null; then
awg_load_container_data 2>/dev/null
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} Контейнер: %-20s \n" "$s" "$total" "$CONTAINER"
else
printf "\r ${CYAN}[%d/%d]${NC} ${YELLOW}⚠${NC} Контейнер не найден \n" "$s" "$total"
fi
else
printf " ${CYAN}[%d/%d]${NC} ${YELLOW}⏳${NC} Проверка WARP..." "$s" "$total"
local ws; ws=$(get_warp_status_3xui)
printf "\r ${CYAN}[%d/%d]${NC} ${GREEN}✓${NC} WARP: %-25s \n" "$s" "$total" "$ws"
fi
echo ""
local w=40 bar=""
for ((i=0; i