- Modified Docker inspect template to include spaces between port bindings. - Updated proxy_info logic to extract only the first port instance. - Fixed concatenation of TCP and UDP ports (e.g., 2725727257 -> 27257).
484 lines
20 KiB
Bash
484 lines
20 KiB
Bash
#!/bin/bash
|
||
# 🚀 SwiftGram MTProxy — Smart Modular Manager (Self-contained)
|
||
# v1.1.0 — Фикс звонков (UDP) + Автономный режим
|
||
|
||
# ── Цвета ────────────────────────────────────────────────────────────────────
|
||
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<filled; i++)); do bar+="█"; done
|
||
for ((i=0; i<empty; i++)); do bar+="░"; done
|
||
printf "\r ${GREEN}[${bar}]${NC} ${pct}%% ${label}" >&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"; }
|
||
get_ip6() { curl -s -6 --max-time 5 https://api6.ipify.org || echo ""; }
|
||
|
||
find_smart_port() {
|
||
local port=443
|
||
if ss -tlnp | grep -qE ":${port}\b"; then
|
||
echo -e " ${YELLOW}ℹ Порт 443 занят (Hiddify/Nginx). Пробую 8443...${NC}" >&2
|
||
port=8443
|
||
if ss -tlnp | grep -qE ":${port}\b"; then
|
||
port=$(( (RANDOM % 10000) + 20000 ))
|
||
echo -e " ${YELLOW}ℹ Порт 8443 тоже занят. Выбран случайный: $port${NC}" >&2
|
||
fi
|
||
fi
|
||
echo "$port"
|
||
}
|
||
|
||
# ── Вшитые исходники бота ────────────────────────────────────────────────────
|
||
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,
|
||
)
|
||
|
||
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 get_ip6() -> str:
|
||
code, out, _ = await sh("curl", "-s", "-6", "--max-time", "5", "https://api6.ipify.org", timeout=8)
|
||
if code == 0 and out:
|
||
return out.strip()
|
||
return ""
|
||
|
||
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 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()
|
||
ip6 = await get_ip6()
|
||
cfg = load_config()
|
||
return {
|
||
"ip4": ip4, "ip6": ip6, "port": port, "secret": secret,
|
||
"domain": cfg.get("domain", "—"),
|
||
"link4": f"tg://proxy?server={ip4}&port={port}&secret={secret}",
|
||
"link6": f"tg://proxy?server={ip6}&port={port}&secret={secret}" if ip6 else None
|
||
}
|
||
|
||
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 = (
|
||
"🚀 <b>SwiftGram MTProxy Manager</b>\n\n"
|
||
"• TCP + UDP (звонки) активны\n"
|
||
"• IPv6 поддержка включена\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 = "❌ <b>Прокси не запущен.</b>"
|
||
else:
|
||
bbr = "✅ Активен" if await check_bbr() else "❌ Выключен"
|
||
text = (
|
||
"✅ <b>SwiftGram работает</b>\n\n"
|
||
f"🌐 IPv4: <code>{info['ip4']}</code>\n"
|
||
f"🔌 Порт: <code>{info['port']}</code>\n"
|
||
f"🚀 BBR: <code>{bbr}</code>\n"
|
||
f"📞 Звонки: <code>✅ UDP открыт</code>\n"
|
||
)
|
||
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"<b>Ссылка:</b>\n<code>{info['link4']}</code>"
|
||
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 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)
|
||
|
||
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.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"
|
||
}
|
||
|
||
# ── 1) Установка MTProxy ─────────────────────────────────────────────────────
|
||
menu_install() {
|
||
clear
|
||
local domains=("google.com" "wikipedia.org" "github.com" "habr.com")
|
||
echo -e "${CYAN}Выберите домен (Fake TLS):${NC}"
|
||
for i in "${!domains[@]}"; do echo -e " ${YELLOW}$((i+1)))${NC} ${domains[$i]}"; done
|
||
read -p "Выбор: " d_idx
|
||
DOMAIN=${domains[$((d_idx-1))]:-google.com}
|
||
|
||
local PORT=$(find_smart_port)
|
||
optimize_system
|
||
fix_firewall "$PORT"
|
||
|
||
spinner_start "Запуск контейнера..."
|
||
docker pull nineseconds/mtg:2 >/dev/null 2>&1
|
||
local SECRET=$(docker run --rm nineseconds/mtg:2 generate-secret --hex "$DOMAIN" 2>/dev/null)
|
||
|
||
docker stop "$CONTAINER_NAME" &>/dev/null
|
||
docker rm "$CONTAINER_NAME" &>/dev/null
|
||
|
||
# ВАЖНО: проброс UDP для звонков
|
||
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 -i prefer-ipv4 \
|
||
0.0.0.0:"$PORT" "$SECRET" > /dev/null 2>&1
|
||
|
||
sleep 2
|
||
spinner_stop
|
||
|
||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||
mkdir -p "$BOT_DIR"
|
||
echo "{\"domain\": \"$DOMAIN\", \"port\": \"$PORT\", \"secret\": \"$SECRET\"}" > "$BOT_DIR/proxy.json"
|
||
echo -e "\n${GREEN}✓ SwiftGram успешно запущен!${NC}"
|
||
show_config
|
||
fi
|
||
read -p "Нажмите Enter..."
|
||
}
|
||
|
||
# ── 3) Настройка бота ────────────────────────────────────────────────────────
|
||
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 Fixed)${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 ;; 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 |