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:
parent
04db4a359e
commit
bfc2fd7b4c
3 changed files with 96 additions and 5 deletions
40
admin.html
40
admin.html
|
|
@ -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"> </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
54
api.py
|
|
@ -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
7
mbs
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue