mirror of
https://github.com/anten-ka/kaskad-pro.git
synced 2026-05-19 11:26:02 +00:00
1808 lines
79 KiB
Bash
1808 lines
79 KiB
Bash
#!/bin/bash
|
||
set -o pipefail
|
||
|
||
# ══════════════════════════════════════════════════════════════
|
||
# KASKAD v2.0 — Cascading VPN / Proxy Manager
|
||
# Telegram Bot · Live Ping · Monitoring · Alerts
|
||
# Channel: https://www.youtube.com/@antenkaru
|
||
# ══════════════════════════════════════════════════════════════
|
||
|
||
KASKAD_VERSION="2.0"
|
||
KASKAD_DIR="/etc/kaskad"
|
||
KASKAD_CONF="$KASKAD_DIR/config"
|
||
KASKAD_LOG="/var/log/kaskad.log"
|
||
MONITOR_DIR="$KASKAD_DIR/monitors"
|
||
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=""
|
||
|
||
# ─── Config ───────────────────────────────────────────────────
|
||
|
||
init_config() {
|
||
mkdir -p "$KASKAD_DIR" "$MONITOR_DIR" "$BOT_STATE_DIR"
|
||
if [ ! -f "$KASKAD_CONF" ]; then
|
||
cat > "$KASKAD_CONF" <<'CONF'
|
||
BOT_TOKEN=""
|
||
BOT_CHAT_ID=""
|
||
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"
|
||
}
|
||
|
||
# ─── 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
|
||
}
|
||
|
||
# ─── 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 > /dev/null 2>&1
|
||
elif command -v dnf &>/dev/null; then
|
||
dnf install -y iptables-services jq qrencode curl > /dev/null 2>&1
|
||
elif command -v yum &>/dev/null; then
|
||
yum install -y iptables-services jq qrencode curl > /dev/null 2>&1
|
||
else
|
||
echo -e "${RED}[ERROR] Неподдерживаемый пакетный менеджер!${NC}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ─── 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: Вход :$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"
|
||
|
||
check_target_reachable "$target_ip" || return
|
||
|
||
read_validated_port "Введите Порт (одинаковый для входа и выхода):"
|
||
local port="$_RET_PORT"
|
||
|
||
echo -e "\n${YELLOW}Будет создано правило:${NC}"
|
||
echo -e " $proto: :$port -> $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"
|
||
|
||
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: :$in_port -> $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 "${MAGENTA}ПОРТ (ВХОД)\tПРОТОКОЛ\tЦЕЛЬ (IP:ВЫХОД)${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
|
||
echo -e "$port\t\t$proto\t\t$dest"
|
||
done
|
||
fi
|
||
echo ""
|
||
echo -e "${GREEN}Задонатить каналу и автору:${NC}"
|
||
if command -v qrencode &>/dev/null; then
|
||
qrencode -t ANSIUTF8 "https://pay.cloudtips.ru/p/7410814f"
|
||
else
|
||
echo "https://pay.cloudtips.ru/p/7410814f"
|
||
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"
|
||
echo -e "${YELLOW}[$i]${NC} Вход: $port ($proto) -> Выход: $dest"
|
||
((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%:*}"
|
||
local target_port="${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..."
|
||
}
|
||
|
||
# ─── Auto-update ──────────────────────────────────────────────
|
||
|
||
self_update() {
|
||
local url="https://raw.githubusercontent.com/anten-ka/kaskad/main/install.sh"
|
||
echo -e "${YELLOW}[*] Загрузка обновления...${NC}"
|
||
if wget -qO /tmp/kaskad_update.sh "$url" 2>/dev/null; then
|
||
cp -f /tmp/kaskad_update.sh /usr/local/bin/gokaskad
|
||
chmod +x /usr/local/bin/gokaskad
|
||
rm -f /tmp/kaskad_update.sh
|
||
echo -e "${GREEN}[OK] Скрипт обновлён! Перезапустите: gokaskad${NC}"
|
||
log_action "Self-update completed"
|
||
else
|
||
echo -e "${RED}[ERROR] Не удалось скачать обновление.${NC}"
|
||
fi
|
||
read -p "Нажмите Enter..."
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# LIVE PING (Terminal — refreshes every ~1 second)
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
ping_live() {
|
||
local ip="$1"
|
||
local -a results=()
|
||
local -a 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}$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} ${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
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
add_monitor() {
|
||
local ip="$1" interval="$2" threshold="$3"
|
||
cat > "$MONITOR_DIR/${ip}.conf" <<EOF
|
||
MON_IP="$ip"
|
||
MON_INTERVAL=$interval
|
||
MON_THRESHOLD=$threshold
|
||
EOF
|
||
log_action "MONITOR ADD: $ip interval=${interval}s threshold=${threshold}ms"
|
||
}
|
||
|
||
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"
|
||
}
|
||
|
||
list_monitors() {
|
||
local found=0
|
||
for conf in "$MONITOR_DIR"/*.conf; do
|
||
[ -f "$conf" ] || continue
|
||
found=1
|
||
local MON_IP="" MON_INTERVAL="" MON_THRESHOLD=""
|
||
source "$conf"
|
||
echo -e " ${WHITE}$MON_IP${NC} интервал: ${MON_INTERVAL}s порог: ${MON_THRESHOLD}ms"
|
||
done
|
||
if [ "$found" -eq 0 ]; then
|
||
echo -e " ${YELLOW}Нет настроенных мониторов.${NC}"
|
||
fi
|
||
}
|
||
|
||
monitor_alert() {
|
||
local ip="$1" ping_ms="$2" threshold="$3"
|
||
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 < 300 )); 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 text
|
||
if [ "$ping_ms" = "TIMEOUT" ]; then
|
||
text="⚠️ <b>ALERT</b>: ${ip}\nPing: TIMEOUT (порог: ${threshold}ms)"
|
||
else
|
||
text="⚠️ <b>ALERT</b>: ${ip}\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=""
|
||
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"
|
||
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"
|
||
fi
|
||
fi
|
||
fi
|
||
done
|
||
sleep 1
|
||
done
|
||
}
|
||
|
||
start_monitoring() {
|
||
if [ -f "$MONITOR_PID_FILE" ] && kill -0 "$(cat "$MONITOR_PID_FILE")" 2>/dev/null; then
|
||
echo -e "${YELLOW}Мониторинг уже запущен (PID $(cat "$MONITOR_PID_FILE")).${NC}"
|
||
return
|
||
fi
|
||
|
||
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
|
||
echo -e "${GREEN}[OK] Мониторинг запущен.${NC}"
|
||
log_action "Monitoring service started"
|
||
}
|
||
|
||
stop_monitoring() {
|
||
systemctl stop kaskad-monitor 2>/dev/null
|
||
systemctl disable kaskad-monitor 2>/dev/null
|
||
rm -f "$MONITOR_PID_FILE"
|
||
echo -e "${GREEN}[OK] Мониторинг остановлен.${NC}"
|
||
log_action "Monitoring service stopped"
|
||
}
|
||
|
||
monitoring_menu() {
|
||
while true; do
|
||
clear
|
||
local mon_status="${RED}Остановлен${NC}"
|
||
if [ -f "$MONITOR_PID_FILE" ] && kill -0 "$(cat "$MONITOR_PID_FILE" 2>/dev/null)" 2>/dev/null; then
|
||
mon_status="${GREEN}Работает (PID $(cat "$MONITOR_PID_FILE"))${NC}"
|
||
fi
|
||
|
||
echo -e "${CYAN}━━━ Мониторинг ━━━${NC}"
|
||
echo -e "Статус: $mon_status"
|
||
echo ""
|
||
list_monitors
|
||
echo ""
|
||
echo -e "1) Добавить мониторинг"
|
||
echo -e "2) Удалить мониторинг"
|
||
echo -e "3) Запустить службу мониторинга"
|
||
echo -e "4) Остановить службу мониторинга"
|
||
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} ${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 "Порог уведомления (мс, 1-65535):"
|
||
local threshold="$_RET_PORT"
|
||
|
||
add_monitor "$sel_ip" "$interval" "$threshold"
|
||
echo -e "${GREEN}[OK] Мониторинг для $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} ${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..."
|
||
;;
|
||
3) start_monitoring; read -p "Enter..." ;;
|
||
4) stop_monitoring; 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 ───────────────────────────────────────────
|
||
|
||
kbd_main() {
|
||
cat <<'JSON'
|
||
[
|
||
[{"text":"🔀 AWG/WireGuard (UDP)","callback_data":"a_u"}],
|
||
[{"text":"🔀 VLESS/XRay (TCP)","callback_data":"a_t"}],
|
||
[{"text":"🔀 MTProto/TProxy (TCP)","callback_data":"a_mt"}],
|
||
[{"text":"🛠 Custom Rule","callback_data":"a_c"}],
|
||
[{"text":"📋 Правила","callback_data":"lr"},{"text":"🏓 Ping","callback_data":"pm"}],
|
||
[{"text":"📊 Мониторинг","callback_data":"mm"}],
|
||
[{"text":"❌ Удалить правило","callback_data":"dr"}],
|
||
[{"text":"🗑 Сбросить всё","callback_data":"fa"}]
|
||
]
|
||
JSON
|
||
}
|
||
|
||
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"}]
|
||
]'
|
||
}
|
||
|
||
build_ip_kbd() {
|
||
local prefix="$1"; shift
|
||
local ips=("$@")
|
||
local rows=""
|
||
local first=1
|
||
for ip in "${ips[@]}"; do
|
||
[ "$first" -eq 0 ] && rows+=","
|
||
rows+="[{\"text\":\"$ip\",\"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
|
||
[ "$first" -eq 0 ] && rows+=","
|
||
rows+="[{\"text\":\"❌ $port ($proto) → $dest\",\"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 v${KASKAD_VERSION}</b>\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"
|
||
;;
|
||
|
||
# ── 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"
|
||
while IFS='|' read -r proto port dest; do
|
||
[ -n "$port" ] && text+="<code>$proto :$port → $dest</code>\n"
|
||
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"
|
||
local target_ip="${d_dest%:*}"
|
||
local target_port="${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 "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)"
|
||
;;
|
||
|
||
# ── 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:}"
|
||
tg_edit "$chat_id" "$msg_id" "🏓 <b>Ping $ip</b>\nВыберите режим:" "$(kbd_ping_opts "$ip")"
|
||
;;
|
||
po:*)
|
||
local ip="${data#po:}"
|
||
(
|
||
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>Ping $ip</b>\nОтвет: <code>${ms} ms</code>" "$(kbd_back)" > /dev/null
|
||
else
|
||
tg_send "$chat_id" "🏓 <b>Ping $ip</b>\nОтвет: <code>timeout</code>" "$(kbd_back)" > /dev/null
|
||
fi
|
||
) &
|
||
;;
|
||
p10:*)
|
||
local ip="${data#p10:}"
|
||
(
|
||
local resp
|
||
resp=$(tg_send "$chat_id" "🏓 Ping $ip (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 $ip (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 resp
|
||
resp=$(tg_send "$chat_id" "🏓 Ping $ip (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>Ping $ip</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>Ping $ip (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:}"
|
||
tg_edit "$chat_id" "$msg_id" "📊 <b>Мониторинг $ip</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"
|
||
tg_edit "$chat_id" "$msg_id" "📊 <b>Мониторинг $ip</b> (каждые ${interval}с)\n\nВведите порог уведомления (мс):" "$(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=""
|
||
source "$conf"
|
||
text+="<code>$MON_IP</code> — каждые ${MON_INTERVAL}с, порог ${MON_THRESHOLD}мс\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"
|
||
tg_edit "$chat_id" "$msg_id" "✅ Мониторинг для <code>$ip</code> удалён." "$(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")
|
||
if [ "$custom" = "1" ]; then
|
||
bot_set_state "$chat_id" "STATE=awaiting_in_port" "PROTO=$proto" "NAME=$name" "CUSTOM=1" "TARGET_IP=$text"
|
||
tg_send "$chat_id" "IP: <code>$text</code> ✅\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=$text"
|
||
tg_send "$chat_id" "IP: <code>$text</code> ✅\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"
|
||
tg_send "$chat_id" "✅ <b>$name настроен!</b>\n<code>$proto :$text → $target_ip:$text</code>" "$(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"
|
||
tg_send "$chat_id" "✅ <b>Custom Rule настроен!</b>\n<code>$proto :$in_port → $target_ip:$text</code>" "$(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"
|
||
add_monitor "$mon_ip" "$mon_interval" "$text"
|
||
tg_send "$chat_id" "✅ Мониторинг для <code>$mon_ip</code> добавлен.\nИнтервал: ${mon_interval}с | Порог: ${text}мс\n\n⚠️ Убедитесь, что служба мониторинга запущена (<code>gokaskad</code> → Мониторинг → Запустить)." "$(kbd_back)" > /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"
|
||
echo "ERROR: BOT_TOKEN не задан в $KASKAD_CONF"
|
||
exit 1
|
||
fi
|
||
|
||
detect_interface
|
||
|
||
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}Бот уже запущен (PID $(cat "$BOT_PID_FILE")).${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="не задан"
|
||
if [ -n "${BOT_TOKEN:-}" ]; then
|
||
token_display="***${BOT_TOKEN: -6}"
|
||
fi
|
||
|
||
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 ""
|
||
echo -e "1) Установить токен бота"
|
||
echo -e "2) Получить Chat ID (авто)"
|
||
echo -e "3) Установить Chat ID вручную"
|
||
echo -e "4) ${GREEN}Запустить бота${NC}"
|
||
echo -e "5) ${RED}Остановить бота${NC}"
|
||
echo -e "0) Назад"
|
||
read -p "Выбор: " choice
|
||
|
||
case $choice in
|
||
1)
|
||
echo -e "Введите токен бота (от @BotFather):"
|
||
read -p "> " new_token
|
||
if [ -n "$new_token" ]; then
|
||
save_config_val "BOT_TOKEN" "$new_token"
|
||
echo -e "${GREEN}[OK] Токен сохранён.${NC}"
|
||
fi
|
||
read -p "Enter..."
|
||
;;
|
||
2)
|
||
if [ -z "${BOT_TOKEN:-}" ]; then
|
||
echo -e "${RED}Сначала задайте токен!${NC}"
|
||
read -p "Enter..."
|
||
continue
|
||
fi
|
||
echo -e "${YELLOW}Отправьте любое сообщение боту в Telegram, затем нажмите Enter.${NC}"
|
||
read -p ""
|
||
local resp
|
||
resp=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=1&offset=-1" 2>/dev/null)
|
||
local cid
|
||
cid=$(echo "$resp" | jq -r '.result[0].message.chat.id // empty')
|
||
if [ -n "$cid" ]; then
|
||
save_config_val "BOT_CHAT_ID" "$cid"
|
||
echo -e "${GREEN}Chat ID: $cid — сохранён!${NC}"
|
||
else
|
||
echo -e "${RED}Не удалось определить Chat ID. Повторите.${NC}"
|
||
fi
|
||
read -p "Enter..."
|
||
;;
|
||
3)
|
||
echo -e "Введите Chat ID:"
|
||
read -p "> " new_cid
|
||
if [ -n "$new_cid" ]; then
|
||
save_config_val "BOT_CHAT_ID" "$new_cid"
|
||
echo -e "${GREEN}[OK] Chat ID сохранён.${NC}"
|
||
fi
|
||
read -p "Enter..."
|
||
;;
|
||
4) start_bot; read -p "Enter..." ;;
|
||
5) stop_bot; read -p "Enter..." ;;
|
||
0) return ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# PROMO & INSTRUCTIONS
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
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}"
|
||
if command -v qrencode &>/dev/null; then
|
||
qrencode -t ANSIUTF8 "https://vk.cc/ct29NQ"
|
||
else
|
||
echo "QR-код не загрузился, используйте ссылки выше."
|
||
fi
|
||
echo -e "${NC}"
|
||
|
||
echo -e "${GREEN}Сканируйте камерой телефона!${NC}"
|
||
echo ""
|
||
read -p "Нажмите Enter для настройки каскадного скрипта..."
|
||
}
|
||
|
||
show_instructions() {
|
||
local page=1
|
||
local 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}"
|
||
echo ""
|
||
echo -e "Каскад — это 'мост' между вашим устройством и зарубежным"
|
||
echo -e "VPN/Proxy сервером. Трафик идёт по цепочке:"
|
||
echo ""
|
||
echo -e " ${WHITE}Телефон/ПК${NC} → ${GREEN}Этот сервер (РФ)${NC} → ${CYAN}Зарубежный VPN${NC} → Интернет"
|
||
echo ""
|
||
echo -e "Зачем это нужно:"
|
||
echo -e " • Провайдер видит только подключение к серверу в РФ"
|
||
echo -e " • Зарубежный сервер не блокируется, т.к. подключение идёт"
|
||
echo -e " с российского IP"
|
||
echo -e " • Скорость выше за счет BBR и близкого первого хопа"
|
||
echo ""
|
||
echo -e "${CYAN}═══ ЧТО НУЖНО ДЛЯ НАЧАЛА ═══${NC}"
|
||
echo ""
|
||
echo -e " ${YELLOW}1.${NC} VPS в РФ (на нём запущен этот скрипт)"
|
||
echo -e " ${YELLOW}2.${NC} Зарубежный VPN/Proxy сервер (AWG, VLESS и т.д.)"
|
||
echo -e " ${YELLOW}3.${NC} IP-адрес и порт зарубежного сервера"
|
||
echo ""
|
||
echo -e "${WHITE}Пример:${NC}"
|
||
echo -e " Зарубежный сервер: ${GREEN}45.10.20.30${NC}, порт ${GREEN}51820${NC} (WireGuard)"
|
||
echo -e " Этот сервер (РФ): ${GREEN}${MY_IP:-185.1.2.3}${NC}"
|
||
;;
|
||
2)
|
||
echo -e "${CYAN}═══ ПУНКТ 1: AmneziaWG / WireGuard (UDP) ═══${NC}"
|
||
echo ""
|
||
echo -e "Создаёт каскад для протоколов на базе UDP:"
|
||
echo -e "AmneziaWG, WireGuard, OpenVPN (UDP) и т.п."
|
||
echo ""
|
||
echo -e "${WHITE}Как использовать:${NC}"
|
||
echo -e " 1. В меню выберите ${YELLOW}1${NC}"
|
||
echo -e " 2. Введите IP зарубежного сервера: ${GREEN}45.10.20.30${NC}"
|
||
echo -e " 3. Введите порт: ${GREEN}51820${NC}"
|
||
echo -e " 4. Подтвердите: ${GREEN}y${NC}"
|
||
echo ""
|
||
echo -e "${WHITE}Что произойдёт:${NC}"
|
||
echo -e " Создастся правило: UDP :51820 → 45.10.20.30:51820"
|
||
echo -e " Весь UDP-трафик на порт 51820 этого сервера будет"
|
||
echo -e " перенаправляться на зарубежный сервер."
|
||
echo ""
|
||
echo -e "${WHITE}Настройка клиента:${NC}"
|
||
echo -e " В приложении AmneziaWG/WireGuard замените Endpoint:"
|
||
echo -e " ${RED}Было:${NC} 45.10.20.30:51820"
|
||
echo -e " ${GREEN}Стало:${NC} ${MY_IP:-185.1.2.3}:51820"
|
||
echo ""
|
||
echo -e "${CYAN}═══ ПУНКТ 2: VLESS / XRay (TCP) ═══${NC}"
|
||
echo ""
|
||
echo -e "Создаёт каскад для TCP-протоколов:"
|
||
echo -e "VLESS, VMess, Reality, Trojan и т.п."
|
||
echo ""
|
||
echo -e "${WHITE}Пример:${NC} Зарубежный VLESS на порту 443"
|
||
echo -e " 1. В меню: ${YELLOW}2${NC}"
|
||
echo -e " 2. IP: ${GREEN}67.89.100.200${NC}"
|
||
echo -e " 3. Порт: ${GREEN}443${NC}"
|
||
echo -e " 4. В клиенте (v2rayNG, NekoBox) замените адрес"
|
||
echo -e " сервера на IP этого VPS"
|
||
;;
|
||
3)
|
||
echo -e "${CYAN}═══ ПУНКТ 3: TProxy / MTProto (TCP) ═══${NC}"
|
||
echo ""
|
||
echo -e "Каскад для Telegram-прокси (MTProto, TProxy)."
|
||
echo ""
|
||
echo -e "${WHITE}Пример:${NC} MTProto прокси на порту 8443"
|
||
echo -e " 1. В меню: ${YELLOW}3${NC}"
|
||
echo -e " 2. IP: ${GREEN}45.10.20.30${NC}"
|
||
echo -e " 3. Порт: ${GREEN}8443${NC}"
|
||
echo -e " 4. В Telegram: Настройки → Прокси → адрес:"
|
||
echo -e " ${GREEN}${MY_IP:-185.1.2.3}${NC}, порт: ${GREEN}8443${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}═══ ПУНКТ 4: Кастомное правило ═══${NC}"
|
||
echo ""
|
||
echo -e "Полная гибкость: разные порты входа и выхода,"
|
||
echo -e "выбор TCP/UDP, проброс SSH, RDP и др."
|
||
echo ""
|
||
echo -e "${WHITE}Пример 1:${NC} Проброс SSH (разные порты)"
|
||
echo -e " Протокол: ${GREEN}tcp${NC}"
|
||
echo -e " IP: ${GREEN}45.10.20.30${NC}"
|
||
echo -e " Входящий порт: ${GREEN}2222${NC} (на этом сервере)"
|
||
echo -e " Исходящий порт: ${GREEN}22${NC} (на целевом сервере)"
|
||
echo -e " Подключение: ssh user@${MY_IP:-185.1.2.3} -p 2222"
|
||
echo ""
|
||
echo -e "${WHITE}Пример 2:${NC} Проброс RDP"
|
||
echo -e " Протокол: ${GREEN}tcp${NC}"
|
||
echo -e " IP: ${GREEN}10.0.0.5${NC}"
|
||
echo -e " Входящий порт: ${GREEN}13389${NC}"
|
||
echo -e " Исходящий порт: ${GREEN}3389${NC}"
|
||
echo ""
|
||
echo -e "${WHITE}Пример 3:${NC} WireGuard на нестандартном порту"
|
||
echo -e " Протокол: ${GREEN}udp${NC}"
|
||
echo -e " IP: ${GREEN}45.10.20.30${NC}"
|
||
echo -e " Входящий порт: ${GREEN}9999${NC}"
|
||
echo -e " Исходящий порт: ${GREEN}51820${NC}"
|
||
echo -e " Клиент подключается к ${MY_IP:-185.1.2.3}:9999"
|
||
;;
|
||
4)
|
||
echo -e "${CYAN}═══ ПУНКТЫ 5-6: ПРАВИЛА И PING ═══${NC}"
|
||
echo ""
|
||
echo -e "${WHITE}Пункт 5 — Активные правила${NC}"
|
||
echo -e " Показывает таблицу всех созданных каскадов:"
|
||
echo -e " ┌────────────┬──────────┬───────────────────┐"
|
||
echo -e " │ Порт (вход)│ Протокол │ Цель (IP:выход) │"
|
||
echo -e " ├────────────┼──────────┼───────────────────┤"
|
||
echo -e " │ 51820 │ udp │ 45.10.20.30:51820 │"
|
||
echo -e " │ 443 │ tcp │ 67.89.100.200:443 │"
|
||
echo -e " └────────────┴──────────┴───────────────────┘"
|
||
echo ""
|
||
echo -e "${WHITE}Пункт 6 — Ping сервера (live)${NC}"
|
||
echo -e " Проверяет связь с целевым сервером в реальном времени."
|
||
echo -e " Экран обновляется каждую секунду."
|
||
echo ""
|
||
echo -e " ${WHITE}Как использовать:${NC}"
|
||
echo -e " 1. В меню: ${YELLOW}6${NC}"
|
||
echo -e " 2. Выберите сервер из списка (например: ${GREEN}1${NC})"
|
||
echo -e " 3. Наблюдайте результаты в реальном времени:"
|
||
echo ""
|
||
echo -e " ${CYAN}━━━ Live Ping: 45.10.20.30 ━━━${NC}"
|
||
echo -e " ${GREEN}#1: 15.3 ms${NC}"
|
||
echo -e " ${GREEN}#2: 14.8 ms${NC}"
|
||
echo -e " ${GREEN}#3: 16.1 ms${NC}"
|
||
echo -e " ${RED}#4: timeout${NC}"
|
||
echo -e " ${GREEN}#5: 15.0 ms${NC}"
|
||
echo ""
|
||
echo -e " ${WHITE}Мин:${NC} 14.8ms ${WHITE}Макс:${NC} 16.1ms ${WHITE}Сред:${NC} 15.3ms"
|
||
echo -e " ${WHITE}Потеряно:${NC} 1 / 5"
|
||
echo ""
|
||
echo -e " ${YELLOW}Ctrl+C${NC} для остановки."
|
||
;;
|
||
5)
|
||
echo -e "${CYAN}═══ ПУНКТ 7: МОНИТОРИНГ ═══${NC}"
|
||
echo ""
|
||
echo -e "Автоматическая проверка доступности целевых серверов"
|
||
echo -e "с отправкой уведомлений в Telegram при проблемах."
|
||
echo ""
|
||
echo -e "${WHITE}Как настроить:${NC}"
|
||
echo -e " 1. В меню: ${YELLOW}7${NC} → ${YELLOW}1) Добавить мониторинг${NC}"
|
||
echo -e " 2. Выберите сервер из списка"
|
||
echo -e " 3. Выберите интервал проверки:"
|
||
echo -e " ${YELLOW}1${NC} — каждые 10 секунд (для критичных сервисов)"
|
||
echo -e " ${YELLOW}2${NC} — каждую минуту (рекомендуется)"
|
||
echo -e " ${YELLOW}3${NC} — каждые 5 минут (для экономии ресурсов)"
|
||
echo -e " 4. Укажите порог в мс (например: ${GREEN}100${NC})"
|
||
echo ""
|
||
echo -e "${WHITE}Что происходит:${NC}"
|
||
echo -e " • Каждые N секунд скрипт пингует целевой сервер"
|
||
echo -e " • Если пинг > порога или timeout — отправляет алерт"
|
||
echo -e " в Telegram (не чаще 1 раза в 5 минут на IP)"
|
||
echo -e " • Все события записываются в ${YELLOW}/var/log/kaskad.log${NC}"
|
||
echo ""
|
||
echo -e "${WHITE}Пример алерта в Telegram:${NC}"
|
||
echo -e " ⚠️ ${RED}ALERT${NC}: 45.10.20.30"
|
||
echo -e " Ping: 250ms (порог: 100ms)"
|
||
echo ""
|
||
echo -e "${WHITE}Управление службой:${NC}"
|
||
echo -e " ${YELLOW}3${NC} — Запустить (создаёт systemd-сервис, переживёт ребут)"
|
||
echo -e " ${YELLOW}4${NC} — Остановить"
|
||
echo ""
|
||
echo -e "${RED}Важно:${NC} Для Telegram-алертов нужно настроить бота (пункт 8)."
|
||
;;
|
||
6)
|
||
echo -e "${CYAN}═══ ПУНКТ 8: TELEGRAM BOT ═══${NC}"
|
||
echo ""
|
||
echo -e "Полное управление сервером из Telegram через кнопки."
|
||
echo ""
|
||
echo -e "${WHITE}НАСТРОЙКА (один раз):${NC}"
|
||
echo ""
|
||
echo -e " ${YELLOW}Шаг A.${NC} Создайте бота:"
|
||
echo -e " 1. Откройте Telegram, найдите ${GREEN}@BotFather${NC}"
|
||
echo -e " 2. Отправьте ${GREEN}/newbot${NC}"
|
||
echo -e " 3. Придумайте имя (например: ${GREEN}Мой Kaskad${NC})"
|
||
echo -e " 4. Придумайте username (например: ${GREEN}my_kaskad_bot${NC})"
|
||
echo -e " 5. BotFather выдаст токен вида:"
|
||
echo -e " ${GREEN}7123456789:AAH1bGz-example-token-here${NC}"
|
||
echo ""
|
||
echo -e " ${YELLOW}Шаг B.${NC} Введите токен:"
|
||
echo -e " Меню: ${YELLOW}8${NC} → ${YELLOW}1) Установить токен бота${NC}"
|
||
echo -e " Вставьте токен и нажмите Enter."
|
||
echo ""
|
||
echo -e " ${YELLOW}Шаг C.${NC} Получите Chat ID:"
|
||
echo -e " Меню: ${YELLOW}8${NC} → ${YELLOW}2) Получить Chat ID${NC}"
|
||
echo -e " 1. Откройте вашего бота в Telegram"
|
||
echo -e " 2. Отправьте ему любое сообщение (например: /start)"
|
||
echo -e " 3. Вернитесь в терминал, нажмите Enter"
|
||
echo -e " 4. Скрипт автоматически найдёт и сохранит Chat ID"
|
||
echo ""
|
||
echo -e " ${YELLOW}Шаг D.${NC} Запустите бота:"
|
||
echo -e " Меню: ${YELLOW}8${NC} → ${YELLOW}4) Запустить бота${NC}"
|
||
echo -e " Бот работает как systemd-сервис (автозапуск при ребуте)."
|
||
echo ""
|
||
echo -e " ${YELLOW}Шаг E.${NC} В Telegram отправьте ${GREEN}/start${NC} — появится меню."
|
||
;;
|
||
7)
|
||
echo -e "${CYAN}═══ TELEGRAM BOT: ВОЗМОЖНОСТИ ═══${NC}"
|
||
echo ""
|
||
echo -e "${WHITE}Главное меню бота:${NC}"
|
||
echo -e " 🔀 AWG/WireGuard — добавить UDP каскад"
|
||
echo -e " 🔀 VLESS/XRay — добавить TCP каскад"
|
||
echo -e " 🔀 MTProto/TProxy — добавить TCP каскад"
|
||
echo -e " 🛠 Custom Rule — кастомное правило (TCP/UDP)"
|
||
echo -e " 📋 Правила — показать активные правила"
|
||
echo -e " 🏓 Ping — пинг целевых серверов"
|
||
echo -e " 📊 Мониторинг — управление мониторами"
|
||
echo -e " ❌ Удалить правило — удалить конкретный каскад"
|
||
echo -e " 🗑 Сбросить всё — полная очистка"
|
||
echo ""
|
||
echo -e "${WHITE}Добавление правил (пример VLESS):${NC}"
|
||
echo -e " 1. Нажмите ${GREEN}🔀 VLESS/XRay${NC}"
|
||
echo -e " 2. Бот: 'Введите IP' → отправьте ${GREEN}45.10.20.30${NC}"
|
||
echo -e " 3. Бот: 'Введите порт' → отправьте ${GREEN}443${NC}"
|
||
echo -e " 4. Бот: '✅ VLESS настроен! tcp :443 → 45.10.20.30:443'"
|
||
echo ""
|
||
echo -e "${WHITE}Ping (три режима):${NC}"
|
||
echo -e " 1. Нажмите ${GREEN}🏓 Ping${NC} → выберите сервер"
|
||
echo -e " 2. ${YELLOW}1 раз${NC} — мгновенный результат: '15.3 ms'"
|
||
echo -e " 3. ${YELLOW}10 раз${NC} — пингует 10 раз, считает среднее"
|
||
echo -e " Результат: '#1: 15ms #2: 14ms ... Среднее: 14.8ms'"
|
||
echo -e " 4. ${YELLOW}60 секунд${NC} — непрерывный пинг, обновление каждые 10с"
|
||
echo -e " Итог: 'Мин: 14ms Макс: 22ms Среднее: 16ms'"
|
||
echo ""
|
||
echo -e "${WHITE}Мониторинг через бота:${NC}"
|
||
echo -e " 1. ${GREEN}📊 Мониторинг${NC} → ${GREEN}➕ Добавить${NC} → выберите сервер"
|
||
echo -e " 2. Выберите интервал (10с / 1мин / 5мин)"
|
||
echo -e " 3. Введите порог: ${GREEN}100${NC}"
|
||
echo -e " 4. Алерты будут приходить в этот же чат."
|
||
echo ""
|
||
echo -e "${CYAN}═══ ПУНКТЫ 9-13 ═══${NC}"
|
||
echo ""
|
||
echo -e " ${YELLOW}9${NC} — Удалить одно правило (выберите из списка)"
|
||
echo -e " ${YELLOW}10${NC} — Сбросить все правила Kaskad (с подтверждением)"
|
||
echo -e " ${YELLOW}11${NC} — Обновить скрипт до последней версии"
|
||
echo -e " ${YELLOW}12${NC} — Показать PROMO партнёров"
|
||
echo -e " ${YELLOW}13${NC} — Эта инструкция"
|
||
;;
|
||
esac
|
||
|
||
echo ""
|
||
echo -e "${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 v${KASKAD_VERSION}"
|
||
echo " YouTube: https://www.youtube.com/@antenkaru"
|
||
echo "******************************************************"
|
||
echo -e "${NC}"
|
||
|
||
echo -e "${WHITE}IP этого сервера: ${GREEN}${MY_IP}${NC}"
|
||
echo -e "${WHITE}Интерфейс: ${CYAN}${IFACE}${NC}"
|
||
echo ""
|
||
echo -e "${YELLOW}Инструкции:${NC}"
|
||
echo -e " ${BLUE}https://boosty.to/anten-ka${NC} | ${BLUE}https://antenka.taplink.ws${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 " 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 ;;
|
||
0) exit 0 ;;
|
||
*) ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# ENTRY POINT
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
case "${1:-}" in
|
||
--bot-daemon)
|
||
init_config
|
||
bot_daemon
|
||
;;
|
||
--monitor-daemon)
|
||
init_config
|
||
monitor_daemon
|
||
;;
|
||
--ping-1)
|
||
init_config
|
||
ip="$2"; chat_id="$3"
|
||
ms=$(ping -c 1 -W 3 "$ip" 2>/dev/null | grep -oP 'time=\K[\d.]+')
|
||
if [ -n "$ms" ]; then
|
||
tg_send "$chat_id" "🏓 Ping $ip: ${ms}ms" "$(kbd_back)" > /dev/null
|
||
else
|
||
tg_send "$chat_id" "🏓 Ping $ip: timeout" "$(kbd_back)" > /dev/null
|
||
fi
|
||
;;
|
||
*)
|
||
check_root
|
||
init_config
|
||
prepare_system
|
||
detect_interface
|
||
get_my_ip
|
||
show_promo
|
||
show_menu
|
||
;;
|
||
esac
|