43 Commits

Author SHA1 Message Date
anten-ka
32f204c871 v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:48:37 +03:00
anten-ka
3495ab5b0f v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:41:23 +03:00
anten-ka
194fb32fec v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:37:28 +03:00
anten-ka
45e5cbabea v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:29:23 +03:00
anten-ka
694f18c22f v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:27:39 +03:00
anten-ka
0d087831d8 v2.4.0 — internationalization (EN/RU) + custom git templates
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
  * validates HTTPS URLs, rejects shell metachars
  * 100MB size guard, 90s clone timeout
  * auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
2026-04-10 11:26:02 +03:00
anten-ka
9c084f37ec bugfix: {{NC}} typo, bot TOML v3 parsing, add_secret v3 format
- install.sh: fix {{NC}} -> ${NC} color escape on line 265
- bot.py: fix TOML parsing for telemt v3 [access.users] format
- bot.py: fix telemt config section [server].port instead of [config].listen_port
- telemt_config.sh: fix add_secret_to_config() for v3 format
2026-04-10 00:16:30 +03:00
anten-ka
63b2fc3717 fix: banner + credits frame alignment (common.sh) 2026-04-09 19:28:46 +03:00
anten-ka
fedc8f77fe fix: frame alignment, ee-prefix for lite mode, qrencode in deps
- All frame boxes: correct 54-char width with emoji compensation
- Lite mode links now include ee-prefix + mask_host hex (fake-TLS)
- Added qrencode to ensure_deps() so QR codes work after fresh install
- Centralized link generation in generate_proxy_link()
- Fixed bot.py get_proxy_link() for lite mode ee-prefix
2026-04-09 19:24:05 +03:00
anten-ka
d70e046035 add bootstrap.sh installer 2026-04-09 11:15:43 +03:00
anten-ka
c70cb36a2b GoTelegram v2.3.1: full push
- MTProxy manager with Telegram bot
- 1801 site templates, 18 categories
- Stealth mode (fake-TLS + nginx mask)
- Admin auto-registration in bot
- Fixed frame alignment, bot venv setup
2026-04-09 11:14:41 +03:00
anten-ka
b36fb5cf10 v2.3.1: fix frame alignment + bot venv setup 2026-04-09 11:04:23 +03:00
anten-ka
8b4b4892a4 v2.3.1: bot install with spinner waiting for first admin + auto/manual choice 2026-04-09 10:54:06 +03:00
anten-ka
046a08fdb6 v2.3.1: admin management (auto-register first admin, /addadmin, /deladmin, menu button) 2026-04-09 10:46:50 +03:00
anten-ka
a21d2ebea2 v2.3.1: fix QR top margin, use domain instead of IP for pro-mode links 2026-04-09 00:59:42 +03:00
anten-ka
96cbd243d9 v2.3.1: promo only on gotelegram launch (once/day), not after proxy setup; bot promo with 2 hosters 2026-04-08 23:08:04 +03:00
anten-ka
3f136ec8a0 v2.3.1: promo update (2 hosters, QR codes, once-per-day display, 5sec delay) 2026-04-08 22:42:29 +03:00
anten-ka
a24d64d33c fix: stats refresh 3s instead of 1s 2026-04-08 22:31:30 +03:00
anten-ka
52912e0ead fix: stats crash (set -u + CSV header), header box, QR spacing 2026-04-08 22:21:17 +03:00
anten-ka
0dae922d1b fix: main menu 30s refresh, stats submenu flicker-free via tput 2026-04-08 22:01:02 +03:00
anten-ka
6ec2123f83 v2.3.0: Lite/Pro rebrand, submenu system, traffic stats, bot stats 2026-04-08 21:49:03 +03:00
anten-ka
364501d66d v2.2.1: redesign dashboard menu - no right borders, clean layout 2026-04-08 21:05:46 +03:00
anten-ka
f445f7a27e feat: dashboard with live status, proxy link, QR, auto-refresh 30s 2026-04-08 20:52:30 +03:00
anten-ka
6e32ca9d12 fix: telemt v3 config format ([server], [censorship], [access.users]), stealth with dns_overrides 2026-04-08 20:31:50 +03:00
anten-ka
7f81c21d8e stealth: telemt on 0.0.0.0:443, nginx on 127.0.0.1:8443, domain-based proxy link with ee-secret 2026-04-08 20:18:25 +03:00
anten-ka
16ef3564f9 fix: remove ColorlibHQ templates (WordPress themes, not static HTML)
1801 templates from 4 sources: html5up, startbootstrap, themewagon, dawidolko.
2026-04-07 00:29:46 +03:00
anten-ka
75a84e209b feat: expand template catalog to 1978 templates from 5 sources
Added ThemeWagon (1517), dawidolko (172), ColorlibHQ (177).
18 categories with auto-categorization.
Updated download_template for new sources.
2026-04-07 00:27:16 +03:00
anten-ka
d6045ace0c fix: stealth mode bind_to 127.0.0.1 + clarify internal port 8443 comments
telemt in stealth mode now binds to 127.0.0.1 instead of 0.0.0.0.
External port 8443 is not exposed - only nginx on 443 is public.
2026-04-07 00:10:13 +03:00
anten-ka
64da5de1ae fix: download_template - handle StartBootstrap dist/ structure + fallback index.html search
StartBootstrap templates store production files in dist/ subdirectory.
Added: dist/ detection, recursive index.html fallback, ls output to stderr.
2026-04-06 23:03:49 +03:00
anten-ka
a5347889bd fix: templates_catalog.sh - redirect show_template_preview stdout to stderr
Lines 168-172 in show_template_preview() were missing >&2,
causing stdout leak into subshell capture chain.
This corrupted template_dir variable and broke Stealth mode deployment.
2026-04-06 22:48:08 +03:00
anten-ka
3938a4ca3b Fix: resolve symlink in SCRIPT_DIR for gotelegram command 2026-04-06 22:32:23 +03:00
anten-ka
19beb2b2df Add gotelegram command (symlink to install.sh) 2026-04-06 22:30:06 +03:00
anten-ka
c2d95b6c2f GoTelegram v2.2.1 - tested & verified
Fixes:
- Subshell capture: all interactive UI output via >&2
- download_telemt: extract to dedicated dir (no find -newer)
- log_* functions: output to stderr
- confirm/select_option: output to stderr

Tested on VPS (Ubuntu 24.04): 14/14 tests PASS
- Quick mode install/start/restart/stop/remove
- Stealth config generation
- Backup/restore
- telemt update check
- Proxy link generation
2026-04-06 22:23:53 +03:00
anten-ka
1293b04430 Fix: download_telemt extract - use dedicated dir instead of find -newer 2026-04-06 22:11:46 +03:00
anten-ka
cc3f547273 Fix: subshell capture bug - interactive menus invisible in $()
- All UI output (echo, printf) in interactive functions now goes to stderr (>&2)
- Only return values go to stdout for $() capture
- Affected: select_quick_domain, select_port, select_category, select_template, show_template_preview
- Also fixed: log_*, confirm, select_option in common.sh
2026-04-06 22:05:20 +03:00
anten-ka
84bc15090a Add bootstrap.sh for private repo installation 2026-04-06 21:53:10 +03:00
anten-ka
c75ea15b31 GoTelegram v2.2.1
- Unified menu (15 items, 3 sections) with full bot management
- Fixed telemt download URL grep pattern
- QR cleanup in bot.py
- Security fixes (XSS, injection, validation)
- Backup/restore improvements
2026-04-06 21:50:02 +03:00
anten-ka
fee25f191e v2.2.1: unified menu with bot management, grouped sections, telemt download fix, QR cleanup, version bump 2026-04-06 21:40:34 +03:00
anten-ka
fe9e5fa019 v2.2.1 hotfix: fix telemt download grep pattern (grep -iE), add QR cleanup, bump version to 2.2.1 2026-04-06 21:32:00 +03:00
anten-ka
13cca51f5f v2.2.1 hotfix: fix telemt download grep pattern (grep -iE), add QR cleanup, bump version to 2.2.1 2026-04-06 21:30:30 +03:00
anten-ka
55134f4864 v2.2.1: clean branch — only v2.2.1 code with 11 security fixes 2026-04-06 21:06:28 +03:00
anten-ka
de60f710f5 v2.2.1: 11 security fixes (safe_edit_message, IP validation, os-release protection, config_get return codes, spinner stderr, domain check, mode sanitization, var init, XSS escape) 2026-04-06 20:49:49 +03:00
anten-ka
4985ec3679 v2.2.1: 11 security fixes (safe_edit_message, IP validation, os-release protection, config_get return codes, spinner stderr, domain check, mode sanitization, var init, XSS escape) 2026-04-06 20:49:31 +03:00
29 changed files with 23696 additions and 2486 deletions

18
.gitignore vendored
View File

@@ -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

View File

@@ -1,99 +0,0 @@
# Предложения по улучшению GoTelegram MTProxy
## Анализ текущего скрипта
Скрипт уже хорошо структурирован: проверка root, установка зависимостей, меню, работа с Docker (nineseconds/mtg). Ниже — конкретные улучшения.
---
## 1. Безопасность и надёжность
### 1.1 Валидация порта
- **Сейчас:** при выборе "свой порт" можно ввести нечисло или порт вне диапазона.
- **Предложение:** проверять число в диапазоне 165535 и повторять запрос при ошибке.
```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-бота.

View File

@@ -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 на сервере. Для разовой установки это допустимо; после установки можно отозвать токен или использовать токен с коротким сроком жизни.
- Не публикуйте готовую команду с реальным токеном в открытый доступ (сайт, чат, репо). Делитесь ею только с тем, кому доверяете установку на сервер.

View File

@@ -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` подставьте свой логин и имя репозитория.

View File

@@ -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 — никому (и ни в какой чат) их передавать не нужно.

