From 60a47156032f741db4f30e4f11e81f57c6837026 Mon Sep 17 00:00:00 2001 From: anten-ka Date: Thu, 5 Mar 2026 17:47:21 +0300 Subject: [PATCH] Kaskad PRO v2.0: cascading VPN/proxy manager with Telegram bot, live ping, monitoring and alerts Made-with: Cursor --- README.md | 140 ++++ install.sh | 1806 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1946 insertions(+) create mode 100644 README.md create mode 100644 install.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..2753658 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# Kaskad PRO v2.0 + +**Каскадный менеджер VPN / Proxy с Telegram-ботом, мониторингом и пингом.** + +> Канал: [YouTube @antenkaru](https://www.youtube.com/@antenkaru) + +--- + +## Возможности + +| Функция | Описание | +|---|---| +| **Каскад UDP** | AmneziaWG, WireGuard — одна команда | +| **Каскад TCP** | VLESS, XRay, VMess, Reality, Trojan | +| **Каскад MTProto** | Telegram-прокси | +| **Custom Rule** | Разные порты входа/выхода, SSH, RDP и т.д. | +| **Live Ping** | Пинг целевого сервера в терминале (обновление 1 сек) | +| **Telegram Bot** | Полное управление через кнопки прямо из Telegram | +| **Ping в боте** | 1 раз / 10 раз (среднее) / 60 секунд (непрерывно) | +| **Мониторинг** | Автопинг каждые 10с / 1мин / 5мин с алертами в Telegram | +| **BBR Turbo** | Автоматическое включение Google BBR | +| **Безопасность** | Валидация IP/портов, маркировка правил, изоляция iptables | + +--- + +## Установка + +Подключитесь к VPS (Ubuntu/Debian/CentOS) под `root`: + +```bash +wget -O install.sh "URL_СКРИПТА" && chmod +x install.sh && ./install.sh +``` + +Повторный запуск: + +```bash +gokaskad +``` + +--- + +## Быстрый старт + +### 1. Создать каскад (пример WireGuard) + +``` +Меню → 1 +IP: 45.10.20.30 +Порт: 51820 +Подтвердить: y +``` + +В клиенте замените Endpoint на IP этого сервера. + +### 2. Настроить Telegram-бота + +``` +Меню → 8 → 1 → Вставить токен от @BotFather +Меню → 8 → 2 → Отправить сообщение боту, нажать Enter +Меню → 8 → 4 → Запустить бота +``` + +В Telegram: `/start` — появится меню с кнопками. + +### 3. Включить мониторинг + +``` +Меню → 7 → 1 → Выбрать сервер → Интервал → Порог (мс) +Меню → 7 → 3 → Запустить службу +``` + +Алерты приходят в Telegram, когда пинг превышает порог. + +--- + +## Структура меню + +``` + 1) AmneziaWG / WireGuard (UDP) + 2) VLESS / XRay (TCP) + 3) TProxy / MTProto (TCP) + 4) Кастомное правило + 5) Активные правила + 6) Ping сервера (live) + 7) Мониторинг + 8) Telegram Bot + 9) Удалить правило +10) Сбросить правила +11) Обновить скрипт +12) PROMO +13) Инструкция (7 страниц) + 0) Выход +``` + +--- + +## Telegram Bot — кнопки + +``` +🔀 AWG/WireGuard (UDP) — добавить UDP каскад +🔀 VLESS/XRay (TCP) — добавить TCP каскад +🔀 MTProto/TProxy (TCP) — добавить TCP каскад +🛠 Custom Rule — произвольное правило +📋 Правила — список активных правил +🏓 Ping — пинг (1x / 10x / 60 сек) +📊 Мониторинг — добавить/удалить/список мониторов +❌ Удалить правило — удалить конкретный каскад +🗑 Сбросить всё — полная очистка (с подтверждением) +``` + +--- + +## Файлы и пути + +| Путь | Назначение | +|---|---| +| `/usr/local/bin/gokaskad` | Глобальная команда запуска | +| `/etc/kaskad/config` | Токен бота и Chat ID | +| `/etc/kaskad/monitors/` | Конфигурации мониторов | +| `/var/log/kaskad.log` | Лог всех действий | + +--- + +## Системные службы + +```bash +# Telegram-бот +systemctl status kaskad-bot +journalctl -u kaskad-bot -f + +# Мониторинг +systemctl status kaskad-monitor +journalctl -u kaskad-monitor -f +``` + +--- + +## Лицензия + +Для частного использования. Распространение запрещено. diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..8f0a191 --- /dev/null +++ b/install.sh @@ -0,0 +1,1806 @@ +#!/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}%0APing: TIMEOUT (порог: ${threshold}ms)" + else + text="⚠️ ALERT: ${ip}%0APing: ${ping_ms}ms (порог: ${threshold}ms)" + fi + curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ + -d "chat_id=${BOT_CHAT_ID}&text=${text}&parse_mode=HTML" > /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="$2" keyboard="${3:-}" + 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="$3" keyboard="${4:-}" + 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