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:\nPID 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${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($1$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$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 <