feat: custom brand name everywhere + a working client-facing site out of the box
User ask, paraphrased: install it, get help wiring up payments, and
immediately have a ready site under your own name — not "MBS Panel"
plastered everywhere and a bunch of manual follow-up.
Two things were actually broken/missing, found by tracing every surface
a real customer or the operator would see:
1. "MBS Panel" was hardcoded in ~20 places (bot messages, subscription
page, admin panel splash/title/sidebar, legal pages, 2FA issuer,
install.sh) with zero way to change it short of editing source.
New BRAND_NAME config value (config.py default "MBS Panel", so this
is 100% backward compatible for existing installs) wired through
everywhere via the same live-settings pattern from the last commit
(settings.get_brand_name(), no restart needed anywhere it's used).
New Настройки → «Название» section in the admin panel to change it.
2. site/index.html and site/cabinet.html — a fully-built landing page +
personal-cabinet template, already in the repo — were never actually
served by anything. Not mounted by FastAPI, not deployed by
install.sh, not linked from anywhere. Pure dead weight: a repo that
looked like it shipped a client site but didn't. Now legal.py gets a
render_site_page() (same {{TOKEN}} substitution + HTML-escaping as
the existing offer/privacy renderer, new tokens: BRAND_NAME,
SITE_DOMAIN, SUB_DOMAIN, BOT_USERNAME) and GET "/" serves the branded
landing page on any host that isn't PANEL_DOMAIN (in practice:
SUB_DOMAIN, which nginx already routes to this backend — zero
install.sh/nginx/certbot changes needed, so this is live on every
existing install without an upgrade step beyond `mbs update`).
GET /cabinet.html serves the cabinet. Landing page's pricing section
now fetches real, live prices from a new public GET /api/plans
instead of showing static duration labels with no numbers.
Also fixed along the way, same staleness-bug class as the payments/HWID
fix last commit, found by grepping for every remaining frozen `from
config import ...` in api.py: BOT_TOKEN/BOT_USERNAME were still frozen
constants in api.py (mbs-api never restarts itself). Concretely this
meant: changing the bot via Настройки → Telegram-бот would leave
_tg_send_message (payment-received notifications) silently trying the
OLD token, admin_get_bot_settings showing the OLD username right after
a successful save, and gift-code links pointing at the OLD bot — all
until a manual mbs restart, same shape as the Platega-secret bug fixed
last commit. Added settings.bot_credentials(), wired it through every
call site (hoisted out of loops where relevant, same N+1 discipline as
always), removed the now-stale "выполни mbs restart" copy from the bot
settings hint.
legal.py's own BOT_USERNAME import was frozen too (used by the /offer
and /privacy {{BOT_USERNAME}} token) — switched to reading it live
in-module (no settings.py import from legal.py, would've been circular
since settings.py already imports legal.py for the env reader).
install.sh: new interactive prompt for the brand name (default "MBS
Panel", so hitting enter reproduces today's behavior exactly), written
to .env, echoed in the final summary along with the now-live site URL.
Verification: same story as always — api.py/bot.py still can't import
locally (no pydantic-core wheel for Python 3.14 on this machine).
py_compile + pyflakes clean across the whole repo. Real runtime test
against an isolated .env fixture: brand name and bot-credential live
reads (no reimport), render_site_page() token substitution correctness
on the actual site/index.html and site/cabinet.html files including an
XSS check (brand name containing <script> comes out HTML-escaped), and
a regression check that adding the BRAND_NAME token to the existing
legal.render() didn't break offer.html/privacy.html. Extracted
SUB_PAGE_TEMPLATE/SUB_PAGE_EXPIRED_TEMPLATE via ast from api.py (can't
import the module, but can pull the string constants) and ran the real
.format() calls against them to catch any brace-escaping mistake in the
new {brand_name} placeholder — CSS braces in those templates are
already double-escaped for .format(), easy to get wrong. Extracted and
node --check'd admin.html's whole inline script, div-tag-balance check
on the full file. install.sh's new prompt+heredoc snippet run standalone
with piped stdin (both a brand name with spaces and an empty/default
input), round-tripped the resulting .env back through the real
env-parsing logic. Extended the existing CI "app wiring" step (which
does import api/bot for real on Linux) with branding assertions calling
the actual route functions directly (api.root(), api.public_plans(),
api.public_branding()) — ran every part of that step's new logic that
doesn't need api.py locally first, to catch what's catchable before
trusting the rest to CI once the account's abuse-review lifts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7d140711fd
commit
7cc50973f6
14 changed files with 228 additions and 52 deletions
36
legal.py
36
legal.py
|
|
@ -1,10 +1,10 @@
|
|||
import html
|
||||
import os
|
||||
|
||||
from config import BASE_DIR, BOT_USERNAME
|
||||
import config
|
||||
|
||||
ENV_PATH = os.path.join(BASE_DIR, ".env")
|
||||
SITE_DIR = os.path.join(BASE_DIR, "site")
|
||||
ENV_PATH = os.path.join(config.BASE_DIR, ".env")
|
||||
SITE_DIR = os.path.join(config.BASE_DIR, "site")
|
||||
|
||||
FIELD_KEYS = ["LEGAL_NAME", "LEGAL_INN", "REFUND_HOURS", "SUPPORT_CONTACT", "SUPPORT_EMAIL", "OFFER_EFFECTIVE_DATE"]
|
||||
|
||||
|
|
@ -60,20 +60,48 @@ def _field(value: str, fallback_label: str) -> str:
|
|||
return html.escape(value) if value else _fallback(fallback_label)
|
||||
|
||||
|
||||
def live_bot_username() -> str:
|
||||
raw = read_env_var("BOT_USERNAME", "")
|
||||
return raw.strip() if raw.strip() else config.BOT_USERNAME
|
||||
|
||||
|
||||
def live_brand_name() -> str:
|
||||
raw = read_env_var("BRAND_NAME", "")
|
||||
return raw.strip() if raw.strip() else config.BRAND_NAME
|
||||
|
||||
|
||||
def render(template_name: str) -> str:
|
||||
path = os.path.join(SITE_DIR, template_name)
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
s = get_settings()
|
||||
bot_username = live_bot_username()
|
||||
replacements = {
|
||||
"EFFECTIVE_DATE": _field(s["OFFER_EFFECTIVE_DATE"], "дата не указана"),
|
||||
"LEGAL_NAME": _field(s["LEGAL_NAME"], "название/ФИО не указано"),
|
||||
"INN": _field(s["LEGAL_INN"], "ИНН не указан"),
|
||||
"BOT_USERNAME": _field(f"@{BOT_USERNAME}" if BOT_USERNAME else "", "бот не указан"),
|
||||
"BOT_USERNAME": _field(f"@{bot_username}" if bot_username else "", "бот не указан"),
|
||||
"REFUND_HOURS": html.escape(s["REFUND_HOURS"]) if s["REFUND_HOURS"] else "24",
|
||||
"SUPPORT_CONTACT": _field(s["SUPPORT_CONTACT"], "контакт не указан"),
|
||||
"SUPPORT_EMAIL": _field(s["SUPPORT_EMAIL"], "email не указан"),
|
||||
"BRAND_NAME": html.escape(live_brand_name()),
|
||||
}
|
||||
for token, value in replacements.items():
|
||||
content = content.replace("{{" + token + "}}", value)
|
||||
return content
|
||||
|
||||
|
||||
def render_site_page(template_name: str) -> str:
|
||||
path = os.path.join(SITE_DIR, template_name)
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
replacements = {
|
||||
"BRAND_NAME": html.escape(live_brand_name()),
|
||||
"SITE_DOMAIN": html.escape(config.SITE_DOMAIN),
|
||||
"SUB_DOMAIN": html.escape(config.SUB_DOMAIN),
|
||||
"BOT_USERNAME": html.escape(live_bot_username()),
|
||||
}
|
||||
for token, value in replacements.items():
|
||||
content = content.replace("{{" + token + "}}", value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue