v2.3.0: Lite/Pro rebrand, submenu system, traffic stats, bot stats

This commit is contained in:
anten-ka
2026-04-08 21:49:03 +03:00
parent 364501d66d
commit 6ec2123f83
11 changed files with 884 additions and 387 deletions

View File

@@ -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"

View File

@@ -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 "📊 <b>Статистика</b>\n\n<i>Данные недоступны. Убедитесь что модуль статистики включён.</i>"
# 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 = ["📊 <b>Статистика трафика</b>\n"]
for label, key in [("Proxy (telemt)", "proxy"), ("Сайт (nginx)", "site")]:
lines.append(f"\n<b>{label}:</b>")
lines.append("<pre>")
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("</pre>")
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"✅ <b>Quick mode installed!</b>\n\n"
f"✅ <b>Lite mode installed!</b>\n\n"
f"<b>Domain:</b> {domain}\n"
f"<b>Mode:</b> Quick\n\n"
f"<b>Mode:</b> 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 <b>{html.escape(category['name'])}</b>:"
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"✅ <b>Stealth mode installed!</b>\n\n"
f"✅ <b>Pro mode installed!</b>\n\n"
f"<b>Template:</b> {html.escape(tpl_id)}\n"
f"<b>Mode:</b> Stealth\n\n"
f"<b>Mode:</b> 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:

View File

@@ -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 "$@"

View File

@@ -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}"

0
lib/backup.sh Normal file → Executable file
View File

6
lib/common.sh Normal file → Executable file
View File

@@ -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}",

424
lib/stats.sh Executable file
View File

@@ -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 <<EOF
{"ts":$ts,"proxy_bytes":$proxy_bytes,"proxy_pkts":$proxy_pkts,"site_bytes":$site_bytes,"site_pkts":$site_pkts}
EOF
fi
# Save snapshot for rate calculation (one per minute)
local minute_key=$(date +%s -d "1 minute ago" +%Y%m%d%H%M 2>/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

0
lib/telemt.sh Normal file → Executable file
View File

20
lib/telemt_config.sh Normal file → Executable file
View File

@@ -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}"

0
lib/templates_catalog.sh Normal file → Executable file
View File

20
lib/website.sh Normal file → Executable file
View File

@@ -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 оставлен)"
}
# ── Смена шаблона ────────────────────────────────────────────────────────────