refactor: rename project to PCAtelegram_web

This commit is contained in:
Андрей Бобырев
2026-06-06 12:40:51 +03:00
parent a01e0ddfba
commit 687d1dbadd
24 changed files with 489 additions and 489 deletions

View File

@@ -1,45 +1,45 @@
# GoTelegram Pro # PCAtelegram_web
Отдельный проект GoTelegram Pro: MTProxy на базе `telemt`, Telegram-бот, локальная web-admin панель, статистика, backup/restore, шаблоны сайта. Отдельный проект PCAtelegram_web: MTProxy на базе `telemt`, Telegram-бот, локальная web-admin панель, статистика, backup/restore, шаблоны сайта.
## Установка ## Установка
На сервере под `root`: На сервере под `root`:
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/andrey271192/gotelegram/main/bootstrap.sh | bash curl -fsSL https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main/bootstrap.sh | bash
``` ```
Если репозиторий private, первый `curl` тоже должен получить GitHub token: Если репозиторий private, первый `curl` тоже должен получить GitHub token:
```bash ```bash
curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" \ curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" \
https://raw.githubusercontent.com/andrey271192/gotelegram/main/bootstrap.sh | \ https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main/bootstrap.sh | \
GITHUB_TOKEN="$GITHUB_TOKEN" bash GITHUB_TOKEN="$GITHUB_TOKEN" bash
``` ```
После установки команда доступна как: После установки команда доступна как:
```bash ```bash
gotelegram pcatelegram_web
``` ```
## Локальная проверка без GitHub ## Локальная проверка без GitHub
```bash ```bash
rsync -a ./ root@SERVER:/opt/gotelegram/ rsync -a ./ root@SERVER:/opt/pcatelegram_web/
ssh root@SERVER 'chmod +x /opt/gotelegram/install.sh /opt/gotelegram/install_gotelegram_bot.sh /opt/gotelegram/lib/*.sh && ln -sf /opt/gotelegram/install.sh /usr/local/bin/gotelegram && gotelegram' ssh root@SERVER 'chmod +x /opt/pcatelegram_web/install.sh /opt/pcatelegram_web/install_pcatelegram_web_bot.sh /opt/pcatelegram_web/lib/*.sh && ln -sf /opt/pcatelegram_web/install.sh /usr/local/bin/pcatelegram_web && pcatelegram_web'
``` ```
## Состав ## Состав
| Путь | Назначение | | Путь | Назначение |
| --- | --- | | --- | --- |
| `bootstrap.sh` | загружает файлы проекта в `/opt/gotelegram` и запускает меню | | `bootstrap.sh` | загружает файлы проекта в `/opt/pcatelegram_web` и запускает меню |
| `install.sh` | основное CLI-меню GoTelegram | | `install.sh` | основное CLI-меню PCAtelegram_web |
| `install_gotelegram_bot.sh` | отдельная установка Telegram-бота | | `install_pcatelegram_web_bot.sh` | отдельная установка Telegram-бота |
| `lib/` | общие функции, telemt, nginx/site, stats, backup, i18n | | `lib/` | общие функции, telemt, nginx/site, stats, backup, i18n |
| `gotelegram-bot/` | Python Telegram bot | | `pcatelegram_web-bot/` | Python Telegram bot |
| `admin-web/` | локальная web-admin панель | | `admin-web/` | локальная web-admin панель |
| `templates_catalog.json` | каталог HTML-шаблонов | | `templates_catalog.json` | каталог HTML-шаблонов |
@@ -47,17 +47,17 @@ ssh root@SERVER 'chmod +x /opt/gotelegram/install.sh /opt/gotelegram/install_got
| Переменная | По умолчанию | Описание | | Переменная | По умолчанию | Описание |
| --- | --- | --- | | --- | --- | --- |
| `GOTELEGRAM_BASE` | `https://raw.githubusercontent.com/andrey271192/gotelegram/main` | база загрузки файлов | | `PCATELEGRAM_WEB_BASE` | `https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main` | база загрузки файлов |
| `GOTELEGRAM_INSTALL_DIR` | `/opt/gotelegram` | путь установки | | `PCATELEGRAM_WEB_INSTALL_DIR` | `/opt/pcatelegram_web` | путь установки |
| `GITHUB_TOKEN` / `GH_TOKEN` | пусто | token для private GitHub raw downloads | | `GITHUB_TOKEN` / `GH_TOKEN` | пусто | token для private GitHub raw downloads |
| `GOTELEGRAM_ADMIN_HOST` | `127.0.0.1` | bind web-admin | | `PCATELEGRAM_WEB_ADMIN_HOST` | `127.0.0.1` | bind web-admin |
| `GOTELEGRAM_ADMIN_PORT` | `1984` | port web-admin | | `PCATELEGRAM_WEB_ADMIN_PORT` | `1984` | port web-admin |
| `GOTELEGRAM_ADMIN_USER` | `admin` | Basic Auth login | | `PCATELEGRAM_WEB_ADMIN_USER` | `admin` | Basic Auth login |
| `GOTELEGRAM_ADMIN_PASSWORD` | пусто | Basic Auth password; обязателен при public bind | | `PCATELEGRAM_WEB_ADMIN_PASSWORD` | пусто | Basic Auth password; обязателен при public bind |
## Web-admin ## Web-admin
После первого запуска `gotelegram` локальная web-admin панель ставится как `gotelegram-admin.service`. После первого запуска `pcatelegram_web` локальная web-admin панель ставится как `pcatelegram_web-admin.service`.
Безопасный локальный доступ через SSH tunnel: Безопасный локальный доступ через SSH tunnel:
@@ -70,10 +70,10 @@ open http://127.0.0.1:1984/
```ini ```ini
[Service] [Service]
Environment=GOTELEGRAM_ADMIN_HOST=0.0.0.0 Environment=PCATELEGRAM_WEB_ADMIN_HOST=0.0.0.0
Environment=GOTELEGRAM_ADMIN_PORT=1984 Environment=PCATELEGRAM_WEB_ADMIN_PORT=1984
Environment=GOTELEGRAM_ADMIN_USER=admin Environment=PCATELEGRAM_WEB_ADMIN_USER=admin
Environment=GOTELEGRAM_ADMIN_PASSWORD=change-me Environment=PCATELEGRAM_WEB_ADMIN_PASSWORD=change-me
``` ```
Без HTTPS public Basic Auth гонит пароль открытым текстом. Для постоянного доступа лучше reverse proxy с TLS. Без HTTPS public Basic Auth гонит пароль открытым текстом. Для постоянного доступа лучше reverse proxy с TLS.
@@ -81,6 +81,6 @@ Environment=GOTELEGRAM_ADMIN_PASSWORD=change-me
## Проверки ## Проверки
```bash ```bash
bash -n bootstrap.sh install.sh install_gotelegram_bot.sh lib/*.sh lib/lang/*.sh bash -n bootstrap.sh install.sh install_pcatelegram_web_bot.sh lib/*.sh lib/lang/*.sh
rg -i "old-brand-name" . rg -i "old-brand-name" .
``` ```

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
goTelegram Pro local web admin. PCAtelegram_web local web admin.
The service is intentionally bound to 127.0.0.1:1984. Operators reach it The service is intentionally bound to 127.0.0.1:1984. Operators reach it
through an SSH tunnel by default. If exposed through a reverse proxy or public through an SSH tunnel by default. If exposed through a reverse proxy or public
bind, enable GOTELEGRAM_ADMIN_PASSWORD. bind, enable PCATELEGRAM_WEB_ADMIN_PASSWORD.
""" """
from __future__ import annotations from __future__ import annotations
@@ -32,35 +32,35 @@ from pathlib import Path
from typing import Any from typing import Any
ADMIN_DIR = Path(os.getenv("GOTELEGRAM_ADMIN_DIR", "/opt/gotelegram-admin")) ADMIN_DIR = Path(os.getenv("PCATELEGRAM_WEB_ADMIN_DIR", "/opt/pcatelegram_web-admin"))
STATIC_DIR = Path(os.getenv("GOTELEGRAM_ADMIN_STATIC", str(ADMIN_DIR / "static"))) STATIC_DIR = Path(os.getenv("PCATELEGRAM_WEB_ADMIN_STATIC", str(ADMIN_DIR / "static")))
GOTELEGRAM_CONFIG = Path(os.getenv("GOTELEGRAM_CONFIG", "/opt/gotelegram/config.json")) PCATELEGRAM_WEB_CONFIG = Path(os.getenv("PCATELEGRAM_WEB_CONFIG", "/opt/pcatelegram_web/config.json"))
TELEMT_CONFIG = Path(os.getenv("TELEMT_CONFIG", "/etc/telemt/config.toml")) TELEMT_CONFIG = Path(os.getenv("TELEMT_CONFIG", "/etc/telemt/config.toml"))
HISTORY_FILE = Path(os.getenv("GOTELEGRAM_STATS_HISTORY", "/opt/gotelegram/stats_history.csv")) HISTORY_FILE = Path(os.getenv("PCATELEGRAM_WEB_STATS_HISTORY", "/opt/pcatelegram_web/stats_history.csv"))
USER_HISTORY_FILE = Path(os.getenv("GOTELEGRAM_USER_STATS_HISTORY", "/opt/gotelegram/user_stats_history.csv")) USER_HISTORY_FILE = Path(os.getenv("PCATELEGRAM_WEB_USER_STATS_HISTORY", "/opt/pcatelegram_web/user_stats_history.csv"))
CURRENT_STATS = Path(os.getenv("GOTELEGRAM_STATS_CURRENT", "/run/gotelegram/stats_current.json")) CURRENT_STATS = Path(os.getenv("PCATELEGRAM_WEB_STATS_CURRENT", "/run/pcatelegram_web/stats_current.json"))
BACKUP_DIR = Path(os.getenv("GOTELEGRAM_BACKUP_DIR", "/opt/gotelegram/backups")) BACKUP_DIR = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_DIR", "/opt/pcatelegram_web/backups"))
INSTALL_DIR = Path(os.getenv("GOTELEGRAM_DIR", "/opt/gotelegram")) INSTALL_DIR = Path(os.getenv("PCATELEGRAM_WEB_DIR", "/opt/pcatelegram_web"))
BOT_DIR = Path(os.getenv("GOTELEGRAM_BOT_DIR", "/opt/gotelegram-bot")) BOT_DIR = Path(os.getenv("PCATELEGRAM_WEB_BOT_DIR", "/opt/pcatelegram_web-bot"))
DISABLED_USERS_FILE = Path(os.getenv("GOTELEGRAM_DISABLED_USERS", "/opt/gotelegram/disabled_users.json")) DISABLED_USERS_FILE = Path(os.getenv("PCATELEGRAM_WEB_DISABLED_USERS", "/opt/pcatelegram_web/disabled_users.json"))
USER_LOCK_FILE = Path(os.getenv("GOTELEGRAM_USER_LOCK", "/run/gotelegram/admin-users.lock")) USER_LOCK_FILE = Path(os.getenv("PCATELEGRAM_WEB_USER_LOCK", "/run/pcatelegram_web/admin-users.lock"))
SHARED_443_CONFIG = Path(os.getenv("GOTELEGRAM_SHARED_443", "/opt/gotelegram/shared-443.json")) SHARED_443_CONFIG = Path(os.getenv("PCATELEGRAM_WEB_SHARED_443", "/opt/pcatelegram_web/shared-443.json"))
BACKUP_SCHEDULE_FILE = Path(os.getenv("GOTELEGRAM_BACKUP_SCHEDULE", "/opt/gotelegram/backup_schedule.json")) BACKUP_SCHEDULE_FILE = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_SCHEDULE", "/opt/pcatelegram_web/backup_schedule.json"))
BACKUP_RESTORE_LOG = Path(os.getenv("GOTELEGRAM_BACKUP_RESTORE_LOG", "/var/log/gotelegram-restore.log")) BACKUP_RESTORE_LOG = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_RESTORE_LOG", "/var/log/pcatelegram_web-restore.log"))
HOST = os.getenv("GOTELEGRAM_ADMIN_HOST", "127.0.0.1") HOST = os.getenv("PCATELEGRAM_WEB_ADMIN_HOST", "127.0.0.1")
PORT = int(os.getenv("GOTELEGRAM_ADMIN_PORT", "1984")) PORT = int(os.getenv("PCATELEGRAM_WEB_ADMIN_PORT", "1984"))
ADMIN_USER = os.getenv("GOTELEGRAM_ADMIN_USER", "admin") ADMIN_USER = os.getenv("PCATELEGRAM_WEB_ADMIN_USER", "admin")
ADMIN_PASSWORD = os.getenv("GOTELEGRAM_ADMIN_PASSWORD", "") ADMIN_PASSWORD = os.getenv("PCATELEGRAM_WEB_ADMIN_PASSWORD", "")
ADMIN_REALM = os.getenv("GOTELEGRAM_ADMIN_REALM", "GoTelegram") ADMIN_REALM = os.getenv("PCATELEGRAM_WEB_ADMIN_REALM", "PCAtelegram_web")
VERSION = "2.5.0" VERSION = "2.5.0"
USER_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$") USER_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$")
LANG_RE = re.compile(r"^(en|ru)$") LANG_RE = re.compile(r"^(en|ru)$")
SENSITIVE_CONFIG_KEYS = {"secret"} SENSITIVE_CONFIG_KEYS = {"secret"}
BACKUP_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+\.tar\.gz(\.enc)?$") BACKUP_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+\.tar\.gz(\.enc)?$")
MAX_UNIQUE_IP_LIMIT = 1000000 MAX_UNIQUE_IP_LIMIT = 1000000
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8")) TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("PCATELEGRAM_WEB_TELEMT_RESTART_DEBOUNCE", "8"))
_LAST_TELEMT_RESTART = 0.0 _LAST_TELEMT_RESTART = 0.0
TRAFFIC_WINDOWS = { TRAFFIC_WINDOWS = {
"15m": 15 * 60, "15m": 15 * 60,
@@ -135,7 +135,7 @@ def save_json(path: Path, data: Any, mode: int = 0o600) -> None:
def read_language(config: dict[str, Any] | None = None) -> str: def read_language(config: dict[str, Any] | None = None) -> str:
config = config or load_json(GOTELEGRAM_CONFIG, {}) or {} config = config or load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
lang = str(config.get("language") or config.get("lang") or "").strip().lower() lang = str(config.get("language") or config.get("lang") or "").strip().lower()
marker = INSTALL_DIR / ".language" marker = INSTALL_DIR / ".language"
if lang not in {"en", "ru"} and marker.exists(): if lang not in {"en", "ru"} and marker.exists():
@@ -154,12 +154,12 @@ def write_language(lang: str) -> dict[str, Any]:
lang = str(lang or "").strip().lower() lang = str(lang or "").strip().lower()
if not LANG_RE.match(lang): if not LANG_RE.match(lang):
raise ValueError("unsupported language") raise ValueError("unsupported language")
config = load_json(GOTELEGRAM_CONFIG, {}) or {} config = load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
if not isinstance(config, dict): if not isinstance(config, dict):
config = {} config = {}
config["language"] = lang config["language"] = lang
config["updated_at"] = utc_now() config["updated_at"] = utc_now()
save_json(GOTELEGRAM_CONFIG, config) save_json(PCATELEGRAM_WEB_CONFIG, config)
INSTALL_DIR.mkdir(parents=True, exist_ok=True) INSTALL_DIR.mkdir(parents=True, exist_ok=True)
(INSTALL_DIR / ".language").write_text(lang + "\n", encoding="utf-8") (INSTALL_DIR / ".language").write_text(lang + "\n", encoding="utf-8")
bot_env = BOT_DIR / ".env" bot_env = BOT_DIR / ".env"
@@ -593,7 +593,7 @@ def listener_for_target(target: str) -> dict[str, Any] | None:
def routed_behind_443() -> list[dict[str, Any]]: def routed_behind_443() -> list[dict[str, Any]]:
config = load_json(GOTELEGRAM_CONFIG, {}) or {} config = load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
mode = str(config.get("mode") or "") mode = str(config.get("mode") or "")
domain = str(config.get("domain") or "") domain = str(config.get("domain") or "")
settings = read_telemt_edge_settings() settings = read_telemt_edge_settings()
@@ -694,7 +694,7 @@ def public_ip() -> str:
def proxy_link(secret: str) -> str: def proxy_link(secret: str) -> str:
config = load_json(GOTELEGRAM_CONFIG, {}) or {} config = load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
mode = str(config.get("mode", "lite")) mode = str(config.get("mode", "lite"))
port = int(config.get("port", 443) or 443) port = int(config.get("port", 443) or 443)
domain = str(config.get("domain", "") or "") domain = str(config.get("domain", "") or "")
@@ -721,7 +721,7 @@ def telemt_api(path: str) -> Any:
def site_status(config: dict[str, Any] | None = None) -> dict[str, Any]: def site_status(config: dict[str, Any] | None = None) -> dict[str, Any]:
config = config or load_json(GOTELEGRAM_CONFIG, {}) or {} config = config or load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
host = str(config.get("domain") or "").strip() host = str(config.get("domain") or "").strip()
if not host: if not host:
return {"host": "", "url": "", "http_code": 0, "ok": False, "checked": False, "error": "domain_missing"} return {"host": "", "url": "", "http_code": 0, "ok": False, "checked": False, "error": "domain_missing"}
@@ -1002,7 +1002,7 @@ def count_user_history_rows(name: str | None = None) -> int:
def stats_status(current: dict[str, Any] | None = None, history: list[dict[str, int]] | None = None) -> dict[str, Any]: def stats_status(current: dict[str, Any] | None = None, history: list[dict[str, int]] | None = None) -> dict[str, Any]:
current = current if current is not None else (load_json(CURRENT_STATS, {}) or {}) current = current if current is not None else (load_json(CURRENT_STATS, {}) or {})
history = history if history is not None else load_stats_history(limit=2) history = history if history is not None else load_stats_history(limit=2)
service = service_status("gotelegram-stats") service = service_status("pcatelegram_web-stats")
now = int(time.time()) now = int(time.time())
ts = int(current.get("ts") or 0) if isinstance(current, dict) else 0 ts = int(current.get("ts") or 0) if isinstance(current, dict) else 0
age = max(0, now - ts) if ts else None age = max(0, now - ts) if ts else None
@@ -1034,9 +1034,9 @@ def stats_status(current: dict[str, Any] | None = None, history: list[dict[str,
def run_stats_action(action: str) -> tuple[bool, str, dict[str, Any]]: def run_stats_action(action: str) -> tuple[bool, str, dict[str, Any]]:
if action == "repair": if action == "repair":
body = ( body = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/stats.sh; " "source /opt/pcatelegram_web/lib/stats.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
"install_stats_collector; " "install_stats_collector; "
"stats_collect" "stats_collect"
@@ -1044,8 +1044,8 @@ def run_stats_action(action: str) -> tuple[bool, str, dict[str, Any]]:
timeout = 180 timeout = 180
else: else:
body = ( body = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/stats.sh; " "source /opt/pcatelegram_web/lib/stats.sh; "
"stats_init >/dev/null 2>&1 || true; " "stats_init >/dev/null 2>&1 || true; "
"stats_collect" "stats_collect"
) )
@@ -1100,9 +1100,9 @@ def backup_schedule_status() -> dict[str, Any]:
except ValueError: except ValueError:
frequency = "off" frequency = "off"
calendar = None calendar = None
active_code, active, _ = run(["systemctl", "is-active", "gotelegram-backup.timer"], timeout=5) active_code, active, _ = run(["systemctl", "is-active", "pcatelegram_web-backup.timer"], timeout=5)
enabled_code, enabled, _ = run(["systemctl", "is-enabled", "gotelegram-backup.timer"], timeout=5) enabled_code, enabled, _ = run(["systemctl", "is-enabled", "pcatelegram_web-backup.timer"], timeout=5)
_, next_run, _ = run(["systemctl", "show", "gotelegram-backup.timer", "--property=NextElapseUSecRealtime", "--value"], timeout=5) _, next_run, _ = run(["systemctl", "show", "pcatelegram_web-backup.timer", "--property=NextElapseUSecRealtime", "--value"], timeout=5)
return { return {
"frequency": frequency, "frequency": frequency,
"calendar": calendar, "calendar": calendar,
@@ -1116,9 +1116,9 @@ def backup_schedule_status() -> dict[str, Any]:
def set_backup_schedule(frequency: str) -> tuple[bool, str, dict[str, Any]]: def set_backup_schedule(frequency: str) -> tuple[bool, str, dict[str, Any]]:
backup_schedule_calendar(frequency) backup_schedule_calendar(frequency)
script = ( script = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
f"set_backup_schedule {shlex.quote(frequency)}" f"set_backup_schedule {shlex.quote(frequency)}"
) )
@@ -1129,11 +1129,11 @@ def set_backup_schedule(frequency: str) -> tuple[bool, str, dict[str, Any]]:
def create_backup() -> tuple[bool, str]: def create_backup() -> tuple[bool, str]:
script = ( script = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/telemt.sh; " "source /opt/pcatelegram_web/lib/telemt.sh; "
"source /opt/gotelegram/lib/website.sh; " "source /opt/pcatelegram_web/lib/website.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
"create_backup \"\"; " "create_backup \"\"; "
"cleanup_old_backups 30" "cleanup_old_backups 30"
@@ -1166,11 +1166,11 @@ def launch_restore_backup(name: str, password: str = "") -> dict[str, Any]:
quoted_log = shlex.quote(str(BACKUP_RESTORE_LOG)) quoted_log = shlex.quote(str(BACKUP_RESTORE_LOG))
script = ( script = (
"sleep 1; " "sleep 1; "
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/telemt.sh; " "source /opt/pcatelegram_web/lib/telemt.sh; "
"source /opt/gotelegram/lib/website.sh; " "source /opt/pcatelegram_web/lib/website.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
"create_backup \"\" >/dev/null 2>&1 || true; " "create_backup \"\" >/dev/null 2>&1 || true; "
f"restore_backup {quoted_path} {quoted_password} yes; " f"restore_backup {quoted_path} {quoted_password} yes; "
@@ -1201,7 +1201,7 @@ def user_qr_png(name: str) -> tuple[bytes, str]:
def read_log_payload(service: str) -> dict[str, Any]: def read_log_payload(service: str) -> dict[str, Any]:
allowed = {"telemt", "nginx", "gotelegram-bot", "gotelegram-stats", "gotelegram-admin"} allowed = {"telemt", "nginx", "pcatelegram_web-bot", "pcatelegram_web-stats", "pcatelegram_web-admin"}
if service not in allowed: if service not in allowed:
raise ValueError("unsupported service") raise ValueError("unsupported service")
code, stdout, stderr = run(["journalctl", "-u", service, "-n", "180", "--no-pager", "-o", "short-iso"], timeout=10) code, stdout, stderr = run(["journalctl", "-u", service, "-n", "180", "--no-pager", "-o", "short-iso"], timeout=10)
@@ -1249,7 +1249,7 @@ def user_payload(
def overview_payload() -> dict[str, Any]: def overview_payload() -> dict[str, Any]:
config = load_json(GOTELEGRAM_CONFIG, {}) or {} config = load_json(PCATELEGRAM_WEB_CONFIG, {}) or {}
language = read_language(config) language = read_language(config)
users = read_user_records() users = read_user_records()
current = load_json(CURRENT_STATS, {}) or {} current = load_json(CURRENT_STATS, {}) or {}
@@ -1258,9 +1258,9 @@ def overview_payload() -> dict[str, Any]:
services = { services = {
"telemt": service_status("telemt"), "telemt": service_status("telemt"),
"nginx": service_status("nginx"), "nginx": service_status("nginx"),
"bot": service_status("gotelegram-bot"), "bot": service_status("pcatelegram_web-bot"),
"stats": service_status("gotelegram-stats"), "stats": service_status("pcatelegram_web-stats"),
"admin": service_status("gotelegram-admin"), "admin": service_status("pcatelegram_web-admin"),
} }
return { return {
"version": VERSION, "version": VERSION,
@@ -1282,7 +1282,7 @@ def overview_payload() -> dict[str, Any]:
class AdminHandler(BaseHTTPRequestHandler): class AdminHandler(BaseHTTPRequestHandler):
server_version = "goTelegramProAdmin/2.5.0" server_version = "PCAtelegram_webProAdmin/2.5.0"
def log_message(self, fmt: str, *args: Any) -> None: def log_message(self, fmt: str, *args: Any) -> None:
print("%s - %s" % (self.address_string(), fmt % args)) print("%s - %s" % (self.address_string(), fmt % args))
@@ -1345,7 +1345,7 @@ class AdminHandler(BaseHTTPRequestHandler):
return json.loads(self.rfile.read(length).decode("utf-8")) return json.loads(self.rfile.read(length).decode("utf-8"))
def require_write_guard(self) -> bool: def require_write_guard(self) -> bool:
if self.command in {"POST", "PUT", "PATCH", "DELETE"} and self.headers.get("X-GoTelegram-Admin") != "1": if self.command in {"POST", "PUT", "PATCH", "DELETE"} and self.headers.get("X-PCAtelegram-Web-Admin") != "1":
self.send_error_json(403, "missing write guard") self.send_error_json(403, "missing write guard")
return False return False
return True return True
@@ -1602,7 +1602,7 @@ class AdminHandler(BaseHTTPRequestHandler):
self.send_json({"ok": True, "data": lang_payload}) self.send_json({"ok": True, "data": lang_payload})
elif path.startswith("/api/services/") and path.endswith("/restart"): elif path.startswith("/api/services/") and path.endswith("/restart"):
service = path[len("/api/services/"):-len("/restart")] service = path[len("/api/services/"):-len("/restart")]
allowed = {"telemt", "nginx", "gotelegram-bot", "gotelegram-stats"} allowed = {"telemt", "nginx", "pcatelegram_web-bot", "pcatelegram_web-stats"}
if service not in allowed: if service not in allowed:
self.send_error_json(400, "unsupported service") self.send_error_json(400, "unsupported service")
return return
@@ -1698,7 +1698,7 @@ def main() -> None:
if not STATIC_DIR.exists(): if not STATIC_DIR.exists():
raise SystemExit(f"static dir not found: {STATIC_DIR}") raise SystemExit(f"static dir not found: {STATIC_DIR}")
httpd = ThreadingHTTPServer((HOST, PORT), AdminHandler) httpd = ThreadingHTTPServer((HOST, PORT), AdminHandler)
print(f"goTelegram Pro admin listening on http://{HOST}:{PORT}") print(f"PCAtelegram_web admin listening on http://{HOST}:{PORT}")
httpd.serve_forever() httpd.serve_forever()

View File

@@ -86,7 +86,7 @@ const i18n = {
backupScheduleTitle: "Automatic backups", backupScheduleTitle: "Automatic backups",
backupScheduleLoading: "Loading schedule...", backupScheduleLoading: "Loading schedule...",
backupIncludesTitle: "Backup contents", backupIncludesTitle: "Backup contents",
backupIncludesText: "telemt config, goTelegram settings, keys, disabled keys, site, templates, SSL certificates, bot, admin panel and traffic history.", backupIncludesText: "telemt config, PCAtelegram_web settings, keys, disabled keys, site, templates, SSL certificates, bot, admin panel and traffic history.",
scheduleOff: "Off", scheduleOff: "Off",
scheduleDaily: "Daily", scheduleDaily: "Daily",
scheduleWeekly: "Weekly", scheduleWeekly: "Weekly",
@@ -210,7 +210,7 @@ const i18n = {
ariaTrafficRange: "Traffic range", ariaTrafficRange: "Traffic range",
ariaTrafficView: "Traffic view", ariaTrafficView: "Traffic view",
promoEyebrow: "Promo", promoEyebrow: "Promo",
promoTitle: "Support goTelegram Pro", promoTitle: "Support PCAtelegram_web",
promoHosting1: "Hosting #1", promoHosting1: "Hosting #1",
promoHosting2: "Hosting #2", promoHosting2: "Hosting #2",
promoTips: "Tips", promoTips: "Tips",
@@ -313,7 +313,7 @@ const i18n = {
backupScheduleTitle: "Автобекапы", backupScheduleTitle: "Автобекапы",
backupScheduleLoading: "Загрузка расписания...", backupScheduleLoading: "Загрузка расписания...",
backupIncludesTitle: "Что входит в бекап", backupIncludesTitle: "Что входит в бекап",
backupIncludesText: "конфиг telemt, настройки goTelegram, ключи, отключённые ключи, сайт, шаблоны, SSL-сертификаты, бот, админка и история трафика.", backupIncludesText: "конфиг telemt, настройки PCAtelegram_web, ключи, отключённые ключи, сайт, шаблоны, SSL-сертификаты, бот, админка и история трафика.",
scheduleOff: "Выкл", scheduleOff: "Выкл",
scheduleDaily: "Каждый день", scheduleDaily: "Каждый день",
scheduleWeekly: "Каждую неделю", scheduleWeekly: "Каждую неделю",
@@ -437,7 +437,7 @@ const i18n = {
ariaTrafficRange: "Период трафика", ariaTrafficRange: "Период трафика",
ariaTrafficView: "Вид трафика", ariaTrafficView: "Вид трафика",
promoEyebrow: "Промо", promoEyebrow: "Промо",
promoTitle: "Поддержать goTelegram Pro", promoTitle: "Поддержать PCAtelegram_web",
promoHosting1: "Хостинг #1", promoHosting1: "Хостинг #1",
promoHosting2: "Хостинг #2", promoHosting2: "Хостинг #2",
promoTips: "Чаевые", promoTips: "Чаевые",
@@ -478,7 +478,7 @@ const state = {
qrLink: "", qrLink: "",
pendingUsers: new Set(), pendingUsers: new Set(),
refreshingAll: false, refreshingAll: false,
autoRefreshEnabled: localStorage.getItem("gotelegram-auto-refresh") !== "0", autoRefreshEnabled: localStorage.getItem("pcatelegram_web-auto-refresh") !== "0",
}; };
const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key; const t = (key) => (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
@@ -562,7 +562,7 @@ function syncAutoRefreshTimer() {
function setAutoRefresh(enabled) { function setAutoRefresh(enabled) {
state.autoRefreshEnabled = Boolean(enabled); state.autoRefreshEnabled = Boolean(enabled);
localStorage.setItem("gotelegram-auto-refresh", state.autoRefreshEnabled ? "1" : "0"); localStorage.setItem("pcatelegram_web-auto-refresh", state.autoRefreshEnabled ? "1" : "0");
updateAutoRefreshToggle(); updateAutoRefreshToggle();
syncAutoRefreshTimer(); syncAutoRefreshTimer();
} }
@@ -570,7 +570,7 @@ function setAutoRefresh(enabled) {
async function api(path, options = {}) { async function api(path, options = {}) {
const headers = { const headers = {
"Accept": "application/json", "Accept": "application/json",
"X-GoTelegram-Admin": "1", "X-PCAtelegram-Web-Admin": "1",
...(options.headers || {}), ...(options.headers || {}),
}; };
if (options.body && !headers["Content-Type"]) headers["Content-Type"] = "application/json"; if (options.body && !headers["Content-Type"]) headers["Content-Type"] = "application/json";
@@ -610,7 +610,7 @@ function applyI18n() {
function setTheme(theme) { function setTheme(theme) {
state.theme = theme === "dark" ? "dark" : "light"; state.theme = theme === "dark" ? "dark" : "light";
document.documentElement.dataset.theme = state.theme; document.documentElement.dataset.theme = state.theme;
localStorage.setItem("gotelegram-theme", state.theme); localStorage.setItem("pcatelegram_web-theme", state.theme);
applyI18n(); applyI18n();
if (state.overview) renderStats(); if (state.overview) renderStats();
if (state.userTraffic) renderUserTraffic(); if (state.userTraffic) renderUserTraffic();
@@ -691,9 +691,9 @@ function renderServices(services = {}) {
const items = [ const items = [
{ key: "telemt", label: "telemt", api: "telemt" }, { key: "telemt", label: "telemt", api: "telemt" },
{ key: "nginx", label: "nginx", api: "nginx" }, { key: "nginx", label: "nginx", api: "nginx" },
{ key: "bot", label: "bot", api: "gotelegram-bot" }, { key: "bot", label: "bot", api: "pcatelegram_web-bot" },
{ key: "stats", label: "stats", api: "gotelegram-stats" }, { key: "stats", label: "stats", api: "pcatelegram_web-stats" },
{ key: "admin", label: "admin", api: "gotelegram-admin" }, { key: "admin", label: "admin", api: "pcatelegram_web-admin" },
]; ];
$("#services").innerHTML = items.map((item) => { $("#services").innerHTML = items.map((item) => {
const status = services[item.key] || "unknown"; const status = services[item.key] || "unknown";
@@ -1639,7 +1639,7 @@ async function copyText(value) {
} }
function maybeShowPromo() { function maybeShowPromo() {
const key = "gotelegram-promo-last"; const key = "pcatelegram_web-promo-last";
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const last = Number(localStorage.getItem(key) || 0); const last = Number(localStorage.getItem(key) || 0);
if (now - last < 86400) return; if (now - last < 86400) return;

View File

@@ -3,10 +3,10 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>goTelegram Pro Admin</title> <title>PCAtelegram_web Admin</title>
<script> <script>
(function () { (function () {
var stored = localStorage.getItem("gotelegram-theme"); var stored = localStorage.getItem("pcatelegram_web-theme");
var theme = stored || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); var theme = stored || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
document.documentElement.dataset.theme = theme; document.documentElement.dataset.theme = theme;
}()); }());
@@ -19,7 +19,7 @@
<div class="brand"> <div class="brand">
<div class="brand-mark">GT</div> <div class="brand-mark">GT</div>
<div> <div>
<strong>goTelegram Pro</strong> <strong>PCAtelegram_web</strong>
<span data-i18n="brandSubtitle">Local Admin</span> <span data-i18n="brandSubtitle">Local Admin</span>
</div> </div>
</div> </div>
@@ -66,7 +66,7 @@
<section class="page-panel active" data-page="dashboard"> <section class="page-panel active" data-page="dashboard">
<section class="visual-overview"> <section class="visual-overview">
<div> <div>
<p class="eyebrow">goTelegram Pro</p> <p class="eyebrow">PCAtelegram_web</p>
<h2 id="visualTitle">Port 443</h2> <h2 id="visualTitle">Port 443</h2>
<p id="visualText">Website, MTProxy and local admin status in one operational view.</p> <p id="visualText">Website, MTProxy and local admin status in one operational view.</p>
</div> </div>
@@ -279,7 +279,7 @@
</div> </div>
<div class="backup-includes"> <div class="backup-includes">
<strong data-i18n="backupIncludesTitle">Backup contents</strong> <strong data-i18n="backupIncludesTitle">Backup contents</strong>
<span data-i18n="backupIncludesText">telemt config, goTelegram settings, keys, disabled keys, site, templates, SSL certificates, bot, admin panel and traffic history.</span> <span data-i18n="backupIncludesText">telemt config, PCAtelegram_web settings, keys, disabled keys, site, templates, SSL certificates, bot, admin panel and traffic history.</span>
</div> </div>
<div id="backupsList" class="backup-list"></div> <div id="backupsList" class="backup-list"></div>
</section> </section>
@@ -307,9 +307,9 @@
<select id="logService"> <select id="logService">
<option value="telemt">telemt</option> <option value="telemt">telemt</option>
<option value="nginx">nginx</option> <option value="nginx">nginx</option>
<option value="gotelegram-bot">bot</option> <option value="pcatelegram_web-bot">bot</option>
<option value="gotelegram-stats">stats</option> <option value="pcatelegram_web-stats">stats</option>
<option value="gotelegram-admin">admin</option> <option value="pcatelegram_web-admin">admin</option>
</select> </select>
<button id="loadLogsBtn" type="button" data-i18n="loadLogs">Load</button> <button id="loadLogsBtn" type="button" data-i18n="loadLogs">Load</button>
</div> </div>
@@ -376,7 +376,7 @@
<div class="promo-card" role="dialog" aria-modal="true" aria-labelledby="promoTitle"> <div class="promo-card" role="dialog" aria-modal="true" aria-labelledby="promoTitle">
<button id="promoClose" class="icon-btn ghost" type="button" aria-label="Close" data-i18n-aria-label="ariaClose">×</button> <button id="promoClose" class="icon-btn ghost" type="button" aria-label="Close" data-i18n-aria-label="ariaClose">×</button>
<p class="eyebrow" data-i18n="promoEyebrow">Promo</p> <p class="eyebrow" data-i18n="promoEyebrow">Promo</p>
<h2 id="promoTitle" data-i18n="promoTitle">Support goTelegram Pro</h2> <h2 id="promoTitle" data-i18n="promoTitle">Support PCAtelegram_web</h2>
<div class="promo-grid"> <div class="promo-grid">
<a href="https://vk.cc/ct29NQ" target="_blank" rel="noreferrer"> <a href="https://vk.cc/ct29NQ" target="_blank" rel="noreferrer">
<strong data-i18n="promoHosting1">Hosting #1</strong> <strong data-i18n="promoHosting1">Hosting #1</strong>

26
bootstrap.sh Normal file → Executable file
View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# GoTelegram Pro — bootstrap installer from this repository. # PCAtelegram_web — bootstrap installer from this repository.
set -euo pipefail set -euo pipefail
GOTELEGRAM_BASE="${GOTELEGRAM_BASE:-https://raw.githubusercontent.com/andrey271192/gotelegram/main}" PCATELEGRAM_WEB_BASE="${PCATELEGRAM_WEB_BASE:-https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main}"
INSTALL_DIR="${GOTELEGRAM_INSTALL_DIR:-/opt/gotelegram}" INSTALL_DIR="${PCATELEGRAM_WEB_INSTALL_DIR:-/opt/pcatelegram_web}"
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
@@ -23,13 +23,13 @@ for cmd in curl jq; do
done done
FILES=( FILES=(
"install.sh" "install_gotelegram_bot.sh" "templates_catalog.json" "install.sh" "install_pcatelegram_web_bot.sh" "templates_catalog.json"
"lib/common.sh" "lib/telemt.sh" "lib/telemt_config.sh" "lib/backup.sh" "lib/common.sh" "lib/telemt.sh" "lib/telemt_config.sh" "lib/backup.sh"
"lib/website.sh" "lib/templates_catalog.sh" "lib/stats.sh" "lib/i18n.sh" "lib/website.sh" "lib/templates_catalog.sh" "lib/stats.sh" "lib/i18n.sh"
"lib/lang/en.sh" "lib/lang/ru.sh" "lib/lang/en.sh" "lib/lang/ru.sh"
"gotelegram-bot/bot.py" "gotelegram-bot/i18n.py" "pcatelegram_web-bot/bot.py" "pcatelegram_web-bot/i18n.py"
"gotelegram-bot/lang/en.json" "gotelegram-bot/lang/ru.json" "pcatelegram_web-bot/lang/en.json" "pcatelegram_web-bot/lang/ru.json"
"gotelegram-bot/config.example.env" "gotelegram-bot/requirements.txt" "gotelegram-bot/README.md" "pcatelegram_web-bot/config.example.env" "pcatelegram_web-bot/requirements.txt" "pcatelegram_web-bot/README.md"
"admin-web/server.py" "admin-web/static/index.html" "admin-web/static/styles.css" "admin-web/static/app.js" "admin-web/server.py" "admin-web/static/index.html" "admin-web/static/styles.css" "admin-web/static/app.js"
) )
@@ -45,7 +45,7 @@ download_file() {
mkdir -p "$(dirname "$local_path")" mkdir -p "$(dirname "$local_path")"
for attempt in 1 2 3; do for attempt in 1 2 3; do
http_code=$(curl -sL "${curl_headers[@]}" -w "%{http_code}" -o "$local_path" "${GOTELEGRAM_BASE%/}/${remote_path}" 2>/dev/null || echo "000") http_code=$(curl -sL "${curl_headers[@]}" -w "%{http_code}" -o "$local_path" "${PCATELEGRAM_WEB_BASE%/}/${remote_path}" 2>/dev/null || echo "000")
if [ "$http_code" = "200" ]; then if [ "$http_code" = "200" ]; then
return 0 return 0
fi fi
@@ -56,8 +56,8 @@ download_file() {
return 1 return 1
} }
echo -e " ${CYAN}${NC} Загрузка GoTelegram из ${GOTELEGRAM_BASE%/}..." echo -e " ${CYAN}${NC} Загрузка PCAtelegram_web из ${PCATELEGRAM_WEB_BASE%/}..."
mkdir -p "${INSTALL_DIR}/lib/lang" "${INSTALL_DIR}/gotelegram-bot/lang" "${INSTALL_DIR}/admin-web/static" mkdir -p "${INSTALL_DIR}/lib/lang" "${INSTALL_DIR}/pcatelegram_web-bot/lang" "${INSTALL_DIR}/admin-web/static"
failed=0 failed=0
for f in "${FILES[@]}"; do for f in "${FILES[@]}"; do
@@ -69,10 +69,10 @@ for f in "${FILES[@]}"; do
done done
[ "$failed" -eq 0 ] || exit 1 [ "$failed" -eq 0 ] || exit 1
chmod +x "${INSTALL_DIR}/install.sh" "${INSTALL_DIR}/install_gotelegram_bot.sh" chmod +x "${INSTALL_DIR}/install.sh" "${INSTALL_DIR}/install_pcatelegram_web_bot.sh"
chmod +x "${INSTALL_DIR}"/lib/*.sh chmod +x "${INSTALL_DIR}"/lib/*.sh
chmod +x "${INSTALL_DIR}/admin-web/server.py" 2>/dev/null || true chmod +x "${INSTALL_DIR}/admin-web/server.py" 2>/dev/null || true
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}/install.sh" "${INSTALL_DIR}/install_pcatelegram_web_bot.sh" "${INSTALL_DIR}"/lib/*.sh "${INSTALL_DIR}"/lib/lang/*.sh 2>/dev/null || true
ln -sf "${INSTALL_DIR}/install.sh" /usr/local/bin/gotelegram ln -sf "${INSTALL_DIR}/install.sh" /usr/local/bin/pcatelegram_web
exec bash "${INSTALL_DIR}/install.sh" "$@" exec bash "${INSTALL_DIR}/install.sh" "$@"

150
install.sh Normal file → Executable file
View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ══════════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════════
# goTelegram Pro v2.5.0 — MTProxy powered by telemt (Rust + Tokio) # PCAtelegram_web v2.5.0 — MTProxy powered by telemt (Rust + Tokio)
# Anti-DPI • Fake TLS • TCP Splice • JA3/JA4 Resistance • i18n (EN/RU) # Anti-DPI • Fake TLS • TCP Splice • JA3/JA4 Resistance • i18n (EN/RU)
# #
# Install: # Install:
@@ -46,7 +46,7 @@ show_main_menu() {
# ── Header (no right border — ANSI breaks alignment) ── # ── Header (no right border — ANSI breaks alignment) ──
echo "" echo ""
echo -e " ${BOLD}${CYAN}${line}${NC}" echo -e " ${BOLD}${CYAN}${line}${NC}"
echo -e " ${BOLD}${WHITE} goTelegram Pro v${GOTELEGRAM_VERSION}${NC} ${DIM}$(t dashboard_title)${NC}" echo -e " ${BOLD}${WHITE} PCAtelegram_web v${PCATELEGRAM_WEB_VERSION}${NC} ${DIM}$(t dashboard_title)${NC}"
echo -e " ${BOLD}${CYAN}${line}${NC}" echo -e " ${BOLD}${CYAN}${line}${NC}"
# ── Service health ── # ── Service health ──
@@ -239,7 +239,7 @@ menu_version() {
echo "" echo ""
echo -e " ${BOLD}${WHITE}$(t version_title)${NC}" echo -e " ${BOLD}${WHITE}$(t version_title)${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}" echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
echo -e " ${WHITE}$(t version_label)${NC} v${GOTELEGRAM_VERSION}" echo -e " ${WHITE}$(t version_label)${NC} v${PCATELEGRAM_WEB_VERSION}"
echo -e " ${WHITE}$(t version_engine)${NC} telemt (Rust + Tokio)" echo -e " ${WHITE}$(t version_engine)${NC} telemt (Rust + Tokio)"
echo -e " ${WHITE}$(t version_tech)${NC} Anti-DPI, Fake TLS, TCP Splice" echo -e " ${WHITE}$(t version_tech)${NC} Anti-DPI, Fake TLS, TCP Splice"
echo -e " ${WHITE}$(t version_license)${NC} MIT" echo -e " ${WHITE}$(t version_license)${NC} MIT"
@@ -248,21 +248,21 @@ menu_version() {
# ── Upgrade migration ──────────────────────────────────────────────────────── # ── Upgrade migration ────────────────────────────────────────────────────────
snapshot_preupgrade_state() { snapshot_preupgrade_state() {
local marker="$GOTELEGRAM_DIR/.preupgrade_${GOTELEGRAM_VERSION}_done" local marker="$PCATELEGRAM_WEB_DIR/.preupgrade_${PCATELEGRAM_WEB_VERSION}_done"
[ -f "$marker" ] && return 0 [ -f "$marker" ] && return 0
mkdir -p "$BACKUP_DIR" mkdir -p "$BACKUP_DIR"
local ts tmp archive local ts tmp archive
ts=$(date +%Y%m%d_%H%M%S) ts=$(date +%Y%m%d_%H%M%S)
tmp="/tmp/gotelegram_preupgrade_${ts}" tmp="/tmp/pcatelegram_web_preupgrade_${ts}"
archive="$BACKUP_DIR/preupgrade_${GOTELEGRAM_VERSION}_${ts}.tar.gz" archive="$BACKUP_DIR/preupgrade_${PCATELEGRAM_WEB_VERSION}_${ts}.tar.gz"
mkdir -p "$tmp" mkdir -p "$tmp"
[ -f "$GOTELEGRAM_CONFIG" ] && mkdir -p "$tmp/opt/gotelegram" && cp "$GOTELEGRAM_CONFIG" "$tmp/opt/gotelegram/config.json" 2>/dev/null [ -f "$PCATELEGRAM_WEB_CONFIG" ] && mkdir -p "$tmp/opt/pcatelegram_web" && cp "$PCATELEGRAM_WEB_CONFIG" "$tmp/opt/pcatelegram_web/config.json" 2>/dev/null
[ -f "$TELEMT_CONFIG" ] && mkdir -p "$tmp/etc/telemt" && cp "$TELEMT_CONFIG" "$tmp/etc/telemt/config.toml" 2>/dev/null [ -f "$TELEMT_CONFIG" ] && mkdir -p "$tmp/etc/telemt" && cp "$TELEMT_CONFIG" "$tmp/etc/telemt/config.toml" 2>/dev/null
[ -f "$NGINX_SITE_CONF" ] && mkdir -p "$tmp/etc/nginx/sites-available" && cp "$NGINX_SITE_CONF" "$tmp/etc/nginx/sites-available/gotelegram" 2>/dev/null [ -f "$NGINX_SITE_CONF" ] && mkdir -p "$tmp/etc/nginx/sites-available" && cp "$NGINX_SITE_CONF" "$tmp/etc/nginx/sites-available/pcatelegram_web" 2>/dev/null
[ -d "$WEBSITE_ROOT" ] && mkdir -p "$tmp/var/www/gotelegram-site" && cp -a "$WEBSITE_ROOT/." "$tmp/var/www/gotelegram-site/" 2>/dev/null [ -d "$WEBSITE_ROOT" ] && mkdir -p "$tmp/var/www/pcatelegram_web-site" && cp -a "$WEBSITE_ROOT/." "$tmp/var/www/pcatelegram_web-site/" 2>/dev/null
[ -f "$BOT_DIR/.env" ] && mkdir -p "$tmp/opt/gotelegram-bot" && cp "$BOT_DIR/.env" "$tmp/opt/gotelegram-bot/.env" 2>/dev/null [ -f "$BOT_DIR/.env" ] && mkdir -p "$tmp/opt/pcatelegram_web-bot" && cp "$BOT_DIR/.env" "$tmp/opt/pcatelegram_web-bot/.env" 2>/dev/null
if tar czf "$archive" -C "$tmp" . 2>/dev/null; then if tar czf "$archive" -C "$tmp" . 2>/dev/null; then
log_dim "Pre-upgrade snapshot: $archive" log_dim "Pre-upgrade snapshot: $archive"
@@ -278,8 +278,8 @@ read_config_or_default() {
detect_deployed_template_id() { detect_deployed_template_id() {
local tpl="" local tpl=""
if [ -f "$WEBSITE_ROOT/.gotelegram_template_id" ]; then if [ -f "$WEBSITE_ROOT/.pcatelegram_web_template_id" ]; then
tpl=$(head -1 "$WEBSITE_ROOT/.gotelegram_template_id" 2>/dev/null || echo "") tpl=$(head -1 "$WEBSITE_ROOT/.pcatelegram_web_template_id" 2>/dev/null || echo "")
[ -n "$tpl" ] && { echo "$tpl"; return 0; } [ -n "$tpl" ] && { echo "$tpl"; return 0; }
fi fi
if [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ]; then if [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ]; then
@@ -293,15 +293,15 @@ detect_deployed_template_id() {
detect_template_source() { detect_template_source() {
local src local src
if [ -f "$WEBSITE_ROOT/.gotelegram_template_source" ]; then if [ -f "$WEBSITE_ROOT/.pcatelegram_web_template_source" ]; then
src=$(head -1 "$WEBSITE_ROOT/.gotelegram_template_source" 2>/dev/null || echo "") src=$(head -1 "$WEBSITE_ROOT/.pcatelegram_web_template_source" 2>/dev/null || echo "")
[ -n "$src" ] && { echo "$src"; return 0; } [ -n "$src" ] && { echo "$src"; return 0; }
fi fi
[ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ] && return 0 [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ] && return 0
read_config_or_default template_source "" read_config_or_default template_source ""
} }
write_normalized_gotelegram_config() { write_normalized_pcatelegram_web_config() {
local mode="$1" port="$2" secret="$3" mask_host="$4" domain="$5" tpl_id="$6" tpl_source="$7" local mode="$1" port="$2" secret="$3" mask_host="$4" domain="$5" tpl_id="$6" tpl_source="$7"
local lang installed_at stats_enabled tmp local lang installed_at stats_enabled tmp
lang=$(read_config_or_default language "$(get_language 2>/dev/null || echo en)") lang=$(read_config_or_default language "$(get_language 2>/dev/null || echo en)")
@@ -310,7 +310,7 @@ write_normalized_gotelegram_config() {
tmp=$(mktemp) || return 1 tmp=$(mktemp) || return 1
jq -n \ jq -n \
--arg version "$GOTELEGRAM_VERSION" \ --arg version "$PCATELEGRAM_WEB_VERSION" \
--arg engine "telemt" \ --arg engine "telemt" \
--arg mode "$mode" \ --arg mode "$mode" \
--argjson port "$port" \ --argjson port "$port" \
@@ -340,22 +340,22 @@ write_normalized_gotelegram_config() {
+ (if $stats_enabled == "true" then {stats_enabled: true} elif $stats_enabled == "false" then {stats_enabled: false} else {} end)' \ + (if $stats_enabled == "true" then {stats_enabled: true} elif $stats_enabled == "false" then {stats_enabled: false} else {} end)' \
> "$tmp" || { rm -f "$tmp"; return 1; } > "$tmp" || { rm -f "$tmp"; return 1; }
mkdir -p "$(dirname "$GOTELEGRAM_CONFIG")" mkdir -p "$(dirname "$PCATELEGRAM_WEB_CONFIG")"
mv "$tmp" "$GOTELEGRAM_CONFIG" mv "$tmp" "$PCATELEGRAM_WEB_CONFIG"
chmod 600 "$GOTELEGRAM_CONFIG" chmod 600 "$PCATELEGRAM_WEB_CONFIG"
} }
auto_migrate_legacy_state() { auto_migrate_legacy_state() {
local marker="$GOTELEGRAM_DIR/.migrated_${GOTELEGRAM_VERSION}" local marker="$PCATELEGRAM_WEB_DIR/.migrated_${PCATELEGRAM_WEB_VERSION}"
local current_version local current_version
current_version=$(read_config_or_default version "") current_version=$(read_config_or_default version "")
if [ -f "$marker" ] && [ "$current_version" = "$GOTELEGRAM_VERSION" ]; then if [ -f "$marker" ] && [ "$current_version" = "$PCATELEGRAM_WEB_VERSION" ]; then
return 0 return 0
fi fi
[ -f "$TELEMT_CONFIG" ] || [ -f "$GOTELEGRAM_CONFIG" ] || [ -d "$WEBSITE_ROOT" ] || return 0 [ -f "$TELEMT_CONFIG" ] || [ -f "$PCATELEGRAM_WEB_CONFIG" ] || [ -d "$WEBSITE_ROOT" ] || return 0
log_step "Миграция состояния goTelegram Pro" log_step "Миграция состояния PCAtelegram_web"
snapshot_preupgrade_state snapshot_preupgrade_state
local mode port secret mask_host domain mask_port tpl_id tpl_source users_block tls_emulation changed=0 users_block_needs_write=0 local mode port secret mask_host domain mask_port tpl_id tpl_source users_block tls_emulation changed=0 users_block_needs_write=0
@@ -405,14 +405,14 @@ auto_migrate_legacy_state() {
tpl_id=$(detect_deployed_template_id) tpl_id=$(detect_deployed_template_id)
tpl_source=$(detect_template_source || echo "") tpl_source=$(detect_template_source || echo "")
if [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ] && [ -n "$tpl_id" ]; then if [ -d "$WEBSITE_ROOT" ] && [ -f "$WEBSITE_ROOT/index.html" ] && [ -n "$tpl_id" ]; then
echo "$tpl_id" > "$WEBSITE_ROOT/.gotelegram_template_id" 2>/dev/null || true echo "$tpl_id" > "$WEBSITE_ROOT/.pcatelegram_web_template_id" 2>/dev/null || true
[ -n "$tpl_source" ] && echo "$tpl_source" > "$WEBSITE_ROOT/.gotelegram_template_source" 2>/dev/null || true [ -n "$tpl_source" ] && echo "$tpl_source" > "$WEBSITE_ROOT/.pcatelegram_web_template_source" 2>/dev/null || true
fi fi
if [ -f "$TELEMT_CONFIG" ]; then if [ -f "$TELEMT_CONFIG" ]; then
if ! grep -q '\[server.api\]' "$TELEMT_CONFIG" 2>/dev/null || \ if ! grep -q '\[server.api\]' "$TELEMT_CONFIG" 2>/dev/null || \
! grep -q 'metrics_listen' "$TELEMT_CONFIG" 2>/dev/null || \ ! grep -q 'metrics_listen' "$TELEMT_CONFIG" 2>/dev/null || \
! grep -q "goTelegram Pro v${GOTELEGRAM_VERSION}" "$TELEMT_CONFIG" 2>/dev/null; then ! grep -q "PCAtelegram_web v${PCATELEGRAM_WEB_VERSION}" "$TELEMT_CONFIG" 2>/dev/null; then
generate_telemt_toml "$secret" "$port" "$mode" "$mask_host" "$mask_port" "$TELEMT_CONFIG" >&2 generate_telemt_toml "$secret" "$port" "$mode" "$mask_host" "$mask_port" "$TELEMT_CONFIG" >&2
replace_telemt_users_block "$users_block" "$TELEMT_CONFIG" replace_telemt_users_block "$users_block" "$TELEMT_CONFIG"
changed=1 changed=1
@@ -423,7 +423,7 @@ auto_migrate_legacy_state() {
fi fi
fi fi
write_normalized_gotelegram_config "$mode" "$port" "$secret" "$mask_host" "$domain" "$tpl_id" "$tpl_source" || \ write_normalized_pcatelegram_web_config "$mode" "$port" "$secret" "$mask_host" "$domain" "$tpl_id" "$tpl_source" || \
log_warning "Не удалось нормализовать config.json" log_warning "Не удалось нормализовать config.json"
if [ "$changed" = "1" ] && systemctl is-active --quiet "$TELEMT_SERVICE" 2>/dev/null; then if [ "$changed" = "1" ] && systemctl is-active --quiet "$TELEMT_SERVICE" 2>/dev/null; then
@@ -520,15 +520,15 @@ install_lite_mode() {
# Start # Start
start_telemt || return start_telemt || return
# Save goTelegram Pro config # Save PCAtelegram_web config
save_gotelegram_config "telemt" "lite" "$port" "$secret" "$domain" "" "" save_pcatelegram_web_config "telemt" "lite" "$port" "$secret" "$domain" "" ""
# Credits # Credits
show_credits show_credits
# Result # Result
show_proxy_info show_proxy_info
log_success "$(tf install_done "$GOTELEGRAM_VERSION" "Lite")" log_success "$(tf install_done "$PCATELEGRAM_WEB_VERSION" "Lite")"
} }
# ── Pro mode ──────────────────────────────────────────────────────────────── # ── Pro mode ────────────────────────────────────────────────────────────────
@@ -620,12 +620,12 @@ install_pro_mode() {
# Save config # Save config
local tpl_id local tpl_id
tpl_id=$(basename "$template_dir") tpl_id=$(basename "$template_dir")
save_gotelegram_config "telemt" "pro" "443" "$raw_secret" "$user_domain" "$user_domain" "$tpl_id" save_pcatelegram_web_config "telemt" "pro" "443" "$raw_secret" "$user_domain" "$user_domain" "$tpl_id"
# Result — use domain and fake-TLS link # Result — use domain and fake-TLS link
show_proxy_info_pro "$user_domain" "$faketls_secret" show_proxy_info_pro "$user_domain" "$faketls_secret"
echo -e " ${WHITE}$(t svc_site):${NC} ${GREEN}https://${user_domain}${NC}" echo -e " ${WHITE}$(t svc_site):${NC} ${GREEN}https://${user_domain}${NC}"
log_success "$(tf install_done "$GOTELEGRAM_VERSION" "Pro")" log_success "$(tf install_done "$PCATELEGRAM_WEB_VERSION" "Pro")"
} }
# ── Статус ─────────────────────────────────────────────────────────────────── # ── Статус ───────────────────────────────────────────────────────────────────
@@ -699,7 +699,7 @@ menu_share() {
echo "" echo ""
echo -e " ${BOLD}$(t share_title)${NC}" echo -e " ${BOLD}$(t share_title)${NC}"
echo "" echo ""
printf "$(t share_line1)\n" "$GOTELEGRAM_VERSION" printf "$(t share_line1)\n" "$PCATELEGRAM_WEB_VERSION"
echo "" echo ""
printf "$(t share_server)\n" "$server_display" printf "$(t share_server)\n" "$server_display"
printf "$(t share_port)\n" "$port" printf "$(t share_port)\n" "$port"
@@ -842,7 +842,7 @@ menu_remove() {
if [ "$mode" = "pro" ]; then if [ "$mode" = "pro" ]; then
remove_pro_mode remove_pro_mode
fi fi
rm -f "$GOTELEGRAM_CONFIG" rm -f "$PCATELEGRAM_WEB_CONFIG"
log_success "$(t remove_proxy_done)" log_success "$(t remove_proxy_done)"
;; ;;
2) 2)
@@ -861,7 +861,7 @@ menu_remove() {
if [ "$mode" = "pro" ]; then if [ "$mode" = "pro" ]; then
remove_pro_mode remove_pro_mode
fi fi
rm -f "$GOTELEGRAM_CONFIG" rm -f "$PCATELEGRAM_WEB_CONFIG"
# Bot # Bot
if [ "$(bot_service_status)" != "not_installed" ]; then if [ "$(bot_service_status)" != "not_installed" ]; then
systemctl stop "$BOT_SERVICE" 2>/dev/null systemctl stop "$BOT_SERVICE" 2>/dev/null
@@ -877,8 +877,8 @@ menu_remove() {
} }
# ── Telegram-бот ──────────────────────────────────────────────────────────── # ── Telegram-бот ────────────────────────────────────────────────────────────
BOT_DIR="/opt/gotelegram-bot" BOT_DIR="/opt/pcatelegram_web-bot"
BOT_SERVICE="gotelegram-bot" BOT_SERVICE="pcatelegram_web-bot"
admin_web_service_status() { admin_web_service_status() {
if ! systemctl list-unit-files "$ADMIN_WEB_SERVICE.service" &>/dev/null 2>&1; then if ! systemctl list-unit-files "$ADMIN_WEB_SERVICE.service" &>/dev/null 2>&1; then
@@ -906,7 +906,7 @@ install_admin_web() {
python_bin=$(command -v python3) python_bin=$(command -v python3)
cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << SVCEOF cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << SVCEOF
[Unit] [Unit]
Description=goTelegram Pro v${GOTELEGRAM_VERSION} Local Web Admin Description=PCAtelegram_web v${PCATELEGRAM_WEB_VERSION} Local Web Admin
After=network.target After=network.target
[Service] [Service]
@@ -915,8 +915,8 @@ WorkingDirectory=$ADMIN_WEB_DIR
ExecStart=$python_bin $ADMIN_WEB_DIR/server.py ExecStart=$python_bin $ADMIN_WEB_DIR/server.py
Restart=always Restart=always
RestartSec=5 RestartSec=5
Environment=GOTELEGRAM_ADMIN_HOST=$ADMIN_WEB_HOST Environment=PCATELEGRAM_WEB_ADMIN_HOST=$ADMIN_WEB_HOST
Environment=GOTELEGRAM_ADMIN_PORT=$ADMIN_WEB_PORT Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -964,20 +964,20 @@ bot_service_status() {
} }
auto_update_bot_if_possible() { auto_update_bot_if_possible() {
[ -d "$SCRIPT_DIR/gotelegram-bot" ] || return 0 [ -d "$SCRIPT_DIR/pcatelegram_web-bot" ] || return 0
[ "$(bot_service_status)" = "not_installed" ] && return 0 [ "$(bot_service_status)" = "not_installed" ] && return 0
[ -f "$BOT_DIR/.env" ] || return 0 [ -f "$BOT_DIR/.env" ] || return 0
local needs_update=0 local needs_update=0
[ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ] && \ [ -f "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" ] && \
! cmp -s "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/bot.py" && needs_update=1 ! cmp -s "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" "$BOT_DIR/bot.py" && needs_update=1
[ -f "$SCRIPT_DIR/gotelegram-bot/i18n.py" ] && \ [ -f "$SCRIPT_DIR/pcatelegram_web-bot/i18n.py" ] && \
! cmp -s "$SCRIPT_DIR/gotelegram-bot/i18n.py" "$BOT_DIR/i18n.py" && needs_update=1 ! cmp -s "$SCRIPT_DIR/pcatelegram_web-bot/i18n.py" "$BOT_DIR/i18n.py" && needs_update=1
[ -f "$SCRIPT_DIR/gotelegram-bot/requirements.txt" ] && \ [ -f "$SCRIPT_DIR/pcatelegram_web-bot/requirements.txt" ] && \
! cmp -s "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/requirements.txt" && needs_update=1 ! cmp -s "$SCRIPT_DIR/pcatelegram_web-bot/requirements.txt" "$BOT_DIR/requirements.txt" && needs_update=1
local lang_file lang_name local lang_file lang_name
for lang_file in "$SCRIPT_DIR"/gotelegram-bot/lang/*.json; do for lang_file in "$SCRIPT_DIR"/pcatelegram_web-bot/lang/*.json; do
[ -e "$lang_file" ] || continue [ -e "$lang_file" ] || continue
lang_name=$(basename "$lang_file") lang_name=$(basename "$lang_file")
! cmp -s "$lang_file" "$BOT_DIR/lang/$lang_name" && needs_update=1 ! cmp -s "$lang_file" "$BOT_DIR/lang/$lang_name" && needs_update=1
@@ -1101,27 +1101,27 @@ bot_install() {
# Copy bot files # Copy bot files
mkdir -p "$BOT_DIR" mkdir -p "$BOT_DIR"
if [ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ]; then if [ -f "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" ]; then
cp "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" "$BOT_DIR/"
cp "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/requirements.txt" "$BOT_DIR/"
[ -f "$SCRIPT_DIR/gotelegram-bot/config.example.env" ] && \ [ -f "$SCRIPT_DIR/pcatelegram_web-bot/config.example.env" ] && \
cp "$SCRIPT_DIR/gotelegram-bot/config.example.env" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/config.example.env" "$BOT_DIR/"
# Copy i18n language files for bot # Copy i18n language files for bot
if [ -d "$SCRIPT_DIR/gotelegram-bot/lang" ]; then if [ -d "$SCRIPT_DIR/pcatelegram_web-bot/lang" ]; then
mkdir -p "$BOT_DIR/lang" mkdir -p "$BOT_DIR/lang"
cp -f "$SCRIPT_DIR/gotelegram-bot/lang/"*.json "$BOT_DIR/lang/" 2>/dev/null cp -f "$SCRIPT_DIR/pcatelegram_web-bot/lang/"*.json "$BOT_DIR/lang/" 2>/dev/null
fi fi
[ -f "$SCRIPT_DIR/gotelegram-bot/i18n.py" ] && \ [ -f "$SCRIPT_DIR/pcatelegram_web-bot/i18n.py" ] && \
cp "$SCRIPT_DIR/gotelegram-bot/i18n.py" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/i18n.py" "$BOT_DIR/"
else else
log_error "$(tf bot_files_not_found "$SCRIPT_DIR/gotelegram-bot/")" log_error "$(tf bot_files_not_found "$SCRIPT_DIR/pcatelegram_web-bot/")"
return 1 return 1
fi fi
# Templates catalog — skip if source and dest are the same file (symlink install case) # Templates catalog — skip if source and dest are the same file (symlink install case)
if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then
local src_tc="$SCRIPT_DIR/templates_catalog.json" local src_tc="$SCRIPT_DIR/templates_catalog.json"
local dst_tc="$GOTELEGRAM_DIR/templates_catalog.json" local dst_tc="$PCATELEGRAM_WEB_DIR/templates_catalog.json"
if [ "$(readlink -f "$src_tc" 2>/dev/null)" != "$(readlink -f "$dst_tc" 2>/dev/null)" ]; then if [ "$(readlink -f "$src_tc" 2>/dev/null)" != "$(readlink -f "$dst_tc" 2>/dev/null)" ]; then
cp "$src_tc" "$dst_tc" cp "$src_tc" "$dst_tc"
fi fi
@@ -1217,7 +1217,7 @@ bot_install() {
# Systemd # Systemd
cat > "/etc/systemd/system/${BOT_SERVICE}.service" << SVCEOF cat > "/etc/systemd/system/${BOT_SERVICE}.service" << SVCEOF
[Unit] [Unit]
Description=goTelegram Pro v${GOTELEGRAM_VERSION} Telegram Bot Description=PCAtelegram_web v${PCATELEGRAM_WEB_VERSION} Telegram Bot
After=network.target After=network.target
[Service] [Service]
@@ -1407,7 +1407,7 @@ bot_remove() {
_promo_block() { _promo_block() {
# Print a promo section without width-fragile box borders (i18n safe) # Print a promo section without width-fragile box borders (i18n safe)
local line2; line2=$(printf '─%.0s' {1..54}) local line2; line2=$(printf '─%.0s' {1..54})
local youtube_link="${GOTELEGRAM_YOUTUBE_LINK:-}" local youtube_link="${PCATELEGRAM_WEB_YOUTUBE_LINK:-}"
echo "" echo ""
echo -e " ${DIM}${line2}${NC}" echo -e " ${DIM}${line2}${NC}"
echo -e " ${BOLD}${YELLOW}$(t promo_host1_title)${NC}" echo -e " ${BOLD}${YELLOW}$(t promo_host1_title)${NC}"
@@ -1437,7 +1437,7 @@ menu_promo() {
# ── Проверка: показывать ли промо (раз в сутки) ──────────────────────────── # ── Проверка: показывать ли промо (раз в сутки) ────────────────────────────
should_show_promo() { should_show_promo() {
local stamp_file="$GOTELEGRAM_DIR/.promo_last_shown" local stamp_file="$PCATELEGRAM_WEB_DIR/.promo_last_shown"
if [ ! -f "$stamp_file" ]; then if [ ! -f "$stamp_file" ]; then
return 0 # никогда не показывали return 0 # никогда не показывали
fi fi
@@ -1452,8 +1452,8 @@ should_show_promo() {
} }
mark_promo_shown() { mark_promo_shown() {
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
date +%s > "$GOTELEGRAM_DIR/.promo_last_shown" date +%s > "$PCATELEGRAM_WEB_DIR/.promo_last_shown"
} }
_promo_qr() { _promo_qr() {
@@ -1473,7 +1473,7 @@ show_promo_with_qr() {
_promo_qr "$(t promo_qr_host1)" "https://vk.cc/ct29NQ" _promo_qr "$(t promo_qr_host1)" "https://vk.cc/ct29NQ"
_promo_qr "$(t promo_qr_host2)" "https://vk.cc/cUxAhj" _promo_qr "$(t promo_qr_host2)" "https://vk.cc/cUxAhj"
_promo_qr "$(t promo_qr_tips)" "https://pay.cloudtips.ru/p/7410814f" _promo_qr "$(t promo_qr_tips)" "https://pay.cloudtips.ru/p/7410814f"
_promo_qr "$(t promo_qr_youtube)" "${GOTELEGRAM_YOUTUBE_LINK:-}" _promo_qr "$(t promo_qr_youtube)" "${PCATELEGRAM_WEB_YOUTUBE_LINK:-}"
fi fi
mark_promo_shown mark_promo_shown
@@ -1489,10 +1489,10 @@ show_promo_with_qr() {
# ── First-run: pick language ───────────────────────────────────────────────── # ── First-run: pick language ─────────────────────────────────────────────────
first_run_language_picker() { first_run_language_picker() {
# Show picker only if language not yet saved # Show picker only if language not yet saved
local marker="${GOTELEGRAM_DIR:-/opt/gotelegram}/.language" local marker="${PCATELEGRAM_WEB_DIR:-/opt/pcatelegram_web}/.language"
local cfg_lang="" local cfg_lang=""
if [ -f "$GOTELEGRAM_CONFIG" ] && command -v jq >/dev/null 2>&1; then if [ -f "$PCATELEGRAM_WEB_CONFIG" ] && command -v jq >/dev/null 2>&1; then
cfg_lang=$(jq -r '.language // empty' "$GOTELEGRAM_CONFIG" 2>/dev/null) cfg_lang=$(jq -r '.language // empty' "$PCATELEGRAM_WEB_CONFIG" 2>/dev/null)
fi fi
if [ -f "$marker" ] || [ -n "$cfg_lang" ]; then if [ -f "$marker" ] || [ -n "$cfg_lang" ]; then
return 0 return 0
@@ -1524,8 +1524,8 @@ menu_language() {
# Non-interactive action dispatcher (bot / CI / scripting interface) # Non-interactive action dispatcher (bot / CI / scripting interface)
# ══════════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════════
# Usage examples: # Usage examples:
# gotelegram --action=change-template --template=th_ariclaw --json # pcatelegram_web --action=change-template --template=th_ariclaw --json
# gotelegram --action=change-lite-domain --domain=google.com --json # pcatelegram_web --action=change-lite-domain --domain=google.com --json
# #
# Rules for action handlers: # Rules for action handlers:
# - Only JSON may be written to stdout (the caller parses it). # - Only JSON may be written to stdout (the caller parses it).
@@ -1558,7 +1558,7 @@ bot_emit_json() {
bot_update_config_field() { bot_update_config_field() {
local key="$1" local key="$1"
local value="$2" local value="$2"
if [ ! -f "$GOTELEGRAM_CONFIG" ]; then if [ ! -f "$PCATELEGRAM_WEB_CONFIG" ]; then
return 1 return 1
fi fi
local tmp now local tmp now
@@ -1566,9 +1566,9 @@ bot_update_config_field() {
now=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S%z) now=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S%z)
if jq --arg k "$key" --arg v "$value" --arg t "$now" \ if jq --arg k "$key" --arg v "$value" --arg t "$now" \
'.[$k] = $v | .updated_at = $t' \ '.[$k] = $v | .updated_at = $t' \
"$GOTELEGRAM_CONFIG" > "$tmp" 2>/dev/null; then "$PCATELEGRAM_WEB_CONFIG" > "$tmp" 2>/dev/null; then
mv "$tmp" "$GOTELEGRAM_CONFIG" mv "$tmp" "$PCATELEGRAM_WEB_CONFIG"
chmod 600 "$GOTELEGRAM_CONFIG" chmod 600 "$PCATELEGRAM_WEB_CONFIG"
return 0 return 0
fi fi
rm -f "$tmp" rm -f "$tmp"
@@ -1713,7 +1713,7 @@ bot_action_change_lite_domain() {
# parallel `change-lite-domain` calls raced on the jq-rewrite of config.json # parallel `change-lite-domain` calls raced on the jq-rewrite of config.json
# and one process would see a truncated file ("no secret in config"). # and one process would see a truncated file ("no secret in config").
bot_action_dispatch() { bot_action_dispatch() {
local lock_file="/var/lock/gotelegram-bot-action.lock" local lock_file="/var/lock/pcatelegram_web-bot-action.lock"
# Make sure /var/lock exists (it does on Debian/Ubuntu; be defensive for minimal images) # Make sure /var/lock exists (it does on Debian/Ubuntu; be defensive for minimal images)
[ -d /var/lock ] || mkdir -p /var/lock 2>/dev/null || true [ -d /var/lock ] || mkdir -p /var/lock 2>/dev/null || true

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — Установка Telegram-бота # PCAtelegram_web v2.5.0 — Установка Telegram-бота
# Создаёт venv, ставит зависимости, настраивает systemd # Создаёт venv, ставит зависимости, настраивает systemd
set -e set -e
@@ -9,11 +9,11 @@ YELLOW='\033[1;33m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
BOT_DIR="/opt/gotelegram-bot" BOT_DIR="/opt/pcatelegram_web-bot"
SERVICE_NAME="gotelegram-bot" SERVICE_NAME="pcatelegram_web-bot"
GOTELEGRAM_DIR="/opt/gotelegram" PCATELEGRAM_WEB_DIR="/opt/pcatelegram_web"
ADMIN_WEB_DIR="/opt/gotelegram-admin" ADMIN_WEB_DIR="/opt/pcatelegram_web-admin"
ADMIN_WEB_SERVICE="gotelegram-admin" ADMIN_WEB_SERVICE="pcatelegram_web-admin"
ADMIN_WEB_PORT="1984" ADMIN_WEB_PORT="1984"
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
@@ -22,7 +22,7 @@ if [ "$EUID" -ne 0 ]; then
fi fi
echo -e "${CYAN}╔═══════════════════════════════════════════╗${NC}" echo -e "${CYAN}╔═══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}${NC} ${GREEN}GoTelegram v2.5.0 — Установка бота${NC} ${CYAN}${NC}" echo -e "${CYAN}${NC} ${GREEN}PCAtelegram_web v2.5.0 — Установка бота${NC} ${CYAN}${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════════╝${NC}" echo -e "${CYAN}╚═══════════════════════════════════════════╝${NC}"
echo "" echo ""
@@ -44,20 +44,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -f "$SCRIPT_DIR/lib/common.sh" ] && source "$SCRIPT_DIR/lib/common.sh" || true [ -f "$SCRIPT_DIR/lib/common.sh" ] && source "$SCRIPT_DIR/lib/common.sh" || true
[ -f "$SCRIPT_DIR/lib/stats.sh" ] && source "$SCRIPT_DIR/lib/stats.sh" || true [ -f "$SCRIPT_DIR/lib/stats.sh" ] && source "$SCRIPT_DIR/lib/stats.sh" || true
if [ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ]; then if [ -f "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" ]; then
echo -e "${GREEN}[*] Копирование файлов бота...${NC}" echo -e "${GREEN}[*] Копирование файлов бота...${NC}"
cp "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/bot.py" "$BOT_DIR/"
cp "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/" cp "$SCRIPT_DIR/pcatelegram_web-bot/requirements.txt" "$BOT_DIR/"
[ -f "$SCRIPT_DIR/gotelegram-bot/config.example.env" ] && cp "$SCRIPT_DIR/gotelegram-bot/config.example.env" "$BOT_DIR/" [ -f "$SCRIPT_DIR/pcatelegram_web-bot/config.example.env" ] && cp "$SCRIPT_DIR/pcatelegram_web-bot/config.example.env" "$BOT_DIR/"
else else
echo -e "${RED}Файлы бота не найдены в $SCRIPT_DIR/gotelegram-bot/${NC}" echo -e "${RED}Файлы бота не найдены в $SCRIPT_DIR/pcatelegram_web-bot/${NC}"
exit 1 exit 1
fi fi
# Копируем каталог шаблонов # Копируем каталог шаблонов
if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$SCRIPT_DIR/templates_catalog.json" "$GOTELEGRAM_DIR/" cp "$SCRIPT_DIR/templates_catalog.json" "$PCATELEGRAM_WEB_DIR/"
echo -e "${GREEN}[*] Каталог шаблонов скопирован${NC}" echo -e "${GREEN}[*] Каталог шаблонов скопирован${NC}"
fi fi
@@ -98,7 +98,7 @@ fi
# ── Systemd ────────────────────────────────────────────────────────────────── # ── Systemd ──────────────────────────────────────────────────────────────────
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
[Unit] [Unit]
Description=GoTelegram v2.5.0 Telegram Bot Description=PCAtelegram_web v2.5.0 Telegram Bot
After=network.target After=network.target
[Service] [Service]
@@ -130,7 +130,7 @@ if [ -f "$SCRIPT_DIR/admin-web/server.py" ]; then
PYTHON_BIN=$(command -v python3) PYTHON_BIN=$(command -v python3)
cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << EOF cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << EOF
[Unit] [Unit]
Description=GoTelegram v2.5.0 Local Web Admin Description=PCAtelegram_web v2.5.0 Local Web Admin
After=network.target After=network.target
[Service] [Service]
@@ -139,8 +139,8 @@ WorkingDirectory=$ADMIN_WEB_DIR
ExecStart=$PYTHON_BIN $ADMIN_WEB_DIR/server.py ExecStart=$PYTHON_BIN $ADMIN_WEB_DIR/server.py
Restart=always Restart=always
RestartSec=5 RestartSec=5
Environment=GOTELEGRAM_ADMIN_HOST=127.0.0.1 Environment=PCATELEGRAM_WEB_ADMIN_HOST=127.0.0.1
Environment=GOTELEGRAM_ADMIN_PORT=$ADMIN_WEB_PORT Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

142
lib/backup.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# goTelegram Pro v2.5.0 — backup and restore (i18n-aware) # PCAtelegram_web v2.5.0 — backup and restore (i18n-aware)
# ── Создание бекапа ────────────────────────────────────────────────────────── # ── Создание бекапа ──────────────────────────────────────────────────────────
create_backup() { create_backup() {
@@ -12,9 +12,9 @@ create_backup() {
mkdir -p "$output_dir" mkdir -p "$output_dir"
while true; do while true; do
if [ "$suffix" -eq 0 ]; then if [ "$suffix" -eq 0 ]; then
backup_name="gotelegram_backup_${timestamp}" backup_name="pcatelegram_web_backup_${timestamp}"
else else
backup_name="gotelegram_backup_${timestamp}_${suffix}" backup_name="pcatelegram_web_backup_${timestamp}_${suffix}"
fi fi
tmp_dir="/tmp/${backup_name}" tmp_dir="/tmp/${backup_name}"
if [ ! -e "$tmp_dir" ] && \ if [ ! -e "$tmp_dir" ] && \
@@ -34,20 +34,20 @@ create_backup() {
cp "$TELEMT_CONFIG" "$tmp_dir/config.toml" cp "$TELEMT_CONFIG" "$tmp_dir/config.toml"
fi fi
# goTelegram Pro конфиг # PCAtelegram_web конфиг
if [ -f "$GOTELEGRAM_CONFIG" ]; then if [ -f "$PCATELEGRAM_WEB_CONFIG" ]; then
cp "$GOTELEGRAM_CONFIG" "$tmp_dir/gotelegram.json" cp "$PCATELEGRAM_WEB_CONFIG" "$tmp_dir/pcatelegram_web.json"
fi fi
if [ -f "$GOTELEGRAM_DIR/disabled_users.json" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/disabled_users.json" ]; then
cp "$GOTELEGRAM_DIR/disabled_users.json" "$tmp_dir/disabled_users.json" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/disabled_users.json" "$tmp_dir/disabled_users.json" 2>/dev/null
fi fi
if [ -f "$GOTELEGRAM_DIR/backup_schedule.json" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/backup_schedule.json" ]; then
cp "$GOTELEGRAM_DIR/backup_schedule.json" "$tmp_dir/backup_schedule.json" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/backup_schedule.json" "$tmp_dir/backup_schedule.json" 2>/dev/null
fi fi
# Language marker (i18n) # Language marker (i18n)
if [ -f "$GOTELEGRAM_DIR/.language" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/.language" ]; then
cp "$GOTELEGRAM_DIR/.language" "$tmp_dir/.language" cp "$PCATELEGRAM_WEB_DIR/.language" "$tmp_dir/.language"
fi fi
# nginx конфиг (stealth mode) # nginx конфиг (stealth mode)
@@ -76,12 +76,12 @@ create_backup() {
fi fi
# Custom templates and catalog # Custom templates and catalog
if [ -d "$GOTELEGRAM_DIR/custom_templates" ]; then if [ -d "$PCATELEGRAM_WEB_DIR/custom_templates" ]; then
mkdir -p "$tmp_dir/custom_templates" mkdir -p "$tmp_dir/custom_templates"
cp -a "$GOTELEGRAM_DIR/custom_templates/." "$tmp_dir/custom_templates/" 2>/dev/null cp -a "$PCATELEGRAM_WEB_DIR/custom_templates/." "$tmp_dir/custom_templates/" 2>/dev/null
fi fi
if [ -f "$GOTELEGRAM_DIR/templates_catalog.json" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/templates_catalog.json" ]; then
cp "$GOTELEGRAM_DIR/templates_catalog.json" "$tmp_dir/templates_catalog.json" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/templates_catalog.json" "$tmp_dir/templates_catalog.json" 2>/dev/null
fi fi
# Bot state (.env has BotFather token, so encrypted backups are strongly recommended) # Bot state (.env has BotFather token, so encrypted backups are strongly recommended)
@@ -100,14 +100,14 @@ create_backup() {
fi fi
# Traffic history # Traffic history
if [ -f "$GOTELEGRAM_DIR/stats_history.csv" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/stats_history.csv" ]; then
cp "$GOTELEGRAM_DIR/stats_history.csv" "$tmp_dir/stats_history.csv" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/stats_history.csv" "$tmp_dir/stats_history.csv" 2>/dev/null
fi fi
if [ -f "$GOTELEGRAM_DIR/user_stats_history.csv" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/user_stats_history.csv" ]; then
cp "$GOTELEGRAM_DIR/user_stats_history.csv" "$tmp_dir/user_stats_history.csv" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/user_stats_history.csv" "$tmp_dir/user_stats_history.csv" 2>/dev/null
fi fi
if [ -f "$GOTELEGRAM_DIR/shared-443.json" ]; then if [ -f "$PCATELEGRAM_WEB_DIR/shared-443.json" ]; then
cp "$GOTELEGRAM_DIR/shared-443.json" "$tmp_dir/shared-443.json" 2>/dev/null cp "$PCATELEGRAM_WEB_DIR/shared-443.json" "$tmp_dir/shared-443.json" 2>/dev/null
fi fi
# Метаданные # Метаданные
@@ -124,7 +124,7 @@ create_backup() {
cat > "$tmp_dir/metadata.json" << EOMETA cat > "$tmp_dir/metadata.json" << EOMETA
{ {
"backup_version": "1.6", "backup_version": "1.6",
"gotelegram_version": "$GOTELEGRAM_VERSION", "pcatelegram_web_version": "$PCATELEGRAM_WEB_VERSION",
"created_at": "$(date -Iseconds)", "created_at": "$(date -Iseconds)",
"hostname": "$(hostname)", "hostname": "$(hostname)",
"ip": "$ip", "ip": "$ip",
@@ -201,7 +201,7 @@ restore_backup() {
return 1 return 1
fi fi
local tmp_dir="/tmp/gotelegram_restore_$$" local tmp_dir="/tmp/pcatelegram_web_restore_$$"
mkdir -p "$tmp_dir" mkdir -p "$tmp_dir"
# Расшифровываем если нужно # Расшифровываем если нужно
@@ -212,7 +212,7 @@ restore_backup() {
read -rs password read -rs password
echo "" echo ""
fi fi
tar_file="/tmp/gotelegram_restore_$$.tar.gz" tar_file="/tmp/pcatelegram_web_restore_$$.tar.gz"
openssl enc -aes-256-cbc -d -pbkdf2 -in "$backup_file" -out "$tar_file" -pass "pass:${password}" 2>/dev/null openssl enc -aes-256-cbc -d -pbkdf2 -in "$backup_file" -out "$tar_file" -pass "pass:${password}" 2>/dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
log_error "$(_t_or backup_bad_pass 'Неверный пароль или повреждённый файл')" log_error "$(_t_or backup_bad_pass 'Неверный пароль или повреждённый файл')"
@@ -233,25 +233,25 @@ restore_backup() {
# Находим папку бекапа # Находим папку бекапа
local backup_dir local backup_dir
backup_dir=$(find "$tmp_dir" -maxdepth 1 -type d -name "gotelegram_backup_*" | head -1) backup_dir=$(find "$tmp_dir" -maxdepth 1 -type d -name "pcatelegram_web_backup_*" | head -1)
[ -z "$backup_dir" ] && backup_dir="$tmp_dir" [ -z "$backup_dir" ] && backup_dir="$tmp_dir"
# Legacy bot backups before v2.5.0 stored absolute paths directly in tar: # Legacy bot backups before v2.5.0 stored absolute paths directly in tar:
# opt/gotelegram/config.json and etc/telemt/config.toml. # opt/pcatelegram_web/config.json and etc/telemt/config.toml.
if [ ! -f "$backup_dir/config.toml" ] && [ -f "$tmp_dir/etc/telemt/config.toml" ]; then if [ ! -f "$backup_dir/config.toml" ] && [ -f "$tmp_dir/etc/telemt/config.toml" ]; then
cp "$tmp_dir/etc/telemt/config.toml" "$backup_dir/config.toml" 2>/dev/null || true cp "$tmp_dir/etc/telemt/config.toml" "$backup_dir/config.toml" 2>/dev/null || true
fi fi
if [ ! -f "$backup_dir/gotelegram.json" ] && [ -f "$tmp_dir/opt/gotelegram/config.json" ]; then if [ ! -f "$backup_dir/pcatelegram_web.json" ] && [ -f "$tmp_dir/opt/pcatelegram_web/config.json" ]; then
cp "$tmp_dir/opt/gotelegram/config.json" "$backup_dir/gotelegram.json" 2>/dev/null || true cp "$tmp_dir/opt/pcatelegram_web/config.json" "$backup_dir/pcatelegram_web.json" 2>/dev/null || true
fi fi
if [ ! -f "$backup_dir/disabled_users.json" ] && [ -f "$tmp_dir/opt/gotelegram/disabled_users.json" ]; then if [ ! -f "$backup_dir/disabled_users.json" ] && [ -f "$tmp_dir/opt/pcatelegram_web/disabled_users.json" ]; then
cp "$tmp_dir/opt/gotelegram/disabled_users.json" "$backup_dir/disabled_users.json" 2>/dev/null || true cp "$tmp_dir/opt/pcatelegram_web/disabled_users.json" "$backup_dir/disabled_users.json" 2>/dev/null || true
fi fi
# Проверяем метаданные # Проверяем метаданные
if [ -f "$backup_dir/metadata.json" ]; then if [ -f "$backup_dir/metadata.json" ]; then
local bk_version bk_mode bk_ip bk_lang bk_date local bk_version bk_mode bk_ip bk_lang bk_date
bk_version=$(jq -r '.gotelegram_version // "unknown"' "$backup_dir/metadata.json") bk_version=$(jq -r '.pcatelegram_web_version // "unknown"' "$backup_dir/metadata.json")
bk_mode=$(jq -r '.mode // "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_ip=$(jq -r '.ip // "unknown"' "$backup_dir/metadata.json")
bk_lang=$(jq -r '.language // "-"' "$backup_dir/metadata.json") bk_lang=$(jq -r '.language // "-"' "$backup_dir/metadata.json")
@@ -280,24 +280,24 @@ restore_backup() {
log_success "$(_t_or backup_restored_telemt 'telemt конфиг восстановлен')" log_success "$(_t_or backup_restored_telemt 'telemt конфиг восстановлен')"
fi fi
# Восстанавливаем goTelegram Pro конфиг # Восстанавливаем PCAtelegram_web конфиг
if [ -f "$backup_dir/gotelegram.json" ]; then if [ -f "$backup_dir/pcatelegram_web.json" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$backup_dir/gotelegram.json" "$GOTELEGRAM_CONFIG" cp "$backup_dir/pcatelegram_web.json" "$PCATELEGRAM_WEB_CONFIG"
log_success "$(_t_or backup_restored_gotelegram 'GoTelegram конфиг восстановлен')" log_success "$(_t_or backup_restored_pcatelegram_web 'PCAtelegram_web конфиг восстановлен')"
fi fi
if [ -f "$backup_dir/disabled_users.json" ]; then if [ -f "$backup_dir/disabled_users.json" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$backup_dir/disabled_users.json" "$GOTELEGRAM_DIR/disabled_users.json" cp "$backup_dir/disabled_users.json" "$PCATELEGRAM_WEB_DIR/disabled_users.json"
chmod 600 "$GOTELEGRAM_DIR/disabled_users.json" 2>/dev/null || true chmod 600 "$PCATELEGRAM_WEB_DIR/disabled_users.json" 2>/dev/null || true
fi fi
if [ -f "$backup_dir/backup_schedule.json" ]; then if [ -f "$backup_dir/backup_schedule.json" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$backup_dir/backup_schedule.json" "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null cp "$backup_dir/backup_schedule.json" "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null
chmod 600 "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null || true chmod 600 "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null || true
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
local restored_schedule local restored_schedule
restored_schedule=$(jq -r '.frequency // "off"' "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null || echo "off") restored_schedule=$(jq -r '.frequency // "off"' "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null || echo "off")
case "$restored_schedule" in case "$restored_schedule" in
off|daily|weekly|monthly) set_backup_schedule "$restored_schedule" >/dev/null 2>&1 || true ;; off|daily|weekly|monthly) set_backup_schedule "$restored_schedule" >/dev/null 2>&1 || true ;;
esac esac
@@ -306,8 +306,8 @@ restore_backup() {
# Восстанавливаем language marker (i18n) # Восстанавливаем language marker (i18n)
if [ -f "$backup_dir/.language" ]; then if [ -f "$backup_dir/.language" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$backup_dir/.language" "$GOTELEGRAM_DIR/.language" cp "$backup_dir/.language" "$PCATELEGRAM_WEB_DIR/.language"
log_success "$(_t_or backup_restored_lang 'Язык интерфейса восстановлен')" log_success "$(_t_or backup_restored_lang 'Язык интерфейса восстановлен')"
fi fi
@@ -347,23 +347,23 @@ restore_backup() {
# Восстанавливаем custom templates/catalog/statistics # Восстанавливаем custom templates/catalog/statistics
if [ -d "$backup_dir/custom_templates" ]; then if [ -d "$backup_dir/custom_templates" ]; then
mkdir -p "$GOTELEGRAM_DIR/custom_templates" mkdir -p "$PCATELEGRAM_WEB_DIR/custom_templates"
cp -a "$backup_dir/custom_templates/." "$GOTELEGRAM_DIR/custom_templates/" 2>/dev/null cp -a "$backup_dir/custom_templates/." "$PCATELEGRAM_WEB_DIR/custom_templates/" 2>/dev/null
log_success "Пользовательские шаблоны восстановлены" log_success "Пользовательские шаблоны восстановлены"
fi fi
if [ -f "$backup_dir/templates_catalog.json" ]; then if [ -f "$backup_dir/templates_catalog.json" ]; then
cp "$backup_dir/templates_catalog.json" "$GOTELEGRAM_DIR/templates_catalog.json" 2>/dev/null cp "$backup_dir/templates_catalog.json" "$PCATELEGRAM_WEB_DIR/templates_catalog.json" 2>/dev/null
fi fi
if [ -f "$backup_dir/stats_history.csv" ]; then if [ -f "$backup_dir/stats_history.csv" ]; then
cp "$backup_dir/stats_history.csv" "$GOTELEGRAM_DIR/stats_history.csv" 2>/dev/null cp "$backup_dir/stats_history.csv" "$PCATELEGRAM_WEB_DIR/stats_history.csv" 2>/dev/null
log_success "История статистики восстановлена" log_success "История статистики восстановлена"
fi fi
if [ -f "$backup_dir/user_stats_history.csv" ]; then if [ -f "$backup_dir/user_stats_history.csv" ]; then
cp "$backup_dir/user_stats_history.csv" "$GOTELEGRAM_DIR/user_stats_history.csv" 2>/dev/null cp "$backup_dir/user_stats_history.csv" "$PCATELEGRAM_WEB_DIR/user_stats_history.csv" 2>/dev/null
log_success "История статистики пользователей восстановлена" log_success "История статистики пользователей восстановлена"
fi fi
if [ -f "$backup_dir/shared-443.json" ]; then if [ -f "$backup_dir/shared-443.json" ]; then
cp "$backup_dir/shared-443.json" "$GOTELEGRAM_DIR/shared-443.json" 2>/dev/null cp "$backup_dir/shared-443.json" "$PCATELEGRAM_WEB_DIR/shared-443.json" 2>/dev/null
fi fi
# Восстанавливаем состояние бота # Восстанавливаем состояние бота
@@ -392,8 +392,8 @@ restore_backup() {
start_telemt start_telemt
fi fi
command -v nginx &>/dev/null && systemctl start nginx 2>/dev/null command -v nginx &>/dev/null && systemctl start nginx 2>/dev/null
systemctl restart gotelegram-bot 2>/dev/null || true systemctl restart pcatelegram_web-bot 2>/dev/null || true
systemctl restart gotelegram-admin 2>/dev/null || true systemctl restart pcatelegram_web-admin 2>/dev/null || true
# Очистка # Очистка
rm -rf "$tmp_dir" rm -rf "$tmp_dir"
@@ -469,43 +469,43 @@ set_backup_schedule() {
return 1 return 1
fi fi
mkdir -p "$GOTELEGRAM_DIR" "$BACKUP_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR" "$BACKUP_DIR"
if [ "$frequency" = "off" ]; then if [ "$frequency" = "off" ]; then
systemctl disable --now gotelegram-backup.timer >/dev/null 2>&1 || true systemctl disable --now pcatelegram_web-backup.timer >/dev/null 2>&1 || true
rm -f /etc/systemd/system/gotelegram-backup.timer /etc/systemd/system/gotelegram-backup.service rm -f /etc/systemd/system/pcatelegram_web-backup.timer /etc/systemd/system/pcatelegram_web-backup.service
systemctl daemon-reload >/dev/null 2>&1 || true systemctl daemon-reload >/dev/null 2>&1 || true
else else
cat > /etc/systemd/system/gotelegram-backup.service << 'EOSVC' cat > /etc/systemd/system/pcatelegram_web-backup.service << 'EOSVC'
[Unit] [Unit]
Description=goTelegram Pro backup Description=PCAtelegram_web backup
Wants=network-online.target Wants=network-online.target
After=network-online.target After=network-online.target
[Service] [Service]
Type=oneshot Type=oneshot
Environment=GOTELEGRAM_BACKUP_KEEP=30 Environment=PCATELEGRAM_WEB_BACKUP_KEEP=30
ExecStart=/bin/bash -lc 'source /opt/gotelegram/lib/common.sh; source /opt/gotelegram/lib/i18n.sh; source /opt/gotelegram/lib/telemt.sh; source /opt/gotelegram/lib/website.sh; source /opt/gotelegram/lib/backup.sh; load_language "$(detect_language 2>/dev/null || echo en)"; create_backup ""; cleanup_old_backups "${GOTELEGRAM_BACKUP_KEEP:-30}"' ExecStart=/bin/bash -lc 'source /opt/pcatelegram_web/lib/common.sh; source /opt/pcatelegram_web/lib/i18n.sh; source /opt/pcatelegram_web/lib/telemt.sh; source /opt/pcatelegram_web/lib/website.sh; source /opt/pcatelegram_web/lib/backup.sh; load_language "$(detect_language 2>/dev/null || echo en)"; create_backup ""; cleanup_old_backups "${PCATELEGRAM_WEB_BACKUP_KEEP:-30}"'
EOSVC EOSVC
cat > /etc/systemd/system/gotelegram-backup.timer << EOTIMER cat > /etc/systemd/system/pcatelegram_web-backup.timer << EOTIMER
[Unit] [Unit]
Description=goTelegram Pro scheduled backup Description=PCAtelegram_web scheduled backup
[Timer] [Timer]
OnCalendar=$calendar OnCalendar=$calendar
Persistent=true Persistent=true
RandomizedDelaySec=15m RandomizedDelaySec=15m
Unit=gotelegram-backup.service Unit=pcatelegram_web-backup.service
[Install] [Install]
WantedBy=timers.target WantedBy=timers.target
EOTIMER EOTIMER
systemctl daemon-reload >/dev/null 2>&1 || return 1 systemctl daemon-reload >/dev/null 2>&1 || return 1
systemctl enable --now gotelegram-backup.timer >/dev/null 2>&1 || return 1 systemctl enable --now pcatelegram_web-backup.timer >/dev/null 2>&1 || return 1
fi fi
cat > "$GOTELEGRAM_DIR/backup_schedule.json" << EOSCHEDULE cat > "$PCATELEGRAM_WEB_DIR/backup_schedule.json" << EOSCHEDULE
{ {
"frequency": "$frequency", "frequency": "$frequency",
"calendar": "$calendar", "calendar": "$calendar",
@@ -513,19 +513,19 @@ EOTIMER
"updated_at": "$(date -Iseconds)" "updated_at": "$(date -Iseconds)"
} }
EOSCHEDULE EOSCHEDULE
chmod 600 "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null || true chmod 600 "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null || true
log_success "Backup schedule: $frequency" log_success "Backup schedule: $frequency"
echo "$frequency" echo "$frequency"
} }
backup_schedule_status() { backup_schedule_status() {
local frequency="off" calendar="" local frequency="off" calendar=""
if [ -f "$GOTELEGRAM_DIR/backup_schedule.json" ] && command -v jq >/dev/null 2>&1; then if [ -f "$PCATELEGRAM_WEB_DIR/backup_schedule.json" ] && command -v jq >/dev/null 2>&1; then
frequency=$(jq -r '.frequency // "off"' "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null || echo "off") frequency=$(jq -r '.frequency // "off"' "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null || echo "off")
calendar=$(jq -r '.calendar // ""' "$GOTELEGRAM_DIR/backup_schedule.json" 2>/dev/null || echo "") calendar=$(jq -r '.calendar // ""' "$PCATELEGRAM_WEB_DIR/backup_schedule.json" 2>/dev/null || echo "")
fi fi
echo "frequency=$frequency calendar=$calendar" echo "frequency=$frequency calendar=$calendar"
systemctl list-timers gotelegram-backup.timer --no-pager 2>/dev/null || true systemctl list-timers pcatelegram_web-backup.timer --no-pager 2>/dev/null || true
} }
# ── Интерактивный бекап ────────────────────────────────────────────────────── # ── Интерактивный бекап ──────────────────────────────────────────────────────

96
lib/common.sh Normal file → Executable file
View File

@@ -1,32 +1,32 @@
#!/bin/bash #!/bin/bash
# goTelegram Pro v2.5.0 — common utilities # PCAtelegram_web v2.5.0 — common utilities
# Colors, logging, spinner, system helpers, v1 compat, i18n-aware # Colors, logging, spinner, system helpers, v1 compat, i18n-aware
# ── Version ─────────────────────────────────────────────────────────────────── # ── Version ───────────────────────────────────────────────────────────────────
GOTELEGRAM_VERSION="2.5.0" PCATELEGRAM_WEB_VERSION="2.5.0"
GOTELEGRAM_NAME="goTelegram Pro" PCATELEGRAM_WEB_NAME="PCAtelegram_web"
# ── Пути ────────────────────────────────────────────────────────────────────── # ── Пути ──────────────────────────────────────────────────────────────────────
GOTELEGRAM_DIR="/opt/gotelegram" PCATELEGRAM_WEB_DIR="/opt/pcatelegram_web"
GOTELEGRAM_CONFIG="$GOTELEGRAM_DIR/config.json" PCATELEGRAM_WEB_CONFIG="$PCATELEGRAM_WEB_DIR/config.json"
TELEMT_CONFIG="/etc/telemt/config.toml" TELEMT_CONFIG="/etc/telemt/config.toml"
TELEMT_BIN="/usr/local/bin/telemt" TELEMT_BIN="/usr/local/bin/telemt"
TELEMT_SERVICE="telemt" TELEMT_SERVICE="telemt"
NGINX_SITE_CONF="/etc/nginx/sites-available/gotelegram" NGINX_SITE_CONF="/etc/nginx/sites-available/pcatelegram_web"
NGINX_SITE_LINK="/etc/nginx/sites-enabled/gotelegram" NGINX_SITE_LINK="/etc/nginx/sites-enabled/pcatelegram_web"
WEBSITE_ROOT="/var/www/gotelegram-site" WEBSITE_ROOT="/var/www/pcatelegram_web-site"
BACKUP_DIR="$GOTELEGRAM_DIR/backups" BACKUP_DIR="$PCATELEGRAM_WEB_DIR/backups"
LOG_FILE="/var/log/gotelegram.log" LOG_FILE="/var/log/pcatelegram_web.log"
BOT_DIR="/opt/gotelegram-bot" BOT_DIR="/opt/pcatelegram_web-bot"
ADMIN_WEB_DIR="/opt/gotelegram-admin" ADMIN_WEB_DIR="/opt/pcatelegram_web-admin"
ADMIN_WEB_SERVICE="gotelegram-admin" ADMIN_WEB_SERVICE="pcatelegram_web-admin"
ADMIN_WEB_HOST="127.0.0.1" ADMIN_WEB_HOST="127.0.0.1"
ADMIN_WEB_PORT="1984" ADMIN_WEB_PORT="1984"
# ── V1 совместимость ───────────────────────────────────────────────────────── # ── V1 совместимость ─────────────────────────────────────────────────────────
V1_CONTAINER_NAME="mtproto-proxy" V1_CONTAINER_NAME="mtproto-proxy"
V1_CONFIG_FILE="/opt/gotelegram-bot/proxy.json" V1_CONFIG_FILE="/opt/pcatelegram_web-bot/proxy.json"
V1_SERVICE_NAME="gotelegram-bot" V1_SERVICE_NAME="pcatelegram_web-bot"
# ── Цвета ──────────────────────────────────────────────────────────────────── # ── Цвета ────────────────────────────────────────────────────────────────────
RED='\033[0;31m' RED='\033[0;31m'
@@ -93,7 +93,7 @@ progress_bar() {
# ── Выполнение с индикатором ───────────────────────────────────────────────── # ── Выполнение с индикатором ─────────────────────────────────────────────────
run_with_spinner() { run_with_spinner() {
local label="$1"; shift local label="$1"; shift
local err_file="/tmp/.gotelegram_spinner_err_$$" local err_file="/tmp/.pcatelegram_web_spinner_err_$$"
spinner_start "$label" spinner_start "$label"
"$@" >/dev/null 2>"$err_file" "$@" >/dev/null 2>"$err_file"
local rc=$? local rc=$?
@@ -119,11 +119,11 @@ show_banner() {
echo "" echo ""
echo -e "${CYAN}${line}${NC}" echo -e "${CYAN}${line}${NC}"
if type tf &>/dev/null; then if type tf &>/dev/null; then
echo -e " ${BOLD}${WHITE}🚀 $(tf banner_title "$GOTELEGRAM_VERSION")${NC}" echo -e " ${BOLD}${WHITE}🚀 $(tf banner_title "$PCATELEGRAM_WEB_VERSION")${NC}"
echo -e " ${DIM}$(t banner_subtitle)${NC}" echo -e " ${DIM}$(t banner_subtitle)${NC}"
echo -e " ${DIM}$(t banner_features)${NC}" echo -e " ${DIM}$(t banner_features)${NC}"
else else
echo -e " ${BOLD}${WHITE}🚀 goTelegram Pro v${GOTELEGRAM_VERSION}${NC}" echo -e " ${BOLD}${WHITE}🚀 PCAtelegram_web v${PCATELEGRAM_WEB_VERSION}${NC}"
echo -e " ${DIM}MTProxy powered by telemt (Rust + Tokio)${NC}" echo -e " ${DIM}MTProxy powered by telemt (Rust + Tokio)${NC}"
echo -e " ${DIM}Anti-DPI • Fake TLS • TCP Splice • JA3/JA4${NC}" echo -e " ${DIM}Anti-DPI • Fake TLS • TCP Splice • JA3/JA4${NC}"
fi fi
@@ -306,7 +306,7 @@ apt_update() {
return 0 return 0
} }
# ── Зависимости GoTelegram ────────────────────────────────────────────────── # ── Зависимости PCAtelegram_web ──────────────────────────────────────────────────
# Полный список внешних команд, которые скрипт использует. Для каждой команды # Полный список внешних команд, которые скрипт использует. Для каждой команды
# указан пакет на apt и dnf/yum (имена различаются: например dig = dnsutils на # указан пакет на apt и dnf/yum (имена различаются: например dig = dnsutils на
# Debian, bind-utils на RHEL). # Debian, bind-utils на RHEL).
@@ -429,7 +429,7 @@ ensure_deps() {
if [ ${#still_missing[@]} -gt 0 ]; then if [ ${#still_missing[@]} -gt 0 ]; then
log_error "Critical dependencies still missing: ${still_missing[*]}" log_error "Critical dependencies still missing: ${still_missing[*]}"
log_error "Install manually and re-run gotelegram" log_error "Install manually and re-run pcatelegram_web"
return 1 return 1
fi fi
@@ -483,32 +483,32 @@ detect_3xui_443_listener() {
warn_3xui_443_conflict() { warn_3xui_443_conflict() {
detect_3xui_443_listener || return 1 detect_3xui_443_listener || return 1
log_warning "Обнаружен 3x-ui/Xray, который уже слушает TCP/443." log_warning "Обнаружен 3x-ui/Xray, который уже слушает TCP/443."
log_warning "goTelegram Pro не будет молча останавливать или переписывать 3x-ui." log_warning "PCAtelegram_web не будет молча останавливать или переписывать 3x-ui."
log_dim "Для настоящего shared-443 нужен один фронтовой TLS/SNI-диспетчер и разные SNI-домены для Xray и goTelegram Pro." log_dim "Для настоящего shared-443 нужен один фронтовой TLS/SNI-диспетчер и разные SNI-домены для Xray и PCAtelegram_web."
mkdir -p "$GOTELEGRAM_DIR" 2>/dev/null mkdir -p "$PCATELEGRAM_WEB_DIR" 2>/dev/null
cat > "$GOTELEGRAM_DIR/shared-443-3xui.md" <<'EOF' 2>/dev/null || true cat > "$PCATELEGRAM_WEB_DIR/shared-443-3xui.md" <<'EOF' 2>/dev/null || true
# goTelegram Pro + 3x-ui on one TCP/443 # PCAtelegram_web + 3x-ui on one TCP/443
goTelegram Pro detected that 3x-ui/Xray already owns TCP/443. Two independent PCAtelegram_web detected that 3x-ui/Xray already owns TCP/443. Two independent
processes cannot bind the same IP:port at the same time. A safe shared setup processes cannot bind the same IP:port at the same time. A safe shared setup
needs one front TLS/SNI dispatcher on 443 and internal backends, for example: needs one front TLS/SNI dispatcher on 443 and internal backends, for example:
- dispatcher: 0.0.0.0:443 (nginx stream ssl_preread) - dispatcher: 0.0.0.0:443 (nginx stream ssl_preread)
- goTelegram Pro telemt: 127.0.0.1:7443 - PCAtelegram_web telemt: 127.0.0.1:7443
- 3x-ui/Xray inbound: 127.0.0.1:9443 - 3x-ui/Xray inbound: 127.0.0.1:9443
- goTelegram Pro nginx mask site: 127.0.0.1:8443 - PCAtelegram_web nginx mask site: 127.0.0.1:8443
The dispatcher routes Xray SNI domains to Xray. Everything else goes to telemt; The dispatcher routes Xray SNI domains to Xray. Everything else goes to telemt;
telemt then decides whether the session is MTProxy or regular HTTPS and forwards telemt then decides whether the session is MTProxy or regular HTTPS and forwards
the website to nginx through dns_overrides. the website to nginx through dns_overrides.
goTelegram Pro can generate the dispatcher with: PCAtelegram_web can generate the dispatcher with:
source /opt/gotelegram/lib/shared443.sh source /opt/pcatelegram_web/lib/shared443.sh
shared443_enable <gotelegram-domain> <xray-sni-domain> 127.0.0.1:9443 shared443_enable <pcatelegram_web-domain> <xray-sni-domain> 127.0.0.1:9443
Move the 3x-ui/Xray inbound from 0.0.0.0:443 to 127.0.0.1:9443 in the panel first, Move the 3x-ui/Xray inbound from 0.0.0.0:443 to 127.0.0.1:9443 in the panel first,
or nginx will not be able to own the public 443 socket. goTelegram Pro intentionally or nginx will not be able to own the public 443 socket. PCAtelegram_web intentionally
does not rewrite the 3x-ui SQLite database or generated Xray config without explicit does not rewrite the 3x-ui SQLite database or generated Xray config without explicit
operator confirmation, because 3x-ui can overwrite manual JSON edits on the next operator confirmation, because 3x-ui can overwrite manual JSON edits on the next
panel change. panel change.
@@ -531,14 +531,14 @@ check_disk_space() {
return 0 return 0
} }
# ── Конфигурация GoTelegram (JSON) ────────────────────────────────────────── # ── Конфигурация PCAtelegram_web (JSON) ──────────────────────────────────────────
save_gotelegram_config() { save_pcatelegram_web_config() {
mkdir -p "$(dirname "$GOTELEGRAM_CONFIG")" mkdir -p "$(dirname "$PCATELEGRAM_WEB_CONFIG")"
local cur_lang local cur_lang
cur_lang=$(type get_language &>/dev/null && get_language || echo en) cur_lang=$(type get_language &>/dev/null && get_language || echo en)
cat > "$GOTELEGRAM_CONFIG" << EOJSON cat > "$PCATELEGRAM_WEB_CONFIG" << EOJSON
{ {
"version": "$GOTELEGRAM_VERSION", "version": "$PCATELEGRAM_WEB_VERSION",
"engine": "${1:-telemt}", "engine": "${1:-telemt}",
"mode": "${2:-lite}", "mode": "${2:-lite}",
"port": ${3:-443}, "port": ${3:-443},
@@ -551,12 +551,12 @@ save_gotelegram_config() {
"updated_at": "$(date -Iseconds)" "updated_at": "$(date -Iseconds)"
} }
EOJSON EOJSON
chmod 600 "$GOTELEGRAM_CONFIG" chmod 600 "$PCATELEGRAM_WEB_CONFIG"
} }
load_gotelegram_config() { load_pcatelegram_web_config() {
if [ -f "$GOTELEGRAM_CONFIG" ]; then if [ -f "$PCATELEGRAM_WEB_CONFIG" ]; then
cat "$GOTELEGRAM_CONFIG" cat "$PCATELEGRAM_WEB_CONFIG"
return 0 return 0
fi fi
echo "{}" echo "{}"
@@ -565,11 +565,11 @@ load_gotelegram_config() {
config_get() { config_get() {
local key="$1" local key="$1"
if [ ! -f "$GOTELEGRAM_CONFIG" ]; then if [ ! -f "$PCATELEGRAM_WEB_CONFIG" ]; then
return 2 # file missing return 2 # file missing
fi fi
local val local val
val=$(jq -r ".$key // empty" "$GOTELEGRAM_CONFIG" 2>/dev/null) val=$(jq -r ".$key // empty" "$PCATELEGRAM_WEB_CONFIG" 2>/dev/null)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
return 3 # invalid JSON return 3 # invalid JSON
fi fi
@@ -671,12 +671,12 @@ migrate_v1_to_v2() {
# Backup v1 config # Backup v1 config
if [ -f "$V1_CONFIG_FILE" ]; then if [ -f "$V1_CONFIG_FILE" ]; then
mkdir -p "$GOTELEGRAM_DIR" mkdir -p "$PCATELEGRAM_WEB_DIR"
cp "$V1_CONFIG_FILE" "$GOTELEGRAM_DIR/v1_backup_proxy.json" 2>/dev/null cp "$V1_CONFIG_FILE" "$PCATELEGRAM_WEB_DIR/v1_backup_proxy.json" 2>/dev/null
if type tf &>/dev/null; then if type tf &>/dev/null; then
log_success "$(tf v1_config_saved "$GOTELEGRAM_DIR/v1_backup_proxy.json")" log_success "$(tf v1_config_saved "$PCATELEGRAM_WEB_DIR/v1_backup_proxy.json")"
else else
log_success "v1 config saved to $GOTELEGRAM_DIR/v1_backup_proxy.json" log_success "v1 config saved to $PCATELEGRAM_WEB_DIR/v1_backup_proxy.json"
fi fi
fi fi
@@ -735,6 +735,6 @@ validate_domain() {
# ── Init: создание директорий ──────────────────────────────────────────────── # ── Init: создание директорий ────────────────────────────────────────────────
init_dirs() { init_dirs() {
mkdir -p "$GOTELEGRAM_DIR" "$BACKUP_DIR" /etc/telemt 2>/dev/null mkdir -p "$PCATELEGRAM_WEB_DIR" "$BACKUP_DIR" /etc/telemt 2>/dev/null
touch "$LOG_FILE" 2>/dev/null touch "$LOG_FILE" 2>/dev/null
} }

12
lib/i18n.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — i18n engine # PCAtelegram_web v2.5.0 — i18n engine
# Internationalization support: EN (English) / RU (Русский) # Internationalization support: EN (English) / RU (Русский)
# #
# Usage: # Usage:
@@ -74,14 +74,14 @@ get_language() {
# ── Detect saved language from config.json, default en ── # ── Detect saved language from config.json, default en ──
detect_language() { detect_language() {
local cfg="${GOTELEGRAM_CONFIG:-/opt/gotelegram/config.json}" local cfg="${PCATELEGRAM_WEB_CONFIG:-/opt/pcatelegram_web/config.json}"
local lang="" local lang=""
if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then
lang=$(jq -r '.language // empty' "$cfg" 2>/dev/null) lang=$(jq -r '.language // empty' "$cfg" 2>/dev/null)
fi fi
# Also check marker file (language set before config.json exists) # Also check marker file (language set before config.json exists)
if [ -z "$lang" ]; then if [ -z "$lang" ]; then
local marker="${GOTELEGRAM_DIR:-/opt/gotelegram}/.language" local marker="${PCATELEGRAM_WEB_DIR:-/opt/pcatelegram_web}/.language"
if [ -f "$marker" ]; then if [ -f "$marker" ]; then
lang=$(head -c 2 "$marker" 2>/dev/null | tr -d '[:space:]') lang=$(head -c 2 "$marker" 2>/dev/null | tr -d '[:space:]')
fi fi
@@ -100,11 +100,11 @@ save_language() {
if ! [[ "$lang" =~ ^(en|ru)$ ]]; then if ! [[ "$lang" =~ ^(en|ru)$ ]]; then
return 1 return 1
fi fi
mkdir -p "${GOTELEGRAM_DIR:-/opt/gotelegram}" 2>/dev/null mkdir -p "${PCATELEGRAM_WEB_DIR:-/opt/pcatelegram_web}" 2>/dev/null
# Always write marker for early-access (before config.json exists) # Always write marker for early-access (before config.json exists)
echo "$lang" > "${GOTELEGRAM_DIR:-/opt/gotelegram}/.language" 2>/dev/null echo "$lang" > "${PCATELEGRAM_WEB_DIR:-/opt/pcatelegram_web}/.language" 2>/dev/null
local cfg="${GOTELEGRAM_CONFIG:-/opt/gotelegram/config.json}" local cfg="${PCATELEGRAM_WEB_CONFIG:-/opt/pcatelegram_web/config.json}"
if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then if [ -f "$cfg" ] && command -v jq >/dev/null 2>&1; then
local tmp local tmp
tmp=$(mktemp) || return 1 tmp=$(mktemp) || return 1

18
lib/lang/en.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# goTelegram Pro v2.5.0 — English translations # PCAtelegram_web v2.5.0 — English translations
# shellcheck disable=SC2034,SC2148 # shellcheck disable=SC2034,SC2148
# ── Common words ──────────────────────────────────────────────────────── # ── Common words ────────────────────────────────────────────────────────
@@ -25,7 +25,7 @@ I18N[success]="Done"
I18N[wait]="Please wait..." I18N[wait]="Please wait..."
# ── Banner ────────────────────────────────────────────────────────────── # ── Banner ──────────────────────────────────────────────────────────────
I18N[banner_title]="goTelegram Pro v%s" I18N[banner_title]="PCAtelegram_web v%s"
I18N[banner_subtitle]="MTProxy powered by telemt (Rust + Tokio)" I18N[banner_subtitle]="MTProxy powered by telemt (Rust + Tokio)"
I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4" I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4"
I18N[credits_title]="Credits / Thanks" I18N[credits_title]="Credits / Thanks"
@@ -75,7 +75,7 @@ I18N[submenu_about_title]=" ABOUT"
I18N[about_version_info]="Version info" I18N[about_version_info]="Version info"
I18N[about_promo]="Promo / Donate" I18N[about_promo]="Promo / Donate"
I18N[version_title]="🔍 Information" I18N[version_title]="🔍 Information"
I18N[version_label]="goTelegram Pro:" I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Engine:" I18N[version_engine]="Engine:"
I18N[version_tech]="Technology:" I18N[version_tech]="Technology:"
I18N[version_license]="License:" I18N[version_license]="License:"
@@ -106,7 +106,7 @@ I18N[install_cfg_mode]="Mode:"
I18N[install_cfg_domain]="Domain:" I18N[install_cfg_domain]="Domain:"
I18N[install_confirm_proxy]="Install proxy?" I18N[install_confirm_proxy]="Install proxy?"
I18N[install_confirm_proxy_site]="Install proxy + website?" I18N[install_confirm_proxy_site]="Install proxy + website?"
I18N[install_done]="goTelegram Pro v%s installed! (%s mode)" I18N[install_done]="PCAtelegram_web v%s installed! (%s mode)"
I18N[install_arch_desc1]="telemt accepts all traffic on 443 (HTTPS masquerade)" 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_desc2]="nginx serves the site on internal port %s"
I18N[install_arch_desc3]="ISP only sees HTTPS traffic to %s:443" I18N[install_arch_desc3]="ISP only sees HTTPS traffic to %s:443"
@@ -125,7 +125,7 @@ I18N[logs_telemt_title]="📋 telemt logs (last %s lines):"
# ── Link / Share ──────────────────────────────────────────────────────── # ── Link / Share ────────────────────────────────────────────────────────
I18N[link_title]="🔗 Connection link:" I18N[link_title]="🔗 Connection link:"
I18N[share_title]="📤 Forward this message:" I18N[share_title]="📤 Forward this message:"
I18N[share_line1]="🔐 MTProxy for Telegram (goTelegram Pro v%s)" I18N[share_line1]="🔐 MTProxy for Telegram (PCAtelegram_web v%s)"
I18N[share_server]="🌍 Server: %s" I18N[share_server]="🌍 Server: %s"
I18N[share_port]="🔌 Port: %s" I18N[share_port]="🔌 Port: %s"
I18N[share_connect_cta]="👉 Connect with one tap:" I18N[share_connect_cta]="👉 Connect with one tap:"
@@ -141,7 +141,7 @@ I18N[website_restart_nginx]="Restart nginx"
I18N[website_change_template]="Change template" I18N[website_change_template]="Change template"
# ── Remove ────────────────────────────────────────────────────────────── # ── Remove ──────────────────────────────────────────────────────────────
I18N[remove_title]="🗑 Remove goTelegram Pro" I18N[remove_title]="🗑 Remove PCAtelegram_web"
I18N[remove_proxy_only]="Remove proxy only (telemt)" I18N[remove_proxy_only]="Remove proxy only (telemt)"
I18N[remove_bot_only]="Remove Telegram bot only" I18N[remove_bot_only]="Remove Telegram bot only"
I18N[remove_all]="Remove everything (proxy + bot + settings)" I18N[remove_all]="Remove everything (proxy + bot + settings)"
@@ -151,7 +151,7 @@ I18N[remove_backup_before]="Create a backup before removal?"
I18N[remove_warn_all]="This will remove EVERYTHING: proxy, bot, site, settings." I18N[remove_warn_all]="This will remove EVERYTHING: proxy, bot, site, settings."
I18N[remove_confirm_all]="Are you absolutely sure?" I18N[remove_confirm_all]="Are you absolutely sure?"
I18N[remove_proxy_done]="Proxy removed" I18N[remove_proxy_done]="Proxy removed"
I18N[remove_all_done]="goTelegram Pro fully removed (proxy + bot)" I18N[remove_all_done]="PCAtelegram_web fully removed (proxy + bot)"
# ── Telegram bot submenu ──────────────────────────────────────────────── # ── Telegram bot submenu ────────────────────────────────────────────────
I18N[bot_title]="🤖 Telegram bot" I18N[bot_title]="🤖 Telegram bot"
@@ -332,7 +332,7 @@ I18N[backup_lang_label]="Language"
I18N[backup_date_label]="Date" I18N[backup_date_label]="Date"
I18N[backup_confirm_restore]="Restore configuration? Current settings will be overwritten." I18N[backup_confirm_restore]="Restore configuration? Current settings will be overwritten."
I18N[backup_restored_telemt]="telemt config restored" I18N[backup_restored_telemt]="telemt config restored"
I18N[backup_restored_gotelegram]="goTelegram Pro config restored" I18N[backup_restored_pcatelegram_web]="PCAtelegram_web config restored"
I18N[backup_restored_lang]="Interface language restored" I18N[backup_restored_lang]="Interface language restored"
I18N[backup_restored_nginx]="nginx config restored" I18N[backup_restored_nginx]="nginx config restored"
I18N[backup_restored_ssl]="SSL certificates restored" I18N[backup_restored_ssl]="SSL certificates restored"
@@ -362,7 +362,7 @@ I18N[auto_refresh]="Refresh in 30 sec"
I18N[deps_installing]="Installing dependencies: %s" I18N[deps_installing]="Installing dependencies: %s"
# ── Migration ─────────────────────────────────────────────────────────── # ── Migration ───────────────────────────────────────────────────────────
I18N[v1_detected]="⚠️ goTelegram Pro v1 (mtg) installation detected" I18N[v1_detected]="⚠️ PCAtelegram_web v1 (mtg) installation detected"
I18N[v1_container]="Container: %s" I18N[v1_container]="Container: %s"
I18N[v1_migration_step]="Migrating from v1 (mtg) to v2 (telemt)" I18N[v1_migration_step]="Migrating from v1 (mtg) to v2 (telemt)"
I18N[v1_found_title]="Found v1 (mtg) installation:" I18N[v1_found_title]="Found v1 (mtg) installation:"

18
lib/lang/ru.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# goTelegram Pro v2.5.0 — Russian translations # PCAtelegram_web v2.5.0 — Russian translations
# shellcheck disable=SC2034,SC2148 # shellcheck disable=SC2034,SC2148
# ── Common words ──────────────────────────────────────────────────────── # ── Common words ────────────────────────────────────────────────────────
@@ -25,7 +25,7 @@ I18N[success]="Готово"
I18N[wait]="Подождите..." I18N[wait]="Подождите..."
# ── Banner ────────────────────────────────────────────────────────────── # ── Banner ──────────────────────────────────────────────────────────────
I18N[banner_title]="goTelegram Pro v%s" I18N[banner_title]="PCAtelegram_web v%s"
I18N[banner_subtitle]="MTProxy на ядре telemt (Rust + Tokio)" I18N[banner_subtitle]="MTProxy на ядре telemt (Rust + Tokio)"
I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4" I18N[banner_features]="Anti-DPI • Fake TLS • TCP Splice • JA3/JA4"
I18N[credits_title]="Благодарности / Credits" I18N[credits_title]="Благодарности / Credits"
@@ -75,7 +75,7 @@ I18N[submenu_about_title]=" О ПРОГРАММЕ"
I18N[about_version_info]="Информация о версии" I18N[about_version_info]="Информация о версии"
I18N[about_promo]="Промо / Донат" I18N[about_promo]="Промо / Донат"
I18N[version_title]="🔍 Информация" I18N[version_title]="🔍 Информация"
I18N[version_label]="goTelegram Pro:" I18N[version_label]="PCAtelegram_web:"
I18N[version_engine]="Ядро:" I18N[version_engine]="Ядро:"
I18N[version_tech]="Технология:" I18N[version_tech]="Технология:"
I18N[version_license]="Лицензия:" I18N[version_license]="Лицензия:"
@@ -106,7 +106,7 @@ I18N[install_cfg_mode]="Режим:"
I18N[install_cfg_domain]="Домен:" I18N[install_cfg_domain]="Домен:"
I18N[install_confirm_proxy]="Установить прокси?" I18N[install_confirm_proxy]="Установить прокси?"
I18N[install_confirm_proxy_site]="Установить прокси + сайт?" I18N[install_confirm_proxy_site]="Установить прокси + сайт?"
I18N[install_done]="goTelegram Pro v%s установлен! (%s-режим)" I18N[install_done]="PCAtelegram_web v%s установлен! (%s-режим)"
I18N[install_arch_desc1]="telemt принимает весь трафик на 443 (маскировка под HTTPS)" I18N[install_arch_desc1]="telemt принимает весь трафик на 443 (маскировка под HTTPS)"
I18N[install_arch_desc2]="nginx обслуживает сайт на внутреннем порту %s" I18N[install_arch_desc2]="nginx обслуживает сайт на внутреннем порту %s"
I18N[install_arch_desc3]="Провайдер видит только HTTPS-трафик к %s:443" I18N[install_arch_desc3]="Провайдер видит только HTTPS-трафик к %s:443"
@@ -125,7 +125,7 @@ I18N[logs_telemt_title]="📋 Логи telemt (последние %s строк)
# ── Link / Share ──────────────────────────────────────────────────────── # ── Link / Share ────────────────────────────────────────────────────────
I18N[link_title]="🔗 Ссылка для подключения:" I18N[link_title]="🔗 Ссылка для подключения:"
I18N[share_title]="📤 Перешлите это сообщение:" I18N[share_title]="📤 Перешлите это сообщение:"
I18N[share_line1]="🔐 MTProxy для Telegram (goTelegram Pro v%s)" I18N[share_line1]="🔐 MTProxy для Telegram (PCAtelegram_web v%s)"
I18N[share_server]="🌍 Сервер: %s" I18N[share_server]="🌍 Сервер: %s"
I18N[share_port]="🔌 Порт: %s" I18N[share_port]="🔌 Порт: %s"
I18N[share_connect_cta]="👉 Подключиться одним нажатием:" I18N[share_connect_cta]="👉 Подключиться одним нажатием:"
@@ -141,7 +141,7 @@ I18N[website_restart_nginx]="Перезапустить nginx"
I18N[website_change_template]="Сменить шаблон" I18N[website_change_template]="Сменить шаблон"
# ── Remove ────────────────────────────────────────────────────────────── # ── Remove ──────────────────────────────────────────────────────────────
I18N[remove_title]="🗑 Удаление goTelegram Pro" I18N[remove_title]="🗑 Удаление PCAtelegram_web"
I18N[remove_proxy_only]="Удалить только прокси (telemt)" I18N[remove_proxy_only]="Удалить только прокси (telemt)"
I18N[remove_bot_only]="Удалить только Telegram-бота" I18N[remove_bot_only]="Удалить только Telegram-бота"
I18N[remove_all]="Удалить всё (прокси + бот + настройки)" I18N[remove_all]="Удалить всё (прокси + бот + настройки)"
@@ -151,7 +151,7 @@ I18N[remove_backup_before]="Сделать бекап перед удалени
I18N[remove_warn_all]="Это удалит ВСЁ: прокси, бот, сайт, настройки." I18N[remove_warn_all]="Это удалит ВСЁ: прокси, бот, сайт, настройки."
I18N[remove_confirm_all]="Вы точно уверены?" I18N[remove_confirm_all]="Вы точно уверены?"
I18N[remove_proxy_done]="Прокси удалён" I18N[remove_proxy_done]="Прокси удалён"
I18N[remove_all_done]="goTelegram Pro полностью удалён (прокси + бот)" I18N[remove_all_done]="PCAtelegram_web полностью удалён (прокси + бот)"
# ── Telegram bot submenu ──────────────────────────────────────────────── # ── Telegram bot submenu ────────────────────────────────────────────────
I18N[bot_title]="🤖 Telegram-бот" I18N[bot_title]="🤖 Telegram-бот"
@@ -332,7 +332,7 @@ I18N[backup_lang_label]="Язык"
I18N[backup_date_label]="Дата" I18N[backup_date_label]="Дата"
I18N[backup_confirm_restore]="Восстановить конфигурацию? Текущие настройки будут перезаписаны." I18N[backup_confirm_restore]="Восстановить конфигурацию? Текущие настройки будут перезаписаны."
I18N[backup_restored_telemt]="telemt конфиг восстановлен" I18N[backup_restored_telemt]="telemt конфиг восстановлен"
I18N[backup_restored_gotelegram]="goTelegram Pro конфиг восстановлен" I18N[backup_restored_pcatelegram_web]="PCAtelegram_web конфиг восстановлен"
I18N[backup_restored_lang]="Язык интерфейса восстановлен" I18N[backup_restored_lang]="Язык интерфейса восстановлен"
I18N[backup_restored_nginx]="nginx конфиг восстановлен" I18N[backup_restored_nginx]="nginx конфиг восстановлен"
I18N[backup_restored_ssl]="SSL сертификаты восстановлены" I18N[backup_restored_ssl]="SSL сертификаты восстановлены"
@@ -362,7 +362,7 @@ I18N[auto_refresh]="Обновление через 30 сек"
I18N[deps_installing]="Установка зависимостей: %s" I18N[deps_installing]="Установка зависимостей: %s"
# ── Migration ─────────────────────────────────────────────────────────── # ── Migration ───────────────────────────────────────────────────────────
I18N[v1_detected]="⚠️ Обнаружена установка goTelegram Pro v1 (mtg)" I18N[v1_detected]="⚠️ Обнаружена установка PCAtelegram_web v1 (mtg)"
I18N[v1_container]="Контейнер: %s" I18N[v1_container]="Контейнер: %s"
I18N[v1_migration_step]="Миграция с v1 (mtg) на v2 (telemt)" I18N[v1_migration_step]="Миграция с v1 (mtg) на v2 (telemt)"
I18N[v1_found_title]="Найдена установка v1 (mtg):" I18N[v1_found_title]="Найдена установка v1 (mtg):"

60
lib/stats.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# stats.sh — Traffic statistics module for GoTelegram v2.5.0 # stats.sh — Traffic statistics module for PCAtelegram_web v2.5.0
# Tracks proxy (telemt port 443) and site (nginx port 8443) traffic # Tracks proxy (telemt port 443) and site (nginx port 8443) traffic
# Uses iptables counters + real-time snapshots + historical CSV # Uses iptables counters + real-time snapshots + historical CSV
@@ -10,12 +10,12 @@ YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
STATS_DIR="/run/gotelegram" STATS_DIR="/run/pcatelegram_web"
HISTORY_FILE="/opt/gotelegram/stats_history.csv" HISTORY_FILE="/opt/pcatelegram_web/stats_history.csv"
USER_HISTORY_FILE="/opt/gotelegram/user_stats_history.csv" USER_HISTORY_FILE="/opt/pcatelegram_web/user_stats_history.csv"
SNAPSHOTS_DIR="$STATS_DIR/snapshots" SNAPSHOTS_DIR="$STATS_DIR/snapshots"
CURRENT_SNAPSHOT="$STATS_DIR/stats_current.json" CURRENT_SNAPSHOT="$STATS_DIR/stats_current.json"
CONFIG_FILE="/opt/gotelegram/config.json" CONFIG_FILE="/opt/pcatelegram_web/config.json"
TELEMT_CONFIG_FILE="/etc/telemt/config.toml" TELEMT_CONFIG_FILE="/etc/telemt/config.toml"
STATS_RETENTION_DAYS="${STATS_RETENTION_DAYS:-365}" STATS_RETENTION_DAYS="${STATS_RETENTION_DAYS:-365}"
STATS_MINUTE_RETENTION_DAYS="${STATS_MINUTE_RETENTION_DAYS:-31}" STATS_MINUTE_RETENTION_DAYS="${STATS_MINUTE_RETENTION_DAYS:-31}"
@@ -35,23 +35,23 @@ stats_init() {
chmod 755 "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null chmod 755 "$STATS_DIR" "$SNAPSHOTS_DIR" 2>/dev/null
# Create iptables chain if not exists # Create iptables chain if not exists
if ! iptables -L GOTELEGRAM_STATS -n >/dev/null 2>&1; then if ! iptables -L PCATELEGRAM_WEB_STATS -n >/dev/null 2>&1; then
iptables -N GOTELEGRAM_STATS 2>/dev/null iptables -N PCATELEGRAM_WEB_STATS 2>/dev/null
fi fi
# Add chain to INPUT if not already present # Add chain to INPUT if not already present
if ! iptables -C INPUT -j GOTELEGRAM_STATS 2>/dev/null; then if ! iptables -C INPUT -j PCATELEGRAM_WEB_STATS 2>/dev/null; then
iptables -I INPUT -j GOTELEGRAM_STATS 2>/dev/null iptables -I INPUT -j PCATELEGRAM_WEB_STATS 2>/dev/null
fi fi
# Add rule for proxy traffic (port 443, TCP) # Add rule for proxy traffic (port 443, TCP)
if ! iptables -C GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null; then if ! iptables -C PCATELEGRAM_WEB_STATS -p tcp --dport 443 2>/dev/null; then
iptables -A GOTELEGRAM_STATS -p tcp --dport 443 2>/dev/null iptables -A PCATELEGRAM_WEB_STATS -p tcp --dport 443 2>/dev/null
fi fi
# Add rule for site traffic (loopback, port 8443, TCP) # Add rule for site traffic (loopback, port 8443, TCP)
if ! iptables -C GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null; then if ! iptables -C PCATELEGRAM_WEB_STATS -i lo -p tcp --dport 8443 2>/dev/null; then
iptables -A GOTELEGRAM_STATS -i lo -p tcp --dport 8443 2>/dev/null iptables -A PCATELEGRAM_WEB_STATS -i lo -p tcp --dport 8443 2>/dev/null
fi fi
# Initialize CSV header if file doesn't exist # Initialize CSV header if file doesn't exist
@@ -81,7 +81,7 @@ stats_collect() {
# Parse iptables output: format is "pkts bytes target" # Parse iptables output: format is "pkts bytes target"
# We need to extract bytes (2nd column) for each rule # We need to extract bytes (2nd column) for each rule
local iptables_output=$(iptables -L GOTELEGRAM_STATS -v -n -x 2>/dev/null) local iptables_output=$(iptables -L PCATELEGRAM_WEB_STATS -v -n -x 2>/dev/null)
# Extract counters for port 443 (proxy) # Extract counters for port 443 (proxy)
proxy_bytes=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $2}') proxy_bytes=$(echo "$iptables_output" | grep "dpt:443" | grep -v "lo" | awk '{print $2}')
@@ -140,7 +140,7 @@ EOF
} }
# Print active telemt usernames from [access.users]. Usernames are restricted by # Print active telemt usernames from [access.users]. Usernames are restricted by
# goTelegram to A-Z/a-z/0-9/_.- so they are safe in URLs and CSV fields. # PCAtelegram_web to A-Z/a-z/0-9/_.- so they are safe in URLs and CSV fields.
stats_active_users() { stats_active_users() {
[[ -f "$TELEMT_CONFIG_FILE" ]] || return 0 [[ -f "$TELEMT_CONFIG_FILE" ]] || return 0
awk ' awk '
@@ -478,9 +478,9 @@ toggle_stats() {
fi fi
# Remove iptables rules # Remove iptables rules
iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null iptables -D INPUT -j PCATELEGRAM_WEB_STATS 2>/dev/null
iptables -F GOTELEGRAM_STATS 2>/dev/null iptables -F PCATELEGRAM_WEB_STATS 2>/dev/null
iptables -X GOTELEGRAM_STATS 2>/dev/null iptables -X PCATELEGRAM_WEB_STATS 2>/dev/null
# Clean up directories # Clean up directories
rm -rf "$STATS_DIR" 2>/dev/null rm -rf "$STATS_DIR" 2>/dev/null
@@ -504,7 +504,7 @@ toggle_stats() {
# Install systemd service for stats collection # Install systemd service for stats collection
install_stats_collector() { install_stats_collector() {
local service_file="/etc/systemd/system/gotelegram-stats.service" local service_file="/etc/systemd/system/pcatelegram_web-stats.service"
# Check if running as root # Check if running as root
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
@@ -527,14 +527,14 @@ install_stats_collector() {
# Create systemd service file # Create systemd service file
cat > "$service_file" <<'EOF' cat > "$service_file" <<'EOF'
[Unit] [Unit]
Description=goTelegram Pro Traffic Stats Collector Description=PCAtelegram_web Traffic Stats Collector
After=network.target After=network.target
Wants=network-online.target Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=root User=root
ExecStart=/bin/bash -c 'source /opt/gotelegram/lib/common.sh; source /opt/gotelegram/lib/stats.sh; stats_init; while true; do stats_collect; sleep 1; done' ExecStart=/bin/bash -c 'source /opt/pcatelegram_web/lib/common.sh; source /opt/pcatelegram_web/lib/stats.sh; stats_init; while true; do stats_collect; sleep 1; done'
Restart=always Restart=always
RestartSec=5 RestartSec=5
StandardOutput=journal StandardOutput=journal
@@ -546,8 +546,8 @@ EOF
chmod 644 "$service_file" chmod 644 "$service_file"
systemctl daemon-reload systemctl daemon-reload
systemctl enable gotelegram-stats.service systemctl enable pcatelegram_web-stats.service
systemctl restart gotelegram-stats.service systemctl restart pcatelegram_web-stats.service
if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then
local tmp local tmp
@@ -560,7 +560,7 @@ EOF
fi fi
fi fi
echo "Сервис gotelegram-stats установлен и запущен" >&2 echo "Сервис pcatelegram_web-stats установлен и запущен" >&2
} }
# Remove stats collector service # Remove stats collector service
@@ -570,15 +570,15 @@ remove_stats_collector() {
return 1 return 1
fi fi
systemctl stop gotelegram-stats.service 2>/dev/null systemctl stop pcatelegram_web-stats.service 2>/dev/null
systemctl disable gotelegram-stats.service 2>/dev/null systemctl disable pcatelegram_web-stats.service 2>/dev/null
rm -f /etc/systemd/system/gotelegram-stats.service rm -f /etc/systemd/system/pcatelegram_web-stats.service
systemctl daemon-reload systemctl daemon-reload
# Remove iptables rules # Remove iptables rules
iptables -D INPUT -j GOTELEGRAM_STATS 2>/dev/null iptables -D INPUT -j PCATELEGRAM_WEB_STATS 2>/dev/null
iptables -F GOTELEGRAM_STATS 2>/dev/null iptables -F PCATELEGRAM_WEB_STATS 2>/dev/null
iptables -X GOTELEGRAM_STATS 2>/dev/null iptables -X PCATELEGRAM_WEB_STATS 2>/dev/null
# Clean up directories and files # Clean up directories and files
rm -rf "$STATS_DIR" 2>/dev/null rm -rf "$STATS_DIR" 2>/dev/null

4
lib/telemt.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — Управление telemt binary # PCAtelegram_web v2.5.0 — Управление telemt binary
# Скачивание, обновление, запуск, остановка через systemd # Скачивание, обновление, запуск, остановка через systemd
TELEMT_GITHUB="telemt/telemt" TELEMT_GITHUB="telemt/telemt"
@@ -159,7 +159,7 @@ install_telemt_service() {
cat > "/etc/systemd/system/${TELEMT_SERVICE}.service" << EOF cat > "/etc/systemd/system/${TELEMT_SERVICE}.service" << EOF
[Unit] [Unit]
Description=goTelegram Pro MTProxy (telemt engine) Description=PCAtelegram_web MTProxy (telemt engine)
Documentation=https://github.com/telemt/telemt Documentation=https://github.com/telemt/telemt
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target

4
lib/telemt_config.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — Генерация TOML конфигурации для telemt # PCAtelegram_web v2.5.0 — Генерация TOML конфигурации для telemt
# ── Популярные домены (не заблокированные в РФ) ────────────────────────────── # ── Популярные домены (не заблокированные в РФ) ──────────────────────────────
QUICK_DOMAINS=( QUICK_DOMAINS=(
@@ -44,7 +44,7 @@ generate_telemt_toml() {
fi fi
cat > "$output" << EOTOML cat > "$output" << EOTOML
# GoTelegram v${GOTELEGRAM_VERSION} — telemt v3 configuration # PCAtelegram_web v${PCATELEGRAM_WEB_VERSION} — telemt v3 configuration
# Сгенерировано: $(date -Iseconds) # Сгенерировано: $(date -Iseconds)
# Режим: ${mask_mode} # Режим: ${mask_mode}

4
lib/templates_catalog.sh Normal file → Executable file
View File

@@ -1,10 +1,10 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — website templates catalog # PCAtelegram_web v2.5.0 — website templates catalog
# Pick from ~1800 templates, preview links, git sparse-checkout downloads, # Pick from ~1800 templates, preview links, git sparse-checkout downloads,
# + custom git URL templates (user-supplied public repos) # + custom git URL templates (user-supplied public repos)
CATALOG_FILE="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")/templates_catalog.json" CATALOG_FILE="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")/templates_catalog.json"
TEMPLATES_CACHE="/tmp/gotelegram_templates" TEMPLATES_CACHE="/tmp/pcatelegram_web_templates"
# Custom git template limits # Custom git template limits
CUSTOM_GIT_MAX_SIZE_MB=100 CUSTOM_GIT_MAX_SIZE_MB=100

14
lib/website.sh Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# GoTelegram v2.5.0 — Управление сайтом (nginx + certbot + шаблоны) # PCAtelegram_web v2.5.0 — Управление сайтом (nginx + certbot + шаблоны)
# ── Установка nginx ────────────────────────────────────────────────────────── # ── Установка nginx ──────────────────────────────────────────────────────────
install_nginx() { install_nginx() {
@@ -39,7 +39,7 @@ generate_nginx_config() {
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
cat > "$NGINX_SITE_CONF" << 'EONGINX' cat > "$NGINX_SITE_CONF" << 'EONGINX'
# GoTelegram v2.5.0 — nginx config # PCAtelegram_web v2.5.0 — nginx config
# Pro: nginx на 127.0.0.1:8443 (внутренний), telemt на 0.0.0.0:443 (внешний) # Pro: nginx на 127.0.0.1:8443 (внутренний), telemt на 0.0.0.0:443 (внешний)
# Обычный браузер → :443 → telemt → 127.0.0.1:8443 → nginx (сайт) # Обычный браузер → :443 → telemt → 127.0.0.1:8443 → nginx (сайт)
@@ -88,7 +88,7 @@ server {
add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Корень сайта # Корень сайта
root /var/www/gotelegram-site; root /var/www/pcatelegram_web-site;
index index.html; index index.html;
location / { location / {
@@ -127,7 +127,7 @@ generate_nginx_temp_config() {
local domain="$1" local domain="$1"
cat > "$NGINX_SITE_CONF" << EONGINX_TEMP cat > "$NGINX_SITE_CONF" << EONGINX_TEMP
# GoTelegram — временный конфиг (до получения SSL) # PCAtelegram_web — временный конфиг (до получения SSL)
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
@@ -138,7 +138,7 @@ server {
allow all; allow all;
} }
root /var/www/gotelegram-site; root /var/www/pcatelegram_web-site;
index index.html; index index.html;
location / { location / {
@@ -257,8 +257,8 @@ deploy_template_to_nginx() {
mkdir -p "$WEBSITE_ROOT" mkdir -p "$WEBSITE_ROOT"
cp -a "$template_dir/." "$WEBSITE_ROOT/" cp -a "$template_dir/." "$WEBSITE_ROOT/"
rm -f "$WEBSITE_ROOT/.custom_git_source" 2>/dev/null || true rm -f "$WEBSITE_ROOT/.custom_git_source" 2>/dev/null || true
echo "$template_id" > "$WEBSITE_ROOT/.gotelegram_template_id" 2>/dev/null || true echo "$template_id" > "$WEBSITE_ROOT/.pcatelegram_web_template_id" 2>/dev/null || true
[ -n "$source_url" ] && echo "$source_url" > "$WEBSITE_ROOT/.gotelegram_template_source" 2>/dev/null || true [ -n "$source_url" ] && echo "$source_url" > "$WEBSITE_ROOT/.pcatelegram_web_template_source" 2>/dev/null || true
chown -R www-data:www-data "$WEBSITE_ROOT" 2>/dev/null || chown -R nginx:nginx "$WEBSITE_ROOT" 2>/dev/null 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" chmod -R 755 "$WEBSITE_ROOT"

View File

@@ -1,4 +1,4 @@
# goTelegram Pro v2.5.0 Bot # PCAtelegram_web v2.5.0 Bot
Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux servers. Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux servers.
@@ -34,10 +34,10 @@ Production-quality Telegram bot for managing MTProxy (telemt engine) on Linux se
Recommended installation path is the main CLI menu: Recommended installation path is the main CLI menu:
```bash ```bash
gotelegram pcatelegram_web
``` ```
Then choose `12) Telegram-bot` → install/update. On repeat goTelegram Pro bootstrap/update, an already installed bot is refreshed automatically: code, i18n files and requirements are copied to `/opt/gotelegram-bot`, `.env` is preserved, dependencies are checked and `gotelegram-bot` is restarted. Then choose `12) Telegram-bot` → install/update. On repeat PCAtelegram_web bootstrap/update, an already installed bot is refreshed automatically: code, i18n files and requirements are copied to `/opt/pcatelegram_web-bot`, `.env` is preserved, dependencies are checked and `pcatelegram_web-bot` is restarted.
### Prerequisites ### Prerequisites
@@ -76,16 +76,16 @@ For systemd service:
```bash ```bash
[Unit] [Unit]
Description=goTelegram Pro Bot Description=PCAtelegram_web Bot
After=network.target After=network.target
[Service] [Service]
Type=simple Type=simple
WorkingDirectory=/opt/gotelegram-bot WorkingDirectory=/opt/pcatelegram_web-bot
ExecStart=/opt/gotelegram-bot/venv/bin/python /opt/gotelegram-bot/bot.py ExecStart=/opt/pcatelegram_web-bot/venv/bin/python /opt/pcatelegram_web-bot/bot.py
Restart=always Restart=always
RestartSec=5 RestartSec=5
Environment=PATH=/opt/gotelegram-bot/venv/bin:/usr/bin Environment=PATH=/opt/pcatelegram_web-bot/venv/bin:/usr/bin
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -97,17 +97,17 @@ WantedBy=multi-user.target
- `BOT_TOKEN` - Telegram bot token (required) - `BOT_TOKEN` - Telegram bot token (required)
- `ALLOWED_IDS` - Comma-separated user IDs (optional, all users allowed if empty) - `ALLOWED_IDS` - Comma-separated user IDs (optional, all users allowed if empty)
- `BOT_LANG` - Default language inherited from goTelegram Pro install language - `BOT_LANG` - Default language inherited from PCAtelegram_web install language
### System Paths ### System Paths
- `GOTELEGRAM_CONFIG` - `/opt/gotelegram/config.json` - `PCATELEGRAM_WEB_CONFIG` - `/opt/pcatelegram_web/config.json`
- `TELEMT_CONFIG` - `/etc/telemt/config.toml` - `TELEMT_CONFIG` - `/etc/telemt/config.toml`
- `TELEMT_SERVICE` - `telemt` (systemd service name) - `TELEMT_SERVICE` - `telemt` (systemd service name)
- `WEBSITE_ROOT` - `/var/www/gotelegram-site` - `WEBSITE_ROOT` - `/var/www/pcatelegram_web-site`
- `BACKUP_DIR` - `/opt/gotelegram/backups` - `BACKUP_DIR` - `/opt/pcatelegram_web/backups`
- `BACKUP_SCHEDULE_FILE` - `/opt/gotelegram/backup_schedule.json` - `BACKUP_SCHEDULE_FILE` - `/opt/pcatelegram_web/backup_schedule.json`
- `TEMPLATES_CATALOG` - `/opt/gotelegram/templates_catalog.json` - `TEMPLATES_CATALOG` - `/opt/pcatelegram_web/templates_catalog.json`
## Architecture ## Architecture
@@ -158,4 +158,4 @@ code, stdout, stderr = await sh("command", "arg1", "arg2")
## License ## License
goTelegram Pro v2.5.0 - Open source community project PCAtelegram_web v2.5.0 - Open source community project

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
goTelegram Pro v2.5.0 Bot - MTProxy Management for Linux PCAtelegram_web v2.5.0 Bot - MTProxy Management for Linux
Manages telemt engine via Telegram interface with full CLI feature parity Manages telemt engine via Telegram interface with full CLI feature parity
Uses python-telegram-bot v21+ Uses python-telegram-bot v21+
Supports EN/RU UI with per-user language preferences. Supports EN/RU UI with per-user language preferences.
@@ -103,28 +103,28 @@ logger = logging.getLogger(__name__)
# CONFIGURATION # CONFIGURATION
# ============================================================================ # ============================================================================
GOTELEGRAM_VERSION = "2.5.0" PCATELEGRAM_WEB_VERSION = "2.5.0"
GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json" PCATELEGRAM_WEB_CONFIG = "/opt/pcatelegram_web/config.json"
DISABLED_USERS_FILE = "/opt/gotelegram/disabled_users.json" DISABLED_USERS_FILE = "/opt/pcatelegram_web/disabled_users.json"
USER_STATS_HISTORY = "/opt/gotelegram/user_stats_history.csv" USER_STATS_HISTORY = "/opt/pcatelegram_web/user_stats_history.csv"
USER_LOCK_FILE = "/run/gotelegram/admin-users.lock" USER_LOCK_FILE = "/run/pcatelegram_web/admin-users.lock"
TELEMT_CONFIG = "/etc/telemt/config.toml" TELEMT_CONFIG = "/etc/telemt/config.toml"
TELEMT_SERVICE = "telemt" TELEMT_SERVICE = "telemt"
WEBSITE_ROOT = "/var/www/gotelegram-site" WEBSITE_ROOT = "/var/www/pcatelegram_web-site"
BACKUP_DIR = "/opt/gotelegram/backups" BACKUP_DIR = "/opt/pcatelegram_web/backups"
BACKUP_SCHEDULE_FILE = "/opt/gotelegram/backup_schedule.json" BACKUP_SCHEDULE_FILE = "/opt/pcatelegram_web/backup_schedule.json"
TEMPLATES_CATALOG = "/opt/gotelegram/templates_catalog.json" TEMPLATES_CATALOG = "/opt/pcatelegram_web/templates_catalog.json"
INSTALL_SH = "/opt/gotelegram/install.sh" INSTALL_SH = "/opt/pcatelegram_web/install.sh"
PROMO_LINK_1 = "https://vk.cc/ct29NQ" PROMO_LINK_1 = "https://vk.cc/ct29NQ"
PROMO_LINK_2 = "https://vk.cc/cUxAhj" PROMO_LINK_2 = "https://vk.cc/cUxAhj"
TIP_LINK = "https://pay.cloudtips.ru/p/7410814f" TIP_LINK = "https://pay.cloudtips.ru/p/7410814f"
YOUTUBE_LINK = os.getenv("GOTELEGRAM_YOUTUBE_LINK", "").strip() YOUTUBE_LINK = os.getenv("PCATELEGRAM_WEB_YOUTUBE_LINK", "").strip()
PROMO_STAMP_FILE = "/opt/gotelegram/.promo_bot_last_shown" PROMO_STAMP_FILE = "/opt/pcatelegram_web/.promo_bot_last_shown"
BOT_TOKEN = os.getenv("BOT_TOKEN") BOT_TOKEN = os.getenv("BOT_TOKEN")
ENV_FILE = "/opt/gotelegram-bot/.env" ENV_FILE = "/opt/pcatelegram_web-bot/.env"
ADMIN_WEB_SERVICE = "gotelegram-admin" ADMIN_WEB_SERVICE = "pcatelegram_web-admin"
ADMIN_WEB_PORT = 1984 ADMIN_WEB_PORT = 1984
@@ -263,7 +263,7 @@ async def sh(*args, timeout: int = 60) -> Tuple[int, str, str]:
# Per-host mutex preventing concurrent install.sh --action invocations. Two # Per-host mutex preventing concurrent install.sh --action invocations. Two
# admins hitting "change template" at the same second could race each other # admins hitting "change template" at the same second could race each other
# and corrupt /var/www/gotelegram-site. One global lock is fine — these are # and corrupt /var/www/pcatelegram_web-site. One global lock is fine — these are
# rare operations and should serialize cleanly. # rare operations and should serialize cleanly.
_BOT_ACTION_LOCK = asyncio.Lock() _BOT_ACTION_LOCK = asyncio.Lock()
@@ -279,7 +279,7 @@ _DOMAIN_RE = re.compile(
) )
_USER_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$") _USER_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$")
MAX_UNIQUE_IP_LIMIT = 1000000 MAX_UNIQUE_IP_LIMIT = 1000000
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8")) TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("PCATELEGRAM_WEB_TELEMT_RESTART_DEBOUNCE", "8"))
_LAST_TELEMT_RESTART = 0.0 _LAST_TELEMT_RESTART = 0.0
@@ -391,7 +391,7 @@ def template_display_name(template_id: str) -> str:
if template_id in ("deployed_site", "existing_site"): if template_id in ("deployed_site", "existing_site"):
return "Existing deployed site" return "Existing deployed site"
if template_id.startswith("custom_"): if template_id.startswith("custom_"):
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
source = config.get("template_source", "") source = config.get("template_source", "")
return f"{template_id} ({source})" if source else template_id return f"{template_id} ({source})" if source else template_id
catalog = load_json(TEMPLATES_CATALOG) or {} catalog = load_json(TEMPLATES_CATALOG) or {}
@@ -652,7 +652,7 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
return return
welcome = ( welcome = (
f"<b>{_tf(user_id, 'welcome_title', GOTELEGRAM_VERSION)}</b>\n\n" f"<b>{_tf(user_id, 'welcome_title', PCATELEGRAM_WEB_VERSION)}</b>\n\n"
f"{_t(user_id, 'welcome_subtitle')}\n" f"{_t(user_id, 'welcome_subtitle')}\n"
f"{_t(user_id, 'welcome_powered')}\n\n" f"{_t(user_id, 'welcome_powered')}\n\n"
f"{_t(user_id, 'welcome_prompt')}" f"{_t(user_id, 'welcome_prompt')}"
@@ -750,10 +750,10 @@ async def get_status_text(user_id: Optional[int] = None) -> str:
lines.append(f"<b>{_t(user_id, 'status_telemt')}:</b> v{version}") lines.append(f"<b>{_t(user_id, 'status_telemt')}:</b> v{version}")
# Config status # Config status
config = load_json(GOTELEGRAM_CONFIG) config = load_json(PCATELEGRAM_WEB_CONFIG)
if config: if config:
lines.append(f"<b>{_t(user_id, 'status_mode')}:</b> {html.escape(str(config.get('mode', 'unknown')))}") lines.append(f"<b>{_t(user_id, 'status_mode')}:</b> {html.escape(str(config.get('mode', 'unknown')))}")
# install.sh/save_gotelegram_config uses "template_id" (not "template") # install.sh/save_pcatelegram_web_config uses "template_id" (not "template")
tpl = config.get("template_id") or config.get("template") tpl = config.get("template_id") or config.get("template")
if tpl: if tpl:
lines.append(f"<b>{_t(user_id, 'status_template')}:</b> {html.escape(template_display_name(str(tpl)))}") lines.append(f"<b>{_t(user_id, 'status_template')}:</b> {html.escape(template_display_name(str(tpl)))}")
@@ -795,15 +795,15 @@ async def get_traffic_stats() -> str:
await sh( await sh(
"bash", "bash",
"-lc", "-lc",
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/stats.sh; " "source /opt/pcatelegram_web/lib/stats.sh; "
"stats_init >/dev/null 2>&1 || true; stats_collect >/dev/null 2>&1 || true", "stats_init >/dev/null 2>&1 || true; stats_collect >/dev/null 2>&1 || true",
timeout=15, timeout=15,
) )
# Read current snapshot # Read current snapshot
current_file = "/run/gotelegram/stats_current.json" current_file = "/run/pcatelegram_web/stats_current.json"
history_file = "/opt/gotelegram/stats_history.csv" history_file = "/opt/pcatelegram_web/stats_history.csv"
try: try:
with open(current_file, "r") as f: with open(current_file, "r") as f:
@@ -1032,7 +1032,7 @@ async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
await query.answer() await query.answer()
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
current_mode = config.get("mode", "") current_mode = config.get("mode", "")
if current_mode != "lite": if current_mode != "lite":
@@ -1040,7 +1040,7 @@ async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
"<b>⚠️ Установка Lite из бота пока не поддерживается</b>\n\n" "<b>⚠️ Установка Lite из бота пока не поддерживается</b>\n\n"
f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n" f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n"
"Чтобы установить Lite, запустите на сервере:\n" "Чтобы установить Lite, запустите на сервере:\n"
"<code>gotelegram</code> → <b>1) Прокси → 1) Установить/Обновить → Lite</b>\n\n" "<code>pcatelegram_web</code> → <b>1) Прокси → 1) Установить/Обновить → Lite</b>\n\n"
"Существующая конфигурация <b>не была изменена</b>." "Существующая конфигурация <b>не была изменена</b>."
) )
keyboard = InlineKeyboardMarkup( keyboard = InlineKeyboardMarkup(
@@ -1185,7 +1185,7 @@ async def _download_custom_git_template(url_with_branch: str) -> Tuple[bool, str
url = base url = base
tpl_id = "custom_" + hashlib.md5(url_with_branch.encode("utf-8")).hexdigest()[:10] tpl_id = "custom_" + hashlib.md5(url_with_branch.encode("utf-8")).hexdigest()[:10]
target_dir = f"/opt/gotelegram/custom_templates/{tpl_id}" target_dir = f"/opt/pcatelegram_web/custom_templates/{tpl_id}"
# Clean previous copy # Clean previous copy
if os.path.isdir(target_dir): if os.path.isdir(target_dir):
@@ -1403,7 +1403,7 @@ async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
return return
# Read current config to decide: in-place change-template vs fresh install # Read current config to decide: in-place change-template vs fresh install
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
current_mode = config.get("mode", "") current_mode = config.get("mode", "")
if current_mode != "pro": if current_mode != "pro":
@@ -1413,7 +1413,7 @@ async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n" f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
"Pro-режим требует ввода домена, email и проверки DNS. " "Pro-режим требует ввода домена, email и проверки DNS. "
"Чтобы установить Pro, запустите на сервере:\n" "Чтобы установить Pro, запустите на сервере:\n"
"<code>gotelegram</code> → <b>1) Прокси → 1) Установить/Обновить → Pro</b>\n\n" "<code>pcatelegram_web</code> → <b>1) Прокси → 1) Установить/Обновить → Pro</b>\n\n"
"Существующая конфигурация <b>не была изменена</b>." "Существующая конфигурация <b>не была изменена</b>."
) )
keyboard = InlineKeyboardMarkup( keyboard = InlineKeyboardMarkup(
@@ -1466,7 +1466,7 @@ async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
+ (f" (<code>{html.escape(err_code)}</code>)" if err_code else "") + (f" (<code>{html.escape(err_code)}</code>)" if err_code else "")
+ "\n\n" + "\n\n"
"Существующая конфигурация <b>не была изменена</b>. " "Существующая конфигурация <b>не была изменена</b>. "
"Попробуйте другой шаблон или запустите <code>gotelegram</code> из консоли." "Попробуйте другой шаблон или запустите <code>pcatelegram_web</code> из консоли."
) )
keyboard = InlineKeyboardMarkup( keyboard = InlineKeyboardMarkup(
@@ -1766,7 +1766,7 @@ def user_traffic_history_summary(name: str) -> str:
async def get_proxy_link_for_secret(secret: str) -> Optional[str]: async def get_proxy_link_for_secret(secret: str) -> Optional[str]:
"""Generate a fake-TLS proxy link for an arbitrary telemt user secret.""" """Generate a fake-TLS proxy link for an arbitrary telemt user secret."""
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
if not secret: if not secret:
return None return None
@@ -1789,7 +1789,7 @@ async def get_proxy_link_for_secret(secret: str) -> Optional[str]:
async def get_proxy_link() -> Optional[str]: async def get_proxy_link() -> Optional[str]:
"""Generate proxy link from config. Pro-mode uses domain + fake-TLS secret.""" """Generate proxy link from config. Pro-mode uses domain + fake-TLS secret."""
config = load_json(GOTELEGRAM_CONFIG) config = load_json(PCATELEGRAM_WEB_CONFIG)
if not config: if not config:
return None return None
@@ -1959,7 +1959,7 @@ async def _user_detail_text(name: str, secret: str, enabled: bool = True, max_un
compact = json.dumps(data, ensure_ascii=False)[:600] compact = json.dumps(data, ensure_ascii=False)[:600]
details = f"\n<pre>{html.escape(compact)}</pre>" details = f"\n<pre>{html.escape(compact)}</pre>"
elif enabled: elif enabled:
details = "\n<i>Runtime API недоступен. Новые установки goTelegram Pro включают его автоматически.</i>" details = "\n<i>Runtime API недоступен. Новые установки PCAtelegram_web включают его автоматически.</i>"
else: else:
details = "\n<i>Ключ отключён и сейчас не принимается telemt.</i>" details = "\n<i>Ключ отключён и сейчас не принимается telemt.</i>"
details += user_traffic_history_summary(name) details += user_traffic_history_summary(name)
@@ -2033,7 +2033,7 @@ async def cb_user_qr(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None
await query.answer("Ссылка недоступна", show_alert=True) await query.answer("Ссылка недоступна", show_alert=True)
return return
qr_file = f"/tmp/gotelegram_user_qr_{hashlib.sha256(name.encode()).hexdigest()[:10]}.png" qr_file = f"/tmp/pcatelegram_web_user_qr_{hashlib.sha256(name.encode()).hexdigest()[:10]}.png"
code, _, _ = await sh("which", "qrencode") code, _, _ = await sh("which", "qrencode")
if code == 0: if code == 0:
code, _, _ = await sh("qrencode", "-o", qr_file, link) code, _, _ = await sh("qrencode", "-o", qr_file, link)
@@ -2340,11 +2340,11 @@ def backup_schedule_state() -> Dict[str, Any]:
async def run_full_backup() -> Tuple[bool, str]: async def run_full_backup() -> Tuple[bool, str]:
script = ( script = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/telemt.sh; " "source /opt/pcatelegram_web/lib/telemt.sh; "
"source /opt/gotelegram/lib/website.sh; " "source /opt/pcatelegram_web/lib/website.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
"create_backup \"\"; " "create_backup \"\"; "
"cleanup_old_backups 30" "cleanup_old_backups 30"
@@ -2358,9 +2358,9 @@ async def set_full_backup_schedule(frequency: str) -> Tuple[bool, str]:
if frequency not in {"off", "daily", "weekly", "monthly"}: if frequency not in {"off", "daily", "weekly", "monthly"}:
return False, "unsupported schedule" return False, "unsupported schedule"
script = ( script = (
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
f"set_backup_schedule {shlex.quote(frequency)}" f"set_backup_schedule {shlex.quote(frequency)}"
) )
@@ -2373,11 +2373,11 @@ async def launch_full_restore(backup_path: str) -> None:
quoted_path = shlex.quote(backup_path) quoted_path = shlex.quote(backup_path)
script = ( script = (
"sleep 1; " "sleep 1; "
"source /opt/gotelegram/lib/common.sh; " "source /opt/pcatelegram_web/lib/common.sh; "
"source /opt/gotelegram/lib/i18n.sh; " "source /opt/pcatelegram_web/lib/i18n.sh; "
"source /opt/gotelegram/lib/telemt.sh; " "source /opt/pcatelegram_web/lib/telemt.sh; "
"source /opt/gotelegram/lib/website.sh; " "source /opt/pcatelegram_web/lib/website.sh; "
"source /opt/gotelegram/lib/backup.sh; " "source /opt/pcatelegram_web/lib/backup.sh; "
"load_language \"$(detect_language 2>/dev/null || echo en)\"; " "load_language \"$(detect_language 2>/dev/null || echo en)\"; "
"create_backup \"\" >/dev/null 2>&1 || true; " "create_backup \"\" >/dev/null 2>&1 || true; "
f"restore_backup {quoted_path} \"\" yes; " f"restore_backup {quoted_path} \"\" yes; "
@@ -2431,14 +2431,14 @@ async def cb_menu_backup(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
"<b>💾 Бекапы</b>\n\n" "<b>💾 Бекапы</b>\n\n"
f"Файлов: <code>{len(backups)}</code>\n" f"Файлов: <code>{len(backups)}</code>\n"
f"Расписание: <b>{labels.get(schedule['frequency'], schedule['frequency'])}</b>\n\n" f"Расписание: <b>{labels.get(schedule['frequency'], schedule['frequency'])}</b>\n\n"
"В бекап входит: telemt config, настройки goTelegram, ключи, отключённые ключи, сайт, шаблоны, SSL, бот, админка и история трафика." "В бекап входит: telemt config, настройки PCAtelegram_web, ключи, отключённые ключи, сайт, шаблоны, SSL, бот, админка и история трафика."
) )
else: else:
text = ( text = (
"<b>💾 Backups</b>\n\n" "<b>💾 Backups</b>\n\n"
f"Files: <code>{len(backups)}</code>\n" f"Files: <code>{len(backups)}</code>\n"
f"Schedule: <b>{labels.get(schedule['frequency'], schedule['frequency'])}</b>\n\n" f"Schedule: <b>{labels.get(schedule['frequency'], schedule['frequency'])}</b>\n\n"
"Backups include telemt config, goTelegram settings, keys, disabled keys, site, templates, SSL, bot, admin panel and traffic history." "Backups include telemt config, PCAtelegram_web settings, keys, disabled keys, site, templates, SSL, bot, admin panel and traffic history."
) )
keyboard = InlineKeyboardMarkup(buttons) keyboard = InlineKeyboardMarkup(buttons)
await safe_edit_message(query,text, reply_markup=keyboard, parse_mode="HTML") await safe_edit_message(query,text, reply_markup=keyboard, parse_mode="HTML")
@@ -2583,7 +2583,7 @@ async def cb_restore_backup(update: Update, context: ContextTypes.DEFAULT_TYPE)
if not safe_path: if not safe_path:
text = "❌ Файл бекапа не найден" if get_user_lang(user_id) == "ru" else "❌ Backup file not found" text = "❌ Файл бекапа не найден" if get_user_lang(user_id) == "ru" else "❌ Backup file not found"
elif safe_path.endswith(".enc"): elif safe_path.endswith(".enc"):
text = "❌ Зашифрованный бекап пока восстанавливается через CLI: gotelegram → Восстановить." if get_user_lang(user_id) == "ru" else "❌ Encrypted backups are restored from CLI for now: gotelegram → Restore." text = "❌ Зашифрованный бекап пока восстанавливается через CLI: pcatelegram_web → Восстановить." if get_user_lang(user_id) == "ru" else "❌ Encrypted backups are restored from CLI for now: pcatelegram_web → Restore."
else: else:
await launch_full_restore(safe_path) await launch_full_restore(safe_path)
text = ( text = (
@@ -2759,7 +2759,7 @@ async def cb_ssl_status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N
async def admin_web_host_hint() -> str: async def admin_web_host_hint() -> str:
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
domain = str(config.get("domain") or "") domain = str(config.get("domain") or "")
if domain: if domain:
return domain return domain
@@ -2998,7 +2998,7 @@ async def cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
text = ( text = (
f"<b> Credits & Acknowledgements</b>\n\n" f"<b> Credits & Acknowledgements</b>\n\n"
f"<b>goTelegram Pro v{GOTELEGRAM_VERSION}</b>\n\n" f"<b>PCAtelegram_web v{PCATELEGRAM_WEB_VERSION}</b>\n\n"
f"Built with love for the Telegram community\n\n" f"Built with love for the Telegram community\n\n"
f"<b>Special thanks to:</b>\n\n" f"<b>Special thanks to:</b>\n\n"
f"🙏 <b>telemt</b> - MTProxy engine\n" f"🙏 <b>telemt</b> - MTProxy engine\n"
@@ -3010,7 +3010,7 @@ async def cb_menu_credits(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
f"🚀 <b>Start Bootstrap</b> - Bootstrap templates\n" f"🚀 <b>Start Bootstrap</b> - Bootstrap templates\n"
f" Professional design framework\n\n" f" Professional design framework\n\n"
f"💬 <b>Community</b> - Your feedback & support\n\n" f"💬 <b>Community</b> - Your feedback & support\n\n"
f"<i>goTelegram Pro is open-source and community-driven</i>" f"<i>PCAtelegram_web is open-source and community-driven</i>"
) )
keyboard = InlineKeyboardMarkup( keyboard = InlineKeyboardMarkup(
@@ -3030,7 +3030,7 @@ async def cb_menu_remove(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
await query.answer() await query.answer()
text = ( text = (
"<b>⚠️ Remove goTelegram Pro</b>\n\n" "<b>⚠️ Remove PCAtelegram_web</b>\n\n"
"This will completely remove the installation.\n" "This will completely remove the installation.\n"
"Are you sure?" "Are you sure?"
) )
@@ -3048,16 +3048,16 @@ async def cb_remove_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE)
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
await safe_edit_message(query,"⏳ Removing goTelegram Pro...") await safe_edit_message(query,"⏳ Removing PCAtelegram_web...")
# Stop service # Stop service
await sh("systemctl", "stop", TELEMT_SERVICE) await sh("systemctl", "stop", TELEMT_SERVICE)
# Remove directories # Remove directories
for path in ["/opt/gotelegram", WEBSITE_ROOT]: for path in ["/opt/pcatelegram_web", WEBSITE_ROOT]:
await sh("rm", "-rf", path) await sh("rm", "-rf", path)
text = "goTelegram Pro removed successfully" text = "PCAtelegram_web removed successfully"
keyboard = InlineKeyboardMarkup( keyboard = InlineKeyboardMarkup(
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]] [[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
) )
@@ -3120,7 +3120,7 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
await query.answer() await query.answer()
buttons = get_main_menu(user_id) buttons = get_main_menu(user_id)
text = ( text = (
f"<b>{_tf(user_id, 'welcome_title', GOTELEGRAM_VERSION)}</b>\n\n" f"<b>{_tf(user_id, 'welcome_title', PCATELEGRAM_WEB_VERSION)}</b>\n\n"
f"{_t(user_id, 'welcome_subtitle')}\n" f"{_t(user_id, 'welcome_subtitle')}\n"
f"{_t(user_id, 'welcome_prompt')}" f"{_t(user_id, 'welcome_prompt')}"
) )
@@ -3158,7 +3158,7 @@ async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
# Re-render main menu in the new language # Re-render main menu in the new language
buttons = get_main_menu(user_id) buttons = get_main_menu(user_id)
text = ( text = (
f"<b>{_tf(user_id, 'welcome_title', GOTELEGRAM_VERSION)}</b>\n\n" f"<b>{_tf(user_id, 'welcome_title', PCATELEGRAM_WEB_VERSION)}</b>\n\n"
f"{_t(user_id, 'welcome_subtitle')}\n" f"{_t(user_id, 'welcome_subtitle')}\n"
f"{_t(user_id, 'welcome_prompt')}" f"{_t(user_id, 'welcome_prompt')}"
) )
@@ -3282,12 +3282,12 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE
if not ok: if not ok:
await update.message.reply_text(_t(user_id, info), parse_mode="HTML") await update.message.reply_text(_t(user_id, info), parse_mode="HTML")
return return
# Success — record in goTelegram Pro config. Use "template_id" (canonical # Success — record in PCAtelegram_web config. Use "template_id" (canonical
# field name written by install.sh/save_gotelegram_config). # field name written by install.sh/save_pcatelegram_web_config).
config = load_json(GOTELEGRAM_CONFIG) or {} config = load_json(PCATELEGRAM_WEB_CONFIG) or {}
config["template_id"] = tpl_id config["template_id"] = tpl_id
config["template_source"] = url config["template_source"] = url
save_json(GOTELEGRAM_CONFIG, config) save_json(PCATELEGRAM_WEB_CONFIG, config)
if config.get("mode") == "pro" and os.path.isdir(info): if config.get("mode") == "pro" and os.path.isdir(info):
try: try:
os.makedirs(WEBSITE_ROOT, exist_ok=True) os.makedirs(WEBSITE_ROOT, exist_ok=True)
@@ -3353,7 +3353,7 @@ def main() -> None:
application.add_error_handler(error_handler) application.add_error_handler(error_handler)
# Run the bot # Run the bot
logger.info(f"goTelegram Pro v{GOTELEGRAM_VERSION} bot starting...") logger.info(f"PCAtelegram_web v{PCATELEGRAM_WEB_VERSION} bot starting...")
application.run_polling(allowed_updates=Update.ALL_TYPES) application.run_polling(allowed_updates=Update.ALL_TYPES)

View File

@@ -1,4 +1,4 @@
# goTelegram Pro v2.5.0 Bot Configuration # PCAtelegram_web v2.5.0 Bot Configuration
# Copy this to .env and fill in your values # Copy this to .env and fill in your values
# Telegram Bot Token from @BotFather # Telegram Bot Token from @BotFather

View File

@@ -1,5 +1,5 @@
""" """
goTelegram Pro v2.5.0 Bot i18n module PCAtelegram_web v2.5.0 Bot i18n module
Provides per-user language preferences and a simple t()/tf() API. Provides per-user language preferences and a simple t()/tf() API.
Usage: Usage:
@@ -23,9 +23,9 @@ logger = logging.getLogger(__name__)
# ── Paths ───────────────────────────────────────────────────────────────── # ── Paths ─────────────────────────────────────────────────────────────────
_MODULE_DIR = Path(__file__).resolve().parent _MODULE_DIR = Path(__file__).resolve().parent
LANG_DIR = _MODULE_DIR / "lang" LANG_DIR = _MODULE_DIR / "lang"
USER_LANG_FILE = Path("/opt/gotelegram-bot/user_langs.json") USER_LANG_FILE = Path("/opt/pcatelegram_web-bot/user_langs.json")
GOTELEGRAM_CONFIG = Path("/opt/gotelegram/config.json") PCATELEGRAM_WEB_CONFIG = Path("/opt/pcatelegram_web/config.json")
GOTELEGRAM_LANG_MARKER = Path("/opt/gotelegram/.language") PCATELEGRAM_WEB_LANG_MARKER = Path("/opt/pcatelegram_web/.language")
# Supported codes; keep in sync with lang/*.json # Supported codes; keep in sync with lang/*.json
SUPPORTED_LANGS = ("en", "ru") SUPPORTED_LANGS = ("en", "ru")
@@ -34,18 +34,18 @@ SUPPORTED_LANGS = ("en", "ru")
def _detect_default_lang() -> str: def _detect_default_lang() -> str:
candidates = [] candidates = []
try: try:
if GOTELEGRAM_CONFIG.exists(): if PCATELEGRAM_WEB_CONFIG.exists():
with open(GOTELEGRAM_CONFIG, "r", encoding="utf-8") as f: with open(PCATELEGRAM_WEB_CONFIG, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
if isinstance(data, dict): if isinstance(data, dict):
candidates.extend([data.get("language"), data.get("lang")]) candidates.extend([data.get("language"), data.get("lang")])
except Exception as e: except Exception as e:
logger.warning("failed to read goTelegram Pro language config: %s", e) logger.warning("failed to read PCAtelegram_web language config: %s", e)
try: try:
if GOTELEGRAM_LANG_MARKER.exists(): if PCATELEGRAM_WEB_LANG_MARKER.exists():
candidates.append(GOTELEGRAM_LANG_MARKER.read_text(encoding="utf-8").strip()[:2]) candidates.append(PCATELEGRAM_WEB_LANG_MARKER.read_text(encoding="utf-8").strip()[:2])
except Exception as e: except Exception as e:
logger.warning("failed to read goTelegram Pro language marker: %s", e) logger.warning("failed to read PCAtelegram_web language marker: %s", e)
candidates.append(os.getenv("BOT_LANG", "")) candidates.append(os.getenv("BOT_LANG", ""))
for raw in candidates: for raw in candidates:
code = str(raw or "").strip().lower() code = str(raw or "").strip().lower()

View File

@@ -6,7 +6,7 @@
"lang_saved": "Language saved: %s", "lang_saved": "Language saved: %s",
"lang_choose": "Choose your language:", "lang_choose": "Choose your language:",
"welcome_title": "goTelegram Pro v%s", "welcome_title": "PCAtelegram_web v%s",
"welcome_subtitle": "🤖 MTProxy Management Bot", "welcome_subtitle": "🤖 MTProxy Management Bot",
"welcome_powered": "Powered by telemt engine", "welcome_powered": "Powered by telemt engine",
"welcome_prompt": "Select an action from the menu below:", "welcome_prompt": "Select an action from the menu below:",
@@ -17,7 +17,7 @@
"btn_no": "❌ No", "btn_no": "❌ No",
"access_denied": "⛔ Access denied.\nYour ID: <code>%s</code>", "access_denied": "⛔ Access denied.\nYour ID: <code>%s</code>",
"help_title": "goTelegram Pro Bot — Commands", "help_title": "PCAtelegram_web 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.", "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_install": "⚙️ Install",

View File

@@ -6,7 +6,7 @@
"lang_saved": "Язык сохранён: %s", "lang_saved": "Язык сохранён: %s",
"lang_choose": "Выберите язык:", "lang_choose": "Выберите язык:",
"welcome_title": "goTelegram Pro v%s", "welcome_title": "PCAtelegram_web v%s",
"welcome_subtitle": "🤖 Бот управления MTProxy", "welcome_subtitle": "🤖 Бот управления MTProxy",
"welcome_powered": "На базе движка telemt", "welcome_powered": "На базе движка telemt",
"welcome_prompt": "Выберите действие в меню ниже:", "welcome_prompt": "Выберите действие в меню ниже:",
@@ -17,7 +17,7 @@
"btn_no": "❌ Нет", "btn_no": "❌ Нет",
"access_denied": "⛔ Доступ запрещён.\nВаш ID: <code>%s</code>", "access_denied": "⛔ Доступ запрещён.\nВаш ID: <code>%s</code>",
"help_title": "goTelegram Pro Bot — Команды", "help_title": "PCAtelegram_web Bot — Команды",
"help_lines": "/start — Главное меню\n/help — Эта справка\n/status — Быстрый статус\n/logs — Последние логи\n/lang — Сменить язык\n/addadmin ID — Добавить админа\n/deladmin ID — Удалить админа\n\nИспользуйте кнопки меню для остальных операций.", "help_lines": "/start — Главное меню\n/help — Эта справка\n/status — Быстрый статус\n/logs — Последние логи\n/lang — Сменить язык\n/addadmin ID — Добавить админа\n/deladmin ID — Удалить админа\n\nИспользуйте кнопки меню для остальных операций.",
"menu_install": "⚙️ Установить", "menu_install": "⚙️ Установить",