diff --git a/install.sh b/install.sh index 0ae88bd..18d34e3 100644 --- a/install.sh +++ b/install.sh @@ -2,12 +2,12 @@ set -o pipefail # ══════════════════════════════════════════════════════════════ -# KASKAD PRO v2.1 — Cascading VPN / Proxy Manager -# Telegram Bot · Live Ping · Monitoring · Alerts · System Stats +# KASKAD PRO v2.2 — Cascading VPN / Proxy Manager +# Telegram Bot · Live Ping · Monitoring · Alerts · GeoIP · System Stats # Channel: https://www.youtube.com/@antenkaru # ══════════════════════════════════════════════════════════════ -KASKAD_VERSION="2.1" +KASKAD_VERSION="2.2" KASKAD_DIR="/etc/kaskad" KASKAD_CONF="$KASKAD_DIR/config" KASKAD_LOG="/var/log/kaskad.log" @@ -36,7 +36,7 @@ init_config() { cat > "$KASKAD_CONF" <<'CONF' BOT_TOKEN="" BOT_CHAT_ID="" -MENU_STYLE="compact" +MENU_STYLE="inline" CONF fi source "$KASKAD_CONF" @@ -52,31 +52,101 @@ save_config_val() { source "$KASKAD_CONF" } -# ─── Aliases (server names) ────────────────────────────────── +# ─── Aliases: IP=name|note|country|isp ──────────────────────── + +set_alias_full() { + local ip="$1" name="$2" note="${3:-}" country="${4:-}" isp="${5:-}" + local val="${name}|${note}|${country}|${isp}" + if grep -q "^${ip}=" "$ALIASES_FILE" 2>/dev/null; then + sed -i "s|^${ip}=.*|${ip}=${val}|" "$ALIASES_FILE" + else + echo "${ip}=${val}" >> "$ALIASES_FILE" + fi +} set_alias() { local ip="$1" name="$2" - if grep -q "^${ip}=" "$ALIASES_FILE" 2>/dev/null; then - sed -i "s|^${ip}=.*|${ip}=${name}|" "$ALIASES_FILE" - else - echo "${ip}=${name}" >> "$ALIASES_FILE" - fi + local existing + existing=$(grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2-) + local old_note old_country old_isp + IFS='|' read -r _ old_note old_country old_isp <<< "$existing" + set_alias_full "$ip" "$name" "${old_note:-}" "${old_country:-}" "${old_isp:-}" } -get_alias() { - local ip="$1" - grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2- +set_alias_note() { + local ip="$1" note="$2" + local existing + existing=$(grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2-) + local old_name old_note old_country old_isp + IFS='|' read -r old_name old_note old_country old_isp <<< "$existing" + set_alias_full "$ip" "${old_name:-}" "$note" "${old_country:-}" "${old_isp:-}" } +set_alias_geo() { + local ip="$1" country="$2" isp="$3" + local existing + existing=$(grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2-) + local old_name old_note old_country old_isp + IFS='|' read -r old_name old_note old_country old_isp <<< "$existing" + set_alias_full "$ip" "${old_name:-}" "${old_note:-}" "$country" "$isp" +} + +get_alias_field() { + local ip="$1" field="$2" + local raw + raw=$(grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2-) + local f_name f_note f_country f_isp + IFS='|' read -r f_name f_note f_country f_isp <<< "$raw" + case "$field" in + name) echo "$f_name" ;; note) echo "$f_note" ;; + country) echo "$f_country" ;; isp) echo "$f_isp" ;; + *) echo "$f_name" ;; + esac +} + +get_alias() { get_alias_field "$1" "name"; } + fmt_ip() { local ip="$1" - local alias - alias=$(get_alias "$ip") - if [ -n "$alias" ]; then - echo "${alias} (${ip})" - else - echo "$ip" + local name country isp + name=$(get_alias_field "$ip" "name") + country=$(get_alias_field "$ip" "country") + isp=$(get_alias_field "$ip" "isp") + local result="" + [ -n "$name" ] && result="${name} " || result="" + result+="($ip)" + if [ -n "$country" ] || [ -n "$isp" ]; then + result+=" " + [ -n "$country" ] && result+="$country" + [ -n "$isp" ] && result+=" | $isp" fi + echo "$result" +} + +fmt_ip_short() { + local ip="$1" + local name + name=$(get_alias_field "$ip" "name") + [ -n "$name" ] && echo "$name ($ip)" || echo "$ip" +} + +fmt_ip_tg() { + local ip="$1" + local name note country isp + name=$(get_alias_field "$ip" "name") + note=$(get_alias_field "$ip" "note") + country=$(get_alias_field "$ip" "country") + isp=$(get_alias_field "$ip" "isp") + local result="" + [ -n "$name" ] && result="$name " || result="" + result+="$ip" + if [ -n "$country" ] || [ -n "$isp" ]; then + result+=" " + [ -n "$country" ] && result+="$country" + [ -n "$isp" ] && result+=" | $isp" + fi + [ -n "$note" ] && result+="\n $note" + echo "$result" } # ─── Logging ────────────────────────────────────────────────── @@ -91,9 +161,7 @@ validate_ip() { local ip="$1" if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then IFS='.' read -r -a octets <<< "$ip" - for o in "${octets[@]}"; do - (( o > 255 )) && return 1 - done + for o in "${octets[@]}"; do (( o > 255 )) && return 1; done return 0 fi return 1 @@ -123,35 +191,136 @@ read_validated_port() { done } -read_server_name() { +# ─── GeoIP + Probe ──────────────────────────────────────────── + +geoip_lookup() { local ip="$1" - local existing - existing=$(get_alias "$ip") - if [ -n "$existing" ]; then - echo -e "Текущее имя для $ip: ${GREEN}$existing${NC}" + curl -s --max-time 5 "http://ip-api.com/json/${ip}?fields=status,country,regionName,city,isp,org" 2>/dev/null +} + +probe_server_cli() { + local ip="$1" + echo -e "\n${CYAN}━━━ Проверка сервера $ip ━━━${NC}" + + echo -e "${YELLOW}[*] GeoIP...${NC}" + local geo + geo=$(geoip_lookup "$ip") + local geo_status geo_country geo_region geo_city geo_isp geo_org + geo_status=$(echo "$geo" | jq -r '.status // "fail"') + if [ "$geo_status" = "success" ]; then + geo_country=$(echo "$geo" | jq -r '.country // ""') + geo_region=$(echo "$geo" | jq -r '.regionName // ""') + geo_city=$(echo "$geo" | jq -r '.city // ""') + geo_isp=$(echo "$geo" | jq -r '.isp // ""') + geo_org=$(echo "$geo" | jq -r '.org // ""') + local geo_loc="${geo_country}" + [ -n "$geo_city" ] && geo_loc+=", ${geo_city}" + local geo_provider="$geo_isp" + [ -n "$geo_org" ] && [ "$geo_org" != "$geo_isp" ] && geo_provider+=" ($geo_org)" + echo -e " ${WHITE}GeoIP:${NC} ${GREEN}${geo_loc}${NC} | ${CYAN}${geo_provider}${NC}" + set_alias_geo "$ip" "$geo_country" "$geo_isp" + else + echo -e " ${RED}GeoIP: не удалось определить${NC}" + fi + + echo -e "${YELLOW}[*] Ping (3x)...${NC}" + local -a pings=() + local plost=0 + for n in 1 2 3; do + local ms + ms=$(ping -c 1 -W 3 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') + if [ -n "$ms" ]; then + pings+=("$ms") + echo -e " #$n: ${GREEN}${ms}ms${NC}" + else + ((plost++)) + echo -e " #$n: ${RED}timeout${NC}" + fi + [ "$n" -lt 3 ] && sleep 1 + done + if [ ${#pings[@]} -gt 0 ]; then + local pavg + pavg=$(printf '%s\n' "${pings[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}') + echo -e " ${WHITE}Среднее: ${pavg}ms${NC} Потеряно: $plost/3" + else + echo -e " ${RED}Сервер не отвечает${NC}" + fi + + echo "" + local existing_name + existing_name=$(get_alias "$ip") + if [ -n "$existing_name" ]; then + echo -e "Текущее имя: ${GREEN}$existing_name${NC}" fi echo -e "Введите имя сервера (или Enter — пропустить):" read -p "> " _RET_NAME - if [ -n "$_RET_NAME" ]; then - set_alias "$ip" "$_RET_NAME" + [ -n "$_RET_NAME" ] && set_alias "$ip" "$_RET_NAME" + + echo -e "Введите примечание (или Enter — пропустить):" + read -p "> " _RET_NOTE + [ -n "$_RET_NOTE" ] && set_alias_note "$ip" "$_RET_NOTE" + + if [ ${#pings[@]} -eq 0 ]; then + echo -e "${YELLOW}[WARN] Сервер не отвечает на ping.${NC}" + read -p "Продолжить? (y/n): " ans + [[ "$ans" != "y" ]] && return 1 fi + return 0 +} + +probe_server_tg() { + local ip="$1" + local result="" + local geo + geo=$(geoip_lookup "$ip") + local geo_status + geo_status=$(echo "$geo" | jq -r '.status // "fail"') + if [ "$geo_status" = "success" ]; then + local geo_country geo_city geo_isp geo_org + geo_country=$(echo "$geo" | jq -r '.country // ""') + geo_city=$(echo "$geo" | jq -r '.city // ""') + geo_isp=$(echo "$geo" | jq -r '.isp // ""') + geo_org=$(echo "$geo" | jq -r '.org // ""') + local geo_loc="$geo_country" + [ -n "$geo_city" ] && geo_loc+=", $geo_city" + result+="🌍 GeoIP: $geo_loc | $geo_isp\n" + set_alias_geo "$ip" "$geo_country" "$geo_isp" + fi + local -a pings=() + local plost=0 + for n in 1 2 3; do + local ms + ms=$(ping -c 1 -W 3 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') + if [ -n "$ms" ]; then + pings+=("$ms") + result+=" #$n: ${ms}ms\n" + else + ((plost++)) + result+=" #$n: timeout\n" + fi + [ "$n" -lt 3 ] && sleep 1 + done + if [ ${#pings[@]} -gt 0 ]; then + local pavg + pavg=$(printf '%s\n' "${pings[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}') + result+="Среднее: ${pavg}ms | Потеряно: $plost/3\n" + else + result+="Сервер не отвечает\n" + fi + echo "$result" } # ─── System ─────────────────────────────────────────────────── check_root() { if [ "$EUID" -ne 0 ]; then - echo -e "${RED}[ERROR] Запустите скрипт с правами root!${NC}" - exit 1 + echo -e "${RED}[ERROR] Запустите скрипт с правами root!${NC}"; exit 1 fi } detect_interface() { IFACE=$(ip route get 8.8.8.8 2>/dev/null | sed -n 's/.*dev \([^ ]*\).*/\1/p' | head -1) - if [[ -z "$IFACE" ]]; then - echo -e "${RED}[ERROR] Не удалось определить сетевой интерфейс!${NC}" - exit 1 - fi + [[ -z "$IFACE" ]] && echo -e "${RED}[ERROR] Не удалось определить интерфейс!${NC}" && exit 1 } get_my_ip() { @@ -168,27 +337,20 @@ save_iptables() { prepare_system() { if [ "$(readlink -f "$0" 2>/dev/null)" != "/usr/local/bin/gokaskad" ]; then - cp -f "$0" "/usr/local/bin/gokaskad" - chmod +x "/usr/local/bin/gokaskad" + cp -f "$0" "/usr/local/bin/gokaskad"; chmod +x "/usr/local/bin/gokaskad" fi - if grep -qE '^[[:space:]]*#?[[:space:]]*net\.ipv4\.ip_forward' /etc/sysctl.conf; then sed -i 's/^#*\s*net\.ipv4\.ip_forward.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf else echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf fi - grep -q "^net.core.default_qdisc=fq" /etc/sysctl.conf || echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf grep -q "^net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf || echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf sysctl -p > /dev/null 2>&1 - export DEBIAN_FRONTEND=noninteractive local need_install=0 - for cmd in iptables jq curl qrencode; do - command -v "$cmd" &>/dev/null || need_install=1 - done + for cmd in iptables jq curl qrencode; do command -v "$cmd" &>/dev/null || need_install=1; done dpkg -s iptables-persistent &>/dev/null 2>&1 || need_install=1 - if [ "$need_install" -eq 1 ]; then if command -v apt-get &>/dev/null; then apt-get update -y > /dev/null 2>&1 @@ -198,40 +360,31 @@ prepare_system() { elif command -v yum &>/dev/null; then yum install -y iptables-services jq qrencode curl procps-ng > /dev/null 2>&1 else - echo -e "${RED}[ERROR] Неподдерживаемый пакетный менеджер!${NC}" - exit 1 + echo -e "${RED}[ERROR] Неподдерживаемый пакетный менеджер!${NC}"; exit 1 fi fi } -# ─── System Stats ───────────────────────────────────────────── - get_system_stats() { - local cpu_line load_avg mem_info disk_info uptime_str top_procs + local cpu_line load_avg mem_info swap_info disk_info uptime_str top_procs cpu_usage cpu_line=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || echo "?") load_avg=$(cat /proc/loadavg 2>/dev/null | awk '{print $1, $2, $3}') mem_info=$(free -m 2>/dev/null | awk '/^Mem:/ {printf "%d/%dMB (%.1f%%)", $3, $2, $3/$2*100}') - local swap_info swap_info=$(free -m 2>/dev/null | awk '/^Swap:/ {if($2>0) printf "%d/%dMB", $3, $2; else print "N/A"}') disk_info=$(df -h / 2>/dev/null | awk 'NR==2 {printf "%s/%s (%s)", $3, $2, $5}') uptime_str=$(uptime -p 2>/dev/null || uptime | sed 's/.*up /up /' | sed 's/,.*load.*//') top_procs=$(ps aux --sort=-%cpu 2>/dev/null | head -8 | awk 'NR>1 {printf "%-6s %-4s%% %-4s%% %s\n", $2, $3, $4, $11}') - - local cpu_usage cpu_usage=$(awk '/^cpu / {u=$2+$4; t=$2+$3+$4+$5+$6+$7+$8; if(t>0) printf "%.1f", u/t*100; else print "0"}' /proc/stat 2>/dev/null) - - local result="" - result+="📊 Системная информация\n\n" - result+="Uptime: ${uptime_str}\n" - result+="CPU: ${cpu_line} ядер | загрузка: ${cpu_usage}%\n" - result+="Load Avg: ${load_avg}\n" - result+="RAM: ${mem_info}\n" - result+="Swap: ${swap_info}\n" - result+="Disk /: ${disk_info}\n\n" - result+="Топ процессов (CPU):\n" - result+="
PID    CPU%  MEM%  CMD\n"
-    result+="${top_procs}
" - echo "$result" + local r="" + r+="📊 Системная информация\n\n" + r+="Uptime: ${uptime_str}\n" + r+="CPU: ${cpu_line} ядер | ${cpu_usage}%\n" + r+="Load: ${load_avg}\n" + r+="RAM: ${mem_info}\n" + r+="Swap: ${swap_info}\n" + r+="Disk /: ${disk_info}\n\n" + r+="Топ CPU:\n
PID    CPU%  MEM%  CMD\n${top_procs}
" + echo "$r" } # ─── iptables helpers ───────────────────────────────────────── @@ -261,52 +414,25 @@ remove_rules_for_port() { iptables -S FORWARD 2>/dev/null | grep "kaskad" | grep -P "\b-p ${proto}\b" | while read -r rule; do local rd=$(echo "$rule" | grep -oP '(?<=--dport )\d+') local rs=$(echo "$rule" | grep -oP '(?<=--sport )\d+') - if [[ "$rd" == "$in_port" || "$rs" == "$in_port" ]]; then - eval "iptables -D ${rule#-A }" 2>/dev/null - fi + [[ "$rd" == "$in_port" || "$rs" == "$in_port" ]] && eval "iptables -D ${rule#-A }" 2>/dev/null done } -check_target_reachable() { - local ip="$1" - if ! ping -c 1 -W 3 "$ip" &>/dev/null; then - echo -e "${YELLOW}[WARN] Сервер $ip не отвечает на ping.${NC}" - read -p "Продолжить? (y/n): " ans - [[ "$ans" != "y" ]] && return 1 - fi - return 0 -} - apply_iptables_rules() { local proto="$1" in_port="$2" out_port="$3" target_ip="$4" name="$5" - echo -e "${YELLOW}[*] Применение правил...${NC}" log_action "ADD rule: $proto :$in_port -> $target_ip:$out_port ($name)" - remove_rules_for_port "$proto" "$in_port" - - iptables -I INPUT -p "$proto" --dport "$in_port" \ - -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT - - iptables -t nat -A PREROUTING -p "$proto" --dport "$in_port" \ - -j DNAT --to-destination "$target_ip:$out_port" - + iptables -I INPUT -p "$proto" --dport "$in_port" -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT + iptables -t nat -A PREROUTING -p "$proto" --dport "$in_port" -j DNAT --to-destination "$target_ip:$out_port" if ! iptables -t nat -C POSTROUTING -o "$IFACE" -j MASQUERADE 2>/dev/null; then iptables -t nat -A POSTROUTING -o "$IFACE" -j MASQUERADE fi - - iptables -I FORWARD -p "$proto" -d "$target_ip" --dport "$out_port" \ - -m state --state NEW,ESTABLISHED,RELATED \ - -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT - - iptables -I FORWARD -p "$proto" -s "$target_ip" --sport "$out_port" \ - -m state --state ESTABLISHED,RELATED \ - -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT - + iptables -I FORWARD -p "$proto" -d "$target_ip" --dport "$out_port" -m state --state NEW,ESTABLISHED,RELATED -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT + iptables -I FORWARD -p "$proto" -s "$target_ip" --sport "$out_port" -m state --state ESTABLISHED,RELATED -m comment --comment "kaskad:${in_port}:${proto}" -j ACCEPT if command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -q "Status: active"; then ufw allow "$in_port/$proto" > /dev/null 2>&1 fi - save_iptables echo -e "${GREEN}[SUCCESS] $name настроен!${NC}" echo -e "$proto: ${MY_IP:-*}:$in_port -> $target_ip:$out_port" @@ -317,29 +443,22 @@ apply_iptables_rules() { configure_rule() { local proto="$1" name="$2" echo -e "\n${CYAN}--- Настройка $name ($proto) ---${NC}" - read_validated_ip "Введите IP адрес назначения:" local target_ip="$_RET_IP" - - read_server_name "$target_ip" - check_target_reachable "$target_ip" || return + probe_server_cli "$target_ip" || return read_validated_port "Введите Порт (одинаковый для входа и выхода):" local port="$_RET_PORT" - echo -e "\n${YELLOW}Будет создано правило:${NC}" - echo -e " $proto: ${MY_IP:-*}:$port -> $(fmt_ip "$target_ip"):$port" + echo -e " $proto: ${MY_IP:-*}:$port -> $(fmt_ip_short "$target_ip"):$port" read -p "Применить? (y/n): " confirm [[ "$confirm" != "y" ]] && return - apply_iptables_rules "$proto" "$port" "$port" "$target_ip" "$name" read -p "Нажмите Enter для возврата в меню..." } configure_custom_rule() { echo -e "\n${CYAN}--- Универсальное кастомное правило ---${NC}" - echo -e "${WHITE}Позволяет указать разные порты для входа и выхода.${NC}\n" - local proto while true; do echo -e "Выберите протокол (${YELLOW}tcp${NC} или ${YELLOW}udp${NC}):" @@ -347,24 +466,18 @@ configure_custom_rule() { [[ "$proto" == "tcp" || "$proto" == "udp" ]] && break echo -e "${RED}Ошибка: введите tcp или udp!${NC}" done - - read_validated_ip "Введите IP адрес назначения (куда отправляем трафик):" + read_validated_ip "Введите IP адрес назначения:" local target_ip="$_RET_IP" - - read_server_name "$target_ip" - check_target_reachable "$target_ip" || return + probe_server_cli "$target_ip" || return read_validated_port "Введите ${YELLOW}ВХОДЯЩИЙ Порт${NC} (на этом сервере):" local in_port="$_RET_PORT" - read_validated_port "Введите ${YELLOW}ИСХОДЯЩИЙ Порт${NC} (на конечном сервере):" local out_port="$_RET_PORT" - echo -e "\n${YELLOW}Будет создано правило:${NC}" - echo -e " $proto: ${MY_IP:-*}:$in_port -> $(fmt_ip "$target_ip"):$out_port" + echo -e " $proto: ${MY_IP:-*}:$in_port -> $(fmt_ip_short "$target_ip"):$out_port" read -p "Применить? (y/n): " confirm [[ "$confirm" != "y" ]] && return - apply_iptables_rules "$proto" "$in_port" "$out_port" "$target_ip" "Custom Rule" read -p "Нажмите Enter для возврата в меню..." } @@ -372,9 +485,8 @@ configure_custom_rule() { # ─── List / Delete / Flush ──────────────────────────────────── list_active_rules() { - echo -e "\n${CYAN}--- Активные переадресации ---${NC}" + echo -e "\n${CYAN}━━━ Активные переадресации ━━━${NC}" echo -e "${WHITE}Сервер каскада: ${GREEN}${MY_IP:-N/A}${NC}\n" - echo -e "${MAGENTA}ВХОД (IP:ПОРТ)\t\tПРОТОКОЛ\tЦЕЛЬ${NC}" local rules rules=$(get_rules_list) if [ -z "$rules" ]; then @@ -382,11 +494,7 @@ list_active_rules() { else echo "$rules" | while IFS='|' read -r proto port dest; do local dest_ip="${dest%:*}" - local alias_str - alias_str=$(get_alias "$dest_ip") - local label="$dest" - [ -n "$alias_str" ] && label="$dest [$alias_str]" - echo -e "${MY_IP:-*}:$port\t\t$proto\t\t$label" + echo -e " ${WHITE}${MY_IP:-*}:${port}${NC} ($proto) → ${GREEN}${dest}${NC} $(fmt_ip "$dest_ip")" done fi echo "" @@ -400,59 +508,41 @@ delete_single_rule() { while IFS='|' read -r proto port dest; do rules_arr[$i]="$proto|$port|$dest" local dest_ip="${dest%:*}" - local label - label=$(fmt_ip "$dest_ip") - echo -e "${YELLOW}[$i]${NC} ${MY_IP:-*}:$port ($proto) -> ${dest#*:} @ $label" + echo -e "${YELLOW}[$i]${NC} ${MY_IP:-*}:$port ($proto) -> $(fmt_ip_short "$dest_ip")" ((i++)) done <<< "$(get_rules_list)" - if [ ${#rules_arr[@]} -eq 0 ]; then - echo -e "${RED}Нет активных правил.${NC}" - read -p "Нажмите Enter..." - return + echo -e "${RED}Нет активных правил.${NC}"; read -p "Нажмите Enter..."; return fi - echo "" - read -p "Номер правила для удаления (0 — отмена): " rule_num - if [[ "$rule_num" == "0" || -z "${rules_arr[$rule_num]:-}" ]]; then return; fi - + read -p "Номер для удаления (0 — отмена): " rule_num + [[ "$rule_num" == "0" || -z "${rules_arr[$rule_num]:-}" ]] && return IFS='|' read -r d_proto d_port d_dest <<< "${rules_arr[$rule_num]}" - local target_ip="${d_dest%:*}" - iptables -t nat -D PREROUTING -p "$d_proto" --dport "$d_port" -j DNAT --to-destination "$d_dest" 2>/dev/null - iptables -S INPUT 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do - eval "iptables -D ${rule#-A }" 2>/dev/null - done - iptables -S FORWARD 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do - eval "iptables -D ${rule#-A }" 2>/dev/null - done - + iptables -S INPUT 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do eval "iptables -D ${rule#-A }" 2>/dev/null; done + iptables -S FORWARD 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do eval "iptables -D ${rule#-A }" 2>/dev/null; done save_iptables log_action "DELETE rule: $d_proto :$d_port -> $d_dest" - echo -e "${GREEN}[OK] Правило удалено.${NC}" - read -p "Нажмите Enter..." + echo -e "${GREEN}[OK] Правило удалено.${NC}"; read -p "Нажмите Enter..." } flush_rules() { echo -e "\n${RED}!!! ВНИМАНИЕ !!!${NC}" - echo "Будут удалены только правила, созданные Kaskad." - read -p "Вы уверены? (y/n): " confirm + echo "Будут удалены только правила Kaskad." + read -p "Уверены? (y/n): " confirm if [[ "$confirm" == "y" ]]; then while iptables -t nat -S PREROUTING 2>/dev/null | grep -q "DNAT"; do - local rule - rule=$(iptables -t nat -S PREROUTING | grep "DNAT" | head -1) + local rule; rule=$(iptables -t nat -S PREROUTING | grep "DNAT" | head -1) eval "iptables -t nat -D ${rule#-A }" 2>/dev/null done for chain in INPUT FORWARD; do while iptables -S "$chain" 2>/dev/null | grep -q "kaskad"; do - local rule - rule=$(iptables -S "$chain" | grep "kaskad" | head -1) + local rule; rule=$(iptables -S "$chain" | grep "kaskad" | head -1) eval "iptables -D ${rule#-A }" 2>/dev/null done done - save_iptables - log_action "FLUSH all kaskad rules" - echo -e "${GREEN}[OK] Правила Kaskad очищены.${NC}" + save_iptables; log_action "FLUSH all kaskad rules" + echo -e "${GREEN}[OK] Очищено.${NC}" fi read -p "Нажмите Enter..." } @@ -463,24 +553,23 @@ manage_aliases_menu() { echo -e "${CYAN}━━━ Имена серверов ━━━${NC}" local -a ips=() while read -r ip; do [ -n "$ip" ] && ips+=("$ip"); done <<< "$(get_target_ips)" - if [ ${#ips[@]} -eq 0 ]; then - echo -e "${YELLOW}Нет целевых серверов.${NC}" - read -p "Enter..." - return - fi + if [ ${#ips[@]} -eq 0 ]; then echo -e "${YELLOW}Нет серверов.${NC}"; read -p "Enter..."; return; fi for i in "${!ips[@]}"; do - echo -e " ${YELLOW}[$((i+1))]${NC} ${ips[$i]} — ${GREEN}$(get_alias "${ips[$i]}" || echo "без имени")${NC}" + echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip "${ips[$i]}")" + local note; note=$(get_alias_field "${ips[$i]}" "note") + [ -n "$note" ] && echo -e " ${WHITE}Примечание:${NC} $note" done echo -e " ${YELLOW}[0]${NC} Назад" - read -p "Выберите сервер для переименования: " choice + read -p "Сервер: " choice [[ "$choice" == "0" || -z "$choice" ]] && return local idx=$((choice - 1)) [ -z "${ips[$idx]:-}" ] && continue - echo -e "Новое имя для ${ips[$idx]}:" - read -p "> " new_name - [ -n "$new_name" ] && set_alias "${ips[$idx]}" "$new_name" - echo -e "${GREEN}[OK] Сохранено.${NC}" - read -p "Enter..." + local sel="${ips[$idx]}" + echo -e "Новое имя для $sel (Enter — оставить):" + read -p "> " nn; [ -n "$nn" ] && set_alias "$sel" "$nn" + echo -e "Новое примечание (Enter — оставить):" + read -p "> " nt; [ -n "$nt" ] && set_alias_note "$sel" "$nt" + echo -e "${GREEN}[OK]${NC}"; read -p "Enter..." done } @@ -488,45 +577,51 @@ manage_aliases_menu() { self_update() { local repo_url="https://raw.githubusercontent.com/anten-ka/kaskad-pro/main/install.sh" - local update_token - update_token=$(bot_get_state "system" "UPDATE_TOKEN" 2>/dev/null) - + local update_token; update_token=$(bot_get_state "system" "UPDATE_TOKEN" 2>/dev/null) echo -e "${YELLOW}[*] Загрузка обновления...${NC}" - local ok=0 - if [ -n "$update_token" ]; then - curl -sL -H "Authorization: token $update_token" "$repo_url" -o /tmp/kaskad_update.sh 2>/dev/null && ok=1 - fi - if [ "$ok" -eq 0 ]; then - wget -qO /tmp/kaskad_update.sh "$repo_url" 2>/dev/null && ok=1 - fi - if [ "$ok" -eq 0 ] && [ -n "$update_token" ]; then - wget -qO /tmp/kaskad_update.sh --header="Authorization: token $update_token" "$repo_url" 2>/dev/null && ok=1 - fi - + [ -n "$update_token" ] && curl -sL -H "Authorization: token $update_token" "$repo_url" -o /tmp/kaskad_update.sh 2>/dev/null && ok=1 + [ "$ok" -eq 0 ] && wget -qO /tmp/kaskad_update.sh "$repo_url" 2>/dev/null && ok=1 + [ "$ok" -eq 0 ] && [ -n "$update_token" ] && wget -qO /tmp/kaskad_update.sh --header="Authorization: token $update_token" "$repo_url" 2>/dev/null && ok=1 if [ "$ok" -eq 1 ] && [ -s /tmp/kaskad_update.sh ]; then - cp -f /tmp/kaskad_update.sh /usr/local/bin/gokaskad - chmod +x /usr/local/bin/gokaskad - rm -f /tmp/kaskad_update.sh - systemctl restart kaskad-bot 2>/dev/null - systemctl restart kaskad-monitor 2>/dev/null - echo -e "${GREEN}[OK] Скрипт обновлён! Службы перезапущены.${NC}" - echo -e "${GREEN}Перезапустите меню: gokaskad${NC}" - log_action "Self-update completed" + cp -f /tmp/kaskad_update.sh /usr/local/bin/gokaskad; chmod +x /usr/local/bin/gokaskad; rm -f /tmp/kaskad_update.sh + systemctl restart kaskad-bot 2>/dev/null; systemctl restart kaskad-monitor 2>/dev/null + echo -e "${GREEN}[OK] Обновлён! Перезапустите: gokaskad${NC}"; log_action "Self-update completed" else - echo -e "${RED}[ERROR] Не удалось скачать обновление.${NC}" - rm -f /tmp/kaskad_update.sh + echo -e "${RED}[ERROR] Не удалось.${NC}"; rm -f /tmp/kaskad_update.sh fi read -p "Нажмите Enter..." } # ═══════════════════════════════════════════════════════════════ -# LIVE PING +# LIVE PING with ASCII bar # ═══════════════════════════════════════════════════════════════ +make_ping_bar() { + local ms_str="$1" width=25 + local ms_int + ms_int=$(awk "BEGIN {printf \"%d\", $ms_str + 0.5}") + local filled=$(( ms_int * width / 100 )) + (( filled > width )) && filled=$width + (( filled < 1 )) && filled=1 + local empty=$(( width - filled )) + + local color="$GREEN" + (( ms_int > 50 )) && color="$YELLOW" + (( ms_int > 100 )) && color="$RED" + + local bar="${color}" + for (( b=0; b/dev/null | grep -oP 'time=\K[\d.]+') ((count++)) + clear + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${CYAN} Live Ping: ${WHITE}$label${CYAN} [Ctrl+C — стоп]${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + if [ -n "$ms" ]; then results+=("$ms") - lines+=("${GREEN}#${count}: ${ms} ms${NC}") + local bar + bar=$(make_ping_bar "$ms") + printf " ${GREEN}#%-4d %7sms${NC} %b\n" "$count" "$ms" "$bar" else ((lost++)) - lines+=("${RED}#${count}: timeout${NC}") + printf " ${RED}#%-4d ------${NC} " "$count" + for (( b=0; b<25; b++ )); do echo -ne "${RED}█${NC}"; done + echo -e " ${RED}TIMEOUT${NC}" fi - clear - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${CYAN} Live Ping: ${WHITE}$(fmt_ip "$ip")${CYAN} [Ctrl+C — стоп]${NC}" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + local show=18 + local total=${#results[@]} + local start_show=$(( count - show )) + (( start_show < 1 )) && start_show=1 - local show=20 - local start_idx=$(( ${#lines[@]} - show )) - (( start_idx < 0 )) && start_idx=0 - for (( i=start_idx; i<${#lines[@]}; i++ )); do - echo -e " ${lines[$i]}" - done + local display_start=$(( start_show - 1 )) + local display_idx=0 + local lines_printed=1 + if (( count > 1 )); then + local hist_start=$(( count - show )) + (( hist_start < 0 )) && hist_start=0 + fi + + echo "" if [ ${#results[@]} -gt 0 ]; then local stats stats=$(printf '%s\n' "${results[@]}" | awk ' BEGIN {mn=999999; mx=0; s=0} {s+=$1; if($1mx)mx=$1} - END {printf "%.2f|%.2f|%.2f", mn, mx, s/NR} - ') + END {printf "%.2f|%.2f|%.2f", mn, mx, s/NR}') IFS='|' read -r s_min s_max s_avg <<< "$stats" - echo "" - echo -e " ${WHITE}Мин:${NC} ${s_min}ms ${WHITE}Макс:${NC} ${s_max}ms ${WHITE}Сред:${NC} ${s_avg}ms" + echo -e " ${WHITE}Мин:${NC} ${s_min}ms ${WHITE}│${NC} ${WHITE}Макс:${NC} ${s_max}ms ${WHITE}│${NC} ${WHITE}Сред:${NC} ${s_avg}ms" fi echo -e " ${WHITE}Потеряно:${NC} $lost / $count" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" sleep 1 done trap - INT - echo "" - read -p "Нажмите Enter для возврата в меню..." + echo ""; read -p "Нажмите Enter для возврата в меню..." } ping_menu() { echo -e "\n${CYAN}--- Ping серверов ---${NC}" local -a ips=() - while read -r ip; do - [ -n "$ip" ] && ips+=("$ip") - done <<< "$(get_target_ips)" - - if [ ${#ips[@]} -eq 0 ]; then - echo -e "${YELLOW}Нет активных целевых серверов.${NC}" - read -p "Нажмите Enter..." - return - fi - - echo -e "Активные целевые серверы:" - for i in "${!ips[@]}"; do - echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip "${ips[$i]}")" - done + while read -r ip; do [ -n "$ip" ] && ips+=("$ip"); done <<< "$(get_target_ips)" + if [ ${#ips[@]} -eq 0 ]; then echo -e "${YELLOW}Нет серверов.${NC}"; read -p "Enter..."; return; fi + echo -e "Серверы:" + for i in "${!ips[@]}"; do echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip "${ips[$i]}")"; done echo -e " ${YELLOW}[0]${NC} Отмена" - - read -p "Выберите сервер: " choice + read -p "Выбор: " choice [[ "$choice" == "0" || -z "$choice" ]] && return local idx=$((choice - 1)) [ -z "${ips[$idx]:-}" ] && return - ping_live "${ips[$idx]}" } # ═══════════════════════════════════════════════════════════════ -# MONITORING (with configurable alert cooldown + auto start/stop) +# MONITORING # ═══════════════════════════════════════════════════════════════ add_monitor() { @@ -629,22 +721,15 @@ remove_monitor() { } has_monitors() { - local found=0 - for conf in "$MONITOR_DIR"/*.conf; do - [ -f "$conf" ] && found=1 && break - done - return $(( 1 - found )) + for conf in "$MONITOR_DIR"/*.conf; do [ -f "$conf" ] && return 0; done + return 1 } sync_monitoring_service() { if has_monitors; then - if ! systemctl is-active kaskad-monitor &>/dev/null 2>&1; then - start_monitoring_silent - fi + systemctl is-active kaskad-monitor &>/dev/null 2>&1 || start_monitoring_silent else - if systemctl is-active kaskad-monitor &>/dev/null 2>&1; then - stop_monitoring_silent - fi + systemctl is-active kaskad-monitor &>/dev/null 2>&1 && stop_monitoring_silent fi } @@ -653,102 +738,68 @@ start_monitoring_silent() { [Unit] Description=Kaskad Monitoring Daemon After=network.target - [Service] Type=simple ExecStart=/usr/local/bin/gokaskad --monitor-daemon Restart=always RestartSec=5 - [Install] WantedBy=multi-user.target EOF - systemctl daemon-reload - systemctl enable kaskad-monitor > /dev/null 2>&1 - systemctl start kaskad-monitor 2>/dev/null + systemctl daemon-reload; systemctl enable kaskad-monitor > /dev/null 2>&1; systemctl start kaskad-monitor 2>/dev/null log_action "Monitoring auto-started" } stop_monitoring_silent() { - systemctl stop kaskad-monitor 2>/dev/null - systemctl disable kaskad-monitor 2>/dev/null - rm -f "$MONITOR_PID_FILE" - log_action "Monitoring auto-stopped (no monitors)" + systemctl stop kaskad-monitor 2>/dev/null; systemctl disable kaskad-monitor 2>/dev/null; rm -f "$MONITOR_PID_FILE" + log_action "Monitoring auto-stopped" } list_monitors() { local found=0 for conf in "$MONITOR_DIR"/*.conf; do - [ -f "$conf" ] || continue - found=1 - local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300 - source "$conf" - local label - label=$(fmt_ip "$MON_IP") - echo -e " ${WHITE}$label${NC} инт: ${MON_INTERVAL}s порог: ${MON_THRESHOLD}ms уведомл: ${MON_COOLDOWN}s" + [ -f "$conf" ] || continue; found=1 + local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300; source "$conf" + echo -e " ${WHITE}$(fmt_ip_short "$MON_IP")${NC} инт: ${MON_INTERVAL}s порог: ${MON_THRESHOLD}ms уведомл: ${MON_COOLDOWN}s" done - if [ "$found" -eq 0 ]; then - echo -e " ${YELLOW}Нет настроенных мониторов.${NC}" - fi + [ "$found" -eq 0 ] && echo -e " ${YELLOW}Нет мониторов.${NC}" } monitor_alert() { local ip="$1" ping_ms="$2" threshold="$3" cooldown="${4:-300}" - local alert_file="$MONITOR_DIR/.last_alert_${ip}" - local last_alert=0 + local alert_file="$MONITOR_DIR/.last_alert_${ip}" last_alert=0 [ -f "$alert_file" ] && last_alert=$(cat "$alert_file") - local now - now=$(date +%s) - - if (( now - last_alert < cooldown )); then return; fi + local now; now=$(date +%s) + (( now - last_alert < cooldown )) && return echo "$now" > "$alert_file" log_action "ALERT: $ip ping=${ping_ms}ms threshold=${threshold}ms" - source "$KASKAD_CONF" 2>/dev/null if [ -n "${BOT_TOKEN:-}" ] && [ -n "${BOT_CHAT_ID:-}" ]; then - local label - label=$(get_alias "$ip") - local header="$ip" - [ -n "$label" ] && header="$label ($ip)" + local header; header=$(fmt_ip_short "$ip") local text - if [ "$ping_ms" = "TIMEOUT" ]; then - text="⚠️ ALERT: ${header}\nPing: TIMEOUT (порог: ${threshold}ms)" - else - text="⚠️ ALERT: ${header}\nPing: ${ping_ms}ms (порог: ${threshold}ms)" - fi + [ "$ping_ms" = "TIMEOUT" ] && text="⚠️ ALERT: ${header}\nPing: TIMEOUT (порог: ${threshold}ms)" \ + || text="⚠️ ALERT: ${header}\nPing: ${ping_ms}ms (порог: ${threshold}ms)" tg_send "$BOT_CHAT_ID" "$text" "" > /dev/null 2>&1 fi } monitor_daemon() { - log_action "Monitor daemon started (PID $$)" - echo $$ > "$MONITOR_PID_FILE" - + log_action "Monitor daemon started (PID $$)"; echo $$ > "$MONITOR_PID_FILE" while true; do - local now - now=$(date +%s) - for conf_file in "$MONITOR_DIR"/*.conf; do - [ -f "$conf_file" ] || continue - local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300 - source "$conf_file" - - local check_file="$MONITOR_DIR/.last_check_${MON_IP}" - local last_check=0 - [ -f "$check_file" ] && last_check=$(cat "$check_file") - - if (( now - last_check >= MON_INTERVAL )); then - echo "$now" > "$check_file" - local ping_result - ping_result=$(ping -c 1 -W 3 "$MON_IP" 2>/dev/null | grep -oP 'time=\K[\d.]+') - - if [ -z "$ping_result" ]; then + local now; now=$(date +%s) + for cf in "$MONITOR_DIR"/*.conf; do + [ -f "$cf" ] || continue + local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300; source "$cf" + local ckf="$MONITOR_DIR/.last_check_${MON_IP}" lc=0 + [ -f "$ckf" ] && lc=$(cat "$ckf") + if (( now - lc >= MON_INTERVAL )); then + echo "$now" > "$ckf" + local pr; pr=$(ping -c 1 -W 3 "$MON_IP" 2>/dev/null | grep -oP 'time=\K[\d.]+') + if [ -z "$pr" ]; then monitor_alert "$MON_IP" "TIMEOUT" "$MON_THRESHOLD" "$MON_COOLDOWN" else - local ping_int - ping_int=$(awk "BEGIN {printf \"%d\", $ping_result + 0.5}") - if (( ping_int > MON_THRESHOLD )); then - monitor_alert "$MON_IP" "$ping_result" "$MON_THRESHOLD" "$MON_COOLDOWN" - fi + local pi; pi=$(awk "BEGIN {printf \"%d\", $pr + 0.5}") + (( pi > MON_THRESHOLD )) && monitor_alert "$MON_IP" "$pr" "$MON_THRESHOLD" "$MON_COOLDOWN" fi fi done @@ -759,85 +810,34 @@ monitor_daemon() { monitoring_menu() { while true; do clear - local mon_status="${RED}Остановлен${NC}" - if systemctl is-active kaskad-monitor &>/dev/null 2>&1; then - mon_status="${GREEN}Работает${NC}" - fi - + local ms="${RED}Остановлен${NC}" + systemctl is-active kaskad-monitor &>/dev/null 2>&1 && ms="${GREEN}Работает${NC}" echo -e "${CYAN}━━━ Мониторинг (авто) ━━━${NC}" - echo -e "Статус: $mon_status" - echo -e "${YELLOW}Служба запускается/останавливается автоматически.${NC}" - echo "" - list_monitors - echo "" - echo -e "1) Добавить мониторинг" - echo -e "2) Удалить мониторинг" - echo -e "0) Назад" + echo -e "Статус: $ms"; echo ""; list_monitors; echo "" + echo -e "1) Добавить"; echo -e "2) Удалить"; echo -e "0) Назад" read -p "Выбор: " choice - case $choice in 1) local -a ips=() while read -r ip; do [ -n "$ip" ] && ips+=("$ip"); done <<< "$(get_target_ips)" - if [ ${#ips[@]} -eq 0 ]; then - echo -e "${YELLOW}Нет активных серверов.${NC}" - read -p "Enter..." - continue - fi - echo "Серверы:" - for i in "${!ips[@]}"; do echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip "${ips[$i]}")"; done - read -p "Сервер: " s_choice - local s_idx=$((s_choice - 1)) - [ -z "${ips[$s_idx]:-}" ] && continue - local sel_ip="${ips[$s_idx]}" - - echo -e "Интервал проверки:" - echo -e " 1) Каждые 10 сек" - echo -e " 2) Каждую минуту" - echo -e " 3) Каждые 5 мин" - read -p "Выбор: " int_ch - local interval=60 - case $int_ch in - 1) interval=10 ;; 2) interval=60 ;; 3) interval=300 ;; - esac - - read_validated_port "Порог уведомления (мс):" - local threshold="$_RET_PORT" - - echo -e "Частота уведомлений (не чаще чем):" - echo -e " 1) Каждые 10 сек" - echo -e " 2) Каждые 60 сек" - echo -e " 3) Каждые 5 мин" - echo -e " 4) Каждые 15 мин" - read -p "Выбор: " cd_ch - local cooldown=300 - case $cd_ch in - 1) cooldown=10 ;; 2) cooldown=60 ;; 3) cooldown=300 ;; 4) cooldown=900 ;; - esac - - add_monitor "$sel_ip" "$interval" "$threshold" "$cooldown" - echo -e "${GREEN}[OK] Мониторинг для $(fmt_ip "$sel_ip") добавлен.${NC}" - read -p "Enter..." - ;; + [ ${#ips[@]} -eq 0 ] && echo -e "${YELLOW}Нет серверов.${NC}" && read -p "Enter..." && continue + for i in "${!ips[@]}"; do echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip_short "${ips[$i]}")"; done + read -p "Сервер: " sc; local si=$((sc-1)); [ -z "${ips[$si]:-}" ] && continue + echo -e "Интервал: 1) 10с 2) 1мин 3) 5мин" + read -p "> " ic; local iv=60; case $ic in 1) iv=10;; 3) iv=300;; esac + read_validated_port "Порог (мс):"; local th="$_RET_PORT" + echo -e "Уведомления: 1) 10с 2) 60с 3) 5мин 4) 15мин" + read -p "> " cc; local cd=300; case $cc in 1) cd=10;; 2) cd=60;; 4) cd=900;; esac + add_monitor "${ips[$si]}" "$iv" "$th" "$cd" + echo -e "${GREEN}[OK]${NC}"; read -p "Enter..." ;; 2) - local -a mon_ips=() - for conf in "$MONITOR_DIR"/*.conf; do - [ -f "$conf" ] || continue - local MON_IP="" - source "$conf" - mon_ips+=("$MON_IP") - done - if [ ${#mon_ips[@]} -eq 0 ]; then - echo -e "${YELLOW}Нет мониторов.${NC}" - read -p "Enter..." - continue - fi - for i in "${!mon_ips[@]}"; do echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip "${mon_ips[$i]}")"; done - read -p "Номер для удаления: " d_ch - local d_idx=$((d_ch - 1)) - [ -n "${mon_ips[$d_idx]:-}" ] && remove_monitor "${mon_ips[$d_idx]}" && echo -e "${GREEN}[OK] Удалено.${NC}" - read -p "Enter..." - ;; + local -a mi=() + for c in "$MONITOR_DIR"/*.conf; do [ -f "$c" ] || continue; local MON_IP=""; source "$c"; mi+=("$MON_IP"); done + [ ${#mi[@]} -eq 0 ] && echo -e "${YELLOW}Нет.${NC}" && read -p "Enter..." && continue + for i in "${!mi[@]}"; do echo -e " ${YELLOW}[$((i+1))]${NC} $(fmt_ip_short "${mi[$i]}")"; done + read -p "Номер: " dc; local di=$((dc-1)) + [ -n "${mi[$di]:-}" ] && remove_monitor "${mi[$di]}" && echo -e "${GREEN}[OK]${NC}" + read -p "Enter..." ;; 0) return ;; esac done @@ -848,10 +848,7 @@ monitoring_menu() { # ═══════════════════════════════════════════════════════════════ tg_api() { - local method="$1" payload="$2" - curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/${method}" \ - -H "Content-Type: application/json" \ - -d "$payload" 2>/dev/null + curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/$1" -H "Content-Type: application/json" -d "$2" 2>/dev/null } tg_send() { @@ -868,6 +865,24 @@ tg_send() { tg_api "sendMessage" "$payload" } +tg_send_reply_kb() { + local chat_id="$1" text keyboard="$3" + text=$(printf '%b' "$2") + local payload + payload=$(jq -n --arg c "$chat_id" --arg t "$text" --argjson k "$keyboard" \ + '{chat_id:$c, text:$t, parse_mode:"HTML", reply_markup:{keyboard:$k, resize_keyboard:true, one_time_keyboard:false}}') + tg_api "sendMessage" "$payload" +} + +tg_remove_reply_kb() { + local chat_id="$1" text="$2" + text=$(printf '%b' "$text") + local payload + payload=$(jq -n --arg c "$chat_id" --arg t "$text" \ + '{chat_id:$c, text:$t, parse_mode:"HTML", reply_markup:{remove_keyboard:true}}') + tg_api "sendMessage" "$payload" +} + tg_edit() { local chat_id="$1" msg_id="$2" text keyboard="${4:-}" text=$(printf '%b' "$3") @@ -883,55 +898,21 @@ tg_edit() { } tg_answer_cb() { - local cb_id="$1" text="${2:-}" - tg_api "answerCallbackQuery" "{\"callback_query_id\":\"$cb_id\",\"text\":\"$text\"}" + tg_api "answerCallbackQuery" "{\"callback_query_id\":\"$1\",\"text\":\"${2:-}\"}" } # ─── Bot state ──────────────────────────────────────────────── -bot_set_state() { - local chat_id="$1"; shift - printf '%s\n' "$@" > "$BOT_STATE_DIR/$chat_id" -} +bot_set_state() { local c="$1"; shift; printf '%s\n' "$@" > "$BOT_STATE_DIR/$c"; } +bot_get_state() { [ -f "$BOT_STATE_DIR/$1" ] && grep "^${2}=" "$BOT_STATE_DIR/$1" | head -1 | cut -d= -f2-; } +bot_clear_state() { rm -f "$BOT_STATE_DIR/$1"; } -bot_get_state() { - local chat_id="$1" key="$2" - [ -f "$BOT_STATE_DIR/$chat_id" ] && grep "^${key}=" "$BOT_STATE_DIR/$chat_id" | head -1 | cut -d= -f2- -} - -bot_clear_state() { - rm -f "$BOT_STATE_DIR/$1" -} +get_menu_style() { source "$KASKAD_CONF" 2>/dev/null; echo "${MENU_STYLE:-inline}"; } # ─── Bot keyboards ─────────────────────────────────────────── -get_menu_style() { - source "$KASKAD_CONF" 2>/dev/null - echo "${MENU_STYLE:-compact}" -} - -kbd_main() { - local style - style=$(get_menu_style) - if [ "$style" = "large" ]; then - cat <<'JSON' -[ - [{"text":"🔀 AmneziaWG / WireGuard (UDP)","callback_data":"a_u"}], - [{"text":"🔀 VLESS / XRay (TCP)","callback_data":"a_t"}], - [{"text":"🔀 MTProto / TProxy (TCP)","callback_data":"a_mt"}], - [{"text":"🛠 Кастомное правило (TCP/UDP)","callback_data":"a_c"}], - [{"text":"📋 Активные правила","callback_data":"lr"}], - [{"text":"🏓 Ping серверов","callback_data":"pm"}], - [{"text":"📊 Мониторинг","callback_data":"mm"}], - [{"text":"💻 Состояние системы","callback_data":"sys"}], - [{"text":"❌ Удалить правило","callback_data":"dr"}], - [{"text":"🗑 Сбросить все правила","callback_data":"fa"}], - [{"text":"🏢 Хостинг, который работает","callback_data":"promo"}], - [{"text":"⚙️ Компактное меню","callback_data":"sw_compact"}] -] -JSON - else - cat <<'JSON' +kbd_inline_main() { + cat <<'JSON' [ [{"text":"🔀 AWG","callback_data":"a_u"},{"text":"🔀 VLESS","callback_data":"a_t"},{"text":"🔀 MTProto","callback_data":"a_mt"}], [{"text":"🛠 Custom","callback_data":"a_c"},{"text":"📋 Правила","callback_data":"lr"}], @@ -939,98 +920,68 @@ JSON [{"text":"💻 Система","callback_data":"sys"}], [{"text":"❌ Удалить","callback_data":"dr"},{"text":"🗑 Сброс","callback_data":"fa"}], [{"text":"🏢 Хостинг","callback_data":"promo"}], - [{"text":"⚙️ Большое меню","callback_data":"sw_large"}] + [{"text":"⌨️ Reply-клавиатура","callback_data":"sw_reply"}] ] JSON - fi } -kbd_back() { - echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]' +reply_kb_json() { + cat <<'JSON' +[ + ["🔀 AWG/WG", "🔀 VLESS"], + ["🔀 MTProto", "🛠 Custom"], + ["📋 Правила", "🏓 Ping"], + ["📊 Монитор", "💻 Система"], + ["❌ Удалить", "🗑 Сброс"], + ["🏢 Хостинг"], + ["/inline"] +] +JSON } -kbd_proto() { - echo '[[{"text":"TCP","callback_data":"a_cp_tcp"},{"text":"UDP","callback_data":"a_cp_udp"}],[{"text":"⬅️ Меню","callback_data":"m"}]]' -} +kbd_back() { echo '[[{"text":"⬅️ Меню","callback_data":"m"}]]'; } +kbd_proto() { echo '[[{"text":"TCP","callback_data":"a_cp_tcp"},{"text":"UDP","callback_data":"a_cp_udp"}],[{"text":"⬅️ Меню","callback_data":"m"}]]'; } kbd_ping_opts() { local ip="$1" - jq -n --arg ip "$ip" '[ - [{"text":"1 раз","callback_data":("po:" + $ip)}], - [{"text":"10 раз (среднее)","callback_data":("p10:" + $ip)}], - [{"text":"60 сек (непрерывно)","callback_data":("p60:" + $ip)}], - [{"text":"⬅️ Меню","callback_data":"m"}] - ]' + jq -n --arg ip "$ip" '[[{"text":"1 раз","callback_data":("po:"+$ip)}],[{"text":"10 раз","callback_data":("p10:"+$ip)}],[{"text":"60 сек","callback_data":("p60:"+$ip)}],[{"text":"⬅️ Меню","callback_data":"m"}]]' } -kbd_monitor() { - cat <<'JSON' -[ - [{"text":"➕ Добавить","callback_data":"ma"}], - [{"text":"📋 Список","callback_data":"ml"}], - [{"text":"➖ Удалить","callback_data":"md"}], - [{"text":"⬅️ Меню","callback_data":"m"}] -] +kbd_monitor() { cat <<'JSON' +[[{"text":"➕ Добавить","callback_data":"ma"}],[{"text":"📋 Список","callback_data":"ml"}],[{"text":"➖ Удалить","callback_data":"md"}],[{"text":"⬅️ Меню","callback_data":"m"}]] JSON } kbd_intervals() { local ip="$1" - jq -n --arg ip "$ip" '[ - [{"text":"10 сек","callback_data":("mi:" + $ip + ":10")}], - [{"text":"1 мин","callback_data":("mi:" + $ip + ":60")}], - [{"text":"5 мин","callback_data":("mi:" + $ip + ":300")}], - [{"text":"⬅️ Меню","callback_data":"m"}] - ]' + jq -n --arg ip "$ip" '[[{"text":"10с","callback_data":("mi:"+$ip+":10")}],[{"text":"1мин","callback_data":("mi:"+$ip+":60")}],[{"text":"5мин","callback_data":("mi:"+$ip+":300")}],[{"text":"⬅️","callback_data":"m"}]]' } kbd_cooldowns() { local ip="$1" interval="$2" threshold="$3" - jq -n --arg ip "$ip" --arg int "$interval" --arg thr "$threshold" '[ - [{"text":"10 сек","callback_data":("mc:" + $ip + ":" + $int + ":" + $thr + ":10")}], - [{"text":"60 сек","callback_data":("mc:" + $ip + ":" + $int + ":" + $thr + ":60")}], - [{"text":"5 мин","callback_data":("mc:" + $ip + ":" + $int + ":" + $thr + ":300")}], - [{"text":"15 мин","callback_data":("mc:" + $ip + ":" + $int + ":" + $thr + ":900")}], - [{"text":"⬅️ Меню","callback_data":"m"}] - ]' + jq -n --arg ip "$ip" --arg i "$interval" --arg t "$threshold" \ + '[[{"text":"10с","callback_data":("mc:"+$ip+":"+$i+":"+$t+":10")}],[{"text":"60с","callback_data":("mc:"+$ip+":"+$i+":"+$t+":60")}],[{"text":"5мин","callback_data":("mc:"+$ip+":"+$i+":"+$t+":300")}],[{"text":"15мин","callback_data":("mc:"+$ip+":"+$i+":"+$t+":900")}],[{"text":"⬅️","callback_data":"m"}]]' } build_ip_kbd() { - local prefix="$1"; shift - local ips=("$@") - local rows="" first=1 + local prefix="$1"; shift; local ips=("$@") rows="" first=1 for ip in "${ips[@]}"; do - local label - label=$(get_alias "$ip") - [ -z "$label" ] && label="$ip" || label="$label ($ip)" - [ "$first" -eq 0 ] && rows+="," - rows+="[{\"text\":\"$label\",\"callback_data\":\"${prefix}:${ip}\"}]" - first=0 + local label; label=$(get_alias "$ip"); [ -z "$label" ] && label="$ip" || label="$label ($ip)" + [ "$first" -eq 0 ] && rows+=","; rows+="[{\"text\":\"$label\",\"callback_data\":\"${prefix}:${ip}\"}]"; first=0 done echo "[${rows},[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" } build_delete_kbd() { - local rules - rules=$(get_rules_list) + local rules; rules=$(get_rules_list) local rows="" i=1 first=1 while IFS='|' read -r proto port dest; do [ -z "$port" ] && continue - local dest_ip="${dest%:*}" - local label - label=$(get_alias "$dest_ip") - local btn_text="❌ ${MY_IP:-*}:$port ($proto) → $dest" - [ -n "$label" ] && btn_text="❌ :$port → $label" - [ "$first" -eq 0 ] && rows+="," - rows+="[{\"text\":\"$btn_text\",\"callback_data\":\"dr_${i}\"}]" - first=0 - ((i++)) + local dip="${dest%:*}" label; label=$(get_alias "$dip") + local bt="❌ :$port ($proto) → $dest"; [ -n "$label" ] && bt="❌ :$port → $label" + [ "$first" -eq 0 ] && rows+=","; rows+="[{\"text\":\"$bt\",\"callback_data\":\"dr_${i}\"}]"; first=0; ((i++)) done <<< "$rules" - if [ -z "$rows" ]; then - echo '[[{"text":"Нет правил","callback_data":"m"}]]' - else - echo "[${rows},[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]" - fi + [ -z "$rows" ] && echo '[[{"text":"Нет правил","callback_data":"m"}]]' || echo "[${rows},[{\"text\":\"⬅️\",\"callback_data\":\"m\"}]]" } # ─── Bot handlers ───────────────────────────────────────────── @@ -1038,509 +989,320 @@ build_delete_kbd() { bot_main_menu() { local chat_id="$1" msg_id="${2:-}" bot_clear_state "$chat_id" + local style; style=$(get_menu_style) local text="Kaskad PRO v${KASKAD_VERSION}\nIP: ${MY_IP:-N/A}\nВыберите действие:" - local kbd - kbd=$(kbd_main) - if [ -n "$msg_id" ]; then - tg_edit "$chat_id" "$msg_id" "$text" "$kbd" + + if [ "$style" = "reply" ]; then + tg_send_reply_kb "$chat_id" "$text" "$(reply_kb_json)" > /dev/null else - tg_send "$chat_id" "$text" "$kbd" + local kbd; kbd=$(kbd_inline_main) + if [ -n "$msg_id" ]; then + tg_edit "$chat_id" "$msg_id" "$text" "$kbd" + else + tg_send "$chat_id" "$text" "$kbd" + fi fi } +bot_handle_reply_text() { + local chat_id="$1" text="$2" + case "$text" in + "🔀 AWG/WG") bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=udp" "NAME=AmneziaWG" "CUSTOM=0"; tg_send "$chat_id" "🔀 AmneziaWG (UDP)\n\nВведите IP:" "$(kbd_back)" > /dev/null ;; + "🔀 VLESS") bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=VLESS" "CUSTOM=0"; tg_send "$chat_id" "🔀 VLESS (TCP)\n\nВведите IP:" "$(kbd_back)" > /dev/null ;; + "🔀 MTProto") bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=MTProto" "CUSTOM=0"; tg_send "$chat_id" "🔀 MTProto (TCP)\n\nВведите IP:" "$(kbd_back)" > /dev/null ;; + "🛠 Custom") tg_send "$chat_id" "🛠 Custom Rule\n\nВыберите протокол:" "$(kbd_proto)" > /dev/null ;; + "📋 Правила") bot_handle_callback "$chat_id" "" "" "lr_new" ;; + "🏓 Ping") bot_handle_callback "$chat_id" "" "" "pm_new" ;; + "📊 Монитор") bot_handle_callback "$chat_id" "" "" "mm_new" ;; + "💻 Система") local s; s=$(get_system_stats); tg_send "$chat_id" "$s" "$(kbd_back)" > /dev/null ;; + "❌ Удалить") tg_send "$chat_id" "❌ Выберите правило:" "$(build_delete_kbd)" > /dev/null ;; + "🗑 Сброс") tg_send "$chat_id" "🗑 Уверены?" '[[{"text":"✅ Да","callback_data":"fa_y"},{"text":"❌ Нет","callback_data":"m"}]]' > /dev/null ;; + "🏢 Хостинг") bot_handle_callback "$chat_id" "" "" "promo_new" ;; + *) return 1 ;; + esac + return 0 +} + 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 - tg_answer_cb "$cb_id" > /dev/null + local use_send=0 + [[ "$data" == *_new ]] && use_send=1 && data="${data%_new}" case "$data" in - m) - bot_main_menu "$chat_id" "$msg_id" - ;; + m) bot_main_menu "$chat_id" "$msg_id" ;; + sw_reply) save_config_val "MENU_STYLE" "reply"; bot_main_menu "$chat_id" ;; + sw_inline) save_config_val "MENU_STYLE" "inline"; bot_main_menu "$chat_id" ;; - sw_compact) - save_config_val "MENU_STYLE" "compact" - bot_main_menu "$chat_id" "$msg_id" - ;; - sw_large) - save_config_val "MENU_STYLE" "large" - bot_main_menu "$chat_id" "$msg_id" - ;; - - # ── Add rules ── - a_u) - bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=udp" "NAME=AmneziaWG" "CUSTOM=0" - tg_edit "$chat_id" "$msg_id" "🔀 AmneziaWG / WireGuard (UDP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" - ;; - a_t) - bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=VLESS" "CUSTOM=0" - tg_edit "$chat_id" "$msg_id" "🔀 VLESS / XRay (TCP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" - ;; - a_mt) - bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=MTProto" "CUSTOM=0" - tg_edit "$chat_id" "$msg_id" "🔀 MTProto / TProxy (TCP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" - ;; - a_c) - tg_edit "$chat_id" "$msg_id" "🛠 Custom Rule\n\nВыберите протокол:" "$(kbd_proto)" - ;; + a_u) bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=udp" "NAME=AmneziaWG" "CUSTOM=0" + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🔀 AmneziaWG (UDP)\n\nВведите IP:" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🔀 AmneziaWG (UDP)\n\nВведите IP:" "$(kbd_back)" ;; + a_t) bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=VLESS" "CUSTOM=0" + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🔀 VLESS (TCP)\n\nВведите IP:" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🔀 VLESS (TCP)\n\nВведите IP:" "$(kbd_back)" ;; + a_mt) bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=MTProto" "CUSTOM=0" + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🔀 MTProto (TCP)\n\nВведите IP:" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🔀 MTProto (TCP)\n\nВведите IP:" "$(kbd_back)" ;; + a_c) [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🛠 Custom\n\nПротокол:" "$(kbd_proto)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🛠 Custom\n\nПротокол:" "$(kbd_proto)" ;; a_cp_tcp|a_cp_udp) local proto="${data#a_cp_}" bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=$proto" "NAME=Custom" "CUSTOM=1" - tg_edit "$chat_id" "$msg_id" "🛠 Custom Rule ($proto)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" - ;; + tg_edit "$chat_id" "$msg_id" "🛠 Custom ($proto)\n\nВведите IP:" "$(kbd_back)" ;; - # ── List rules ── lr) - local rules text="" - rules=$(get_rules_list) - if [ -z "$rules" ]; then - text="📋 Нет активных правил." + local rules text=""; rules=$(get_rules_list) + if [ -z "$rules" ]; then text="📋 Нет правил." else - text="📋 Активные правила:\nСервер: ${MY_IP:-N/A}\n\n" + text="📋 Правила\nСервер: ${MY_IP:-N/A}\n\n" while IFS='|' read -r proto port dest; do - if [ -n "$port" ]; then - local dest_ip="${dest%:*}" - local label - label=$(get_alias "$dest_ip") - local line="${MY_IP:-*}:$port ($proto) → $dest" - [ -n "$label" ] && line+=" [$label]" - text+="$line\n" - fi + [ -n "$port" ] || continue + local dip="${dest%:*}" + text+="${MY_IP:-*}:$port ($proto) → $dest\n" + text+=" $(fmt_ip_tg "$dip")\n" done <<< "$rules" fi - tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" - ;; + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "$text" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" ;; - # ── Delete rule ── - dr) - tg_edit "$chat_id" "$msg_id" "❌ Выберите правило для удаления:" "$(build_delete_kbd)" - ;; + dr) [ "$use_send" -eq 1 ] && tg_send "$chat_id" "❌ Выберите:" "$(build_delete_kbd)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "❌ Выберите:" "$(build_delete_kbd)" ;; dr_*) - local idx="${data#dr_}" - local line - line=$(get_rules_list | sed -n "${idx}p") + local idx="${data#dr_}" line; line=$(get_rules_list | sed -n "${idx}p") if [ -n "$line" ]; then - IFS='|' read -r d_proto d_port d_dest <<< "$line" - iptables -t nat -D PREROUTING -p "$d_proto" --dport "$d_port" -j DNAT --to-destination "$d_dest" 2>/dev/null - iptables -S INPUT 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do - eval "iptables -D ${rule#-A }" 2>/dev/null - done - iptables -S FORWARD 2>/dev/null | grep "kaskad:${d_port}:${d_proto}" | while read -r rule; do - eval "iptables -D ${rule#-A }" 2>/dev/null - done - save_iptables - log_action "BOT DELETE rule: $d_proto :$d_port -> $d_dest" - tg_edit "$chat_id" "$msg_id" "✅ Правило $d_proto :$d_port → $d_dest удалено." "$(kbd_back)" - else - tg_edit "$chat_id" "$msg_id" "Правило не найдено." "$(kbd_back)" - fi - ;; + IFS='|' read -r dp dpo dd <<< "$line" + iptables -t nat -D PREROUTING -p "$dp" --dport "$dpo" -j DNAT --to-destination "$dd" 2>/dev/null + iptables -S INPUT 2>/dev/null | grep "kaskad:${dpo}:${dp}" | while read -r r; do eval "iptables -D ${r#-A }" 2>/dev/null; done + iptables -S FORWARD 2>/dev/null | grep "kaskad:${dpo}:${dp}" | while read -r r; do eval "iptables -D ${r#-A }" 2>/dev/null; done + save_iptables; log_action "BOT DELETE: $dp :$dpo -> $dd" + tg_edit "$chat_id" "$msg_id" "✅ $dp :$dpo → $dd удалено." "$(kbd_back)" + else tg_edit "$chat_id" "$msg_id" "Не найдено." "$(kbd_back)"; fi ;; - # ── Flush ── - fa) - tg_edit "$chat_id" "$msg_id" "🗑 Вы уверены?\nБудут удалены ВСЕ правила Kaskad." \ - '[[{"text":"✅ Да, сбросить","callback_data":"fa_y"},{"text":"❌ Отмена","callback_data":"m"}]]' - ;; + fa) tg_edit "$chat_id" "$msg_id" "🗑 Уверены?" '[[{"text":"✅ Да","callback_data":"fa_y"},{"text":"❌ Нет","callback_data":"m"}]]' ;; fa_y) while iptables -t nat -S PREROUTING 2>/dev/null | grep -q "DNAT"; do - local rule - rule=$(iptables -t nat -S PREROUTING | grep "DNAT" | head -1) - eval "iptables -t nat -D ${rule#-A }" 2>/dev/null + local r; r=$(iptables -t nat -S PREROUTING | grep "DNAT" | head -1); eval "iptables -t nat -D ${r#-A }" 2>/dev/null done - for chain in INPUT FORWARD; do - while iptables -S "$chain" 2>/dev/null | grep -q "kaskad"; do - local rule - rule=$(iptables -S "$chain" | grep "kaskad" | head -1) - eval "iptables -D ${rule#-A }" 2>/dev/null + for ch in INPUT FORWARD; do + while iptables -S "$ch" 2>/dev/null | grep -q "kaskad"; do + local r; r=$(iptables -S "$ch" | grep "kaskad" | head -1); eval "iptables -D ${r#-A }" 2>/dev/null done done - save_iptables - log_action "BOT FLUSH all kaskad rules" - tg_edit "$chat_id" "$msg_id" "✅ Все правила Kaskad удалены." "$(kbd_back)" - ;; + save_iptables; log_action "BOT FLUSH" + tg_edit "$chat_id" "$msg_id" "✅ Очищено." "$(kbd_back)" ;; - # ── System stats ── - sys) - local stats - stats=$(get_system_stats) - tg_edit "$chat_id" "$msg_id" "$stats" "$(kbd_back)" - ;; + sys) local s; s=$(get_system_stats) + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "$s" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "$s" "$(kbd_back)" ;; - # ── Promo ── promo) - local promo_text="" - promo_text+="🏢 Хостинг, который работает\n\n" - promo_text+="🌍 Локации: РФ и Европа\n" - promo_text+="👉 https://vk.cc/ct29NQ\n\n" - promo_text+="OFF60 — 60% скидка на 1-й месяц\n" - promo_text+="antenka20 — +20% к балансу (3 мес)\n" - promo_text+="antenka6 — +15% к балансу (6 мес)\n" - promo_text+="antenka12 — +5% к балансу (12 мес)\n\n" - promo_text+="🇧🇾 Локация: Беларусь\n" - promo_text+="👉 https://vk.cc/cUxAhj\n\n" - promo_text+="OFF60 — 60% скидка на 1-й месяц" - tg_edit "$chat_id" "$msg_id" "$promo_text" "$(kbd_back)" - ;; + local pt="🏢 Хостинг, который работает\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% скидка" + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "$pt" "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "$pt" "$(kbd_back)" ;; - # ── Ping ── - pm) - local -a ips=() + pm) local -a ips=() while read -r ip; do [ -n "$ip" ] && ips+=("$ip"); done <<< "$(get_target_ips)" if [ ${#ips[@]} -eq 0 ]; then - tg_edit "$chat_id" "$msg_id" "🏓 Нет целевых серверов для пинга." "$(kbd_back)" + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🏓 Нет серверов." "$(kbd_back)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🏓 Нет серверов." "$(kbd_back)" else - tg_edit "$chat_id" "$msg_id" "🏓 Выберите сервер:" "$(build_ip_kbd "ps" "${ips[@]}")" - fi - ;; - ps:*) - local ip="${data#ps:}" - local label - label=$(fmt_ip "$ip") - tg_edit "$chat_id" "$msg_id" "🏓 Ping $label\nВыберите режим:" "$(kbd_ping_opts "$ip")" - ;; - po:*) - local ip="${data#po:}" - local label - label=$(fmt_ip "$ip") - ( - local ms - ms=$(ping -c 1 -W 3 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') - if [ -n "$ms" ]; then - tg_send "$chat_id" "🏓 $label\nОтвет: ${ms} ms" "$(kbd_back)" > /dev/null - else - tg_send "$chat_id" "🏓 $label\nОтвет: timeout" "$(kbd_back)" > /dev/null - fi - ) & - ;; - p10:*) - local ip="${data#p10:}" - local label - label=$(fmt_ip "$ip") - ( - local resp - resp=$(tg_send "$chat_id" "🏓 Ping $label (10 раз)...\nОжидайте." "") - local mid - mid=$(echo "$resp" | jq -r '.result.message_id // empty') + [ "$use_send" -eq 1 ] && tg_send "$chat_id" "🏓 Сервер:" "$(build_ip_kbd "ps" "${ips[@]}")" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "🏓 Сервер:" "$(build_ip_kbd "ps" "${ips[@]}")" + fi ;; + ps:*) local ip="${data#ps:}"; local lb; lb=$(fmt_ip_short "$ip") + tg_edit "$chat_id" "$msg_id" "🏓 $lb\nРежим:" "$(kbd_ping_opts "$ip")" ;; + po:*) local ip="${data#po:}"; local lb; lb=$(fmt_ip_short "$ip") + ( local ms; ms=$(ping -c 1 -W 3 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') + [ -n "$ms" ] && tg_send "$chat_id" "🏓 $lb\n${ms} ms" "$(kbd_back)" > /dev/null \ + || tg_send "$chat_id" "🏓 $lb\ntimeout" "$(kbd_back)" > /dev/null ) & ;; + p10:*) local ip="${data#p10:}"; local lb; lb=$(fmt_ip_short "$ip") + ( local resp; resp=$(tg_send "$chat_id" "🏓 $lb (10x)..." "") + local mid; mid=$(echo "$resp" | jq -r '.result.message_id // empty') + local -a res=(); local lost=0 txt="" + for n in $(seq 1 10); do + local ms; ms=$(ping -c 1 -W 2 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') + [ -n "$ms" ] && res+=("$ms") && txt+="#$n: ${ms}ms\n" || { ((lost++)); txt+="#$n: timeout\n"; } + sleep 1 + done + local sm="🏓 $lb (10x)\n${txt}" + [ ${#res[@]} -gt 0 ] && { local av; av=$(printf '%s\n' "${res[@]}" | awk '{s+=$1} END {printf "%.2f",s/NR}'); sm+="\nСреднее: ${av}ms"; } + sm+="\nПотеряно: $lost/10" + [ -n "$mid" ] && tg_edit "$chat_id" "$mid" "$sm" "$(kbd_back)" > /dev/null || tg_send "$chat_id" "$sm" "$(kbd_back)" > /dev/null + ) & ;; + p60:*) local ip="${data#p60:}"; local lb; lb=$(fmt_ip_short "$ip") + ( local resp; resp=$(tg_send "$chat_id" "🏓 $lb (60с)..." "") + local mid; mid=$(echo "$resp" | jq -r '.result.message_id // empty') + local -a res=(); local lost=0 + for n in $(seq 1 60); do + local ms; ms=$(ping -c 1 -W 2 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') + [ -n "$ms" ] && res+=("$ms") || ((lost++)) + if (( n % 10 == 0 )) && [ -n "$mid" ]; then + local p="🏓 $lb: ${n}/60с\nОК: ${#res[@]} | Lost: $lost" + [ ${#res[@]} -gt 0 ] && { local pa; pa=$(printf '%s\n' "${res[@]}" | awk '{s+=$1} END {printf "%.2f",s/NR}'); p+="\nСред: ${pa}ms"; } + tg_edit "$chat_id" "$mid" "$p" "" > /dev/null + fi; sleep 1 + done + local sm="🏓 $lb (60с) — готово\n" + if [ ${#res[@]} -gt 0 ]; then + local st; st=$(printf '%s\n' "${res[@]}" | awk 'BEGIN{mn=999999;mx=0;s=0}{s+=$1;if($1mx)mx=$1}END{printf "%.2f|%.2f|%.2f",mn,mx,s/NR}') + IFS='|' read -r sn sx sa <<< "$st"; sm+="Мин: ${sn}ms\nМакс: ${sx}ms\nСред: ${sa}ms\n" + fi; sm+="Потеряно: $lost/60" + [ -n "$mid" ] && tg_edit "$chat_id" "$mid" "$sm" "$(kbd_back)" > /dev/null || tg_send "$chat_id" "$sm" "$(kbd_back)" > /dev/null + ) & ;; - local -a results=() - local lost=0 text="" - for i in $(seq 1 10); do - local ms - ms=$(ping -c 1 -W 2 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') - if [ -n "$ms" ]; then - results+=("$ms") - text+="#$i: ${ms}ms\n" - else - ((lost++)) - text+="#$i: timeout\n" - fi - sleep 1 - done - - local summary="🏓 Ping $label (10 раз)\n${text}" - if [ ${#results[@]} -gt 0 ]; then - local avg - avg=$(printf '%s\n' "${results[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}') - summary+="\nСреднее: ${avg}ms" - fi - summary+="\nПотеряно: $lost / 10" - - if [ -n "$mid" ]; then - tg_edit "$chat_id" "$mid" "$summary" "$(kbd_back)" > /dev/null - else - tg_send "$chat_id" "$summary" "$(kbd_back)" > /dev/null - fi - ) & - ;; - p60:*) - local ip="${data#p60:}" - local label - label=$(fmt_ip "$ip") - ( - local resp - resp=$(tg_send "$chat_id" "🏓 Ping $label (60 сек)...\nОбновление каждые 10 сек." "") - local mid - mid=$(echo "$resp" | jq -r '.result.message_id // empty') - - local -a results=() - local lost=0 - for i in $(seq 1 60); do - local ms - ms=$(ping -c 1 -W 2 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+') - if [ -n "$ms" ]; then results+=("$ms"); else ((lost++)); fi - - if (( i % 10 == 0 )) && [ -n "$mid" ]; then - local partial="🏓 $label: ${i}/60 сек\nУспешно: ${#results[@]} | Потеряно: $lost" - if [ ${#results[@]} -gt 0 ]; then - local pavg - pavg=$(printf '%s\n' "${results[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}') - partial+="\nСреднее: ${pavg}ms" - fi - tg_edit "$chat_id" "$mid" "$partial" "" > /dev/null - fi - sleep 1 - done - - local summary="🏓 $label (60 сек) — завершён\n" - if [ ${#results[@]} -gt 0 ]; then - local stats - stats=$(printf '%s\n' "${results[@]}" | awk ' - BEGIN{mn=999999;mx=0;s=0} - {s+=$1;if($1mx)mx=$1} - END{printf "%.2f|%.2f|%.2f",mn,mx,s/NR}') - IFS='|' read -r s_min s_max s_avg <<< "$stats" - summary+="Мин: ${s_min}ms\nМакс: ${s_max}ms\nСреднее: ${s_avg}ms\n" - fi - summary+="Потеряно: $lost / 60" - - if [ -n "$mid" ]; then - tg_edit "$chat_id" "$mid" "$summary" "$(kbd_back)" > /dev/null - else - tg_send "$chat_id" "$summary" "$(kbd_back)" > /dev/null - fi - ) & - ;; - - # ── Monitoring ── - mm) - tg_edit "$chat_id" "$msg_id" "📊 Мониторинг\nВыберите действие:" "$(kbd_monitor)" - ;; - ma) - local -a ips=() + mm) [ "$use_send" -eq 1 ] && tg_send "$chat_id" "📊 Мониторинг" "$(kbd_monitor)" > /dev/null \ + || tg_edit "$chat_id" "$msg_id" "📊 Мониторинг" "$(kbd_monitor)" ;; + ma) local -a ips=() while read -r ip; do [ -n "$ip" ] && ips+=("$ip"); done <<< "$(get_target_ips)" - if [ ${#ips[@]} -eq 0 ]; then - tg_edit "$chat_id" "$msg_id" "Нет целевых серверов." "$(kbd_back)" - else - tg_edit "$chat_id" "$msg_id" "📊 Выберите сервер для мониторинга:" "$(build_ip_kbd "ma" "${ips[@]}")" - fi - ;; - ma:*) - local ip="${data#ma:}" - local label - label=$(fmt_ip "$ip") - tg_edit "$chat_id" "$msg_id" "📊 Мониторинг $label\nВыберите интервал проверки:" "$(kbd_intervals "$ip")" - ;; - mi:*) - local rest="${data#mi:}" - local ip="${rest%:*}" - local interval="${rest##*:}" - bot_set_state "$chat_id" "STATE=awaiting_threshold" "MON_IP=$ip" "MON_INTERVAL=$interval" - local label - label=$(fmt_ip "$ip") - tg_edit "$chat_id" "$msg_id" "📊 $label (каждые ${interval}с)\n\nВведите порог уведомления (мс):" "$(kbd_back)" - ;; - mc:*) - local rest="${data#mc:}" - IFS=':' read -r ip interval threshold cooldown <<< "$rest" - add_monitor "$ip" "$interval" "$threshold" "$cooldown" - local label - label=$(fmt_ip "$ip") - tg_edit "$chat_id" "$msg_id" "✅ Мониторинг для $label добавлен.\nИнтервал: ${interval}с | Порог: ${threshold}мс | Уведомл: ${cooldown}с" "$(kbd_back)" - ;; - ml) - local text="📊 Активные мониторы:\n" - local found=0 - for conf in "$MONITOR_DIR"/*.conf; do - [ -f "$conf" ] || continue - found=1 - local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300 - source "$conf" - local label - label=$(fmt_ip "$MON_IP") - text+="$label\n инт: ${MON_INTERVAL}с | порог: ${MON_THRESHOLD}мс | уведомл: ${MON_COOLDOWN}с\n" + [ ${#ips[@]} -eq 0 ] && tg_edit "$chat_id" "$msg_id" "Нет серверов." "$(kbd_back)" \ + || tg_edit "$chat_id" "$msg_id" "📊 Сервер:" "$(build_ip_kbd "ma" "${ips[@]}")" ;; + ma:*) local ip="${data#ma:}"; tg_edit "$chat_id" "$msg_id" "📊 $(fmt_ip_short "$ip")\nИнтервал:" "$(kbd_intervals "$ip")" ;; + mi:*) local rest="${data#mi:}" ip="${rest%:*}" interval="${rest##*:}" + bot_set_state "$chat_id" "STATE=awaiting_threshold" "MON_IP=$ip" "MON_INTERVAL=$interval" + tg_edit "$chat_id" "$msg_id" "📊 $(fmt_ip_short "$ip") (${interval}с)\n\nПорог (мс):" "$(kbd_back)" ;; + mc:*) local rest="${data#mc:}"; IFS=':' read -r ip interval threshold cooldown <<< "$rest" + add_monitor "$ip" "$interval" "$threshold" "$cooldown" + tg_edit "$chat_id" "$msg_id" "✅ $(fmt_ip_short "$ip")\n${interval}с | ${threshold}мс | уведомл: ${cooldown}с" "$(kbd_back)" ;; + ml) local t="📊 Мониторы:\n"; local found=0 + for c in "$MONITOR_DIR"/*.conf; do + [ -f "$c" ] || continue; found=1 + local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" MON_COOLDOWN=300; source "$c" + t+="$(fmt_ip_tg "$MON_IP")\n ${MON_INTERVAL}с | ${MON_THRESHOLD}мс | ${MON_COOLDOWN}с\n" done - [ "$found" -eq 0 ] && text+="Нет настроенных мониторов." - tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_monitor)" - ;; - md) - local -a mon_ips=() - for conf in "$MONITOR_DIR"/*.conf; do - [ -f "$conf" ] || continue - local MON_IP="" - source "$conf" - mon_ips+=("$MON_IP") - done - if [ ${#mon_ips[@]} -eq 0 ]; then - tg_edit "$chat_id" "$msg_id" "Нет мониторов для удаления." "$(kbd_monitor)" - else - tg_edit "$chat_id" "$msg_id" "📊 Выберите монитор для удаления:" "$(build_ip_kbd "md" "${mon_ips[@]}")" - fi - ;; - md:*) - local ip="${data#md:}" - remove_monitor "$ip" - local label - label=$(fmt_ip "$ip") - tg_edit "$chat_id" "$msg_id" "✅ Мониторинг для $label удалён." "$(kbd_monitor)" - ;; + [ "$found" -eq 0 ] && t+="Нет." + tg_edit "$chat_id" "$msg_id" "$t" "$(kbd_monitor)" ;; + md) local -a mi=() + for c in "$MONITOR_DIR"/*.conf; do [ -f "$c" ] || continue; local MON_IP=""; source "$c"; mi+=("$MON_IP"); done + [ ${#mi[@]} -eq 0 ] && tg_edit "$chat_id" "$msg_id" "Нет." "$(kbd_monitor)" \ + || tg_edit "$chat_id" "$msg_id" "📊 Удалить:" "$(build_ip_kbd "md" "${mi[@]}")" ;; + md:*) local ip="${data#md:}"; remove_monitor "$ip" + tg_edit "$chat_id" "$msg_id" "✅ $(fmt_ip_short "$ip") удалён." "$(kbd_monitor)" ;; esac } bot_handle_message() { local chat_id="$1" text="$2" - if [ "$text" = "/start" ] || [ "$text" = "/menu" ]; then - bot_main_menu "$chat_id" - return + if [ "$text" = "/start" ] || [ "$text" = "/menu" ]; then bot_main_menu "$chat_id"; return; fi + if [ "$text" = "/inline" ]; then + save_config_val "MENU_STYLE" "inline" + tg_remove_reply_kb "$chat_id" "Переключено на inline-кнопки." > /dev/null + bot_main_menu "$chat_id"; return fi - local state - state=$(bot_get_state "$chat_id" "STATE") + local style; style=$(get_menu_style) + if [ "$style" = "reply" ]; then + local state; state=$(bot_get_state "$chat_id" "STATE") + if [ -z "$state" ]; then + bot_handle_reply_text "$chat_id" "$text" && return + fi + fi + + local state; state=$(bot_get_state "$chat_id" "STATE") case "$state" in awaiting_ip) if ! validate_ip "$text"; then - tg_send "$chat_id" "❌ Некорректный IP-адрес. Попробуйте ещё раз:" "$(kbd_back)" > /dev/null - return + tg_send "$chat_id" "❌ Некорректный IP. Ещё раз:" "$(kbd_back)" > /dev/null; return fi local proto name custom - proto=$(bot_get_state "$chat_id" "PROTO") - name=$(bot_get_state "$chat_id" "NAME") - custom=$(bot_get_state "$chat_id" "CUSTOM") + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME"); custom=$(bot_get_state "$chat_id" "CUSTOM") + + local probe_msg; probe_msg=$(tg_send "$chat_id" "🔍 Проверяю $text..." "") + local probe_mid; probe_mid=$(echo "$probe_msg" | jq -r '.result.message_id // empty') + local probe_result; probe_result=$(probe_server_tg "$text") + local info_text="IP: $text ✅\n${probe_result}\nВведите имя сервера (или - — пропустить):" + if [ -n "$probe_mid" ]; then + tg_edit "$chat_id" "$probe_mid" "$info_text" "$(kbd_back)" > /dev/null + else + tg_send "$chat_id" "$info_text" "$(kbd_back)" > /dev/null + fi bot_set_state "$chat_id" "STATE=awaiting_name" "PROTO=$proto" "NAME=$name" "CUSTOM=$custom" "TARGET_IP=$text" - tg_send "$chat_id" "IP: $text ✅\n\nВведите имя сервера (или - чтобы пропустить):" "$(kbd_back)" > /dev/null ;; awaiting_name) local proto name custom target_ip - proto=$(bot_get_state "$chat_id" "PROTO") - name=$(bot_get_state "$chat_id" "NAME") - custom=$(bot_get_state "$chat_id" "CUSTOM") - target_ip=$(bot_get_state "$chat_id" "TARGET_IP") - if [ "$text" != "-" ] && [ -n "$text" ]; then - set_alias "$target_ip" "$text" - fi + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME") + custom=$(bot_get_state "$chat_id" "CUSTOM"); target_ip=$(bot_get_state "$chat_id" "TARGET_IP") + if [ "$text" != "-" ] && [ -n "$text" ]; then set_alias "$target_ip" "$text"; fi + bot_set_state "$chat_id" "STATE=awaiting_note" "PROTO=$proto" "NAME=$name" "CUSTOM=$custom" "TARGET_IP=$target_ip" + tg_send "$chat_id" "Примечание (или - — пропустить):" "$(kbd_back)" > /dev/null + ;; + awaiting_note) + local proto name custom target_ip + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME") + custom=$(bot_get_state "$chat_id" "CUSTOM"); target_ip=$(bot_get_state "$chat_id" "TARGET_IP") + if [ "$text" != "-" ] && [ -n "$text" ]; then set_alias_note "$target_ip" "$text"; fi if [ "$custom" = "1" ]; then bot_set_state "$chat_id" "STATE=awaiting_in_port" "PROTO=$proto" "NAME=$name" "CUSTOM=1" "TARGET_IP=$target_ip" - tg_send "$chat_id" "Сервер: $(fmt_ip "$target_ip")\n\nВведите ВХОДЯЩИЙ порт:" "$(kbd_back)" > /dev/null + tg_send "$chat_id" "Сервер: $(fmt_ip_short "$target_ip")\n\nВХОДЯЩИЙ порт:" "$(kbd_back)" > /dev/null else bot_set_state "$chat_id" "STATE=awaiting_port" "PROTO=$proto" "NAME=$name" "CUSTOM=0" "TARGET_IP=$target_ip" - tg_send "$chat_id" "Сервер: $(fmt_ip "$target_ip")\n\nВведите порт:" "$(kbd_back)" > /dev/null - fi - ;; + tg_send "$chat_id" "Сервер: $(fmt_ip_short "$target_ip")\n\nВведите порт:" "$(kbd_back)" > /dev/null + fi ;; awaiting_port) - if ! validate_port "$text"; then - tg_send "$chat_id" "❌ Некорректный порт (1-65535)." "" > /dev/null - return - fi + if ! validate_port "$text"; then tg_send "$chat_id" "❌ Порт (1-65535)." "" > /dev/null; return; fi local proto name target_ip - proto=$(bot_get_state "$chat_id" "PROTO") - name=$(bot_get_state "$chat_id" "NAME") - target_ip=$(bot_get_state "$chat_id" "TARGET_IP") + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME"); target_ip=$(bot_get_state "$chat_id" "TARGET_IP") bot_clear_state "$chat_id" apply_iptables_rules "$proto" "$text" "$text" "$target_ip" "$name" - local label - label=$(fmt_ip "$target_ip") - tg_send "$chat_id" "✅ $name настроен!\n$proto ${MY_IP:-*}:$text → $target_ip:$text\nСервер: $label" "$(kbd_back)" > /dev/null - ;; + tg_send "$chat_id" "✅ $name\n$proto ${MY_IP:-*}:$text → $target_ip:$text\n$(fmt_ip_tg "$target_ip")" "$(kbd_back)" > /dev/null ;; awaiting_in_port) - if ! validate_port "$text"; then - tg_send "$chat_id" "❌ Некорректный порт (1-65535)." "" > /dev/null - return - fi + if ! validate_port "$text"; then tg_send "$chat_id" "❌ Порт." "" > /dev/null; return; fi local proto name target_ip - proto=$(bot_get_state "$chat_id" "PROTO") - name=$(bot_get_state "$chat_id" "NAME") - target_ip=$(bot_get_state "$chat_id" "TARGET_IP") + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME"); target_ip=$(bot_get_state "$chat_id" "TARGET_IP") bot_set_state "$chat_id" "STATE=awaiting_out_port" "PROTO=$proto" "NAME=$name" "CUSTOM=1" "TARGET_IP=$target_ip" "IN_PORT=$text" - tg_send "$chat_id" "Входящий порт: $text ✅\n\nВведите ИСХОДЯЩИЙ порт:" "$(kbd_back)" > /dev/null - ;; + tg_send "$chat_id" "Вход: $text ✅\n\nИСХОДЯЩИЙ порт:" "$(kbd_back)" > /dev/null ;; awaiting_out_port) - if ! validate_port "$text"; then - tg_send "$chat_id" "❌ Некорректный порт (1-65535)." "" > /dev/null - return - fi + if ! validate_port "$text"; then tg_send "$chat_id" "❌ Порт." "" > /dev/null; return; fi local proto name target_ip in_port - proto=$(bot_get_state "$chat_id" "PROTO") - name=$(bot_get_state "$chat_id" "NAME") - target_ip=$(bot_get_state "$chat_id" "TARGET_IP") - in_port=$(bot_get_state "$chat_id" "IN_PORT") + proto=$(bot_get_state "$chat_id" "PROTO"); name=$(bot_get_state "$chat_id" "NAME") + target_ip=$(bot_get_state "$chat_id" "TARGET_IP"); in_port=$(bot_get_state "$chat_id" "IN_PORT") bot_clear_state "$chat_id" apply_iptables_rules "$proto" "$in_port" "$text" "$target_ip" "$name" - local label - label=$(fmt_ip "$target_ip") - tg_send "$chat_id" "✅ Custom Rule настроен!\n$proto ${MY_IP:-*}:$in_port → $target_ip:$text\nСервер: $label" "$(kbd_back)" > /dev/null - ;; + tg_send "$chat_id" "✅ Custom\n$proto ${MY_IP:-*}:$in_port → $target_ip:$text\n$(fmt_ip_tg "$target_ip")" "$(kbd_back)" > /dev/null ;; awaiting_threshold) - if ! validate_port "$text"; then - tg_send "$chat_id" "❌ Введите число от 1 до 65535 (мс):" "" > /dev/null - return - fi + if ! validate_port "$text"; then tg_send "$chat_id" "❌ Число (1-65535):" "" > /dev/null; return; fi local mon_ip mon_interval - mon_ip=$(bot_get_state "$chat_id" "MON_IP") - mon_interval=$(bot_get_state "$chat_id" "MON_INTERVAL") + mon_ip=$(bot_get_state "$chat_id" "MON_IP"); mon_interval=$(bot_get_state "$chat_id" "MON_INTERVAL") bot_clear_state "$chat_id" - local label - label=$(fmt_ip "$mon_ip") - tg_send "$chat_id" "📊 $label\nИнтервал: ${mon_interval}с | Порог: ${text}мс\n\nВыберите частоту уведомлений:" "$(kbd_cooldowns "$mon_ip" "$mon_interval" "$text")" > /dev/null - ;; - *) - tg_send "$chat_id" "Используйте /start или /menu для вызова меню." "" > /dev/null - ;; + tg_send "$chat_id" "📊 $(fmt_ip_short "$mon_ip")\n${mon_interval}с | ${text}мс\n\nЧастота уведомлений:" "$(kbd_cooldowns "$mon_ip" "$mon_interval" "$text")" > /dev/null ;; + *) tg_send "$chat_id" "/start или /menu" "" > /dev/null ;; esac } # ─── Bot daemon ─────────────────────────────────────────────── bot_daemon() { - log_action "Bot daemon started (PID $$)" - echo $$ > "$BOT_PID_FILE" - + log_action "Bot daemon started (PID $$)"; echo $$ > "$BOT_PID_FILE" source "$KASKAD_CONF" - if [ -z "$BOT_TOKEN" ]; then - log_action "BOT ERROR: BOT_TOKEN is empty" - exit 1 - fi - - detect_interface - get_my_ip - + [ -z "$BOT_TOKEN" ] && log_action "BOT ERROR: no token" && exit 1 + detect_interface; get_my_ip 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) - - if [ -z "$response" ]; then sleep 2; continue; fi - - local ok - ok=$(echo "$response" | jq -r '.ok // "false"') - if [ "$ok" != "true" ]; then sleep 5; continue; fi - - local count - count=$(echo "$response" | jq '.result | length') - - for (( i=0; i /dev/null - continue - fi - - bot_handle_callback "$cb_chat_id" "$cb_msg_id" "$cb_id" "$cb_data" + 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 msg_chat_id msg_text - msg_chat_id=$(echo "$update" | jq -r '.message.chat.id // empty') - msg_text=$(echo "$update" | jq -r '.message.text // empty') - - if [ -n "$msg_chat_id" ] && [ -n "$msg_text" ]; then - if [ -n "$BOT_CHAT_ID" ] && [ "$msg_chat_id" != "$BOT_CHAT_ID" ]; then - tg_send "$msg_chat_id" "⛔ Нет доступа.\nВаш Chat ID: $msg_chat_id" "" > /dev/null - continue - fi - bot_handle_message "$msg_chat_id" "$msg_text" + 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 + bot_handle_message "$mci" "$mtx" fi fi done @@ -1549,10 +1311,8 @@ bot_daemon() { start_bot() { source "$KASKAD_CONF" - if [ -z "$BOT_TOKEN" ]; then echo -e "${RED}Сначала задайте BOT_TOKEN!${NC}"; return; fi - if [ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE")" 2>/dev/null; then - echo -e "${YELLOW}Бот уже запущен.${NC}"; return - fi + [ -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/kaskad-bot.service < /dev/null 2>&1 - systemctl start kaskad-bot - sleep 1 - if systemctl is-active kaskad-bot &>/dev/null; then - echo -e "${GREEN}[OK] Бот запущен.${NC}" - log_action "Bot service started" - else - echo -e "${RED}[ERROR] Проверьте: journalctl -u kaskad-bot${NC}" - fi + systemctl daemon-reload; systemctl enable kaskad-bot > /dev/null 2>&1; systemctl start kaskad-bot; sleep 1 + systemctl is-active kaskad-bot &>/dev/null && echo -e "${GREEN}[OK] Бот запущен.${NC}" && log_action "Bot started" \ + || echo -e "${RED}[ERROR] journalctl -u kaskad-bot${NC}" } stop_bot() { - systemctl stop kaskad-bot 2>/dev/null - systemctl disable kaskad-bot 2>/dev/null - rm -f "$BOT_PID_FILE" - echo -e "${GREEN}[OK] Бот остановлен.${NC}" - log_action "Bot service stopped" + systemctl stop kaskad-bot 2>/dev/null; systemctl disable kaskad-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 "$KASKAD_CONF" 2>/dev/null - local bot_status="${RED}Остановлен${NC}" - if [ -f "$BOT_PID_FILE" ] && kill -0 "$(cat "$BOT_PID_FILE" 2>/dev/null)" 2>/dev/null; then - bot_status="${GREEN}Работает (PID $(cat "$BOT_PID_FILE"))${NC}" - fi - local token_display="не задан" - [ -n "${BOT_TOKEN:-}" ] && token_display="***${BOT_TOKEN: -6}" - local ut_val - ut_val=$(bot_get_state "system" "UPDATE_TOKEN" 2>/dev/null) - local ut_display="не задан" - [ -n "$ut_val" ] && ut_display="***${ut_val: -6}" - + clear; source "$KASKAD_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}" + local ut; ut=$(bot_get_state "system" "UPDATE_TOKEN" 2>/dev/null); local ud="нет"; [ -n "$ut" ] && ud="***${ut: -6}" echo -e "${CYAN}━━━ Telegram Bot ━━━${NC}" - echo -e "Статус: $bot_status" - echo -e "Токен: ${YELLOW}$token_display${NC}" - echo -e "Chat ID: ${YELLOW}${BOT_CHAT_ID:-не задан}${NC}" - echo -e "Update Token: ${YELLOW}$ut_display${NC}" - echo -e "Меню: ${YELLOW}${MENU_STYLE:-compact}${NC}" - echo "" - 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 "6) Токен обновления (GitHub PAT)" - echo -e "0) Назад" - read -p "Выбор: " choice - - case $choice in - 1) echo "Введите токен (от @BotFather):"; read -p "> " t; [ -n "$t" ] && save_config_val "BOT_TOKEN" "$t" && echo -e "${GREEN}OK${NC}"; read -p "Enter..." ;; - 2) - [ -z "${BOT_TOKEN:-}" ] && echo -e "${RED}Сначала токен!${NC}" && read -p "Enter..." && continue - echo -e "${YELLOW}Отправьте боту сообщение в Telegram, затем Enter.${NC}"; read -p "" - local cid; cid=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=1&offset=-1" | jq -r '.result[0].message.chat.id // empty') - [ -n "$cid" ] && save_config_val "BOT_CHAT_ID" "$cid" && echo -e "${GREEN}Chat ID: $cid${NC}" || echo -e "${RED}Не удалось.${NC}" - read -p "Enter..." ;; - 3) echo "Chat ID:"; read -p "> " c; [ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && echo -e "${GREEN}OK${NC}"; read -p "Enter..." ;; - 4) start_bot; read -p "Enter..." ;; - 5) stop_bot; read -p "Enter..." ;; - 6) echo "GitHub PAT:"; read -p "> " u; [ -n "$u" ] && bot_set_state "system" "UPDATE_TOKEN=$u" && echo -e "${GREEN}OK${NC}"; read -p "Enter..." ;; + echo -e "Статус: $bs\nТокен: ${YELLOW}$td${NC}\nChat ID: ${YELLOW}${BOT_CHAT_ID:-нет}${NC}\nUpdate: ${YELLOW}$ud${NC}\nМеню: ${YELLOW}${MENU_STYLE:-inline}${NC}\n" + echo -e "1) Токен бота\n2) Chat ID (авто)\n3) Chat ID (вручную)\n4) ${GREEN}Запустить${NC}\n5) ${RED}Остановить${NC}\n6) GitHub PAT\n0) Назад" + read -p "Выбор: " ch + case $ch in + 1) echo "Токен:"; read -p "> " t; [ -n "$t" ] && save_config_val "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" && echo -e "${GREEN}$c${NC}" || echo -e "${RED}Нет.${NC}"; read -p "Enter..." ;; + 3) echo "ID:"; read -p "> " c; [ -n "$c" ] && save_config_val "BOT_CHAT_ID" "$c" && echo -e "${GREEN}OK${NC}"; read -p "Enter..." ;; + 4) start_bot; read -p "Enter..." ;; 5) stop_bot; read -p "Enter..." ;; + 6) echo "PAT:"; read -p "> " u; [ -n "$u" ] && bot_set_state "system" "UPDATE_TOKEN=$u" && echo -e "${GREEN}OK${NC}"; read -p "Enter..." ;; 0) return ;; esac done } # ═══════════════════════════════════════════════════════════════ -# PROMO & INSTRUCTIONS (kept from previous version) +# PROMO & INSTRUCTIONS # ═══════════════════════════════════════════════════════════════ show_promo() { - clear - echo "" + clear; echo "" echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${MAGENTA}║ ХОСТИНГ, КОТОРЫЙ РАБОТАЕТ СО СКИДКОЙ ДО -60% ║${NC}" + echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ║${NC}" echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - echo -e "${CYAN}🌍 ЛОКАЦИИ: РФ И ЕВРОПА${NC}" - echo -e "${WHITE} >>> https://vk.cc/ct29NQ${NC}" - printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка на первый месяц" - printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka20" "Буст 20% + 3% (при оплате за 3 мес)" - printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka6" "Буст 15% + 5% (при оплате за 6 мес)" - printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka12" "Буст 5% + 5% (при оплате за 12 мес)" - echo -e "\n${CYAN}🇧🇾 ЛОКАЦИЯ: БЕЛАРУСЬ${NC}" - echo -e "${WHITE} >>> https://vk.cc/cUxAhj${NC}" - printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "60% скидка на первый месяц" - echo "" - echo -e "\n${YELLOW}Генерация QR-кода... (3 сек)${NC}" - for i in {3..1}; do echo -ne "$i..."; sleep 1; done; echo "" - echo -e "\n${WHITE}" - command -v qrencode &>/dev/null && qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ" || echo "Используйте ссылки выше." - echo -e "${NC}" - echo -e "${GREEN}Сканируйте камерой телефона!${NC}" - echo "" + 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-код... (3с)${NC}"; for i in 3 2 1; do echo -ne "$i..."; sleep 1; done; echo "" + echo -e "\n${WHITE}"; command -v qrencode &>/dev/null && qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ" || echo "Ссылки выше."; echo -e "${NC}" read -p "Нажмите Enter..." } show_instructions() { - local page=1 total_pages=7 + local p=1 tp=7 while true; do - clear - echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${MAGENTA}║ 📚 ИНСТРУКЦИЯ KASKAD PRO v${KASKAD_VERSION} (стр. ${page}/${total_pages}) ║${NC}" - echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - case $page in - 1) echo -e "${CYAN}═══ ЧТО ТАКОЕ КАСКАД? ═══${NC}\n\nКаскад — 'мост' между устройством и зарубежным VPN/Proxy.\n\n ${WHITE}Клиент${NC} → ${GREEN}Этот сервер (РФ)${NC} → ${CYAN}Зарубежный VPN${NC} → Интернет\n\nПровайдер видит только подключение к серверу в РФ.\n\n${CYAN}═══ ЧТО НУЖНО ═══${NC}\n 1. VPS в РФ\n 2. Зарубежный VPN/Proxy\n 3. IP и порт зарубежного сервера" ;; - 2) echo -e "${CYAN}═══ ПУНКТ 1: AWG/WG (UDP) ═══${NC}\n\n Меню → 1 → IP: 45.10.20.30 → Порт: 51820\n В клиенте Endpoint: ${MY_IP:-185.1.2.3}:51820\n\n${CYAN}═══ ПУНКТ 2: VLESS (TCP) ═══${NC}\n\n Меню → 2 → IP: 67.89.100.200 → Порт: 443\n В v2rayNG/NekoBox замените адрес сервера" ;; - 3) echo -e "${CYAN}═══ ПУНКТ 3: MTProto ═══${NC}\n\n Меню → 3 → IP → Порт: 8443\n Telegram → Прокси → ${MY_IP:-*}:8443\n\n${CYAN}═══ ПУНКТ 4: Custom ═══${NC}\n\n Разные порты входа/выхода, SSH, RDP\n Пример SSH: tcp, IP, вход: 2222, выход: 22\n Подключение: ssh user@${MY_IP:-*} -p 2222" ;; - 4) echo -e "${CYAN}═══ ПРАВИЛА И PING ═══${NC}\n\nПункт 5 — таблица правил с IP каскада и именами серверов\nПункт 6 — Live Ping (1 сек обновление, Ctrl+C стоп)\nПункт 14 — управление именами серверов" ;; - 5) echo -e "${CYAN}═══ МОНИТОРИНГ ═══${NC}\n\nАвтопроверка серверов с Telegram-алертами.\n\n Меню → 7 → Добавить → сервер → интервал → порог → частота уведомл.\n\nСлужба запускается/останавливается автоматически.\nЧастота уведомлений: 10с / 60с / 5мин / 15мин" ;; - 6) echo -e "${CYAN}═══ TELEGRAM BOT ═══${NC}\n\n Шаг A. @BotFather → /newbot → получить токен\n Шаг B. Меню → 8 → 1 → вставить токен\n Шаг C. Меню → 8 → 2 → отправить боту сообщение → Enter\n Шаг D. Меню → 8 → 4 → Запустить\n Шаг E. В Telegram: /start\n\nБот имеет 2 стиля меню: компактный и большой (кнопка внизу)" ;; - 7) echo -e "${CYAN}═══ ВОЗМОЖНОСТИ БОТА ═══${NC}\n\n Добавление/удаление правил через кнопки\n Ping: 1x / 10x (среднее) / 60 сек\n Мониторинг: добавить/удалить/список\n 💻 Система: CPU, RAM, Swap, диск, топ процессов\n 🏢 Хостинг: промокоды партнёров\n Имена серверов отображаются везде\n\n Пункты 9-13: удаление, сброс, обновление, промо, инструкция" ;; + clear; echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${MAGENTA}║ 📚 KASKAD PRO v${KASKAD_VERSION} (${p}/${tp}) ║${NC}" + echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}\n" + case $p in + 1) echo -e "${CYAN}═══ ЧТО ТАКОЕ КАСКАД ═══${NC}\n\nМост: Клиент → Этот сервер → Зарубежный VPN → Интернет\nПровайдер видит только РФ IP.\n\nНужно: VPS в РФ + зарубежный VPN + IP:Порт" ;; + 2) echo -e "${CYAN}═══ AWG/VLESS/MTProto ═══${NC}\n\n1) AWG: UDP, порт 51820\n2) VLESS: TCP, порт 443\n3) MTProto: TCP, порт 8443\n\nПосле ввода IP — автоматический GeoIP + пинг-тест" ;; + 3) echo -e "${CYAN}═══ CUSTOM / ПРАВИЛА ═══${NC}\n\n4) Custom: разные порты, SSH/RDP\n5) Правила: таблица с IP каскада + GeoIP\n14) Имена серверов + примечания" ;; + 4) echo -e "${CYAN}═══ PING ═══${NC}\n\n6) Live Ping с ASCII-графиком:\n ▓▓▓░░░░░ — зелёный (<50ms)\n ▓▓▓▓▓▓░░ — жёлтый (50-100ms)\n ▓▓▓▓▓▓▓▓ — красный (>100ms)\n ████████ — TIMEOUT" ;; + 5) echo -e "${CYAN}═══ МОНИТОРИНГ ═══${NC}\n\n7) Автопроверка с алертами в Telegram\nИнтервалы: 10с / 1мин / 5мин\nУведомления: 10с / 60с / 5мин / 15мин\nСлужба авто-запуск/стоп" ;; + 6) echo -e "${CYAN}═══ TELEGRAM BOT ═══${NC}\n\n8) @BotFather → токен → Chat ID → запуск\nДва режима меню:\n Inline — кнопки под сообщением\n Reply — большие кнопки внизу экрана\nПереключение: кнопка в меню или /inline" ;; + 7) echo -e "${CYAN}═══ ВОЗМОЖНОСТИ БОТА ═══${NC}\n\nПравила, Ping (1x/10x/60с), Мониторинг\n💻 Система: CPU/RAM/Swap/Disk/процессы\n🏢 Хостинг: промокоды\nGeoIP + пинг-тест при добавлении\nИмена + примечания серверов" ;; esac echo -e "\n${MAGENTA}──────────────────────────────────────────────────────────────${NC}" - if [ "$page" -eq 1 ]; then echo -e " ${YELLOW}[N]${NC} Далее ${YELLOW}[0]${NC} Выход" - elif [ "$page" -eq "$total_pages" ]; then echo -e " ${YELLOW}[P]${NC} Назад ${YELLOW}[0]${NC} Выход" - else echo -e " ${YELLOW}[P]${NC} Назад ${YELLOW}[N]${NC} Далее ${YELLOW}[0]${NC} Выход"; fi + [ "$p" -eq 1 ] && echo -e " ${YELLOW}[N]${NC} Далее ${YELLOW}[0]${NC} Выход" + [ "$p" -eq "$tp" ] && echo -e " ${YELLOW}[P]${NC} Назад ${YELLOW}[0]${NC} Выход" + (( p > 1 && p < tp )) && echo -e " ${YELLOW}[P]${NC} Назад ${YELLOW}[N]${NC} Далее ${YELLOW}[0]${NC} Выход" read -p " > " nav - case "$nav" in - [nN]) (( page < total_pages )) && ((page++)) ;; [pP]) (( page > 1 )) && ((page--)) ;; 0) return ;; [1-7]) page="$nav" ;; esac + case "$nav" in [nN]) ((p1)) && ((p--));; 0) return;; [1-7]) p="$nav";; esac done } @@ -1698,52 +1408,35 @@ show_instructions() { show_menu() { while true; do clear - echo -e "${MAGENTA}" - echo "******************************************************" - echo " anten-ka канал представляет... Kaskad PRO v${KASKAD_VERSION}" + echo -e "${MAGENTA}******************************************************" + echo " anten-ka · Kaskad PRO v${KASKAD_VERSION}" echo " YouTube: https://www.youtube.com/@antenkaru" - echo "******************************************************" - echo -e "${NC}" - echo -e "${WHITE}IP сервера: ${GREEN}${MY_IP}${NC} ${WHITE}Интерфейс: ${CYAN}${IFACE}${NC}" - echo -e "${YELLOW}Инструкции:${NC} ${BLUE}https://boosty.to/anten-ka${NC}" - echo -e "${GREEN}Донат:${NC} https://pay.cloudtips.ru/p/7410814f" + echo -e "******************************************************${NC}" + echo -e "${WHITE}IP: ${GREEN}${MY_IP}${NC} ${WHITE}Iface: ${CYAN}${IFACE}${NC}" echo -e "------------------------------------------------------" - echo -e " 1) Настроить ${CYAN}AmneziaWG / WireGuard${NC} (UDP)" - echo -e " 2) Настроить ${CYAN}VLESS / XRay${NC} (TCP)" - echo -e " 3) Настроить ${CYAN}TProxy / MTProto${NC} (TCP)" - echo -e " 4) 🛠 Создать ${YELLOW}Кастомное правило${NC}" - echo -e " 5) 📋 Активные правила" - echo -e " 6) 🏓 ${CYAN}Ping сервера (live)${NC}" + echo -e " 1) ${CYAN}AmneziaWG / WireGuard${NC} (UDP)" + echo -e " 2) ${CYAN}VLESS / XRay${NC} (TCP)" + echo -e " 3) ${CYAN}TProxy / MTProto${NC} (TCP)" + echo -e " 4) 🛠 ${YELLOW}Кастомное правило${NC}" + echo -e " 5) 📋 Правила" + echo -e " 6) 🏓 ${CYAN}Ping (live)${NC}" echo -e " 7) 📊 ${CYAN}Мониторинг${NC}" echo -e " 8) 🤖 ${CYAN}Telegram Bot${NC}" - echo -e " 9) ${RED}Удалить одно правило${NC}" - echo -e "10) ${RED}Сбросить правила Kaskad${NC}" + echo -e " 9) ${RED}Удалить правило${NC}" + echo -e "10) ${RED}Сбросить всё${NC}" echo -e "11) ${YELLOW}Обновить скрипт${NC}" echo -e "12) ${YELLOW}PROMO${NC}" echo -e "13) ${MAGENTA}📚 Инструкция${NC}" echo -e "14) ${WHITE}Имена серверов${NC}" echo -e " 0) Выход" echo -e "------------------------------------------------------" - read -p "Ваш выбор: " choice - - case $choice in - 1) configure_rule "udp" "AmneziaWG" ;; - 2) configure_rule "tcp" "VLESS" ;; - 3) configure_rule "tcp" "MTProto/TProxy" ;; - 4) configure_custom_rule ;; - 5) list_active_rules ;; - 6) ping_menu ;; - 7) monitoring_menu ;; - 8) bot_menu ;; - 9) delete_single_rule ;; - 10) flush_rules ;; - 11) self_update ;; - 12) show_promo ;; - 13) show_instructions ;; - 14) manage_aliases_menu ;; - 0) exit 0 ;; - *) ;; - esac + read -p "Выбор: " ch + case $ch in + 1) configure_rule "udp" "AmneziaWG";; 2) configure_rule "tcp" "VLESS";; + 3) configure_rule "tcp" "MTProto/TProxy";; 4) configure_custom_rule;; + 5) list_active_rules;; 6) ping_menu;; 7) monitoring_menu;; 8) bot_menu;; + 9) delete_single_rule;; 10) flush_rules;; 11) self_update;; 12) show_promo;; + 13) show_instructions;; 14) manage_aliases_menu;; 0) exit 0;; esac done } @@ -1752,21 +1445,7 @@ show_menu() { # ═══════════════════════════════════════════════════════════════ case "${1:-}" in - --bot-daemon) - init_config - bot_daemon - ;; - --monitor-daemon) - init_config - monitor_daemon - ;; - *) - check_root - init_config - prepare_system - detect_interface - get_my_ip - show_promo - show_menu - ;; + --bot-daemon) init_config; bot_daemon ;; + --monitor-daemon) init_config; monitor_daemon ;; + *) check_root; init_config; prepare_system; detect_interface; get_my_ip; show_promo; show_menu ;; esac