mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 11:26:03 +00:00
feat(v2.4.2): bot non-interactive install.sh actions
- install.sh: new bot_action_dispatch entry point for --action=X --json CLI invocation from the bot/scripts - install.sh: bot_action_change_template — reuses download_template + deploy_template_to_nginx, updates config.json template_id - install.sh: bot_action_change_lite_domain — regenerates telemt TOML with new fake-TLS mask domain, restarts telemt - bot.py: run_bot_action() subprocess wrapper parses JSON result - bot.py: cb_pro_confirm now performs real change-template when already in pro mode (fresh install still routes to CLI) - bot.py: cb_lite_domain now performs real change-lite-domain when already in lite mode - version -> 2.4.2
This commit is contained in:
@@ -100,13 +100,14 @@ logger = logging.getLogger(__name__)
|
||||
# CONFIGURATION
|
||||
# ============================================================================
|
||||
|
||||
GOTELEGRAM_VERSION = "2.4.0"
|
||||
GOTELEGRAM_VERSION = "2.4.2"
|
||||
GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json"
|
||||
TELEMT_CONFIG = "/etc/telemt/config.toml"
|
||||
TELEMT_SERVICE = "telemt"
|
||||
WEBSITE_ROOT = "/var/www/gotelegram-site"
|
||||
BACKUP_DIR = "/opt/gotelegram/backups"
|
||||
TEMPLATES_CATALOG = "/opt/gotelegram/templates_catalog.json"
|
||||
INSTALL_SH = "/opt/gotelegram/install.sh"
|
||||
|
||||
PROMO_LINK_1 = "https://vk.cc/ct29NQ"
|
||||
PROMO_LINK_2 = "https://vk.cc/cUxAhj"
|
||||
@@ -239,6 +240,58 @@ async def sh(*args, timeout: int = 60) -> Tuple[int, str, str]:
|
||||
return (-1, "", str(e))
|
||||
|
||||
|
||||
async def run_bot_action(action: str, timeout: int = 300, **params) -> Dict:
|
||||
"""Invoke install.sh --action=X --json and parse the JSON result.
|
||||
|
||||
Args:
|
||||
action: action name (e.g. "change-template", "change-lite-domain")
|
||||
timeout: seconds to wait for completion (long ops: template download can take time)
|
||||
**params: arbitrary key→value pairs, each passed as --key=value
|
||||
|
||||
Returns:
|
||||
dict with at least {"status": "success|error", "message": "..."}.
|
||||
Transport errors are mapped to {"status":"error","message":..., "code":"transport"}
|
||||
"""
|
||||
cmd = ["bash", INSTALL_SH, f"--action={action}", "--json"]
|
||||
for k, v in params.items():
|
||||
if v is None:
|
||||
continue
|
||||
cmd.append(f"--{k.replace('_', '-')}={v}")
|
||||
|
||||
code, stdout, stderr = await sh(*cmd, timeout=timeout)
|
||||
stdout = (stdout or "").strip()
|
||||
|
||||
# install.sh may print multiple log lines to stderr; the JSON is on stdout.
|
||||
# Pick the last non-empty line that looks like JSON (robust to any stray output).
|
||||
json_line = None
|
||||
for line in reversed(stdout.splitlines()):
|
||||
line = line.strip()
|
||||
if line.startswith("{") and line.endswith("}"):
|
||||
json_line = line
|
||||
break
|
||||
|
||||
if json_line:
|
||||
try:
|
||||
data = json.loads(json_line)
|
||||
if isinstance(data, dict) and "status" in data:
|
||||
return data
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"run_bot_action: JSON parse failed: {e} | line={json_line!r}")
|
||||
|
||||
# No JSON from install.sh — synthesize an error result
|
||||
tail = (stderr or "")[-300:] if stderr else ""
|
||||
logger.error(
|
||||
f"run_bot_action({action}): no JSON output, rc={code}, "
|
||||
f"stdout={stdout[-300:]!r}, stderr={tail!r}"
|
||||
)
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "install.sh did not return a JSON result",
|
||||
"code": "transport",
|
||||
"rc": str(code),
|
||||
}
|
||||
|
||||
|
||||
def load_json(path: str) -> Optional[Dict]:
|
||||
"""Load JSON file."""
|
||||
try:
|
||||
@@ -808,16 +861,13 @@ async def cb_install_mode_lite(update: Update, context: ContextTypes.DEFAULT_TYP
|
||||
|
||||
|
||||
async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Lite domain selection callback — safe stub.
|
||||
"""Lite domain selection callback — real implementation (v2.4.2+).
|
||||
|
||||
Historic bug (v2.4.1): this callback used to overwrite
|
||||
/opt/gotelegram/config.json with a minimal fake config (no secret, no
|
||||
mask_host, no engine), silently corrupting the live proxy and showing a
|
||||
fake "✅ Lite mode installed!" — while never actually invoking install.sh.
|
||||
|
||||
Installing/changing modes non-interactively from the bot is not yet wired
|
||||
up. Until it is, refuse cleanly and route the user to the CLI instead of
|
||||
damaging the working configuration.
|
||||
Branches on current mode:
|
||||
* lite mode (active): invoke `install.sh --action=change-lite-domain`
|
||||
which regenerates the telemt TOML with a new fake-TLS mask domain and
|
||||
restarts the service. Preserves secret/port.
|
||||
* any other mode: route to CLI. Fresh Lite install is interactive.
|
||||
"""
|
||||
query = update.callback_query
|
||||
data = query.data
|
||||
@@ -830,14 +880,52 @@ async def cb_lite_domain(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
||||
|
||||
await query.answer()
|
||||
|
||||
text = (
|
||||
"<b>⚠️ Установка из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n"
|
||||
"Чтобы установить или переключить режим Lite, запустите на сервере:\n"
|
||||
"<code>gotelegram</code>\n\n"
|
||||
"Затем: <b>1) Прокси → 1) Установить/Обновить → Lite</b>.\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
config = load_json(GOTELEGRAM_CONFIG) or {}
|
||||
current_mode = config.get("mode", "")
|
||||
|
||||
if current_mode != "lite":
|
||||
text = (
|
||||
"<b>⚠️ Установка Lite из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный домен: <code>{html.escape(domain)}</code>\n\n"
|
||||
"Чтобы установить Lite, запустите на сервере:\n"
|
||||
"<code>gotelegram</code> → <b>1) Прокси → 1) Установить/Обновить → Lite</b>\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
)
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||
)
|
||||
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
return
|
||||
|
||||
# Lite active — switch fake-TLS mask domain in place
|
||||
progress_text = (
|
||||
"<b>⏳ Меняю маскировочный домен...</b>\n\n"
|
||||
f"Новый домен: <code>{html.escape(domain)}</code>\n\n"
|
||||
"Перегенерирую конфиг telemt и перезапускаю сервис."
|
||||
)
|
||||
await safe_edit_message(query, progress_text, parse_mode="HTML")
|
||||
|
||||
result = await run_bot_action("change-lite-domain", timeout=30, domain=domain)
|
||||
|
||||
if result.get("status") == "success":
|
||||
text = (
|
||||
"<b>✅ Маскировочный домен обновлён</b>\n\n"
|
||||
f"Новый домен: <code>{html.escape(domain)}</code>\n\n"
|
||||
"telemt перезапущен. <b>Важно:</b> старые ссылки подключения больше "
|
||||
"не будут работать — нужно заново раздать новые."
|
||||
)
|
||||
else:
|
||||
err_msg = result.get("message", "unknown error")
|
||||
err_code = result.get("code", "")
|
||||
text = (
|
||||
"<b>❌ Не удалось сменить домен</b>\n\n"
|
||||
f"Домен: <code>{html.escape(domain)}</code>\n"
|
||||
f"Причина: <code>{html.escape(err_msg)}</code>"
|
||||
+ (f" (<code>{html.escape(err_code)}</code>)" if err_code else "")
|
||||
+ "\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||
)
|
||||
@@ -1111,19 +1199,19 @@ async def cb_pro_template(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
||||
|
||||
|
||||
async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Confirm Pro template selection — safe stub.
|
||||
"""Confirm Pro template selection — real implementation (v2.4.2+).
|
||||
|
||||
Historic bug (v2.4.1): this callback used to overwrite
|
||||
/opt/gotelegram/config.json with a fake {"mode":"pro","template":...,
|
||||
"port":443} blob — no secret, no domain, no mask_host — silently
|
||||
corrupting the live proxy so link generation and status broke. At the
|
||||
same time the user saw "✅ Pro mode installed!" although install.sh was
|
||||
never invoked, no template was actually downloaded, nginx was not
|
||||
reconfigured and certbot was not touched.
|
||||
Branches on current mode:
|
||||
* pro mode (active deployment): invoke `install.sh --action=change-template`
|
||||
which downloads the new template and redeploys it to nginx. Reuses the
|
||||
existing domain + SSL cert.
|
||||
* any other mode (or no install at all): route to CLI. Fresh Pro install
|
||||
still requires interactive flow (domain, email, DNS check) — not safe
|
||||
to run headless from the bot.
|
||||
|
||||
Non-interactive Pro install/change from the bot is not yet implemented.
|
||||
Until it is, refuse cleanly and route the user to the CLI instead of
|
||||
damaging the working configuration.
|
||||
Historic context: v2.4.1 stub used to overwrite config.json with a fake
|
||||
blob; that was replaced with a safe message in v2.4.1 hotfix; now in
|
||||
v2.4.2 we wire the real change-template path through install.sh.
|
||||
"""
|
||||
query = update.callback_query
|
||||
data = query.data
|
||||
@@ -1131,19 +1219,63 @@ async def cb_pro_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
||||
|
||||
await query.answer()
|
||||
|
||||
text = (
|
||||
"<b>⚠️ Установка шаблона из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
|
||||
"Чтобы установить или сменить шаблон, запустите на сервере:\n"
|
||||
"<code>gotelegram</code>\n\n"
|
||||
"Затем: <b>1) Прокси → 1) Установить/Обновить → Pro</b> "
|
||||
"(или <b>7) Сменить режим/шаблон</b> для смены текущего).\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
# Read current config to decide: in-place change-template vs fresh install
|
||||
config = load_json(GOTELEGRAM_CONFIG) or {}
|
||||
current_mode = config.get("mode", "")
|
||||
|
||||
if current_mode != "pro":
|
||||
# Fresh install / mode switch — still routes to CLI (needs domain, SSL)
|
||||
text = (
|
||||
"<b>⚠️ Установка Pro из бота пока не поддерживается</b>\n\n"
|
||||
f"Выбранный шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
|
||||
"Pro-режим требует ввода домена, email и проверки DNS. "
|
||||
"Чтобы установить Pro, запустите на сервере:\n"
|
||||
"<code>gotelegram</code> → <b>1) Прокси → 1) Установить/Обновить → Pro</b>\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>."
|
||||
)
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||
)
|
||||
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
return
|
||||
|
||||
# Pro mode is active — perform change-template in place
|
||||
progress_text = (
|
||||
"<b>⏳ Меняю шаблон сайта...</b>\n\n"
|
||||
f"Шаблон: <code>{html.escape(tpl_id)}</code>\n\n"
|
||||
"Скачиваю репозиторий и разворачиваю в nginx. "
|
||||
"Это может занять 30–90 секунд."
|
||||
)
|
||||
await safe_edit_message(query, progress_text, parse_mode="HTML")
|
||||
|
||||
# Template download + git clone can be slow — generous timeout
|
||||
result = await run_bot_action("change-template", timeout=180, template=tpl_id)
|
||||
|
||||
if result.get("status") == "success":
|
||||
domain = result.get("domain", config.get("domain", ""))
|
||||
text = (
|
||||
"<b>✅ Шаблон обновлён</b>\n\n"
|
||||
f"Новый шаблон: <code>{html.escape(tpl_id)}</code>\n"
|
||||
f"Сайт: <a href=\"https://{html.escape(domain, quote=True)}\">https://{html.escape(domain)}</a>\n\n"
|
||||
"Прокси продолжает работать без перерыва."
|
||||
)
|
||||
else:
|
||||
err_msg = result.get("message", "unknown error")
|
||||
err_code = result.get("code", "")
|
||||
text = (
|
||||
"<b>❌ Не удалось сменить шаблон</b>\n\n"
|
||||
f"Шаблон: <code>{html.escape(tpl_id)}</code>\n"
|
||||
f"Причина: <code>{html.escape(err_msg)}</code>"
|
||||
+ (f" (<code>{html.escape(err_code)}</code>)" if err_code else "")
|
||||
+ "\n\n"
|
||||
"Существующая конфигурация <b>не была изменена</b>. "
|
||||
"Попробуйте другой шаблон или запустите <code>gotelegram</code> из консоли."
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(_t(_uid(update), "btn_back"), callback_data="menu_main")]]
|
||||
)
|
||||
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML")
|
||||
await safe_edit_message(query, text, reply_markup=keyboard, parse_mode="HTML", disable_web_page_preview=True)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user