Files
kaskad-pro/install.sh

1773 lines
76 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -o pipefail
# ══════════════════════════════════════════════════════════════
# KASKAD PRO v2.1 — Cascading VPN / Proxy Manager
# Telegram Bot · Live Ping · Monitoring · Alerts · System Stats
# Channel: https://www.youtube.com/@antenkaru
# ══════════════════════════════════════════════════════════════
KASKAD_VERSION="2.1"
KASKAD_DIR="/etc/kaskad"
KASKAD_CONF="$KASKAD_DIR/config"
KASKAD_LOG="/var/log/kaskad.log"
MONITOR_DIR="$KASKAD_DIR/monitors"
ALIASES_FILE="$KASKAD_DIR/aliases"
BOT_STATE_DIR="$KASKAD_DIR/bot_state"
BOT_PID_FILE="/var/run/kaskad_bot.pid"
MONITOR_PID_FILE="/var/run/kaskad_monitor.pid"
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'
YELLOW='\033[1;33m'; MAGENTA='\033[0;35m'; WHITE='\033[1;37m'
BLUE='\033[0;34m'; NC='\033[0m'
IFACE=""
MY_IP=""
BOT_TOKEN=""
BOT_CHAT_ID=""
MENU_STYLE=""
# ─── Config ───────────────────────────────────────────────────
init_config() {
mkdir -p "$KASKAD_DIR" "$MONITOR_DIR" "$BOT_STATE_DIR"
touch "$ALIASES_FILE"
if [ ! -f "$KASKAD_CONF" ]; then
cat > "$KASKAD_CONF" <<'CONF'
BOT_TOKEN=""
BOT_CHAT_ID=""
MENU_STYLE="compact"
CONF
fi
source "$KASKAD_CONF"
}
save_config_val() {
local key="$1" value="$2"
if grep -q "^${key}=" "$KASKAD_CONF" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=\"${value}\"|" "$KASKAD_CONF"
else
echo "${key}=\"${value}\"" >> "$KASKAD_CONF"
fi
source "$KASKAD_CONF"
}
# ─── Aliases (server names) ──────────────────────────────────
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
}
get_alias() {
local ip="$1"
grep "^${ip}=" "$ALIASES_FILE" 2>/dev/null | head -1 | cut -d= -f2-
}
fmt_ip() {
local ip="$1"
local alias
alias=$(get_alias "$ip")
if [ -n "$alias" ]; then
echo "${alias} (${ip})"
else
echo "$ip"
fi
}
# ─── Logging ──────────────────────────────────────────────────
log_action() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$KASKAD_LOG"
}
# ─── Validation ───────────────────────────────────────────────
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
return 0
fi
return 1
}
validate_port() {
[[ "$1" =~ ^[0-9]+$ ]] && (( $1 >= 1 && $1 <= 65535 ))
}
read_validated_ip() {
local prompt="${1:-Введите IP адрес назначения:}"
while true; do
echo -e "$prompt"
read -p "> " _RET_IP
if validate_ip "$_RET_IP"; then return 0; fi
echo -e "${RED}Ошибка: некорректный IP-адрес!${NC}"
done
}
read_validated_port() {
local prompt="${1:-Введите порт:}"
while true; do
echo -e "$prompt"
read -p "> " _RET_PORT
if validate_port "$_RET_PORT"; then return 0; fi
echo -e "${RED}Ошибка: порт должен быть числом от 1 до 65535!${NC}"
done
}
read_server_name() {
local ip="$1"
local existing
existing=$(get_alias "$ip")
if [ -n "$existing" ]; then
echo -e "Текущее имя для $ip: ${GREEN}$existing${NC}"
fi
echo -e "Введите имя сервера (или Enter — пропустить):"
read -p "> " _RET_NAME
if [ -n "$_RET_NAME" ]; then
set_alias "$ip" "$_RET_NAME"
fi
}
# ─── System ───────────────────────────────────────────────────
check_root() {
if [ "$EUID" -ne 0 ]; then
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
}
get_my_ip() {
MY_IP=$(curl -s4 --max-time 5 ifconfig.me 2>/dev/null || echo "N/A")
}
save_iptables() {
if command -v netfilter-persistent &>/dev/null; then
netfilter-persistent save > /dev/null 2>&1
elif command -v service &>/dev/null; then
service iptables save > /dev/null 2>&1
fi
}
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"
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
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
apt-get install -y iptables-persistent netfilter-persistent qrencode jq curl procps > /dev/null 2>&1
elif command -v dnf &>/dev/null; then
dnf install -y iptables-services jq qrencode curl procps-ng > /dev/null 2>&1
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
fi
fi
}
# ─── System Stats ─────────────────────────────────────────────
get_system_stats() {
local cpu_line load_avg mem_info disk_info uptime_str top_procs
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+="<b>📊 Системная информация</b>\n\n"
result+="<b>Uptime:</b> ${uptime_str}\n"
result+="<b>CPU:</b> ${cpu_line} ядер | загрузка: ${cpu_usage}%\n"
result+="<b>Load Avg:</b> ${load_avg}\n"
result+="<b>RAM:</b> ${mem_info}\n"
result+="<b>Swap:</b> ${swap_info}\n"
result+="<b>Disk /:</b> ${disk_info}\n\n"
result+="<b>Топ процессов (CPU):</b>\n"
result+="<pre>PID CPU% MEM% CMD\n"
result+="${top_procs}</pre>"
echo "$result"
}
# ─── iptables helpers ─────────────────────────────────────────
get_rules_list() {
iptables -t nat -S PREROUTING 2>/dev/null | grep "DNAT" | while read -r line; do
local port proto dest
port=$(echo "$line" | grep -oP '(?<=--dport )\d+')
proto=$(echo "$line" | grep -oP '(?<=-p )\w+')
dest=$(echo "$line" | grep -oP '(?<=--to-destination )[\d.:]+')
[ -n "$port" ] && echo "${proto}|${port}|${dest}"
done
}
get_target_ips() {
get_rules_list | awk -F'|' '{split($3,a,":"); print a[1]}' | sort -u
}
remove_rules_for_port() {
local proto="$1" in_port="$2"
iptables -t nat -S PREROUTING 2>/dev/null | grep "DNAT" | grep -P "\b--dport ${in_port}\b" | grep -P "\b-p ${proto}\b" | while read -r rule; do
eval "iptables -t nat -D ${rule#-A }" 2>/dev/null
done
iptables -S INPUT 2>/dev/null | grep "kaskad" | grep -P "\b--dport ${in_port}\b" | grep -P "\b-p ${proto}\b" | while read -r rule; do
eval "iptables -D ${rule#-A }" 2>/dev/null
done
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
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"
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
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"
}
# ─── Interactive rule configuration ──────────────────────────
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
read_validated_port "Введите Порт (одинаковый для входа и выхода):"
local port="$_RET_PORT"
echo -e "\n${YELLOW}Будет создано правило:${NC}"
echo -e " $proto: ${MY_IP:-*}:$port -> $(fmt_ip "$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}):"
read -p "> " proto
[[ "$proto" == "tcp" || "$proto" == "udp" ]] && break
echo -e "${RED}Ошибка: введите tcp или udp!${NC}"
done
read_validated_ip "Введите IP адрес назначения (куда отправляем трафик):"
local target_ip="$_RET_IP"
read_server_name "$target_ip"
check_target_reachable "$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"
read -p "Применить? (y/n): " confirm
[[ "$confirm" != "y" ]] && return
apply_iptables_rules "$proto" "$in_port" "$out_port" "$target_ip" "Custom Rule"
read -p "Нажмите Enter для возврата в меню..."
}
# ─── List / Delete / Flush ────────────────────────────────────
list_active_rules() {
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
echo -e "${YELLOW}Нет активных правил.${NC}"
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"
done
fi
echo ""
read -p "Нажмите Enter..."
}
delete_single_rule() {
echo -e "\n${CYAN}--- Удаление правила ---${NC}"
local -a rules_arr=()
local i=1
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"
((i++))
done <<< "$(get_rules_list)"
if [ ${#rules_arr[@]} -eq 0 ]; then
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
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
save_iptables
log_action "DELETE rule: $d_proto :$d_port -> $d_dest"
echo -e "${GREEN}[OK] Правило удалено.${NC}"
read -p "Нажмите Enter..."
}
flush_rules() {
echo -e "\n${RED}!!! ВНИМАНИЕ !!!${NC}"
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)
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)
eval "iptables -D ${rule#-A }" 2>/dev/null
done
done
save_iptables
log_action "FLUSH all kaskad rules"
echo -e "${GREEN}[OK] Правила Kaskad очищены.${NC}"
fi
read -p "Нажмите Enter..."
}
manage_aliases_menu() {
while true; do
clear
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
for i in "${!ips[@]}"; do
echo -e " ${YELLOW}[$((i+1))]${NC} ${ips[$i]}${GREEN}$(get_alias "${ips[$i]}" || echo "без имени")${NC}"
done
echo -e " ${YELLOW}[0]${NC} Назад"
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..."
done
}
# ─── Auto-update ──────────────────────────────────────────────
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)
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
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"
else
echo -e "${RED}[ERROR] Не удалось скачать обновление.${NC}"
rm -f /tmp/kaskad_update.sh
fi
read -p "Нажмите Enter..."
}
# ═══════════════════════════════════════════════════════════════
# LIVE PING
# ═══════════════════════════════════════════════════════════════
ping_live() {
local ip="$1"
local -a results=() lines=()
local count=0 lost=0 running=1
trap 'running=0' INT
while [ "$running" -eq 1 ]; do
local ms
ms=$(ping -c 1 -W 2 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+')
((count++))
if [ -n "$ms" ]; then
results+=("$ms")
lines+=("${GREEN}#${count}: ${ms} ms${NC}")
else
((lost++))
lines+=("${RED}#${count}: 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=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
if [ ${#results[@]} -gt 0 ]; then
local stats
stats=$(printf '%s\n' "${results[@]}" | awk '
BEGIN {mn=999999; mx=0; s=0}
{s+=$1; if($1<mn)mn=$1; if($1>mx)mx=$1}
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"
fi
echo -e " ${WHITE}Потеряно:${NC} $lost / $count"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
sleep 1
done
trap - INT
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
echo -e " ${YELLOW}[0]${NC} Отмена"
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)
# ═══════════════════════════════════════════════════════════════
add_monitor() {
local ip="$1" interval="$2" threshold="$3" cooldown="${4:-300}"
cat > "$MONITOR_DIR/${ip}.conf" <<EOF
MON_IP="$ip"
MON_INTERVAL=$interval
MON_THRESHOLD=$threshold
MON_COOLDOWN=$cooldown
EOF
log_action "MONITOR ADD: $ip interval=${interval}s threshold=${threshold}ms cooldown=${cooldown}s"
sync_monitoring_service
}
remove_monitor() {
local ip="$1"
rm -f "$MONITOR_DIR/${ip}.conf" "$MONITOR_DIR/.last_check_${ip}" "$MONITOR_DIR/.last_alert_${ip}"
log_action "MONITOR REMOVE: $ip"
sync_monitoring_service
}
has_monitors() {
local found=0
for conf in "$MONITOR_DIR"/*.conf; do
[ -f "$conf" ] && found=1 && break
done
return $(( 1 - found ))
}
sync_monitoring_service() {
if has_monitors; then
if ! systemctl is-active kaskad-monitor &>/dev/null 2>&1; then
start_monitoring_silent
fi
else
if systemctl is-active kaskad-monitor &>/dev/null 2>&1; then
stop_monitoring_silent
fi
fi
}
start_monitoring_silent() {
cat > /etc/systemd/system/kaskad-monitor.service <<EOF
[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
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)"
}
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"
done
if [ "$found" -eq 0 ]; then
echo -e " ${YELLOW}Нет настроенных мониторов.${NC}"
fi
}
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
[ -f "$alert_file" ] && last_alert=$(cat "$alert_file")
local now
now=$(date +%s)
if (( now - last_alert < cooldown )); then return; fi
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 text
if [ "$ping_ms" = "TIMEOUT" ]; then
text="⚠️ <b>ALERT</b>: ${header}\nPing: TIMEOUT (порог: ${threshold}ms)"
else
text="⚠️ <b>ALERT</b>: ${header}\nPing: ${ping_ms}ms (порог: ${threshold}ms)"
fi
tg_send "$BOT_CHAT_ID" "$text" "" > /dev/null 2>&1
fi
}
monitor_daemon() {
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
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
fi
fi
done
sleep 1
done
}
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
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) Назад"
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..."
;;
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..."
;;
0) return ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# TELEGRAM BOT
# ═══════════════════════════════════════════════════════════════
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
}
tg_send() {
local chat_id="$1" text keyboard="${3:-}"
text=$(printf '%b' "$2")
local payload
if [ -n "$keyboard" ]; then
payload=$(jq -n --arg c "$chat_id" --arg t "$text" --argjson k "$keyboard" \
'{chat_id:$c, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}')
else
payload=$(jq -n --arg c "$chat_id" --arg t "$text" \
'{chat_id:$c, text:$t, parse_mode:"HTML"}')
fi
tg_api "sendMessage" "$payload"
}
tg_edit() {
local chat_id="$1" msg_id="$2" text keyboard="${4:-}"
text=$(printf '%b' "$3")
local payload
if [ -n "$keyboard" ]; then
payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" --argjson k "$keyboard" \
'{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML", reply_markup:{inline_keyboard:$k}}')
else
payload=$(jq -n --arg c "$chat_id" --argjson m "$msg_id" --arg t "$text" \
'{chat_id:$c, message_id:$m, text:$t, parse_mode:"HTML"}')
fi
tg_api "editMessageText" "$payload"
}
tg_answer_cb() {
local cb_id="$1" text="${2:-}"
tg_api "answerCallbackQuery" "{\"callback_query_id\":\"$cb_id\",\"text\":\"$text\"}"
}
# ─── Bot state ────────────────────────────────────────────────
bot_set_state() {
local chat_id="$1"; shift
printf '%s\n' "$@" > "$BOT_STATE_DIR/$chat_id"
}
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"
}
# ─── 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'
[
[{"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"}],
[{"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_large"}]
]
JSON
fi
}
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"}]
]'
}
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"}]
]'
}
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"}]
]'
}
build_ip_kbd() {
local prefix="$1"; shift
local ips=("$@")
local 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
done
echo "[${rows},[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]"
}
build_delete_kbd() {
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++))
done <<< "$rules"
if [ -z "$rows" ]; then
echo '[[{"text":"Нет правил","callback_data":"m"}]]'
else
echo "[${rows},[{\"text\":\"⬅️ Меню\",\"callback_data\":\"m\"}]]"
fi
}
# ─── Bot handlers ─────────────────────────────────────────────
bot_main_menu() {
local chat_id="$1" msg_id="${2:-}"
bot_clear_state "$chat_id"
local text="<b>Kaskad PRO v${KASKAD_VERSION}</b>\nIP: <code>${MY_IP:-N/A}</code>\nВыберите действие:"
local kbd
kbd=$(kbd_main)
if [ -n "$msg_id" ]; then
tg_edit "$chat_id" "$msg_id" "$text" "$kbd"
else
tg_send "$chat_id" "$text" "$kbd"
fi
}
bot_handle_callback() {
local chat_id="$1" msg_id="$2" cb_id="$3" data="$4"
tg_answer_cb "$cb_id" > /dev/null
case "$data" in
m)
bot_main_menu "$chat_id" "$msg_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" "🔀 <b>AmneziaWG / WireGuard (UDP)</b>\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" "🔀 <b>VLESS / XRay (TCP)</b>\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" "🔀 <b>MTProto / TProxy (TCP)</b>\n\nВведите IP адрес целевого сервера:" "$(kbd_back)"
;;
a_c)
tg_edit "$chat_id" "$msg_id" "🛠 <b>Custom Rule</b>\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" "🛠 <b>Custom Rule ($proto)</b>\n\nВведите IP адрес целевого сервера:" "$(kbd_back)"
;;
# ── List rules ──
lr)
local rules text=""
rules=$(get_rules_list)
if [ -z "$rules" ]; then
text="📋 <b>Нет активных правил.</b>"
else
text="📋 <b>Активные правила:</b>\n<b>Сервер:</b> <code>${MY_IP:-N/A}</code>\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="<code>${MY_IP:-*}:$port ($proto) → $dest</code>"
[ -n "$label" ] && line+=" [$label]"
text+="$line\n"
fi
done <<< "$rules"
fi
tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)"
;;
# ── Delete rule ──
dr)
tg_edit "$chat_id" "$msg_id" "❌ <b>Выберите правило для удаления:</b>" "$(build_delete_kbd)"
;;
dr_*)
local idx="${data#dr_}"
local 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" "✅ Правило <code>$d_proto :$d_port$d_dest</code> удалено." "$(kbd_back)"
else
tg_edit "$chat_id" "$msg_id" "Правило не найдено." "$(kbd_back)"
fi
;;
# ── Flush ──
fa)
tg_edit "$chat_id" "$msg_id" "🗑 <b>Вы уверены?</b>\nБудут удалены ВСЕ правила Kaskad." \
'[[{"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
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
done
done
save_iptables
log_action "BOT FLUSH all kaskad rules"
tg_edit "$chat_id" "$msg_id" "✅ Все правила Kaskad удалены." "$(kbd_back)"
;;
# ── System stats ──
sys)
local stats
stats=$(get_system_stats)
tg_edit "$chat_id" "$msg_id" "$stats" "$(kbd_back)"
;;
# ── Promo ──
promo)
local promo_text=""
promo_text+="<b>🏢 Хостинг, который работает</b>\n\n"
promo_text+="<b>🌍 Локации: РФ и Европа</b>\n"
promo_text+="👉 https://vk.cc/ct29NQ\n\n"
promo_text+="<code>OFF60</code> — 60% скидка на 1-й месяц\n"
promo_text+="<code>antenka20</code> — +20% к балансу (3 мес)\n"
promo_text+="<code>antenka6</code> — +15% к балансу (6 мес)\n"
promo_text+="<code>antenka12</code> — +5% к балансу (12 мес)\n\n"
promo_text+="<b>🇧🇾 Локация: Беларусь</b>\n"
promo_text+="👉 https://vk.cc/cUxAhj\n\n"
promo_text+="<code>OFF60</code> — 60% скидка на 1-й месяц"
tg_edit "$chat_id" "$msg_id" "$promo_text" "$(kbd_back)"
;;
# ── Ping ──
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)"
else
tg_edit "$chat_id" "$msg_id" "🏓 <b>Выберите сервер:</b>" "$(build_ip_kbd "ps" "${ips[@]}")"
fi
;;
ps:*)
local ip="${data#ps:}"
local label
label=$(fmt_ip "$ip")
tg_edit "$chat_id" "$msg_id" "🏓 <b>Ping $label</b>\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" "🏓 <b>$label</b>\nОтвет: <code>${ms} ms</code>" "$(kbd_back)" > /dev/null
else
tg_send "$chat_id" "🏓 <b>$label</b>\nОтвет: <code>timeout</code>" "$(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')
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="🏓 <b>Ping $label (10 раз)</b>\n${text}"
if [ ${#results[@]} -gt 0 ]; then
local avg
avg=$(printf '%s\n' "${results[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}')
summary+="\n<b>Среднее: ${avg}ms</b>"
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="🏓 <b>$label</b>: ${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="🏓 <b>$label (60 сек) — завершён</b>\n"
if [ ${#results[@]} -gt 0 ]; then
local stats
stats=$(printf '%s\n' "${results[@]}" | awk '
BEGIN{mn=999999;mx=0;s=0}
{s+=$1;if($1<mn)mn=$1;if($1>mx)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" "📊 <b>Мониторинг</b>\nВыберите действие:" "$(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" "📊 <b>Мониторинг $label</b>\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" "📊 <b>$label</b> (каждые ${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" "✅ Мониторинг для <b>$label</b> добавлен.\nИнтервал: ${interval}с | Порог: ${threshold}мс | Уведомл: ${cooldown}с" "$(kbd_back)"
;;
ml)
local text="📊 <b>Активные мониторы:</b>\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+="<b>$label</b>\n инт: ${MON_INTERVAL}с | порог: ${MON_THRESHOLD}мс | уведомл: ${MON_COOLDOWN}с\n"
done
[ "$found" -eq 0 ] && text+="<i>Нет настроенных мониторов.</i>"
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" "✅ Мониторинг для <b>$label</b> удалён." "$(kbd_monitor)"
;;
esac
}
bot_handle_message() {
local chat_id="$1" text="$2"
if [ "$text" = "/start" ] || [ "$text" = "/menu" ]; then
bot_main_menu "$chat_id"
return
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
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")
bot_set_state "$chat_id" "STATE=awaiting_name" "PROTO=$proto" "NAME=$name" "CUSTOM=$custom" "TARGET_IP=$text"
tg_send "$chat_id" "IP: <code>$text</code> ✅\n\nВведите имя сервера (или <code>-</code> чтобы пропустить):" "$(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
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" "Сервер: <b>$(fmt_ip "$target_ip")</b>\n\nВведите <b>ВХОДЯЩИЙ</b> порт:" "$(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" "Сервер: <b>$(fmt_ip "$target_ip")</b>\n\nВведите порт:" "$(kbd_back)" > /dev/null
fi
;;
awaiting_port)
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")
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" "✅ <b>$name настроен!</b>\n<code>$proto ${MY_IP:-*}:$text$target_ip:$text</code>\nСервер: $label" "$(kbd_back)" > /dev/null
;;
awaiting_in_port)
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")
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" "Входящий порт: <code>$text</code> ✅\n\nВведите <b>ИСХОДЯЩИЙ</b> порт:" "$(kbd_back)" > /dev/null
;;
awaiting_out_port)
if ! validate_port "$text"; then
tg_send "$chat_id" "❌ Некорректный порт (1-65535)." "" > /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")
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" "✅ <b>Custom Rule настроен!</b>\n<code>$proto ${MY_IP:-*}:$in_port$target_ip:$text</code>\nСервер: $label" "$(kbd_back)" > /dev/null
;;
awaiting_threshold)
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")
bot_clear_state "$chat_id"
local label
label=$(fmt_ip "$mon_ip")
tg_send "$chat_id" "📊 <b>$label</b>\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"
source "$KASKAD_CONF"
if [ -z "$BOT_TOKEN" ]; then
log_action "BOT ERROR: BOT_TOKEN is empty"
exit 1
fi
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<count; i++ )); do
local update
update=$(echo "$response" | jq ".result[$i]")
local update_id
update_id=$(echo "$update" | jq -r '.update_id')
offset=$((update_id + 1))
local cb_data cb_id cb_chat_id cb_msg_id
cb_data=$(echo "$update" | jq -r '.callback_query.data // empty')
if [ -n "$cb_data" ]; then
cb_id=$(echo "$update" | jq -r '.callback_query.id')
cb_chat_id=$(echo "$update" | jq -r '.callback_query.message.chat.id')
cb_msg_id=$(echo "$update" | jq -r '.callback_query.message.message_id')
if [ -n "$BOT_CHAT_ID" ] && [ "$cb_chat_id" != "$BOT_CHAT_ID" ]; then
tg_answer_cb "$cb_id" "Unauthorized" > /dev/null
continue
fi
bot_handle_callback "$cb_chat_id" "$cb_msg_id" "$cb_id" "$cb_data"
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: <code>$msg_chat_id</code>" "" > /dev/null
continue
fi
bot_handle_message "$msg_chat_id" "$msg_text"
fi
fi
done
done
}
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
cat > /etc/systemd/system/kaskad-bot.service <<EOF
[Unit]
Description=Kaskad Telegram Bot
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gokaskad --bot-daemon
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable kaskad-bot > /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
}
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"
}
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}"
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..." ;;
0) return ;;
esac
done
}
# ═══════════════════════════════════════════════════════════════
# PROMO & INSTRUCTIONS (kept from previous version)
# ═══════════════════════════════════════════════════════════════
show_promo() {
clear
echo ""
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${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 ""
read -p "Нажмите Enter..."
}
show_instructions() {
local page=1 total_pages=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: удаление, сброс, обновление, промо, инструкция" ;;
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
read -p " > " nav
case "$nav" in
[nN]) (( page < total_pages )) && ((page++)) ;; [pP]) (( page > 1 )) && ((page--)) ;; 0) return ;; [1-7]) page="$nav" ;; esac
done
}
# ═══════════════════════════════════════════════════════════════
# MAIN MENU
# ═══════════════════════════════════════════════════════════════
show_menu() {
while true; do
clear
echo -e "${MAGENTA}"
echo "******************************************************"
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 "------------------------------------------------------"
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 "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
done
}
# ═══════════════════════════════════════════════════════════════
# ENTRY POINT
# ═══════════════════════════════════════════════════════════════
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
;;
esac