#!/bin/bash # šŸš€ SwiftGram MTProxy — Smart Modular Manager (Self-contained) # v1.2.0 — UDP Calls + Port Selection + Advanced Diagnostics # ── Цвета ──────────────────────────────────────────────────────────────────── 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="swiftgram-proxy" BOT_DIR="/opt/swiftgram" SERVICE_NAME="swiftgram-bot" # ── Дпиннер Šø прогресс-бар ──────────────────────────────────────────────────── spin_pid="" spinner_start() { local msg="${1:-ŠŸŠ¾Š“Š¾Š¶Š“ŠøŃ‚Šµ...}" ( local frames=('ā ‹' 'ā ™' 'ā ¹' 'ā ø' 'ā ¼' 'ā “' 'ā ¦' 'ā §' 'ā ‡' 'ā ') local i=0 while true; do printf "\r ${CYAN}${frames[$i]}${NC} ${msg}" >&2 i=$(( (i+1) % ${#frames[@]} )) sleep 0.12 done ) & spin_pid=$! } spinner_stop() { [ -n "$spin_pid" ] && kill "$spin_pid" 2>/dev/null && wait "$spin_pid" 2>/dev/null spin_pid="" printf "\r\033[K" >&2 } progress_bar() { local current="$1" total="$2" label="${3:-}" local pct=$(( current * 100 / total )) local filled=$(( pct / 2 )) local empty=$(( 50 - filled )) local bar="" for ((i=0; i&2 [ "$current" -eq "$total" ] && echo "" >&2 } run_with_progress() { local label="$1"; shift spinner_start "$label" "$@" >/dev/null 2>&1 local rc=$? spinner_stop if [ $rc -eq 0 ]; then echo -e " ${GREEN}āœ“${NC} $label" else echo -e " ${RED}āœ—${NC} $label ${RED}(ошибка)${NC}" fi return $rc } # ── ŠŸŃ€Š¾Š²ŠµŃ€ŠŗŠø системы ───────────────────────────────────────────────────────── 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 "$@" 2>/dev/null elif command -v yum &>/dev/null; then yum install -y "$@" fi } # ── ŠžŠæŃ‚ŠøŠ¼ŠøŠ·Š°Ń†ŠøŃ Дети (BBR) ─────────────────────────────────────────────────── optimize_system() { if ! sysctl net.ipv4.tcp_congestion_control | grep -q "bbr"; then spinner_start "ŠžŠæŃ‚ŠøŠ¼ŠøŠ·Š°Ń†ŠøŃ сетевого стека (BBR)..." { echo "net.core.default_qdisc=fq" echo "net.ipv4.tcp_congestion_control=bbr" echo "net.ipv4.ip_local_port_range=1024 65535" echo "net.core.somaxconn=65535" } >> /etc/sysctl.conf sysctl -p >/dev/null 2>&1 spinner_stop echo -e " ${GREEN}āœ“${NC} BBR ŃƒŃŠŗŠ¾Ń€ŠµŠ½ŠøŠµ Š²ŠŗŠ»ŃŽŃ‡ŠµŠ½Š¾" fi } # ── Firewall (Фикс звонков) ────────────────────────────────────────────────── fix_firewall() { local port="$1" if command -v ufw &>/dev/null && ufw status | grep -q "active"; then ufw allow "$port"/tcp >/dev/null 2>&1 ufw allow "$port"/udp >/dev/null 2>&1 elif command -v firewall-cmd &>/dev/null && systemctl is-active --quiet firewalld; then firewall-cmd --permanent --add-port="$port"/tcp >/dev/null 2>&1 firewall-cmd --permanent --add-port="$port"/udp >/dev/null 2>&1 firewall-cmd --reload >/dev/null 2>&1 fi echo -e " ${GREEN}āœ“${NC} Firewall: порты $port (TCP/UDP) открыты" } # ── Установка зависимостей ─────────────────────────────────────────────────── install_base_deps() { local steps=0 total=4 progress_bar $steps $total "ŠŸŃ€Š¾Š²ŠµŃ€ŠŗŠ° зависимостей..." if ! command -v curl &>/dev/null; then run_with_progress "Установка curl" install_pkg curl; fi steps=$((steps+1)); progress_bar $steps $total "curl" if ! command -v docker &>/dev/null; then spinner_start "Установка Docker..." curl -fsSL https://get.docker.com | sh >/dev/null 2>&1 systemctl enable --now docker >/dev/null 2>&1 spinner_stop fi steps=$((steps+1)); progress_bar $steps $total "docker" if ! command -v qrencode &>/dev/null; then run_with_progress "Установка qrencode" install_pkg qrencode; fi steps=$((steps+1)); progress_bar $steps $total "qrencode" if ! docker info &>/dev/null 2>&1; then systemctl start docker 2>/dev/null; sleep 2; fi steps=$((steps+1)); progress_bar $steps $total "Готово" echo "" } # ── Утилиты IP ────────────────────────────────────────────────────────────── get_ip4() { curl -s -4 --max-time 5 https://api.ipify.org || echo "0.0.0.0"; } # ── Š’ŃˆŠøŃ‚Ń‹Šµ исхоГники бота ──────────────────────────────────────────────────── prepare_bot_source() { mkdir -p "$BOT_DIR" cat << 'EOF' > "$BOT_DIR/requirements.txt" python-telegram-bot==20.8 EOF cat << 'EOF' > "$BOT_DIR/bot.py" #!/usr/bin/env python3 """ SwiftGram MTProxy — Telegram-бот Š“Š»Ń ŃƒŠæŃ€Š°Š²Š»ŠµŠ½ŠøŃ MTProxy на сервере. """ 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("/opt/swiftgram/.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 = os.environ.get("CONTAINER_NAME", "swiftgram-proxy") CONFIG_FILE = Path(os.environ.get("CONFIG_PATH", "/opt/swiftgram/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", ] 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_ip4() -> 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 check_bbr() -> bool: code, out, _ = await sh("sysctl", "net.ipv4.tcp_congestion_control", timeout=5) return "bbr" in out.lower() 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 docker_containers_info() -> str: code, out, _ = await sh("docker", "ps", "--format", "{{.Names}}\t{{.Image}}\t{{.Ports}}", timeout=10) return out 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) for line in out.splitlines(): if f":{port} " in line or f":{port}\t" in line: return line return None 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: 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.split()[0] if hp else "443" ip4 = await get_ip4() cfg = load_config() return { "ip4": ip4, "port": port, "secret": secret, "domain": cfg.get("domain", "—"), "link4": f"tg://proxy?server={ip4}&port={port}&secret={secret}" } 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")], ]) HELP_TEXT = ( "šŸš€ SwiftGram MTProxy Manager\n\n" "• TCP + UDP (звонки) активны\n" "• BBR Š¾ŠæŃ‚ŠøŠ¼ŠøŠ·Š°Ń†ŠøŃ\n\n" ) async def start(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_user or not _ok(update.effective_user.id): return msg = update.message or (update.callback_query and update.callback_query.message) 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: if not update.effective_user or not _ok(update.effective_user.id): return info = await proxy_info() if not info: text = "āŒ ŠŸŃ€Š¾ŠŗŃŠø не Š·Š°ŠæŃƒŃ‰ŠµŠ½." else: bbr = "āœ… Активен" if await check_bbr() else "āŒ Š’Ń‹ŠŗŠ»ŃŽŃ‡ŠµŠ½" containers = await docker_containers_info() other = "\n".join(l for l in containers.splitlines() if CONTAINER_NAME not in l) text = ( "āœ… SwiftGram работает\n\n" f"🌐 IPv4: {info['ip4']}\n" f"šŸ”Œ ŠŸŠ¾Ń€Ń‚: {info['port']}\n" f"šŸš€ BBR: {bbr}\n" f"šŸ“ž Звонки: āœ… UDP открыт\n" ) if other: text += f"\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 update.message.reply_text(text, parse_mode="HTML", reply_markup=kb) async def cmd_link(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_user or not _ok(update.effective_user.id): return info = await proxy_info() if not info: text = "āŒ ŠŸŃ€Š¾ŠŗŃŠø не Š·Š°ŠæŃƒŃ‰ŠµŠ½." else: text = f"Дсылка:\n{info['link4']}" 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 update.message.reply_text(text, parse_mode="HTML", reply_markup=kb) async def cmd_restart(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_user or not _ok(update.effective_user.id): return await sh("docker", "restart", CONTAINER_NAME) kb = InlineKeyboardMarkup([[InlineKeyboardButton("ā—€ļø ŠœŠµŠ½ŃŽ", callback_data="menu_main")]]) if update.callback_query: await update.callback_query.edit_message_text("āœ… ŠŸŠµŃ€ŠµŠ·Š°ŠæŃƒŃ‰ŠµŠ½Š¾", reply_markup=kb) async def cmd_logs(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_user or not _ok(update.effective_user.id): return running = await proxy_running() if not running: text = "āŒ ŠšŠ¾Š½Ń‚ŠµŠ¹Š½ŠµŃ€ не найГен или остановлен." else: # Š—Š°ŠæŃ€Š°ŃˆŠøŠ²Š°ŠµŠ¼ 50 строк Šø Š¾Š±ŃŠŠµŠ“ŠøŠ½ŃŠµŠ¼ вывоГы code, out, err = await sh("docker", "logs", "--tail", "50", CONTAINER_NAME, timeout=15) combined_logs = (out.strip() + "\n" + err.strip()).strip() if not combined_logs: text = "šŸ“‹ ŠšŠ¾Š½Ń‚ŠµŠ¹Š½ŠµŃ€ Š·Š°ŠæŃƒŃ‰ŠµŠ½, но логи пока ŠæŃƒŃŃ‚Ń‹.\nŠŸŠ¾ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠøŃ‚ŃŒŃŃ Šŗ прокси, чтобы ŠæŠ¾ŃŠ²ŠøŠ»ŠøŃŃŒ записи." else: text = f"
{html.escape(combined_logs)}
" 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 update.message.reply_text(text, parse_mode="HTML", reply_markup=kb) async def do_install(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: domain = ctx.user_data.get("install_domain", "google.com") port = ctx.user_data.get("install_port", "443") msg = update.callback_query.message if update.callback_query else update.message await msg.reply_text(f"ā³ Установка на порт {port}...") _, s_out, _ = await sh("docker", "run", "--rm", "nineseconds/mtg:2", "generate-secret", "--hex", domain) secret = s_out.strip().split()[-1] if s_out else "" await sh("docker", "stop", CONTAINER_NAME) await sh("docker", "rm", CONTAINER_NAME) 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 ) if code == 0: save_config({"domain": domain, "port": port, "secret": secret}) await msg.reply_text(f"āœ… Установлено!") await cmd_status(update, ctx) else: await msg.reply_text(f"āŒ ŠžŃˆŠøŠ±ŠŗŠ°: {err}") 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 text.isdigit() and (1 <= int(text) <= 65535): ctx.user_data["install_port"] = text ctx.user_data["install_wait_port"] = False await do_install(update, ctx) async def callback_handler(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: query = update.callback_query await query.answer() data = query.data if data == "menu_main": await start(update, ctx) elif data == "menu_status": await cmd_status(update, ctx) elif data == "menu_link": await cmd_link(update, ctx) elif data == "menu_restart": await cmd_restart(update, ctx) elif data == "menu_logs": await cmd_logs(update, ctx) elif data == "menu_install": 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 = [] await query.edit_message_text("🌐 Выберите Гомен:", reply_markup=InlineKeyboardMarkup(buttons)) elif data.startswith("dom_"): idx = int(data[4:]) ctx.user_data["install_domain"] = DOMAINS[idx] busy_443 = await check_port(443) kb = InlineKeyboardMarkup([ [InlineKeyboardButton("443 " + ("āš ļø" if busy_443 else "āœ…"), callback_data="port_443"), InlineKeyboardButton("8443", callback_data="port_8443")], [InlineKeyboardButton("ā—€ļø ŠœŠµŠ½ŃŽ", callback_data="menu_main")] ]) ctx.user_data["install_wait_port"] = True await query.edit_message_text("šŸ”Œ Выберите порт или ввеГите свой:", reply_markup=kb) elif data.startswith("port_"): ctx.user_data["install_port"] = data[5:] ctx.user_data["install_wait_port"] = False await do_install(update, ctx) elif data == "menu_remove": await sh("docker", "stop", CONTAINER_NAME) await sh("docker", "rm", CONTAINER_NAME) await query.edit_message_text("āœ… УГалено.") def main() -> None: if not BOT_TOKEN: return app = Application.builder().token(BOT_TOKEN).build() app.add_handler(CommandHandler("start", start)) app.add_handler(CallbackQueryHandler(callback_handler)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, text_handler)) app.run_polling() if __name__ == "__main__": main() EOF chmod +x "$BOT_DIR/bot.py" } # ── ŠŸŠ¾ŠŗŠ°Š·Š°Ń‚ŃŒ Ганные ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠµŠ½ŠøŃ ────────────────────────────────────────────── show_config() { if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo -e "${RED}ŠŸŃ€Š¾ŠŗŃŠø не Š·Š°ŠæŃƒŃ‰ŠµŠ½!${NC}"; return fi local DATA=$(cat "$BOT_DIR/proxy.json" 2>/dev/null) local PORT=$(echo "$DATA" | grep -oP '(?<="port": ")[^"]*') local SECRET=$(echo "$DATA" | grep -oP '(?<="secret": ")[^"]*') local IP4=$(get_ip4) local LINK="tg://proxy?server=$IP4&port=$PORT&secret=$SECRET" echo -e "\n${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e " IPv4: ${WHITE}$IP4${NC}" echo -e " ŠŸŠ¾Ń€Ń‚: ${WHITE}$PORT${NC} (TCP + UDP)" echo -e " Secret: ${WHITE}$SECRET${NC}" echo -e "\n Дсылка: ${BLUE}$LINK${NC}" echo "" qrencode -t ANSIUTF8 "$LINK" } # ── ŠœŠµŠ½ŃŽ ŃƒŃŃ‚Š°Š½Š¾Š²ŠŗŠø ─────────────────────────────────────────────────────────── menu_install() { echo -e "${CYAN}Установка ŠæŃ€Š¾ŠøŠ·Š²Š¾Š“ŠøŃ‚ŃŃ через бота.${NC}" echo -e "ŠŠ°ŃŃ‚Ń€Š¾Š¹Ń‚Šµ бота команГой в Š¼ŠµŠ½ŃŽ, затем ŠøŃŠæŠ¾Š»ŃŒŠ·ŃƒŠ¹Ń‚е /start в Telegram." } # ── ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠ° бота ─────────────────────────────────────────────────────────── menu_setup_bot() { clear echo -e "${CYAN}ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠ° Telegram бота...${NC}" if ! command -v python3 &>/dev/null; then run_with_progress "Python3" install_pkg python3 python3-pip python3-venv; fi prepare_bot_source cd "$BOT_DIR" [ ! -d "venv" ] && python3 -m venv venv >/dev/null 2>&1 ./venv/bin/pip install -r requirements.txt -q read -p "ВвеГите BOT_TOKEN: " TOKEN read -p "Š’Š°Ńˆ Telegram ID: " ADMIN_ID { echo "BOT_TOKEN=$TOKEN" echo "ALLOWED_IDS=$ADMIN_ID" echo "CONTAINER_NAME=$CONTAINER_NAME" echo "CONFIG_PATH=$BOT_DIR/proxy.json" } > .env cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF [Unit] Description=SwiftGram Bot Service After=network.target docker.service [Service] Type=simple WorkingDirectory=$BOT_DIR ExecStart=$BOT_DIR/venv/bin/python $BOT_DIR/bot.py Restart=always [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now "$SERVICE_NAME" systemctl restart "$SERVICE_NAME" echo -e "\n${GREEN}āœ“ Бот Š·Š°ŠæŃƒŃ‰ŠµŠ½!${NC}" read -p "ŠŠ°Š¶Š¼ŠøŃ‚Šµ Enter..." } # ── Главный цикл ───────────────────────────────────────────────────────────── install_base_deps SELF="$(realpath "$0")" [ "$SELF" != "/usr/local/bin/swiftgram" ] && { cp "$SELF" /usr/local/bin/swiftgram; chmod +x /usr/local/bin/swiftgram; } while true; do clear echo -e "${MAGENTA}SWIFTGRAM MANAGER (UDP + Diagnostics)${NC}" docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$" && echo -e " ŠŸŃ€Š¾ŠŗŃŠø: ${GREEN}Š ŠŠ‘ŠžŠ¢ŠŠ•Š¢${NC}" || echo -e " ŠŸŃ€Š¾ŠŗŃŠø: ${RED}Š’Š«ŠšŠ›Š®Š§Š•Š${NC}" echo -e "\n ${GREEN}1)${NC} Š£ŃŃ‚Š°Š½Š¾Š²ŠøŃ‚ŃŒ (через бота)\n ${GREEN}2)${NC} ŠŸŠ¾ŠŗŠ°Š·Š°Ń‚ŃŒ Ганные (QR)\n ${CYAN}3)${NC} ŠŠ°ŃŃ‚Ń€Š¾ŠøŃ‚ŃŒ бота\n ${RED}6)${NC} Š£Š“Š°Š»ŠøŃ‚ŃŒ\n ${WHITE}0)${NC} ВыхоГ" read -p "ŠŸŃƒŠ½ŠŗŃ‚: " m_idx case $m_idx in 1) menu_install; read -p "Enter..." ;; 2) clear; show_config; read -p "Enter..." ;; 3) menu_setup_bot ;; 6) docker stop "$CONTAINER_NAME" && docker rm "$CONTAINER_NAME"; rm -rf "$BOT_DIR"; exit 0 ;; 0) exit 0 ;; esac done