diff --git a/install.sh b/install.sh
index 72a3168..0cb60a0 100644
--- a/install.sh
+++ b/install.sh
@@ -1,669 +1,380 @@
#!/bin/bash
-# GoTelegram MTProxy Bot — всё в одном файле.
+# GoTelegram MTProxy — всё в одном файле.
# Установка: curl -sL -H "Authorization: token TOKEN" https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install.sh -o /usr/local/bin/gotelegram && chmod +x /usr/local/bin/gotelegram && gotelegram
-RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
-fail() { echo -e "${RED}[ОШИБКА] $1${NC}"; exit 1; }
-
-[ "$EUID" -ne 0 ] && { echo -e "${RED}Запустите с sudo.${NC}"; exit 1; }
+# ── Цвета ────────────────────────────────────────────────────────────────────
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+CYAN='\033[0;36m'
+YELLOW='\033[1;33m'
+MAGENTA='\033[0;35m'
+BLUE='\033[0;34m'
+WHITE='\033[1;37m'
+NC='\033[0m'
+# ── Конфиг ───────────────────────────────────────────────────────────────────
+CONTAINER_NAME="mtproto-proxy"
BOT_DIR="/opt/gotelegram-bot"
SERVICE_NAME="gotelegram-bot"
+TIP_LINK="https://pay.cloudtips.ru/p/7410814f"
+PROMO_LINK="https://vk.cc/ct29NQ"
-echo -e "${GREEN}╔═══════════════════════════════════════════╗${NC}"
-echo -e "${GREEN}║ GoTelegram MTProxy Bot — установка ║${NC}"
-echo -e "${GREEN}╚═══════════════════════════════════════════╝${NC}"
+# ── Проверка root ────────────────────────────────────────────────────────────
+if [ "$EUID" -ne 0 ]; then
+ echo -e "${RED}Запустите с sudo / root.${NC}"
+ exit 1
+fi
-# ── Зависимости ──────────────────────────────────────────────────────────────
+# ── Установка пакетов ────────────────────────────────────────────────────────
install_pkg() {
if command -v apt-get &>/dev/null; then
apt-get update -qq && apt-get install -y -qq "$@"
elif command -v dnf &>/dev/null; then
- dnf install -y "$@"
+ dnf install -y "$@" 2>/dev/null
elif command -v yum &>/dev/null; then
yum install -y "$@"
fi
}
-for cmd in python3 curl; do
- command -v $cmd &>/dev/null || { echo -e "${YELLOW}[*] Установка $cmd...${NC}"; install_pkg $cmd; }
-done
-command -v python3 &>/dev/null || fail "python3 не установлен."
-
-PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "3")
-echo -e "${GREEN}[*] Python ${PY_VER} — проверка venv...${NC}"
-if ! python3 -m venv --help &>/dev/null 2>&1; then
- echo -e "${YELLOW}[*] Установка python${PY_VER}-venv...${NC}"
- install_pkg "python${PY_VER}-venv" 2>/dev/null
- install_pkg python3-venv 2>/dev/null
- install_pkg python3-pip 2>/dev/null
- python3 -m venv --help &>/dev/null 2>&1 || fail "Не удалось установить python-venv. Выполните вручную: apt install python${PY_VER}-venv"
-fi
-echo -e "${GREEN}[*] python-venv готов.${NC}"
-
-if ! command -v docker &>/dev/null; then
- echo -e "${YELLOW}[*] Docker не найден. Устанавливаю...${NC}"
- curl -fsSL https://get.docker.com | sh
- systemctl enable --now docker
-fi
-if ! docker info &>/dev/null 2>&1; then
- systemctl start docker 2>/dev/null || true
- sleep 2
- docker info &>/dev/null 2>&1 || { echo -e "${RED}Docker не запускается.${NC}"; exit 1; }
-fi
-
-EXISTING=$(docker ps --format "{{.Names}}\t{{.Image}}\t{{.Ports}}" 2>/dev/null)
-if [ -n "$EXISTING" ]; then
- echo -e "${CYAN}[*] Работающие контейнеры (не будут затронуты):${NC}"
- echo "$EXISTING" | while IFS= read -r line; do echo " $line"; done
-fi
-
-# ── Встроенные файлы бота ────────────────────────────────────────────────────
-mkdir -p "$BOT_DIR"
-
-cat > "$BOT_DIR/requirements.txt" << 'REQEOF'
-python-telegram-bot>=21.0
-REQEOF
-
-cat > "$BOT_DIR/bot.py" << 'BOTEOF'
-#!/usr/bin/env python3
-"""
-GoTelegram MTProxy — Telegram-бот для управления MTProxy на сервере.
-Кнопочное меню, проверка портов, совместимость с Amnezia/3x-ui,
-кнопка «Поделиться ключом», TCP+UDP для звонков.
-"""
-
-import asyncio
-import html
-import json
-import os
-import re
-from pathlib import Path
-
-_env_path = Path(__file__).resolve().parent / ".env"
-if not _env_path.exists():
- _env_path = Path("/etc/gotelegram-bot/.env")
-if _env_path.exists():
- with open(_env_path, encoding="utf-8") as f:
- for line in f:
- line = line.strip()
- if line and not line.startswith("#") and "=" in line:
- k, v = line.split("=", 1)
- os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
-
-from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
-from telegram.ext import (
- Application,
- CommandHandler,
- CallbackQueryHandler,
- ContextTypes,
- MessageHandler,
- filters,
-)
-
-BOT_TOKEN = os.environ.get("BOT_TOKEN")
-_allowed = os.environ.get("ALLOWED_IDS", "").strip()
-try:
- ALLOWED_IDS = set(int(x) for x in _allowed.split(",") if x.strip()) if _allowed else None
-except ValueError:
- ALLOWED_IDS = None
-
-CONTAINER_NAME = "mtproto-proxy"
-CONFIG_FILE = Path("/opt/gotelegram-bot/proxy.json")
-DOMAINS = [
- "google.com", "wikipedia.org", "habr.com", "github.com",
- "coursera.org", "udemy.com", "medium.com", "stackoverflow.com",
- "bbc.com", "cnn.com", "reuters.com", "nytimes.com",
- "lenta.ru", "rbc.ru", "ria.ru", "kommersant.ru",
- "stepik.org", "duolingo.com", "khanacademy.org", "ted.com",
-]
-PROMO_LINK = "https://vk.cc/ct29NQ"
-TIP_LINK = "https://pay.cloudtips.ru/p/7410814f"
-
-
-def _ok(uid: int) -> bool:
- return ALLOWED_IDS is None or uid in ALLOWED_IDS
-
-
-def _decode(data: bytes) -> str:
- return (data or b"").decode("utf-8", errors="replace").strip()
-
-
-async def sh(*args: str, timeout: int = 60) -> tuple[int, str, str]:
- proc = await asyncio.create_subprocess_exec(
- *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
- )
- try:
- out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
- except asyncio.TimeoutError:
- proc.kill()
- await proc.wait()
- return -1, "", "Timeout"
- return proc.returncode or 0, _decode(out), _decode(err)
-
-
-async def get_ip() -> str:
- for url in ("https://api.ipify.org", "https://icanhazip.com", "https://ifconfig.me"):
- code, out, _ = await sh("curl", "-s", "-4", "--max-time", "5", url, timeout=8)
- if code == 0 and out:
- m = re.search(r"(\d{1,3}\.){3}\d{1,3}", out)
- if m:
- return m.group(0)
- return "0.0.0.0"
-
-
-async def proxy_running() -> bool:
- code, out, _ = await sh("docker", "ps", "--format", "{{.Names}}", timeout=10)
- return code == 0 and CONTAINER_NAME in out
-
-
-async def docker_val(fmt: str) -> str:
- code, out, _ = await sh("docker", "inspect", CONTAINER_NAME, "--format", fmt, timeout=10)
- return out.strip() if code == 0 else ""
-
-
-async def check_port(port: int) -> str | None:
- if await proxy_running():
- hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}")
- if str(port) in hp.split():
- return None
- code, out, _ = await sh("ss", "-tlnp", timeout=5)
- if code != 0:
- code, out, _ = await sh("netstat", "-tlnp", timeout=5)
- for line in out.splitlines():
- if f":{port} " in line or f":{port}\t" in line:
- return line
- return None
-
-
-async def docker_containers_info() -> str:
- code, out, _ = await sh("docker", "ps", "--format", "{{.Names}}\t{{.Image}}\t{{.Ports}}", timeout=10)
- return out if code == 0 else ""
-
-
-def save_config(data: dict) -> None:
- CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
- CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
-
-
-def load_config() -> dict:
- if CONFIG_FILE.exists():
- try:
- return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
- except Exception:
- pass
- return {}
-
-
-async def proxy_info() -> dict | None:
- if not await proxy_running():
- return None
- cmd_str = await docker_val("{{range .Config.Cmd}}{{.}} {{end}}")
- secret = cmd_str.split()[-1] if cmd_str else ""
- hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}}{{end}}")
- port = hp or "443"
- ip = await get_ip()
- link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
- cfg = load_config()
- return {"ip": ip, "port": port, "secret": secret, "link": link, "domain": cfg.get("domain", "—")}
-
-
-def main_menu_kb() -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup([
- [InlineKeyboardButton("🔧 Установить / Обновить", callback_data="menu_install")],
- [InlineKeyboardButton("📊 Статус", callback_data="menu_status"),
- InlineKeyboardButton("🔗 Ссылка", callback_data="menu_link")],
- [InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
- [InlineKeyboardButton("🔄 Перезапуск", callback_data="menu_restart"),
- InlineKeyboardButton("📋 Логи", callback_data="menu_logs")],
- [InlineKeyboardButton("🗑 Удалить", callback_data="menu_remove"),
- InlineKeyboardButton("🏷 Промо", callback_data="menu_promo")],
- ])
-
-
-HELP_TEXT = (
- "🚀 GoTelegram MTProxy Bot\n\n"
- "Управление MTProxy (Fake TLS) на сервере.\n"
- "TCP + UDP (звонки) поддержаны.\n\n"
- "Используйте кнопки ниже или команды:\n"
- "/install /status /link /share /restart /logs /remove /promo"
-)
-
-
-async def start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- if not update.effective_user:
- return
- if not _ok(update.effective_user.id):
- msg = update.message or (update.callback_query and update.callback_query.message)
- if msg:
- await msg.reply_text("⛔ Доступ запрещён.")
- return
- if update.message:
- await update.message.reply_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
- elif update.callback_query:
- await update.callback_query.edit_message_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
-
-
-async def cmd_status(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- await msg.reply_text("⛔")
- return
- info = await proxy_info()
- if not info:
- text = "❌ Прокси не запущен.\nНажмите Установить для настройки."
- else:
- containers = await docker_containers_info()
- other = "\n".join(l for l in containers.splitlines() if CONTAINER_NAME not in l)
- text = (
- "✅ Прокси работает\n\n"
- f"IP: {html.escape(info['ip'])}\n"
- f"Порт: {html.escape(info['port'])}\n"
- f"Домен: {html.escape(info['domain'])}\n"
- f"Secret: {html.escape(info['secret'])}\n\n"
- f"Ссылка:\n{html.escape(info['link'])}"
- )
- if other:
- text += f"\n\n📦 Другие контейнеры:\n
{html.escape(other)}"
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
- else:
- await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
-
-
-async def cmd_link(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- info = await proxy_info()
- text = f"{html.escape(info['link'])}" if info else "❌ Прокси не запущен."
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
- else:
- await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
-
-
-async def cmd_share(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- info = await proxy_info()
- if not info:
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
- else:
- await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
- return
- tg_link = info["link"]
- share_text = (
- f"🔐 MTProxy для Telegram\n\n"
- f"🌍 Сервер: {html.escape(info['ip'])}\n"
- f"🔌 Порт: {html.escape(info['port'])}\n"
- f"🔑 Secret: {html.escape(info['secret'])}\n\n"
- f"👉 Подключиться одним нажатием:\n"
- f"{html.escape(tg_link)}\n\n"
- f"Просто нажмите на ссылку или перешлите это сообщение."
- )
- kb = InlineKeyboardMarkup([
- [InlineKeyboardButton("📤 Переслать другу", switch_inline_query=tg_link)],
- [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
- ])
- if update.callback_query:
- await update.callback_query.edit_message_text(share_text, parse_mode="HTML", reply_markup=kb)
- else:
- await msg.reply_text(share_text, parse_mode="HTML", reply_markup=kb)
-
-
-async def cmd_remove(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- chat = msg.chat
- if update.callback_query:
- await update.callback_query.edit_message_text("⏳ Удаляю прокси...")
- else:
- await chat.send_message("⏳ Удаляю прокси...")
- await sh("docker", "stop", CONTAINER_NAME, timeout=15)
- await sh("docker", "rm", CONTAINER_NAME, timeout=10)
- text = "✅ Прокси удалён." if not await proxy_running() else "⚠️ Не удалось удалить."
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- await chat.send_message(text, reply_markup=kb)
-
-
-async def cmd_restart(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- if not await proxy_running():
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
- else:
- await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
- return
- chat = msg.chat
- if update.callback_query:
- await update.callback_query.edit_message_text("⏳ Перезапуск...")
- code, _, err = await sh("docker", "restart", CONTAINER_NAME, timeout=30)
- text = "✅ Перезапущен." if code == 0 else f"❌ Ошибка: {err or 'unknown'}"
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- await chat.send_message(text, reply_markup=kb)
-
-
-async def cmd_logs(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- if not await proxy_running():
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
- else:
- await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
- return
- code, out, err = await sh("docker", "logs", "--tail", "40", CONTAINER_NAME, timeout=15)
- text = (out or "") + (("\n" + err) if err else "") or "Нет вывода."
- if len(text) > 4000:
- text = text[-4000:]
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
- else:
- await msg.reply_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
-
-
-async def cmd_promo(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- text = (
- "💰 Хостинг со скидкой до -60%\n"
- f"Ссылка: {PROMO_LINK}\n\n"
- "Промокоды: OFF60, antenka20, antenka6, antenka12\n\n"
- f"Донат: {TIP_LINK}"
- )
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
- if update.callback_query:
- await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
- else:
- await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
-
-
-async def install_step_domain(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- msg = update.message or (update.callback_query and update.callback_query.message)
- if not update.effective_user or not msg:
- return
- if not _ok(update.effective_user.id):
- return
- buttons = []
- row = []
- for i, d in enumerate(DOMAINS):
- row.append(InlineKeyboardButton(d, callback_data=f"dom_{i}"))
- if len(row) == 2:
- buttons.append(row)
- row = []
- if row:
- buttons.append(row)
- text = "🌐 Выберите домен для маскировки (Fake TLS):"
- if update.callback_query:
- await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
- else:
- await msg.reply_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
-
-
-async def install_step_port(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- query = update.callback_query
- domain = ctx.user_data.get("install_domain", "google.com")
- busy_443 = await check_port(443)
- busy_8443 = await check_port(8443)
- rows = []
- label_443 = "443 (рекомендуется)" if not busy_443 else "443 ⚠️ занят"
- label_8443 = "8443" if not busy_8443 else "8443 ⚠️ занят"
- rows.append([
- InlineKeyboardButton(label_443, callback_data="port_443"),
- InlineKeyboardButton(label_8443, callback_data="port_8443"),
- ])
- rows.append([InlineKeyboardButton("◀️ Меню", callback_data="menu_main")])
- port_info = ""
- if busy_443:
- port_info += f"\n⚠️ Порт 443 занят:\n{html.escape(busy_443[:300])}\n"
- if busy_8443:
- port_info += f"\n⚠️ Порт 8443 занят:\n{html.escape(busy_8443[:300])}\n"
- text = (
- f"Домен: {html.escape(domain)}\n\n"
- f"🔌 Выберите порт или введите свой (1-65535):{port_info}"
- )
- ctx.user_data["install_wait_port"] = True
- await query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(rows))
-
-
-async def install_port_chosen(update: Update, ctx: ContextTypes.DEFAULT_TYPE, port_str: str) -> None:
- port = int(port_str)
- msg = update.callback_query.message if update.callback_query else update.message
- if not msg:
- return
- chat = msg.chat
- busy = await check_port(port)
- if busy:
- kb = InlineKeyboardMarkup([
- [InlineKeyboardButton(f"Всё равно использовать {port}", callback_data=f"force_{port}")],
- [InlineKeyboardButton("Выбрать другой порт", callback_data="reselect_port")],
- [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
- ])
- text = (
- f"⚠️ Порт {port} занят!\n\n"
- f"{html.escape(busy[:500])}\n\n"
- "Можно использовать всё равно (если это ваш процесс) или выбрать другой."
- )
- if update.callback_query:
- await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
- else:
- await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
- ctx.user_data["install_port"] = port_str
- return
- ctx.user_data["install_port"] = port_str
- ctx.user_data["install_wait_port"] = False
- await do_install(update, ctx)
-
-
-async def do_install(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- domain = ctx.user_data.get("install_domain") or "google.com"
- port = ctx.user_data.get("install_port") or "443"
- if update.callback_query:
- msg = update.callback_query.message
- await msg.edit_text("⏳ Генерация secret и запуск контейнера...", reply_markup=None)
- elif update.message:
- msg = update.message
- await msg.reply_text("⏳ Генерация secret и запуск контейнера...")
- else:
- return
- chat = msg.chat
- code, _, _ = await sh("docker", "info", timeout=10)
- if code != 0:
- await chat.send_message(
- "❌ Docker не установлен или не запущен.\n"
- "Установите: curl -fsSL https://get.docker.com | sh",
- parse_mode="HTML",
- reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]]),
- )
- return
- code, secret_out, err = await sh(
- "docker", "run", "--rm", "nineseconds/mtg:2", "generate-secret", "--hex", domain, timeout=60,
- )
- if code != 0:
- await chat.send_message(f"❌ Генерация secret: {err or secret_out}")
- return
- secret = secret_out.strip().split()[-1] if secret_out.strip() else ""
- if not secret:
- await chat.send_message("❌ Пустой secret.")
- return
- await sh("docker", "stop", CONTAINER_NAME, timeout=15)
- await sh("docker", "rm", CONTAINER_NAME, timeout=10)
- code, _, err = await sh(
- "docker", "run", "-d",
- "--name", CONTAINER_NAME, "--restart", "always",
- "-p", f"{port}:{port}/tcp", "-p", f"{port}:{port}/udp",
- "nineseconds/mtg:2", "simple-run",
- "-n", "1.1.1.1", "-t", "1.0.0.1", "-i", "prefer-ipv4",
- f"0.0.0.0:{port}", secret, timeout=90,
- )
- if code != 0:
- await chat.send_message(f"❌ Запуск контейнера: {err}")
- return
- save_config({"domain": domain, "port": port, "secret": secret})
- ip = await get_ip()
- link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
- text = (
- "✅ Прокси установлен!\n\n"
- f"🌍 IP: {html.escape(ip)}\n"
- f"🔌 Порт: {html.escape(port)} (TCP + UDP)\n"
- f"🎭 Домен: {html.escape(domain)}\n"
- f"🔑 Secret: {html.escape(secret)}\n\n"
- f"👉 Ссылка:\n{html.escape(link)}\n\n"
- "📞 Звонки в Telegram поддержаны (UDP)."
- )
- kb = InlineKeyboardMarkup([
- [InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
- [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
- ])
- await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
- for k in ("install_domain", "install_port", "install_wait_port"):
- ctx.user_data.pop(k, None)
-
-
-async def callback_handler(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- query = update.callback_query
- if not query or not update.effective_user:
- return
- await query.answer()
- if not _ok(update.effective_user.id):
- await query.edit_message_text("⛔ Доступ запрещён.")
- return
- data = query.data or ""
- if data == "menu_main":
- await start(update, ctx)
- elif data == "menu_install":
- await install_step_domain(update, ctx)
- elif data == "menu_status":
- await cmd_status(update, ctx)
- elif data == "menu_link":
- await cmd_link(update, ctx)
- elif data == "menu_share":
- await cmd_share(update, ctx)
- elif data == "menu_restart":
- await cmd_restart(update, ctx)
- elif data == "menu_logs":
- await cmd_logs(update, ctx)
- elif data == "menu_remove":
- await cmd_remove(update, ctx)
- elif data == "menu_promo":
- await cmd_promo(update, ctx)
- elif data.startswith("dom_"):
- try:
- idx = int(data[4:])
- except ValueError:
- await query.edit_message_text("❌ Ошибка. /install")
- return
- if not (0 <= idx < len(DOMAINS)):
- await query.edit_message_text("❌ Неверный выбор. /install")
- return
- ctx.user_data["install_domain"] = DOMAINS[idx]
- await install_step_port(update, ctx)
- elif data == "port_443":
- await install_port_chosen(update, ctx, "443")
- elif data == "port_8443":
- await install_port_chosen(update, ctx, "8443")
- elif data.startswith("force_"):
- ctx.user_data["install_port"] = data[6:]
- ctx.user_data["install_wait_port"] = False
- await do_install(update, ctx)
- elif data == "reselect_port":
- await install_step_port(update, ctx)
-
-
-async def text_handler(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None:
- if not update.message or not ctx.user_data.get("install_wait_port"):
- return
- text = (update.message.text or "").strip()
- if not re.match(r"^\d+$", text):
- return
- port = int(text)
- if not (1 <= port <= 65535):
- await update.message.reply_text("Введите число от 1 до 65535.")
- return
- await install_port_chosen(update, ctx, str(port))
-
-
-def main() -> None:
- if not BOT_TOKEN:
- raise SystemExit("Задайте BOT_TOKEN в .env")
- app = Application.builder().token(BOT_TOKEN).build()
- app.add_handler(CommandHandler("start", start))
- app.add_handler(CommandHandler("help", start))
- app.add_handler(CommandHandler("install", install_step_domain))
- app.add_handler(CommandHandler("status", cmd_status))
- app.add_handler(CommandHandler("link", cmd_link))
- app.add_handler(CommandHandler("share", cmd_share))
- app.add_handler(CommandHandler("remove", cmd_remove))
- app.add_handler(CommandHandler("restart", cmd_restart))
- app.add_handler(CommandHandler("logs", cmd_logs))
- app.add_handler(CommandHandler("promo", cmd_promo))
- app.add_handler(CallbackQueryHandler(callback_handler))
- app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, text_handler))
- app.run_polling(allowed_updates=Update.ALL_TYPES)
-
-
-if __name__ == "__main__":
- main()
-BOTEOF
-
-echo -e "${GREEN}[*] Файлы бота записаны.${NC}"
-
-# ── Python venv ──────────────────────────────────────────────────────────────
-echo -e "${GREEN}[*] Настройка Python venv...${NC}"
-if [ ! -d "$BOT_DIR/venv" ]; then
- python3 -m venv "$BOT_DIR/venv" || fail "Не удалось создать venv. Установите: apt install python3-venv"
-fi
-echo -e "${GREEN}[*] Установка зависимостей (pip)...${NC}"
-"$BOT_DIR/venv/bin/pip" install --upgrade pip -q 2>/dev/null || true
-"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q || fail "pip install не удался."
-
-# ── Конфиг (.env) ────────────────────────────────────────────────────────────
-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}"
+install_base_deps() {
+ for cmd in curl docker; do
+ if ! command -v $cmd &>/dev/null; then
+ echo -e "${YELLOW}[*] Установка $cmd...${NC}"
+ if [ "$cmd" = "docker" ]; then
+ curl -fsSL https://get.docker.com | sh
+ systemctl enable --now docker
+ else
+ install_pkg $cmd
+ fi
+ fi
done
- echo "BOT_TOKEN=$TOKEN" > "$BOT_DIR/.env"
- chmod 600 "$BOT_DIR/.env"
- echo -e "${GREEN}[*] .env создан.${NC}"
-else
- echo -e "${GREEN}[*] .env уже есть — пропускаю.${NC}"
-fi
+ if ! command -v qrencode &>/dev/null; then
+ install_pkg qrencode 2>/dev/null || true
+ fi
+ if ! docker info &>/dev/null 2>&1; then
+ systemctl start docker 2>/dev/null || true
+ sleep 2
+ fi
+}
-# ── systemd ──────────────────────────────────────────────────────────────────
-cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
+# ── Утилиты ──────────────────────────────────────────────────────────────────
+get_ip() {
+ local ip
+ ip=$(curl -s -4 --max-time 5 https://api.ipify.org 2>/dev/null \
+ || curl -s -4 --max-time 5 https://icanhazip.com 2>/dev/null \
+ || echo "0.0.0.0")
+ echo "$ip" | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1
+}
+
+check_port() {
+ local port="$1"
+ # Если порт занят нашим контейнером — ОК
+ if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then
+ local hp
+ hp=$(docker inspect "$CONTAINER_NAME" --format='{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}' 2>/dev/null)
+ for p in $hp; do [ "$p" = "$port" ] && return 1; done
+ fi
+ # Проверяем через ss или netstat
+ local line
+ line=$(ss -tlnp 2>/dev/null | grep -E ":${port}\b" | head -1)
+ [ -z "$line" ] && line=$(netstat -tlnp 2>/dev/null | grep -E ":${port}\b" | head -1)
+ if [ -n "$line" ]; then
+ echo "$line"
+ return 0
+ fi
+ return 1
+}
+
+show_containers() {
+ local list
+ list=$(docker ps --format "{{.Names}}\t{{.Image}}\t{{.Ports}}" 2>/dev/null | grep -v "^${CONTAINER_NAME}")
+ if [ -n "$list" ]; then
+ echo -e "${CYAN} Другие контейнеры на сервере:${NC}"
+ echo "$list" | while IFS= read -r l; do echo " $l"; done
+ fi
+}
+
+proxy_is_running() {
+ docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$"
+}
+
+# ── Показать данные подключения ──────────────────────────────────────────────
+show_config() {
+ if ! proxy_is_running; then
+ echo -e "${RED}Прокси не запущен! Выберите пункт 1 для установки.${NC}"
+ return
+ fi
+ local SECRET IP PORT LINK
+ SECRET=$(docker inspect "$CONTAINER_NAME" --format='{{range .Config.Cmd}}{{.}} {{end}}' 2>/dev/null | awk '{print $NF}')
+ IP=$(get_ip)
+ PORT=$(docker inspect "$CONTAINER_NAME" --format='{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}}{{end}}' 2>/dev/null)
+ PORT=${PORT:-443}
+ LINK="tg://proxy?server=$IP&port=$PORT&secret=$SECRET"
+
+ echo ""
+ echo -e "${GREEN}=== ДАННЫЕ ПОДКЛЮЧЕНИЯ ===${NC}"
+ echo -e " IP: ${WHITE}$IP${NC}"
+ echo -e " Port: ${WHITE}$PORT${NC} (TCP + UDP)"
+ echo -e " Secret: ${WHITE}$SECRET${NC}"
+ echo -e " Ссылка: ${BLUE}$LINK${NC}"
+ echo ""
+ if command -v qrencode &>/dev/null; then
+ qrencode -t ANSIUTF8 "$LINK"
+ fi
+ show_containers
+}
+
+# ── ПРОМО ────────────────────────────────────────────────────────────────────
+show_promo() {
+ clear
+ echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${MAGENTA}║ ХОСТИНГ СО СКИДКОЙ ДО -60% ОТ ANTEN-KA ║${NC}"
+ echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
+ echo -e "${CYAN} >>> Ссылка: $PROMO_LINK ${NC}"
+ echo -e "\n${MAGENTA}❖ ••••••••••••••••••• АКТУАЛЬНЫЕ ПРОМОКОДЫ •••••••••••••••••• ❖${NC}"
+ printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "OFF60" "Скидка 60% на ПЕРВЫЙ МЕСЯЦ"
+ printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka20" "Буст 20% + 3% (оплата за 3 МЕС)"
+ printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka6" "Буст 15% + 5% (оплата за 6 МЕС)"
+ printf " ${YELLOW}%-12s${NC} : ${WHITE}%s${NC}\n" "antenka12" "Буст 5% + 5% (оплата за 12 МЕС)"
+ echo -e "${MAGENTA}❖ •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• ❖${NC}"
+ if command -v qrencode &>/dev/null; then
+ qrencode -t ANSIUTF8 "$PROMO_LINK"
+ echo -e "${GREEN}Сканируйте QR для перехода на хостинг${NC}"
+ fi
+ echo "--------------------------------------------------------------"
+ read -p "Нажмите [ENTER] для возврата в меню..."
+}
+
+# ── 1) Установить / Обновить MTProxy ─────────────────────────────────────────
+menu_install() {
+ clear
+ echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${CYAN}║ Выберите домен для маскировки (Fake TLS) ║${NC}"
+ echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
+
+ local domains=(
+ "google.com" "wikipedia.org" "habr.com" "github.com"
+ "coursera.org" "udemy.com" "medium.com" "stackoverflow.com"
+ "bbc.com" "cnn.com" "reuters.com" "nytimes.com"
+ "lenta.ru" "rbc.ru" "ria.ru" "kommersant.ru"
+ "stepik.org" "duolingo.com" "khanacademy.org" "ted.com"
+ )
+
+ for i in "${!domains[@]}"; do
+ printf " ${YELLOW}%2d)${NC} %-22s" "$((i+1))" "${domains[$i]}"
+ [[ $(( (i+1) % 2 )) -eq 0 ]] && echo ""
+ done
+ echo ""
+
+ local d_idx DOMAIN
+ read -p "Ваш выбор [1-${#domains[@]}]: " d_idx
+ DOMAIN=${domains[$((d_idx-1))]}
+ DOMAIN=${DOMAIN:-google.com}
+ echo -e " Домен: ${GREEN}$DOMAIN${NC}"
+
+ # ── Выбор порта с проверкой занятости ────────────────────────────────────
+ echo ""
+ echo -e "${CYAN}--- Выберите порт ---${NC}"
+
+ local busy_line
+ echo -n " 1) 443 (Рекомендуется) "
+ if busy_line=$(check_port 443); then
+ echo -e "${RED}[ЗАНЯТ: $busy_line]${NC}"
+ else
+ echo -e "${GREEN}[свободен]${NC}"
+ fi
+
+ echo -n " 2) 8443 "
+ if busy_line=$(check_port 8443); then
+ echo -e "${RED}[ЗАНЯТ: $busy_line]${NC}"
+ else
+ echo -e "${GREEN}[свободен]${NC}"
+ fi
+
+ echo -e " 3) Свой порт"
+
+ local p_choice PORT
+ read -p " Выбор: " p_choice
+ case $p_choice in
+ 2) PORT=8443 ;;
+ 3)
+ while true; do
+ read -p " Введите порт (1-65535): " PORT
+ [[ "$PORT" =~ ^[0-9]+$ ]] && (( PORT >= 1 && PORT <= 65535 )) && break
+ echo -e " ${RED}Неверный порт.${NC}"
+ done
+ ;;
+ *) PORT=443 ;;
+ esac
+
+ # Финальная проверка выбранного порта
+ if busy_line=$(check_port "$PORT"); then
+ echo ""
+ echo -e " ${YELLOW}Порт $PORT занят:${NC}"
+ echo -e " ${RED}$busy_line${NC}"
+ echo -e " 1) Всё равно использовать (если это ваш процесс)"
+ echo -e " 2) Отмена"
+ local force_choice
+ read -p " Выбор: " force_choice
+ if [ "$force_choice" != "1" ]; then
+ echo -e " ${YELLOW}Отменено.${NC}"
+ read -p " Нажмите Enter..."
+ return
+ fi
+ fi
+
+ echo ""
+ echo -e "${YELLOW}[*] Настройка прокси (домен: $DOMAIN, порт: $PORT)...${NC}"
+
+ # Docker проверка
+ if ! docker info &>/dev/null 2>&1; then
+ echo -e "${RED}Docker не запущен!${NC}"
+ read -p "Нажмите Enter..."
+ return
+ fi
+
+ # Генерация secret
+ echo -e "${GREEN}[*] Генерация secret...${NC}"
+ local SECRET
+ SECRET=$(docker run --rm nineseconds/mtg:2 generate-secret --hex "$DOMAIN" 2>/dev/null)
+ if [ -z "$SECRET" ]; then
+ echo -e "${RED}Ошибка генерации secret.${NC}"
+ read -p "Нажмите Enter..."
+ return
+ fi
+
+ # Остановка старого
+ docker stop "$CONTAINER_NAME" &>/dev/null
+ docker rm "$CONTAINER_NAME" &>/dev/null
+
+ # Запуск с TCP + UDP (UDP для звонков)
+ echo -e "${GREEN}[*] Запуск контейнера (TCP + UDP)...${NC}"
+ docker run -d --name "$CONTAINER_NAME" --restart always \
+ -p "$PORT":"$PORT"/tcp \
+ -p "$PORT":"$PORT"/udp \
+ nineseconds/mtg:2 simple-run \
+ -n 1.1.1.1 -t 1.0.0.1 -i prefer-ipv4 \
+ 0.0.0.0:"$PORT" "$SECRET" > /dev/null 2>&1
+
+ if ! proxy_is_running; then
+ echo -e "${RED}Контейнер не запустился. Проверьте: docker logs $CONTAINER_NAME${NC}"
+ read -p "Нажмите Enter..."
+ return
+ fi
+
+ # Сохраняем конфиг
+ mkdir -p "$BOT_DIR"
+ cat > "$BOT_DIR/proxy.json" << CFGEOF
+{"domain": "$DOMAIN", "port": "$PORT", "secret": "$SECRET"}
+CFGEOF
+
+ clear
+ echo -e "${GREEN}Прокси установлен! (TCP + UDP, звонки поддержаны)${NC}"
+ show_config
+ read -p "Нажмите Enter для возврата в меню..."
+}
+
+# ── 3) Настроить Telegram-бот ─────────────────────────────────────────────────
+menu_setup_bot() {
+ clear
+ echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${CYAN}║ Настройка Telegram-бота ║${NC}"
+ echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
+
+ # Проверка статуса
+ if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
+ echo -e " Статус бота: ${GREEN}работает${NC}"
+ echo ""
+ echo -e " 1) Обновить файлы бота и перезапустить"
+ echo -e " 2) Изменить BOT_TOKEN"
+ echo -e " 3) Остановить бота"
+ echo -e " 0) Назад"
+ local sub
+ read -p " Выбор: " sub
+ case $sub in
+ 1)
+ write_bot_files
+ install_bot_deps
+ systemctl restart "$SERVICE_NAME"
+ echo -e "${GREEN}[*] Бот обновлён и перезапущен.${NC}"
+ ;;
+ 2)
+ echo -e "${YELLOW}Введите новый BOT_TOKEN:${NC}"
+ local tok
+ read -r tok
+ tok=$(echo "$tok" | tr -d '[:space:]')
+ if [ -n "$tok" ]; then
+ echo "BOT_TOKEN=$tok" > "$BOT_DIR/.env"
+ chmod 600 "$BOT_DIR/.env"
+ systemctl restart "$SERVICE_NAME"
+ echo -e "${GREEN}[*] Токен обновлён, бот перезапущен.${NC}"
+ else
+ echo -e "${RED}Пустой токен, отмена.${NC}"
+ fi
+ ;;
+ 3)
+ systemctl stop "$SERVICE_NAME"
+ echo -e "${YELLOW}Бот остановлен.${NC}"
+ ;;
+ *) return ;;
+ esac
+ read -p "Нажмите Enter..."
+ return
+ fi
+
+ echo -e " Статус бота: ${RED}не установлен / не запущен${NC}"
+ echo ""
+ echo -e " Бот позволяет управлять MTProxy из Telegram:"
+ echo -e " установка, статус, ссылка, поделиться ключом и т.д."
+ echo ""
+ read -p " Установить бота? (y/n): " yn
+ [ "$yn" != "y" ] && [ "$yn" != "Y" ] && return
+
+ # Зависимости Python
+ echo -e "${GREEN}[*] Проверка Python...${NC}"
+ if ! command -v python3 &>/dev/null; then
+ install_pkg python3 python3-pip
+ fi
+ command -v python3 &>/dev/null || { echo -e "${RED}python3 не найден!${NC}"; read -p "Enter..."; return; }
+
+ local PY_VER
+ PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "3")
+ if ! python3 -m venv --help &>/dev/null 2>&1; then
+ echo -e "${YELLOW}[*] Установка python${PY_VER}-venv...${NC}"
+ install_pkg "python${PY_VER}-venv" 2>/dev/null
+ install_pkg python3-venv 2>/dev/null
+ install_pkg python3-pip 2>/dev/null
+ python3 -m venv --help &>/dev/null 2>&1 || {
+ echo -e "${RED}Не удалось установить venv. Выполните: apt install python${PY_VER}-venv${NC}"
+ read -p "Enter..."; return
+ }
+ fi
+
+ # Файлы бота
+ write_bot_files
+
+ # venv + pip
+ install_bot_deps
+
+ # BOT_TOKEN
+ if [ ! -f "$BOT_DIR/.env" ]; then
+ echo ""
+ echo -e "${YELLOW}Введите BOT_TOKEN от @BotFather:${NC}"
+ local TOKEN=""
+ while [ -z "$TOKEN" ]; do
+ read -r TOKEN
+ TOKEN=$(echo "$TOKEN" | tr -d '[:space:]')
+ [ -z "$TOKEN" ] && echo -e "${RED}Токен не может быть пустым.${NC}"
+ done
+ echo "BOT_TOKEN=$TOKEN" > "$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 MTProxy Bot
After=network.target docker.service
@@ -680,15 +391,531 @@ Environment=PATH=$BOT_DIR/venv/bin:/usr/bin:/usr/local/bin
WantedBy=multi-user.target
EOF
-systemctl daemon-reload
-systemctl enable "$SERVICE_NAME" 2>/dev/null
-systemctl restart "$SERVICE_NAME" 2>/dev/null || systemctl start "$SERVICE_NAME"
+ systemctl daemon-reload
+ systemctl enable "$SERVICE_NAME" 2>/dev/null
+ 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 -e "Бот: systemctl status $SERVICE_NAME"
-echo -e "Логи: journalctl -u $SERVICE_NAME -f"
-echo -e "Конфиг: $BOT_DIR/.env"
-exit 0
+ echo ""
+ echo -e "${GREEN}[*] Telegram-бот установлен и запущен!${NC}"
+ echo -e " Проверка: systemctl status $SERVICE_NAME"
+ echo -e " Логи: journalctl -u $SERVICE_NAME -f"
+ read -p " Нажмите Enter..."
+}
+
+write_bot_files() {
+ mkdir -p "$BOT_DIR"
+
+ cat > "$BOT_DIR/requirements.txt" << 'REQEOF'
+python-telegram-bot>=21.0
+REQEOF
+
+ cat > "$BOT_DIR/bot.py" << 'BOTEOF'
+#!/usr/bin/env python3
+import asyncio, html, json, os, re
+from pathlib import Path
+_env_path = Path(__file__).resolve().parent / ".env"
+if _env_path.exists():
+ with open(_env_path, encoding="utf-8") as f:
+ for line in f:
+ line = line.strip()
+ if line and not line.startswith("#") and "=" in line:
+ k, v = line.split("=", 1)
+ os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
+from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
+from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes, MessageHandler, filters
+
+BOT_TOKEN = os.environ.get("BOT_TOKEN")
+_allowed = os.environ.get("ALLOWED_IDS", "").strip()
+try:
+ ALLOWED_IDS = set(int(x) for x in _allowed.split(",") if x.strip()) if _allowed else None
+except ValueError:
+ ALLOWED_IDS = None
+
+CONTAINER_NAME = "mtproto-proxy"
+CONFIG_FILE = Path("/opt/gotelegram-bot/proxy.json")
+DOMAINS = [
+ "google.com","wikipedia.org","habr.com","github.com",
+ "coursera.org","udemy.com","medium.com","stackoverflow.com",
+ "bbc.com","cnn.com","reuters.com","nytimes.com",
+ "lenta.ru","rbc.ru","ria.ru","kommersant.ru",
+ "stepik.org","duolingo.com","khanacademy.org","ted.com",
+]
+PROMO_LINK = "https://vk.cc/ct29NQ"
+TIP_LINK = "https://pay.cloudtips.ru/p/7410814f"
+
+def _ok(uid):
+ return ALLOWED_IDS is None or uid in ALLOWED_IDS
+def _decode(data):
+ return (data or b"").decode("utf-8", errors="replace").strip()
+
+async def sh(*args, timeout=60):
+ proc = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
+ try:
+ out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
+ except asyncio.TimeoutError:
+ proc.kill(); await proc.wait(); return -1, "", "Timeout"
+ return proc.returncode or 0, _decode(out), _decode(err)
+
+async def get_ip():
+ for url in ("https://api.ipify.org","https://icanhazip.com","https://ifconfig.me"):
+ code, out, _ = await sh("curl","-s","-4","--max-time","5",url, timeout=8)
+ if code == 0 and out:
+ m = re.search(r"(\d{1,3}\.){3}\d{1,3}", out)
+ if m: return m.group(0)
+ return "0.0.0.0"
+
+async def proxy_running():
+ code, out, _ = await sh("docker","ps","--format","{{.Names}}", timeout=10)
+ return code == 0 and CONTAINER_NAME in out
+
+async def docker_val(fmt):
+ code, out, _ = await sh("docker","inspect",CONTAINER_NAME,"--format",fmt, timeout=10)
+ return out.strip() if code == 0 else ""
+
+async def check_port(port):
+ if await proxy_running():
+ hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}} {{end}}")
+ if str(port) in hp.split(): return None
+ code, out, _ = await sh("ss","-tlnp", timeout=5)
+ if code != 0: code, out, _ = await sh("netstat","-tlnp", timeout=5)
+ for line in out.splitlines():
+ if f":{port} " in line or f":{port}\t" in line: return line
+ return None
+
+async def docker_containers_info():
+ code, out, _ = await sh("docker","ps","--format","{{.Names}}\t{{.Image}}\t{{.Ports}}", timeout=10)
+ return out if code == 0 else ""
+
+def save_config(data):
+ CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
+ CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
+
+def load_config():
+ if CONFIG_FILE.exists():
+ try: return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
+ except Exception: pass
+ return {}
+
+async def proxy_info():
+ if not await proxy_running(): return None
+ cmd_str = await docker_val("{{range .Config.Cmd}}{{.}} {{end}}")
+ secret = cmd_str.split()[-1] if cmd_str else ""
+ hp = await docker_val("{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}}{{end}}")
+ port = hp or "443"
+ ip = await get_ip()
+ link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
+ cfg = load_config()
+ return {"ip":ip,"port":port,"secret":secret,"link":link,"domain":cfg.get("domain","—")}
+
+def main_menu_kb():
+ return InlineKeyboardMarkup([
+ [InlineKeyboardButton("🔧 Установить / Обновить", callback_data="menu_install")],
+ [InlineKeyboardButton("📊 Статус", callback_data="menu_status"),
+ InlineKeyboardButton("🔗 Ссылка", callback_data="menu_link")],
+ [InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
+ [InlineKeyboardButton("🔄 Перезапуск", callback_data="menu_restart"),
+ InlineKeyboardButton("📋 Логи", callback_data="menu_logs")],
+ [InlineKeyboardButton("🗑 Удалить", callback_data="menu_remove"),
+ InlineKeyboardButton("🏷 Промо", callback_data="menu_promo")],
+ ])
+
+HELP_TEXT = (
+ "🚀 GoTelegram MTProxy Bot\n\n"
+ "Управление MTProxy (Fake TLS) на сервере.\n"
+ "TCP + UDP (звонки) поддержаны.\n\n"
+ "Используйте кнопки ниже или команды:\n"
+ "/install /status /link /share /restart /logs /remove /promo"
+)
+
+async def start(update, ctx):
+ if not update.effective_user: return
+ if not _ok(update.effective_user.id):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if msg: await msg.reply_text("⛔ Доступ запрещён.")
+ return
+ if update.message:
+ await update.message.reply_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
+ elif update.callback_query:
+ await update.callback_query.edit_message_text(HELP_TEXT, parse_mode="HTML", reply_markup=main_menu_kb())
+
+async def cmd_status(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): await msg.reply_text("⛔"); return
+ info = await proxy_info()
+ if not info:
+ text = "❌ Прокси не запущен.\nНажмите Установить."
+ else:
+ containers = await docker_containers_info()
+ other = "\n".join(l for l in containers.splitlines() if CONTAINER_NAME not in l)
+ text = ("✅ Прокси работает\n\n"
+ f"IP: {html.escape(info['ip'])}\n"
+ f"Порт: {html.escape(info['port'])}\n"
+ f"Домен: {html.escape(info['domain'])}\n"
+ f"Secret: {html.escape(info['secret'])}\n\n"
+ f"Ссылка:\n{html.escape(info['link'])}")
+ if other:
+ text += f"\n\n📦 Другие контейнеры:\n{html.escape(other)}"
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query:
+ await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
+ else:
+ await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
+
+async def cmd_link(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ info = await proxy_info()
+ text = f"{html.escape(info['link'])}" if info else "❌ Прокси не запущен."
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query:
+ await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
+ else:
+ await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
+
+async def cmd_share(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ info = await proxy_info()
+ if not info:
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
+ else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
+ return
+ tg_link = info["link"]
+ share_text = (
+ f"🔐 MTProxy для Telegram\n\n"
+ f"🌍 Сервер: {html.escape(info['ip'])}\n"
+ f"🔌 Порт: {html.escape(info['port'])}\n"
+ f"🔑 Secret: {html.escape(info['secret'])}\n\n"
+ f"👉 Подключиться одним нажатием:\n"
+ f"{html.escape(tg_link)}\n\n"
+ f"Просто нажмите на ссылку или перешлите это сообщение.")
+ kb = InlineKeyboardMarkup([
+ [InlineKeyboardButton("📤 Переслать другу", switch_inline_query=tg_link)],
+ [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
+ ])
+ if update.callback_query:
+ await update.callback_query.edit_message_text(share_text, parse_mode="HTML", reply_markup=kb)
+ else:
+ await msg.reply_text(share_text, parse_mode="HTML", reply_markup=kb)
+
+async def cmd_remove(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ chat = msg.chat
+ if update.callback_query: await update.callback_query.edit_message_text("⏳ Удаляю прокси...")
+ else: await chat.send_message("⏳ Удаляю прокси...")
+ await sh("docker","stop",CONTAINER_NAME, timeout=15)
+ await sh("docker","rm",CONTAINER_NAME, timeout=10)
+ text = "✅ Прокси удалён." if not await proxy_running() else "⚠️ Не удалось удалить."
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ await chat.send_message(text, reply_markup=kb)
+
+async def cmd_restart(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ if not await proxy_running():
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
+ else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
+ return
+ chat = msg.chat
+ if update.callback_query: await update.callback_query.edit_message_text("⏳ Перезапуск...")
+ code, _, err = await sh("docker","restart",CONTAINER_NAME, timeout=30)
+ text = "✅ Перезапущен." if code == 0 else f"❌ Ошибка: {err or 'unknown'}"
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ await chat.send_message(text, reply_markup=kb)
+
+async def cmd_logs(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ if not await proxy_running():
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query: await update.callback_query.edit_message_text("❌ Прокси не запущен.", reply_markup=kb)
+ else: await msg.reply_text("❌ Прокси не запущен.", reply_markup=kb)
+ return
+ code, out, err = await sh("docker","logs","--tail","40",CONTAINER_NAME, timeout=15)
+ text = (out or "") + (("\n" + err) if err else "") or "Нет вывода."
+ if len(text) > 4000: text = text[-4000:]
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query:
+ await update.callback_query.edit_message_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
+ else:
+ await msg.reply_text(f"{html.escape(text)}", parse_mode="HTML", reply_markup=kb)
+
+async def cmd_promo(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ text = ("💰 Хостинг со скидкой до -60%\n"
+ f"Ссылка: {PROMO_LINK}\n\nПромокоды: OFF60, antenka20, antenka6, antenka12\n\nДонат: {TIP_LINK}")
+ kb = InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]])
+ if update.callback_query:
+ await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
+ else:
+ await msg.reply_text(text, parse_mode="HTML", reply_markup=kb)
+
+async def install_step_domain(update, ctx):
+ msg = update.message or (update.callback_query and update.callback_query.message)
+ if not update.effective_user or not msg: return
+ if not _ok(update.effective_user.id): return
+ buttons, row = [], []
+ for i, d in enumerate(DOMAINS):
+ row.append(InlineKeyboardButton(d, callback_data=f"dom_{i}"))
+ if len(row) == 2: buttons.append(row); row = []
+ if row: buttons.append(row)
+ text = "🌐 Выберите домен для маскировки (Fake TLS):"
+ if update.callback_query:
+ await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
+ else:
+ await msg.reply_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(buttons))
+
+async def install_step_port(update, ctx):
+ query = update.callback_query
+ domain = ctx.user_data.get("install_domain", "google.com")
+ busy_443 = await check_port(443)
+ busy_8443 = await check_port(8443)
+ rows = []
+ l443 = "443 (рекомендуется)" if not busy_443 else "443 ⚠️ занят"
+ l8443 = "8443" if not busy_8443 else "8443 ⚠️ занят"
+ rows.append([InlineKeyboardButton(l443, callback_data="port_443"), InlineKeyboardButton(l8443, callback_data="port_8443")])
+ rows.append([InlineKeyboardButton("◀️ Меню", callback_data="menu_main")])
+ pi = ""
+ if busy_443: pi += f"\n⚠️ Порт 443 занят:\n{html.escape(busy_443[:300])}\n"
+ if busy_8443: pi += f"\n⚠️ Порт 8443 занят:\n{html.escape(busy_8443[:300])}\n"
+ text = f"Домен: {html.escape(domain)}\n\n🔌 Выберите порт или введите свой (1-65535):{pi}"
+ ctx.user_data["install_wait_port"] = True
+ await query.edit_message_text(text, parse_mode="HTML", reply_markup=InlineKeyboardMarkup(rows))
+
+async def install_port_chosen(update, ctx, port_str):
+ port = int(port_str)
+ msg = update.callback_query.message if update.callback_query else update.message
+ if not msg: return
+ chat = msg.chat
+ busy = await check_port(port)
+ if busy:
+ kb = InlineKeyboardMarkup([
+ [InlineKeyboardButton(f"Всё равно использовать {port}", callback_data=f"force_{port}")],
+ [InlineKeyboardButton("Выбрать другой порт", callback_data="reselect_port")],
+ [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
+ ])
+ text = f"⚠️ Порт {port} занят!\n\n{html.escape(busy[:500])}\n\nМожно использовать всё равно или выбрать другой."
+ if update.callback_query: await update.callback_query.edit_message_text(text, parse_mode="HTML", reply_markup=kb)
+ else: await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
+ ctx.user_data["install_port"] = port_str
+ return
+ ctx.user_data["install_port"] = port_str
+ ctx.user_data["install_wait_port"] = False
+ await do_install(update, ctx)
+
+async def do_install(update, ctx):
+ domain = ctx.user_data.get("install_domain") or "google.com"
+ port = ctx.user_data.get("install_port") or "443"
+ if update.callback_query:
+ msg = update.callback_query.message
+ await msg.edit_text("⏳ Генерация secret и запуск контейнера...", reply_markup=None)
+ elif update.message:
+ msg = update.message
+ await msg.reply_text("⏳ Генерация secret и запуск контейнера...")
+ else: return
+ chat = msg.chat
+ code, _, _ = await sh("docker","info", timeout=10)
+ if code != 0:
+ await chat.send_message("❌ Docker не запущен.", parse_mode="HTML",
+ reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("◀️ Меню", callback_data="menu_main")]]))
+ return
+ code, secret_out, err = await sh("docker","run","--rm","nineseconds/mtg:2","generate-secret","--hex",domain, timeout=60)
+ if code != 0: await chat.send_message(f"❌ Генерация secret: {err or secret_out}"); return
+ secret = secret_out.strip().split()[-1] if secret_out.strip() else ""
+ if not secret: await chat.send_message("❌ Пустой secret."); return
+ await sh("docker","stop",CONTAINER_NAME, timeout=15)
+ await sh("docker","rm",CONTAINER_NAME, timeout=10)
+ code, _, err = await sh("docker","run","-d","--name",CONTAINER_NAME,"--restart","always",
+ "-p",f"{port}:{port}/tcp","-p",f"{port}:{port}/udp",
+ "nineseconds/mtg:2","simple-run","-n","1.1.1.1","-t","1.0.0.1","-i","prefer-ipv4",
+ f"0.0.0.0:{port}",secret, timeout=90)
+ if code != 0: await chat.send_message(f"❌ Запуск контейнера: {err}"); return
+ save_config({"domain":domain,"port":port,"secret":secret})
+ ip = await get_ip()
+ link = f"tg://proxy?server={ip}&port={port}&secret={secret}"
+ text = ("✅ Прокси установлен!\n\n"
+ f"🌍 IP: {html.escape(ip)}\n"
+ f"🔌 Порт: {html.escape(port)} (TCP + UDP)\n"
+ f"🎭 Домен: {html.escape(domain)}\n"
+ f"🔑 Secret: {html.escape(secret)}\n\n"
+ f"👉 Ссылка:\n{html.escape(link)}\n\n📞 Звонки поддержаны (UDP).")
+ kb = InlineKeyboardMarkup([
+ [InlineKeyboardButton("📤 Поделиться ключом", callback_data="menu_share")],
+ [InlineKeyboardButton("◀️ Меню", callback_data="menu_main")],
+ ])
+ await chat.send_message(text, parse_mode="HTML", reply_markup=kb)
+ for k in ("install_domain","install_port","install_wait_port"): ctx.user_data.pop(k, None)
+
+async def callback_handler(update, ctx):
+ query = update.callback_query
+ if not query or not update.effective_user: return
+ await query.answer()
+ if not _ok(update.effective_user.id): await query.edit_message_text("⛔ Доступ запрещён."); return
+ data = query.data or ""
+ if data == "menu_main": await start(update, ctx)
+ elif data == "menu_install": await install_step_domain(update, ctx)
+ elif data == "menu_status": await cmd_status(update, ctx)
+ elif data == "menu_link": await cmd_link(update, ctx)
+ elif data == "menu_share": await cmd_share(update, ctx)
+ elif data == "menu_restart": await cmd_restart(update, ctx)
+ elif data == "menu_logs": await cmd_logs(update, ctx)
+ elif data == "menu_remove": await cmd_remove(update, ctx)
+ elif data == "menu_promo": await cmd_promo(update, ctx)
+ elif data.startswith("dom_"):
+ try: idx = int(data[4:])
+ except ValueError: await query.edit_message_text("❌ Ошибка."); return
+ if not (0 <= idx < len(DOMAINS)): await query.edit_message_text("❌ Неверный выбор."); return
+ ctx.user_data["install_domain"] = DOMAINS[idx]
+ await install_step_port(update, ctx)
+ elif data == "port_443": await install_port_chosen(update, ctx, "443")
+ elif data == "port_8443": await install_port_chosen(update, ctx, "8443")
+ elif data.startswith("force_"):
+ ctx.user_data["install_port"] = data[6:]
+ ctx.user_data["install_wait_port"] = False
+ await do_install(update, ctx)
+ elif data == "reselect_port": await install_step_port(update, ctx)
+
+async def text_handler(update, ctx):
+ if not update.message or not ctx.user_data.get("install_wait_port"): return
+ text = (update.message.text or "").strip()
+ if not re.match(r"^\d+$", text): return
+ port = int(text)
+ if not (1 <= port <= 65535): await update.message.reply_text("Введите число от 1 до 65535."); return
+ await install_port_chosen(update, ctx, str(port))
+
+def main():
+ if not BOT_TOKEN: raise SystemExit("Задайте BOT_TOKEN в .env")
+ app = Application.builder().token(BOT_TOKEN).build()
+ app.add_handler(CommandHandler("start", start))
+ app.add_handler(CommandHandler("help", start))
+ app.add_handler(CommandHandler("install", install_step_domain))
+ app.add_handler(CommandHandler("status", cmd_status))
+ app.add_handler(CommandHandler("link", cmd_link))
+ app.add_handler(CommandHandler("share", cmd_share))
+ app.add_handler(CommandHandler("remove", cmd_remove))
+ app.add_handler(CommandHandler("restart", cmd_restart))
+ app.add_handler(CommandHandler("logs", cmd_logs))
+ app.add_handler(CommandHandler("promo", cmd_promo))
+ app.add_handler(CallbackQueryHandler(callback_handler))
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, text_handler))
+ app.run_polling(allowed_updates=Update.ALL_TYPES)
+
+if __name__ == "__main__":
+ main()
+BOTEOF
+}
+
+install_bot_deps() {
+ echo -e "${GREEN}[*] Настройка Python venv...${NC}"
+ if [ ! -d "$BOT_DIR/venv" ]; then
+ python3 -m venv "$BOT_DIR/venv" || { echo -e "${RED}Ошибка создания venv.${NC}"; return 1; }
+ fi
+ echo -e "${GREEN}[*] Установка зависимостей (pip)...${NC}"
+ "$BOT_DIR/venv/bin/pip" install --upgrade pip -q 2>/dev/null || true
+ "$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q || { echo -e "${RED}pip install не удался.${NC}"; return 1; }
+}
+
+# ── Выход ────────────────────────────────────────────────────────────────────
+show_exit() {
+ clear
+ show_config
+ echo ""
+ echo -e "${MAGENTA}Поддержка автора (CloudTips):${NC}"
+ echo -e " $TIP_LINK"
+ echo -e " YouTube: https://www.youtube.com/@antenkaru"
+ if command -v qrencode &>/dev/null; then
+ qrencode -t ANSIUTF8 "$TIP_LINK"
+ fi
+ exit 0
+}
+
+# ══════════════════════════════════════════════════════════════════════════════
+# ██ СТАРТ СКРИПТА
+# ══════════════════════════════════════════════════════════════════════════════
+
+install_base_deps
+
+# Копируем себя в /usr/local/bin/gotelegram (если запущены из другого места)
+SELF="$(realpath "$0")"
+if [ "$SELF" != "/usr/local/bin/gotelegram" ]; then
+ cp "$SELF" /usr/local/bin/gotelegram && chmod +x /usr/local/bin/gotelegram
+fi
+
+show_promo
+
+# ── Главное меню (цикл) ─────────────────────────────────────────────────────
+while true; do
+ echo ""
+ echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${MAGENTA}║ GoTelegram Manager (by anten-ka) ║${NC}"
+ echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════════╝${NC}"
+
+ # Статус прокси
+ if proxy_is_running; then
+ echo -e " Прокси: ${GREEN}работает${NC}"
+ else
+ echo -e " Прокси: ${RED}не запущен${NC}"
+ fi
+ # Статус бота
+ if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
+ echo -e " Telegram-бот: ${GREEN}работает${NC}"
+ else
+ echo -e " Telegram-бот: ${YELLOW}не настроен${NC}"
+ fi
+ echo ""
+ echo -e " ${GREEN}1)${NC} Установить / Обновить прокси"
+ echo -e " ${GREEN}2)${NC} Показать данные подключения"
+ echo -e " ${CYAN}3)${NC} Настроить Telegram-бот"
+ echo -e " ${GREEN}4)${NC} Перезапустить прокси"
+ echo -e " ${GREEN}5)${NC} Логи прокси"
+ echo -e " ${YELLOW}6)${NC} Показать PROMO"
+ echo -e " ${RED}7)${NC} Удалить прокси"
+ echo -e " ${WHITE}0)${NC} Выход"
+ echo ""
+ read -p " Пункт: " m_idx
+ case $m_idx in
+ 1) menu_install ;;
+ 2) clear; show_config; read -p "Нажмите Enter..." ;;
+ 3) menu_setup_bot ;;
+ 4)
+ if proxy_is_running; then
+ docker restart "$CONTAINER_NAME" && echo -e "${GREEN}Перезапущен.${NC}" || echo -e "${RED}Ошибка.${NC}"
+ else
+ echo -e "${RED}Прокси не запущен.${NC}"
+ fi
+ read -p "Нажмите Enter..."
+ ;;
+ 5)
+ if proxy_is_running; then
+ docker logs --tail 50 "$CONTAINER_NAME" 2>&1
+ else
+ echo -e "${RED}Прокси не запущен.${NC}"
+ fi
+ read -p "Нажмите Enter..."
+ ;;
+ 6) show_promo ;;
+ 7)
+ if proxy_is_running; then
+ docker stop "$CONTAINER_NAME" &>/dev/null
+ docker rm "$CONTAINER_NAME" &>/dev/null
+ echo -e "${GREEN}Прокси удалён.${NC}"
+ else
+ echo -e "${YELLOW}Прокси не был запущен.${NC}"
+ fi
+ read -p "Нажмите Enter..."
+ ;;
+ 0) show_exit ;;
+ *) echo -e "${RED}Неверный ввод.${NC}" ;;
+ esac
+done