mbs-api ran single-worker uvicorn with no --workers flag at all — one
event loop handling every request. Now install.sh (and mbs update, so
existing installs pick it up too) compute a worker count from nproc
(clamped 1-4, matching typical VPS core counts) and bake it into the
systemd unit via sed substitution of a __WORKERS__ placeholder. Safe
to parallelize: verified no api.py module-level mutable state, all
of it already goes through sqlite (payment idempotency and the
xray-config file lock are already correct across separate processes,
not just asyncio tasks within one — confirmed both are OS/db-level,
not in-process). Verified with a mock dry-run of the new systemd-unit
section (fake nproc, real sed substitution) producing the expected
ExecStart line for several core counts.
Also: build_subscription_text() — called on every single hit of
/sub/{token}, the single most frequently called endpoint in the whole
app, since every VPN client re-fetches on every reconnect — was doing
one db.get_node() call per distinct node in a user's subscriptions
instead of fetching once. Same N+1 shape as the admin-endpoint bugs
fixed yesterday, except this one is on the hot path, not just the
admin panel. Fixed to batch-fetch via db.list_nodes() once.
Verified: correct output for a 4-node subscription (each node's
address appears exactly once, de1's 4 transports all present,
nothing silently dropped) and measured ~1.3ms/call average.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
136 lines
4.7 KiB
Bash
136 lines
4.7 KiB
Bash
#!/bin/bash
|
||
set -e
|
||
|
||
APP_DIR="/opt/mbs-panel"
|
||
ENV_FILE="$APP_DIR/.env"
|
||
|
||
usage() {
|
||
cat << 'EOF'
|
||
mbs — управление MBS Panel
|
||
|
||
mbs pass [новый_пароль] сменить пароль админ-панели (без аргумента — сгенерировать случайный)
|
||
mbs status статус всех сервисов (bot, api, xray, nginx)
|
||
mbs restart перезапустить всё (bot, api, xray, reload nginx)
|
||
mbs logs [bot|api|xray] последние строки лога (по умолчанию api)
|
||
mbs domain показать текущий домен панели
|
||
mbs update обновить код (зеркало api.savsis.xyz, потом GitHub) и перезапустить (не трогает .env и базу)
|
||
EOF
|
||
}
|
||
|
||
cmd_pass() {
|
||
local new_pass="$1"
|
||
if [ -z "$new_pass" ]; then
|
||
new_pass=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 14)
|
||
fi
|
||
if grep -q "^ADMIN_PANEL_PASSWORD=" "$ENV_FILE"; then
|
||
sed -i "s#^ADMIN_PANEL_PASSWORD=.*#ADMIN_PANEL_PASSWORD=$new_pass#" "$ENV_FILE"
|
||
else
|
||
echo "ADMIN_PANEL_PASSWORD=$new_pass" >> "$ENV_FILE"
|
||
fi
|
||
systemctl restart mbs-api
|
||
echo "Новый пароль панели: $new_pass"
|
||
}
|
||
|
||
cmd_status() {
|
||
systemctl --no-pager status mbs-bot mbs-api xray nginx 2>&1 | grep -E "●|Active:"
|
||
}
|
||
|
||
cmd_restart() {
|
||
systemctl restart mbs-bot mbs-api xray
|
||
systemctl reload nginx 2>/dev/null || true
|
||
echo "Перезапущено: bot, api, xray (+ reload nginx)."
|
||
}
|
||
|
||
cmd_logs() {
|
||
local svc="${1:-api}"
|
||
case "$svc" in
|
||
bot) journalctl -u mbs-bot -n 60 --no-pager ;;
|
||
api) journalctl -u mbs-api -n 60 --no-pager ;;
|
||
xray) journalctl -u xray -n 60 --no-pager ;;
|
||
*) echo "неизвестный сервис: $svc (bot|api|xray)"; exit 1 ;;
|
||
esac
|
||
}
|
||
|
||
cmd_domain() {
|
||
grep "^PANEL_DOMAIN=" "$ENV_FILE"
|
||
}
|
||
|
||
cmd_update() {
|
||
cd "$APP_DIR"
|
||
echo "проверяю обновления..."
|
||
local remote="origin"
|
||
if ! git fetch --quiet origin main 2>/dev/null; then
|
||
if git remote | grep -q '^github$'; then
|
||
echo "зеркало недоступно, пробую github..."
|
||
if ! git fetch --quiet github main 2>/dev/null; then
|
||
echo "не удалось получить обновления ни с зеркала, ни с github"
|
||
return 1
|
||
fi
|
||
remote="github"
|
||
else
|
||
echo "не удалось получить обновления"
|
||
return 1
|
||
fi
|
||
fi
|
||
local before after
|
||
before=$(git rev-parse HEAD)
|
||
after=$(git rev-parse "$remote/main")
|
||
if [ "$before" = "$after" ]; then
|
||
echo "уже последняя версия ($before)."
|
||
return 0
|
||
fi
|
||
echo "текущая: $before"
|
||
echo "новая: $after"
|
||
if ! git merge --ff-only "$remote/main" --quiet; then
|
||
echo "не вышло быстро обновиться (похоже, файлы правились вручную на сервере) — разберись руками: git status"
|
||
return 1
|
||
fi
|
||
echo "обновляю зависимости..."
|
||
venv/bin/pip install --quiet -r requirements.txt
|
||
echo "проверяю код..."
|
||
if ! venv/bin/python -m py_compile *.py; then
|
||
echo "новый код не проходит проверку, откатываюсь на $before..."
|
||
git reset --hard "$before" --quiet
|
||
venv/bin/pip install --quiet -r requirements.txt
|
||
return 1
|
||
fi
|
||
echo "обновляю сам CLI..."
|
||
cp "$APP_DIR/mbs" /usr/local/bin/mbs
|
||
chmod +x /usr/local/bin/mbs
|
||
|
||
echo "обновляю systemd-юниты..."
|
||
local cpu_count api_workers
|
||
cpu_count=$(nproc 2>/dev/null || echo 1)
|
||
if [ "$cpu_count" -lt 2 ]; then api_workers=1
|
||
elif [ "$cpu_count" -gt 4 ]; then api_workers=4
|
||
else api_workers=$cpu_count
|
||
fi
|
||
cp "$APP_DIR/systemd/mbs-bot.service" /etc/systemd/system/mbs-bot.service
|
||
sed "s/__WORKERS__/$api_workers/" "$APP_DIR/systemd/mbs-api.service" > /etc/systemd/system/mbs-api.service
|
||
systemctl daemon-reload
|
||
|
||
echo "перезапускаю..."
|
||
systemctl restart mbs-bot mbs-api xray
|
||
systemctl reload nginx 2>/dev/null || true
|
||
sleep 2
|
||
if systemctl is-active --quiet mbs-bot && systemctl is-active --quiet mbs-api; then
|
||
echo "обновлено: $before -> $(git rev-parse --short HEAD)"
|
||
else
|
||
echo "сервисы не поднялись после обновления, откатываюсь на $before..."
|
||
git reset --hard "$before" --quiet
|
||
venv/bin/pip install --quiet -r requirements.txt
|
||
systemctl restart mbs-bot mbs-api
|
||
echo "откачено обратно на $before"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
case "$1" in
|
||
pass) cmd_pass "$2" ;;
|
||
status) cmd_status ;;
|
||
restart) cmd_restart ;;
|
||
logs) cmd_logs "$2" ;;
|
||
domain) cmd_domain ;;
|
||
update) cmd_update ;;
|
||
*) usage ;;
|
||
esac
|