diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100644 index b3a778c..0000000 --- a/bootstrap.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# GoTelegram v2.2.1 — Bootstrap installer (private repo) -set -euo pipefail - -TOKEN="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" -REPO="anten-ka/gotelegram_pro" -BRANCH="test" -API="https://api.github.com/repos/$REPO" -INSTALL_DIR="/opt/gotelegram" - -echo -e "\033[1;36m" -echo "╔══════════════════════════════════════════╗" -echo "║ GoTelegram v2.2.1 — Установка ║" -echo "╚══════════════════════════════════════════╝" -echo -e "\033[0m" - -# Функция загрузки файла из приватного репо -dl() { - local path="$1" dest="$2" - mkdir -p "$(dirname "$dest")" - curl -sfL -H "Authorization: token $TOKEN" \ - -H "Accept: application/vnd.github.v3.raw" \ - "$API/contents/$path?ref=$BRANCH" -o "$dest" - sed -i 's/\r$//' "$dest" - echo " ✓ $path" -} - -echo "📦 Скачиваю файлы..." -mkdir -p "$INSTALL_DIR/lib" "$INSTALL_DIR/gotelegram-bot" - -# Основные файлы -dl "install.sh" "$INSTALL_DIR/install.sh" -dl "install_gotelegram_bot.sh" "$INSTALL_DIR/install_gotelegram_bot.sh" -dl "templates_catalog.json" "$INSTALL_DIR/templates_catalog.json" - -# Библиотеки -dl "lib/common.sh" "$INSTALL_DIR/lib/common.sh" -dl "lib/telemt.sh" "$INSTALL_DIR/lib/telemt.sh" -dl "lib/telemt_config.sh" "$INSTALL_DIR/lib/telemt_config.sh" -dl "lib/backup.sh" "$INSTALL_DIR/lib/backup.sh" -dl "lib/website.sh" "$INSTALL_DIR/lib/website.sh" -dl "lib/templates_catalog.sh" "$INSTALL_DIR/lib/templates_catalog.sh" - -# Бот -dl "gotelegram-bot/bot.py" "$INSTALL_DIR/gotelegram-bot/bot.py" -dl "gotelegram-bot/config.example.env" "$INSTALL_DIR/gotelegram-bot/config.example.env" -dl "gotelegram-bot/requirements.txt" "$INSTALL_DIR/gotelegram-bot/requirements.txt" -dl "gotelegram-bot/README.md" "$INSTALL_DIR/gotelegram-bot/README.md" - -# Права -chmod +x "$INSTALL_DIR/install.sh" "$INSTALL_DIR/install_gotelegram_bot.sh" - -# Команда gotelegram -ln -sf "$INSTALL_DIR/install.sh" /usr/local/bin/gotelegram -chmod +x /usr/local/bin/gotelegram - -echo "" -echo -e "\033[1;32m✅ Все 13 файлов скачаны в $INSTALL_DIR\033[0m" -echo -e "\033[1;33m💡 Команда для запуска меню: gotelegram\033[0m" -echo "" - -# Запускаем меню -exec bash "$INSTALL_DIR/install.sh" diff --git a/gotelegram-bot/bot.py b/gotelegram-bot/bot.py index ae2a9ad..a73e0b9 100644 --- a/gotelegram-bot/bot.py +++ b/gotelegram-bot/bot.py @@ -6,14 +6,17 @@ Uses python-telegram-bot v21+ """ import asyncio +import csv import html import json import logging import os import re import subprocess +import time import toml from datetime import datetime +from io import StringIO from pathlib import Path from typing import Tuple, Optional, List, Dict, Any @@ -47,7 +50,7 @@ logger = logging.getLogger(__name__) # CONFIGURATION # ============================================================================ -GOTELEGRAM_VERSION = "2.2.0" +GOTELEGRAM_VERSION = "2.3.0" GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json" TELEMT_CONFIG = "/etc/telemt/config.toml" TELEMT_SERVICE = "telemt" @@ -69,7 +72,7 @@ for _id_str in ALLOWED_IDS_STR.split(","): except ValueError: logging.warning(f"Invalid ALLOWED_IDS entry: {_id_str}") -QUICK_DOMAINS = [ +LITE_DOMAINS = [ "google.com", "microsoft.com", "cloudflare.com", @@ -277,10 +280,13 @@ def get_main_menu() -> InlineKeyboardMarkup: InlineKeyboardButton("🎁 Promo", callback_data="menu_promo"), ], [ + InlineKeyboardButton("📊 Traffic Stats", callback_data="menu_stats"), InlineKeyboardButton("🗑️ Remove", callback_data="menu_remove"), - InlineKeyboardButton("ℹ️ Credits", callback_data="menu_credits"), ], - [InlineKeyboardButton("❌ Close", callback_data="close_menu")], + [ + InlineKeyboardButton("ℹ️ Credits", callback_data="menu_credits"), + InlineKeyboardButton("❌ Close", callback_data="close_menu"), + ], ] return InlineKeyboardMarkup(buttons) @@ -403,19 +409,135 @@ async def get_status_text() -> str: return "\n".join(lines) -async def cb_menu_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Status callback.""" +async def get_traffic_stats() -> str: + """Get formatted traffic statistics.""" + # Read current snapshot + current_file = "/run/gotelegram/stats_current.json" + history_file = "/opt/gotelegram/stats_history.csv" + + try: + with open(current_file, "r") as f: + current = json.load(f) + except Exception: + return "📊 Статистика\n\nДанные недоступны. Убедитесь что модуль статистики включён." + + # Read history + history = [] + try: + with open(history_file, "r") as f: + reader = csv.reader(f) + for row in reader: + if len(row) >= 3: + history.append({ + "ts": int(row[0]), + "proxy": int(row[1]), + "site": int(row[2]), + }) + except Exception: + pass + + now = int(time.time()) + + def format_bytes(b): + if b < 1024: + return f"{b} B" + if b < 1048576: + return f"{b/1024:.1f} KB" + if b < 1073741824: + return f"{b/1048576:.1f} MB" + return f"{b/1073741824:.1f} GB" + + def format_rate(bps): + if bps < 1024: + return f"{bps:.0f} B/s" + if bps < 1048576: + return f"{bps/1024:.1f} KB/s" + return f"{bps/1048576:.1f} MB/s" + + def calc_for_period(secs, key): + target_ts = now - secs + # Find closest snapshot to target_ts + closest = None + for h in history: + if h["ts"] <= target_ts: + if closest is None or h["ts"] > closest["ts"]: + closest = h + if closest is None: + return "—", "—" + + current_val = current.get(f"{key}_bytes", 0) + diff = current_val - closest[key] + if diff < 0: + diff = 0 + elapsed = now - closest["ts"] + if elapsed <= 0: + elapsed = 1 + rate = diff / elapsed + return format_bytes(diff), format_rate(rate) + + periods = [ + ("1 мин", 60), + ("5 мин", 300), + ("60 мин", 3600), + ("1 день", 86400), + ("7 дней", 604800), + ("30 дней", 2592000), + ("365 дней", 31536000), + ] + + lines = ["📊 Статистика трафика\n"] + + for label, key in [("Proxy (telemt)", "proxy"), ("Сайт (nginx)", "site")]: + lines.append(f"\n{label}:") + lines.append("
")
+        lines.append(f"{'Период':<10} │ {'Трафик':>10} │ {'Скорость':>10}")
+        lines.append("─" * 36)
+        for name, secs in periods:
+            total, rate = calc_for_period(secs, key)
+            lines.append(f"{name:<10} │ {total:>10} │ {rate:>10}")
+        lines.append("
") + + return "\n".join(lines) + + +async def cb_menu_stats(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Show traffic statistics.""" query = update.callback_query await query.answer() - await safe_edit_message(query,"⏳ Checking status...") + stats_text = await get_traffic_stats() - status_text = await get_status_text() - keyboard = InlineKeyboardMarkup( - [[InlineKeyboardButton("« Back", callback_data="menu_main")]] + keyboard = [ + [InlineKeyboardButton("🔄 Обновить", callback_data="menu_stats")], + [InlineKeyboardButton("« Меню", callback_data="menu_main")], + ] + + await safe_edit_message( + query, + stats_text, + reply_markup=InlineKeyboardMarkup(keyboard), + parse_mode="HTML", ) - await safe_edit_message(query, - status_text, reply_markup=keyboard, parse_mode="HTML" + + +async def cb_menu_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Status callback — show detailed proxy/server status.""" + query = update.callback_query + await query.answer() + + if not await require_auth(update, context): + return + + text = await get_status_text() + keyboard = [ + [InlineKeyboardButton("🔄 Обновить", callback_data="menu_status")], + [InlineKeyboardButton("« Меню", callback_data="menu_main")], + ] + await safe_edit_message( + query, + text, + reply_markup=InlineKeyboardMarkup(keyboard), + parse_mode="HTML", ) @@ -427,8 +549,8 @@ async def cb_menu_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> def get_install_mode_menu() -> InlineKeyboardMarkup: """Install mode selection menu.""" buttons = [ - [InlineKeyboardButton("⚡ Quick Mode", callback_data="install_mode_quick")], - [InlineKeyboardButton("🔒 Stealth Mode", callback_data="install_mode_stealth")], + [InlineKeyboardButton("⚡ Lite", callback_data="install_mode_lite")], + [InlineKeyboardButton("🛡 Pro", callback_data="install_mode_pro")], [InlineKeyboardButton("« Back", callback_data="menu_main")], ] return InlineKeyboardMarkup(buttons) @@ -452,7 +574,7 @@ async def cb_menu_install(update: Update, context: ContextTypes.DEFAULT_TYPE) -> ) buttons = [ [InlineKeyboardButton("🔄 Migrate from v1", callback_data="install_migrate")], - [InlineKeyboardButton("✨ Fresh Install", callback_data="install_mode_quick")], + [InlineKeyboardButton("✨ Fresh Install", callback_data="install_mode_lite")], [InlineKeyboardButton("« Back", callback_data="menu_main")], ] keyboard = InlineKeyboardMarkup(buttons) @@ -465,39 +587,39 @@ async def cb_menu_install(update: Update, context: ContextTypes.DEFAULT_TYPE) -> ) -async def cb_install_mode_quick(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Quick mode domain selection.""" +async def cb_install_mode_lite(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Lite mode domain selection.""" query = update.callback_query await query.answer() # Show domains with pagination (4 per row, 2 rows) buttons = [] - for i in range(0, len(QUICK_DOMAINS), 2): + for i in range(0, len(LITE_DOMAINS), 2): row = [] for j in range(2): - if i + j < len(QUICK_DOMAINS): - domain = QUICK_DOMAINS[i + j] + if i + j < len(LITE_DOMAINS): + domain = LITE_DOMAINS[i + j] row.append( InlineKeyboardButton( - domain, callback_data=f"quick_dom_{i+j}" + domain, callback_data=f"lite_dom_{i+j}" ) ) buttons.append(row) buttons.append([InlineKeyboardButton("« Back", callback_data="menu_install")]) - text = "Select a domain for quick mode:" + text = "Select a domain for Lite mode:" keyboard = InlineKeyboardMarkup(buttons) await safe_edit_message(query,text, reply_markup=keyboard) -async def cb_quick_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Quick domain selection callback.""" +async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Lite domain selection callback.""" query = update.callback_query data = query.data try: domain_idx = int(data.split("_")[-1]) - domain = QUICK_DOMAINS[domain_idx] + domain = LITE_DOMAINS[domain_idx] except (ValueError, IndexError): await query.answer("Invalid domain selection") return @@ -507,7 +629,7 @@ async def cb_quick_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> # Simulate installation (in real scenario, call install script) config = { - "mode": "quick", + "mode": "lite", "domain": domain, "port": 443, "installed_at": datetime.now().isoformat(), @@ -515,9 +637,9 @@ async def cb_quick_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> if save_json(GOTELEGRAM_CONFIG, config): text = ( - f"✅ Quick mode installed!\n\n" + f"✅ Lite mode installed!\n\n" f"Domain: {domain}\n" - f"Mode: Quick\n\n" + f"Mode: Lite\n\n" f"Service starting... Check status in 10 seconds." ) keyboard = InlineKeyboardMarkup( @@ -535,8 +657,8 @@ async def cb_quick_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> ) -async def cb_install_mode_stealth(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Stealth mode - show template categories.""" +async def cb_install_mode_pro(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Pro mode - show template categories.""" query = update.callback_query await query.answer() @@ -555,22 +677,22 @@ async def cb_install_mode_stealth(update: Update, context: ContextTypes.DEFAULT_ buttons.append( [ InlineKeyboardButton( - f"📁 {cat['name']}", callback_data=f"stealth_cat_{cat['id']}" + f"📁 {cat['name']}", callback_data=f"pro_cat_{cat['id']}" ) ] ) buttons.append([InlineKeyboardButton("« Back", callback_data="menu_install")]) - text = "Stealth Mode - Select Template Category:" + text = "Pro Mode - Select Template Category:" keyboard = InlineKeyboardMarkup(buttons) await safe_edit_message(query,text, reply_markup=keyboard) -async def cb_stealth_category(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: +async def cb_pro_category(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Show templates in category.""" query = update.callback_query data = query.data - cat_id = data.removeprefix("stealth_cat_") + cat_id = data.removeprefix("pro_cat_") await query.answer() @@ -597,22 +719,22 @@ async def cb_stealth_category(update: Update, context: ContextTypes.DEFAULT_TYPE buttons.append( [ InlineKeyboardButton( - f"🎨 {tpl['name']}", callback_data=f"stealth_tpl_{tpl['id']}" + f"🎨 {tpl['name']}", callback_data=f"pro_tpl_{tpl['id']}" ) ] ) - buttons.append([InlineKeyboardButton("« Back", callback_data="install_mode_stealth")]) + buttons.append([InlineKeyboardButton("« Back", callback_data="install_mode_pro")]) text = f"Select template from {html.escape(category['name'])}:" keyboard = InlineKeyboardMarkup(buttons) await safe_edit_message(query,text, reply_markup=keyboard, parse_mode="HTML") -async def cb_stealth_template(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: +async def cb_pro_template(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Show template preview and confirm.""" query = update.callback_query data = query.data - tpl_id = data.removeprefix("stealth_tpl_") + tpl_id = data.removeprefix("pro_tpl_") await query.answer() @@ -651,26 +773,26 @@ async def cb_stealth_template(update: Update, context: ContextTypes.DEFAULT_TYPE buttons = [ [ InlineKeyboardButton( - "✅ Install", callback_data=f"stealth_confirm_{tpl_id}" + "✅ Install", callback_data=f"pro_confirm_{tpl_id}" ) ], - [InlineKeyboardButton("« Back", callback_data="install_mode_stealth")], + [InlineKeyboardButton("« Back", callback_data="install_mode_pro")], ] keyboard = InlineKeyboardMarkup(buttons) await safe_edit_message(query,text, reply_markup=keyboard, parse_mode="HTML") -async def cb_stealth_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Confirm and install stealth template.""" +async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Confirm and install pro template.""" query = update.callback_query data = query.data - tpl_id = data.removeprefix("stealth_confirm_") + tpl_id = data.removeprefix("pro_confirm_") await query.answer() await safe_edit_message(query,"⏳ Installing template...") config = { - "mode": "stealth", + "mode": "pro", "template": tpl_id, "port": 443, "installed_at": datetime.now().isoformat(), @@ -678,9 +800,9 @@ async def cb_stealth_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) if save_json(GOTELEGRAM_CONFIG, config): text = ( - f"✅ Stealth mode installed!\n\n" + f"✅ Pro mode installed!\n\n" f"Template: {html.escape(tpl_id)}\n" - f"Mode: Stealth\n\n" + f"Mode: Pro\n\n" f"Service starting... Check status in 10 seconds." ) keyboard = InlineKeyboardMarkup( @@ -1074,8 +1196,8 @@ async def cb_menu_change(update: Update, context: ContextTypes.DEFAULT_TYPE) -> await query.answer() buttons = [ - [InlineKeyboardButton("⚡ Switch to Quick Mode", callback_data="change_quick")], - [InlineKeyboardButton("🔒 Switch to Stealth Mode", callback_data="change_stealth")], + [InlineKeyboardButton("⚡ Switch to Lite Mode", callback_data="change_lite")], + [InlineKeyboardButton("🛡 Switch to Pro Mode", callback_data="change_pro")], [InlineKeyboardButton("« Back", callback_data="menu_main")], ] keyboard = InlineKeyboardMarkup(buttons) @@ -1084,20 +1206,20 @@ async def cb_menu_change(update: Update, context: ContextTypes.DEFAULT_TYPE) -> ) -async def cb_change_quick(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Switch to quick mode — show domain selection.""" +async def cb_change_lite(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Switch to lite mode — show domain selection.""" query = update.callback_query await query.answer() - # Reuse the quick mode domain selection flow - await cb_install_mode_quick(update, context) + # Reuse the lite mode domain selection flow + await cb_install_mode_lite(update, context) -async def cb_change_stealth(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: - """Switch to stealth mode — show template categories.""" +async def cb_change_pro(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Switch to pro mode — show template categories.""" query = update.callback_query await query.answer() - # Reuse the stealth mode template selection flow - await cb_install_mode_stealth(update, context) + # Reuse the pro mode template selection flow + await cb_install_mode_pro(update, context) async def cb_install_migrate(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: @@ -1328,27 +1450,28 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> "menu_promo": cb_menu_promo, "menu_credits": cb_menu_credits, "menu_remove": cb_menu_remove, - "install_mode_quick": cb_install_mode_quick, - "install_mode_stealth": cb_install_mode_stealth, + "install_mode_lite": cb_install_mode_lite, + "install_mode_pro": cb_install_mode_pro, "backup_create": cb_backup_create, "backup_list": cb_backup_list, "ssl_renew": cb_ssl_renew, "ssl_status": cb_ssl_status, "remove_confirm": cb_remove_confirm, - "change_quick": cb_change_quick, - "change_stealth": cb_change_stealth, + "change_lite": cb_change_lite, + "change_pro": cb_change_pro, "install_migrate": cb_install_migrate, + "menu_stats": cb_menu_stats, } # Pattern-based handlers - if data.startswith("quick_dom_"): - await cb_quick_domain(update, context) - elif data.startswith("stealth_cat_"): - await cb_stealth_category(update, context) - elif data.startswith("stealth_tpl_"): - await cb_stealth_template(update, context) - elif data.startswith("stealth_confirm_"): - await cb_stealth_confirm(update, context) + if data.startswith("lite_dom_"): + await cb_lite_domain(update, context) + elif data.startswith("pro_cat_"): + await cb_pro_category(update, context) + elif data.startswith("pro_tpl_"): + await cb_pro_template(update, context) + elif data.startswith("pro_confirm_"): + await cb_pro_confirm(update, context) elif data.startswith("restore_idx_"): await cb_restore_backup(update, context) elif data in handlers: diff --git a/install.sh b/install.sh index 2ca529a..f392c24 100755 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # ══════════════════════════════════════════════════════════════════════════════ -# GoTelegram v2.2.1 — MTProxy на ядре telemt (Rust + Tokio) +# GoTelegram v2.3.0 — MTProxy на ядре telemt (Rust + Tokio) # Anti-DPI • Fake TLS • TCP Splice • JA3/JA4 Resistance # # Установка: @@ -20,8 +20,9 @@ source "$LIB_DIR/telemt_config.sh" source "$LIB_DIR/website.sh" source "$LIB_DIR/templates_catalog.sh" source "$LIB_DIR/backup.sh" +[ -f "$LIB_DIR/stats.sh" ] && source "$LIB_DIR/stats.sh" -# ── Главное меню ───────────────────────────────────────────────────────────── +# ── Главное меню (Compact Dashboard + 5 Top-Level Items) ────────────────────── show_main_menu() { local proxy_status bot_status nginx_st mode domain secret port ip link ssl_expiry proxy_status=$(telemt_status) @@ -33,14 +34,14 @@ show_main_menu() { port=$(get_config_value port 2>/dev/null || echo "443") ip=$(get_server_ip 2>/dev/null || echo "N/A") - local W=60 + local W=54 local line; line=$(printf '━%.0s' $(seq 1 $W)) local line2; line2=$(printf '─%.0s' $(seq 1 $W)) # ── Заголовок ── echo "" echo -e " ${BOLD}${CYAN}┏${line}┓${NC}" - echo -e " ${BOLD}${CYAN}┃${NC} ${BOLD}${WHITE}GoTelegram v${GOTELEGRAM_VERSION}${NC} — Панель мониторинга ${BOLD}${CYAN}┃${NC}" + echo -e " ${BOLD}${CYAN}┃${NC} ${BOLD}${WHITE}GoTelegram v${GOTELEGRAM_VERSION}${NC} Панель управления ${BOLD}${CYAN}┃${NC}" echo -e " ${BOLD}${CYAN}┗${line}┛${NC}" # ── Здоровье сервисов ── @@ -62,10 +63,10 @@ show_main_menu() { running) nginx_icon="●"; nginx_color="${GREEN}" ;; *) nginx_icon="✗"; nginx_color="${RED}" ;; esac - echo -e " ${nginx_color}${nginx_icon}${NC} nginx ${nginx_color}${nginx_st}${NC} ${DIM}(127.0.0.1:8443)${NC}" + echo -e " ${nginx_icon}${nginx_color}${NC} nginx ${nginx_color}${nginx_st}${NC} ${DIM}(127.0.0.1:8443)${NC}" - # Site (stealth) - if [ "$mode" = "stealth" ] && [ -n "$domain" ]; then + # Site (pro) + if [ "$mode" = "pro" ] && [ -n "$domain" ]; then local site_icon site_color if curl -sk --max-time 3 "https://${domain}/" -o /dev/null 2>/dev/null; then site_icon="●"; site_color="${GREEN}" @@ -96,7 +97,7 @@ show_main_menu() { # ── Прокси-ссылка + QR ── if [ -n "$secret" ] && [ "$proxy_status" = "running" ]; then - if [ "$mode" = "stealth" ] && [ -n "$domain" ]; then + if [ "$mode" = "pro" ] && [ -n "$domain" ]; then local raw_secret faketls_secret domain_hex raw_secret="$secret" domain_hex=$(printf '%s' "$domain" | xxd -p | tr -d '\n') @@ -117,27 +118,129 @@ show_main_menu() { echo "" fi else - echo -e " ${DIM}Прокси не настроен. Выберите п.1 для установки.${NC}" + echo -e " ${DIM}Прокси не настроен. Выберите пункт 1.${NC}" echo "" fi # ── Меню ── echo -e " ${DIM}${line2}${NC}" - echo -e " ${DIM}ПРОКСИ${NC} ${DIM}УПРАВЛЕНИЕ${NC}" - echo -e " ${CYAN}1${NC}) Установить / Обновить ${CYAN}8${NC}) Бекап" - echo -e " ${CYAN}2${NC}) Статус подробно ${CYAN}9${NC}) Восстановить" - echo -e " ${CYAN}3${NC}) Скопировать ссылку ${CYAN}10${NC}) Обновить telemt" - echo -e " ${CYAN}4${NC}) Поделиться ключом ${CYAN}11${NC}) Сайт / SSL" - echo -e " ${CYAN}5${NC}) Перезапуск" - echo -e " ${CYAN}6${NC}) Логи ${DIM}БОТ И ПРОЧЕЕ${NC}" - echo -e " ${CYAN}7${NC}) Сменить режим / шаблон ${CYAN}12${NC}) Telegram-бот" - echo -e " ${CYAN}13${NC}) Удалить всё" - echo -e " ${CYAN}0${NC}) ${DIM}Выход${NC} ${CYAN}14${NC}) Промо" + echo -e " ${CYAN}1${NC}) Прокси ▸" + echo -e " ${CYAN}2${NC}) Статистика ▸" + echo -e " ${CYAN}3${NC}) Управление ▸" + echo -e " ${CYAN}4${NC}) Telegram-бот ▸" + echo -e " ${CYAN}5${NC}) О программе ▸" + echo -e " ${CYAN}0${NC}) ${DIM}Выход${NC}" echo -e " ${DIM}${line2}${NC}" - echo -e " ${DIM}Обновление через 30 сек${NC}" + echo -e " ${DIM}Обновление через 1 сек${NC}" echo -ne " ${WHITE}▸ ${NC}" } +# ── Подменю: Прокси ────────────────────────────────────────────────────────── +submenu_proxy() { + while true; do + echo "" + echo -e " ${BOLD}${WHITE}🚀 ПРОКСИ${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -e " ${CYAN}1${NC}) Установить / Обновить" + echo -e " ${CYAN}2${NC}) Статус подробно" + echo -e " ${CYAN}3${NC}) Скопировать ссылку" + echo -e " ${CYAN}4${NC}) Поделиться ключом" + echo -e " ${CYAN}5${NC}) Перезапуск" + echo -e " ${CYAN}6${NC}) Логи" + echo -e " ${CYAN}7${NC}) Сменить режим / шаблон" + echo -e " ${CYAN}0${NC}) « Назад" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -ne " ${WHITE}Выбор:${NC} " + read -r ch + + case "$ch" in + 1) menu_install ;; + 2) menu_status ;; + 3) menu_link ;; + 4) menu_share ;; + 5) menu_restart ;; + 6) menu_logs ;; + 7) menu_change_mode ;; + 0) break ;; + *) log_error "Неверный выбор" ;; + esac + + echo "" + echo -ne " ${DIM}Нажмите Enter...${NC}" + read -r + done +} + +# ── Подменю: Управление ────────────────────────────────────────────────────── +submenu_manage() { + while true; do + echo "" + echo -e " ${BOLD}${WHITE}⚙️ УПРАВЛЕНИЕ${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -e " ${CYAN}1${NC}) Бекап" + echo -e " ${CYAN}2${NC}) Восстановить" + echo -e " ${CYAN}3${NC}) Обновить telemt" + echo -e " ${CYAN}4${NC}) Сайт / SSL" + echo -e " ${CYAN}5${NC}) Удалить" + echo -e " ${CYAN}0${NC}) « Назад" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -ne " ${WHITE}Выбор:${NC} " + read -r ch + + case "$ch" in + 1) interactive_backup ;; + 2) interactive_restore ;; + 3) update_telemt ;; + 4) menu_website ;; + 5) menu_remove ;; + 0) break ;; + *) log_error "Неверный выбор" ;; + esac + + echo "" + echo -ne " ${DIM}Нажмите Enter...${NC}" + read -r + done +} + +# ── Подменю: О программе ───────────────────────────────────────────────────── +submenu_about() { + while true; do + echo "" + echo -e " ${BOLD}${WHITE}ℹ️ О ПРОГРАММЕ${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -e " ${CYAN}1${NC}) Информация о версии" + echo -e " ${CYAN}2${NC}) Промо / Донат" + echo -e " ${CYAN}0${NC}) « Назад" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -ne " ${WHITE}Выбор:${NC} " + read -r ch + + case "$ch" in + 1) menu_version ;; + 2) menu_promo ;; + 0) break ;; + *) log_error "Неверный выбор" ;; + esac + + echo "" + echo -ne " ${DIM}Нажмите Enter...${NC}" + read -r + done +} + +# ── Информация о версии ────────────────────────────────────────────────────── +menu_version() { + echo "" + echo -e " ${BOLD}${WHITE}🔍 Информация${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -e " ${WHITE}GoTelegram:${NC} v${GOTELEGRAM_VERSION}" + echo -e " ${WHITE}Ядро:${NC} telemt (Rust + Tokio)" + echo -e " ${WHITE}Технология:${NC} Anti-DPI, Fake TLS, TCP Splice" + echo -e " ${WHITE}Лицензия:${NC} MIT" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" +} + # ── Установка: выбор режима ────────────────────────────────────────────────── menu_install() { # Проверяем v1 @@ -154,29 +257,29 @@ menu_install() { echo "" echo -e " ${BOLD}${WHITE}🎭 Выберите режим маскировки:${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" - echo -e " ${CYAN}1)${NC} ${GREEN}⚡ Quick${NC} — маскировка под популярный сайт" + echo -e " ${CYAN}1)${NC} ${GREEN}⚡ Lite${NC} — маскировка под популярный сайт" echo -e " ${DIM}Быстро, без домена. telemt маскирует трафик${NC}" echo -e " ${DIM}под выбранный сайт (google.com и т.д.)${NC}" echo "" - echo -e " ${CYAN}2)${NC} ${MAGENTA}🛡 Stealth${NC} — свой сайт + полная маскировка" + echo -e " ${CYAN}2)${NC} ${MAGENTA}🛡 Pro${NC} — свой сайт + полная маскировка" echo -e " ${DIM}nginx + SSL + HTML-шаблон + telemt.${NC}" echo -e " ${DIM}DPI видит реальный сайт с реальным сертификатом.${NC}" - echo -e " ${DIM}Требует: домен, направленный на этот сервер.${NC}" + echo -e " ${DIM}Требует: домен, направленный на этот сервер.{{NC}" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" echo -ne " ${WHITE}Выбор (1/2):${NC} " read -r mode_choice mode_choice="${mode_choice:-}" case "$mode_choice" in - 1) install_quick_mode ;; - 2) install_stealth_mode ;; + 1) install_lite_mode ;; + 2) install_pro_mode ;; *) log_error "Неверный выбор: ${mode_choice:-<пусто>}" ;; esac } -# ── Quick-режим ────────────────────────────────────────────────────────────── -install_quick_mode() { - log_step "Установка Quick-режима" +# ── Lite-режим ─────────────────────────────────────────────────────────────── +install_lite_mode() { + log_step "Установка Lite-режима" # Выбор домена local domain @@ -200,7 +303,7 @@ install_quick_mode() { echo -e " IP: ${CYAN}${ip}${NC}" echo -e " Порт: ${CYAN}${port}${NC}" echo -e " Маскировка: ${CYAN}${domain}${NC}" - echo -e " Режим: ${GREEN}Quick${NC}" + echo -e " Режим: ${GREEN}Lite${NC}" echo "" if ! confirm "Установить прокси?"; then @@ -212,7 +315,7 @@ install_quick_mode() { install_telemt_full || return # Генерируем конфиг telemt - generate_telemt_toml "$secret" "$port" "quick" "$domain" "443" + generate_telemt_toml "$secret" "$port" "lite" "$domain" "443" # Валидация validate_telemt_config || return @@ -221,19 +324,19 @@ install_quick_mode() { start_telemt || return # Сохраняем GoTelegram конфиг - save_gotelegram_config "telemt" "quick" "$port" "$secret" "$domain" "" "" + save_gotelegram_config "telemt" "lite" "$port" "$secret" "$domain" "" "" # Благодарности show_credits # Результат show_proxy_info - log_success "GoTelegram v${GOTELEGRAM_VERSION} установлен! (Quick-режим)" + log_success "GoTelegram v${GOTELEGRAM_VERSION} установлен! (Lite-режим)" } -# ── Stealth-режим ──────────────────────────────────────────────────────────── -install_stealth_mode() { - log_step "Установка Stealth-режима" +# ── Pro-режим ──────────────────────────────────────────────────────────────── +install_pro_mode() { + log_step "Установка Pro-режима" # Ввод домена echo "" @@ -266,7 +369,7 @@ install_stealth_mode() { template_dir=$(interactive_template_selection) [ $? -ne 0 ] && return - # Архитектура Stealth: + # Архитектура Pro: # telemt слушает на 0.0.0.0:443 (принимает ВСЕ подключения) # nginx слушает на 127.0.0.1:8443 с SSL (обслуживает сайт) # MTProxy клиент → :443 → telemt (проксирует) @@ -291,7 +394,7 @@ install_stealth_mode() { echo -e " ${BOLD}${WHITE}📋 Конфигурация:${NC}" echo -e " Домен: ${CYAN}${user_domain}${NC}" echo -e " Порт: ${CYAN}443 (telemt + nginx внутри)${NC}" - echo -e " Режим: ${MAGENTA}Stealth (fake-TLS)${NC}" + echo -e " Режим: ${MAGENTA}Pro (fake-TLS)${NC}" echo "" if ! confirm "Установить прокси + сайт?"; then @@ -303,10 +406,10 @@ install_stealth_mode() { install_telemt_full || return # Конфиг telemt: слушает 443, маскировка на локальный nginx через dns_override - generate_telemt_toml "$raw_secret" "443" "stealth" "$user_domain" "$nginx_internal_port" + generate_telemt_toml "$raw_secret" "443" "pro" "$user_domain" "$nginx_internal_port" # Настройка сайта (nginx на внутреннем порту + certbot + шаблон) - setup_stealth_mode "$user_domain" "$template_dir" "$nginx_internal_port" "$ssl_email" || return + setup_pro_mode "$user_domain" "$template_dir" "$nginx_internal_port" "$ssl_email" || return # Останавливаем nginx на 443 перед запуском telemt (telemt займёт 443) # nginx уже перенастроен на внутренний порт @@ -318,22 +421,22 @@ install_stealth_mode() { # Сохраняем конфиг local tpl_id tpl_id=$(basename "$template_dir") - save_gotelegram_config "telemt" "stealth" "443" "$raw_secret" "$user_domain" "$user_domain" "$tpl_id" + save_gotelegram_config "telemt" "pro" "443" "$raw_secret" "$user_domain" "$user_domain" "$tpl_id" # Результат — используем домен и fake-TLS ссылку - show_proxy_info_stealth "$user_domain" "$faketls_secret" + show_proxy_info_pro "$user_domain" "$faketls_secret" echo -e " ${WHITE}Сайт:${NC} ${GREEN}https://${user_domain}${NC}" - log_success "GoTelegram v${GOTELEGRAM_VERSION} установлен! (Stealth-режим)" + log_success "GoTelegram v${GOTELEGRAM_VERSION} установлен! (Pro-режим)" } # ── Статус ─────────────────────────────────────────────────────────────────── menu_status() { show_proxy_info - # Дополнительно для stealth + # Дополнительно для pro local mode mode=$(config_get mode 2>/dev/null) - if [ "$mode" = "stealth" ]; then + if [ "$mode" = "pro" ]; then local domain domain=$(config_get domain 2>/dev/null) if [ -n "$domain" ]; then @@ -396,7 +499,7 @@ menu_restart() { restart_telemt local mode mode=$(config_get mode 2>/dev/null) - if [ "$mode" = "stealth" ]; then + if [ "$mode" = "pro" ]; then restart_nginx fi } @@ -417,16 +520,16 @@ menu_change_mode() { echo "" echo -e " ${WHITE}Текущий режим:${NC} ${CYAN}${current_mode}${NC}" echo "" - echo -e " ${CYAN}1)${NC} Сменить шаблон сайта (только stealth)" - echo -e " ${CYAN}2)${NC} Переключить режим (quick ↔ stealth)" - echo -e " ${CYAN}0)${NC} Назад" + echo -e " ${CYAN}1${NC}) Сменить шаблон сайта (только pro)" + echo -e " ${CYAN}2${NC}) Переключить режим (lite ↔ pro)" + echo -e " ${CYAN}0${NC}) Назад" echo -ne " ${WHITE}Выбор:${NC} " read -r ch case "$ch" in 1) - if [ "$current_mode" != "stealth" ]; then - log_error "Смена шаблона доступна только в stealth-режиме" + if [ "$current_mode" != "pro" ]; then + log_error "Смена шаблона доступна только в pro-режиме" return fi local template_dir @@ -443,12 +546,13 @@ menu_change_mode() { esac } -# ── Управление сайтом ─────────────────────────────────────────────────────── +# ── Управление сайтом ──────────────────────────────────────────────────────── menu_website() { local mode mode=$(config_get mode 2>/dev/null) - if [ "$mode" != "stealth" ]; then - log_info "Управление сайтом доступно только в stealth-режиме" + + if [ "$mode" != "pro" ]; then + log_info "Управление сайтом доступно только в pro-режиме" return fi @@ -460,10 +564,10 @@ menu_website() { echo -e " Домен: ${CYAN}${domain}${NC}" echo -e " SSL до: $(get_ssl_expiry "$domain")" echo "" - echo -e " ${CYAN}1)${NC} Обновить SSL сертификат" - echo -e " ${CYAN}2)${NC} Перезапустить nginx" - echo -e " ${CYAN}3)${NC} Сменить шаблон" - echo -e " ${CYAN}0)${NC} Назад" + echo -e " ${CYAN}1${NC}) Обновить SSL сертификат" + echo -e " ${CYAN}2${NC}) Перезапустить nginx" + echo -e " ${CYAN}3${NC}) Сменить шаблон" + echo -e " ${CYAN}0${NC}) Назад" echo -ne " ${WHITE}Выбор:${NC} " read -r ch @@ -484,10 +588,10 @@ menu_remove() { echo "" echo -e " ${BOLD}${RED}🗑 Удаление GoTelegram${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" - echo -e " ${CYAN}1)${NC} Удалить только прокси (telemt)" - echo -e " ${CYAN}2)${NC} Удалить только Telegram-бота" - echo -e " ${CYAN}3)${NC} Удалить всё (прокси + бот + настройки)" - echo -e " ${CYAN}0)${NC} Назад" + echo -e " ${CYAN}1${NC}) Удалить только прокси (telemt)" + echo -e " ${CYAN}2${NC}) Удалить только Telegram-бота" + echo -e " ${CYAN}3${NC}) Удалить всё (прокси + бот + настройки)" + echo -e " ${CYAN}0${NC}) Назад" echo -ne " ${WHITE}Выбор:${NC} " read -r rm_choice @@ -501,8 +605,8 @@ menu_remove() { remove_telemt local mode mode=$(config_get mode 2>/dev/null) - if [ "$mode" = "stealth" ]; then - remove_stealth_mode + if [ "$mode" = "pro" ]; then + remove_pro_mode fi rm -f "$GOTELEGRAM_CONFIG" log_success "Прокси удалён" @@ -520,8 +624,8 @@ menu_remove() { remove_telemt local mode mode=$(config_get mode 2>/dev/null) - if [ "$mode" = "stealth" ]; then - remove_stealth_mode + if [ "$mode" = "pro" ]; then + remove_pro_mode fi rm -f "$GOTELEGRAM_CONFIG" # Бот @@ -563,21 +667,21 @@ menu_bot() { running) echo -e " Статус: ${GREEN}● Работает${NC}" echo "" - echo -e " ${CYAN}1)${NC} 📊 Статус бота" - echo -e " ${CYAN}2)${NC} 📋 Логи бота" - echo -e " ${CYAN}3)${NC} 🔄 Перезапустить бота" - echo -e " ${CYAN}4)${NC} ⏹ Остановить бота" - echo -e " ${CYAN}5)${NC} ⚙️ Настройки (.env)" - echo -e " ${CYAN}6)${NC} 🗑 Удалить бота" + echo -e " ${CYAN}1${NC}) 📊 Статус бота" + echo -e " ${CYAN}2${NC}) 📋 Логи бота" + echo -e " ${CYAN}3${NC}) 🔄 Перезапустить бота" + echo -e " ${CYAN}4${NC}) ⏹ Остановить бота" + echo -e " ${CYAN}5${NC}) ⚙️ Настройки (.env)" + echo -e " ${CYAN}6${NC}) 🗑 Удалить бота" ;; stopped) echo -e " Статус: ${YELLOW}○ Остановлен${NC}" echo "" - echo -e " ${CYAN}1)${NC} 📊 Статус бота" - echo -e " ${CYAN}2)${NC} 📋 Логи бота" - echo -e " ${CYAN}3)${NC} ▶️ Запустить бота" - echo -e " ${CYAN}5)${NC} ⚙️ Настройки (.env)" - echo -e " ${CYAN}6)${NC} 🗑 Удалить бота" + echo -e " ${CYAN}1${NC}) 📊 Статус бота" + echo -e " ${CYAN}2${NC}) 📋 Логи бота" + echo -e " ${CYAN}3${NC}) ▶️ Запустить бота" + echo -e " ${CYAN}5${NC}) ⚙️ Настройки (.env)" + echo -e " ${CYAN}6${NC}) 🗑 Удалить бота" ;; *) echo -e " Статус: ${RED}✗ Не установлен${NC}" @@ -585,11 +689,11 @@ menu_bot() { echo -e " ${DIM}Бот позволяет управлять прокси прямо из Telegram:${NC}" echo -e " ${DIM}статус, перезапуск, смена режима, бекап, QR-код.${NC}" echo "" - echo -e " ${CYAN}1)${NC} 🔧 Установить бота" + echo -e " ${CYAN}1${NC}) 🔧 Установить бота" ;; esac - echo -e " ${CYAN}0)${NC} « Назад" + echo -e " ${CYAN}0${NC}) « Назад" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" echo -ne " ${WHITE}Выбор:${NC} " read -r ch @@ -762,9 +866,9 @@ bot_edit_config() { fi echo "" - echo -e " ${CYAN}1)${NC} Сменить BOT_TOKEN" - echo -e " ${CYAN}2)${NC} Изменить ALLOWED_IDS" - echo -e " ${CYAN}0)${NC} Назад" + echo -e " ${CYAN}1${NC}) Сменить BOT_TOKEN" + echo -e " ${CYAN}2${NC}) Изменить ALLOWED_IDS" + echo -e " ${CYAN}0${NC}) Назад" echo -ne " ${WHITE}Выбор:${NC} " read -r ch @@ -842,33 +946,74 @@ main() { while true; do clear show_main_menu - # Auto-refresh: 30 sec timeout - if read -t 30 -r choice; then + # Auto-refresh: 1 sec timeout + if read -t 1 -r choice; then case "$choice" in - 1) menu_install ;; - 2) menu_status ;; - 3) menu_link ;; - 4) menu_share ;; - 5) menu_restart ;; - 6) menu_logs ;; - 7) menu_change_mode ;; - 8) interactive_backup ;; - 9) interactive_restore ;; - 10) update_telemt ;; - 11) menu_website ;; - 12) menu_bot ;; - 13) menu_remove ;; - 14) menu_promo ;; + 1) submenu_proxy ;; + 2) submenu_stats ;; + 3) submenu_manage ;; + 4) menu_bot ;; + 5) submenu_about ;; 0|q|exit) echo ""; log_info "До встречи! 👋"; exit 0 ;; *) log_error "Неверный выбор" ;; esac - - echo "" - echo -ne " ${DIM}Нажмите Enter для возврата в меню...${NC}" - read -r fi # If read timed out, loop refreshes the dashboard done } +# ── Статистика (авто-обновление 1 сек) ────────────────────────────────────── +submenu_stats() { + # Инициализируем статистику при первом входе + if type stats_init &>/dev/null; then + stats_init 2>/dev/null + fi + + while true; do + clear + echo "" + echo -e " ${BOLD}${WHITE}📊 Статистика трафика${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + + if type show_traffic_stats &>/dev/null; then + show_traffic_stats + else + echo -e " ${DIM}Модуль статистики не загружен.${NC}" + echo -e " ${DIM}Файл lib/stats.sh не найден.${NC}" + fi + + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + local stats_on="вкл" + if type toggle_stats &>/dev/null; then + local cfg_val + cfg_val=$(config_get stats_enabled 2>/dev/null || echo "true") + [ "$cfg_val" = "false" ] && stats_on="выкл" + fi + echo -e " ${CYAN}1${NC}) Вкл/Выкл подсчёт (сейчас: ${stats_on})" + echo -e " ${CYAN}2${NC}) Установить/обновить сборщик статистики" + echo -e " ${CYAN}0${NC}) ${DIM}← Назад${NC}" + echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" + echo -e " ${DIM}Обновление через 1 сек${NC}" + echo -ne " ${WHITE}▸ ${NC}" + + if read -t 1 -r ch; then + case "$ch" in + 1) + if type toggle_stats &>/dev/null; then + toggle_stats + echo -ne " ${DIM}Нажмите Enter...${NC}"; read -r + fi + ;; + 2) + if type install_stats_collector &>/dev/null; then + install_stats_collector + echo -ne " ${DIM}Нажмите Enter...${NC}"; read -r + fi + ;; + 0|"") return ;; + esac + fi + done +} + main "$@" diff --git a/install_gotelegram_bot.sh b/install_gotelegram_bot.sh deleted file mode 100644 index bb0f1a6..0000000 --- a/install_gotelegram_bot.sh +++ /dev/null @@ -1,132 +0,0 @@ -#!/bin/bash -# GoTelegram v2.2.1 — Установка Telegram-бота -# Создаёт venv, ставит зависимости, настраивает systemd - -set -e -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - -BOT_DIR="/opt/gotelegram-bot" -SERVICE_NAME="gotelegram-bot" -GOTELEGRAM_DIR="/opt/gotelegram" - -if [ "$EUID" -ne 0 ]; then - echo -e "${RED}Запустите с sudo.${NC}" - exit 1 -fi - -echo -e "${CYAN}╔═══════════════════════════════════════════╗${NC}" -echo -e "${CYAN}║${NC} ${GREEN}GoTelegram v2.2.1 — Установка бота${NC} ${CYAN}║${NC}" -echo -e "${CYAN}╚═══════════════════════════════════════════╝${NC}" -echo "" - -# ── Python ─────────────────────────────────────────────────────────────────── -if ! command -v python3 &>/dev/null; then - echo -e "${YELLOW}[*] Установка python3...${NC}" - if command -v apt-get &>/dev/null; then - apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv - elif command -v dnf &>/dev/null; then - dnf install -y -q python3 python3-pip - elif command -v yum &>/dev/null; then - yum install -y -q python3 python3-pip - fi -fi - -# ── Каталог бота ───────────────────────────────────────────────────────────── -mkdir -p "$BOT_DIR" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -if [ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ]; then - echo -e "${GREEN}[*] Копирование файлов бота...${NC}" - cp "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/" - cp "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/" - [ -f "$SCRIPT_DIR/gotelegram-bot/config.example.env" ] && cp "$SCRIPT_DIR/gotelegram-bot/config.example.env" "$BOT_DIR/" -else - echo -e "${RED}Файлы бота не найдены в $SCRIPT_DIR/gotelegram-bot/${NC}" - exit 1 -fi - -# Копируем каталог шаблонов -if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then - mkdir -p "$GOTELEGRAM_DIR" - cp "$SCRIPT_DIR/templates_catalog.json" "$GOTELEGRAM_DIR/" - echo -e "${GREEN}[*] Каталог шаблонов скопирован${NC}" -fi - -# ── Virtual environment ────────────────────────────────────────────────────── -if [ ! -d "$BOT_DIR/venv" ]; then - echo -e "${GREEN}[*] Создание виртуального окружения...${NC}" - python3 -m venv "$BOT_DIR/venv" -fi - -echo -e "${GREEN}[*] Установка зависимостей...${NC}" -"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q - -# ── Конфигурация ───────────────────────────────────────────────────────────── -if [ ! -f "$BOT_DIR/.env" ]; then - echo "" - echo -e "${YELLOW}Введите BOT_TOKEN от @BotFather:${NC}" - TOKEN="" - while [ -z "$TOKEN" ]; do - read -r TOKEN - TOKEN=$(echo "$TOKEN" | tr -d '[:space:]') - [ -z "$TOKEN" ] && echo -e "${RED}Токен не может быть пустым.${NC}" - done - - echo -ne "${YELLOW}ID администратора (Enter = доступ для всех):${NC} " - read -r ADMIN_ID - - { - echo "BOT_TOKEN=$TOKEN" - [ -n "$ADMIN_ID" ] && echo "ALLOWED_IDS=$ADMIN_ID" - } > "$BOT_DIR/.env" - - chmod 600 "$BOT_DIR/.env" - echo -e "${GREEN}[*] .env создан${NC}" -else - echo -e "${GREEN}[*] .env уже существует${NC}" -fi - -# ── Systemd ────────────────────────────────────────────────────────────────── -cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF -[Unit] -Description=GoTelegram v2.2.1 Telegram Bot -After=network.target - -[Service] -Type=simple -WorkingDirectory=$BOT_DIR -ExecStart=$BOT_DIR/venv/bin/python $BOT_DIR/bot.py -Restart=always -RestartSec=5 -Environment=PATH=$BOT_DIR/venv/bin:/usr/bin - -[Install] -WantedBy=multi-user.target -EOF - -systemctl daemon-reload -systemctl enable "$SERVICE_NAME" -systemctl restart "$SERVICE_NAME" 2>/dev/null || systemctl start "$SERVICE_NAME" - -echo "" -echo -e "${GREEN}╔═══════════════════════════════════════════╗${NC}" -echo -e "${GREEN}║ ✅ Бот установлен и запущен! ║${NC}" -echo -e "${GREEN}╚═══════════════════════════════════════════╝${NC}" -echo "" -echo -e "Проверка: ${CYAN}systemctl status $SERVICE_NAME${NC}" -echo -e "Логи: ${CYAN}journalctl -u $SERVICE_NAME -f${NC}" -echo -e "Настройки: ${CYAN}$BOT_DIR/.env${NC}" -echo "" - -# Благодарности -echo -e "${CYAN}─────────────────────────────────────────────${NC}" -echo -e "💜 Спасибо авторам открытых проектов:" -echo -e " ${CYAN}telemt${NC} — MTProxy engine (Rust)" -echo -e " ${CYAN}HTML5 UP${NC} — шаблоны сайтов (CC BY 3.0)" -echo -e " ${CYAN}learning-zone${NC} — 150+ HTML5 шаблонов" -echo -e " ${CYAN}Start Bootstrap${NC} — Bootstrap шаблоны (MIT)" -echo -e "${CYAN}─────────────────────────────────────────────${NC}" diff --git a/lib/backup.sh b/lib/backup.sh old mode 100644 new mode 100755 diff --git a/lib/common.sh b/lib/common.sh old mode 100644 new mode 100755 index edaf7f3..cef9bfe --- a/lib/common.sh +++ b/lib/common.sh @@ -1,9 +1,9 @@ #!/bin/bash -# GoTelegram v2.2 — Общие утилиты +# GoTelegram v2.3 — Общие утилиты # Цвета, логирование, спиннер, системные функции, совместимость с v1 # ── Версия ──────────────────────────────────────────────────────────────────── -GOTELEGRAM_VERSION="2.2.1" +GOTELEGRAM_VERSION="2.3.0" GOTELEGRAM_NAME="GoTelegram" # ── Пути ────────────────────────────────────────────────────────────────────── @@ -270,7 +270,7 @@ save_gotelegram_config() { { "version": "$GOTELEGRAM_VERSION", "engine": "${1:-telemt}", - "mode": "${2:-quick}", + "mode": "${2:-lite}", "port": ${3:-443}, "secret": "${4:-}", "mask_host": "${5:-google.com}", diff --git a/lib/stats.sh b/lib/stats.sh new file mode 100755 index 0000000..799ba73 --- /dev/null +++ b/lib/stats.sh @@ -0,0 +1,424 @@ +#!/bin/bash +# stats.sh — Traffic statistics module for GoTelegram +# Tracks proxy (telemt port 443) and site (nginx port 8443) traffic +# Uses iptables counters + real-time snapshots + historical CSV + +# Color codes (from common.sh) +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +STATS_DIR="/run/gotelegram" +HISTORY_FILE="/opt/gotelegram/stats_history.csv" +SNAPSHOTS_DIR="$STATS_DIR/snapshots" +CURRENT_SNAPSHOT="$STATS_DIR/stats_current.json" +CONFIG_FILE="/opt/gotelegram/config.json" + +# Initialize stats infrastructure +stats_init() { + # Create runtime directory + mkdir -p "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null + chmod 755 "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null + + # Create iptables chain if not exists + if ! iptables -L GOTELEGRAM_STATS -n >/dev/null 2>&1; then + iptables -N GOTELEGRAM_STATS 2>/dev/null + fi + + # Add chain to INPUT if not already present + if ! iptables -C INPUT -j GOTELEGRAM_STATS 2>/dev/null; then + iptables -I INPUT -j GOTELEGRAM_STATS 2>/dev/null + fi + + # Add rule for proxy traffic (port 443, TCP) + if ! iptables -C GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null; then + iptables -A GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null + fi + + # Add rule for site traffic (loopback, port 8443, TCP) + if ! iptables -C GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null; then + iptables -A GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null + fi + + # Initialize CSV header if file doesn't exist + if [[ ! -f "$HISTORY_FILE" ]]; then + echo "epoch,proxy_bytes,site_bytes" > "$HISTORY_FILE" 2>/dev/null + fi + + # Write initial snapshot + stats_collect +} + +# Collect current traffic statistics from iptables +stats_collect() { + local proxy_bytes=0 proxy_pkts=0 site_bytes=0 site_pkts=0 + local ts=$(date +%s) + local temp_file=$(mktemp) + + # Parse iptables output: format is "pkts bytes target" + # We need to extract bytes (2nd column) for each rule + local iptables_output=$(iptables -L GOTELEGRAM_STATS -v -n -x 2>/dev/null) + + # Extract counters for port 443 (proxy) + proxy_bytes=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $2}') + proxy_pkts=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $1}') + + # Extract counters for port 8443 on loopback (site) + site_bytes=$(echo "$iptables_output" | grep "dpt:8443" | awk '{print $2}') + site_pkts=$(echo "$iptables_output" | grep "dpt:8443" | awk '{print $1}') + + # Default to 0 if not found + proxy_bytes=${proxy_bytes:-0} + proxy_pkts=${proxy_pkts:-0} + site_bytes=${site_bytes:-0} + site_pkts=${site_pkts:-0} + + # Write current snapshot as JSON + if command -v jq &>/dev/null; then + echo "{\"ts\":$ts,\"proxy_bytes\":$proxy_bytes,\"proxy_pkts\":$proxy_pkts,\"site_bytes\":$site_bytes,\"site_pkts\":$site_pkts}" > "$CURRENT_SNAPSHOT" 2>/dev/null + else + cat > "$CURRENT_SNAPSHOT" 2>/dev/null </dev/null || date +%Y%m%d%H%M) + local snapshot_file="$SNAPSHOTS_DIR/snap_${minute_key}.json" + cp "$CURRENT_SNAPSHOT" "$snapshot_file" 2>/dev/null + + # Append to history CSV (once per minute, check if last entry is fresh) + if [[ -f "$HISTORY_FILE" ]]; then + local last_ts=$(tail -1 "$HISTORY_FILE" 2>/dev/null | cut -d, -f1) + local current_minute=$((ts - (ts % 60))) + + if [[ -z "$last_ts" ]] || [[ $((current_minute - last_ts)) -ge 60 ]]; then + echo "$current_minute,$proxy_bytes,$site_bytes" >> "$HISTORY_FILE" 2>/dev/null + + # Cleanup old entries (keep only 365 days) + stats_cleanup_history + fi + fi + + rm -f "$temp_file" 2>/dev/null +} + +# Read current snapshot as JSON +stats_read_current() { + if [[ -f "$CURRENT_SNAPSHOT" ]]; then + cat "$CURRENT_SNAPSHOT" + else + echo "{}" + fi +} + +# Extract value from JSON (fallback if jq not available) +json_get() { + local json="$1" + local key="$2" + + if command -v jq &>/dev/null; then + echo "$json" | jq -r ".${key}" 2>/dev/null || echo "0" + else + echo "$json" | grep -o "\"$key\":[^,}]*" | cut -d: -f2 | tr -d ' "' || echo "0" + fi +} + +# Convert bytes to human-readable format +format_bytes() { + local bytes=$1 + + if (( bytes < 1024 )); then + printf "%.0f B" "$bytes" + elif (( bytes < 1024 * 1024 )); then + printf "%.1f KB" "$(echo "scale=1; $bytes / 1024" | bc 2>/dev/null || echo "$((bytes / 1024))")" + elif (( bytes < 1024 * 1024 * 1024 )); then + printf "%.1f MB" "$(echo "scale=1; $bytes / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024))")" + elif (( bytes < 1024 * 1024 * 1024 * 1024 )); then + printf "%.1f GB" "$(echo "scale=1; $bytes / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024 / 1024))")" + else + printf "%.1f TB" "$(echo "scale=1; $bytes / 1024 / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024 / 1024 / 1024))")" + fi +} + +# Convert bytes/sec to human-readable rate +format_rate() { + local bytes_per_sec=$1 + + if (( bytes_per_sec < 1024 )); then + printf "%.0f B/s" "$bytes_per_sec" + elif (( bytes_per_sec < 1024 * 1024 )); then + printf "%.1f KB/s" "$(echo "scale=1; $bytes_per_sec / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024))")" + elif (( bytes_per_sec < 1024 * 1024 * 1024 )); then + printf "%.1f MB/s" "$(echo "scale=1; $bytes_per_sec / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024 / 1024))")" + else + printf "%.1f GB/s" "$(echo "scale=1; $bytes_per_sec / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024 / 1024 / 1024))")" + fi +} + +# Calculate traffic rates and totals from history +stats_calculate_rates() { + local traffic_type="$1" # "proxy" or "site" + local col_idx=2 # proxy_bytes is column 2 + + [[ "$traffic_type" == "site" ]] && col_idx=3 # site_bytes is column 3 + + local now=$(date +%s) + local result="" + + # 1 minute rate + local ts_1m=$((now - 60)) + local bytes_now=$(tail -1 "$HISTORY_FILE" 2>/dev/null | cut -d, -f"$col_idx") + local bytes_1m=$(awk -F, -v ts="$ts_1m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_1m=$((bytes_now - (bytes_1m > 0 ? bytes_1m : bytes_now))) + [[ $diff_1m -lt 0 ]] && diff_1m=0 + local rate_1m=$((diff_1m / 60)) + local bytes_1m_fmt=$(format_bytes "$diff_1m") + local rate_1m_fmt=$(format_rate "$rate_1m") + + # 5 minute rate + local ts_5m=$((now - 300)) + local bytes_5m=$(awk -F, -v ts="$ts_5m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_5m=$((bytes_now - (bytes_5m > 0 ? bytes_5m : bytes_now))) + [[ $diff_5m -lt 0 ]] && diff_5m=0 + local rate_5m=$((diff_5m / 300)) + local bytes_5m_fmt=$(format_bytes "$diff_5m") + local rate_5m_fmt=$(format_rate "$rate_5m") + + # 60 minute rate + local ts_60m=$((now - 3600)) + local bytes_60m=$(awk -F, -v ts="$ts_60m" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_60m=$((bytes_now - (bytes_60m > 0 ? bytes_60m : bytes_now))) + [[ $diff_60m -lt 0 ]] && diff_60m=0 + local rate_60m=$((diff_60m / 3600)) + local bytes_60m_fmt=$(format_bytes "$diff_60m") + local rate_60m_fmt=$(format_rate "$rate_60m") + + # 1 day total + local ts_1d=$((now - 86400)) + local bytes_1d=$(awk -F, -v ts="$ts_1d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_1d=$((bytes_now - (bytes_1d > 0 ? bytes_1d : bytes_now))) + [[ $diff_1d -lt 0 ]] && diff_1d=0 + local rate_1d=$((diff_1d > 0 ? diff_1d / 86400 : 0)) + local bytes_1d_fmt=$(format_bytes "$diff_1d") + local rate_1d_fmt=$(format_rate "$rate_1d") + + # 7 days total + local ts_7d=$((now - 604800)) + local bytes_7d=$(awk -F, -v ts="$ts_7d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_7d=$((bytes_now - (bytes_7d > 0 ? bytes_7d : bytes_now))) + [[ $diff_7d -lt 0 ]] && diff_7d=0 + local rate_7d=$((diff_7d > 0 ? diff_7d / 604800 : 0)) + local bytes_7d_fmt=$(format_bytes "$diff_7d") + local rate_7d_fmt=$(format_rate "$rate_7d") + + # 30 days total + local ts_30d=$((now - 2592000)) + local bytes_30d=$(awk -F, -v ts="$ts_30d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_30d=$((bytes_now - (bytes_30d > 0 ? bytes_30d : bytes_now))) + [[ $diff_30d -lt 0 ]] && diff_30d=0 + local rate_30d=$((diff_30d > 0 ? diff_30d / 2592000 : 0)) + local bytes_30d_fmt=$(format_bytes "$diff_30d") + local rate_30d_fmt=$(format_rate "$rate_30d") + + # 365 days total + local ts_365d=$((now - 31536000)) + local bytes_365d=$(awk -F, -v ts="$ts_365d" '$1 >= ts' "$HISTORY_FILE" 2>/dev/null | head -1 | cut -d, -f"$col_idx") + local diff_365d=$((bytes_now - (bytes_365d > 0 ? bytes_365d : bytes_now))) + [[ $diff_365d -lt 0 ]] && diff_365d=0 + local rate_365d=$((diff_365d > 0 ? diff_365d / 31536000 : 0)) + local bytes_365d_fmt=$(format_bytes "$diff_365d") + local rate_365d_fmt=$(format_rate "$rate_365d") + + # Return as pipe-delimited format for table display + echo "$bytes_1m_fmt|$rate_1m_fmt|$bytes_5m_fmt|$rate_5m_fmt|$bytes_60m_fmt|$rate_60m_fmt|$bytes_1d_fmt|$rate_1d_fmt|$bytes_7d_fmt|$rate_7d_fmt|$bytes_30d_fmt|$rate_30d_fmt|$bytes_365d_fmt|$rate_365d_fmt" +} + +# Main display function for traffic statistics +show_traffic_stats() { + # Ensure stats are collected + stats_collect + + # Get current counters + local current_json=$(stats_read_current) + local proxy_pkts=$(json_get "$current_json" "proxy_pkts") + local site_pkts=$(json_get "$current_json" "site_pkts") + + # Calculate rates for proxy + local proxy_rates=$(stats_calculate_rates "proxy") + IFS='|' read -r p1m p1mr p5m p5mr p60m p60mr p1d p1dr p7d p7dr p30d p30dr p365d p365dr <<< "$proxy_rates" + + # Calculate rates for site + local site_rates=$(stats_calculate_rates "site") + IFS='|' read -r s1m s1mr s5m s5mr s60m s60mr s1d s1dr s7d s7dr s30d s30dr s365d s365dr <<< "$site_rates" + + # Display proxy stats + { + echo "" + echo -e "${BLUE} Proxy (telemt, порт 443):${NC}" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + echo -e "${BLUE} Период │ Входящий │ Скорость${NC}" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + printf " %-9s │ %14s │ %s\n" "1 мин" "$p1m" "$p1mr" + printf " %-9s │ %14s │ %s\n" "5 мин" "$p5m" "$p5mr" + printf " %-9s │ %14s │ %s\n" "60 мин" "$p60m" "$p60mr" + printf " %-9s │ %14s │ %s\n" "1 день" "$p1d" "$p1dr" + printf " %-9s │ %14s │ %s\n" "7 дней" "$p7d" "$p7dr" + printf " %-9s │ %14s │ %s\n" "30 дней" "$p30d" "$p30dr" + printf " %-9s │ %14s │ %s\n" "365 дней" "$p365d" "$p365dr" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + printf " Пакетов: %d\n\n" "$proxy_pkts" + + echo -e "${BLUE} Сайт (nginx, порт 8443):${NC}" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + echo -e "${BLUE} Период │ Входящий │ Скорость${NC}" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + printf " %-9s │ %14s │ %s\n" "1 мин" "$s1m" "$s1mr" + printf " %-9s │ %14s │ %s\n" "5 мин" "$s5m" "$s5mr" + printf " %-9s │ %14s │ %s\n" "60 мин" "$s60m" "$s60mr" + printf " %-9s │ %14s │ %s\n" "1 день" "$s1d" "$s1dr" + printf " %-9s │ %14s │ %s\n" "7 дней" "$s7d" "$s7dr" + printf " %-9s │ %14s │ %s\n" "30 дней" "$s30d" "$s30dr" + printf " %-9s │ %14s │ %s\n" "365 дней" "$s365d" "$s365dr" + echo -e "${BLUE} ─────────────────────────────────────────${NC}" + printf " Пакетов: %d\n" "$site_pkts" + echo "" + } >&2 +} + +# Clean up history older than 365 days +stats_cleanup_history() { + if [[ ! -f "$HISTORY_FILE" ]]; then + return + fi + + local now=$(date +%s) + local ts_365d=$((now - 31536000)) + local temp_file=$(mktemp) + + # Keep header + entries from last 365 days + { + head -1 "$HISTORY_FILE" + awk -F, -v ts="$ts_365d" '$1 >= ts' "$HISTORY_FILE" | tail -n +2 + } > "$temp_file" 2>/dev/null + + mv "$temp_file" "$HISTORY_FILE" 2>/dev/null +} + +# Toggle stats collection on/off +toggle_stats() { + local current_state="false" + + # Read current state from config + if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then + current_state=$(jq -r '.stats_enabled // false' "$CONFIG_FILE" 2>/dev/null) + fi + + # Toggle + if [[ "$current_state" == "true" ]]; then + # Disable stats + if [[ -f "$CONFIG_FILE" ]]; then + if command -v jq &>/dev/null; then + jq '.stats_enabled = false' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" 2>/dev/null + mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE" + fi + fi + + # Remove iptables rules + iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null + iptables -F GOTELEGRAM_STATS 2>/dev/null + iptables -X GOTELEGRAM_STATS 2>/dev/null + + # Clean up directories + rm -rf "$STATS_DIR" 2>/dev/null + + echo "Сбор статистики ОТКЛЮЧЕН" >&2 + else + # Enable stats + if [[ -f "$CONFIG_FILE" ]]; then + if command -v jq &>/dev/null; then + jq '.stats_enabled = true' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" 2>/dev/null + mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE" + fi + fi + + # Initialize stats collection + stats_init + + echo "Сбор статистики ВКЛЮЧЕН" >&2 + fi +} + +# Install systemd service for stats collection +install_stats_collector() { + local service_file="/etc/systemd/system/gotelegram-stats.service" + + # Check if running as root + if [[ $EUID -ne 0 ]]; then + echo "Требуется root для установки сервиса" >&2 + return 1 + fi + + # Get script directory (resolve symlinks) + local script_dir=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") + local lib_dir=$(dirname "$script_dir") + + # Create systemd service file + cat > "$service_file" <<'EOF' +[Unit] +Description=GoTelegram Traffic Stats Collector +After=network.target +Wants=network-online.target + +[Service] +Type=simple +User=root +ExecStart=/bin/bash -c 'source /opt/gotelegram/lib/common.sh; source /opt/gotelegram/lib/stats.sh; stats_init; while true; do stats_collect; sleep 1; done' +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + + chmod 644 "$service_file" + systemctl daemon-reload + systemctl enable gotelegram-stats.service + systemctl start gotelegram-stats.service + + echo "Сервис gotelegram-stats установлен и запущен" >&2 +} + +# Remove stats collector service +remove_stats_collector() { + if [[ $EUID -ne 0 ]]; then + echo "Требуется root для удаления сервиса" >&2 + return 1 + fi + + systemctl stop gotelegram-stats.service 2>/dev/null + systemctl disable gotelegram-stats.service 2>/dev/null + rm -f /etc/systemd/system/gotelegram-stats.service + systemctl daemon-reload + + # Remove iptables rules + iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null + iptables -F GOTELEGRAM_STATS 2>/dev/null + iptables -X GOTELEGRAM_STATS 2>/dev/null + + # Clean up directories and files + rm -rf "$STATS_DIR" 2>/dev/null + rm -f "$HISTORY_FILE" 2>/dev/null + + echo "Сервис статистики удалён" >&2 +} + +# Export functions for external use +export -f stats_init stats_collect stats_read_current stats_calculate_rates +export -f show_traffic_stats format_bytes format_rate toggle_stats +export -f stats_cleanup_history install_stats_collector remove_stats_collector +export -f json_get diff --git a/lib/telemt.sh b/lib/telemt.sh old mode 100644 new mode 100755 diff --git a/lib/telemt_config.sh b/lib/telemt_config.sh old mode 100644 new mode 100755 index 16ba023..3cac21a --- a/lib/telemt_config.sh +++ b/lib/telemt_config.sh @@ -29,17 +29,17 @@ QUICK_DOMAINS=( generate_telemt_toml() { local secret="$1" local port="${2:-443}" - local mask_mode="${3:-quick}" # quick | stealth + local mask_mode="${3:-lite}" # lite | pro local mask_domain="${4:-google.com}" local mask_port="${5:-443}" local output="${6:-$TELEMT_CONFIG}" mkdir -p "$(dirname "$output")" - # DNS override для stealth: домен резолвится в 127.0.0.1 + # DNS override для pro: домен резолвится в 127.0.0.1 # чтобы mask-трафик шёл на локальный nginx, а не в интернет local dns_line="" - if [ "$mask_mode" = "stealth" ]; then + if [ "$mask_mode" = "pro" ]; then dns_line="dns_overrides = [\"${mask_domain}:${mask_port}:127.0.0.1\"]" fi @@ -56,7 +56,7 @@ listen_addr_ipv4 = "0.0.0.0" tls_domain = "${mask_domain}" mask = true mask_port = ${mask_port} -tls_emulation = $([ "$mask_mode" = "stealth" ] && echo "false" || echo "true") +tls_emulation = $([ "$mask_mode" = "pro" ] && echo "false" || echo "true") [access.users] main = "${secret}" @@ -261,7 +261,7 @@ show_proxy_info() { status=$(telemt_status) local mode - mode=$(config_get mode 2>/dev/null || echo "quick") + mode=$(config_get mode 2>/dev/null || echo "lite") local status_icon status_text case "$status" in @@ -290,21 +290,21 @@ show_proxy_info() { fi } -# ── Вывод информации о прокси (Stealth-режим) ────────────────────────────── -# В stealth-режиме ссылка содержит домен (не IP) и fake-TLS секрет (ee...) -show_proxy_info_stealth() { +# ── Вывод информации о прокси (Pro-режим) ────────────────────────────────── +# В pro-режиме ссылка содержит домен (не IP) и fake-TLS секрет (ee...) +show_proxy_info_pro() { local domain="$1" local faketls_secret="$2" local link="tg://proxy?server=${domain}&port=443&secret=${faketls_secret}" echo "" - echo -e " ${BOLD}${WHITE}✅ Stealth-прокси настроен${NC}" + echo -e " ${BOLD}${WHITE}✅ Pro-прокси настроен${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" echo -e " ${WHITE}Ядро:${NC} telemt (Rust)" echo -e " ${WHITE}Домен:${NC} ${CYAN}${domain}${NC}" echo -e " ${WHITE}Порт:${NC} ${CYAN}443${NC} (внешний, telemt)" - echo -e " ${WHITE}Режим:${NC} ${MAGENTA}Stealth (fake-TLS)${NC}" + echo -e " ${WHITE}Режим:${NC} ${MAGENTA}Pro (fake-TLS)${NC}" echo -e " ${WHITE}nginx:${NC} ${CYAN}127.0.0.1:8443${NC} (внутренний)" echo -e " ${WHITE}Secret:${NC} ${CYAN}${faketls_secret:0:20}...${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" diff --git a/lib/templates_catalog.sh b/lib/templates_catalog.sh old mode 100644 new mode 100755 diff --git a/lib/website.sh b/lib/website.sh old mode 100644 new mode 100755 index 1b8f89d..68e5c8d --- a/lib/website.sh +++ b/lib/website.sh @@ -39,8 +39,8 @@ generate_nginx_config() { mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled cat > "$NGINX_SITE_CONF" << 'EONGINX' -# GoTelegram v2.2 — nginx config -# Stealth: nginx на 127.0.0.1:8443 (внутренний), telemt на 0.0.0.0:443 (внешний) +# GoTelegram v2.3 — nginx config +# Pro: nginx на 127.0.0.1:8443 (внутренний), telemt на 0.0.0.0:443 (внешний) # Обычный браузер → :443 → telemt → 127.0.0.1:8443 → nginx (сайт) server { @@ -258,14 +258,14 @@ deploy_template_to_nginx() { log_success "Шаблон развёрнут в $WEBSITE_ROOT" } -# ── Полная установка stealth-режима ────────────────────────────────────────── -setup_stealth_mode() { +# ── Полная установка pro-режима ────────────────────────────────────────────── +setup_pro_mode() { local domain="$1" local template_dir="$2" local proxy_port="${3:-443}" local email="${4:-}" - log_step "Настройка stealth-режима" + log_step "Настройка pro-режима" # 1. Устанавливаем nginx run_with_spinner "Установка nginx" install_nginx || return 1 @@ -298,7 +298,7 @@ setup_stealth_mode() { # 8. Показываем благодарности авторам шаблонов show_credits - log_success "Stealth-режим настроен: https://${domain}" + log_success "Pro-режим настроен: https://${domain}" return 0 } @@ -322,13 +322,13 @@ restart_nginx() { fi } -# ── Удаление stealth-режима ────────────────────────────────────────────────── -remove_stealth_mode() { - log_info "Удаление stealth-режима..." +# ── Удаление pro-режима ────────────────────────────────────────────────────── +remove_pro_mode() { + log_info "Удаление pro-режима..." rm -f "$NGINX_SITE_CONF" "$NGINX_SITE_LINK" rm -rf "$WEBSITE_ROOT" systemctl restart nginx 2>/dev/null - log_success "Stealth-режим удалён (nginx оставлен)" + log_success "Pro-режим удалён (nginx оставлен)" } # ── Смена шаблона ────────────────────────────────────────────────────────────