126
bootstrap.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/bin/bash
# GoTelegram Pro — Bootstrap installer for private repo
# Downloads all files and launches install.sh
set -euo pipefail
REPO="anten-ka/gotelegram_pro"
BRANCH="${GOTELEGRAM_BRANCH:-test}"
PAT="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"
INSTALL_DIR="/opt/gotelegram"
# Use raw.githubusercontent.com (CDN) — faster and avoids Contents API caching
# issues that occasionally return 404 for recently added files on non-default branches.
RAW="https://raw.githubusercontent.com/${REPO}/${BRANCH}"
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m'
BOLD='\033[1m'
echo ""
echo -e " ${YELLOW}╔══════════════════════════════════════════════════════╗${NC}"
echo -e " ${YELLOW}${NC} ${BOLD}GoTelegram Pro — Установка${NC} ${YELLOW}${NC}"
echo -e " ${YELLOW}${NC} ${YELLOW}${NC}"
echo -e " ${YELLOW}${NC} MTProxy менеджер с Telegram-ботом ${YELLOW}${NC}"
echo -e " ${YELLOW}${NC} Stealth-режим, 1800+ шаблонов сайтов ${YELLOW}${NC}"
echo -e " ${YELLOW}╚══════════════════════════════════════════════════════╝${NC}"
echo ""
# Check root
if [ "$(id -u)" -ne 0 ]; then
echo -e " ${RED}${NC} Запустите от root: ${CYAN}sudo bash bootstrap.sh${NC}"
exit 1
fi
# Check dependencies
for cmd in curl jq; do
if ! command -v "$cmd" &>/dev/null; then
echo -e " ${CYAN}${NC} Установка $cmd..."
apt-get update -qq && apt-get install -y -qq "$cmd" >/dev/null 2>&1
fi
done
download_file() {
local remote_path="$1"
local local_path="$2"
local dir
dir=$(dirname "$local_path")
mkdir -p "$dir"
# Retry up to 3 times with short backoff to tolerate transient CDN hiccups
local attempt http_code
for attempt in 1 2 3; do
http_code=$(curl -sL -w "%{http_code}" -o "$local_path" \
-H "Authorization: token ${PAT}" \
"${RAW}/${remote_path}")
if [ "$http_code" = "200" ]; then
return 0
fi
sleep 1
done
echo -e " ${RED}${NC} Ошибка загрузки ${remote_path} (HTTP ${http_code})"
return 1
}
# File list
FILES=(
"install.sh"
"install_gotelegram_bot.sh"
"templates_catalog.json"
"lib/common.sh"
"lib/telemt.sh"
"lib/telemt_config.sh"
"lib/backup.sh"
"lib/website.sh"
"lib/templates_catalog.sh"
"lib/i18n.sh"
"lib/lang/en.sh"
"lib/lang/ru.sh"
"gotelegram-bot/bot.py"
"gotelegram-bot/i18n.py"
"gotelegram-bot/lang/en.json"
"gotelegram-bot/lang/ru.json"
"gotelegram-bot/config.example.env"
"gotelegram-bot/requirements.txt"
"gotelegram-bot/README.md"
)
echo -e " ${CYAN}${NC} Загрузка файлов в ${INSTALL_DIR}..."
mkdir -p "${INSTALL_DIR}/lib/lang" "${INSTALL_DIR}/gotelegram-bot/lang"
failed=0
for f in "${FILES[@]}"; do
if download_file "$f" "${INSTALL_DIR}/${f}"; then
echo -e " ${GREEN}${NC} ${f}"
else
failed=$((failed + 1))
fi
done
if [ "$failed" -gt 0 ]; then
echo ""
echo -e " ${RED}${NC} Не удалось загрузить ${failed} файл(ов)"
echo -e " ${YELLOW}Проверьте токен доступа и подключение к сети${NC}"
exit 1
fi
# Fix permissions and line endings
echo -e " ${CYAN}${NC} Настройка прав..."
chmod +x "${INSTALL_DIR}/install.sh" "${INSTALL_DIR}/install_gotelegram_bot.sh"
chmod +x "${INSTALL_DIR}"/lib/*.sh
sed -i 's/\r$//' "${INSTALL_DIR}/install.sh" "${INSTALL_DIR}/install_gotelegram_bot.sh" "${INSTALL_DIR}"/lib/*.sh "${INSTALL_DIR}"/lib/lang/*.sh 2>/dev/null || true
sed -i 's/\r$//' "${INSTALL_DIR}"/gotelegram-bot/*.py "${INSTALL_DIR}"/gotelegram-bot/lang/*.json 2>/dev/null || true
# Create symlink
ln -sf "${INSTALL_DIR}/install.sh" /usr/local/bin/gotelegram
echo -e " ${GREEN}${NC} Команда ${CYAN}gotelegram${NC} доступна"
echo ""
echo -e " ${GREEN}${NC} Установка завершена! Запуск..."
echo ""
# Launch
exec bash "${INSTALL_DIR}/install.sh"

View File

@@ -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"

View File

@@ -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
| Команда | Описание |
|--------|----------|
| `/start`, `/help` | Справка |
| `/install` | Установить или обновить прокси (выбор домена и порта) |
| `/status` | Статус и данные подключения (IP, порт, secret, ссылка) |
| `/link` | Только ссылка `tg://proxy` |
| `/restart` | Перезапустить контейнер |
| `/logs` | Последние логи контейнера |
| `/remove` | Удалить прокси |
| `/promo` | Промо хостинга |
- **Complete CLI Feature Parity** - All menu items from CLI version
- Install (Quick/Stealth modes)
- Status monitoring
- Proxy link generation
- Share with QR codes
- Service restart
- Logs viewing
- Mode/template changes
- Backup/restore
- 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
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)).
### Закрытый репозиторий (установка по ключу)
Для **приватного** репо используется клонирование по **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`
### Локально (файлы уже рядом со скриптом)
2. Create .env file:
```bash
sudo ./install_gotelegram_bot.sh
cp config.example.env .env
# Edit .env and set your BOT_TOKEN
nano .env
```
## Конфигурация
Файл: `/opt/gotelegram-bot/.env`
- **BOT_TOKEN** — токен от @BotFather (обязательно).
- **ALLOWED_IDS** — опционально. Список ID пользователей через запятую; если не задан, бот доступен всем.
После изменения `.env` перезапуск сервиса:
3. (Optional) Restrict access to specific users:
```bash
sudo systemctl restart gotelegram-bot
# Edit .env and uncomment ALLOWED_IDS
# ALLOWED_IDS=123456789,987654321
```
## Требования на сервере
- Linux (systemd), Docker, Python 3.
- Перед использованием бота на сервере должен быть установлен Docker (бот сам поднимает контейнер `nineseconds/mtg:2` по команде `/install`).
## Управление сервисом
### Running the Bot
```bash
sudo systemctl status gotelegram-bot
sudo systemctl restart gotelegram-bot
journalctl -u gotelegram-bot -f
python3 bot.py
```
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

View File

@@ -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
# Опционально: список ID пользователей с доступом (через запятую). Пусто = все.
# Comma-separated list of allowed Telegram user IDs
# Leave empty to allow all users
# ALLOWED_IDS=123456789,987654321

142
gotelegram-bot/i18n.py Normal file
View File

@@ -0,0 +1,142 @@
"""
GoTelegram v2.4 Bot — i18n module
Provides per-user language preferences and a simple t()/tf() API.
Usage:
from i18n import t, tf, set_user_lang, get_user_lang, get_language_name
msg = t(user_id, "menu_status")
msg = tf(user_id, "backup_created_fmt", filename)
Language files live next to this module in lang/<code>.json.
Per-user choices are persisted to USER_LANG_FILE (one JSON dict: user_id -> code).
"""
import json
import logging
import os
from pathlib import Path
from typing import Dict, Optional
logger = logging.getLogger(__name__)
# ── Paths ─────────────────────────────────────────────────────────────────
_MODULE_DIR = Path(__file__).resolve().parent
LANG_DIR = _MODULE_DIR / "lang"
USER_LANG_FILE = Path("/opt/gotelegram-bot/user_langs.json")
# Supported codes; keep in sync with lang/*.json
SUPPORTED_LANGS = ("en", "ru")
DEFAULT_LANG = os.getenv("BOT_LANG", "en").strip().lower() or "en"
if DEFAULT_LANG not in SUPPORTED_LANGS:
DEFAULT_LANG = "en"
LANG_NAMES = {
"en": "English",
"ru": "Русский",
}
# ── Caches ────────────────────────────────────────────────────────────────
_LANG_CACHE: Dict[str, Dict[str, str]] = {}
_USER_LANGS: Dict[int, str] = {}
_USER_LANGS_LOADED = False
def _load_lang_file(code: str) -> Dict[str, str]:
"""Load lang/<code>.json into the cache and return it."""
if code in _LANG_CACHE:
return _LANG_CACHE[code]
path = LANG_DIR / f"{code}.json"
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
raise ValueError("lang file must contain a top-level object")
_LANG_CACHE[code] = data
return data
except FileNotFoundError:
logger.warning("lang file not found: %s", path)
except Exception as e:
logger.warning("failed to load %s: %s", path, e)
_LANG_CACHE[code] = {}
return _LANG_CACHE[code]
def _load_user_langs() -> None:
"""Load per-user language preferences from USER_LANG_FILE."""
global _USER_LANGS, _USER_LANGS_LOADED
_USER_LANGS_LOADED = True
try:
if USER_LANG_FILE.exists():
with open(USER_LANG_FILE, "r", encoding="utf-8") as f:
raw = json.load(f)
if isinstance(raw, dict):
_USER_LANGS = {
int(k): v for k, v in raw.items()
if isinstance(v, str) and v in SUPPORTED_LANGS
}
except Exception as e:
logger.warning("failed to load user_langs: %s", e)
_USER_LANGS = {}
def _save_user_langs() -> None:
"""Persist per-user language preferences."""
try:
USER_LANG_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(USER_LANG_FILE, "w", encoding="utf-8") as f:
json.dump(
{str(k): v for k, v in _USER_LANGS.items()},
f, ensure_ascii=False, indent=2,
)
except Exception as e:
logger.warning("failed to save user_langs: %s", e)
# ── Public API ────────────────────────────────────────────────────────────
def get_user_lang(user_id: Optional[int]) -> str:
"""Return the language code for the given user (or DEFAULT_LANG)."""
if not _USER_LANGS_LOADED:
_load_user_langs()
if user_id is None:
return DEFAULT_LANG
return _USER_LANGS.get(int(user_id), DEFAULT_LANG)
def set_user_lang(user_id: int, code: str) -> bool:
"""Set the per-user language preference and persist it."""
if not _USER_LANGS_LOADED:
_load_user_langs()
code = (code or "").strip().lower()
if code not in SUPPORTED_LANGS:
return False
_USER_LANGS[int(user_id)] = code
_save_user_langs()
return True
def get_language_name(code: str) -> str:
return LANG_NAMES.get(code, code)
def t(user_id: Optional[int], key: str, default: Optional[str] = None) -> str:
"""Translate key for the given user. Falls back to English, then default/key."""
code = get_user_lang(user_id)
table = _load_lang_file(code)
if key in table:
return table[key]
if code != "en":
en_table = _load_lang_file("en")
if key in en_table:
return en_table[key]
return default if default is not None else key
def tf(user_id: Optional[int], key: str, *args, default: Optional[str] = None) -> str:
"""Format a translated string with positional args using %-formatting."""
template = t(user_id, key, default=default)
try:
return template % args if args else template
except (TypeError, ValueError):
return template

116
gotelegram-bot/lang/en.json Normal file
View File

@@ -0,0 +1,116 @@
{
"lang_english": "English",
"lang_russian": "Русский",
"lang_title": "🌐 Language",
"lang_current": "Current language: %s",
"lang_saved": "Language saved: %s",
"lang_choose": "Choose your language:",
"welcome_title": "GoTelegram v%s",
"welcome_subtitle": "🤖 MTProxy Management Bot",
"welcome_powered": "Powered by telemt engine",
"welcome_prompt": "Select an action from the menu below:",
"waiting_admin_title": "👋 Hi, %s!",
"waiting_admin_body": "The bot is not configured yet.\nYour Telegram ID: <code>%s</code>\n\nAssign you as administrator?",
"btn_yes": "✅ Yes",
"btn_no": "❌ No",
"access_denied": "⛔ Access denied.\nYour ID: <code>%s</code>",
"help_title": "GoTelegram Bot — Commands",
"help_lines": "/start — Main menu\n/help — This help\n/status — Quick status\n/logs — Latest logs\n/lang — Change language\n/addadmin ID — Add admin\n/deladmin ID — Remove admin\n\nUse the menu buttons for other operations.",
"menu_install": "⚙️ Install",
"menu_status": "📊 Status",
"menu_link": "🔗 Link",
"menu_share": "📤 Share",
"menu_restart": "🔄 Restart",
"menu_logs": "📋 Logs",
"menu_change": "⚡ Change Mode/Template",
"menu_backup": "💾 Backup",
"menu_restore": "↩️ Restore",
"menu_update": "📡 Update telemt",
"menu_website": "🌐 Website/SSL",
"menu_promo": "🎁 Promo",
"menu_stats": "📊 Traffic Stats",
"menu_remove": "🗑️ Remove",
"menu_admins": "👤 Admins",
"menu_credits": " Credits",
"menu_language": "🌐 Language",
"menu_close": "❌ Close",
"btn_back": "⬅️ Back",
"btn_refresh": "🔄 Refresh",
"btn_cancel": "❌ Cancel",
"btn_confirm": "✅ Confirm",
"status_checking": "⏳ Checking status...",
"status_title": "📊 Current Status",
"status_service": "Service",
"status_running": "✅ Running",
"status_stopped": "❌ Stopped",
"status_telemt": "Telemt",
"status_mode": "Mode",
"status_template": "Template",
"status_domain": "Domain",
"status_port": "Port",
"status_listen_port": "Listen Port",
"status_tls_domain": "TLS Domain",
"logs_failed": "Failed to retrieve logs",
"link_fetching": "⏳ Fetching link...",
"link_unavailable": "Link unavailable (proxy not installed?)",
"share_title": "📤 Share Proxy",
"share_body": "Send this link to your client:",
"restart_title": "🔄 Restart",
"restart_progress": "⏳ Restarting telemt...",
"restart_ok": "✅ telemt restarted",
"restart_fail": "❌ Restart failed",
"install_title": "⚙️ Install / Update",
"install_pick_mode": "Select installation mode:",
"install_mode_lite": "🚀 Lite (quick, no site)",
"install_mode_pro": "🎨 Pro (stealth + website)",
"backup_title": "💾 Backup",
"backup_creating": "⏳ Creating backup...",
"backup_created_fmt": "✅ Backup created: %s",
"backup_failed": "❌ Backup creation failed",
"backup_list_title": "Available backups:",
"backup_none": "No backups yet",
"backup_restore_title": "↩️ Restore backup",
"backup_restoring": "⏳ Restoring...",
"backup_restored": "✅ Backup restored",
"update_title": "📡 Update telemt",
"update_progress": "⏳ Updating telemt binary...",
"update_ok": "✅ telemt updated",
"update_fail": "❌ Update failed",
"website_title": "🌐 Website / SSL",
"ssl_renew_progress": "⏳ Renewing SSL...",
"ssl_renewed": "✅ SSL renewed",
"ssl_renew_fail": "❌ SSL renew failed",
"ssl_status_title": "🔒 SSL Status",
"remove_title": "🗑️ Remove",
"remove_warn": "⚠️ This will stop and remove telemt, nginx site and configs. Continue?",
"remove_progress": "⏳ Removing...",
"remove_done": "✅ Removed",
"admins_title": "👤 Administrators",
"admins_list": "Current admin IDs:",
"admins_empty": "No admins configured",
"promo_title": "🎁 Promo",
"credits_title": " Credits",
"cg_title": "🔗 Custom Git Template",
"cg_ask_url": "Send me the HTTPS git URL of a static site repository.\nOptionally append <code>@branch</code>.",
"cg_cloning": "⏳ Cloning %s ...",
"cg_invalid": "❌ Invalid URL. Only HTTPS git URLs are allowed.",
"cg_timeout": "❌ Clone timeout (repository too large or slow)",
"cg_too_big": "❌ Repository too large (>100MB)",
"cg_no_index": "❌ No index.html found in repository",
"cg_ok_fmt": "✅ Custom template downloaded: %s"
}

116
gotelegram-bot/lang/ru.json Normal file
View File

@@ -0,0 +1,116 @@
{
"lang_english": "English",
"lang_russian": "Русский",
"lang_title": "🌐 Язык",
"lang_current": "Текущий язык: %s",
"lang_saved": "Язык сохранён: %s",
"lang_choose": "Выберите язык:",
"welcome_title": "GoTelegram v%s",
"welcome_subtitle": "🤖 Бот управления MTProxy",
"welcome_powered": "На базе движка telemt",
"welcome_prompt": "Выберите действие в меню ниже:",
"waiting_admin_title": "👋 Привет, %s!",
"waiting_admin_body": "Бот ещё не настроен.\nВаш Telegram ID: <code>%s</code>\n\nНазначить вас администратором?",
"btn_yes": "✅ Да",
"btn_no": "❌ Нет",
"access_denied": "⛔ Доступ запрещён.\nВаш ID: <code>%s</code>",
"help_title": "GoTelegram Bot — Команды",
"help_lines": "/start — Главное меню\n/help — Эта справка\n/status — Быстрый статус\n/logs — Последние логи\n/lang — Сменить язык\n/addadmin ID — Добавить админа\n/deladmin ID — Удалить админа\n\nИспользуйте кнопки меню для остальных операций.",
"menu_install": "⚙️ Установить",
"menu_status": "📊 Статус",
"menu_link": "🔗 Ссылка",
"menu_share": "📤 Поделиться",
"menu_restart": "🔄 Перезапуск",
"menu_logs": "📋 Логи",
"menu_change": "⚡ Сменить режим/шаблон",
"menu_backup": "💾 Бекап",
"menu_restore": "↩️ Восстановить",
"menu_update": "📡 Обновить telemt",
"menu_website": "🌐 Сайт/SSL",
"menu_promo": "🎁 Промо",
"menu_stats": "📊 Трафик",
"menu_remove": "🗑️ Удалить",
"menu_admins": "👤 Админы",
"menu_credits": " О проекте",
"menu_language": "🌐 Язык",
"menu_close": "❌ Закрыть",
"btn_back": "⬅️ Назад",
"btn_refresh": "🔄 Обновить",
"btn_cancel": "❌ Отмена",
"btn_confirm": "✅ Подтвердить",
"status_checking": "⏳ Проверяю статус...",
"status_title": "📊 Текущий статус",
"status_service": "Сервис",
"status_running": "✅ Работает",
"status_stopped": "❌ Остановлен",
"status_telemt": "Telemt",
"status_mode": "Режим",
"status_template": "Шаблон",
"status_domain": "Домен",
"status_port": "Порт",
"status_listen_port": "Порт прослушивания",
"status_tls_domain": "TLS домен",
"logs_failed": "Не удалось получить логи",
"link_fetching": "⏳ Получаю ссылку...",
"link_unavailable": "Ссылка недоступна (прокси не установлен?)",
"share_title": "📤 Поделиться прокси",
"share_body": "Отправьте эту ссылку клиенту:",
"restart_title": "🔄 Перезапуск",
"restart_progress": "⏳ Перезапускаю telemt...",
"restart_ok": "✅ telemt перезапущен",
"restart_fail": "❌ Ошибка перезапуска",
"install_title": "⚙️ Установка / Обновление",
"install_pick_mode": "Выберите режим установки:",
"install_mode_lite": "🚀 Lite (быстро, без сайта)",
"install_mode_pro": "🎨 Pro (stealth + сайт)",
"backup_title": "💾 Бекап",
"backup_creating": "⏳ Создаю бекап...",
"backup_created_fmt": "✅ Бекап создан: %s",
"backup_failed": "❌ Не удалось создать бекап",
"backup_list_title": "Доступные бекапы:",
"backup_none": "Бекапов пока нет",
"backup_restore_title": "↩️ Восстановление бекапа",
"backup_restoring": "⏳ Восстанавливаю...",
"backup_restored": "✅ Бекап восстановлен",
"update_title": "📡 Обновление telemt",
"update_progress": "⏳ Обновляю telemt...",
"update_ok": "✅ telemt обновлён",
"update_fail": "❌ Ошибка обновления",
"website_title": "🌐 Сайт / SSL",
"ssl_renew_progress": "⏳ Обновляю SSL...",
"ssl_renewed": "✅ SSL обновлён",
"ssl_renew_fail": "❌ Ошибка обновления SSL",
"ssl_status_title": "🔒 Статус SSL",
"remove_title": "🗑️ Удаление",
"remove_warn": "⚠️ Это остановит и удалит telemt, сайт nginx и конфиги. Продолжить?",
"remove_progress": "⏳ Удаляю...",
"remove_done": "✅ Удалено",
"admins_title": "👤 Администраторы",
"admins_list": "Текущие ID админов:",
"admins_empty": "Админы не настроены",
"promo_title": "🎁 Промо",
"credits_title": " О проекте",
"cg_title": "🔗 Свой git-шаблон",
"cg_ask_url": "Отправьте HTTPS git-URL репозитория со статическим сайтом.\nПри желании добавьте <code>@branch</code>.",
"cg_cloning": "⏳ Клонирую %s ...",
"cg_invalid": "❌ Неверный URL. Разрешены только HTTPS git-URL.",
"cg_timeout": "❌ Таймаут клонирования (репозиторий слишком большой или медленный)",
"cg_too_big": "❌ Репозиторий слишком большой (>100МБ)",
"cg_no_index": "❌ В репозитории не найден index.html",
"cg_ok_fmt": "✅ Свой шаблон загружен: %s"
}

View File

@@ -1 +1,3 @@
python-telegram-bot>=21.0
python-dotenv>=1.0.0
toml>=0.10.2

2486
install.sh Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -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"

177
install_gotelegram_bot.sh Normal file → Executable file
View File

@@ -1,121 +1,100 @@
#!/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
# GoTelegram v2.2.1 — Установка Telegram-бота
# Создаёт venv, ставит зависимости, настраивает systemd
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
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" ...
GOTELEGRAM_DIR="/opt/gotelegram"
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}"
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Запустите с sudo.${NC}"
exit 1
fi
fi
if ! command -v docker &>/dev/null; then
echo -e "${YELLOW}[!] Docker не найден. Установите Docker для работы /install.${NC}"
fi
# Каталог бота
echo -e "${CYAN}╔═══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}${NC} ${GREEN}GoTelegram v2.2.1 — Установка бота${NC} ${CYAN}${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════════╝${NC}"
echo ""
# ── Python ───────────────────────────────────────────────────────────────────
if ! command -v python3 &>/dev/null; then
echo -e "${YELLOW}[*] Установка python3...${NC}"
if command -v apt-get &>/dev/null; then
apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv
elif command -v dnf &>/dev/null; then
dnf install -y -q python3 python3-pip
elif command -v yum &>/dev/null; then
yum install -y -q python3 python3-pip
fi
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/"
echo -e "${GREEN}[*] Копирование файлов бота...${NC}"
cp "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/"
cp "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/"
[ -f "$SCRIPT_DIR/gotelegram-bot/config.example.env" ] && cp "$SCRIPT_DIR/gotelegram-bot/config.example.env" "$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"
echo -e "${RED}Файлы бота не найдены в $SCRIPT_DIR/gotelegram-bot/${NC}"
exit 1
fi
fi
# venv и зависимости
if [ ! -d "$BOT_DIR/venv" ]; then
python3 -m venv "$BOT_DIR/venv"
# Копируем каталог шаблонов
if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then
mkdir -p "$GOTELEGRAM_DIR"
cp "$SCRIPT_DIR/templates_catalog.json" "$GOTELEGRAM_DIR/"
echo -e "${GREEN}[*] Каталог шаблонов скопирован${NC}"
fi
# ── Virtual environment ──────────────────────────────────────────────────────
if [ ! -d "$BOT_DIR/venv" ]; then
echo -e "${GREEN}[*] Создание виртуального окружения...${NC}"
python3 -m venv "$BOT_DIR/venv"
fi
echo -e "${GREEN}[*] Установка зависимостей...${NC}"
"$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}"
echo ""
echo -e "${YELLOW}Введите BOT_TOKEN от @BotFather:${NC}"
TOKEN=""
while [ -z "$TOKEN" ]; do
read -r TOKEN
TOKEN=$(echo "$TOKEN" | tr -d '[:space:]')
[ -z "$TOKEN" ] && echo -e "${RED}Токен не может быть пустым.${NC}"
done
echo -ne "${YELLOW}ID администратора (Enter = доступ для всех):${NC} "
read -r ADMIN_ID
{
echo "BOT_TOKEN=$TOKEN"
[ -n "$ADMIN_ID" ] && echo "ALLOWED_IDS=$ADMIN_ID"
} > "$BOT_DIR/.env"
chmod 600 "$BOT_DIR/.env"
echo -e "${GREEN}[*] .env создан${NC}"
else
echo -e "${GREEN}[*] .env уже есть.${NC}"
echo -e "${GREEN}[*] .env уже существует${NC}"
fi
# systemd
# ── Systemd ──────────────────────────────────────────────────────────────────
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
[Unit]
Description=GoTelegram MTProxy Bot
After=network.target docker.service
Description=GoTelegram v2.2.1 Telegram Bot
After=network.target
[Service]
Type=simple
@@ -132,8 +111,22 @@ 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
echo ""
echo -e "${GREEN}╔═══════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ ✅ Бот установлен и запущен! ║${NC}"
echo -e "${GREEN}╚═══════════════════════════════════════════╝${NC}"
echo ""
echo -e "Проверка: ${CYAN}systemctl status $SERVICE_NAME${NC}"
echo -e "Логи: ${CYAN}journalctl -u $SERVICE_NAME -f${NC}"
echo -e "Настройки: ${CYAN}$BOT_DIR/.env${NC}"
echo ""
# Благодарности
echo -e "${CYAN}─────────────────────────────────────────────${NC}"
echo -e "💜 Спасибо авторам открытых проектов:"
echo -e " ${CYAN}telemt${NC} — MTProxy engine (Rust)"
echo -e " ${CYAN}HTML5 UP${NC} — шаблоны сайтов (CC BY 3.0)"
echo -e " ${CYAN}learning-zone${NC} — 150+ HTML5 шаблонов"
echo -e " ${CYAN}Start Bootstrap${NC} — Bootstrap шаблоны (MIT)"
echo -e "${CYAN}─────────────────────────────────────────────${NC}"

View File

@@ -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

373
lib/backup.sh Executable file
View File

@@ -0,0 +1,373 @@
#!/bin/bash
# GoTelegram v2.4 — backup and restore (i18n-aware)
# ── Создание бекапа ──────────────────────────────────────────────────────────
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 "$(_t_or backup_collecting 'Собираю конфигурацию...')"
# 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
# Language marker (i18n)
if [ -f "$GOTELEGRAM_DIR/.language" ]; then
cp "$GOTELEGRAM_DIR/.language" "$tmp_dir/.language"
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 "$(_t_or backup_site_included 'Шаблон сайта включён')"
fi
# Метаданные
local ip mode engine lang port domain
ip=$(get_server_ip)
mode=$(config_get mode 2>/dev/null || echo "unknown")
engine=$(config_get engine 2>/dev/null || echo "telemt")
lang=$(type get_language &>/dev/null && get_language 2>/dev/null || echo "en")
port=$(config_get port 2>/dev/null || echo "443")
# Ensure port is numeric; fall back to 443 if garbage
[[ "$port" =~ ^[0-9]+$ ]] || port=443
domain=$(config_get domain 2>/dev/null || echo "")
cat > "$tmp_dir/metadata.json" << EOMETA
{
"backup_version": "1.1",
"gotelegram_version": "$GOTELEGRAM_VERSION",
"created_at": "$(date -Iseconds)",
"hostname": "$(hostname)",
"ip": "$ip",
"engine": "$engine",
"mode": "$mode",
"language": "$lang",
"port": $port,
"domain": "$domain"
}
EOMETA
# Архивируем
local tar_file="/tmp/${backup_name}.tar.gz"
if ! tar czf "$tar_file" -C /tmp "$backup_name" 2>/dev/null; then
log_error "$(_t_or backup_archive_err 'Ошибка создания архива')"
rm -rf "$tmp_dir"
rm -f "$tar_file"
return 1
fi
if [ ! -f "$tar_file" ]; then
log_error "$(_t_or backup_archive_missing 'Архив не создан')"
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 "$(_t_or backup_encrypt_err 'Ошибка шифрования')"
rm -f "$tar_file"
rm -rf "$tmp_dir"
return 1
fi
rm -f "$tar_file"
log_success "$(_t_or backup_encrypted 'Бекап зашифрован (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)
if type tf &>/dev/null; then
log_success "$(tf backup_created_fmt "$final_file" "$size")"
else
log_success "Бекап создан: $final_file ($size)"
fi
echo "$final_file"
return 0
}
# ── Восстановление из бекапа ────────────────────────────────────────────────
restore_backup() {
local backup_file="$1"
local password="$2"
if [ ! -f "$backup_file" ]; then
if type tf &>/dev/null; then
log_error "$(tf backup_file_not_found_fmt "$backup_file")"
else
log_error "Файл не найден: $backup_file"
fi
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 " $(_t_or backup_enter_pass 'Введите пароль от бекапа'): "
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 "$(_t_or backup_bad_pass 'Неверный пароль или повреждённый файл')"
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 "$(_t_or backup_extract_err 'Ошибка распаковки архива')"
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_lang bk_date
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")
bk_lang=$(jq -r '.language // "-"' "$backup_dir/metadata.json")
bk_date=$(jq -r '.created_at // "-"' "$backup_dir/metadata.json")
echo ""
echo -e " ${BOLD}${WHITE}📦 $(_t_or backup_label 'Бекап'):${NC}"
echo -e " $(_t_or backup_version_label 'Версия'): $bk_version | $(_t_or backup_mode_label 'Режим'): $bk_mode | IP: $bk_ip | $(_t_or backup_lang_label 'Язык'): $bk_lang"
echo -e " $(_t_or backup_date_label 'Дата'): $bk_date"
echo ""
fi
if ! confirm "$(_t_or backup_confirm_restore 'Восстановить конфигурацию? Текущие настройки будут перезаписаны.')"; 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 "$(_t_or backup_restored_telemt 'telemt конфиг восстановлен')"
fi
# Восстанавливаем GoTelegram конфиг
if [ -f "$backup_dir/gotelegram.json" ]; then
mkdir -p "$GOTELEGRAM_DIR"
cp "$backup_dir/gotelegram.json" "$GOTELEGRAM_CONFIG"
log_success "$(_t_or backup_restored_gotelegram 'GoTelegram конфиг восстановлен')"
fi
# Восстанавливаем language marker (i18n)
if [ -f "$backup_dir/.language" ]; then
mkdir -p "$GOTELEGRAM_DIR"
cp "$backup_dir/.language" "$GOTELEGRAM_DIR/.language"
log_success "$(_t_or backup_restored_lang 'Язык интерфейса восстановлен')"
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 "$(_t_or backup_restored_nginx '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 "$(_t_or backup_restored_ssl '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 "$(_t_or backup_restored_site 'Шаблон сайта восстановлен')"
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 "$(_t_or backup_restore_done 'Восстановление завершено!')"
show_proxy_info
return 0
}
# ── Список бекапов ───────────────────────────────────────────────────────────
list_backups() {
if [ ! -d "$BACKUP_DIR" ] || [ -z "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]; then
log_info "$(_t_or backup_none 'Бекапов нет')"
return 1
fi
echo ""
echo -e " ${BOLD}${WHITE}📦 $(_t_or backup_list_title 'Доступные бекапы'):${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
if type tf &>/dev/null; then
log_dim "$(tf backup_cleanup_fmt "$to_delete" "$keep")"
else
log_dim "Удалено $to_delete старых бекапов (оставлено $keep)"
fi
fi
}
# ── Интерактивный бекап ──────────────────────────────────────────────────────
interactive_backup() {
echo ""
echo -e " ${BOLD}${WHITE}💾 $(_t_or backup_create_title 'Создание бекапа')${NC}"
echo -ne " $(_t_or backup_encrypt_prompt 'Зашифровать бекап паролем?') [Y/n]: "
read -r use_pass
local password=""
if [[ ! "$use_pass" =~ ^[Nn] ]]; then
echo -ne " $(_t_or backup_enter_pass 'Введите пароль'): "
read -rs password
echo ""
echo -ne " $(_t_or backup_repeat_pass 'Повторите пароль'): "
read -rs password2
echo ""
if [ "$password" != "$password2" ]; then
log_error "$(_t_or backup_pass_mismatch 'Пароли не совпадают')"
return 1
fi
if [ ${#password} -lt 6 ]; then
log_error "$(_t_or backup_pass_short 'Пароль слишком короткий (минимум 6 символов)')"
return 1
fi
fi
create_backup "$password"
cleanup_old_backups
}
# ── Интерактивное восстановление ─────────────────────────────────────────────
interactive_restore() {
list_backups || return 1
echo -ne " $(_t_or backup_pick_prompt 'Номер бекапа (или путь к файлу)'): "
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 "$(_t_or backup_not_found 'Бекап не найден')"
return 1
fi
restore_backup "$backup_file"
}

504
lib/common.sh Executable file
View File

@@ -0,0 +1,504 @@
#!/bin/bash
# GoTelegram v2.4 — common utilities
# Colors, logging, spinner, system helpers, v1 compat, i18n-aware
# ── Version ───────────────────────────────────────────────────────────────────
GOTELEGRAM_VERSION="2.4.0"
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
}
# ── Spinner ──────────────────────────────────────────────────────────────────
_spin_pid=""
spinner_start() {
local default_msg
default_msg=$(type t &>/dev/null && t wait || echo "Please wait...")
local msg="${1:-$default_msg}"
(
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
local err_label
err_label=$(type t &>/dev/null && t error || echo "error")
log_error "$label ${RED}(${err_label}, code: $rc)${NC}"
if [ -s "$err_file" ]; then
log_dim " $(head -3 "$err_file")"
fi
fi
rm -f "$err_file"
return $rc
}
# ── Banner ───────────────────────────────────────────────────────────────────
show_banner() {
local line
line=$(printf '━%.0s' $(seq 1 60))
echo ""
echo -e "${CYAN}${line}${NC}"
if type tf &>/dev/null; then
echo -e " ${BOLD}${WHITE}🚀 $(tf banner_title "$GOTELEGRAM_VERSION")${NC}"
echo -e " ${DIM}$(t banner_subtitle)${NC}"
echo -e " ${DIM}$(t banner_features)${NC}"
else
echo -e " ${BOLD}${WHITE}🚀 GoTelegram v${GOTELEGRAM_VERSION}${NC}"
echo -e " ${DIM}MTProxy powered by telemt (Rust + Tokio)${NC}"
echo -e " ${DIM}Anti-DPI • Fake TLS • TCP Splice • JA3/JA4${NC}"
fi
echo -e "${CYAN}${line}${NC}"
echo ""
}
# ── Credits ──────────────────────────────────────────────────────────────────
show_credits() {
local line
line=$(printf '─%.0s' $(seq 1 60))
echo ""
echo -e "${MAGENTA}${line}${NC}"
echo -e " ${BOLD}$(type t &>/dev/null && t credits_title || echo 'Credits')${NC}"
echo -e "${MAGENTA}${line}${NC}"
echo -e " ${WHITE}telemt${NC} — MTProxy engine (Rust)"
echo -e " ${DIM}github.com/telemt/telemt${NC}"
echo ""
echo -e " ${WHITE}HTML5 UP${NC} — responsive HTML/CSS templates"
echo -e " ${DIM}html5up.net • CC BY 3.0 • @ajlkn${NC}"
echo ""
echo -e " ${WHITE}learning-zone${NC} — 150+ HTML5 templates"
echo -e " ${DIM}github.com/learning-zone/website-templates${NC}"
echo ""
echo -e " ${WHITE}Start Bootstrap${NC} — MIT license"
echo -e " ${DIM}startbootstrap.com${NC}"
echo -e "${MAGENTA}${line}${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
}
_t_or() {
# Helper: translate if i18n available, otherwise return fallback
local key="$1" fallback="$2"
if type t &>/dev/null; then
t "$key"
else
echo "$fallback"
fi
}
check_root() {
if [ "$EUID" -ne 0 ]; then
log_error "$(_t_or err_need_root 'Run the script with sudo / as root')"
exit 1
fi
}
check_os() {
if [ ! -f /etc/os-release ]; then
log_error "$(_t_or err_os_unknown 'Failed to detect OS. Linux is required.')"
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 contains suspicious strings, skipping"
return 0
fi
. /etc/os-release
case "$ID" in
ubuntu|debian|centos|rocky|almalinux|fedora|rhel)
log_dim "OS: $PRETTY_NAME"
return 0
;;
*)
log_warning "OS $ID may be incompatible. Supported: 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 "$(_t_or err_bad_pkg_mgr 'Unknown package manager')"; return 1 ;;
esac
}
ensure_deps() {
local missing=()
for cmd in curl jq openssl git qrencode; do
if ! command -v "$cmd" &>/dev/null; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
if type tf &>/dev/null; then
log_step "$(tf deps_installing "${missing[*]}")"
else
log_step "Installing dependencies: ${missing[*]}"
fi
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
if type tf &>/dev/null; then
log_error "$(tf err_low_disk "$avail_mb" "$min_mb")"
else
log_error "Low disk space: ${avail_mb}MB (need ${min_mb}MB+)"
fi
return 1
fi
return 0
}
# ── Конфигурация GoTelegram (JSON) ──────────────────────────────────────────
save_gotelegram_config() {
mkdir -p "$(dirname "$GOTELEGRAM_CONFIG")"
local cur_lang
cur_lang=$(type get_language &>/dev/null && get_language || echo en)
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:-}",
"language": "${cur_lang}",
"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
return 2 # file missing
fi
local val
val=$(jq -r ".$key // empty" "$GOTELEGRAM_CONFIG" 2>/dev/null)
if [ $? -ne 0 ]; then
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 "$(_t_or v1_migration_step 'Migrating from v1 (mtg) to 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 "Failed to extract secret from v1. A new one will be generated."
return 1
fi
echo ""
echo -e " ${WHITE}$(_t_or v1_found_title 'Found v1 (mtg) installation:')${NC}"
if type tf &>/dev/null; then
echo -e " $(tf v1_port "$old_port")"
echo -e " $(tf v1_secret "${old_secret:0:16}")"
else
echo -e " Port: ${CYAN}${old_port}${NC}"
echo -e " Secret: ${CYAN}${old_secret:0:16}...${NC}"
fi
echo ""
echo -e " ${YELLOW}$(_t_or warning 'Warning'):${NC} $(_t_or v1_incompatible 'mtg secret is NOT directly compatible with telemt.')"
echo -e " $(_t_or v1_new_link 'Clients will need a new link.')"
echo ""
echo -ne " $(_t_or v1_stop_migrate 'Stop v1 container and migrate to v2? [Y/n]:') "
read -r ans
if [[ "$ans" =~ ^[Nn] ]]; then
log_info "$(_t_or v1_migration_cancelled 'Migration cancelled. v1 left intact.')"
return 1
fi
# Stop v1
log_info "$(_t_or v1_stopping 'Stopping v1 container...')"
docker stop "$V1_CONTAINER_NAME" 2>/dev/null
docker rm "$V1_CONTAINER_NAME" 2>/dev/null
# Backup v1 config
if [ -f "$V1_CONFIG_FILE" ]; then
mkdir -p "$GOTELEGRAM_DIR"
cp "$V1_CONFIG_FILE" "$GOTELEGRAM_DIR/v1_backup_proxy.json" 2>/dev/null
if type tf &>/dev/null; then
log_success "$(tf v1_config_saved "$GOTELEGRAM_DIR/v1_backup_proxy.json")"
else
log_success "v1 config saved to $GOTELEGRAM_DIR/v1_backup_proxy.json"
fi
fi
if type tf &>/dev/null; then
log_success "$(tf v1_port_freed "$old_port")"
else
log_success "v1 stopped. Port $old_port freed."
fi
return 0
}
# ── Confirm prompt ───────────────────────────────────────────────────────────
confirm() {
local default_msg
default_msg=$(_t_or install_continue_anyway 'Continue?')
local msg="${1:-$default_msg}"
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}$(_t_or choose 'Choose'):${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
}

140
lib/i18n.sh Executable file
View File

@@ -0,0 +1,140 @@
#!/bin/bash
# GoTelegram v2.4 — i18n engine
# Internationalization support: EN (English) / RU (Русский)
#
# Usage:
# source lib/i18n.sh
# load_language "ru" # or "en"
# echo "$(t menu_install)" # translated string
# printf "$(t greeting)\n" "$name" # with format args
# ── Global i18n state ──
declare -gA I18N
LANG_CODE="${LANG_CODE:-en}"
LANG_FILE=""
# ── Load a language ──
# Sources lib/lang/${lang}.sh into the I18N associative array.
# Falls back to English if requested language file is missing.
load_language() {
local lang="${1:-en}"
# Sanitize: only allow [a-z]{2} codes
if ! [[ "$lang" =~ ^[a-z]{2}$ ]]; then
lang="en"
fi
local lang_dir
lang_dir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/lang"
local lang_file="${lang_dir}/${lang}.sh"
if [ ! -f "$lang_file" ]; then
lang_file="${lang_dir}/en.sh"
lang="en"
fi
if [ -f "$lang_file" ]; then
# Clear previous keys then source the new language
I18N=()
# shellcheck disable=SC1090
source "$lang_file"
LANG_CODE="$lang"
LANG_FILE="$lang_file"
return 0
fi
return 1
}
# ── Translate: fetch value by key ──
# t <key> → echoes translation (or key if missing)
t() {
local key="$1"
local val="${I18N[$key]:-}"
if [ -z "$val" ]; then
# Fallback to key name so missing translations are visible
echo "$key"
else
echo "$val"
fi
}
# ── Translate + printf-style formatting ──
# tf <key> <arg1> <arg2> ...
tf() {
local key="$1"
shift
local fmt="${I18N[$key]:-$key}"
# shellcheck disable=SC2059
printf "$fmt" "$@"
}
# ── Get current language code ──
get_language() {
echo "$LANG_CODE"
}
# ── Detect saved language from config.json, default en ──
detect_language() {
local cfg="${GOTELEGRAM_CONFIG:-/opt/gotelegram/config.json}"
local lang=""
if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then
lang=$(jq -r '.language // empty' "$cfg" 2>/dev/null)
fi
# Also check marker file (language set before config.json exists)
if [ -z "$lang" ]; then
local marker="${GOTELEGRAM_DIR:-/opt/gotelegram}/.language"
if [ -f "$marker" ]; then
lang=$(head -c 2 "$marker" 2>/dev/null | tr -d '[:space:]')
fi
fi
# Sanitize
if ! [[ "$lang" =~ ^(en|ru)$ ]]; then
lang="en"
fi
echo "$lang"
}
# ── Persist selected language ──
# Saves to config.json if present, otherwise to marker file
save_language() {
local lang="$1"
if ! [[ "$lang" =~ ^(en|ru)$ ]]; then
return 1
fi
mkdir -p "${GOTELEGRAM_DIR:-/opt/gotelegram}" 2>/dev/null
# Always write marker for early-access (before config.json exists)
echo "$lang" > "${GOTELEGRAM_DIR:-/opt/gotelegram}/.language" 2>/dev/null
local cfg="${GOTELEGRAM_CONFIG:-/opt/gotelegram/config.json}"
if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then
local tmp
tmp=$(mktemp) || return 1
if jq --arg lang "$lang" '. + {language: $lang}' "$cfg" > "$tmp" 2>/dev/null; then
mv "$tmp" "$cfg"
chmod 600 "$cfg"
else
rm -f "$tmp"
fi
fi
return 0
}
# ── First-run interactive language picker ──
# Shows a minimal, language-agnostic picker (keeps it culture-neutral).
# Returns the chosen code via echo.
pick_language_interactive() {
echo "" >&2
echo " ┌──────────────────────────────────────────┐" >&2
echo " │ Select language / Выберите язык │" >&2
echo " ├──────────────────────────────────────────┤" >&2
echo " │ 1) English │" >&2
echo " │ 2) Русский │" >&2
echo " └──────────────────────────────────────────┘" >&2
echo -n " > " >&2
local ch
read -r ch
case "$ch" in
1|en|EN|english|English) echo "en" ;;
2|ru|RU|russian|Russian|русский) echo "ru" ;;
*) echo "en" ;;
esac
}

375
lib/lang/en.sh Executable file
View File

@@ -0,0 +1,375 @@
#!/bin/bash
# GoTelegram v2.4 — English translations
# shellcheck disable=SC2034,SC2148
# ── Common words ────────────────────────────────────────────────────────
I18N[yes]="Yes"
I18N[no]="No"
I18N[ok]="OK"
I18N[cancel]="Cancel"
I18N[back]="« Back"
I18N[exit]="Exit"
I18N[skip]="Skip"
I18N[choose]="Choose"
I18N[press_enter]="Press Enter..."
I18N[press_enter_to_return]="Press Enter to return to menu..."
I18N[invalid_choice]="Invalid choice"
I18N[running]="running"
I18N[stopped]="stopped"
I18N[not_installed]="not installed"
I18N[unknown]="unknown"
I18N[error]="Error"
I18N[warning]="Warning"
I18N[info]="Info"
I18N[success]="Done"
I18N[wait]="Please wait..."
# ── Banner ──────────────────────────────────────────────────────────────
I18N[banner_title]="GoTelegram v%s"
I18N[banner_subtitle]="MTProxy powered by telemt (Rust + Tokio)"
I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4"
I18N[credits_title]="Credits / Thanks"
# ── Main menu (dashboard) ───────────────────────────────────────────────
I18N[dashboard_title]="Control panel"
I18N[svc_proxy]="Proxy"
I18N[svc_nginx]="nginx"
I18N[svc_site]="Site"
I18N[svc_ssl]="SSL"
I18N[svc_bot]="Bot"
I18N[ssl_until]="until %s"
I18N[net_ip]="IP:"
I18N[net_port]="Port:"
I18N[net_mode]="Mode:"
I18N[net_domain]="Domain:"
I18N[connection_link]="Telegram connection link:"
I18N[proxy_not_configured]="Proxy is not configured. Select option 1."
I18N[menu_proxy]="Proxy ▸"
I18N[menu_stats]="Statistics ▸"
I18N[menu_manage]="Management ▸"
I18N[menu_telegram_bot]="Telegram bot ▸"
I18N[menu_about]="About ▸"
I18N[auto_refresh_30s]="Refresh in 30 sec"
# ── Submenu: Proxy ──────────────────────────────────────────────────────
I18N[submenu_proxy_title]="🚀 PROXY"
I18N[proxy_install_update]="Install / Update"
I18N[proxy_status_detail]="Detailed status"
I18N[proxy_copy_link]="Copy link"
I18N[proxy_share]="Share key"
I18N[proxy_restart]="Restart"
I18N[proxy_logs]="Logs"
I18N[proxy_change_mode]="Change mode / template"
# ── Submenu: Manage ─────────────────────────────────────────────────────
I18N[submenu_manage_title]="⚙️ MANAGEMENT"
I18N[manage_backup]="Backup"
I18N[manage_restore]="Restore"
I18N[manage_update_telemt]="Update telemt"
I18N[manage_site_ssl]="Site / SSL"
I18N[manage_remove]="Remove"
I18N[manage_language]="Language / Язык"
# ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" ABOUT"
I18N[about_version_info]="Version info"
I18N[about_promo]="Promo / Donate"
I18N[version_title]="🔍 Information"
I18N[version_label]="GoTelegram:"
I18N[version_engine]="Engine:"
I18N[version_tech]="Technology:"
I18N[version_license]="License:"
# ── Install flow ────────────────────────────────────────────────────────
I18N[install_select_mode]="🎭 Select masquerade mode:"
I18N[install_lite_title]="⚡ Lite — masquerade as popular website"
I18N[install_lite_desc1]="Fast, no domain needed. telemt disguises traffic"
I18N[install_lite_desc2]="as the chosen site (google.com etc.)"
I18N[install_pro_title]="🛡 Pro — your own site + full masquerade"
I18N[install_pro_desc1]="nginx + SSL + HTML template + telemt."
I18N[install_pro_desc2]="DPI sees a real website with a real certificate."
I18N[install_pro_desc3]="Requires: a domain pointing to this server."
I18N[install_mode_choice]="Choice (1/2):"
I18N[install_bad_choice]="Invalid choice: %s"
I18N[install_lite_step]="Installing Lite mode"
I18N[install_pro_step]="Installing Pro mode"
I18N[install_enter_domain]="Enter your domain (e.g. example.com):"
I18N[install_bad_domain]="Invalid domain: %s"
I18N[install_dns_mismatch]="Domain %s points to %s, not to %s"
I18N[install_continue_anyway]="Continue anyway?"
I18N[install_enter_email]="Email for SSL (Enter = no email):"
I18N[install_config_title]="📋 Configuration:"
I18N[install_cfg_ip]="IP:"
I18N[install_cfg_port]="Port:"
I18N[install_cfg_mask]="Masquerade:"
I18N[install_cfg_mode]="Mode:"
I18N[install_cfg_domain]="Domain:"
I18N[install_confirm_proxy]="Install proxy?"
I18N[install_confirm_proxy_site]="Install proxy + website?"
I18N[install_done]="GoTelegram v%s installed! (%s mode)"
I18N[install_arch_desc1]="telemt accepts all traffic on 443 (HTTPS masquerade)"
I18N[install_arch_desc2]="nginx serves the site on internal port %s"
I18N[install_arch_desc3]="ISP only sees HTTPS traffic to %s:443"
# ── Change mode/template ────────────────────────────────────────────────
I18N[change_current_mode]="Current mode:"
I18N[change_template]="Change site template (pro only)"
I18N[change_mode_switch]="Switch mode (lite ↔ pro)"
I18N[change_only_pro]="Template change is available in pro mode only"
I18N[change_requires_reinstall]="Mode switch requires reinstall."
I18N[change_reinstall_confirm]="Reinstall proxy?"
# ── Logs ────────────────────────────────────────────────────────────────
I18N[logs_telemt_title]="📋 telemt logs (last %s lines):"
# ── Link / Share ────────────────────────────────────────────────────────
I18N[link_title]="🔗 Connection link:"
I18N[share_title]="📤 Forward this message:"
I18N[share_line1]="🔐 MTProxy for Telegram (GoTelegram v%s)"
I18N[share_server]="🌍 Server: %s"
I18N[share_port]="🔌 Port: %s"
I18N[share_connect_cta]="👉 Connect with one tap:"
I18N[share_footer]="Just tap the link or configure manually."
# ── Website ─────────────────────────────────────────────────────────────
I18N[website_title]="🌐 Website management"
I18N[website_domain]="Domain:"
I18N[website_ssl_until]="SSL until:"
I18N[website_only_pro]="Website management is available in pro mode only"
I18N[website_renew_ssl]="Renew SSL certificate"
I18N[website_restart_nginx]="Restart nginx"
I18N[website_change_template]="Change template"
# ── Remove ──────────────────────────────────────────────────────────────
I18N[remove_title]="🗑 Remove GoTelegram"
I18N[remove_proxy_only]="Remove proxy only (telemt)"
I18N[remove_bot_only]="Remove Telegram bot only"
I18N[remove_all]="Remove everything (proxy + bot + settings)"
I18N[remove_warn_proxy]="This will remove the proxy and all its settings."
I18N[remove_confirm_proxy]="Remove proxy?"
I18N[remove_backup_before]="Create a backup before removal?"
I18N[remove_warn_all]="This will remove EVERYTHING: proxy, bot, site, settings."
I18N[remove_confirm_all]="Are you absolutely sure?"
I18N[remove_proxy_done]="Proxy removed"
I18N[remove_all_done]="GoTelegram fully removed (proxy + bot)"
# ── Telegram bot submenu ────────────────────────────────────────────────
I18N[bot_title]="🤖 Telegram bot"
I18N[bot_status_running]="● Running"
I18N[bot_status_stopped]="○ Stopped"
I18N[bot_status_not_installed]="✗ Not installed"
I18N[bot_menu_status]="📊 Bot status"
I18N[bot_menu_logs]="📋 Bot logs"
I18N[bot_menu_restart]="🔄 Restart bot"
I18N[bot_menu_stop]="⏹ Stop bot"
I18N[bot_menu_start]="▶️ Start bot"
I18N[bot_menu_settings]="⚙️ Settings (.env)"
I18N[bot_menu_remove]="🗑 Remove bot"
I18N[bot_menu_install]="🔧 Install bot"
I18N[bot_intro1]="The bot lets you manage the proxy from Telegram:"
I18N[bot_intro2]="status, restart, change mode, backup, QR code."
I18N[bot_install_step]="Installing Telegram bot"
I18N[bot_install_python]="Installing Python3..."
I18N[bot_files_not_found]="Bot files not found in %s"
I18N[bot_create_venv]="Creating virtual environment..."
I18N[bot_install_deps]="Installing dependencies..."
I18N[bot_enter_token]="Enter BOT_TOKEN from @BotFather:"
I18N[bot_token_empty]="Token cannot be empty"
I18N[bot_token]="Token:"
I18N[bot_add_admin_how]="How to add the administrator?"
I18N[bot_admin_auto]="Auto — bot will capture the ID on first /start"
I18N[bot_admin_manual]="Manual — enter the ID now"
I18N[bot_admin_ids_prompt]="Admin IDs (space or comma separated):"
I18N[bot_env_created]=".env created"
I18N[bot_env_exists]=".env already exists, settings preserved"
I18N[bot_wait_admin_title]="Waiting for administrator"
I18N[bot_wait_admin_msg1]="Open the bot in Telegram and send"
I18N[bot_wait_admin_msg2]="The bot will automatically make you an admin"
I18N[bot_wait_admin_skip]="Press Ctrl+C to skip"
I18N[bot_wait_spinner]="Waiting... send /start to the bot (%d sec)"
I18N[bot_admin_assigned]="Administrator assigned!"
I18N[bot_wait_skipped]="Skipped. Add admin later via: menu → Telegram bot → Settings"
I18N[bot_wait_timeout]="Timeout (5 min). Add admin via: menu → Telegram bot → Settings"
I18N[bot_installed]="Bot installed and running!"
I18N[bot_status_title]="📊 Telegram bot status"
I18N[bot_token_configured]="configured"
I18N[bot_access_open]="all users"
I18N[bot_logs_title]="📋 Bot logs (last 30 lines):"
I18N[bot_settings_title]="⚙️ Bot settings"
I18N[bot_current_env]="Current .env:"
I18N[bot_change_token]="Change BOT_TOKEN"
I18N[bot_change_allowed]="Change ALLOWED_IDS"
I18N[bot_new_token]="New BOT_TOKEN:"
I18N[bot_token_empty_err]="Empty token"
I18N[bot_token_updated]="Token updated, bot restarted"
I18N[bot_allowed_prompt]="ALLOWED_IDS (space or comma separated, empty = auto):"
I18N[bot_access_updated]="Access updated, bot restarted"
I18N[bot_remove_warn]="This will remove the Telegram bot and all its settings."
I18N[bot_remove_confirm]="Remove bot?"
I18N[bot_removed]="Bot fully removed"
I18N[bot_restarted]="Bot restarted"
I18N[bot_stopped]="Bot stopped"
I18N[bot_started]="Bot started"
I18N[bot_status_colon]="Status:"
I18N[bot_access_colon]="Access:"
I18N[bot_access_ids_fmt]="ID: %s"
# ── Promo / Donate ──────────────────────────────────────────────────────
I18N[promo_host1_title]="💰 HOSTING #1 — UP TO 60% OFF"
I18N[promo_host2_title]="💰 HOSTING #2 — UP TO 60% OFF"
I18N[promo_tips_title]="☕ Donate / Tips"
I18N[promo_link_label]="Link:"
I18N[promo_off60]="60%% discount on the first month"
I18N[promo_ant20]="20%% + 3%% when paid for 3 months"
I18N[promo_ant6]="15%% + 5%% when paid for 6 months"
I18N[promo_qr_host1]="── QR: Hosting #1 ──"
I18N[promo_qr_host2]="── QR: Hosting #2 ──"
I18N[promo_qr_tips]="── QR: Donate / Tips ──"
I18N[promo_menu_in]="Menu in %d sec..."
# ── Stats ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Traffic statistics"
I18N[stats_module_missing]="Statistics module not loaded."
I18N[stats_file_missing]="File lib/stats.sh not found."
I18N[stats_toggle]="Toggle counter (now: %s)"
I18N[stats_install_collector]="Install/update stats collector"
I18N[stats_auto_refresh]="Refresh every 3 sec"
I18N[stats_on]="on"
I18N[stats_off]="off"
# ── Templates catalog ───────────────────────────────────────────────────
I18N[templates_categories]="📂 Site template categories:"
I18N[templates_custom_git]="📎 Custom template from git URL"
I18N[templates_random]="🎲 Random template"
I18N[templates_count_fmt]="(%d templates)"
I18N[templates_list]="📋 %s — available templates:"
I18N[templates_preview_title]="🔍 Template preview:"
I18N[templates_name]="Name:"
I18N[templates_source]="Source:"
I18N[templates_description]="Description:"
I18N[templates_preview]="👁 Preview:"
I18N[templates_preview_hint]="Open the link in a browser to preview the template"
I18N[templates_repo]="📦 Repo:"
I18N[templates_thanks]="💜 Thanks to the authors of %s for the open source code!"
I18N[templates_install_this]="Install this template?"
I18N[templates_cat_empty]="No templates in this category"
I18N[templates_downloading]="Downloading template \"%s\"..."
I18N[templates_downloaded]="Template \"%s\" downloaded"
I18N[templates_downloaded_subfolder]="Template \"%s\" downloaded (from subfolder)"
I18N[templates_no_index]="Template does not contain index.html"
I18N[templates_path]="Path: %s"
I18N[templates_catalog_not_found]="Templates catalog not found: %s"
# ── Custom git template ─────────────────────────────────────────────────
I18N[custom_git_title]="📎 CUSTOM TEMPLATE FROM GIT URL"
I18N[custom_git_help_1]="You can use ANY public static HTML repository as a template."
I18N[custom_git_help_2]="The repository must be public and contain a ready-made"
I18N[custom_git_help_3]="index.html (build via npm is NOT performed)."
I18N[custom_git_formats]="Supported URL formats:"
I18N[custom_git_fmt_github]=" • https://github.com/user/repo"
I18N[custom_git_fmt_gitlab]=" • https://gitlab.com/user/repo"
I18N[custom_git_fmt_gitext]=" • https://example.com/user/repo.git"
I18N[custom_git_fmt_branch]=" • https://github.com/user/repo@branch (branch after @)"
I18N[custom_git_auto_detect]="Repository structure (auto-detection):"
I18N[custom_git_auto_1]=" 1. index.html in repo root"
I18N[custom_git_auto_2]=" 2. dist/index.html (StartBootstrap, Vite, webpack)"
I18N[custom_git_auto_3]=" 3. public/ or build/ or _site/ or site/ or docs/"
I18N[custom_git_auto_4]=" 4. Fallback: search index.html across whole repo"
I18N[custom_git_requirements]="Requirements:"
I18N[custom_git_req_1]=" • HTTPS only (ssh:// and git:// are blocked)"
I18N[custom_git_req_2]=" • Public repositories only"
I18N[custom_git_req_3]=" • Repo size up to 100 MB"
I18N[custom_git_req_4]=" • Static HTML (no PHP/Python/Node server code)"
I18N[custom_git_examples]="Tested example repos:"
I18N[custom_git_ex_1]=" • https://github.com/html5up-collective/strata"
I18N[custom_git_ex_2]=" • https://github.com/StartBootstrap/startbootstrap-landing-page"
I18N[custom_git_enter_url]="Paste git URL (or Enter to cancel):"
I18N[custom_git_empty]="No URL provided, cancelled"
I18N[custom_git_bad_url]="Invalid URL. Only https:// addresses are accepted"
I18N[custom_git_cloning]="Cloning repository..."
I18N[custom_git_clone_failed]="Failed to clone repository: %s"
I18N[custom_git_too_big]="Repository is too large: %s (limit 100MB)"
I18N[custom_git_scanning]="Scanning for index.html..."
I18N[custom_git_found_at]="✓ Found index.html in: %s"
I18N[custom_git_no_index]="index.html not found in the repository"
I18N[custom_git_installed]="Custom template installed from %s"
I18N[custom_git_saved]="Template URL saved in config (menu → Site → Update from git)"
# ── First-run language picker ───────────────────────────────────────────
I18N[lang_picker_title]="Select language / Выберите язык"
I18N[lang_english]="English"
I18N[lang_russian]="Русский"
I18N[lang_saved]="Language saved: %s"
I18N[lang_change_prompt]="Select a new language:"
# ── Backup ──────────────────────────────────────────────────────────────
I18N[backup_title]="💾 Backup"
I18N[backup_creating]="Creating backup..."
I18N[backup_created]="Backup created: %s"
I18N[backup_failed]="Backup creation failed"
I18N[backup_restore_title]="↩️ Restore from backup"
I18N[backup_no_files]="No backup files"
I18N[backup_select]="Select a backup to restore:"
I18N[backup_restoring]="Restoring..."
I18N[backup_restored]="Backup restored"
I18N[backup_collecting]="Collecting configuration..."
I18N[backup_site_included]="Website template included"
I18N[backup_archive_err]="Archive creation failed"
I18N[backup_archive_missing]="Archive not created"
I18N[backup_encrypt_err]="Encryption failed"
I18N[backup_encrypted]="Backup encrypted (AES-256-CBC)"
I18N[backup_created_fmt]="Backup created: %s (%s)"
I18N[backup_file_not_found_fmt]="File not found: %s"
I18N[backup_enter_pass]="Enter backup password"
I18N[backup_bad_pass]="Wrong password or corrupted file"
I18N[backup_extract_err]="Archive extraction failed"
I18N[backup_label]="Backup"
I18N[backup_version_label]="Version"
I18N[backup_mode_label]="Mode"
I18N[backup_lang_label]="Language"
I18N[backup_date_label]="Date"
I18N[backup_confirm_restore]="Restore configuration? Current settings will be overwritten."
I18N[backup_restored_telemt]="telemt config restored"
I18N[backup_restored_gotelegram]="GoTelegram config restored"
I18N[backup_restored_lang]="Interface language restored"
I18N[backup_restored_nginx]="nginx config restored"
I18N[backup_restored_ssl]="SSL certificates restored"
I18N[backup_restored_site]="Website template restored"
I18N[backup_restore_done]="Restore completed!"
I18N[backup_none]="No backups"
I18N[backup_list_title]="Available backups"
I18N[backup_cleanup_fmt]="Removed %s old backups (kept %s)"
I18N[backup_create_title]="Create backup"
I18N[backup_encrypt_prompt]="Encrypt backup with a password?"
I18N[backup_repeat_pass]="Repeat password"
I18N[backup_pass_mismatch]="Passwords do not match"
I18N[backup_pass_short]="Password too short (minimum 6 characters)"
I18N[backup_pick_prompt]="Backup number (or path to file)"
I18N[backup_not_found]="Backup not found"
# ── Errors / misc ───────────────────────────────────────────────────────
I18N[err_need_root]="Run the script with sudo / as root"
I18N[err_os_unknown]="Failed to detect OS. Linux is required."
I18N[err_low_disk]="Low disk space: %sMB (need %sMB+)"
I18N[err_bad_pkg_mgr]="Unknown package manager"
I18N[err_unexpected]="Unexpected error"
I18N[bye]="See you later! 👋"
I18N[auto_refresh]="Refresh in 30 sec"
# ── Deps ────────────────────────────────────────────────────────────────
I18N[deps_installing]="Installing dependencies: %s"
# ── Migration ───────────────────────────────────────────────────────────
I18N[v1_detected]="⚠️ GoTelegram v1 (mtg) installation detected"
I18N[v1_container]="Container: %s"
I18N[v1_migration_step]="Migrating from v1 (mtg) to v2 (telemt)"
I18N[v1_found_title]="Found v1 (mtg) installation:"
I18N[v1_port]="Port: %s"
I18N[v1_secret]="Secret: %s..."
I18N[v1_incompatible]="mtg secret is NOT directly compatible with telemt."
I18N[v1_new_link]="Clients will need a new link."
I18N[v1_stop_migrate]="Stop v1 container and migrate to v2? [Y/n]:"
I18N[v1_migration_cancelled]="Migration cancelled. v1 left intact."
I18N[v1_stopping]="Stopping v1 container..."
I18N[v1_config_saved]="v1 config saved to %s"
I18N[v1_port_freed]="v1 stopped. Port %s freed."

375
lib/lang/ru.sh Executable file
View File

@@ -0,0 +1,375 @@
#!/bin/bash
# GoTelegram v2.4 — Russian translations
# shellcheck disable=SC2034,SC2148
# ── Common words ────────────────────────────────────────────────────────
I18N[yes]="Да"
I18N[no]="Нет"
I18N[ok]="OK"
I18N[cancel]="Отмена"
I18N[back]="« Назад"
I18N[exit]="Выход"
I18N[skip]="Пропустить"
I18N[choose]="Выбор"
I18N[press_enter]="Нажмите Enter..."
I18N[press_enter_to_return]="Нажмите Enter для возврата в меню..."
I18N[invalid_choice]="Неверный выбор"
I18N[running]="работает"
I18N[stopped]="остановлен"
I18N[not_installed]="не установлен"
I18N[unknown]="неизвестно"
I18N[error]="Ошибка"
I18N[warning]="Внимание"
I18N[info]="Инфо"
I18N[success]="Готово"
I18N[wait]="Подождите..."
# ── Banner ──────────────────────────────────────────────────────────────
I18N[banner_title]="GoTelegram v%s"
I18N[banner_subtitle]="MTProxy на ядре telemt (Rust + Tokio)"
I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4"
I18N[credits_title]="Благодарности / Credits"
# ── Main menu (dashboard) ───────────────────────────────────────────────
I18N[dashboard_title]="Панель управления"
I18N[svc_proxy]="Прокси"
I18N[svc_nginx]="nginx"
I18N[svc_site]="Сайт"
I18N[svc_ssl]="SSL"
I18N[svc_bot]="Бот"
I18N[ssl_until]="до %s"
I18N[net_ip]="IP:"
I18N[net_port]="Порт:"
I18N[net_mode]="Режим:"
I18N[net_domain]="Домен:"
I18N[connection_link]="Ссылка для Telegram:"
I18N[proxy_not_configured]="Прокси не настроен. Выберите пункт 1."
I18N[menu_proxy]="Прокси ▸"
I18N[menu_stats]="Статистика ▸"
I18N[menu_manage]="Управление ▸"
I18N[menu_telegram_bot]="Telegram-бот ▸"
I18N[menu_about]="О программе ▸"
I18N[auto_refresh_30s]="Обновление через 30 сек"
# ── Submenu: Proxy ──────────────────────────────────────────────────────
I18N[submenu_proxy_title]="🚀 ПРОКСИ"
I18N[proxy_install_update]="Установить / Обновить"
I18N[proxy_status_detail]="Статус подробно"
I18N[proxy_copy_link]="Скопировать ссылку"
I18N[proxy_share]="Поделиться ключом"
I18N[proxy_restart]="Перезапуск"
I18N[proxy_logs]="Логи"
I18N[proxy_change_mode]="Сменить режим / шаблон"
# ── Submenu: Manage ─────────────────────────────────────────────────────
I18N[submenu_manage_title]="⚙️ УПРАВЛЕНИЕ"
I18N[manage_backup]="Бекап"
I18N[manage_restore]="Восстановить"
I18N[manage_update_telemt]="Обновить telemt"
I18N[manage_site_ssl]="Сайт / SSL"
I18N[manage_remove]="Удалить"
I18N[manage_language]="Язык / Language"
# ── Submenu: About ──────────────────────────────────────────────────────
I18N[submenu_about_title]=" О ПРОГРАММЕ"
I18N[about_version_info]="Информация о версии"
I18N[about_promo]="Промо / Донат"
I18N[version_title]="🔍 Информация"
I18N[version_label]="GoTelegram:"
I18N[version_engine]="Ядро:"
I18N[version_tech]="Технология:"
I18N[version_license]="Лицензия:"
# ── Install flow ────────────────────────────────────────────────────────
I18N[install_select_mode]="🎭 Выберите режим маскировки:"
I18N[install_lite_title]="⚡ Lite — маскировка под популярный сайт"
I18N[install_lite_desc1]="Быстро, без домена. telemt маскирует трафик"
I18N[install_lite_desc2]="под выбранный сайт (google.com и т.д.)"
I18N[install_pro_title]="🛡 Pro — свой сайт + полная маскировка"
I18N[install_pro_desc1]="nginx + SSL + HTML-шаблон + telemt."
I18N[install_pro_desc2]="DPI видит реальный сайт с реальным сертификатом."
I18N[install_pro_desc3]="Требует: домен, направленный на этот сервер."
I18N[install_mode_choice]="Выбор (1/2):"
I18N[install_bad_choice]="Неверный выбор: %s"
I18N[install_lite_step]="Установка Lite-режима"
I18N[install_pro_step]="Установка Pro-режима"
I18N[install_enter_domain]="Введите ваш домен (например, example.com):"
I18N[install_bad_domain]="Некорректный домен: %s"
I18N[install_dns_mismatch]="Домен %s указывает на %s, а не на %s"
I18N[install_continue_anyway]="Продолжить всё равно?"
I18N[install_enter_email]="Email для SSL (Enter = без email):"
I18N[install_config_title]="📋 Конфигурация:"
I18N[install_cfg_ip]="IP:"
I18N[install_cfg_port]="Порт:"
I18N[install_cfg_mask]="Маскировка:"
I18N[install_cfg_mode]="Режим:"
I18N[install_cfg_domain]="Домен:"
I18N[install_confirm_proxy]="Установить прокси?"
I18N[install_confirm_proxy_site]="Установить прокси + сайт?"
I18N[install_done]="GoTelegram v%s установлен! (%s-режим)"
I18N[install_arch_desc1]="telemt принимает весь трафик на 443 (маскировка под HTTPS)"
I18N[install_arch_desc2]="nginx обслуживает сайт на внутреннем порту %s"
I18N[install_arch_desc3]="Провайдер видит только HTTPS-трафик к %s:443"
# ── Change mode/template ────────────────────────────────────────────────
I18N[change_current_mode]="Текущий режим:"
I18N[change_template]="Сменить шаблон сайта (только pro)"
I18N[change_mode_switch]="Переключить режим (lite ↔ pro)"
I18N[change_only_pro]="Смена шаблона доступна только в pro-режиме"
I18N[change_requires_reinstall]="Переключение режима требует переустановки."
I18N[change_reinstall_confirm]="Переустановить прокси?"
# ── Logs ────────────────────────────────────────────────────────────────
I18N[logs_telemt_title]="📋 Логи telemt (последние %s строк):"
# ── Link / Share ────────────────────────────────────────────────────────
I18N[link_title]="🔗 Ссылка для подключения:"
I18N[share_title]="📤 Перешлите это сообщение:"
I18N[share_line1]="🔐 MTProxy для Telegram (GoTelegram v%s)"
I18N[share_server]="🌍 Сервер: %s"
I18N[share_port]="🔌 Порт: %s"
I18N[share_connect_cta]="👉 Подключиться одним нажатием:"
I18N[share_footer]="Просто нажмите на ссылку или настройте вручную."
# ── Website ─────────────────────────────────────────────────────────────
I18N[website_title]="🌐 Управление сайтом"
I18N[website_domain]="Домен:"
I18N[website_ssl_until]="SSL до:"
I18N[website_only_pro]="Управление сайтом доступно только в pro-режиме"
I18N[website_renew_ssl]="Обновить SSL сертификат"
I18N[website_restart_nginx]="Перезапустить nginx"
I18N[website_change_template]="Сменить шаблон"
# ── Remove ──────────────────────────────────────────────────────────────
I18N[remove_title]="🗑 Удаление GoTelegram"
I18N[remove_proxy_only]="Удалить только прокси (telemt)"
I18N[remove_bot_only]="Удалить только Telegram-бота"
I18N[remove_all]="Удалить всё (прокси + бот + настройки)"
I18N[remove_warn_proxy]="Это удалит прокси и все его настройки."
I18N[remove_confirm_proxy]="Удалить прокси?"
I18N[remove_backup_before]="Сделать бекап перед удалением?"
I18N[remove_warn_all]="Это удалит ВСЁ: прокси, бот, сайт, настройки."
I18N[remove_confirm_all]="Вы точно уверены?"
I18N[remove_proxy_done]="Прокси удалён"
I18N[remove_all_done]="GoTelegram полностью удалён (прокси + бот)"
# ── Telegram bot submenu ────────────────────────────────────────────────
I18N[bot_title]="🤖 Telegram-бот"
I18N[bot_status_running]="● Работает"
I18N[bot_status_stopped]="○ Остановлен"
I18N[bot_status_not_installed]="✗ Не установлен"
I18N[bot_menu_status]="📊 Статус бота"
I18N[bot_menu_logs]="📋 Логи бота"
I18N[bot_menu_restart]="🔄 Перезапустить бота"
I18N[bot_menu_stop]="⏹ Остановить бота"
I18N[bot_menu_start]="▶️ Запустить бота"
I18N[bot_menu_settings]="⚙️ Настройки (.env)"
I18N[bot_menu_remove]="🗑 Удалить бота"
I18N[bot_menu_install]="🔧 Установить бота"
I18N[bot_intro1]="Бот позволяет управлять прокси прямо из Telegram:"
I18N[bot_intro2]="статус, перезапуск, смена режима, бекап, QR-код."
I18N[bot_install_step]="Установка Telegram-бота"
I18N[bot_install_python]="Установка Python3..."
I18N[bot_files_not_found]="Файлы бота не найдены в %s"
I18N[bot_create_venv]="Создание виртуального окружения..."
I18N[bot_install_deps]="Установка зависимостей..."
I18N[bot_enter_token]="Введите BOT_TOKEN от @BotFather:"
I18N[bot_token_empty]="Токен не может быть пустым"
I18N[bot_token]="Token:"
I18N[bot_add_admin_how]="Как добавить администратора?"
I18N[bot_admin_auto]="Автоматически — бот определит ID при первом /start"
I18N[bot_admin_manual]="Вручную — ввести ID сейчас"
I18N[bot_admin_ids_prompt]="ID администраторов (через пробел/запятую):"
I18N[bot_env_created]=".env создан"
I18N[bot_env_exists]=".env уже существует, настройки сохранены"
I18N[bot_wait_admin_title]="Ожидание администратора"
I18N[bot_wait_admin_msg1]="Откройте бота в Telegram и отправьте"
I18N[bot_wait_admin_msg2]="Бот автоматически назначит вас администратором"
I18N[bot_wait_admin_skip]="Нажмите Ctrl+C чтобы пропустить"
I18N[bot_wait_spinner]="Ожидание... напишите /start боту (%d сек)"
I18N[bot_admin_assigned]="Администратор назначен!"
I18N[bot_wait_skipped]="Пропущено. Добавить админа позже: меню → Telegram-бот → Настройки"
I18N[bot_wait_timeout]="Таймаут (5 мин). Добавить админа: меню → Telegram-бот → Настройки"
I18N[bot_installed]="Бот установлен и запущен!"
I18N[bot_status_title]="📊 Статус Telegram-бота"
I18N[bot_token_configured]="настроен"
I18N[bot_access_open]="все пользователи"
I18N[bot_logs_title]="📋 Логи бота (последние 30 строк):"
I18N[bot_settings_title]="⚙️ Настройки бота"
I18N[bot_current_env]="Текущий .env:"
I18N[bot_change_token]="Сменить BOT_TOKEN"
I18N[bot_change_allowed]="Изменить ALLOWED_IDS"
I18N[bot_new_token]="Новый BOT_TOKEN:"
I18N[bot_token_empty_err]="Пустой токен"
I18N[bot_token_updated]="Токен обновлён, бот перезапущен"
I18N[bot_allowed_prompt]="ALLOWED_IDS (через пробел/запятую, пусто = авто):"
I18N[bot_access_updated]="Доступ обновлён, бот перезапущен"
I18N[bot_remove_warn]="Это удалит Telegram-бота и все его настройки."
I18N[bot_remove_confirm]="Удалить бота?"
I18N[bot_removed]="Бот полностью удалён"
I18N[bot_restarted]="Бот перезапущен"
I18N[bot_stopped]="Бот остановлен"
I18N[bot_started]="Бот запущен"
I18N[bot_status_colon]="Статус:"
I18N[bot_access_colon]="Доступ:"
I18N[bot_access_ids_fmt]="ID: %s"
# ── Promo / Donate ──────────────────────────────────────────────────────
I18N[promo_host1_title]="💰 ХОСТИНГ #1 — СКИДКА ДО 60%"
I18N[promo_host2_title]="💰 ХОСТИНГ #2 — СКИДКА ДО 60%"
I18N[promo_tips_title]="☕ Донат / Чаевые"
I18N[promo_link_label]="Ссылка:"
I18N[promo_off60]="60%% скидки на первый месяц"
I18N[promo_ant20]="20%% + 3%% при оплате за 3 месяца"
I18N[promo_ant6]="15%% + 5%% при оплате за 6 месяцев"
I18N[promo_qr_host1]="── QR: Хостинг #1 ──"
I18N[promo_qr_host2]="── QR: Хостинг #2 ──"
I18N[promo_qr_tips]="── QR: Чаевые / Донат ──"
I18N[promo_menu_in]="Меню через %d сек..."
# ── Stats ───────────────────────────────────────────────────────────────
I18N[stats_title]="📊 Статистика трафика"
I18N[stats_module_missing]="Модуль статистики не загружен."
I18N[stats_file_missing]="Файл lib/stats.sh не найден."
I18N[stats_toggle]="Вкл/Выкл подсчёт (сейчас: %s)"
I18N[stats_install_collector]="Установить/обновить сборщик статистики"
I18N[stats_auto_refresh]="Обновление каждые 3 сек"
I18N[stats_on]="вкл"
I18N[stats_off]="выкл"
# ── Templates catalog ───────────────────────────────────────────────────
I18N[templates_categories]="📂 Категории шаблонов сайтов:"
I18N[templates_custom_git]="📎 Свой шаблон по git URL"
I18N[templates_random]="🎲 Случайный шаблон"
I18N[templates_count_fmt]="(%d шаблонов)"
I18N[templates_list]="📋 %s — доступные шаблоны:"
I18N[templates_preview_title]="🔍 Превью шаблона:"
I18N[templates_name]="Название:"
I18N[templates_source]="Источник:"
I18N[templates_description]="Описание:"
I18N[templates_preview]="👁 Превью:"
I18N[templates_preview_hint]="Откройте ссылку в браузере для просмотра шаблона"
I18N[templates_repo]="📦 Репо:"
I18N[templates_thanks]="💜 Спасибо авторам %s за открытый код!"
I18N[templates_install_this]="Установить этот шаблон?"
I18N[templates_cat_empty]="В этой категории нет шаблонов"
I18N[templates_downloading]="Скачивание шаблона \"%s\"..."
I18N[templates_downloaded]="Шаблон \"%s\" скачан"
I18N[templates_downloaded_subfolder]="Шаблон \"%s\" скачан (из подпапки)"
I18N[templates_no_index]="Шаблон не содержит index.html"
I18N[templates_path]="Путь: %s"
I18N[templates_catalog_not_found]="Каталог шаблонов не найден: %s"
# ── Custom git template ─────────────────────────────────────────────────
I18N[custom_git_title]="📎 СВОЙ ШАБЛОН ПО GIT URL"
I18N[custom_git_help_1]="Вы можете использовать ЛЮБОЙ репозиторий со статическим HTML-сайтом"
I18N[custom_git_help_2]="в качестве шаблона. Репозиторий должен быть публичным и содержать"
I18N[custom_git_help_3]="готовый index.html (сборка через npm НЕ выполняется)."
I18N[custom_git_formats]="Поддерживаемые форматы URL:"
I18N[custom_git_fmt_github]=" • https://github.com/user/repo"
I18N[custom_git_fmt_gitlab]=" • https://gitlab.com/user/repo"
I18N[custom_git_fmt_gitext]=" • https://example.com/user/repo.git"
I18N[custom_git_fmt_branch]=" • https://github.com/user/repo@branch (ветка после @)"
I18N[custom_git_auto_detect]="Структура репозитория (авто-определение):"
I18N[custom_git_auto_1]=" 1. index.html в корне репозитория"
I18N[custom_git_auto_2]=" 2. dist/index.html (StartBootstrap, Vite, webpack)"
I18N[custom_git_auto_3]=" 3. public/ или build/ или _site/ или site/ или docs/"
I18N[custom_git_auto_4]=" 4. Fallback: поиск index.html по всему репозиторию"
I18N[custom_git_requirements]="Требования:"
I18N[custom_git_req_1]=" • Только HTTPS (ssh:// и git:// блокируются)"
I18N[custom_git_req_2]=" • Только публичные репозитории"
I18N[custom_git_req_3]=" • Размер репо не более 100 МБ"
I18N[custom_git_req_4]=" • Статический HTML (без серверного кода PHP/Python/Node)"
I18N[custom_git_examples]="Примеры проверенных репо:"
I18N[custom_git_ex_1]=" • https://github.com/html5up-collective/strata"
I18N[custom_git_ex_2]=" • https://github.com/StartBootstrap/startbootstrap-landing-page"
I18N[custom_git_enter_url]="Вставьте git URL (или Enter для отмены):"
I18N[custom_git_empty]="URL не указан, отмена"
I18N[custom_git_bad_url]="Недопустимый URL. Принимаются только https:// адреса"
I18N[custom_git_cloning]="Клонирование репозитория..."
I18N[custom_git_clone_failed]="Не удалось клонировать репозиторий: %s"
I18N[custom_git_too_big]="Репозиторий слишком большой: %s (лимит 100MB)"
I18N[custom_git_scanning]="Поиск index.html в структуре..."
I18N[custom_git_found_at]="✓ Найден index.html в: %s"
I18N[custom_git_no_index]="index.html не найден в репозитории"
I18N[custom_git_installed]="Свой шаблон установлен из %s"
I18N[custom_git_saved]="URL шаблона сохранён в конфиге (меню → Сайт → Обновить из git)"
# ── First-run language picker ───────────────────────────────────────────
I18N[lang_picker_title]="Выберите язык / Select language"
I18N[lang_english]="English"
I18N[lang_russian]="Русский"
I18N[lang_saved]="Язык сохранён: %s"
I18N[lang_change_prompt]="Выберите новый язык:"
# ── Backup ──────────────────────────────────────────────────────────────
I18N[backup_title]="💾 Бекап"
I18N[backup_creating]="Создание бекапа..."
I18N[backup_created]="Бекап создан: %s"
I18N[backup_failed]="Ошибка создания бекапа"
I18N[backup_restore_title]="↩️ Восстановление из бекапа"
I18N[backup_no_files]="Нет файлов бекапа"
I18N[backup_select]="Выберите бекап для восстановления:"
I18N[backup_restoring]="Восстановление..."
I18N[backup_restored]="Бекап восстановлен"
I18N[backup_collecting]="Собираю конфигурацию..."
I18N[backup_site_included]="Шаблон сайта включён"
I18N[backup_archive_err]="Ошибка создания архива"
I18N[backup_archive_missing]="Архив не создан"
I18N[backup_encrypt_err]="Ошибка шифрования"
I18N[backup_encrypted]="Бекап зашифрован (AES-256-CBC)"
I18N[backup_created_fmt]="Бекап создан: %s (%s)"
I18N[backup_file_not_found_fmt]="Файл не найден: %s"
I18N[backup_enter_pass]="Введите пароль от бекапа"
I18N[backup_bad_pass]="Неверный пароль или повреждённый файл"
I18N[backup_extract_err]="Ошибка распаковки архива"
I18N[backup_label]="Бекап"
I18N[backup_version_label]="Версия"
I18N[backup_mode_label]="Режим"
I18N[backup_lang_label]="Язык"
I18N[backup_date_label]="Дата"
I18N[backup_confirm_restore]="Восстановить конфигурацию? Текущие настройки будут перезаписаны."
I18N[backup_restored_telemt]="telemt конфиг восстановлен"
I18N[backup_restored_gotelegram]="GoTelegram конфиг восстановлен"
I18N[backup_restored_lang]="Язык интерфейса восстановлен"
I18N[backup_restored_nginx]="nginx конфиг восстановлен"
I18N[backup_restored_ssl]="SSL сертификаты восстановлены"
I18N[backup_restored_site]="Шаблон сайта восстановлен"
I18N[backup_restore_done]="Восстановление завершено!"
I18N[backup_none]="Бекапов нет"
I18N[backup_list_title]="Доступные бекапы"
I18N[backup_cleanup_fmt]="Удалено %s старых бекапов (оставлено %s)"
I18N[backup_create_title]="Создание бекапа"
I18N[backup_encrypt_prompt]="Зашифровать бекап паролем?"
I18N[backup_repeat_pass]="Повторите пароль"
I18N[backup_pass_mismatch]="Пароли не совпадают"
I18N[backup_pass_short]="Пароль слишком короткий (минимум 6 символов)"
I18N[backup_pick_prompt]="Номер бекапа (или путь к файлу)"
I18N[backup_not_found]="Бекап не найден"
# ── Errors / misc ───────────────────────────────────────────────────────
I18N[err_need_root]="Запустите скрипт с sudo / от root"
I18N[err_os_unknown]="Не удалось определить ОС. Требуется Linux."
I18N[err_low_disk]="Мало места на диске: %sMB (нужно %sMB+)"
I18N[err_bad_pkg_mgr]="Неизвестный пакетный менеджер"
I18N[err_unexpected]="Неожиданная ошибка"
I18N[bye]="До встречи! 👋"
I18N[auto_refresh]="Обновление через 30 сек"
# ── Deps ────────────────────────────────────────────────────────────────
I18N[deps_installing]="Установка зависимостей: %s"
# ── Migration ───────────────────────────────────────────────────────────
I18N[v1_detected]="⚠️ Обнаружена установка GoTelegram v1 (mtg)"
I18N[v1_container]="Контейнер: %s"
I18N[v1_migration_step]="Миграция с v1 (mtg) на v2 (telemt)"
I18N[v1_found_title]="Найдена установка v1 (mtg):"
I18N[v1_port]="Порт: %s"
I18N[v1_secret]="Secret: %s..."
I18N[v1_incompatible]="секрет mtg НЕ совместим с telemt напрямую."
I18N[v1_new_link]="Клиентам потребуется новая ссылка."
I18N[v1_stop_migrate]="Остановить v1 контейнер и перейти на v2? [Y/n]:"
I18N[v1_migration_cancelled]="Миграция отменена. v1 оставлен без изменений."
I18N[v1_stopping]="Остановка v1 контейнера..."
I18N[v1_config_saved]="Конфиг v1 сохранён в %s"
I18N[v1_port_freed]="v1 остановлен. Порт %s освобождён."

327
lib/telemt.sh Executable file
View 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 полностью удалён"
}

346
lib/telemt_config.sh Executable file
View File

@@ -0,0 +1,346 @@
#!/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
# telemt v3: добавляем ключ в секцию [access.users]
# Формат: name = "secret" под блоком [access.users]
if grep -q '\[access\.users\]' "$config"; then
sed -i "/\[access\.users\]/a ${name} = \"${secret}\"" "$config"
else
cat >> "$config" << EOSECRET
[access.users]
${name} = "${secret}"
EOSECRET
fi
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 server="${1:-$(get_server_ip)}"
local port="${2:-443}"
local secret="$3"
local mask_host="${4:-}"
# Если указан mask_host (fake-TLS), формируем ee-секрет
if [ -n "$mask_host" ]; then
local domain_hex
domain_hex=$(printf '%s' "$mask_host" | xxd -p | tr -d '\n')
secret="ee${secret}${domain_hex}"
fi
echo "tg://proxy?server=${server}&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 "")
# Генерация ссылки: оба режима используют ee-секрет с mask_host
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
link=$(generate_proxy_link "$domain" "$port" "$secret" "$domain")
else
link=$(generate_proxy_link "$ip" "$port" "$secret" "$mask_host")
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
}

595
lib/templates_catalog.sh Executable file
View File

@@ -0,0 +1,595 @@
#!/bin/bash
# GoTelegram v2.4 — website templates catalog
# Pick from ~1800 templates, preview links, git sparse-checkout downloads,
# + custom git URL templates (user-supplied public repos)
CATALOG_FILE="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")/templates_catalog.json"
TEMPLATES_CACHE="/tmp/gotelegram_templates"
# Custom git template limits
CUSTOM_GIT_MAX_SIZE_MB=100
CUSTOM_GIT_CLONE_TIMEOUT=90
# ── Catalog loading ────────────────────────────────────────────────────
load_catalog() {
if [ ! -f "$CATALOG_FILE" ]; then
if type tf &>/dev/null; then
log_error "$(tf templates_catalog_not_found "$CATALOG_FILE")"
else
log_error "Templates catalog not found: $CATALOG_FILE"
fi
return 1
fi
return 0
}
# ── Categories ─────────────────────────────────────────────────────────
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
}
# ── Templates in a category ────────────────────────────────────────────
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
}
# ── Template info ──────────────────────────────────────────────────────
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
}
# ── Interactive category picker (returns category id or special __custom_git__/__random__) ──
select_category() {
load_catalog || return 1
echo "" >&2
echo -e " ${BOLD}${WHITE}$(t templates_categories)${NC}" >&2
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
# First item: custom git URL template
printf " ${CYAN}%2d)${NC} ${GREEN}%s${NC}\n" 1 "$(t templates_custom_git)" >&2
local cats=()
local i=2
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}$(tf templates_count_fmt "$count")${NC}\n" "$i" "$name" >&2
cats+=("$id")
((i++))
done < <(get_categories)
printf " ${CYAN}%2d)${NC} %s\n" "$i" "$(t templates_random)" >&2
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
echo -ne " ${WHITE}$(t choose):${NC} " >&2
read -r choice
if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
log_error "$(t invalid_choice)"
return 1
fi
# Custom git URL
if [ "$choice" -eq 1 ]; then
echo "__custom_git__"
return 0
fi
# Random
if [ "$choice" -eq "$i" ]; then
local random_cat="${cats[$((RANDOM % ${#cats[@]}))]}"
echo "$random_cat"
return 0
fi
# Regular category (offset by 1 because item 1 is custom git)
if [ "$choice" -ge 2 ] && [ "$choice" -lt "$i" ]; then
echo "${cats[$((choice-2))]}"
return 0
fi
log_error "$(t invalid_choice)"
return 1
}
# ── Interactive template picker ────────────────────────────────────────
select_template() {
local cat_id="$1"
local cat_name
cat_name=$(get_category_name "$cat_id")
echo "" >&2
echo -e " ${BOLD}${WHITE}$(tf templates_list "$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 "$(t templates_cat_empty)"
return 1
fi
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}" >&2
echo -ne " ${WHITE}$(t choose) (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 preview
show_template_preview "$selected_id" || return 1
echo "$selected_id"
return 0
fi
log_error "$(t invalid_choice)"
return 1
}
# ── Template preview ───────────────────────────────────────────────────
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}$(t templates_preview_title)${NC}" >&2
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
echo -e " ${WHITE}$(t templates_name)${NC} $name" >&2
echo -e " ${WHITE}$(t templates_source)${NC} $source" >&2
echo -e " ${WHITE}$(t templates_description)${NC} $description" >&2
if [ -n "$preview_url" ]; then
echo "" >&2
echo -e " ${GREEN}$(t templates_preview)${NC} ${CYAN}${preview_url}${NC}" >&2
echo -e " ${DIM}$(t templates_preview_hint)${NC}" >&2
fi
if [ -n "$repo_url" ]; then
echo -e " ${DIM}$(t templates_repo) ${repo_url}${NC}" >&2
fi
# Thanks
echo "" >&2
echo -e " ${MAGENTA}$(tf templates_thanks "$source")${NC}" >&2
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}" >&2
echo "" >&2
if ! confirm "$(t templates_install_this)"; then
return 1
fi
return 0
}
# ── Template download (from catalog) ───────────────────────────────────
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 "$(tf templates_downloading "$name")"
# HTML5 UP — one repo with folders
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: full 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 — one big repo
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 — each template in its own repo
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 stores production files in 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 — each template in its own repo
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 — one big repo with folders (similar to 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
# Check result
if [ -f "$clone_dir/index.html" ]; then
log_success "$(tf templates_downloaded "$name")"
echo "$clone_dir"
return 0
else
# fallback: find index.html in subfolders (non-standard structure)
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 "$(tf templates_downloaded_subfolder "$name")"
echo "$clone_dir"
return 0
fi
fi
log_error "$(t templates_no_index)"
log_dim "$(tf templates_path "$clone_dir")"
ls -la "$clone_dir" 2>/dev/null >&2
return 1
fi
}
# ── Custom git URL helpers ─────────────────────────────────────────────
# Validate a user-supplied git URL
# Accepts: https://host/path[.git][@branch]
# Rejects: ssh://, git://, file://, absolute file paths
_validate_custom_git_url() {
local url="$1"
# Must begin with https://
[[ "$url" =~ ^https:// ]] || return 1
# Reject shell metacharacters that could be exploited
[[ "$url" =~ [[:space:]\;\`\$\(\)\<\>\|\\\&] ]] && return 1
# Reasonable length limit
[ "${#url}" -gt 512 ] && return 1
return 0
}
# Parse URL → sets CUSTOM_GIT_CLEAN and CUSTOM_GIT_BRANCH globals
_parse_custom_git_url() {
local url="$1"
CUSTOM_GIT_CLEAN=""
CUSTOM_GIT_BRANCH=""
# Handle trailing @branch
if [[ "$url" =~ ^(https://[^@]+)@([A-Za-z0-9._/-]+)$ ]]; then
CUSTOM_GIT_CLEAN="${BASH_REMATCH[1]}"
CUSTOM_GIT_BRANCH="${BASH_REMATCH[2]}"
else
CUSTOM_GIT_CLEAN="$url"
fi
# Strip trailing slash
CUSTOM_GIT_CLEAN="${CUSTOM_GIT_CLEAN%/}"
# Append .git if missing (works better with git clone on some hosts)
if [[ ! "$CUSTOM_GIT_CLEAN" =~ \.git$ ]]; then
CUSTOM_GIT_CLEAN="${CUSTOM_GIT_CLEAN}.git"
fi
}
# Check repo size (in MB) by inspecting cloned directory
_clone_dir_size_mb() {
local dir="$1"
du -sm "$dir" 2>/dev/null | awk '{print $1}'
}
# ── Show detailed help for custom git template ─────────────────────────
show_custom_git_help() {
local line
line=$(printf '─%.0s' $(seq 1 60))
echo "" >&2
echo -e " ${BOLD}${GREEN}$(t custom_git_title)${NC}" >&2
echo -e " ${DIM}${line}${NC}" >&2
echo -e " $(t custom_git_help_1)" >&2
echo -e " $(t custom_git_help_2)" >&2
echo -e " $(t custom_git_help_3)" >&2
echo "" >&2
echo -e " ${BOLD}${WHITE}$(t custom_git_formats)${NC}" >&2
echo -e " ${CYAN}$(t custom_git_fmt_github)${NC}" >&2
echo -e " ${CYAN}$(t custom_git_fmt_gitlab)${NC}" >&2
echo -e " ${CYAN}$(t custom_git_fmt_gitext)${NC}" >&2
echo -e " ${CYAN}$(t custom_git_fmt_branch)${NC}" >&2
echo "" >&2
echo -e " ${BOLD}${WHITE}$(t custom_git_auto_detect)${NC}" >&2
echo -e " $(t custom_git_auto_1)" >&2
echo -e " $(t custom_git_auto_2)" >&2
echo -e " $(t custom_git_auto_3)" >&2
echo -e " $(t custom_git_auto_4)" >&2
echo "" >&2
echo -e " ${BOLD}${WHITE}$(t custom_git_requirements)${NC}" >&2
echo -e " ${YELLOW}$(t custom_git_req_1)${NC}" >&2
echo -e " ${YELLOW}$(t custom_git_req_2)${NC}" >&2
echo -e " ${YELLOW}$(t custom_git_req_3)${NC}" >&2
echo -e " ${YELLOW}$(t custom_git_req_4)${NC}" >&2
echo "" >&2
echo -e " ${BOLD}${WHITE}$(t custom_git_examples)${NC}" >&2
echo -e " ${DIM}$(t custom_git_ex_1)${NC}" >&2
echo -e " ${DIM}$(t custom_git_ex_2)${NC}" >&2
echo -e " ${DIM}${line}${NC}" >&2
echo "" >&2
}
# ── Download a custom git template ─────────────────────────────────────
# Prompts user for a URL (unless passed), clones, detects index.html,
# copies result into $output_dir/custom_<hash>, echoes the final path.
download_custom_git_template() {
local url="${1:-}"
local output_dir="${2:-$TEMPLATES_CACHE}"
show_custom_git_help
if [ -z "$url" ]; then
echo -ne " ${WHITE}$(t custom_git_enter_url)${NC} " >&2
read -r url
url=$(echo "$url" | tr -d '\r\n[:space:]')
fi
if [ -z "$url" ]; then
log_error "$(t custom_git_empty)"
return 1
fi
if ! _validate_custom_git_url "$url"; then
log_error "$(t custom_git_bad_url)"
return 1
fi
_parse_custom_git_url "$url"
local clean_url="$CUSTOM_GIT_CLEAN"
local branch="$CUSTOM_GIT_BRANCH"
# Stable-ish directory name from a hash of the original URL
local hash
hash=$(echo -n "$url" | md5sum 2>/dev/null | awk '{print $1}' | head -c 10)
[ -z "$hash" ] && hash=$(date +%s)
local tpl_id="custom_${hash}"
local clone_dir="$output_dir/${tpl_id}"
local tmp_clone="/tmp/custom_git_clone_$$"
rm -rf "$clone_dir" "$tmp_clone"
mkdir -p "$clone_dir"
log_info "$(t custom_git_cloning)"
# Clone with timeout so a hung server can't freeze the installer
local clone_status=0
local git_args=("clone" "--depth" "1")
[ -n "$branch" ] && git_args+=("--branch" "$branch")
git_args+=("$clean_url" "$tmp_clone")
if command -v timeout &>/dev/null; then
timeout "$CUSTOM_GIT_CLONE_TIMEOUT" git "${git_args[@]}" 2>/tmp/custom_git_err_$$
clone_status=$?
else
git "${git_args[@]}" 2>/tmp/custom_git_err_$$
clone_status=$?
fi
if [ $clone_status -ne 0 ] || [ ! -d "$tmp_clone" ]; then
local err_msg
err_msg=$(head -3 "/tmp/custom_git_err_$$" 2>/dev/null | tr '\n' ' ')
rm -f "/tmp/custom_git_err_$$"
rm -rf "$tmp_clone" "$clone_dir"
log_error "$(tf custom_git_clone_failed "${err_msg:-$clone_status}")"
return 1
fi
rm -f "/tmp/custom_git_err_$$"
# Drop .git before measuring size (we only care about payload)
rm -rf "$tmp_clone/.git"
# Size guard
local size_mb
size_mb=$(_clone_dir_size_mb "$tmp_clone")
if [ -n "$size_mb" ] && [ "$size_mb" -gt "$CUSTOM_GIT_MAX_SIZE_MB" ]; then
rm -rf "$tmp_clone" "$clone_dir"
log_error "$(tf custom_git_too_big "${size_mb}MB")"
return 1
fi
log_info "$(t custom_git_scanning)"
# Priority list of common static-site output folders
local candidates=("" "dist" "public" "build" "_site" "site" "docs" "out" "www")
local found_dir=""
for sub in "${candidates[@]}"; do
local try_dir="$tmp_clone"
[ -n "$sub" ] && try_dir="$tmp_clone/$sub"
if [ -f "$try_dir/index.html" ]; then
found_dir="$try_dir"
break
fi
done
# Fallback: search for any index.html in the repo (shallow depth first)
if [ -z "$found_dir" ]; then
local fallback_index
fallback_index=$(find "$tmp_clone" -maxdepth 4 -name "index.html" -type f 2>/dev/null | head -1)
if [ -n "$fallback_index" ]; then
found_dir=$(dirname "$fallback_index")
fi
fi
if [ -z "$found_dir" ] || [ ! -f "$found_dir/index.html" ]; then
rm -rf "$tmp_clone" "$clone_dir"
log_error "$(t custom_git_no_index)"
return 1
fi
# Show what we found (human-friendly relative path)
local rel_path="${found_dir#$tmp_clone}"
rel_path="${rel_path#/}"
[ -z "$rel_path" ] && rel_path="(root)"
log_dim "$(tf custom_git_found_at "$rel_path")"
# Copy the detected directory as the new template
cp -r "$found_dir"/* "$clone_dir/" 2>/dev/null
cp -r "$found_dir"/.[!.]* "$clone_dir/" 2>/dev/null
rm -rf "$tmp_clone"
if [ ! -f "$clone_dir/index.html" ]; then
rm -rf "$clone_dir"
log_error "$(t custom_git_no_index)"
return 1
fi
# Remember the URL so users can see what template they used
echo "$url" > "$clone_dir/.custom_git_source" 2>/dev/null
log_success "$(tf custom_git_installed "$url")"
echo "$clone_dir"
return 0
}
# ── Full interactive template selection ───────────────────────────────
interactive_template_selection() {
load_catalog || return 1
# Category selection
local cat_id
cat_id=$(select_category)
[ $? -ne 0 ] && return 1
# Custom git URL path
if [ "$cat_id" = "__custom_git__" ]; then
local template_dir
template_dir=$(download_custom_git_template)
[ $? -ne 0 ] && return 1
echo "$template_dir"
return 0
fi
# Template selection
local tpl_id
tpl_id=$(select_template "$cat_id")
[ $? -ne 0 ] && return 1
# Download
local template_dir
template_dir=$(download_template "$tpl_id")
[ $? -ne 0 ] && return 1
echo "$template_dir"
return 0
}

340
lib/website.sh Executable file
View 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 "Шаблон сайта обновлён"
}

View File

@@ -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

File diff suppressed because it is too large Load Diff