Files
kaskad-pro/install.sh

1847 lines
81 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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 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}"
echo -e "${YELLOW}Если репо приватный, задайте токен: меню 8 → доп. опции${NC}"
rm -f /tmp/kaskad_update.sh
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 ""
local ut_display="не задан"
local ut_val
ut_val=$(bot_get_state "system" "UPDATE_TOKEN" 2>/dev/null)
[ -n "$ut_val" ] && ut_display="***${ut_val: -6}"
echo -e "Update Token: ${YELLOW}$ut_display${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 -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..." ;;
6)
echo -e "Введите GitHub PAT (для скачивания обновлений из приватного репо):"
read -p "> " new_ut
if [ -n "$new_ut" ]; then
bot_set_state "system" "UPDATE_TOKEN=$new_ut"
echo -e "${GREEN}[OK] Токен обновления сохранён.${NC}"
fi
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