#!/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($1mx)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" < "$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="⚠️ ALERT: ${ip}\nPing: TIMEOUT (порог: ${threshold}ms)" else text="⚠️ ALERT: ${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 < /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="Kaskad v${KASKAD_VERSION}\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" "🔀 AmneziaWG / WireGuard (UDP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" ;; a_t) bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=VLESS" "CUSTOM=0" tg_edit "$chat_id" "$msg_id" "🔀 VLESS / XRay (TCP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" ;; a_mt) bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=tcp" "NAME=MTProto" "CUSTOM=0" tg_edit "$chat_id" "$msg_id" "🔀 MTProto / TProxy (TCP)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" ;; a_c) tg_edit "$chat_id" "$msg_id" "🛠 Custom Rule\n\nВыберите протокол:" "$(kbd_proto)" ;; a_cp_tcp|a_cp_udp) local proto="${data#a_cp_}" bot_set_state "$chat_id" "STATE=awaiting_ip" "PROTO=$proto" "NAME=Custom" "CUSTOM=1" tg_edit "$chat_id" "$msg_id" "🛠 Custom Rule ($proto)\n\nВведите IP адрес целевого сервера:" "$(kbd_back)" ;; # ── List rules ── lr) local rules text="" rules=$(get_rules_list) if [ -z "$rules" ]; then text="📋 Нет активных правил." else text="📋 Активные правила:\n" while IFS='|' read -r proto port dest; do [ -n "$port" ] && text+="$proto :$port → $dest\n" done <<< "$rules" fi tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_back)" ;; # ── Delete rule ── dr) tg_edit "$chat_id" "$msg_id" "❌ Выберите правило для удаления:" "$(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" "✅ Правило $d_proto :$d_port → $d_dest удалено." "$(kbd_back)" else tg_edit "$chat_id" "$msg_id" "Правило не найдено." "$(kbd_back)" fi ;; # ── Flush ── fa) tg_edit "$chat_id" "$msg_id" "🗑 Вы уверены?\nБудут удалены ВСЕ правила Kaskad." \ '[[{"text":"✅ Да, сбросить","callback_data":"fa_y"},{"text":"❌ Отмена","callback_data":"m"}]]' ;; fa_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" "🏓 Выберите сервер:" "$(build_ip_kbd "ps" "${ips[@]}")" fi ;; ps:*) local ip="${data#ps:}" tg_edit "$chat_id" "$msg_id" "🏓 Ping $ip\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" "🏓 Ping $ip\nОтвет: ${ms} ms" "$(kbd_back)" > /dev/null else tg_send "$chat_id" "🏓 Ping $ip\nОтвет: timeout" "$(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="🏓 Ping $ip (10 раз)\n${text}" if [ ${#results[@]} -gt 0 ]; then local avg avg=$(printf '%s\n' "${results[@]}" | awk '{s+=$1} END {printf "%.2f", s/NR}') summary+="\nСреднее: ${avg}ms" fi summary+="\nПотеряно: $lost / 10" if [ -n "$mid" ]; then tg_edit "$chat_id" "$mid" "$summary" "$(kbd_back)" > /dev/null else tg_send "$chat_id" "$summary" "$(kbd_back)" > /dev/null fi ) & ;; p60:*) local ip="${data#p60:}" ( local 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="🏓 Ping $ip: ${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="🏓 Ping $ip (60 сек) — завершён\n" if [ ${#results[@]} -gt 0 ]; then local stats stats=$(printf '%s\n' "${results[@]}" | awk ' BEGIN{mn=999999;mx=0;s=0} {s+=$1;if($1mx)mx=$1} END{printf "%.2f|%.2f|%.2f",mn,mx,s/NR}') IFS='|' read -r s_min s_max s_avg <<< "$stats" summary+="Мин: ${s_min}ms\nМакс: ${s_max}ms\nСреднее: ${s_avg}ms\n" fi summary+="Потеряно: $lost / 60" if [ -n "$mid" ]; then tg_edit "$chat_id" "$mid" "$summary" "$(kbd_back)" > /dev/null else tg_send "$chat_id" "$summary" "$(kbd_back)" > /dev/null fi ) & ;; # ── Monitoring ── mm) tg_edit "$chat_id" "$msg_id" "📊 Мониторинг\nВыберите действие:" "$(kbd_monitor)" ;; ma) local -a ips=() 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" "📊 Мониторинг $ip\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" "📊 Мониторинг $ip (каждые ${interval}с)\n\nВведите порог уведомления (мс):" "$(kbd_back)" ;; ml) local text="📊 Активные мониторы:\n" local found=0 for conf in "$MONITOR_DIR"/*.conf; do [ -f "$conf" ] || continue found=1 local MON_IP="" MON_INTERVAL="" MON_THRESHOLD="" source "$conf" text+="$MON_IP — каждые ${MON_INTERVAL}с, порог ${MON_THRESHOLD}мс\n" done [ "$found" -eq 0 ] && text+="Нет настроенных мониторов." tg_edit "$chat_id" "$msg_id" "$text" "$(kbd_monitor)" ;; md) local -a mon_ips=() for conf in "$MONITOR_DIR"/*.conf; do [ -f "$conf" ] || continue local MON_IP="" source "$conf" mon_ips+=("$MON_IP") done if [ ${#mon_ips[@]} -eq 0 ]; then tg_edit "$chat_id" "$msg_id" "Нет мониторов для удаления." "$(kbd_monitor)" else tg_edit "$chat_id" "$msg_id" "📊 Выберите монитор для удаления:" "$(build_ip_kbd "md" "${mon_ips[@]}")" fi ;; md:*) local ip="${data#md:}" remove_monitor "$ip" tg_edit "$chat_id" "$msg_id" "✅ Мониторинг для $ip удалён." "$(kbd_monitor)" ;; esac } bot_handle_message() { local chat_id="$1" text="$2" if [ "$text" = "/start" ] || [ "$text" = "/menu" ]; then bot_main_menu "$chat_id" return 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: $text ✅\n\nВведите ВХОДЯЩИЙ порт (на этом сервере):" "$(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: $text ✅\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" "✅ $name настроен!\n$proto :$text → $target_ip:$text" "$(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" "Входящий порт: $text ✅\n\nВведите ИСХОДЯЩИЙ порт (на целевом сервере):" "$(kbd_back)" > /dev/null ;; awaiting_out_port) if ! validate_port "$text"; then tg_send "$chat_id" "❌ Некорректный порт (1-65535)." "" > /dev/null return fi 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" "✅ Custom Rule настроен!\n$proto :$in_port → $target_ip:$text" "$(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" "✅ Мониторинг для $mon_ip добавлен.\nИнтервал: ${mon_interval}с | Порог: ${text}мс\n\n⚠️ Убедитесь, что служба мониторинга запущена (gokaskad → Мониторинг → Запустить)." "$(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 /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: $msg_chat_id" "" > /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 < /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