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 <