settings: change Telegram bot token/username from the admin UI (validated via getMe); mbs restart now covers bot+api+xray+nginx

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Savsis? 2026-09-10 23:29:58 +05:00
parent 04db4a359e
commit bfc2fd7b4c
3 changed files with 96 additions and 5 deletions

View file

@ -294,6 +294,7 @@
<div class="nav-item" data-view="traffic" onclick="showView('traffic')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3,13 8,13 10,7 14,19 16,13 21,13"/></svg>Трафик</div>
<div class="nav-item" data-view="payments" onclick="showView('payments')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>Платежи</div>
<div class="nav-item" data-view="docs" onclick="showView('docs')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14,2 14,8 20,8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/></svg>Документация</div>
<div class="nav-item" data-view="settings" onclick="showView('settings')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>Настройки</div>
<div class="sidebar-footer">
<button class="btn ghost" style="width:100%" onclick="logout()">Выйти</button>
<div class="version-tag">MBS Panel v1.0.0 · <a href="https://github.com/devsavsis/mbs-panel/releases/latest" target="_blank" style="color:inherit">обновления</a></div>
@ -520,6 +521,21 @@
<p class="page-sub" style="margin-top:8px">Посмотреть забаненные IP по конкретному джейлу: <code>fail2ban-client status sshd</code>. Разбанить: <code>fail2ban-client set sshd unbanip АЙПИ</code>.</p>
</div>
</div>
<div id="view-settings" class="view">
<div class="page-title">Настройки</div>
<div class="page-sub">Смена телеграм-бота без переустановки панели</div>
<div class="section">
<div class="section-head"><h2>Telegram-бот</h2></div>
<p class="page-sub" style="margin-bottom:16px">Сейчас: <b id="settings-bot-username">—</b> (токен: <span id="settings-bot-token" style="font-family:'Fira Mono',monospace">—</span>)</p>
<div class="form-row">
<div><label class="f">Новый токен (от @BotFather)</label><input type="text" id="settings-bot-token-input" placeholder="123456789:AAAA..."></div>
<div style="flex:0"><label class="f">&nbsp;</label><button class="btn" onclick="saveBotSettings()">Сменить бота</button></div>
</div>
<p class="check-hint">Панель сама проверит токен у Telegram (запрос getMe) перед применением и подставит настоящий юзернейм бота — придумывать не нужно. После смены перезапустится только бот; если уведомления от api (например об оплате) продолжат идти со старого бота, выполни на сервере <code>mbs restart</code>.</p>
<div id="settings-bot-result"></div>
</div>
</div>
</div>
</div>
@ -573,6 +589,7 @@ function showView(name) {
if (name === "nodes") loadNodes();
if (name === "traffic") loadTraffic();
if (name === "payments") loadPayments();
if (name === "settings") loadBotSettings();
}
const COUNTRIES = [
@ -827,6 +844,29 @@ async function checkPayment(id, btn) {
}
}
async function loadBotSettings() {
const data = await api("/admin/api/settings/bot");
document.getElementById("settings-bot-username").textContent = "@" + data.username;
document.getElementById("settings-bot-token").textContent = data.token_masked;
document.getElementById("settings-bot-result").innerHTML = "";
}
async function saveBotSettings() {
const input = document.getElementById("settings-bot-token-input");
const token = input.value.trim();
const result = document.getElementById("settings-bot-result");
if (!token) return;
result.innerHTML = '<p class="page-sub" style="margin-top:10px">Проверяю токен у Telegram…</p>';
try {
const res = await api("/admin/api/settings/bot", { method: "POST", body: JSON.stringify({ token }) });
result.innerHTML = `<p class="page-sub" style="margin-top:10px;color:var(--green)">Готово: бот сменён на @${esc(res.username)}${res.restarted ? "" : " (сохранено, но авто-рестарт не удался — перезапусти вручную: mbs restart)"}</p>`;
input.value = "";
loadBotSettings();
} catch (e) {
result.innerHTML = '<p class="page-sub" style="margin-top:10px;color:var(--red)">Не получилось — проверь токен и попробуй снова</p>';
}
}
function fmtDate(iso) { return iso ? iso.slice(0, 10) : "—"; }
function statusBadge(active, daysLeft) {
if (!active) return '<span class="badge bad">истекла</span>';

54
api.py
View file

@ -2,6 +2,8 @@ import datetime
import json
import os
import re
import subprocess
import urllib.request
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi import Body
@ -14,14 +16,32 @@ import payments
import xray_manager
from config import (
PLANS, PLANS_BY_CODE, SITE_DOMAIN, SUB_DOMAIN, PANEL_DOMAIN,
ADMIN_PANEL_PASSWORD, BOT_USERNAME, BOT_TOKEN,
ADMIN_PANEL_PASSWORD, BOT_USERNAME, BOT_TOKEN, BASE_DIR,
HWID_LIMIT_ENABLED, HWID_FALLBACK_LIMIT,
)
HWID_RE = re.compile(r"^[a-zA-Z0-9=-]{10,64}$")
ENV_PATH = os.path.join(BASE_DIR, ".env")
db.init_db()
def _update_env_var(key: str, value: str):
lines = []
if os.path.exists(ENV_PATH):
with open(ENV_PATH, encoding="utf-8") as f:
lines = f.readlines()
found = False
for i, line in enumerate(lines):
if line.strip().startswith(f"{key}="):
lines[i] = f"{key}={value}\n"
found = True
break
if not found:
lines.append(f"{key}={value}\n")
with open(ENV_PATH, "w", encoding="utf-8") as f:
f.writelines(lines)
app = FastAPI(title="mbs-api")
app.add_middleware(
@ -275,7 +295,6 @@ def install_script(token: str):
def _tg_send_message(tg_id: int, text: str):
import urllib.request
url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
data = json.dumps({"chat_id": tg_id, "text": text, "parse_mode": "HTML"}).encode()
req = urllib.request.Request(url, data=data, method="POST", headers={"Content-Type": "application/json"})
@ -432,6 +451,37 @@ def admin_me(request: Request):
@app.get("/admin/api/settings/bot")
def admin_get_bot_settings(request: Request):
require_admin(request)
masked = f"{BOT_TOKEN[:8]}...{BOT_TOKEN[-4:]}" if len(BOT_TOKEN) > 14 else "***"
return {"username": BOT_USERNAME, "token_masked": masked}
@app.post("/admin/api/settings/bot")
def admin_set_bot_settings(request: Request, body: dict = Body(...)):
require_admin(request)
token = (body.get("token") or "").strip()
if not token:
raise HTTPException(400, "token required")
try:
with urllib.request.urlopen(f"https://api.telegram.org/bot{token}/getMe", timeout=10) as resp:
data = json.loads(resp.read())
except Exception:
raise HTTPException(400, "не получилось проверить токен — нет связи с Telegram")
if not data.get("ok"):
raise HTTPException(400, "Telegram отклонил этот токен")
username = data["result"]["username"]
_update_env_var("BOT_TOKEN", token)
_update_env_var("BOT_USERNAME", username)
try:
subprocess.run(["systemctl", "restart", "mbs-bot"], check=True, timeout=15)
restarted = True
except Exception:
restarted = False
return {"ok": True, "username": username, "restarted": restarted}
@app.get("/admin/api/stats")
def admin_stats(request: Request):
require_admin(request)

7
mbs
View file

@ -10,7 +10,7 @@ mbs — управление MBS Panel
mbs pass [новый_пароль] сменить пароль админ-панели (без аргумента — сгенерировать случайный)
mbs status статус всех сервисов (bot, api, xray, nginx)
mbs restart перезапустить bot + api
mbs restart перезапустить всё (bot, api, xray, reload nginx)
mbs logs [bot|api|xray] последние строки лога (по умолчанию api)
mbs domain показать текущий домен панели
mbs update обновить код с GitHub и перезапустить (не трогает .env и базу)
@ -36,8 +36,9 @@ cmd_status() {
}
cmd_restart() {
systemctl restart mbs-bot mbs-api
echo "Перезапущено."
systemctl restart mbs-bot mbs-api xray
systemctl reload nginx 2>/dev/null || true
echo "Перезапущено: bot, api, xray (+ reload nginx)."
}
cmd_logs() {