mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-25 18:16:04 +00:00
Compare commits
29 Commits
v1.0
...
a21d2ebea2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a21d2ebea2 | ||
|
|
96cbd243d9 | ||
|
|
3f136ec8a0 | ||
|
|
a24d64d33c | ||
|
|
52912e0ead | ||
|
|
0dae922d1b | ||
|
|
6ec2123f83 | ||
|
|
364501d66d | ||
|
|
f445f7a27e | ||
|
|
6e32ca9d12 | ||
|
|
7f81c21d8e | ||
|
|
16ef3564f9 | ||
|
|
75a84e209b | ||
|
|
d6045ace0c | ||
|
|
64da5de1ae | ||
|
|
a5347889bd | ||
|
|
3938a4ca3b | ||
|
|
19beb2b2df | ||
|
|
c2d95b6c2f | ||
|
|
1293b04430 | ||
|
|
cc3f547273 | ||
|
|
84bc15090a | ||
|
|
c75ea15b31 | ||
|
|
fee25f191e | ||
|
|
fe9e5fa019 | ||
|
|
13cca51f5f | ||
|
|
55134f4864 | ||
|
|
de60f710f5 | ||
|
|
4985ec3679 |
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,18 +0,0 @@
|
|||||||
# Секреты и конфиг с токенами
|
|
||||||
.env
|
|
||||||
*.env.local
|
|
||||||
config.local.env
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
*.egg-info/
|
|
||||||
.eggs/
|
|
||||||
|
|
||||||
# Временные и системные
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
*.log
|
|
||||||
*.tmp
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# Предложения по улучшению 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-бота.
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# Установка по ссылке (ключ в команде)
|
|
||||||
|
|
||||||
Чтобы клиент или вы могли ставить бота **одной командой**, в неё подставляется ключ (GitHub Personal Access Token). «Ссылка» = эта одна строка, которую копируют и вставляют в терминал сервера.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Одна команда с ключом (готовый шаблон)
|
|
||||||
|
|
||||||
Подставьте свой **GITHUB_TOKEN** (один раз) в команду ниже и сохраните получившуюся строку. Её можно:
|
|
||||||
- отправить клиенту (пусть вставит в SSH-сессию и запустит);
|
|
||||||
- сохранить у себя как «ссылку для установки».
|
|
||||||
|
|
||||||
**Шаблон (замените `ghp_ВАШ_ТОКЕН` на свой токен):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GITHUB_TOKEN=ghp_ВАШ_ТОКЕН bash -c 'git clone --depth 1 "https://${GITHUB_TOKEN}@github.com/anten-ka/gotelegram_pro.git" /tmp/gp && sudo -E /tmp/gp/install_gotelegram_bot.sh'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Важно:** переменная `GITHUB_TOKEN` задаётся перед `bash -c`, поэтому во внутренней команде `${GITHUB_TOKEN}` подставится на сервере при выполнении. Токен попадёт в `git clone` и в установочный скрипт.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Через скрипт из репозитория (то же самое, но через raw-ссылку)
|
|
||||||
|
|
||||||
Если репозиторий **публичный**, можно дать такую «ссылку» (подставьте свой токен):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GITHUB_TOKEN=ghp_ВАШ_ТОКЕН curl -sL https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install_by_key.sh | sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
Если репо **приватный**, raw-файл без доступа не откроется, поэтому используйте вариант 1 выше (клонирование по токену в одной команде).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Короткая «ссылка» через публичный Gist (приватный репо)
|
|
||||||
|
|
||||||
Чтобы команда была короче и не светить репо, можно выложить **только** маленький скрипт установки в публичный Gist. Тогда «установка по ссылке» = одна команда с ключом, а скрипт берётся по публичной ссылке.
|
|
||||||
|
|
||||||
### Шаг 1: создать Gist
|
|
||||||
|
|
||||||
1. Перейдите на https://gist.github.com
|
|
||||||
2. Создайте новый Gist (публичный).
|
|
||||||
3. Имя файла: `install_gotelegram_pro.sh`
|
|
||||||
4. Вставьте содержимое (подставьте свой репо, если другой):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
T="${GITHUB_TOKEN:?Задайте GITHUB_TOKEN}"
|
|
||||||
R="https://${T}@github.com/anten-ka/gotelegram_pro.git"
|
|
||||||
D="/tmp/gp_$$"
|
|
||||||
trap "rm -rf $D" EXIT
|
|
||||||
git clone --depth 1 --branch main "$R" "$D"
|
|
||||||
sudo -E "$D/install_gotelegram_bot.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Нажмите **Create public gist** и скопируйте **Raw**-ссылку на файл (например `https://gist.githubusercontent.com/USER/ID/raw/xxx/install_gotelegram_pro.sh`).
|
|
||||||
|
|
||||||
### Шаг 2: «ссылка» для установки
|
|
||||||
|
|
||||||
Вы даёте клиенту одну команду (вместо `ghp_ВАШ_ТОКЕН` — свой токен, вместо `ССЫЛКА_НА_RAW_GIST` — ссылку из шага 1):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GITHUB_TOKEN=ghp_ВАШ_ТОКЕН curl -sL ССЫЛКА_НА_RAW_GIST | sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx curl -sL https://gist.githubusercontent.com/anten-ka/xxxxxxxx/raw/xxx/install_gotelegram_pro.sh | sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
Клиент только копирует эту строку, вставляет в терминал сервера и запускает — установка идёт по ключу из команды.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Безопасность
|
|
||||||
|
|
||||||
- Токен в команде виден в истории shell на сервере. Для разовой установки это допустимо; после установки можно отозвать токен или использовать токен с коротким сроком жизни.
|
|
||||||
- Не публикуйте готовую команду с реальным токеном в открытый доступ (сайт, чат, репо). Делитесь ею только с тем, кому доверяете установку на сервер.
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
# Установка из закрытого GitHub-репозитория (по ключу)
|
|
||||||
|
|
||||||
Инструкция для размещения проекта в **приватном** репозитории и установки на сервер **по SSH-ключу** или **по токену (PAT)**.
|
|
||||||
|
|
||||||
**Установка одной командой с ключом («ключ в ссылке»):** см. **[INSTALL_LINK.md](INSTALL_LINK.md)** — одна строка с подставленным токеном для копирования в терминал.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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` подставьте свой логин и имя репозитория.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
═══════════════════════════════════════════════════════════════
|
|
||||||
Как выложить проект в 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 — никому (и ни в какой чат) их передавать не нужно.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -1,70 +1,147 @@
|
|||||||
# GoTelegram MTProxy Bot
|
# GoTelegram v2.2 Bot
|
||||||
|
|
||||||
Telegram-бот для управления MTProxy на сервере — те же функции, что и у CLI `gotelegram`, но через бота.
|
Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux servers.
|
||||||
|
|
||||||
## Команды
|
## Features
|
||||||
|
|
||||||
| Команда | Описание |
|
- **Complete CLI Feature Parity** - All menu items from CLI version
|
||||||
|--------|----------|
|
- Install (Quick/Stealth modes)
|
||||||
| `/start`, `/help` | Справка |
|
- Status monitoring
|
||||||
| `/install` | Установить или обновить прокси (выбор домена и порта) |
|
- Proxy link generation
|
||||||
| `/status` | Статус и данные подключения (IP, порт, secret, ссылка) |
|
- Share with QR codes
|
||||||
| `/link` | Только ссылка `tg://proxy` |
|
- Service restart
|
||||||
| `/restart` | Перезапустить контейнер |
|
- Logs viewing
|
||||||
| `/logs` | Последние логи контейнера |
|
- Mode/template changes
|
||||||
| `/remove` | Удалить прокси |
|
- Backup/restore
|
||||||
| `/promo` | Промо хостинга |
|
- telemt updates
|
||||||
|
- Website/SSL management
|
||||||
|
- Remove installation
|
||||||
|
- Promotional links
|
||||||
|
|
||||||
## Установка на сервер
|
- **Template Browsing** - Browse categories → templates → preview → install
|
||||||
|
- **V1 Migration** - Detects old mtg Docker container and offers migration
|
||||||
|
- **Access Control** - ALLOWED_IDS from .env
|
||||||
|
- **Async/Await** - Full async support via python-telegram-bot v21+
|
||||||
|
- **Inline Keyboards** - Modern UI with callback-based navigation
|
||||||
|
- **Shell Integration** - Executes system commands via asyncio subprocess
|
||||||
|
- **Error Handling** - Production-ready error handling
|
||||||
|
|
||||||
### Публичный репозиторий (одной командой)
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Linux system with systemd
|
||||||
|
- telemt installed and running
|
||||||
|
- Telegram Bot Token from @BotFather
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
```bash
|
```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
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
При установке скрипт запросит **BOT_TOKEN** (получить у [@BotFather](https://t.me/BotFather)).
|
2. Create .env file:
|
||||||
|
|
||||||
### Закрытый репозиторий (установка по ключу)
|
|
||||||
|
|
||||||
Для **приватного** репо используется клонирование по **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
|
```bash
|
||||||
sudo ./install_gotelegram_bot.sh
|
cp config.example.env .env
|
||||||
|
# Edit .env and set your BOT_TOKEN
|
||||||
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
## Конфигурация
|
3. (Optional) Restrict access to specific users:
|
||||||
|
|
||||||
Файл: `/opt/gotelegram-bot/.env`
|
|
||||||
|
|
||||||
- **BOT_TOKEN** — токен от @BotFather (обязательно).
|
|
||||||
- **ALLOWED_IDS** — опционально. Список ID пользователей через запятую; если не задан, бот доступен всем.
|
|
||||||
|
|
||||||
После изменения `.env` перезапуск сервиса:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart gotelegram-bot
|
# Edit .env and uncomment ALLOWED_IDS
|
||||||
|
# ALLOWED_IDS=123456789,987654321
|
||||||
```
|
```
|
||||||
|
|
||||||
## Требования на сервере
|
### Running the Bot
|
||||||
|
|
||||||
- Linux (systemd), Docker, Python 3.
|
|
||||||
- Перед использованием бота на сервере должен быть установлен Docker (бот сам поднимает контейнер `nineseconds/mtg:2` по команде `/install`).
|
|
||||||
|
|
||||||
## Управление сервисом
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl status gotelegram-bot
|
python3 bot.py
|
||||||
sudo systemctl restart gotelegram-bot
|
|
||||||
journalctl -u gotelegram-bot -f
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For systemd service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[Unit]
|
||||||
|
Description=GoTelegram Bot
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=gotelegram
|
||||||
|
WorkingDirectory=/opt/gotelegram/bot
|
||||||
|
ExecStart=/usr/bin/python3 /opt/gotelegram/bot/bot.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### .env Variables
|
||||||
|
|
||||||
|
- `BOT_TOKEN` - Telegram bot token (required)
|
||||||
|
- `ALLOWED_IDS` - Comma-separated user IDs (optional, all users allowed if empty)
|
||||||
|
|
||||||
|
### System Paths
|
||||||
|
|
||||||
|
- `GOTELEGRAM_CONFIG` - `/opt/gotelegram/config.json`
|
||||||
|
- `TELEMT_CONFIG` - `/etc/telemt/config.toml`
|
||||||
|
- `TELEMT_SERVICE` - `telemt` (systemd service name)
|
||||||
|
- `WEBSITE_ROOT` - `/var/www/gotelegram-site`
|
||||||
|
- `BACKUP_DIR` - `/opt/gotelegram/backups`
|
||||||
|
- `TEMPLATES_CATALOG` - `/opt/gotelegram/templates_catalog.json`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Single File Design
|
||||||
|
All functionality in one `bot.py` for simplicity and ease of deployment.
|
||||||
|
|
||||||
|
### Command Handlers
|
||||||
|
- `/start` - Main menu
|
||||||
|
- `/help` - Help text
|
||||||
|
- `/status` - Quick status
|
||||||
|
- `/logs` - Recent logs
|
||||||
|
|
||||||
|
### Callback Handlers
|
||||||
|
Organized by feature:
|
||||||
|
- Installation (quick/stealth modes)
|
||||||
|
- Status monitoring
|
||||||
|
- Backup/restore
|
||||||
|
- SSL management
|
||||||
|
- Updates
|
||||||
|
- Removal
|
||||||
|
|
||||||
|
### Shell Integration
|
||||||
|
Async subprocess wrapper:
|
||||||
|
```python
|
||||||
|
code, stdout, stderr = await sh("command", "arg1", "arg2")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Callback Data Convention
|
||||||
|
|
||||||
|
- `menu_*` - Menu items
|
||||||
|
- `install_mode_*` - Install options
|
||||||
|
- `quick_dom_*` - Domain selection
|
||||||
|
- `stealth_cat_*` - Template categories
|
||||||
|
- `stealth_tpl_*` - Template selection
|
||||||
|
- `stealth_confirm_*` - Confirm installation
|
||||||
|
- `backup_*` - Backup operations
|
||||||
|
- `ssl_*` - SSL operations
|
||||||
|
- `restore_backup_*` - Restore operations
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- **telemt** - MTProxy engine foundation
|
||||||
|
- **HTML5UP** - Beautiful web templates
|
||||||
|
- **Learning Zone** - Educational resources
|
||||||
|
- **Start Bootstrap** - Bootstrap framework
|
||||||
|
- **Community** - Your feedback and support
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GoTelegram v2.2 - Open source community project
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,9 @@
|
|||||||
# Скопируйте в .env и заполните
|
# GoTelegram v2.2 Bot Configuration
|
||||||
|
# Copy this to .env and fill in your values
|
||||||
|
|
||||||
|
# Telegram Bot Token from @BotFather
|
||||||
BOT_TOKEN=your_bot_token_from_@BotFather
|
BOT_TOKEN=your_bot_token_from_@BotFather
|
||||||
# Опционально: список ID пользователей с доступом (через запятую). Пусто = все.
|
|
||||||
|
# Comma-separated list of allowed Telegram user IDs
|
||||||
|
# Leave empty to allow all users
|
||||||
# ALLOWED_IDS=123456789,987654321
|
# ALLOWED_IDS=123456789,987654321
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
python-telegram-bot>=21.0
|
python-telegram-bot>=21.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
toml>=0.10.2
|
||||||
|
|||||||
2393
install.sh
Normal file → Executable file
2393
install.sh
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Установка по ключу «в ссылке»: одна команда с токеном.
|
|
||||||
# Вариант 1 — подставьте свой GITHUB_TOKEN в команду и дайте её клиенту:
|
|
||||||
# GITHUB_TOKEN=ghp_ВАШ_ТОКЕН bash -c 'curl -sL https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install_by_key.sh | sudo -E bash'
|
|
||||||
# Вариант 2 — если этот файл выложен в публичный Gist, «ссылка» будет короче (см. INSTALL_LINK.md).
|
|
||||||
|
|
||||||
set -e
|
|
||||||
REPO="${GOTETELEGRAM_REPO:-https://github.com/anten-ka/gotelegram_pro.git}"
|
|
||||||
BRANCH="${GIT_BRANCH:-main}"
|
|
||||||
DIR="/tmp/gotelegram_pro_$$"
|
|
||||||
trap "rm -rf $DIR" EXIT
|
|
||||||
|
|
||||||
if [ -z "$GITHUB_TOKEN" ]; then
|
|
||||||
echo "Задайте GITHUB_TOKEN. Пример:"
|
|
||||||
echo " GITHUB_TOKEN=ghp_xxx curl -sL ВАША_ССЫЛКА | sudo -E bash"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[*] Клонирование репозитория..."
|
|
||||||
git clone --depth 1 --branch "$BRANCH" "https://${GITHUB_TOKEN}@${REPO#https://}" "$DIR"
|
|
||||||
echo "[*] Запуск установки..."
|
|
||||||
exec sudo -E "$DIR/install_gotelegram_bot.sh"
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Готовая ссылка в формате kaskad
|
|
||||||
# Замените YOUR_GITHUB_TOKEN на свой токен (https://github.com/settings/tokens, право repo)
|
|
||||||
# Токен нужен дважды: в curl (скачать скрипт) и в конце (чтобы скрипт скачал файлы бота из приватного репо).
|
|
||||||
|
|
||||||
curl -sL -H "Authorization: token YOUR_GITHUB_TOKEN" https://raw.githubusercontent.com/anten-ka/gotelegram_pro/main/install.sh -o /usr/local/bin/gotelegram && chmod +x /usr/local/bin/gotelegram && systemctl restart gotelegram-bot 2>/dev/null; gotelegram YOUR_GITHUB_TOKEN
|
|
||||||
341
lib/backup.sh
Executable file
341
lib/backup.sh
Executable file
@@ -0,0 +1,341 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.2 — Бекап и восстановление конфигурации
|
||||||
|
|
||||||
|
# ── Создание бекапа ──────────────────────────────────────────────────────────
|
||||||
|
create_backup() {
|
||||||
|
local password="$1"
|
||||||
|
local output_dir="${2:-$BACKUP_DIR}"
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||||
|
local backup_name="gotelegram_backup_${timestamp}"
|
||||||
|
local tmp_dir="/tmp/${backup_name}"
|
||||||
|
|
||||||
|
mkdir -p "$tmp_dir" "$output_dir"
|
||||||
|
|
||||||
|
# Собираем файлы
|
||||||
|
log_info "Собираю конфигурацию..."
|
||||||
|
|
||||||
|
# telemt конфиг
|
||||||
|
if [ -f "$TELEMT_CONFIG" ]; then
|
||||||
|
cp "$TELEMT_CONFIG" "$tmp_dir/config.toml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GoTelegram конфиг
|
||||||
|
if [ -f "$GOTELEGRAM_CONFIG" ]; then
|
||||||
|
cp "$GOTELEGRAM_CONFIG" "$tmp_dir/gotelegram.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# nginx конфиг (stealth mode)
|
||||||
|
if [ -f "$NGINX_SITE_CONF" ]; then
|
||||||
|
cp "$NGINX_SITE_CONF" "$tmp_dir/nginx.conf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSL сертификаты
|
||||||
|
local domain
|
||||||
|
domain=$(config_get domain 2>/dev/null)
|
||||||
|
if [ -n "$domain" ] && [ -d "/etc/letsencrypt/live/$domain" ]; then
|
||||||
|
mkdir -p "$tmp_dir/certs"
|
||||||
|
cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$tmp_dir/certs/" 2>/dev/null
|
||||||
|
cp "/etc/letsencrypt/live/$domain/privkey.pem" "$tmp_dir/certs/" 2>/dev/null
|
||||||
|
log_dim "SSL сертификаты включены"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Шаблон сайта (если есть)
|
||||||
|
if [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ]; then
|
||||||
|
mkdir -p "$tmp_dir/site"
|
||||||
|
cp -r "$WEBSITE_ROOT"/* "$tmp_dir/site/"
|
||||||
|
log_dim "Шаблон сайта включён"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Метаданные
|
||||||
|
local ip mode engine
|
||||||
|
ip=$(get_server_ip)
|
||||||
|
mode=$(config_get mode 2>/dev/null || echo "unknown")
|
||||||
|
engine=$(config_get engine 2>/dev/null || echo "telemt")
|
||||||
|
|
||||||
|
cat > "$tmp_dir/metadata.json" << EOMETA
|
||||||
|
{
|
||||||
|
"backup_version": "1.0",
|
||||||
|
"gotelegram_version": "$GOTELEGRAM_VERSION",
|
||||||
|
"created_at": "$(date -Iseconds)",
|
||||||
|
"hostname": "$(hostname)",
|
||||||
|
"ip": "$ip",
|
||||||
|
"engine": "$engine",
|
||||||
|
"mode": "$mode",
|
||||||
|
"port": $(config_get port 2>/dev/null || echo "443"),
|
||||||
|
"domain": "$(config_get domain 2>/dev/null)"
|
||||||
|
}
|
||||||
|
EOMETA
|
||||||
|
|
||||||
|
# Архивируем
|
||||||
|
local tar_file="/tmp/${backup_name}.tar.gz"
|
||||||
|
if ! tar czf "$tar_file" -C /tmp "$backup_name" 2>/dev/null; then
|
||||||
|
log_error "Ошибка создания архива"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
rm -f "$tar_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$tar_file" ]; then
|
||||||
|
log_error "Архив не создан"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Шифруем если задан пароль
|
||||||
|
local final_file=""
|
||||||
|
if [ -n "$password" ]; then
|
||||||
|
final_file="${output_dir}/${backup_name}.tar.gz.enc"
|
||||||
|
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$tar_file" -out "$final_file" -pass "pass:${password}" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Ошибка шифрования"
|
||||||
|
rm -f "$tar_file"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rm -f "$tar_file"
|
||||||
|
log_success "Бекап зашифрован (AES-256-CBC)"
|
||||||
|
else
|
||||||
|
final_file="${output_dir}/${backup_name}.tar.gz"
|
||||||
|
mv "$tar_file" "$final_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SHA256 подпись
|
||||||
|
sha256sum "$final_file" > "${final_file}.sha256" 2>/dev/null
|
||||||
|
|
||||||
|
# Очистка
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
|
||||||
|
local size
|
||||||
|
size=$(du -h "$final_file" | cut -f1)
|
||||||
|
log_success "Бекап создан: $final_file ($size)"
|
||||||
|
echo "$final_file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Восстановление из бекапа ────────────────────────────────────────────────
|
||||||
|
restore_backup() {
|
||||||
|
local backup_file="$1"
|
||||||
|
local password="$2"
|
||||||
|
|
||||||
|
if [ ! -f "$backup_file" ]; then
|
||||||
|
log_error "Файл не найден: $backup_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp_dir="/tmp/gotelegram_restore_$$"
|
||||||
|
mkdir -p "$tmp_dir"
|
||||||
|
|
||||||
|
# Расшифровываем если нужно
|
||||||
|
local tar_file=""
|
||||||
|
if echo "$backup_file" | grep -q '\.enc$'; then
|
||||||
|
if [ -z "$password" ]; then
|
||||||
|
echo -ne " Введите пароль от бекапа: "
|
||||||
|
read -rs password
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
tar_file="/tmp/gotelegram_restore_$$.tar.gz"
|
||||||
|
openssl enc -aes-256-cbc -d -pbkdf2 -in "$backup_file" -out "$tar_file" -pass "pass:${password}" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Неверный пароль или повреждённый файл"
|
||||||
|
rm -rf "$tmp_dir" "$tar_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
tar_file="$backup_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Распаковываем
|
||||||
|
tar xzf "$tar_file" -C "$tmp_dir" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Ошибка распаковки архива"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Находим папку бекапа
|
||||||
|
local backup_dir
|
||||||
|
backup_dir=$(find "$tmp_dir" -maxdepth 1 -type d -name "gotelegram_backup_*" | head -1)
|
||||||
|
[ -z "$backup_dir" ] && backup_dir="$tmp_dir"
|
||||||
|
|
||||||
|
# Проверяем метаданные
|
||||||
|
if [ -f "$backup_dir/metadata.json" ]; then
|
||||||
|
local bk_version bk_mode bk_ip
|
||||||
|
bk_version=$(jq -r '.gotelegram_version // "unknown"' "$backup_dir/metadata.json")
|
||||||
|
bk_mode=$(jq -r '.mode // "unknown"' "$backup_dir/metadata.json")
|
||||||
|
bk_ip=$(jq -r '.ip // "unknown"' "$backup_dir/metadata.json")
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${WHITE}📦 Бекап:${NC}"
|
||||||
|
echo -e " Версия: $bk_version | Режим: $bk_mode | IP: $bk_ip"
|
||||||
|
echo -e " Дата: $(jq -r '.created_at' "$backup_dir/metadata.json")"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! confirm "Восстановить конфигурацию? Текущие настройки будут перезаписаны."; then
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Останавливаем сервисы
|
||||||
|
stop_telemt 2>/dev/null
|
||||||
|
systemctl stop nginx 2>/dev/null
|
||||||
|
|
||||||
|
# Восстанавливаем telemt конфиг
|
||||||
|
if [ -f "$backup_dir/config.toml" ]; then
|
||||||
|
mkdir -p /etc/telemt
|
||||||
|
cp "$backup_dir/config.toml" "$TELEMT_CONFIG"
|
||||||
|
chmod 600 "$TELEMT_CONFIG"
|
||||||
|
log_success "telemt конфиг восстановлен"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Восстанавливаем GoTelegram конфиг
|
||||||
|
if [ -f "$backup_dir/gotelegram.json" ]; then
|
||||||
|
mkdir -p "$GOTELEGRAM_DIR"
|
||||||
|
cp "$backup_dir/gotelegram.json" "$GOTELEGRAM_CONFIG"
|
||||||
|
log_success "GoTelegram конфиг восстановлен"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Восстанавливаем nginx конфиг
|
||||||
|
if [ -f "$backup_dir/nginx.conf" ]; then
|
||||||
|
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
|
||||||
|
cp "$backup_dir/nginx.conf" "$NGINX_SITE_CONF"
|
||||||
|
ln -sf "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
|
||||||
|
log_success "nginx конфиг восстановлен"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Восстанавливаем SSL
|
||||||
|
if [ -d "$backup_dir/certs" ]; then
|
||||||
|
local domain
|
||||||
|
domain=$(config_get domain 2>/dev/null)
|
||||||
|
if [ -n "$domain" ]; then
|
||||||
|
local cert_dir="/etc/letsencrypt/live/$domain"
|
||||||
|
mkdir -p "$cert_dir"
|
||||||
|
cp "$backup_dir/certs/"* "$cert_dir/" 2>/dev/null
|
||||||
|
log_success "SSL сертификаты восстановлены"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Восстанавливаем шаблон сайта
|
||||||
|
if [ -d "$backup_dir/site" ]; then
|
||||||
|
mkdir -p "$WEBSITE_ROOT"
|
||||||
|
cp -r "$backup_dir/site"/* "$WEBSITE_ROOT/"
|
||||||
|
chown -R www-data:www-data "$WEBSITE_ROOT" 2>/dev/null
|
||||||
|
log_success "Шаблон сайта восстановлен"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Запускаем сервисы
|
||||||
|
if is_telemt_installed; then
|
||||||
|
start_telemt
|
||||||
|
fi
|
||||||
|
systemctl start nginx 2>/dev/null
|
||||||
|
|
||||||
|
# Очистка
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
[ "$tar_file" != "$backup_file" ] && rm -f "$tar_file"
|
||||||
|
|
||||||
|
log_success "Восстановление завершено!"
|
||||||
|
show_proxy_info
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Список бекапов ───────────────────────────────────────────────────────────
|
||||||
|
list_backups() {
|
||||||
|
if [ ! -d "$BACKUP_DIR" ] || [ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]; then
|
||||||
|
log_info "Бекапов нет"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${WHITE}📦 Доступные бекапы:${NC}"
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}"
|
||||||
|
|
||||||
|
local i=1
|
||||||
|
for f in "$BACKUP_DIR"/gotelegram_backup_*.tar.gz*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
[[ "$f" == *.sha256 ]] && continue
|
||||||
|
local size date_str name
|
||||||
|
size=$(du -h "$f" | cut -f1)
|
||||||
|
name=$(basename "$f")
|
||||||
|
date_str=$(echo "$name" | grep -oE '[0-9]{8}_[0-9]{6}' | head -1)
|
||||||
|
local encrypted=""
|
||||||
|
[[ "$f" == *.enc ]] && encrypted=" 🔒"
|
||||||
|
echo -e " ${CYAN}${i})${NC} ${name} (${size})${encrypted}"
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Очистка старых бекапов ───────────────────────────────────────────────────
|
||||||
|
cleanup_old_backups() {
|
||||||
|
local keep="${1:-5}"
|
||||||
|
local count
|
||||||
|
count=$(find "$BACKUP_DIR" -name "gotelegram_backup_*.tar.gz*" ! -name "*.sha256" 2>/dev/null | wc -l)
|
||||||
|
|
||||||
|
if [ "$count" -gt "$keep" ]; then
|
||||||
|
local to_delete=$((count - keep))
|
||||||
|
find "$BACKUP_DIR" -name "gotelegram_backup_*.tar.gz*" ! -name "*.sha256" 2>/dev/null | sort | head -n "$to_delete" | while read -r f; do
|
||||||
|
rm -f "$f" "${f}.sha256"
|
||||||
|
done
|
||||||
|
log_dim "Удалено $to_delete старых бекапов (оставлено $keep)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Интерактивный бекап ──────────────────────────────────────────────────────
|
||||||
|
interactive_backup() {
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${WHITE}💾 Создание бекапа${NC}"
|
||||||
|
echo -ne " Зашифровать бекап паролем? [Y/n]: "
|
||||||
|
read -r use_pass
|
||||||
|
|
||||||
|
local password=""
|
||||||
|
if [[ ! "$use_pass" =~ ^[Nn] ]]; then
|
||||||
|
echo -ne " Введите пароль: "
|
||||||
|
read -rs password
|
||||||
|
echo ""
|
||||||
|
echo -ne " Повторите пароль: "
|
||||||
|
read -rs password2
|
||||||
|
echo ""
|
||||||
|
if [ "$password" != "$password2" ]; then
|
||||||
|
log_error "Пароли не совпадают"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ ${#password} -lt 6 ]; then
|
||||||
|
log_error "Пароль слишком короткий (минимум 6 символов)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_backup "$password"
|
||||||
|
cleanup_old_backups
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Интерактивное восстановление ─────────────────────────────────────────────
|
||||||
|
interactive_restore() {
|
||||||
|
list_backups || return 1
|
||||||
|
|
||||||
|
echo -ne " Номер бекапа (или путь к файлу): "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
local backup_file=""
|
||||||
|
if [[ "$choice" =~ ^[0-9]+$ ]]; then
|
||||||
|
local i=1
|
||||||
|
for f in "$BACKUP_DIR"/gotelegram_backup_*.tar.gz*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
[[ "$f" == *.sha256 ]] && continue
|
||||||
|
if [ "$i" -eq "$choice" ]; then
|
||||||
|
backup_file="$f"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
elif [ -f "$choice" ]; then
|
||||||
|
backup_file="$choice"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$backup_file" ]; then
|
||||||
|
log_error "Бекап не найден"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
restore_backup "$backup_file"
|
||||||
|
}
|
||||||
456
lib/common.sh
Executable file
456
lib/common.sh
Executable file
@@ -0,0 +1,456 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.3 — Общие утилиты
|
||||||
|
# Цвета, логирование, спиннер, системные функции, совместимость с v1
|
||||||
|
|
||||||
|
# ── Версия ────────────────────────────────────────────────────────────────────
|
||||||
|
GOTELEGRAM_VERSION="2.3.1"
|
||||||
|
GOTELEGRAM_NAME="GoTelegram"
|
||||||
|
|
||||||
|
# ── Пути ──────────────────────────────────────────────────────────────────────
|
||||||
|
GOTELEGRAM_DIR="/opt/gotelegram"
|
||||||
|
GOTELEGRAM_CONFIG="$GOTELEGRAM_DIR/config.json"
|
||||||
|
TELEMT_CONFIG="/etc/telemt/config.toml"
|
||||||
|
TELEMT_BIN="/usr/local/bin/telemt"
|
||||||
|
TELEMT_SERVICE="telemt"
|
||||||
|
NGINX_SITE_CONF="/etc/nginx/sites-available/gotelegram"
|
||||||
|
NGINX_SITE_LINK="/etc/nginx/sites-enabled/gotelegram"
|
||||||
|
WEBSITE_ROOT="/var/www/gotelegram-site"
|
||||||
|
BACKUP_DIR="$GOTELEGRAM_DIR/backups"
|
||||||
|
LOG_FILE="/var/log/gotelegram.log"
|
||||||
|
BOT_DIR="/opt/gotelegram-bot"
|
||||||
|
|
||||||
|
# ── V1 совместимость ─────────────────────────────────────────────────────────
|
||||||
|
V1_CONTAINER_NAME="mtproto-proxy"
|
||||||
|
V1_CONFIG_FILE="/opt/gotelegram-bot/proxy.json"
|
||||||
|
V1_SERVICE_NAME="gotelegram-bot"
|
||||||
|
|
||||||
|
# ── Цвета ────────────────────────────────────────────────────────────────────
|
||||||
|
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'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
DIM='\033[2m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# ── Логирование ──────────────────────────────────────────────────────────────
|
||||||
|
log_info() { echo -e " ${CYAN}ℹ${NC} $*" >&2; }
|
||||||
|
log_success() { echo -e " ${GREEN}✓${NC} $*" >&2; }
|
||||||
|
log_warning() { echo -e " ${YELLOW}⚠${NC} $*" >&2; }
|
||||||
|
log_error() { echo -e " ${RED}✗${NC} $*" >&2; }
|
||||||
|
log_step() { echo -e "\n${BOLD}${WHITE} $*${NC}" >&2; }
|
||||||
|
log_dim() { echo -e " ${DIM}$*${NC}" >&2; }
|
||||||
|
|
||||||
|
log_to_file() {
|
||||||
|
local ts; ts=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[$ts] $*" >> "$LOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Спиннер ──────────────────────────────────────────────────────────────────
|
||||||
|
_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.1
|
||||||
|
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_spinner() {
|
||||||
|
local label="$1"; shift
|
||||||
|
local err_file="/tmp/.gotelegram_spinner_err_$$"
|
||||||
|
spinner_start "$label"
|
||||||
|
"$@" >/dev/null 2>"$err_file"
|
||||||
|
local rc=$?
|
||||||
|
spinner_stop
|
||||||
|
if [ $rc -eq 0 ]; then
|
||||||
|
log_success "$label"
|
||||||
|
else
|
||||||
|
log_error "$label ${RED}(ошибка, код: $rc)${NC}"
|
||||||
|
if [ -s "$err_file" ]; then
|
||||||
|
log_dim " $(head -3 "$err_file")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$err_file"
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Баннер ───────────────────────────────────────────────────────────────────
|
||||||
|
show_banner() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${BOLD}${WHITE}🚀 GoTelegram v${GOTELEGRAM_VERSION}${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${DIM}MTProxy на ядре telemt (Rust + Tokio)${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}║${NC} ${DIM}Anti-DPI • Fake TLS • TCP Splice • JA3/JA4${NC} ${CYAN}║${NC}"
|
||||||
|
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Благодарности ────────────────────────────────────────────────────────────
|
||||||
|
show_credits() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${BOLD}Благодарности / Credits${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}╟──────────────────────────────────────────────────────────╢${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${WHITE}telemt${NC} — MTProxy engine (Rust) ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${DIM}github.com/telemt/telemt${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${WHITE}HTML5 UP${NC} — адаптивные HTML/CSS шаблоны ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${DIM}html5up.net • CC BY 3.0 • @ajlkn${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${WHITE}learning-zone${NC} — 150+ HTML5 шаблонов ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${DIM}github.com/learning-zone/website-templates${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${WHITE}Start Bootstrap${NC} — MIT лицензия ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}║${NC} ${DIM}startbootstrap.com${NC} ${MAGENTA}║${NC}"
|
||||||
|
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Системные утилиты ────────────────────────────────────────────────────────
|
||||||
|
_valid_ip() {
|
||||||
|
# Validate that each octet is 0-255
|
||||||
|
local ip="$1"
|
||||||
|
local IFS='.'
|
||||||
|
read -ra octets <<< "$ip"
|
||||||
|
[ ${#octets[@]} -ne 4 ] && return 1
|
||||||
|
for octet in "${octets[@]}"; do
|
||||||
|
[[ "$octet" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
[ "$octet" -gt 255 ] && return 1
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_server_ip() {
|
||||||
|
local ip raw
|
||||||
|
for url in "https://api.ipify.org" "https://icanhazip.com" "https://ifconfig.me"; do
|
||||||
|
raw=$(curl -s -4 --max-time 5 "$url" 2>/dev/null)
|
||||||
|
ip=$(echo "$raw" | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
|
||||||
|
if [ -n "$ip" ] && _valid_ip "$ip"; then
|
||||||
|
echo "$ip"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "0.0.0.0"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_root() {
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
log_error "Запустите скрипт с sudo / от root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_os() {
|
||||||
|
if [ ! -f /etc/os-release ]; then
|
||||||
|
log_error "Не удалось определить ОС. Требуется Linux."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Validate os-release before sourcing (reject command injection: ;, backticks, $())
|
||||||
|
if grep -qE '(;|`|\$\(|\$\{)' /etc/os-release 2>/dev/null; then
|
||||||
|
log_warning "/etc/os-release содержит подозрительные строки, пропускаем"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
. /etc/os-release
|
||||||
|
case "$ID" in
|
||||||
|
ubuntu|debian|centos|rocky|almalinux|fedora|rhel)
|
||||||
|
log_dim "ОС: $PRETTY_NAME"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_warning "ОС $ID может быть несовместима. Поддерживаются: Ubuntu, Debian, CentOS, Rocky."
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_arch() {
|
||||||
|
local arch
|
||||||
|
arch=$(uname -m)
|
||||||
|
case "$arch" in
|
||||||
|
x86_64|amd64) echo "amd64" ;;
|
||||||
|
aarch64|arm64) echo "arm64" ;;
|
||||||
|
armv7*|armhf) echo "armv7" ;;
|
||||||
|
*) echo "$arch" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_pkg_manager() {
|
||||||
|
if command -v apt-get &>/dev/null; then echo "apt"
|
||||||
|
elif command -v dnf &>/dev/null; then echo "dnf"
|
||||||
|
elif command -v yum &>/dev/null; then echo "yum"
|
||||||
|
else echo "unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_pkg() {
|
||||||
|
local pkg="$1"
|
||||||
|
case "$(get_pkg_manager)" in
|
||||||
|
apt) apt-get install -y -qq "$pkg" ;;
|
||||||
|
dnf) dnf install -y -q "$pkg" ;;
|
||||||
|
yum) yum install -y -q "$pkg" ;;
|
||||||
|
*) log_error "Неизвестный пакетный менеджер"; return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_deps() {
|
||||||
|
local missing=()
|
||||||
|
for cmd in curl jq openssl git; do
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
missing+=("$cmd")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
|
log_step "Установка зависимостей: ${missing[*]}"
|
||||||
|
case "$(get_pkg_manager)" in
|
||||||
|
apt) apt-get update -qq && apt-get install -y -qq "${missing[@]}" ;;
|
||||||
|
dnf) dnf install -y -q "${missing[@]}" ;;
|
||||||
|
yum) yum install -y -q "${missing[@]}" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_port() {
|
||||||
|
local port="$1"
|
||||||
|
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 # свободен
|
||||||
|
}
|
||||||
|
|
||||||
|
check_disk_space() {
|
||||||
|
local min_mb="${1:-500}"
|
||||||
|
local avail_mb
|
||||||
|
avail_mb=$(df -m / | awk 'NR==2 {print $4}')
|
||||||
|
if [ "$avail_mb" -lt "$min_mb" ]; then
|
||||||
|
log_error "Мало места на диске: ${avail_mb}MB (нужно ${min_mb}MB+)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Конфигурация GoTelegram (JSON) ──────────────────────────────────────────
|
||||||
|
save_gotelegram_config() {
|
||||||
|
mkdir -p "$(dirname "$GOTELEGRAM_CONFIG")"
|
||||||
|
cat > "$GOTELEGRAM_CONFIG" << EOJSON
|
||||||
|
{
|
||||||
|
"version": "$GOTELEGRAM_VERSION",
|
||||||
|
"engine": "${1:-telemt}",
|
||||||
|
"mode": "${2:-lite}",
|
||||||
|
"port": ${3:-443},
|
||||||
|
"secret": "${4:-}",
|
||||||
|
"mask_host": "${5:-google.com}",
|
||||||
|
"domain": "${6:-}",
|
||||||
|
"template_id": "${7:-}",
|
||||||
|
"installed_at": "$(date -Iseconds)",
|
||||||
|
"updated_at": "$(date -Iseconds)"
|
||||||
|
}
|
||||||
|
EOJSON
|
||||||
|
chmod 600 "$GOTELEGRAM_CONFIG"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_gotelegram_config() {
|
||||||
|
if [ -f "$GOTELEGRAM_CONFIG" ]; then
|
||||||
|
cat "$GOTELEGRAM_CONFIG"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "{}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
config_get() {
|
||||||
|
local key="$1"
|
||||||
|
if [ ! -f "$GOTELEGRAM_CONFIG" ]; then
|
||||||
|
log_dim "Конфиг не найден: $GOTELEGRAM_CONFIG" >&2
|
||||||
|
return 2 # file missing
|
||||||
|
fi
|
||||||
|
local val
|
||||||
|
val=$(jq -r ".$key // empty" "$GOTELEGRAM_CONFIG" 2>/dev/null)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_dim "Ошибка чтения JSON: $GOTELEGRAM_CONFIG" >&2
|
||||||
|
return 3 # invalid JSON
|
||||||
|
fi
|
||||||
|
if [ -z "$val" ]; then
|
||||||
|
return 1 # key missing or empty
|
||||||
|
fi
|
||||||
|
echo "$val"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── V1 совместимость ─────────────────────────────────────────────────────────
|
||||||
|
detect_v1_installation() {
|
||||||
|
# Проверяем наличие mtg Docker контейнера (v1)
|
||||||
|
if command -v docker &>/dev/null; then
|
||||||
|
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${V1_CONTAINER_NAME}$"; then
|
||||||
|
return 0 # v1 обнаружена
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Проверяем наличие конфига v1
|
||||||
|
if [ -f "$V1_CONFIG_FILE" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_v1_config() {
|
||||||
|
# Извлекаем данные из работающего v1 контейнера
|
||||||
|
if ! command -v docker &>/dev/null; then
|
||||||
|
echo "{}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local running
|
||||||
|
running=$(docker ps --format '{{.Names}}' 2>/dev/null | grep "^${V1_CONTAINER_NAME}$")
|
||||||
|
|
||||||
|
if [ -z "$running" ]; then
|
||||||
|
# Пробуем из сохранённого конфига
|
||||||
|
if [ -f "$V1_CONFIG_FILE" ]; then
|
||||||
|
cat "$V1_CONFIG_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "{}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Достаём из Docker
|
||||||
|
local cmd_str port secret ip
|
||||||
|
cmd_str=$(docker inspect "$V1_CONTAINER_NAME" --format='{{range .Config.Cmd}}{{.}} {{end}}' 2>/dev/null)
|
||||||
|
secret=$(echo "$cmd_str" | awk '{print $NF}')
|
||||||
|
port=$(docker inspect "$V1_CONTAINER_NAME" --format='{{range $p,$c := .HostConfig.PortBindings}}{{(index $c 0).HostPort}}{{end}}' 2>/dev/null)
|
||||||
|
ip=$(get_server_ip)
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg secret "$secret" \
|
||||||
|
--arg port "${port:-443}" \
|
||||||
|
--arg ip "$ip" \
|
||||||
|
'{secret: $secret, port: ($port | tonumber), ip: $ip, engine: "mtg"}'
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_v1_to_v2() {
|
||||||
|
log_step "Миграция с v1 (mtg) на v2 (telemt)"
|
||||||
|
|
||||||
|
local v1_config
|
||||||
|
v1_config=$(get_v1_config)
|
||||||
|
|
||||||
|
local old_port old_secret
|
||||||
|
old_port=$(echo "$v1_config" | jq -r '.port // 443')
|
||||||
|
old_secret=$(echo "$v1_config" | jq -r '.secret // empty')
|
||||||
|
|
||||||
|
if [ -z "$old_secret" ]; then
|
||||||
|
log_warning "Не удалось извлечь secret из v1. Будет создан новый."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " ${WHITE}Найдена установка v1 (mtg):${NC}"
|
||||||
|
echo -e " Порт: ${CYAN}${old_port}${NC}"
|
||||||
|
echo -e " Secret: ${CYAN}${old_secret:0:16}...${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}Внимание:${NC} секрет mtg НЕ совместим с telemt напрямую."
|
||||||
|
echo -e " Клиентам потребуется новая ссылка."
|
||||||
|
echo ""
|
||||||
|
echo -ne " Остановить v1 контейнер и перейти на v2? [Y/n]: "
|
||||||
|
read -r ans
|
||||||
|
if [[ "$ans" =~ ^[Nn] ]]; then
|
||||||
|
log_info "Миграция отменена. v1 оставлен без изменений."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Останавливаем v1
|
||||||
|
log_info "Остановка v1 контейнера..."
|
||||||
|
docker stop "$V1_CONTAINER_NAME" 2>/dev/null
|
||||||
|
docker rm "$V1_CONTAINER_NAME" 2>/dev/null
|
||||||
|
|
||||||
|
# Бекапим v1 конфиг
|
||||||
|
if [ -f "$V1_CONFIG_FILE" ]; then
|
||||||
|
mkdir -p "$GOTELEGRAM_DIR"
|
||||||
|
cp "$V1_CONFIG_FILE" "$GOTELEGRAM_DIR/v1_backup_proxy.json" 2>/dev/null
|
||||||
|
log_success "Конфиг v1 сохранён в $GOTELEGRAM_DIR/v1_backup_proxy.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "v1 остановлен. Порт $old_port освобождён."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Подтверждение ────────────────────────────────────────────────────────────
|
||||||
|
confirm() {
|
||||||
|
local msg="${1:-Продолжить?}"
|
||||||
|
echo -ne " ${msg} [Y/n]: " >&2
|
||||||
|
read -r ans
|
||||||
|
[[ ! "$ans" =~ ^[Nn] ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Выбор из списка ──────────────────────────────────────────────────────────
|
||||||
|
select_option() {
|
||||||
|
local title="$1"
|
||||||
|
shift
|
||||||
|
local options=("$@")
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}${title}${NC}" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}" >&2
|
||||||
|
local i=1
|
||||||
|
for opt in "${options[@]}"; do
|
||||||
|
echo -e " ${CYAN}${i})${NC} ${opt}" >&2
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}" >&2
|
||||||
|
echo -ne " ${WHITE}Выбор:${NC} " >&2
|
||||||
|
read -r choice
|
||||||
|
echo "$choice"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Генерация случайного hex ─────────────────────────────────────────────────
|
||||||
|
generate_hex() {
|
||||||
|
local len="${1:-32}"
|
||||||
|
openssl rand -hex "$((len/2))" 2>/dev/null || head -c "$((len/2))" /dev/urandom | xxd -p | tr -d '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Проверка домена ──────────────────────────────────────────────────────────
|
||||||
|
validate_domain() {
|
||||||
|
local domain="$1"
|
||||||
|
if echo "$domain" | grep -qE '^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Init: создание директорий ────────────────────────────────────────────────
|
||||||
|
init_dirs() {
|
||||||
|
mkdir -p "$GOTELEGRAM_DIR" "$BACKUP_DIR" /etc/telemt 2>/dev/null
|
||||||
|
touch "$LOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
406
lib/stats.sh
Executable file
406
lib/stats.sh
Executable file
@@ -0,0 +1,406 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# stats.sh — Traffic statistics module for GoTelegram
|
||||||
|
# Tracks proxy (telemt port 443) and site (nginx port 8443) traffic
|
||||||
|
# Uses iptables counters + real-time snapshots + historical CSV
|
||||||
|
|
||||||
|
# Color codes (from common.sh)
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
STATS_DIR="/run/gotelegram"
|
||||||
|
HISTORY_FILE="/opt/gotelegram/stats_history.csv"
|
||||||
|
SNAPSHOTS_DIR="$STATS_DIR/snapshots"
|
||||||
|
CURRENT_SNAPSHOT="$STATS_DIR/stats_current.json"
|
||||||
|
CONFIG_FILE="/opt/gotelegram/config.json"
|
||||||
|
|
||||||
|
# Initialize stats infrastructure
|
||||||
|
stats_init() {
|
||||||
|
# Create runtime directory
|
||||||
|
mkdir -p "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null
|
||||||
|
chmod 755 "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
# Create iptables chain if not exists
|
||||||
|
if ! iptables -L GOTELEGRAM_STATS -n >/dev/null 2>&1; then
|
||||||
|
iptables -N GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add chain to INPUT if not already present
|
||||||
|
if ! iptables -C INPUT -j GOTELEGRAM_STATS 2>/dev/null; then
|
||||||
|
iptables -I INPUT -j GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add rule for proxy traffic (port 443, TCP)
|
||||||
|
if ! iptables -C GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null; then
|
||||||
|
iptables -A GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add rule for site traffic (loopback, port 8443, TCP)
|
||||||
|
if ! iptables -C GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null; then
|
||||||
|
iptables -A GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize CSV header if file doesn't exist
|
||||||
|
if [[ ! -f "$HISTORY_FILE" ]]; then
|
||||||
|
echo "epoch,proxy_bytes,site_bytes" > "$HISTORY_FILE" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write initial snapshot
|
||||||
|
stats_collect
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect current traffic statistics from iptables
|
||||||
|
stats_collect() {
|
||||||
|
local proxy_bytes=0 proxy_pkts=0 site_bytes=0 site_pkts=0
|
||||||
|
local ts=$(date +%s)
|
||||||
|
local temp_file=$(mktemp)
|
||||||
|
|
||||||
|
# Parse iptables output: format is "pkts bytes target"
|
||||||
|
# We need to extract bytes (2nd column) for each rule
|
||||||
|
local iptables_output=$(iptables -L GOTELEGRAM_STATS -v -n -x 2>/dev/null)
|
||||||
|
|
||||||
|
# Extract counters for port 443 (proxy)
|
||||||
|
proxy_bytes=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $2}')
|
||||||
|
proxy_pkts=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $1}')
|
||||||
|
|
||||||
|
# Extract counters for port 8443 on loopback (site)
|
||||||
|
site_bytes=$(echo "$iptables_output" | grep "dpt:8443" | awk '{print $2}')
|
||||||
|
site_pkts=$(echo "$iptables_output" | grep "dpt:8443" | awk '{print $1}')
|
||||||
|
|
||||||
|
# Default to 0 if not found
|
||||||
|
proxy_bytes=${proxy_bytes:-0}
|
||||||
|
proxy_pkts=${proxy_pkts:-0}
|
||||||
|
site_bytes=${site_bytes:-0}
|
||||||
|
site_pkts=${site_pkts:-0}
|
||||||
|
|
||||||
|
# Write current snapshot as JSON
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
echo "{\"ts\":$ts,\"proxy_bytes\":$proxy_bytes,\"proxy_pkts\":$proxy_pkts,\"site_bytes\":$site_bytes,\"site_pkts\":$site_pkts}" > "$CURRENT_SNAPSHOT" 2>/dev/null
|
||||||
|
else
|
||||||
|
cat > "$CURRENT_SNAPSHOT" 2>/dev/null <<EOF
|
||||||
|
{"ts":$ts,"proxy_bytes":$proxy_bytes,"proxy_pkts":$proxy_pkts,"site_bytes":$site_bytes,"site_pkts":$site_pkts}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save snapshot for rate calculation (one per minute)
|
||||||
|
local minute_key
|
||||||
|
minute_key=$(date +%Y%m%d%H%M 2>/dev/null)
|
||||||
|
local snapshot_file="$SNAPSHOTS_DIR/snap_${minute_key}.json"
|
||||||
|
cp "$CURRENT_SNAPSHOT" "$snapshot_file" 2>/dev/null
|
||||||
|
|
||||||
|
# Append to history CSV (once per minute, check if last entry is fresh)
|
||||||
|
if [[ -f "$HISTORY_FILE" ]]; then
|
||||||
|
local last_ts
|
||||||
|
last_ts=$(grep -E '^[0-9]' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f1)
|
||||||
|
last_ts="${last_ts:-0}"
|
||||||
|
local current_minute=$((ts - (ts % 60)))
|
||||||
|
|
||||||
|
if [[ "$last_ts" -eq 0 ]] || [[ $((current_minute - last_ts)) -ge 60 ]]; then
|
||||||
|
echo "$current_minute,$proxy_bytes,$site_bytes" >> "$HISTORY_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
# Cleanup old entries (keep only 365 days)
|
||||||
|
stats_cleanup_history
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$temp_file" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read current snapshot as JSON
|
||||||
|
stats_read_current() {
|
||||||
|
if [[ -f "$CURRENT_SNAPSHOT" ]]; then
|
||||||
|
cat "$CURRENT_SNAPSHOT"
|
||||||
|
else
|
||||||
|
echo "{}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract value from JSON (fallback if jq not available)
|
||||||
|
json_get() {
|
||||||
|
local json="$1"
|
||||||
|
local key="$2"
|
||||||
|
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
echo "$json" | jq -r ".${key}" 2>/dev/null || echo "0"
|
||||||
|
else
|
||||||
|
echo "$json" | grep -o "\"$key\":[^,}]*" | cut -d: -f2 | tr -d ' "' || echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert bytes to human-readable format
|
||||||
|
format_bytes() {
|
||||||
|
local bytes=$1
|
||||||
|
|
||||||
|
if (( bytes < 1024 )); then
|
||||||
|
printf "%.0f B" "$bytes"
|
||||||
|
elif (( bytes < 1024 * 1024 )); then
|
||||||
|
printf "%.1f KB" "$(echo "scale=1; $bytes / 1024" | bc 2>/dev/null || echo "$((bytes / 1024))")"
|
||||||
|
elif (( bytes < 1024 * 1024 * 1024 )); then
|
||||||
|
printf "%.1f MB" "$(echo "scale=1; $bytes / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024))")"
|
||||||
|
elif (( bytes < 1024 * 1024 * 1024 * 1024 )); then
|
||||||
|
printf "%.1f GB" "$(echo "scale=1; $bytes / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024 / 1024))")"
|
||||||
|
else
|
||||||
|
printf "%.1f TB" "$(echo "scale=1; $bytes / 1024 / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes / 1024 / 1024 / 1024 / 1024))")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert bytes/sec to human-readable rate
|
||||||
|
format_rate() {
|
||||||
|
local bytes_per_sec=$1
|
||||||
|
|
||||||
|
if (( bytes_per_sec < 1024 )); then
|
||||||
|
printf "%.0f B/s" "$bytes_per_sec"
|
||||||
|
elif (( bytes_per_sec < 1024 * 1024 )); then
|
||||||
|
printf "%.1f KB/s" "$(echo "scale=1; $bytes_per_sec / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024))")"
|
||||||
|
elif (( bytes_per_sec < 1024 * 1024 * 1024 )); then
|
||||||
|
printf "%.1f MB/s" "$(echo "scale=1; $bytes_per_sec / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024 / 1024))")"
|
||||||
|
else
|
||||||
|
printf "%.1f GB/s" "$(echo "scale=1; $bytes_per_sec / 1024 / 1024 / 1024" | bc 2>/dev/null || echo "$((bytes_per_sec / 1024 / 1024 / 1024))")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Safely convert value to integer (returns 0 for empty/non-numeric)
|
||||||
|
_to_int() {
|
||||||
|
local val="${1:-0}"
|
||||||
|
# Strip non-numeric chars, default to 0
|
||||||
|
val="${val//[^0-9]/}"
|
||||||
|
echo "${val:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate diff safely (never negative, never crashes on empty)
|
||||||
|
_safe_diff() {
|
||||||
|
local a=$(_to_int "$1")
|
||||||
|
local b=$(_to_int "$2")
|
||||||
|
local d=$((a - b))
|
||||||
|
(( d < 0 )) && d=0
|
||||||
|
echo "$d"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate traffic rates and totals from history
|
||||||
|
stats_calculate_rates() {
|
||||||
|
local traffic_type="$1" # "proxy" or "site"
|
||||||
|
local col_idx=2 # proxy_bytes is column 2
|
||||||
|
[[ "$traffic_type" == "site" ]] && col_idx=3
|
||||||
|
|
||||||
|
local now
|
||||||
|
now=$(date +%s)
|
||||||
|
|
||||||
|
# Get latest data line (skip header with grep -E '^[0-9]')
|
||||||
|
local bytes_now
|
||||||
|
bytes_now=$(_to_int "$(grep -E '^[0-9]' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f"$col_idx")")
|
||||||
|
|
||||||
|
local periods="60 300 3600 86400 604800 2592000 31536000"
|
||||||
|
local results=""
|
||||||
|
|
||||||
|
for secs in $periods; do
|
||||||
|
local target_ts=$((now - secs))
|
||||||
|
# Find closest entry at or after target timestamp (skip header)
|
||||||
|
local old_val
|
||||||
|
old_val=$(_to_int "$(awk -F, -v ts="$target_ts" '$1 ~ /^[0-9]/ && $1 <= ts' "$HISTORY_FILE" 2>/dev/null | tail -1 | cut -d, -f"$col_idx")")
|
||||||
|
|
||||||
|
local diff
|
||||||
|
diff=$(_safe_diff "$bytes_now" "$old_val")
|
||||||
|
local rate=$(( secs > 0 ? diff / secs : 0 ))
|
||||||
|
|
||||||
|
local bytes_fmt rate_fmt
|
||||||
|
bytes_fmt=$(format_bytes "$diff")
|
||||||
|
rate_fmt=$(format_rate "$rate")
|
||||||
|
|
||||||
|
if [ -z "$results" ]; then
|
||||||
|
results="${bytes_fmt}|${rate_fmt}"
|
||||||
|
else
|
||||||
|
results="${results}|${bytes_fmt}|${rate_fmt}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$results"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main display function for traffic statistics
|
||||||
|
show_traffic_stats() {
|
||||||
|
# Ensure stats are collected
|
||||||
|
stats_collect
|
||||||
|
|
||||||
|
# Get current counters
|
||||||
|
local current_json=$(stats_read_current)
|
||||||
|
local proxy_pkts=$(json_get "$current_json" "proxy_pkts")
|
||||||
|
local site_pkts=$(json_get "$current_json" "site_pkts")
|
||||||
|
|
||||||
|
# Calculate rates for proxy
|
||||||
|
local proxy_rates=$(stats_calculate_rates "proxy")
|
||||||
|
IFS='|' read -r p1m p1mr p5m p5mr p60m p60mr p1d p1dr p7d p7dr p30d p30dr p365d p365dr <<< "$proxy_rates"
|
||||||
|
|
||||||
|
# Calculate rates for site
|
||||||
|
local site_rates=$(stats_calculate_rates "site")
|
||||||
|
IFS='|' read -r s1m s1mr s5m s5mr s60m s60mr s1d s1dr s7d s7dr s30d s30dr s365d s365dr <<< "$site_rates"
|
||||||
|
|
||||||
|
# Display proxy stats
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE} Proxy (telemt, порт 443):${NC}"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
echo -e "${BLUE} Период │ Входящий │ Скорость${NC}"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "1 мин" "$p1m" "$p1mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "5 мин" "$p5m" "$p5mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "60 мин" "$p60m" "$p60mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "1 день" "$p1d" "$p1dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "7 дней" "$p7d" "$p7dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "30 дней" "$p30d" "$p30dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "365 дней" "$p365d" "$p365dr"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
printf " Пакетов: %d\n\n" "$proxy_pkts"
|
||||||
|
|
||||||
|
echo -e "${BLUE} Сайт (nginx, порт 8443):${NC}"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
echo -e "${BLUE} Период │ Входящий │ Скорость${NC}"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "1 мин" "$s1m" "$s1mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "5 мин" "$s5m" "$s5mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "60 мин" "$s60m" "$s60mr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "1 день" "$s1d" "$s1dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "7 дней" "$s7d" "$s7dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "30 дней" "$s30d" "$s30dr"
|
||||||
|
printf " %-9s │ %14s │ %s\n" "365 дней" "$s365d" "$s365dr"
|
||||||
|
echo -e "${BLUE} ─────────────────────────────────────────${NC}"
|
||||||
|
printf " Пакетов: %d\n" "$site_pkts"
|
||||||
|
echo ""
|
||||||
|
} >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean up history older than 365 days
|
||||||
|
stats_cleanup_history() {
|
||||||
|
if [[ ! -f "$HISTORY_FILE" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local now=$(date +%s)
|
||||||
|
local ts_365d=$((now - 31536000))
|
||||||
|
local temp_file=$(mktemp)
|
||||||
|
|
||||||
|
# Keep header + entries from last 365 days
|
||||||
|
{
|
||||||
|
head -1 "$HISTORY_FILE"
|
||||||
|
awk -F, -v ts="$ts_365d" '$1 >= ts' "$HISTORY_FILE" | tail -n +2
|
||||||
|
} > "$temp_file" 2>/dev/null
|
||||||
|
|
||||||
|
mv "$temp_file" "$HISTORY_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Toggle stats collection on/off
|
||||||
|
toggle_stats() {
|
||||||
|
local current_state="false"
|
||||||
|
|
||||||
|
# Read current state from config
|
||||||
|
if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then
|
||||||
|
current_state=$(jq -r '.stats_enabled // false' "$CONFIG_FILE" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Toggle
|
||||||
|
if [[ "$current_state" == "true" ]]; then
|
||||||
|
# Disable stats
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
jq '.stats_enabled = false' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" 2>/dev/null
|
||||||
|
mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove iptables rules
|
||||||
|
iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
iptables -F GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
iptables -X GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
|
||||||
|
# Clean up directories
|
||||||
|
rm -rf "$STATS_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
echo "Сбор статистики ОТКЛЮЧЕН" >&2
|
||||||
|
else
|
||||||
|
# Enable stats
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
jq '.stats_enabled = true' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" 2>/dev/null
|
||||||
|
mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize stats collection
|
||||||
|
stats_init
|
||||||
|
|
||||||
|
echo "Сбор статистики ВКЛЮЧЕН" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install systemd service for stats collection
|
||||||
|
install_stats_collector() {
|
||||||
|
local service_file="/etc/systemd/system/gotelegram-stats.service"
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "Требуется root для установки сервиса" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get script directory (resolve symlinks)
|
||||||
|
local script_dir=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
|
||||||
|
local lib_dir=$(dirname "$script_dir")
|
||||||
|
|
||||||
|
# Create systemd service file
|
||||||
|
cat > "$service_file" <<'EOF'
|
||||||
|
[Unit]
|
||||||
|
Description=GoTelegram Traffic Stats Collector
|
||||||
|
After=network.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=/bin/bash -c 'source /opt/gotelegram/lib/common.sh; source /opt/gotelegram/lib/stats.sh; stats_init; while true; do stats_collect; sleep 1; done'
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod 644 "$service_file"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable gotelegram-stats.service
|
||||||
|
systemctl start gotelegram-stats.service
|
||||||
|
|
||||||
|
echo "Сервис gotelegram-stats установлен и запущен" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove stats collector service
|
||||||
|
remove_stats_collector() {
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "Требуется root для удаления сервиса" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl stop gotelegram-stats.service 2>/dev/null
|
||||||
|
systemctl disable gotelegram-stats.service 2>/dev/null
|
||||||
|
rm -f /etc/systemd/system/gotelegram-stats.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Remove iptables rules
|
||||||
|
iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
iptables -F GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
iptables -X GOTELEGRAM_STATS 2>/dev/null
|
||||||
|
|
||||||
|
# Clean up directories and files
|
||||||
|
rm -rf "$STATS_DIR" 2>/dev/null
|
||||||
|
rm -f "$HISTORY_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
echo "Сервис статистики удалён" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Export functions for external use
|
||||||
|
export -f stats_init stats_collect stats_read_current stats_calculate_rates
|
||||||
|
export -f show_traffic_stats format_bytes format_rate toggle_stats
|
||||||
|
export -f stats_cleanup_history install_stats_collector remove_stats_collector
|
||||||
|
export -f json_get
|
||||||
327
lib/telemt.sh
Executable file
327
lib/telemt.sh
Executable file
@@ -0,0 +1,327 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.2 — Управление telemt binary
|
||||||
|
# Скачивание, обновление, запуск, остановка через systemd
|
||||||
|
|
||||||
|
TELEMT_GITHUB="telemt/telemt"
|
||||||
|
TELEMT_RELEASE_API="https://api.github.com/repos/${TELEMT_GITHUB}/releases/latest"
|
||||||
|
TELEMT_USER="telemt"
|
||||||
|
TELEMT_GROUP="telemt"
|
||||||
|
|
||||||
|
# ── Получение последней версии ───────────────────────────────────────────────
|
||||||
|
get_latest_telemt_version() {
|
||||||
|
local resp
|
||||||
|
resp=$(curl -s --max-time 10 "$TELEMT_RELEASE_API" 2>/dev/null)
|
||||||
|
if [ $? -ne 0 ] || [ -z "$resp" ]; then
|
||||||
|
log_error "Не удалось получить информацию о релизах telemt"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$resp" | jq -r '.tag_name // empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_telemt_download_url() {
|
||||||
|
local arch
|
||||||
|
arch=$(get_arch)
|
||||||
|
local resp
|
||||||
|
resp=$(curl -s --max-time 10 "$TELEMT_RELEASE_API" 2>/dev/null)
|
||||||
|
if [ -z "$resp" ]; then return 1; fi
|
||||||
|
|
||||||
|
# URL format: telemt-x86_64-linux-gnu.tar.gz (arch BEFORE linux)
|
||||||
|
local arch_pattern
|
||||||
|
case "$arch" in
|
||||||
|
amd64) arch_pattern="(amd64|x86_64)" ;;
|
||||||
|
arm64) arch_pattern="(arm64|aarch64)" ;;
|
||||||
|
armv7) arch_pattern="(armv7|arm)" ;;
|
||||||
|
*) arch_pattern="${arch}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$resp" | jq -r ".assets[].browser_download_url" 2>/dev/null \
|
||||||
|
| grep -iE "$arch_pattern" \
|
||||||
|
| grep -i "linux" \
|
||||||
|
| grep -v "sha256" \
|
||||||
|
| grep "gnu" \
|
||||||
|
| head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Установленная версия ─────────────────────────────────────────────────────
|
||||||
|
get_installed_telemt_version() {
|
||||||
|
if [ -x "$TELEMT_BIN" ]; then
|
||||||
|
"$TELEMT_BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_telemt_installed() {
|
||||||
|
[ -x "$TELEMT_BIN" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Скачивание и установка ───────────────────────────────────────────────────
|
||||||
|
download_telemt() {
|
||||||
|
local url
|
||||||
|
url=$(get_telemt_download_url)
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
log_error "Не найден бинарник telemt для архитектуры $(get_arch)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp_file="/tmp/telemt_download_$$"
|
||||||
|
local extract_dir="/tmp/telemt_extract_$$"
|
||||||
|
log_info "Скачивание: $url"
|
||||||
|
|
||||||
|
if ! curl -L -s --max-time 120 -o "$tmp_file" "$url"; then
|
||||||
|
log_error "Ошибка скачивания telemt"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем что файл не пустой и не HTML
|
||||||
|
local file_size
|
||||||
|
file_size=$(stat -c%s "$tmp_file" 2>/dev/null || echo 0)
|
||||||
|
if [ "$file_size" -lt 1000 ]; then
|
||||||
|
log_error "Скачанный файл слишком маленький ($file_size байт) — возможна ошибка сети"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Определяем тип файла и распаковываем
|
||||||
|
local mime extracted=""
|
||||||
|
mime=$(file -b --mime-type "$tmp_file" 2>/dev/null)
|
||||||
|
rm -rf "$extract_dir"
|
||||||
|
mkdir -p "$extract_dir"
|
||||||
|
|
||||||
|
case "$mime" in
|
||||||
|
application/gzip|application/x-gzip)
|
||||||
|
tar xzf "$tmp_file" -C "$extract_dir" 2>/dev/null
|
||||||
|
extracted=$(find "$extract_dir" -name "telemt" -type f 2>/dev/null | head -1)
|
||||||
|
if [ -z "$extracted" ]; then
|
||||||
|
# Может быть просто gzip без tar
|
||||||
|
gunzip -c "$tmp_file" > "$extract_dir/telemt_bin" 2>/dev/null
|
||||||
|
extracted="$extract_dir/telemt_bin"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
application/x-tar)
|
||||||
|
tar xf "$tmp_file" -C "$extract_dir" 2>/dev/null
|
||||||
|
extracted=$(find "$extract_dir" -name "telemt" -type f 2>/dev/null | head -1)
|
||||||
|
;;
|
||||||
|
application/zip)
|
||||||
|
unzip -o "$tmp_file" -d "$extract_dir" 2>/dev/null
|
||||||
|
extracted=$(find "$extract_dir" -name "telemt" -type f 2>/dev/null | head -1)
|
||||||
|
;;
|
||||||
|
application/octet-stream|application/x-executable)
|
||||||
|
extracted="$tmp_file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Пробуем определить по содержимому
|
||||||
|
if file "$tmp_file" 2>/dev/null | grep -q "ELF"; then
|
||||||
|
extracted="$tmp_file"
|
||||||
|
else
|
||||||
|
# Пробуем как tar.gz
|
||||||
|
tar xzf "$tmp_file" -C "$extract_dir" 2>/dev/null
|
||||||
|
extracted=$(find "$extract_dir" -name "telemt" -type f 2>/dev/null | head -1)
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$extracted" ] || [ ! -f "$extracted" ]; then
|
||||||
|
log_error "Не удалось извлечь бинарник telemt (mime: $mime)"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
rm -rf "$extract_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Устанавливаем
|
||||||
|
cp "$extracted" "$TELEMT_BIN"
|
||||||
|
chmod 755 "$TELEMT_BIN"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
rm -rf "$extract_dir"
|
||||||
|
|
||||||
|
# Проверяем
|
||||||
|
if "$TELEMT_BIN" --version &>/dev/null; then
|
||||||
|
log_success "telemt $(get_installed_telemt_version) установлен в $TELEMT_BIN"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Бинарник telemt не запускается ($(file -b "$TELEMT_BIN" 2>/dev/null))"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Системный пользователь ───────────────────────────────────────────────────
|
||||||
|
create_telemt_user() {
|
||||||
|
if ! id "$TELEMT_USER" &>/dev/null; then
|
||||||
|
useradd -r -s /usr/sbin/nologin -d /etc/telemt "$TELEMT_USER" 2>/dev/null
|
||||||
|
log_dim "Создан системный пользователь: $TELEMT_USER"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Systemd сервис ───────────────────────────────────────────────────────────
|
||||||
|
install_telemt_service() {
|
||||||
|
local config_path="${1:-$TELEMT_CONFIG}"
|
||||||
|
|
||||||
|
cat > "/etc/systemd/system/${TELEMT_SERVICE}.service" << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=GoTelegram MTProxy (telemt engine)
|
||||||
|
Documentation=https://github.com/telemt/telemt
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
ExecStart=$TELEMT_BIN run $config_path
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
LimitNOFILE=65535
|
||||||
|
|
||||||
|
# Безопасность
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
log_success "Systemd сервис $TELEMT_SERVICE создан"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Управление сервисом ──────────────────────────────────────────────────────
|
||||||
|
start_telemt() {
|
||||||
|
systemctl start "$TELEMT_SERVICE" 2>/dev/null
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet "$TELEMT_SERVICE"; then
|
||||||
|
log_success "telemt запущен"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "telemt не запустился"
|
||||||
|
journalctl -u "$TELEMT_SERVICE" --no-pager -n 10 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_telemt() {
|
||||||
|
if systemctl is-active --quiet "$TELEMT_SERVICE" 2>/dev/null; then
|
||||||
|
systemctl stop "$TELEMT_SERVICE"
|
||||||
|
log_success "telemt остановлен"
|
||||||
|
else
|
||||||
|
log_dim "telemt уже остановлен"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_telemt() {
|
||||||
|
systemctl restart "$TELEMT_SERVICE" 2>/dev/null
|
||||||
|
sleep 2
|
||||||
|
if systemctl is-active --quiet "$TELEMT_SERVICE"; then
|
||||||
|
log_success "telemt перезапущен"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "telemt не перезапустился"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_telemt() {
|
||||||
|
systemctl enable "$TELEMT_SERVICE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
telemt_status() {
|
||||||
|
if ! is_telemt_installed; then
|
||||||
|
echo "not_installed"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if systemctl is-active --quiet "$TELEMT_SERVICE" 2>/dev/null; then
|
||||||
|
echo "running"
|
||||||
|
elif systemctl is-enabled --quiet "$TELEMT_SERVICE" 2>/dev/null; then
|
||||||
|
echo "stopped"
|
||||||
|
else
|
||||||
|
echo "disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
telemt_logs() {
|
||||||
|
local lines="${1:-40}"
|
||||||
|
journalctl -u "$TELEMT_SERVICE" --no-pager -n "$lines" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
telemt_uptime() {
|
||||||
|
local started
|
||||||
|
started=$(systemctl show "$TELEMT_SERVICE" --property=ActiveEnterTimestamp --value 2>/dev/null)
|
||||||
|
if [ -n "$started" ] && [ "$started" != "" ]; then
|
||||||
|
echo "$started"
|
||||||
|
else
|
||||||
|
echo "N/A"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Обновление ───────────────────────────────────────────────────────────────
|
||||||
|
check_telemt_update() {
|
||||||
|
local current latest
|
||||||
|
current=$(get_installed_telemt_version)
|
||||||
|
latest=$(get_latest_telemt_version)
|
||||||
|
|
||||||
|
if [ -z "$current" ] || [ -z "$latest" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$current" != "$latest" ]; then
|
||||||
|
echo "$latest"
|
||||||
|
return 0 # есть обновление
|
||||||
|
fi
|
||||||
|
return 1 # актуально
|
||||||
|
}
|
||||||
|
|
||||||
|
update_telemt() {
|
||||||
|
local latest
|
||||||
|
latest=$(check_telemt_update)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_info "telemt уже последней версии ($(get_installed_telemt_version))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Доступно обновление: $(get_installed_telemt_version) → $latest"
|
||||||
|
if ! confirm "Обновить telemt?"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
stop_telemt
|
||||||
|
if download_telemt; then
|
||||||
|
start_telemt
|
||||||
|
log_success "telemt обновлён до $latest"
|
||||||
|
else
|
||||||
|
start_telemt # запускаем старую версию обратно
|
||||||
|
log_error "Обновление не удалось"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Полная установка telemt ──────────────────────────────────────────────────
|
||||||
|
install_telemt_full() {
|
||||||
|
log_step "Установка telemt"
|
||||||
|
|
||||||
|
# Создаём директории
|
||||||
|
mkdir -p /etc/telemt
|
||||||
|
|
||||||
|
# Скачиваем бинарник
|
||||||
|
run_with_spinner "Скачивание telemt" download_telemt || return 1
|
||||||
|
|
||||||
|
# Устанавливаем systemd сервис
|
||||||
|
install_telemt_service
|
||||||
|
|
||||||
|
# Включаем автозапуск
|
||||||
|
enable_telemt
|
||||||
|
|
||||||
|
log_success "telemt готов к работе"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Удаление telemt ──────────────────────────────────────────────────────────
|
||||||
|
remove_telemt() {
|
||||||
|
stop_telemt
|
||||||
|
systemctl disable "$TELEMT_SERVICE" 2>/dev/null
|
||||||
|
rm -f "/etc/systemd/system/${TELEMT_SERVICE}.service"
|
||||||
|
systemctl daemon-reload
|
||||||
|
rm -f "$TELEMT_BIN"
|
||||||
|
rm -rf /etc/telemt
|
||||||
|
log_success "telemt полностью удалён"
|
||||||
|
}
|
||||||
336
lib/telemt_config.sh
Executable file
336
lib/telemt_config.sh
Executable file
@@ -0,0 +1,336 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.2 — Генерация TOML конфигурации для telemt
|
||||||
|
|
||||||
|
# ── Популярные домены (не заблокированные в РФ) ──────────────────────────────
|
||||||
|
QUICK_DOMAINS=(
|
||||||
|
"google.com"
|
||||||
|
"microsoft.com"
|
||||||
|
"cloudflare.com"
|
||||||
|
"apple.com"
|
||||||
|
"amazon.com"
|
||||||
|
"github.com"
|
||||||
|
"stackoverflow.com"
|
||||||
|
"medium.com"
|
||||||
|
"wikipedia.org"
|
||||||
|
"coursera.org"
|
||||||
|
"udemy.com"
|
||||||
|
"habr.com"
|
||||||
|
"stepik.org"
|
||||||
|
"duolingo.com"
|
||||||
|
"khanacademy.org"
|
||||||
|
"bbc.com"
|
||||||
|
"reuters.com"
|
||||||
|
"nytimes.com"
|
||||||
|
"ted.com"
|
||||||
|
"zoom.us"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Генерация TOML конфига (telemt v3 формат) ───────────────────────────────
|
||||||
|
generate_telemt_toml() {
|
||||||
|
local secret="$1"
|
||||||
|
local port="${2:-443}"
|
||||||
|
local mask_mode="${3:-lite}" # lite | pro
|
||||||
|
local mask_domain="${4:-google.com}"
|
||||||
|
local mask_port="${5:-443}"
|
||||||
|
local output="${6:-$TELEMT_CONFIG}"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$output")"
|
||||||
|
|
||||||
|
# DNS override для pro: домен резолвится в 127.0.0.1
|
||||||
|
# чтобы mask-трафик шёл на локальный nginx, а не в интернет
|
||||||
|
local dns_line=""
|
||||||
|
if [ "$mask_mode" = "pro" ]; then
|
||||||
|
dns_line="dns_overrides = [\"${mask_domain}:${mask_port}:127.0.0.1\"]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$output" << EOTOML
|
||||||
|
# GoTelegram v${GOTELEGRAM_VERSION} — telemt v3 configuration
|
||||||
|
# Сгенерировано: $(date -Iseconds)
|
||||||
|
# Режим: ${mask_mode}
|
||||||
|
|
||||||
|
[server]
|
||||||
|
port = ${port}
|
||||||
|
listen_addr_ipv4 = "0.0.0.0"
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "${mask_domain}"
|
||||||
|
mask = true
|
||||||
|
mask_port = ${mask_port}
|
||||||
|
tls_emulation = $([ "$mask_mode" = "pro" ] && echo "false" || echo "true")
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
main = "${secret}"
|
||||||
|
|
||||||
|
[network]
|
||||||
|
${dns_line}
|
||||||
|
EOTOML
|
||||||
|
|
||||||
|
chmod 600 "$output"
|
||||||
|
log_success "Конфиг telemt записан: $output"
|
||||||
|
log_dim "Режим: $mask_mode, домен: $mask_domain, порт mask: $mask_port"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Добавление дополнительного секрета ───────────────────────────────────────
|
||||||
|
add_secret_to_config() {
|
||||||
|
local name="$1"
|
||||||
|
local secret="$2"
|
||||||
|
local config="${3:-$TELEMT_CONFIG}"
|
||||||
|
|
||||||
|
if [ ! -f "$config" ]; then
|
||||||
|
log_error "Конфиг не найден: $config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Добавляем новый блок [[users]]
|
||||||
|
cat >> "$config" << EOSECRET
|
||||||
|
|
||||||
|
[[users]]
|
||||||
|
name = "${name}"
|
||||||
|
secret = "${secret}"
|
||||||
|
EOSECRET
|
||||||
|
|
||||||
|
log_success "Добавлен секрет: $name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Чтение текущего конфига (telemt v3 формат) ──────────────────────────────
|
||||||
|
get_config_value() {
|
||||||
|
local key="$1"
|
||||||
|
local config="${2:-$TELEMT_CONFIG}"
|
||||||
|
|
||||||
|
if [ ! -f "$config" ]; then return 1; fi
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
secret)
|
||||||
|
# [access.users] main = "..."
|
||||||
|
grep -A5 '\[access.users\]' "$config" | grep -m1 '=' | sed 's/.*=\s*"\(.*\)"/\1/' | tr -d ' '
|
||||||
|
;;
|
||||||
|
port)
|
||||||
|
# [server] port = 443
|
||||||
|
grep -A5 '\[server\]' "$config" | grep 'port\s*=' | head -1 | sed 's/.*=\s*\([0-9]*\)/\1/' | tr -d ' '
|
||||||
|
;;
|
||||||
|
mask_host|tls_domain)
|
||||||
|
# [censorship] tls_domain = "..."
|
||||||
|
grep -A10 '\[censorship\]' "$config" | grep 'tls_domain\s*=' | sed 's/.*=\s*"\(.*\)"/\1/'
|
||||||
|
;;
|
||||||
|
mask_port)
|
||||||
|
grep -A10 '\[censorship\]' "$config" | grep 'mask_port\s*=' | sed 's/.*=\s*\([0-9]*\)/\1/' | tr -d ' '
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
grep "$key" "$config" | head -1 | sed 's/.*=\s*"\?\(.*\)"\?/\1/' | tr -d ' "'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Валидация конфига ────────────────────────────────────────────────────────
|
||||||
|
validate_telemt_config() {
|
||||||
|
local config="${1:-$TELEMT_CONFIG}"
|
||||||
|
|
||||||
|
if [ ! -f "$config" ]; then
|
||||||
|
log_error "Конфиг не найден: $config"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем обязательные поля
|
||||||
|
local secret port host
|
||||||
|
secret=$(get_config_value secret "$config")
|
||||||
|
port=$(get_config_value port "$config")
|
||||||
|
host=$(get_config_value mask_host "$config")
|
||||||
|
|
||||||
|
local errors=0
|
||||||
|
|
||||||
|
if [ -z "$secret" ]; then
|
||||||
|
log_error "Не задан secret"
|
||||||
|
((errors++))
|
||||||
|
elif [ ${#secret} -lt 32 ]; then
|
||||||
|
log_warning "Secret слишком короткий (${#secret} символов, рекомендуется 32+)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$port" ]; then
|
||||||
|
log_error "Не задан порт (bind_to)"
|
||||||
|
((errors++))
|
||||||
|
elif [ "$port" -lt 1 ] || [ "$port" -gt 65535 ] 2>/dev/null; then
|
||||||
|
log_error "Порт вне диапазона: $port"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$host" ]; then
|
||||||
|
log_error "Не задан маскировочный хост (censorship.tls_domain)"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $errors -gt 0 ]; then
|
||||||
|
log_error "Найдено ошибок: $errors"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Конфиг валиден"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Выбор домена (интерактивный) ─────────────────────────────────────────────
|
||||||
|
select_quick_domain() {
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}🌐 Выберите домен для маскировки (Fake TLS):${NC}" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}" >&2
|
||||||
|
|
||||||
|
local i=1
|
||||||
|
local row=""
|
||||||
|
for d in "${QUICK_DOMAINS[@]}"; do
|
||||||
|
printf " ${CYAN}%2d)${NC} %-25s" "$i" "$d" >&2
|
||||||
|
if (( i % 2 == 0 )); then
|
||||||
|
echo "" >&2
|
||||||
|
fi
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
if (( (i-1) % 2 != 0 )); then echo "" >&2; fi
|
||||||
|
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}" >&2
|
||||||
|
echo -ne " ${WHITE}Выбор (1-${#QUICK_DOMAINS[@]}):${NC} " >&2
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#QUICK_DOMAINS[@]} ]; then
|
||||||
|
echo "${QUICK_DOMAINS[$((choice-1))]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_error "Неверный выбор"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Выбор порта (интерактивный) ──────────────────────────────────────────────
|
||||||
|
select_port() {
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}🔌 Выберите порт:${NC}" >&2
|
||||||
|
|
||||||
|
# Проверяем стандартные порты
|
||||||
|
local busy_443 busy_8443
|
||||||
|
busy_443=$(check_port 443)
|
||||||
|
busy_8443=$(check_port 8443)
|
||||||
|
|
||||||
|
local label_443="443 (рекомендуется)"
|
||||||
|
local label_8443="8443"
|
||||||
|
[ -n "$busy_443" ] && label_443="443 ⚠️ занят"
|
||||||
|
[ -n "$busy_8443" ] && label_8443="8443 ⚠️ занят"
|
||||||
|
|
||||||
|
echo -e " ${CYAN}1)${NC} $label_443" >&2
|
||||||
|
echo -e " ${CYAN}2)${NC} $label_8443" >&2
|
||||||
|
echo -e " ${CYAN}3)${NC} Свой порт" >&2
|
||||||
|
|
||||||
|
if [ -n "$busy_443" ]; then
|
||||||
|
echo -e " ${DIM} ⚠ Порт 443 занят: $(echo "$busy_443" | head -c 60)${NC}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -ne " ${WHITE}Выбор:${NC} " >&2
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
1) echo "443" ;;
|
||||||
|
2) echo "8443" ;;
|
||||||
|
3)
|
||||||
|
echo -ne " Введите порт (1-65535): " >&2
|
||||||
|
read -r custom_port
|
||||||
|
if [[ "$custom_port" =~ ^[0-9]+$ ]] && [ "$custom_port" -ge 1 ] && [ "$custom_port" -le 65535 ]; then
|
||||||
|
echo "$custom_port"
|
||||||
|
else
|
||||||
|
log_error "Неверный порт"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) echo "443" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Генерация ссылки tg://proxy ──────────────────────────────────────────────
|
||||||
|
generate_proxy_link() {
|
||||||
|
local ip="${1:-$(get_server_ip)}"
|
||||||
|
local port="${2:-443}"
|
||||||
|
local secret="$3"
|
||||||
|
echo "tg://proxy?server=${ip}&port=${port}&secret=${secret}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Вывод информации о прокси ────────────────────────────────────────────────
|
||||||
|
show_proxy_info() {
|
||||||
|
local config="${1:-$TELEMT_CONFIG}"
|
||||||
|
local secret port mask_host ip link status
|
||||||
|
|
||||||
|
secret=$(get_config_value secret "$config")
|
||||||
|
port=$(get_config_value port "$config")
|
||||||
|
mask_host=$(get_config_value mask_host "$config")
|
||||||
|
ip=$(get_server_ip)
|
||||||
|
status=$(telemt_status)
|
||||||
|
|
||||||
|
local mode domain
|
||||||
|
mode=$(config_get mode 2>/dev/null || echo "lite")
|
||||||
|
domain=$(config_get domain 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Pro-режим: ссылка с доменом и fake-TLS секретом
|
||||||
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
||||||
|
local domain_hex faketls_secret
|
||||||
|
domain_hex=$(printf '%s' "$domain" | xxd -p | tr -d '\n')
|
||||||
|
faketls_secret="ee${secret}${domain_hex}"
|
||||||
|
link="tg://proxy?server=${domain}&port=${port}&secret=${faketls_secret}"
|
||||||
|
else
|
||||||
|
link=$(generate_proxy_link "$ip" "$port" "$secret")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local status_icon status_text
|
||||||
|
case "$status" in
|
||||||
|
running) status_icon="✅"; status_text="Работает" ;;
|
||||||
|
stopped) status_icon="⏸️"; status_text="Остановлен" ;;
|
||||||
|
*) status_icon="❌"; status_text="Не установлен" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${WHITE}${status_icon} Статус прокси: ${status_text}${NC}"
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||||
|
echo -e " ${WHITE}Ядро:${NC} telemt (Rust)"
|
||||||
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
||||||
|
echo -e " ${WHITE}Домен:${NC} ${CYAN}${domain}${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${WHITE}IP:${NC} ${CYAN}${ip}${NC}"
|
||||||
|
fi
|
||||||
|
echo -e " ${WHITE}Порт:${NC} ${CYAN}${port}${NC}"
|
||||||
|
echo -e " ${WHITE}Режим:${NC} ${CYAN}${mode}${NC}"
|
||||||
|
echo -e " ${WHITE}Маскировка:${NC} ${CYAN}${mask_host}${NC}"
|
||||||
|
echo -e " ${WHITE}Secret:${NC} ${CYAN}${secret:0:16}...${NC}"
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..50})${NC}"
|
||||||
|
echo -e " ${WHITE}Ссылка:${NC}"
|
||||||
|
echo -e " ${GREEN}${link}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# QR если доступен
|
||||||
|
if command -v qrencode &>/dev/null; then
|
||||||
|
qrencode -t UTF8 -m 2 "$link" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Вывод информации о прокси (Pro-режим) ──────────────────────────────────
|
||||||
|
# В pro-режиме ссылка содержит домен (не IP) и fake-TLS секрет (ee...)
|
||||||
|
show_proxy_info_pro() {
|
||||||
|
local domain="$1"
|
||||||
|
local faketls_secret="$2"
|
||||||
|
|
||||||
|
local link="tg://proxy?server=${domain}&port=443&secret=${faketls_secret}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${WHITE}✅ Pro-прокси настроен${NC}"
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
||||||
|
echo -e " ${WHITE}Ядро:${NC} telemt (Rust)"
|
||||||
|
echo -e " ${WHITE}Домен:${NC} ${CYAN}${domain}${NC}"
|
||||||
|
echo -e " ${WHITE}Порт:${NC} ${CYAN}443${NC} (внешний, telemt)"
|
||||||
|
echo -e " ${WHITE}Режим:${NC} ${MAGENTA}Pro (fake-TLS)${NC}"
|
||||||
|
echo -e " ${WHITE}nginx:${NC} ${CYAN}127.0.0.1:8443${NC} (внутренний)"
|
||||||
|
echo -e " ${WHITE}Secret:${NC} ${CYAN}${faketls_secret:0:20}...${NC}"
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
||||||
|
echo -e " ${WHITE}Ссылка для Telegram:${NC}"
|
||||||
|
echo -e " ${GREEN}${link}${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${DIM}Провайдер видит: HTTPS-трафик к ${domain}:443${NC}"
|
||||||
|
echo -e " ${DIM}Telegram-клиент маскирует соединение под TLS${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# QR если доступен
|
||||||
|
if command -v qrencode &>/dev/null; then
|
||||||
|
qrencode -t UTF8 -m 2 "$link" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
351
lib/templates_catalog.sh
Executable file
351
lib/templates_catalog.sh
Executable file
@@ -0,0 +1,351 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.2 — Каталог шаблонов сайтов
|
||||||
|
# Выбор из ~200 шаблонов, превью-ссылки, скачивание через git sparse-checkout
|
||||||
|
|
||||||
|
CATALOG_FILE="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")/templates_catalog.json"
|
||||||
|
TEMPLATES_CACHE="/tmp/gotelegram_templates"
|
||||||
|
|
||||||
|
# ── Загрузка каталога ────────────────────────────────────────────────────────
|
||||||
|
load_catalog() {
|
||||||
|
if [ ! -f "$CATALOG_FILE" ]; then
|
||||||
|
log_error "Каталог шаблонов не найден: $CATALOG_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Категории ────────────────────────────────────────────────────────────────
|
||||||
|
get_categories() {
|
||||||
|
jq -r '.categories[] | "\(.id)|\(.name)|\(.icon)|\(.templates | length)"' "$CATALOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
get_category_name() {
|
||||||
|
local cat_id="$1"
|
||||||
|
jq -r ".categories[] | select(.id == \"$cat_id\") | .name" "$CATALOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Шаблоны по категории ────────────────────────────────────────────────────
|
||||||
|
get_templates_by_category() {
|
||||||
|
local cat_id="$1"
|
||||||
|
jq -r ".categories[] | select(.id == \"$cat_id\") | .templates[] | \"\(.id)|\(.name)|\(.source)|\(.preview_url)\"" "$CATALOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Информация о шаблоне ────────────────────────────────────────────────────
|
||||||
|
get_template_info() {
|
||||||
|
local tpl_id="$1"
|
||||||
|
jq ".categories[].templates[] | select(.id == \"$tpl_id\")" "$CATALOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
get_template_field() {
|
||||||
|
local tpl_id="$1"
|
||||||
|
local field="$2"
|
||||||
|
jq -r ".categories[].templates[] | select(.id == \"$tpl_id\") | .$field" "$CATALOG_FILE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Интерактивный выбор категории ────────────────────────────────────────────
|
||||||
|
select_category() {
|
||||||
|
load_catalog || return 1
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}📂 Категории шаблонов сайтов:${NC}" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
|
||||||
|
|
||||||
|
local cats=()
|
||||||
|
local i=1
|
||||||
|
while IFS='|' read -r id name icon count; do
|
||||||
|
[ "$count" -eq 0 ] && continue
|
||||||
|
local emoji
|
||||||
|
case "$icon" in
|
||||||
|
briefcase) emoji="🏢" ;;
|
||||||
|
shopping-cart) emoji="🛒" ;;
|
||||||
|
heart) emoji="🏥" ;;
|
||||||
|
book) emoji="🎓" ;;
|
||||||
|
palette) emoji="📸" ;;
|
||||||
|
home) emoji="🏠" ;;
|
||||||
|
utensils) emoji="🍕" ;;
|
||||||
|
rocket) emoji="🎨" ;;
|
||||||
|
chart-bar) emoji="🔧" ;;
|
||||||
|
*) emoji="📄" ;;
|
||||||
|
esac
|
||||||
|
printf " ${CYAN}%2d)${NC} ${emoji} %-30s ${DIM}(%d шаблонов)${NC}\n" "$i" "$name" "$count" >&2
|
||||||
|
cats+=("$id")
|
||||||
|
((i++))
|
||||||
|
done < <(get_categories)
|
||||||
|
|
||||||
|
printf " ${CYAN}%2d)${NC} 🎲 Случайный шаблон\n" "$i" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
|
||||||
|
echo -ne " ${WHITE}Выбор:${NC} " >&2
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
# Случайный
|
||||||
|
if [ "$choice" -eq "$i" ] 2>/dev/null; then
|
||||||
|
local random_cat="${cats[$((RANDOM % ${#cats[@]}))]}"
|
||||||
|
echo "$random_cat"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -lt "$i" ]; then
|
||||||
|
echo "${cats[$((choice-1))]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_error "Неверный выбор"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Интерактивный выбор шаблона ──────────────────────────────────────────────
|
||||||
|
select_template() {
|
||||||
|
local cat_id="$1"
|
||||||
|
local cat_name
|
||||||
|
cat_name=$(get_category_name "$cat_id")
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}📋 $cat_name — доступные шаблоны:${NC}" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}" >&2
|
||||||
|
|
||||||
|
local tpls=()
|
||||||
|
local i=1
|
||||||
|
while IFS='|' read -r id name source preview; do
|
||||||
|
printf " ${CYAN}%2d)${NC} %-30s ${DIM}[%s]${NC}\n" "$i" "$name" "$source" >&2
|
||||||
|
tpls+=("$id")
|
||||||
|
((i++))
|
||||||
|
done < <(get_templates_by_category "$cat_id")
|
||||||
|
|
||||||
|
if [ ${#tpls[@]} -eq 0 ]; then
|
||||||
|
log_info "В этой категории нет шаблонов"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}" >&2
|
||||||
|
echo -ne " ${WHITE}Выбор (1-$((i-1))):${NC} " >&2
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -lt "$i" ]; then
|
||||||
|
local selected_id="${tpls[$((choice-1))]}"
|
||||||
|
|
||||||
|
# Показываем превью
|
||||||
|
show_template_preview "$selected_id"
|
||||||
|
|
||||||
|
echo "$selected_id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_error "Неверный выбор"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Показ превью шаблона ────────────────────────────────────────────────────
|
||||||
|
show_template_preview() {
|
||||||
|
local tpl_id="$1"
|
||||||
|
local info
|
||||||
|
info=$(get_template_info "$tpl_id")
|
||||||
|
|
||||||
|
local name source preview_url repo_url description
|
||||||
|
name=$(echo "$info" | jq -r '.name')
|
||||||
|
source=$(echo "$info" | jq -r '.source')
|
||||||
|
preview_url=$(echo "$info" | jq -r '.preview_url // empty')
|
||||||
|
repo_url=$(echo "$info" | jq -r '.repo_url // empty')
|
||||||
|
description=$(echo "$info" | jq -r '.description // "—"')
|
||||||
|
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${BOLD}${WHITE}🔍 Превью шаблона:${NC}" >&2
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
|
||||||
|
echo -e " ${WHITE}Название:${NC} $name" >&2
|
||||||
|
echo -e " ${WHITE}Источник:${NC} $source" >&2
|
||||||
|
echo -e " ${WHITE}Описание:${NC} $description" >&2
|
||||||
|
|
||||||
|
if [ -n "$preview_url" ]; then
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${GREEN}👁 Превью:${NC} ${CYAN}${preview_url}${NC}" >&2
|
||||||
|
echo -e " ${DIM}Откройте ссылку в браузере для просмотра шаблона${NC}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$repo_url" ]; then
|
||||||
|
echo -e " ${DIM}📦 Репо: ${repo_url}${NC}" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Благодарность автору
|
||||||
|
echo "" >&2
|
||||||
|
echo -e " ${MAGENTA}💜 Спасибо авторам ${source} за открытый код!${NC}" >&2
|
||||||
|
|
||||||
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
|
||||||
|
echo "" >&2
|
||||||
|
|
||||||
|
if ! confirm "Установить этот шаблон?"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Скачивание шаблона ───────────────────────────────────────────────────────
|
||||||
|
download_template() {
|
||||||
|
local tpl_id="$1"
|
||||||
|
local output_dir="${2:-$TEMPLATES_CACHE}"
|
||||||
|
local info
|
||||||
|
info=$(get_template_info "$tpl_id")
|
||||||
|
|
||||||
|
local repo_url sparse_path source name
|
||||||
|
repo_url=$(echo "$info" | jq -r '.repo_url')
|
||||||
|
sparse_path=$(echo "$info" | jq -r '.sparse_path')
|
||||||
|
source=$(echo "$info" | jq -r '.source')
|
||||||
|
name=$(echo "$info" | jq -r '.name')
|
||||||
|
|
||||||
|
local clone_dir="$output_dir/${tpl_id}"
|
||||||
|
rm -rf "$clone_dir"
|
||||||
|
mkdir -p "$clone_dir"
|
||||||
|
|
||||||
|
log_info "Скачивание шаблона \"$name\"..."
|
||||||
|
|
||||||
|
# Для HTML5 UP — отдельный репо с папками
|
||||||
|
if [ "$source" = "html5up" ]; then
|
||||||
|
local tmp_clone="/tmp/html5up_clone_$$"
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
|
||||||
|
# Sparse checkout
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
# Fallback: полный clone
|
||||||
|
git clone --depth 1 "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$tmp_clone" ]; then
|
||||||
|
cd "$tmp_clone" && git sparse-checkout set "$sparse_path" 2>/dev/null
|
||||||
|
if [ -d "$tmp_clone/$sparse_path" ]; then
|
||||||
|
cp -r "$tmp_clone/$sparse_path"/* "$clone_dir/"
|
||||||
|
fi
|
||||||
|
cd - >/dev/null
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
|
||||||
|
# Для learning-zone — один большой репо
|
||||||
|
elif [ "$source" = "learning-zone" ]; then
|
||||||
|
local tmp_clone="/tmp/lz_clone_$$"
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
git clone --depth 1 "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$tmp_clone" ]; then
|
||||||
|
cd "$tmp_clone" && git sparse-checkout set "$sparse_path" 2>/dev/null
|
||||||
|
if [ -d "$tmp_clone/$sparse_path" ]; then
|
||||||
|
cp -r "$tmp_clone/$sparse_path"/* "$clone_dir/"
|
||||||
|
fi
|
||||||
|
cd - >/dev/null
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
|
||||||
|
# Для StartBootstrap — каждый шаблон в своём репо
|
||||||
|
elif [ "$source" = "startbootstrap" ]; then
|
||||||
|
local sb_tmp="/tmp/sb_clone_$$"
|
||||||
|
rm -rf "$sb_tmp"
|
||||||
|
git clone --depth 1 "$repo_url" "$sb_tmp" 2>/dev/null
|
||||||
|
if [ -d "$sb_tmp" ]; then
|
||||||
|
rm -rf "$sb_tmp/.git"
|
||||||
|
# StartBootstrap хранит production-файлы в dist/
|
||||||
|
if [ -f "$sb_tmp/dist/index.html" ]; then
|
||||||
|
cp -r "$sb_tmp/dist/"* "$clone_dir/"
|
||||||
|
elif [ -f "$sb_tmp/index.html" ]; then
|
||||||
|
cp -r "$sb_tmp/"* "$clone_dir/"
|
||||||
|
else
|
||||||
|
local found_index
|
||||||
|
found_index=$(find "$sb_tmp" -name "index.html" -type f 2>/dev/null | head -1)
|
||||||
|
if [ -n "$found_index" ]; then
|
||||||
|
local found_dir
|
||||||
|
found_dir=$(dirname "$found_index")
|
||||||
|
cp -r "$found_dir/"* "$clone_dir/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -rf "$sb_tmp"
|
||||||
|
|
||||||
|
# Для ThemeWagon / ColorlibHQ — каждый шаблон в отдельном репо
|
||||||
|
elif [ "$source" = "themewagon" ] || [ "$source" = "colorlib" ]; then
|
||||||
|
local tw_tmp="/tmp/tw_clone_$$"
|
||||||
|
rm -rf "$tw_tmp"
|
||||||
|
git clone --depth 1 "$repo_url" "$tw_tmp" 2>/dev/null
|
||||||
|
if [ -d "$tw_tmp" ]; then
|
||||||
|
rm -rf "$tw_tmp/.git"
|
||||||
|
if [ -f "$tw_tmp/dist/index.html" ]; then
|
||||||
|
cp -r "$tw_tmp/dist/"* "$clone_dir/"
|
||||||
|
elif [ -f "$tw_tmp/index.html" ]; then
|
||||||
|
cp -r "$tw_tmp/"* "$clone_dir/"
|
||||||
|
else
|
||||||
|
local found_index
|
||||||
|
found_index=$(find "$tw_tmp" -name "index.html" -type f -maxdepth 3 2>/dev/null | head -1)
|
||||||
|
if [ -n "$found_index" ]; then
|
||||||
|
local found_dir
|
||||||
|
found_dir=$(dirname "$found_index")
|
||||||
|
cp -r "$found_dir/"* "$clone_dir/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -rf "$tw_tmp"
|
||||||
|
|
||||||
|
# Для dawidolko — один большой репо с папками (как learning-zone)
|
||||||
|
elif [ "$source" = "dawidolko" ]; then
|
||||||
|
local tmp_clone="/tmp/dw_clone_$$"
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
git clone --depth 1 "$repo_url" "$tmp_clone" 2>/dev/null
|
||||||
|
fi
|
||||||
|
if [ -d "$tmp_clone" ]; then
|
||||||
|
cd "$tmp_clone" && git sparse-checkout set "$sparse_path" 2>/dev/null
|
||||||
|
if [ -d "$tmp_clone/$sparse_path" ]; then
|
||||||
|
cp -r "$tmp_clone/$sparse_path"/* "$clone_dir/"
|
||||||
|
fi
|
||||||
|
cd - >/dev/null
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp_clone"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем результат
|
||||||
|
if [ -f "$clone_dir/index.html" ]; then
|
||||||
|
log_success "Шаблон \"$name\" скачан"
|
||||||
|
echo "$clone_dir"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
# fallback: ищем index.html в подпапках (нестандартная структура)
|
||||||
|
local fallback_index
|
||||||
|
fallback_index=$(find "$clone_dir" -name "index.html" -type f 2>/dev/null | head -1)
|
||||||
|
if [ -n "$fallback_index" ]; then
|
||||||
|
local fallback_dir
|
||||||
|
fallback_dir=$(dirname "$fallback_index")
|
||||||
|
if [ "$fallback_dir" != "$clone_dir" ]; then
|
||||||
|
cp -r "$fallback_dir/"* "$clone_dir/"
|
||||||
|
log_success "Шаблон \"$name\" скачан (из подпапки)"
|
||||||
|
echo "$clone_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
log_error "Шаблон не содержит index.html"
|
||||||
|
log_dim "Путь: $clone_dir"
|
||||||
|
ls -la "$clone_dir" 2>/dev/null >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Полный интерактивный процесс выбора ──────────────────────────────────────
|
||||||
|
interactive_template_selection() {
|
||||||
|
load_catalog || return 1
|
||||||
|
|
||||||
|
# Выбор категории
|
||||||
|
local cat_id
|
||||||
|
cat_id=$(select_category)
|
||||||
|
[ $? -ne 0 ] && return 1
|
||||||
|
|
||||||
|
# Выбор шаблона
|
||||||
|
local tpl_id
|
||||||
|
tpl_id=$(select_template "$cat_id")
|
||||||
|
[ $? -ne 0 ] && return 1
|
||||||
|
|
||||||
|
# Скачивание
|
||||||
|
local template_dir
|
||||||
|
template_dir=$(download_template "$tpl_id")
|
||||||
|
[ $? -ne 0 ] && return 1
|
||||||
|
|
||||||
|
echo "$template_dir"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
340
lib/website.sh
Executable file
340
lib/website.sh
Executable file
@@ -0,0 +1,340 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# GoTelegram v2.2 — Управление сайтом (nginx + certbot + шаблоны)
|
||||||
|
|
||||||
|
# ── Установка nginx ──────────────────────────────────────────────────────────
|
||||||
|
install_nginx() {
|
||||||
|
if command -v nginx &>/dev/null; then
|
||||||
|
log_dim "nginx уже установлен"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log_info "Установка nginx..."
|
||||||
|
case "$(get_pkg_manager)" in
|
||||||
|
apt) apt-get update -qq && apt-get install -y -qq nginx ;;
|
||||||
|
dnf) dnf install -y -q nginx ;;
|
||||||
|
yum) yum install -y -q nginx ;;
|
||||||
|
esac
|
||||||
|
systemctl enable nginx 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Установка certbot ────────────────────────────────────────────────────────
|
||||||
|
install_certbot() {
|
||||||
|
if command -v certbot &>/dev/null; then
|
||||||
|
log_dim "certbot уже установлен"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log_info "Установка certbot..."
|
||||||
|
case "$(get_pkg_manager)" in
|
||||||
|
apt) apt-get install -y -qq certbot python3-certbot-nginx ;;
|
||||||
|
dnf) dnf install -y -q certbot python3-certbot-nginx ;;
|
||||||
|
yum) yum install -y -q certbot python3-certbot-nginx ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Генерация nginx конфига ──────────────────────────────────────────────────
|
||||||
|
generate_nginx_config() {
|
||||||
|
local domain="$1"
|
||||||
|
local proxy_port="${2:-443}"
|
||||||
|
local use_ssl="${3:-true}"
|
||||||
|
|
||||||
|
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
|
||||||
|
|
||||||
|
cat > "$NGINX_SITE_CONF" << 'EONGINX'
|
||||||
|
# GoTelegram v2.3 — nginx config
|
||||||
|
# Pro: nginx на 127.0.0.1:8443 (внутренний), telemt на 0.0.0.0:443 (внешний)
|
||||||
|
# Обычный браузер → :443 → telemt → 127.0.0.1:8443 → nginx (сайт)
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name DOMAIN_PLACEHOLDER;
|
||||||
|
|
||||||
|
# Let's Encrypt ACME challenge
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
allow all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Редирект на HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:SSL_PORT_PLACEHOLDER ssl http2;
|
||||||
|
server_name DOMAIN_PLACEHOLDER;
|
||||||
|
|
||||||
|
# SSL сертификаты
|
||||||
|
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
|
||||||
|
|
||||||
|
# Современные TLS настройки
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
ssl_prefer_server_ciphers off;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
|
# OCSP stapling
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Корень сайта
|
||||||
|
root /var/www/gotelegram-site;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
expires 30d;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Кеширование статики
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Скрываем служебные файлы
|
||||||
|
location ~ /\. { deny all; }
|
||||||
|
location = /robots.txt { allow all; log_not_found off; access_log off; }
|
||||||
|
location = /favicon.ico { log_not_found off; access_log off; }
|
||||||
|
}
|
||||||
|
EONGINX
|
||||||
|
|
||||||
|
# Подставляем значения (используем | как разделитель, чтобы / в домене не ломал sed)
|
||||||
|
local escaped_domain
|
||||||
|
escaped_domain=$(printf '%s\n' "$domain" | sed 's/[&/\]/\\&/g')
|
||||||
|
sed -i "s|DOMAIN_PLACEHOLDER|${escaped_domain}|g" "$NGINX_SITE_CONF"
|
||||||
|
sed -i "s|SSL_PORT_PLACEHOLDER|${proxy_port}|g" "$NGINX_SITE_CONF"
|
||||||
|
|
||||||
|
# Активируем сайт
|
||||||
|
rm -f /etc/nginx/sites-enabled/default 2>/dev/null
|
||||||
|
ln -sf "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
|
||||||
|
|
||||||
|
log_success "nginx конфиг создан для $domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Временный конфиг (до получения SSL) ──────────────────────────────────────
|
||||||
|
generate_nginx_temp_config() {
|
||||||
|
local domain="$1"
|
||||||
|
|
||||||
|
cat > "$NGINX_SITE_CONF" << EONGINX_TEMP
|
||||||
|
# GoTelegram — временный конфиг (до получения SSL)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name ${domain};
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
allow all;
|
||||||
|
}
|
||||||
|
|
||||||
|
root /var/www/gotelegram-site;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files \$uri \$uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EONGINX_TEMP
|
||||||
|
|
||||||
|
rm -f /etc/nginx/sites-enabled/default 2>/dev/null
|
||||||
|
ln -sf "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
|
||||||
|
mkdir -p /var/www/certbot
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Получение SSL сертификата ────────────────────────────────────────────────
|
||||||
|
obtain_ssl_certificate() {
|
||||||
|
local domain="$1"
|
||||||
|
local email="${2:-}"
|
||||||
|
|
||||||
|
if [ ! -d "/etc/letsencrypt/live/$domain" ]; then
|
||||||
|
log_info "Получение SSL сертификата для $domain..."
|
||||||
|
|
||||||
|
# Временный конфиг для ACME challenge
|
||||||
|
generate_nginx_temp_config "$domain"
|
||||||
|
systemctl restart nginx 2>/dev/null
|
||||||
|
|
||||||
|
local certbot_args=(
|
||||||
|
certonly
|
||||||
|
--webroot
|
||||||
|
-w /var/www/certbot
|
||||||
|
-d "$domain"
|
||||||
|
--non-interactive
|
||||||
|
--agree-tos
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -n "$email" ]; then
|
||||||
|
certbot_args+=(--email "$email")
|
||||||
|
else
|
||||||
|
certbot_args+=(--register-unsafely-without-email)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if certbot "${certbot_args[@]}" 2>/dev/null; then
|
||||||
|
log_success "SSL сертификат получен для $domain"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Не удалось получить SSL сертификат"
|
||||||
|
log_dim "Убедитесь что домен $domain направлен на IP этого сервера"
|
||||||
|
log_dim "и порт 80 открыт в файрволе."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_dim "SSL сертификат уже существует для $domain"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Авто-обновление сертификата ──────────────────────────────────────────────
|
||||||
|
setup_ssl_auto_renewal() {
|
||||||
|
# Certbot systemd timer (предпочтительно)
|
||||||
|
if [ -f /etc/systemd/system/certbot.timer ] || [ -f /lib/systemd/system/certbot.timer ]; then
|
||||||
|
systemctl enable certbot.timer 2>/dev/null
|
||||||
|
systemctl start certbot.timer 2>/dev/null
|
||||||
|
log_success "Авто-обновление SSL через systemd timer"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: cron
|
||||||
|
if ! crontab -l 2>/dev/null | grep -q "certbot renew"; then
|
||||||
|
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab -
|
||||||
|
log_success "Авто-обновление SSL через cron (3:00 ежедневно)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Обновление сертификата вручную ───────────────────────────────────────────
|
||||||
|
renew_ssl_certificate() {
|
||||||
|
log_info "Обновление SSL сертификата..."
|
||||||
|
if certbot renew --quiet --post-hook "systemctl reload nginx" 2>/dev/null; then
|
||||||
|
log_success "Сертификат обновлён"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Ошибка обновления сертификата"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Дата истечения SSL ───────────────────────────────────────────────────────
|
||||||
|
get_ssl_expiry() {
|
||||||
|
local domain="$1"
|
||||||
|
local cert="/etc/letsencrypt/live/$domain/fullchain.pem"
|
||||||
|
if [ -f "$cert" ]; then
|
||||||
|
openssl x509 -enddate -noout -in "$cert" 2>/dev/null | sed 's/notAfter=//'
|
||||||
|
else
|
||||||
|
echo "N/A"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Деплой шаблона сайта ─────────────────────────────────────────────────────
|
||||||
|
deploy_template_to_nginx() {
|
||||||
|
local template_dir="$1"
|
||||||
|
|
||||||
|
if [ ! -d "$template_dir" ] || [ ! -f "$template_dir/index.html" ]; then
|
||||||
|
log_error "Шаблон не содержит index.html: $template_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Бекапим старый сайт
|
||||||
|
if [ -d "$WEBSITE_ROOT" ] && [ "$(ls -A "$WEBSITE_ROOT" 2>/dev/null)" ]; then
|
||||||
|
local backup_name="site_backup_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
mv "$WEBSITE_ROOT" "/tmp/$backup_name" 2>/dev/null
|
||||||
|
log_dim "Старый сайт сохранён в /tmp/$backup_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$WEBSITE_ROOT"
|
||||||
|
cp -r "$template_dir"/* "$WEBSITE_ROOT/"
|
||||||
|
chown -R www-data:www-data "$WEBSITE_ROOT" 2>/dev/null || chown -R nginx:nginx "$WEBSITE_ROOT" 2>/dev/null
|
||||||
|
chmod -R 755 "$WEBSITE_ROOT"
|
||||||
|
|
||||||
|
log_success "Шаблон развёрнут в $WEBSITE_ROOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Полная установка pro-режима ──────────────────────────────────────────────
|
||||||
|
setup_pro_mode() {
|
||||||
|
local domain="$1"
|
||||||
|
local template_dir="$2"
|
||||||
|
local proxy_port="${3:-443}"
|
||||||
|
local email="${4:-}"
|
||||||
|
|
||||||
|
log_step "Настройка pro-режима"
|
||||||
|
|
||||||
|
# 1. Устанавливаем nginx
|
||||||
|
run_with_spinner "Установка nginx" install_nginx || return 1
|
||||||
|
|
||||||
|
# 2. Устанавливаем certbot
|
||||||
|
run_with_spinner "Установка certbot" install_certbot || return 1
|
||||||
|
|
||||||
|
# 3. Деплоим шаблон сайта
|
||||||
|
deploy_template_to_nginx "$template_dir" || return 1
|
||||||
|
|
||||||
|
# 4. Получаем SSL
|
||||||
|
obtain_ssl_certificate "$domain" "$email" || return 1
|
||||||
|
|
||||||
|
# 5. Генерируем полный nginx конфиг с SSL
|
||||||
|
generate_nginx_config "$domain" "$proxy_port"
|
||||||
|
|
||||||
|
# 6. Тестируем и перезапускаем nginx
|
||||||
|
if nginx -t 2>/dev/null; then
|
||||||
|
systemctl restart nginx
|
||||||
|
log_success "nginx запущен с SSL"
|
||||||
|
else
|
||||||
|
log_error "Ошибка в конфигурации nginx"
|
||||||
|
nginx -t
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Настраиваем авто-обновление SSL
|
||||||
|
setup_ssl_auto_renewal
|
||||||
|
|
||||||
|
# 8. Показываем благодарности авторам шаблонов
|
||||||
|
show_credits
|
||||||
|
|
||||||
|
log_success "Pro-режим настроен: https://${domain}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Управление nginx ────────────────────────────────────────────────────────
|
||||||
|
nginx_status() {
|
||||||
|
if systemctl is-active --quiet nginx 2>/dev/null; then
|
||||||
|
echo "running"
|
||||||
|
else
|
||||||
|
echo "stopped"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_nginx() {
|
||||||
|
if nginx -t 2>/dev/null; then
|
||||||
|
systemctl restart nginx 2>/dev/null
|
||||||
|
log_success "nginx перезапущен"
|
||||||
|
else
|
||||||
|
log_error "Ошибка конфигурации nginx"
|
||||||
|
nginx -t
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Удаление pro-режима ──────────────────────────────────────────────────────
|
||||||
|
remove_pro_mode() {
|
||||||
|
log_info "Удаление pro-режима..."
|
||||||
|
rm -f "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
|
||||||
|
rm -rf "$WEBSITE_ROOT"
|
||||||
|
systemctl restart nginx 2>/dev/null
|
||||||
|
log_success "Pro-режим удалён (nginx оставлен)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Смена шаблона ────────────────────────────────────────────────────────────
|
||||||
|
switch_template() {
|
||||||
|
local new_template_dir="$1"
|
||||||
|
deploy_template_to_nginx "$new_template_dir"
|
||||||
|
# nginx не требует перезапуска — статика обновилась на месте
|
||||||
|
log_success "Шаблон сайта обновлён"
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
@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
|
|
||||||
16339
templates_catalog.json
Normal file
16339
templates_catalog.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user