diff --git a/warp.sh b/warp.sh index 6e5ded2..9122608 100644 --- a/warp.sh +++ b/warp.sh @@ -2,12 +2,12 @@ set -o pipefail # ══════════════════════════════════════════════════════════════ -# WARP Manager v2.1 — Unified 3X-UI + AmneziaWG +# WARP Manager v2.2 — Unified 3X-UI + AmneziaWG # Cloudflare WARP · Telegram Bot · Auto-detect mode # Channel: https://www.youtube.com/@antenkaru # ══════════════════════════════════════════════════════════════ -WARP_VERSION="2.1" +WARP_VERSION="2.2" WARP_DIR="/etc/warp-manager" WARP_CONF="$WARP_DIR/config" WARP_LOG="/var/log/warp-manager.log" @@ -43,6 +43,7 @@ LICENSE_KEY="" MODE="" CONTAINER="" +CONTAINERS="" AWG_VPN_CONF="" AWG_VPN_IF="" AWG_VPN_QUICK_CMD="" @@ -53,6 +54,7 @@ AWG_WARP_EXIT_IP="" declare -a AWG_SELECTED_IPS=() declare -a AWG_CLIENT_IPS=() declare -A AWG_CLIENT_NAMES=() +declare -a AWG_ALL_CONTAINERS=() # ═══════════════════════════════════════════════════════════════ # CONFIG @@ -68,6 +70,7 @@ BOT_CHAT_ID="" LICENSE_KEY="" MODE="" CONTAINER="" +CONTAINERS="" CONF fi source "$WARP_CONF" @@ -522,40 +525,63 @@ uninstall_3xui() { # AMNEZIA BACKEND — WARP via wgcf (WireGuard inside Docker) # ═══════════════════════════════════════════════════════════════ +awg_discover_containers() { + AWG_ALL_CONTAINERS=() + mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^amnezia-awg2$|^amnezia-awg$' || true) + if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then + mapfile -t AWG_ALL_CONTAINERS < <(docker ps --format '{{.Names}}' 2>/dev/null | grep -i "amnezia" || true) + fi + if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then + echo -e "${RED}Контейнеры amnezia-awg / amnezia-awg2 не найдены.${NC}" + return 1 + fi + CONTAINERS="${AWG_ALL_CONTAINERS[*]}" + save_config_val "CONTAINERS" "$CONTAINERS" + CONTAINER="${AWG_ALL_CONTAINERS[0]}" + save_config_val "CONTAINER" "$CONTAINER" + return 0 +} + awg_pick_container() { if [ -n "${CONTAINER:-}" ]; then docker exec "$CONTAINER" sh -c "true" 2>/dev/null && return 0 CONTAINER="" fi - - 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 + awg_discover_containers || return 1 + if [ ${#AWG_ALL_CONTAINERS[@]} -eq 1 ]; then + CONTAINER="${AWG_ALL_CONTAINERS[0]}" fi save_config_val "CONTAINER" "$CONTAINER" return 0 } +awg_select_container() { + awg_discover_containers 2>/dev/null + if [ ${#AWG_ALL_CONTAINERS[@]} -le 1 ]; then + CONTAINER="${AWG_ALL_CONTAINERS[0]:-}" + return 0 + fi + echo -e "\n${CYAN}Выберите контейнер:${NC}" + local i=1 + for c in "${AWG_ALL_CONTAINERS[@]}"; do + local has_warp="нет" + docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null && has_warp="${GREEN}да${NC}" + echo -e " ${GREEN}$i)${NC} $c [WARP: $has_warp]" + ((i++)) + done + echo -e " ${DIM}0) Отмена${NC}" + while true; do + read -p " > " ch + [ "$ch" = "0" ] && return 1 + if [[ "$ch" =~ ^[0-9]+$ ]] && (( ch >= 1 && ch <= ${#AWG_ALL_CONTAINERS[@]} )); then + CONTAINER="${AWG_ALL_CONTAINERS[$((ch-1))]}" + save_config_val "CONTAINER" "$CONTAINER" + awg_load_container_data + return 0 + fi + done +} + awg_load_container_data() { if [ "$CONTAINER" = "amnezia-awg2" ]; then AWG_VPN_CONF="/opt/amnezia/awg/awg0.conf" @@ -818,6 +844,44 @@ uninstall_awg() { echo -e " ${GREEN}✓${NC} wgcf и профили удалены" } +awg_check_containers() { + clear; echo -e "\n${CYAN}━━━ Контейнеры AmneziaWG ━━━${NC}\n" + local saved_container="${CONTAINER:-}" + awg_discover_containers 2>/dev/null + # discover persists CONTAINER=first match; restore user's choice (or "") on disk + CONTAINER="$saved_container" + save_config_val "CONTAINER" "$saved_container" + if [ ${#AWG_ALL_CONTAINERS[@]} -eq 0 ]; then + echo -e " ${RED}Контейнеры не найдены.${NC}" + read -p " Enter..."; return + fi + + printf " ${WHITE}%-20s %-10s %-10s %-12s${NC}\n" "Контейнер" "Статус" "WARP" "Клиентов" + echo -e " ${CYAN}────────────────────────────────────────────────────${NC}" + for c in "${AWG_ALL_CONTAINERS[@]}"; do + local status="Running" + docker exec "$c" sh -c "true" 2>/dev/null || status="${RED}Down${NC}" + [ "$status" = "Running" ] && status="${GREEN}Running${NC}" + + local has_warp="${DIM}Нет${NC}" + local client_info="—" + if docker exec "$c" sh -c "[ -f '/opt/warp/warp.conf' ]" 2>/dev/null; then + local warp_up="${YELLOW}Выкл${NC}" + docker exec "$c" sh -c "ip addr show warp >/dev/null 2>&1" 2>/dev/null && warp_up="${GREEN}Вкл${NC}" + has_warp="$warp_up" + local cnt; cnt=$(docker exec "$c" sh -c "cat /opt/warp/clients.list 2>/dev/null | grep -c '.' || echo 0" | tr -d '\r') + client_info="${cnt}" + fi + + local marker="" + [ "$c" = "${CONTAINER:-}" ] && marker=" ${CYAN}*${NC}" + printf " %-20s %-22b %-22b %-12s%b\n" "$c" "$status" "$has_warp" "$client_info" "$marker" + done + + echo -e "\n ${DIM}* = текущий контейнер${NC}" + echo ""; read -p " Enter..." +} + awg_restart_container() { echo -e "\n${YELLOW}Перезапуск контейнера ${CONTAINER}...${NC}" docker restart "$CONTAINER" >/dev/null @@ -938,6 +1002,9 @@ awg_get_client_ips() { } awg_toggle_clients_ssh() { + if [ ${#AWG_ALL_CONTAINERS[@]} -gt 1 ]; then + awg_select_container || return + fi awg_get_client_ips; awg_parse_clients_table; awg_load_clients if [ ${#AWG_CLIENT_IPS[@]} -eq 0 ]; then @@ -950,7 +1017,8 @@ awg_toggle_clients_ssh() { while true; do local pending_set=" ${pending_ips[*]+"${pending_ips[*]}"} " - clear; echo -e "\n${CYAN}━━━ Управление клиентами WARP ━━━${NC}\n" + 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 @@ -1502,14 +1570,14 @@ bot_daemon() { cbi=$(echo "$upd" | jq -r '.callback_query.id') cci=$(echo "$upd" | jq -r '.callback_query.message.chat.id') cmi=$(echo "$upd" | jq -r '.callback_query.message.message_id') - [ -n "$BOT_CHAT_ID" ] && [ "$cci" != "$BOT_CHAT_ID" ] && { tg_answer_cb "$cbi" "Нет доступа" > /dev/null; continue; } + if ! is_bot_admin "$cci"; then tg_answer_cb "$cbi" "Нет доступа" > /dev/null; continue; fi 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; } + 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 @@ -1552,16 +1620,66 @@ stop_bot() { 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() { - echo -e "${YELLOW} Отправьте боту любое сообщение в Telegram, затем нажмите Enter.${NC}" + 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 c; c=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=1&offset=-1" | jq -r '.result[0].message.chat.id // empty') - if [ -n "$c" ]; then - save_config_val "BOT_CHAT_ID" "$c"; BOT_CHAT_ID="$c" - echo -e " ${GREEN}✓ Chat ID: $c${NC}" + 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 } @@ -1600,6 +1718,20 @@ bot_wizard() { 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 @@ -1612,28 +1744,60 @@ bot_menu() { 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 " 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) Назад" + 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) echo " Токен:"; read -p " > " t + 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..." ;; - 2) bot_auto_chatid; 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}" + 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..." ;; - 4) start_bot; read -p " Enter..." ;; - 5) stop_bot; read -p " Enter..." ;; 0) return ;; esac done @@ -1767,7 +1931,10 @@ show_menu() { echo -e "\n${CYAN}── Прочее ─────────────────────────────────────────────${NC}" echo -e " 9) ${YELLOW}PROMO${NC}" echo -e " 10) ${MAGENTA}📚 Инструкция${NC}" - echo -e " 11) ${RED}⚠ Полное удаление${NC}" + if has_awg_mode; then + echo -e " 11) 🔍 ${WHITE}Проверить контейнеры${NC}" + fi + echo -e " 12) ${RED}⚠ Полное удаление${NC}" echo -e " 0) Выход" echo -e "${CYAN}──────────────────────────────────────────────────────${NC}" read -p " Выбор: " ch @@ -1788,7 +1955,8 @@ show_menu() { 8) bot_menu ;; 9) show_promo ;; 10) show_info ;; - 11) full_uninstall ;; + 11) has_awg_mode && awg_check_containers ;; + 12) full_uninstall ;; 0) exit 0 ;; esac done