From 29b6e05866b8f8dab0acea2bd2ba434c7e9c6826 Mon Sep 17 00:00:00 2001 From: anten-ka Date: Fri, 6 Mar 2026 14:59:00 +0300 Subject: [PATCH] GoTelegram MTProxy + bot (gotelegram_pro) Made-with: Cursor --- .gitignore | 18 ++ IMPROVEMENTS.md | 99 ++++++++ INSTALL_PRIVATE.md | 144 +++++++++++ PUSH_README.txt | 34 +++ bootstrap_install.sh | 65 +++++ gotelegram-bot/README.md | 70 ++++++ gotelegram-bot/bot.py | 402 ++++++++++++++++++++++++++++++ gotelegram-bot/config.example.env | 4 + gotelegram-bot/requirements.txt | 1 + install_gotelegram_bot.sh | 139 +++++++++++ push_to_github.bat | 54 ++++ 11 files changed, 1030 insertions(+) create mode 100644 .gitignore create mode 100644 IMPROVEMENTS.md create mode 100644 INSTALL_PRIVATE.md create mode 100644 PUSH_README.txt create mode 100644 bootstrap_install.sh create mode 100644 gotelegram-bot/README.md create mode 100644 gotelegram-bot/bot.py create mode 100644 gotelegram-bot/config.example.env create mode 100644 gotelegram-bot/requirements.txt create mode 100644 install_gotelegram_bot.sh create mode 100644 push_to_github.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fd7284 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Секреты и конфиг с токенами +.env +*.env.local +config.local.env + +# Python +__pycache__/ +*.py[cod] +venv/ +.venv/ +*.egg-info/ +.eggs/ + +# Временные и системные +.DS_Store +Thumbs.db +*.log +*.tmp diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md new file mode 100644 index 0000000..e691080 --- /dev/null +++ b/IMPROVEMENTS.md @@ -0,0 +1,99 @@ +# Предложения по улучшению GoTelegram MTProxy + +## Анализ текущего скрипта + +Скрипт уже хорошо структурирован: проверка root, установка зависимостей, меню, работа с Docker (nineseconds/mtg). Ниже — конкретные улучшения. + +--- + +## 1. Безопасность и надёжность + +### 1.1 Валидация порта +- **Сейчас:** при выборе "свой порт" можно ввести нечисло или порт вне диапазона. +- **Предложение:** проверять число в диапазоне 1–65535 и повторять запрос при ошибке. + +```bash +while true; do + read -p "Введите порт (1-65535): " PORT + [[ "$PORT" =~ ^[0-9]+$ ]] && (( PORT >= 1 && PORT <= 65535 )) && break + echo "Неверный порт." +done +``` + +### 1.2 Проверка занятости порта +- Перед запуском контейнера проверять, что порт свободен (например, `ss -tlnp | grep -q ":$PORT "`), и выводить понятное сообщение, если порт занят. + +### 1.3 Остановка только своего контейнера +- В пункте 4 (удаление) использовать `docker stop mtproto-proxy 2>/dev/null; docker rm mtproto-proxy 2>/dev/null` и проверять код возврата, чтобы не путать с отсутствием контейнера. + +--- + +## 2. Функциональность + +### 2.1 Свой домен для Fake TLS +- В списке доменов добавить пункт "Свой домен" с вводом произвольной строки и базовой проверкой (например, наличие точки, длина). + +### 2.2 Рестарт прокси +- Отдельный пункт меню "Перезапустить прокси" без пересоздания контейнера: `docker restart mtproto-proxy`. + +### 2.3 Логи +- Пункт "Показать логи" (последние N строк): `docker logs --tail 50 mtproto-proxy`. + +### 2.4 Обновление образа mtg +- Пункт "Обновить образ mtg": `docker pull nineseconds/mtg:2`, затем предложить пересоздать прокси с теми же параметрами (сохранить secret/порт из текущего контейнера). + +### 2.5 Сохранение конфигурации +- Сохранять выбранные домен и порт в файл (например, `/etc/gotelegram.conf` или `~/.gotelegram`), чтобы при обновлении/переустановке не вводить заново. + +--- + +## 3. Удобство и совместимость + +### 3.1 Поддержка RHEL/CentOS +- В `install_deps` для установки `qrencode` использовать `command -v apt-get` / `command -v yum` или `dnf`, а не только `apt-get` и `yum`, чтобы корректно работало на RHEL 8+. + +### 3.2 Неинтерактивный режим +- Поддержка аргументов для автоматической установки, например: + `./setup_gotelegram.sh --domain wikipedia.org --port 443 --no-promo` +- Полезно для скриптов развёртывания и для бота. + +### 3.3 Отключение промо +- Переменная окружения или флаг, например `SKIP_PROMO=1` или `--no-promo`, чтобы при первом запуске не показывать промо (для автоматизации и бота). + +--- + +## 4. Вывод и интерфейс + +### 4.1 Текстовый вывод ссылки для копирования +- В `show_config` выводить ссылку в одну строку без переносов, чтобы удобно копировать из терминала и отправлять в бота/чат. + +### 4.2 Сообщение при отсутствии прокси +- В пункте 2 при отсутствии контейнера не только "Прокси не найден!", но и подсказка: "Выберите пункт 1 для установки." + +### 4.3 Версия скрипта +- В начале или в меню выводить версию (например, из переменной `VERSION="1.0"`), чтобы при обновлениях было проще отлаживать. + +--- + +## 5. Мелкие правки + +### 5.1 Заголовок промо +- В рамке промо текст "ХОСТИНГ СО СКИДКОЙ..." разной длины — выровнять по ширине рамки или центрировать, чтобы рамка не "прыгала". + +### 5.2 Имя контейнера +- Сейчас жёстко `mtproto-proxy`. Имеет смысл вынести в переменную в начале скрипта (например, `CONTAINER_NAME="mtproto-proxy"`), чтобы при необходимости можно было менять в одном месте. + +### 5.3 Таймауты curl +- В `get_ip` уже есть `--max-time 5`. Можно добавить резервный источник (например, `ifconfig.me` или `ipecho.net/plain`) на случай недоступности основных. + +--- + +## Приоритеты внедрения + +| Приоритет | Улучшение | +|-----------|-----------| +| Высокий | Валидация порта, проверка занятости порта, неинтерактивный режим / отключение промо | +| Средний | Свой домен, рестарт, логи, сохранение конфигурации | +| Низкий | Версия скрипта, выравнивание промо, дополнительные источники IP | + +Эти изменения улучшат устойчивость скрипта в автоматизации и при использовании через Telegram-бота. diff --git a/INSTALL_PRIVATE.md b/INSTALL_PRIVATE.md new file mode 100644 index 0000000..e54ccde --- /dev/null +++ b/INSTALL_PRIVATE.md @@ -0,0 +1,144 @@ +# Установка из закрытого GitHub-репозитория (по ключу) + +Инструкция для размещения проекта в **приватном** репозитории и установки на сервер **по SSH-ключу** или **по токену (PAT)**. + +--- + +## 1. Размещение в закрытом репозитории + +### 1.1 Создать приватный репозиторий + +1. GitHub → **New repository**. +2. Имя, например: `gotelegram_pro`. +3. Выберите **Private**. +4. Не добавляйте README, .gitignore и лицензию, если пушите существующий проект. + +### 1.2 Загрузить код (с локальной машины) + +```bash +cd "Скрипт mtproxy" +git init +git remote add origin git@github.com:ВАШ_ЛОГИН/gotelegram_pro.git +git add . +git commit -m "GoTelegram MTProxy + bot" +git branch -M main +git push -u origin main +``` + +Если репо уже есть и вы только добавляете remote: + +```bash +git remote add origin git@github.com:ВАШ_ЛОГИН/gotelegram_pro.git +git push -u origin main +``` + +### 1.3 Настроить доступ «по ключу» + +Доступ к приватному репо с сервера возможен двумя способами. + +| Способ | Когда использовать | +|--------|--------------------| +| **SSH-ключ** | На сервере уже настроен `ssh-agent` и ключ добавлен в GitHub (Deploy key или ваш SSH key). | +| **Personal Access Token (PAT)** | Удобно для одноразовой установки или CI; токен передаётся в переменной окружения. | + +--- + +## 2. Установка по SSH-ключу + +### 2.1 Добавить SSH-ключ в GitHub + +**На сервере** (или с машины, с которой будете клонировать): + +```bash +# Сгенерировать ключ (если ещё нет) +ssh-keygen -t ed25519 -C "server-gotelegram" -f ~/.ssh/gotelegram_deploy -N "" +cat ~/.ssh/gotelegram_deploy.pub +``` + +- **Личный репо:** GitHub → **Settings → SSH and GPG keys → New SSH key** → вставить содержимое `.pub`. +- **Организация:** добавить ключ как **Deploy key** в настройках репозитория (Read-only достаточно). + +Проверка: + +```bash +ssh -T git@github.com +``` + +### 2.2 Клонировать и установить + +**Вариант А:** скопируйте `bootstrap_install.sh` на сервер (через scp или вставьте содержимое в файл), затем: + +```bash +chmod +x bootstrap_install.sh +GIT_REPO_SSH="git@github.com:ВАШ_ЛОГИН/gotelegram_pro.git" sudo bash bootstrap_install.sh +``` + +**Вариант Б:** клонировать репо вручную и запустить установку: + +```bash +git clone git@github.com:ВАШ_ЛОГИН/gotelegram_pro.git /tmp/gotelegram_pro +sudo /tmp/gotelegram_pro/install_gotelegram_bot.sh +``` + +--- + +## 3. Установка по токену (PAT) + +### 3.1 Создать Personal Access Token + +1. GitHub → **Settings → Developer settings → Personal access tokens**. +2. **Tokens (classic)** → **Generate new token**. +3. Права: минимум **repo** (полный доступ к приватным репо). +4. Скопировать токен (один раз показывается). + +### 3.2 Установка одной командой (bootstrap) + +Скопируйте **bootstrap_install.sh** на сервер (scp или создайте файл и вставьте содержимое из репо). Затем: + +```bash +# Подставьте свой токен и репо +export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +export GIT_REPO_HTTPS="https://github.com/ВАШ_ЛОГИН/gotelegram_pro.git" +sudo -E bash bootstrap_install.sh +``` + +`-E` сохраняет переменные окружения (в т.ч. `GITHUB_TOKEN`) при sudo. + +Вариант без экспорта (токен в истории не сохраняйте в продакшене): + +```bash +GITHUB_TOKEN=ghp_xxx GIT_REPO_HTTPS=https://github.com/USER/gotelegram_pro.git sudo -E bash bootstrap_install.sh +``` + +### 3.3 Установка через install_gotelegram_bot.sh + +Если скрипт **install_gotelegram_bot.sh** уже лежит на сервере (скопирован из репо), но папки **gotelegram-bot** рядом нет, скрипт сам попытается клонировать репо по токену: + +```bash +export GITHUB_TOKEN="ghp_xxxxxxxxxxxx" +export GOTETELEGRAM_BOT_REPO="https://github.com/ВАШ_ЛОГИН/gotelegram_pro" +sudo -E ./install_gotelegram_bot.sh +``` + +Токен в процессе установки **не сохраняется** на диск, только используется для `git clone`. + +--- + +## 4. Безопасность + +- **SSH:** используйте отдельный ключ (Deploy key) с доступом только к этому репо и при необходимости только на чтение. +- **PAT:** не коммитьте токен в репо и не оставляйте в истории shell. Для повторных установок лучше SSH или новый токен. +- После установки можно удалить клонированную папку: `rm -rf /tmp/gotelegram_pro*`. + +--- + +## 5. Краткая шпаргалка + +| Задача | Команда | +|--------|--------| +| Установка по SSH (репо уже клонирован) | `sudo ./install_gotelegram_bot.sh` | +| Установка по SSH (bootstrap) | `GIT_REPO_SSH=git@github.com:USER/REPO.git sudo bash bootstrap_install.sh` | +| Установка по токену (bootstrap) | `GITHUB_TOKEN=ghp_xxx GIT_REPO_HTTPS=https://github.com/USER/REPO.git sudo -E bash bootstrap_install.sh` | +| Установка по токену (только install-скрипт) | `GITHUB_TOKEN=ghp_xxx sudo -E ./install_gotelegram_bot.sh` | + +Вместо `USER/REPO` подставьте свой логин и имя репозитория. diff --git a/PUSH_README.txt b/PUSH_README.txt new file mode 100644 index 0000000..e0c90b0 --- /dev/null +++ b/PUSH_README.txt @@ -0,0 +1,34 @@ +═══════════════════════════════════════════════════════════════ + Как выложить проект в GitHub (ключ давать никому не нужно) +═══════════════════════════════════════════════════════════════ + +1. Создайте репозиторий на GitHub + • Откройте: https://github.com/new + • Repository name: gotelegram_pro + • Выберите Private (или Public) + • НЕ добавляйте README, .gitignore, License — нажмите Create repository + +2. Настройте доступ к GitHub с этого компьютера (один раз) + + Вариант А — по SSH (удобно навсегда): + • Установите Git: https://git-scm.com/download/win + • Создайте ключ: в терминале (Git Bash или PowerShell): + ssh-keygen -t ed25519 -C "your@email" -f "%USERPROFILE%\.ssh\id_ed25519" -N "" + • Скопируйте публичный ключ в буфер: + type %USERPROFILE%\.ssh\id_ed25519.pub + • GitHub → Settings → SSH and GPG keys → New SSH key → вставьте ключ + • Проверка: ssh -T git@github.com + + Вариант Б — по токену (разовый или если не хотите SSH): + • GitHub → Settings → Developer settings → Personal access tokens + • Generate new token (classic), право: repo + • Скопируйте токен (ghp_...) + • При первом push Git спросит логин и пароль — в пароль вставьте этот токен + • Или: git remote set-url origin https://ВАШ_ТОКЕН@github.com/anten-ka/gotelegram_pro.git + +3. Запустите выкладку + • Дважды щёлкните: push_to_github.bat + • Подтвердите отправку (y) + • Если всё настроено — код уйдёт в репо без ввода ключа в чат + +Ключ или токен нужны только на вашем компьютере в настройках Git — никому (и ни в какой чат) их передавать не нужно. diff --git a/bootstrap_install.sh b/bootstrap_install.sh new file mode 100644 index 0000000..d60d42a --- /dev/null +++ b/bootstrap_install.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Bootstrap: клонирует приватный репозиторий (по SSH или по токену) и запускает установку. +# Использование: +# По SSH-ключу: GIT_REPO_SSH=git@github.com:USER/REPO.git bash bootstrap_install.sh +# По токену: GITHUB_TOKEN=ghp_xxx GIT_REPO_HTTPS=https://github.com/USER/REPO.git bash bootstrap_install.sh +# Или задайте репо по умолчанию ниже и запустите: sudo bash bootstrap_install.sh + +set -e +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Репозиторий по умолчанию (подставьте свой) +REPO_SSH="${GIT_REPO_SSH:-git@github.com:anten-ka/gotelegram_pro.git}" +REPO_HTTPS="${GIT_REPO_HTTPS:-https://github.com/anten-ka/gotelegram_pro.git}" +BRANCH="${GIT_BRANCH:-main}" +CLONE_DIR="/tmp/gotelegram_pro_install_$$" + +cleanup() { rm -rf "$CLONE_DIR"; } +trap cleanup EXIT + +echo -e "${GREEN}[*] Установка GoTelegram Bot из приватного репозитория${NC}" + +if ! command -v git &>/dev/null; then + echo -e "${YELLOW}[*] Установка git...${NC}" + if command -v apt-get &>/dev/null; then + apt-get update && apt-get install -y git + elif command -v dnf &>/dev/null; then + dnf install -y git + elif command -v yum &>/dev/null; then + yum install -y git + else + echo -e "${RED}Установите git вручную.${NC}" + exit 1 + fi +fi + +if [ -n "$GITHUB_TOKEN" ]; then + echo -e "${GREEN}[*] Клонирование по токену (HTTPS)...${NC}" + # Подставляем токен в URL для доступа к приватному репо + if [[ "$REPO_HTTPS" == https://github.com/* ]]; then + CLONE_URL="https://${GITHUB_TOKEN}@github.com/${REPO_HTTPS#https://github.com/}" + else + CLONE_URL="$REPO_HTTPS" + fi + git clone --depth 1 --branch "$BRANCH" "$CLONE_URL" "$CLONE_DIR" +elif [ -n "$GIT_REPO_SSH" ]; then + echo -e "${GREEN}[*] Клонирование по SSH...${NC}" + git clone --depth 1 --branch "$BRANCH" "$GIT_REPO_SSH" "$CLONE_DIR" +else + echo -e "${GREEN}[*] Клонирование по SSH (ключ по умолчанию)...${NC}" + git clone --depth 1 --branch "$BRANCH" "$REPO_SSH" "$CLONE_DIR" +fi + +if [ ! -f "$CLONE_DIR/install_gotelegram_bot.sh" ]; then + echo -e "${RED}В репозитории не найден install_gotelegram_bot.sh${NC}" + exit 1 +fi + +chmod +x "$CLONE_DIR/install_gotelegram_bot.sh" +# Передаём токен в установочный скрипт, если использовали HTTPS (для совместимости) +export GITHUB_TOKEN +echo -e "${GREEN}[*] Запуск установки...${NC}" +exec sudo "$CLONE_DIR/install_gotelegram_bot.sh" diff --git a/gotelegram-bot/README.md b/gotelegram-bot/README.md new file mode 100644 index 0000000..dd97fc3 --- /dev/null +++ b/gotelegram-bot/README.md @@ -0,0 +1,70 @@ +# GoTelegram MTProxy Bot + +Telegram-бот для управления MTProxy на сервере — те же функции, что и у CLI `gotelegram`, но через бота. + +## Команды + +| Команда | Описание | +|--------|----------| +| `/start`, `/help` | Справка | +| `/install` | Установить или обновить прокси (выбор домена и порта) | +| `/status` | Статус и данные подключения (IP, порт, secret, ссылка) | +| `/link` | Только ссылка `tg://proxy` | +| `/restart` | Перезапустить контейнер | +| `/logs` | Последние логи контейнера | +| `/remove` | Удалить прокси | +| `/promo` | Промо хостинга | + +## Установка на сервер + +### Публичный репозиторий (одной командой) + +```bash +curl -sL https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install_gotelegram_bot.sh -o /tmp/install_gotelegram_bot.sh && sudo bash /tmp/install_gotelegram_bot.sh +``` + +При установке скрипт запросит **BOT_TOKEN** (получить у [@BotFather](https://t.me/BotFather)). + +### Закрытый репозиторий (установка по ключу) + +Для **приватного** репо используется клонирование по **SSH-ключу** или по **токену (PAT)**. Подробно: **[INSTALL_PRIVATE.md](../INSTALL_PRIVATE.md)** в корне репозитория. + +Кратко: +- **По SSH:** скопируйте `bootstrap_install.sh` на сервер, затем + `GIT_REPO_SSH=git@github.com:USER/REPO.git sudo bash bootstrap_install.sh` +- **По токену:** + `GITHUB_TOKEN=ghp_xxx GIT_REPO_HTTPS=https://github.com/USER/REPO.git sudo -E bash bootstrap_install.sh` +- Или клонируйте репо вручную и запустите: + `sudo ./install_gotelegram_bot.sh` + +### Локально (файлы уже рядом со скриптом) + +```bash +sudo ./install_gotelegram_bot.sh +``` + +## Конфигурация + +Файл: `/opt/gotelegram-bot/.env` + +- **BOT_TOKEN** — токен от @BotFather (обязательно). +- **ALLOWED_IDS** — опционально. Список ID пользователей через запятую; если не задан, бот доступен всем. + +После изменения `.env` перезапуск сервиса: + +```bash +sudo systemctl restart gotelegram-bot +``` + +## Требования на сервере + +- Linux (systemd), Docker, Python 3. +- Перед использованием бота на сервере должен быть установлен Docker (бот сам поднимает контейнер `nineseconds/mtg:2` по команде `/install`). + +## Управление сервисом + +```bash +sudo systemctl status gotelegram-bot +sudo systemctl restart gotelegram-bot +journalctl -u gotelegram-bot -f +``` diff --git a/gotelegram-bot/bot.py b/gotelegram-bot/bot.py new file mode 100644 index 0000000..4fa5b2a --- /dev/null +++ b/gotelegram-bot/bot.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +""" +GoTelegram MTProxy — Telegram-бот для управления MTProxy на сервере. +Функции: установка, статус, ссылка, удаление, рестарт, логи (по аналогии с CLI gotelegram). +""" + +import asyncio +import html +import os +import re +from pathlib import Path + +# Загрузка .env из текущей папки или /etc/gotelegram-bot +_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() +if _allowed: + try: + ALLOWED_IDS = set(int(x.strip()) for x in _allowed.split(",") if x.strip()) + except ValueError: + ALLOWED_IDS = None +else: + ALLOWED_IDS = None # все пользователи + +CONTAINER_NAME = "mtproto-proxy" +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 check_access(user_id: int) -> bool: + if ALLOWED_IDS is None: + return True + return user_id in ALLOWED_IDS + + +def _decode(data: bytes) -> str: + return (data or b"").decode("utf-8", errors="replace").strip() + + +async def run_cmd(*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: + stdout, stderr = 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(stdout), _decode(stderr) + + +async def docker_inspect(fmt: str) -> str: + code, out, err = await run_cmd( + "docker", "inspect", CONTAINER_NAME, "--format", fmt, timeout=10 + ) + if code != 0: + return "" + return out.strip() + + +async def get_ip() -> str: + for url in ["https://api.ipify.org", "https://icanhazip.com"]: + code, out, _ = await run_cmd("curl", "-s", "-4", "--max-time", "5", url, timeout=8) + if code == 0 and out: + m = re.search(r"([0-9]{1,3}\.){3}[0-9]{1,3}", out) + if m: + return m.group(0) + return "0.0.0.0" + + +async def proxy_is_running() -> bool: + code, out, _ = await run_cmd("docker", "ps", "--format", "{{.Names}}", timeout=10) + if code != 0: + return False + return CONTAINER_NAME in (out or "") + + +# --- Обработчики --- + +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + text = ( + "🚀 *GoTelegram MTProxy Bot*\n\n" + "Управление MTProxy на этом сервере.\n\n" + "Команды:\n" + "/install — установить или обновить прокси (выбор домена и порта)\n" + "/status — статус контейнера и данные подключения\n" + "/link — только ссылка tg://proxy\n" + "/restart — перезапустить прокси\n" + "/logs — последние логи\n" + "/remove — удалить прокси\n" + "/promo — промо хостинга\n" + "/help — эта справка" + ) + await update.message.reply_text(text, parse_mode="Markdown") + + +async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + if not await proxy_is_running(): + await update.message.reply_text( + "❌ Прокси не запущен.\nИспользуйте /install для установки." + ) + return + secret = await docker_inspect("{{range .Config.Cmd}}{{.}} {{end}}") + secret = secret.split()[-1] if secret else "" + port = await docker_inspect("{{range $p, $conf := .HostConfig.PortBindings}}{{(index $conf 0).HostPort}}{{end}}") + port = port or "443" + ip = await get_ip() + link = f"tg://proxy?server={ip}&port={port}&secret={secret}" + # HTML безопаснее для произвольного secret (экранируем) + text = ( + "✅ Прокси запущен\n\n" + f"IP: {html.escape(ip)}\n" + f"Port: {html.escape(port)}\n" + f"Secret: {html.escape(secret)}\n\n" + f"Ссылка (скопируйте):\n{html.escape(link)}" + ) + await update.message.reply_text(text, parse_mode="HTML") + + +async def cmd_link(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + if not await proxy_is_running(): + await update.message.reply_text("❌ Прокси не запущен. /install") + return + secret = await docker_inspect("{{range .Config.Cmd}}{{.}} {{end}}") + secret = secret.split()[-1] if secret else "" + port = await docker_inspect("{{range $p, $conf := .HostConfig.PortBindings}}{{(index $conf 0).HostPort}}{{end}}") + port = port or "443" + ip = await get_ip() + link = f"tg://proxy?server={ip}&port={port}&secret={secret}" + await update.message.reply_text(link) + + +async def cmd_remove(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + await update.message.reply_text("Удаляю прокси...") + await run_cmd("docker", "stop", CONTAINER_NAME, timeout=15) + await run_cmd("docker", "rm", CONTAINER_NAME, timeout=10) + if await proxy_is_running(): + await update.message.reply_text("⚠️ Не удалось удалить. Проверьте docker вручную.") + else: + await update.message.reply_text("✅ Прокси удалён.") + + +async def cmd_restart(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + if not await proxy_is_running(): + await update.message.reply_text("❌ Прокси не запущен. /install") + return + await update.message.reply_text("Перезапускаю...") + code, _, err = await run_cmd("docker", "restart", CONTAINER_NAME, timeout=30) + if code == 0: + await update.message.reply_text("✅ Прокси перезапущен.") + else: + await update.message.reply_text(f"❌ Ошибка: {err or 'unknown'}") + + +async def cmd_logs(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + if not await proxy_is_running(): + await update.message.reply_text("❌ Прокси не запущен. /install") + return + code, out, err = await run_cmd("docker", "logs", "--tail", "40", CONTAINER_NAME, timeout=15) + text = (out or "") + (("\n" + err) if err else "") + if not text: + text = "Нет вывода." + if len(text) > 4000: + text = text[-4000:] + await update.message.reply_text(f"
{html.escape(text)}
", parse_mode="HTML") + + +async def cmd_promo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + return + text = ( + "💰 *Хостинг со скидкой до -60%*\n" + f"Ссылка: {PROMO_LINK}\n\n" + "Промокоды: OFF60, antenka20, antenka6, antenka12\n\n" + f"Донат: {TIP_LINK}" + ) + await update.message.reply_text(text, parse_mode="Markdown") + + +# --- Установка: выбор домена и порта через инлайн-кнопки и диалог --- + +async def install_choice_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.effective_user or not update.message: + return + if not check_access(update.effective_user.id): + await update.message.reply_text("⛔ Доступ запрещён.") + 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) + await update.message.reply_text( + "Выберите домен для маскировки (Fake TLS):", + reply_markup=InlineKeyboardMarkup(buttons), + ) + + +async def install_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + query = update.callback_query + if not query or not update.effective_user: + return + await query.answer() + if not check_access(update.effective_user.id): + await query.edit_message_text("⛔ Доступ запрещён.") + return + data = query.data or "" + if 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 + domain = DOMAINS[idx] + context.user_data["gotelegram_domain"] = domain + kb = InlineKeyboardMarkup([ + [InlineKeyboardButton("443 (рекомендуется)", callback_data="port_443"), + InlineKeyboardButton("8443", callback_data="port_8443")], + ]) + await query.edit_message_text( + f"Домен: {domain}\n\nВыберите порт или введите свой (1-65535):", + reply_markup=kb, + ) + context.user_data["gotelegram_wait_port"] = True + return + if data == "port_443": + context.user_data["gotelegram_port"] = "443" + context.user_data["gotelegram_wait_port"] = False + await do_install(update, context) + return + if data == "port_8443": + context.user_data["gotelegram_port"] = "8443" + context.user_data["gotelegram_wait_port"] = False + await do_install(update, context) + return + + +async def do_install(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + domain = context.user_data.get("gotelegram_domain") or "google.com" + port = context.user_data.get("gotelegram_port") or "443" + if update.callback_query: + msg = update.callback_query.message + await msg.edit_text("⏳ Генерация secret и запуск контейнера...", reply_markup=None) + else: + msg = update.message + chat = msg.chat + if not update.callback_query: + await chat.send_message("⏳ Генерация secret и запуск контейнера...") + + # generate secret + code, secret_out, err = await run_cmd( + "docker", "run", "--rm", "nineseconds/mtg:2", "generate-secret", "--hex", domain, + timeout=30, + ) + if code != 0: + await chat.send_message(f"❌ Ошибка генерации secret: {err or secret_out}") + return + secret = (secret_out or "").strip().split()[-1] or secret_out.strip() + if not secret: + await chat.send_message("❌ Не удалось получить secret.") + return + + await run_cmd("docker", "stop", CONTAINER_NAME, timeout=15) + await run_cmd("docker", "rm", CONTAINER_NAME, timeout=10) + code, _, err = await run_cmd( + "docker", "run", "-d", "--name", CONTAINER_NAME, "--restart", "always", + "-p", f"{port}:{port}", + "nineseconds/mtg:2", "simple-run", "-n", "1.1.1.1", "-i", "prefer-ipv4", f"0.0.0.0:{port}", secret, + timeout=60, + ) + if code != 0: + await chat.send_message(f"❌ Ошибка запуска контейнера: {err}") + return + ip = await get_ip() + link = f"tg://proxy?server={ip}&port={port}&secret={secret}" + text = ( + "✅ Прокси установлен.\n" + f"Домен: {html.escape(domain)}, порт: {html.escape(port)}\n\n" + f"Ссылка:\n{html.escape(link)}" + ) + await chat.send_message(text, parse_mode="HTML") + context.user_data.pop("gotelegram_domain", None) + context.user_data.pop("gotelegram_port", None) + context.user_data.pop("gotelegram_wait_port", None) + + +async def handle_port_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + if not update.message: + return + if not context.user_data.get("gotelegram_wait_port"): + return + text = (update.message.text or "").strip() + if not re.match(r"^[0-9]+$", text): + return + port_num = int(text) + if not (1 <= port_num <= 65535): + await update.message.reply_text("Введите число от 1 до 65535.") + return + context.user_data["gotelegram_port"] = str(port_num) + context.user_data["gotelegram_wait_port"] = False + await do_install(update, context) + + +async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await start(update, context) + + +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", help_cmd)) + app.add_handler(CommandHandler("install", install_choice_domain)) + app.add_handler(CommandHandler("status", cmd_status)) + app.add_handler(CommandHandler("link", cmd_link)) + 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(install_callback)) + app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_port_message)) + app.run_polling(allowed_updates=Update.ALL_TYPES) + + +if __name__ == "__main__": + main() diff --git a/gotelegram-bot/config.example.env b/gotelegram-bot/config.example.env new file mode 100644 index 0000000..f4b44da --- /dev/null +++ b/gotelegram-bot/config.example.env @@ -0,0 +1,4 @@ +# Скопируйте в .env и заполните +BOT_TOKEN=your_bot_token_from_@BotFather +# Опционально: список ID пользователей с доступом (через запятую). Пусто = все. +# ALLOWED_IDS=123456789,987654321 diff --git a/gotelegram-bot/requirements.txt b/gotelegram-bot/requirements.txt new file mode 100644 index 0000000..ea4513c --- /dev/null +++ b/gotelegram-bot/requirements.txt @@ -0,0 +1 @@ +python-telegram-bot>=21.0 diff --git a/install_gotelegram_bot.sh b/install_gotelegram_bot.sh new file mode 100644 index 0000000..8960fae --- /dev/null +++ b/install_gotelegram_bot.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# Установка GoTelegram MTProxy Bot (по образцу gokaskad). +# Запуск: curl -sL URL/install_gotelegram_bot.sh -o /tmp/install_gotelegram_bot.sh && sudo bash /tmp/install_gotelegram_bot.sh +# Или с токеном GitHub (приватный репо): curl -sL -H "Authorization: token YOUR_GITHUB_TOKEN" URL -o /tmp/install_gotelegram_bot.sh && sudo bash /tmp/install_gotelegram_bot.sh + +set -e +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Запустите скрипт с sudo.${NC}" + exit 1 +fi + +BOT_DIR="/opt/gotelegram-bot" +SERVICE_NAME="gotelegram-bot" +REPO_URL="${GOTETELEGRAM_BOT_REPO:-https://github.com/anten-ka/gotelegram_pro}" +# Для приватного репо задайте GITHUB_TOKEN перед запуском или в одной команде: +# GITHUB_TOKEN=ghp_xxx curl -sL -H "Authorization: token $GITHUB_TOKEN" ... + +echo -e "${GREEN}[*] Установка GoTelegram Bot...${NC}" + +# Зависимости +if ! command -v python3 &>/dev/null; then + if command -v apt-get &>/dev/null; then + apt-get update && apt-get install -y python3 python3-pip python3-venv + elif command -v dnf &>/dev/null; then + dnf install -y python3 python3-pip python3-virtualenv 2>/dev/null || dnf install -y python3 python3-pip + elif command -v yum &>/dev/null; then + yum install -y python3 python3-pip + else + echo -e "${RED}Установите python3 вручную.${NC}" + exit 1 + fi +fi +if ! command -v docker &>/dev/null; then + echo -e "${YELLOW}[!] Docker не найден. Установите Docker для работы /install.${NC}" +fi + +# Каталог бота +mkdir -p "$BOT_DIR" +cd "$BOT_DIR" + +# Откуда брать файлы: локальная папка (клонированный репо) или скачивание +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ]; then + echo -e "${GREEN}[*] Копирование файлов из $SCRIPT_DIR/gotelegram-bot${NC}" + cp -r "$SCRIPT_DIR/gotelegram-bot"/* "$BOT_DIR/" +else + # Приватный репо: клонируем по токену (raw URL с токеном для приватного не работает) + if [ -n "$GITHUB_TOKEN" ] && command -v git &>/dev/null; then + echo -e "${GREEN}[*] Клонирование приватного репозитория (HTTPS + токен)...${NC}" + CLONE_DIR="/tmp/gotelegram_pro_fetch_$$" + trap "rm -rf '$CLONE_DIR'" EXIT + REPO_PATH="${REPO_URL#https://}" + REPO_PATH="${REPO_PATH#http://}" + git clone --depth 1 --branch "${GIT_BRANCH:-main}" "https://${GITHUB_TOKEN}@${REPO_PATH}" "$CLONE_DIR" 2>/dev/null || { + echo -e "${RED}Клонирование не удалось. Проверьте GITHUB_TOKEN и доступ к репо.${NC}" + exit 1 + } + if [ -f "$CLONE_DIR/gotelegram-bot/bot.py" ]; then + cp -r "$CLONE_DIR/gotelegram-bot"/* "$BOT_DIR/" + fi + rm -rf "$CLONE_DIR" + trap - EXIT + fi + # Если файлов всё ещё нет — пробуем raw (только для публичного репо) + if [ ! -f "$BOT_DIR/bot.py" ]; then + echo -e "${YELLOW}[*] Скачивание файлов из репозитория (публичный доступ)...${NC}" + for f in bot.py requirements.txt config.example.env; do + if [ -n "$GITHUB_TOKEN" ]; then + curl -sL -f -H "Authorization: token $GITHUB_TOKEN" \ + "$REPO_URL/raw/main/gotelegram-bot/$f" -o "$BOT_DIR/$f" 2>/dev/null || true + fi + [ ! -f "$BOT_DIR/$f" ] && curl -sL -f "$REPO_URL/raw/main/gotelegram-bot/$f" -o "$BOT_DIR/$f" 2>/dev/null || true + done + fi + if [ ! -f "$BOT_DIR/bot.py" ]; then + echo -e "${RED}Не удалось получить файлы бота.${NC}" + echo -e " Для приватного репо: задайте GITHUB_TOKEN и убедитесь, что установлен git." + echo -e " Или клонируйте репо вручную и запустите: sudo ./install_gotelegram_bot.sh" + exit 1 + fi +fi + +# venv и зависимости +if [ ! -d "$BOT_DIR/venv" ]; then + python3 -m venv "$BOT_DIR/venv" +fi +"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q + +# Конфиг +if [ ! -f "$BOT_DIR/.env" ]; then + echo -e "${YELLOW}Введите BOT_TOKEN от @BotFather:${NC}" + TOKEN="" + while [ -z "$TOKEN" ]; do + read -r TOKEN + TOKEN=$(echo "$TOKEN" | tr -d '[:space:]') + [ -z "$TOKEN" ] && echo -e "${RED}Токен не может быть пустым. Повторите:${NC}" + done + { + echo "BOT_TOKEN=$TOKEN" + [ -f "$BOT_DIR/config.example.env" ] && grep -v "^BOT_TOKEN=" "$BOT_DIR/config.example.env" | grep -v "^#" + } > "$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 + +[Service] +Type=simple +WorkingDirectory=$BOT_DIR +ExecStart=$BOT_DIR/venv/bin/python $BOT_DIR/bot.py +Restart=always +RestartSec=5 +Environment=PATH=$BOT_DIR/venv/bin:/usr/bin + +[Install] +WantedBy=multi-user.target +EOF + +systemctl daemon-reload +systemctl enable "$SERVICE_NAME" +systemctl restart "$SERVICE_NAME" 2>/dev/null || systemctl start "$SERVICE_NAME" +echo -e "${GREEN}[*] Сервис $SERVICE_NAME установлен и запущен.${NC}" +echo -e "Проверка: systemctl status $SERVICE_NAME" +echo -e "Логи: journalctl -u $SERVICE_NAME -f" +echo -e "Настройки: $BOT_DIR/.env (BOT_TOKEN, ALLOWED_IDS)" +exit 0 diff --git a/push_to_github.bat b/push_to_github.bat new file mode 100644 index 0000000..e232da9 --- /dev/null +++ b/push_to_github.bat @@ -0,0 +1,54 @@ +@echo off +chcp 65001 >nul +setlocal +cd /d "%~dp0" + +set REMOTE=git@github.com:anten-ka/gotelegram_pro.git +set BRANCH=main + +echo [*] Репозиторий: https://github.com/anten-ka/gotelegram_pro +echo [*] Папка: %CD% +echo. + +where git >nul 2>nul +if errorlevel 1 ( + echo Ошибка: git не найден. Установите Git for Windows. + pause + exit /b 1 +) + +if not exist ".git" ( + echo [*] Инициализация репозитория... + git init +) + +git remote remove origin 2>nul +git remote add origin %REMOTE% +git branch -M %BRANCH% +git add . +git status + +set /p CONFIRM="Закоммитить и отправить на GitHub? (y/n): " +if /i not "%CONFIRM%"=="y" ( + echo Отменено. + pause + exit /b 0 +) + +git commit -m "GoTelegram MTProxy + bot (gotelegram_pro)" 2>nul || git commit --allow-empty -m "Update" +echo [*] Отправка на origin/%BRANCH%... +git push -u origin %BRANCH% + +if errorlevel 1 ( + echo. + echo Не удалось отправить. Проверьте: + echo 1. Репо создан на GitHub: https://github.com/anten-ka/gotelegram_pro + echo 2. Доступ по SSH: ssh -T git@github.com + echo 3. Или настройте токен: git remote set-url origin https://ВАШ_ТОКЕН@github.com/anten-ka/gotelegram_pro.git + pause + exit /b 1 +) + +echo. +echo Готово: https://github.com/anten-ka/gotelegram_pro +pause