Initial commit: VPN Telegram bot (sanitized defaults)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""Thin wrapper around RemnawaveSDK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from remnawave import RemnawaveSDK
|
||||
from remnawave.models import LoginRequestDto
|
||||
|
||||
from config import Settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_sdk(settings: Settings, *, token: str | None = None) -> RemnawaveSDK:
|
||||
kwargs: dict = {
|
||||
"base_url": settings.remnawave_base_url,
|
||||
"token": token if token is not None else settings.remnawave_token,
|
||||
}
|
||||
if settings.caddy_token:
|
||||
kwargs["caddy_token"] = settings.caddy_token
|
||||
return RemnawaveSDK(**kwargs)
|
||||
|
||||
|
||||
async def create_sdk_ready(settings: Settings) -> RemnawaveSDK:
|
||||
"""
|
||||
Собирает SDK.
|
||||
Если заданы REMNAWAVE_USERNAME/PASSWORD — логинится и берёт admin JWT
|
||||
(нужно для create/update users, если API-токен только на чтение).
|
||||
"""
|
||||
if settings.remnawave_username and settings.remnawave_password:
|
||||
bootstrap = create_sdk(settings, token=settings.remnawave_token or "bootstrap")
|
||||
try:
|
||||
login = await bootstrap.auth.login(
|
||||
LoginRequestDto(
|
||||
username=settings.remnawave_username,
|
||||
password=settings.remnawave_password,
|
||||
)
|
||||
)
|
||||
logger.info("Remnawave: вошли как %s (admin JWT)", settings.remnawave_username)
|
||||
return create_sdk(settings, token=login.access_token)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error(
|
||||
"Не удалось войти в Remnawave как %s: %s. Используем API-токен.",
|
||||
settings.remnawave_username,
|
||||
exc,
|
||||
)
|
||||
|
||||
return create_sdk(settings)
|
||||
|
||||
|
||||
async def probe_users_write_access(sdk: RemnawaveSDK) -> tuple[bool, str]:
|
||||
"""Проверка, что токен может создавать пользователей."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from remnawave.enums import UserStatus
|
||||
from remnawave.models import CreateUserRequestDto
|
||||
|
||||
username = f"rwprobe_{int(datetime.now(timezone.utc).timestamp()) % 10_000_000}"
|
||||
expire = datetime.now(timezone.utc) + timedelta(hours=1)
|
||||
try:
|
||||
created = await sdk.users.create_user(
|
||||
CreateUserRequestDto(
|
||||
username=username,
|
||||
expire_at=expire,
|
||||
status=UserStatus.ACTIVE,
|
||||
description="write-access probe (auto-delete)",
|
||||
)
|
||||
)
|
||||
try:
|
||||
await sdk.users.delete_user(str(created.uuid))
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("probe user created but not deleted: %s", created.uuid)
|
||||
return True, "OK: users:write доступен"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
msg = str(exc)
|
||||
if "403" in msg or "Forbidden" in msg:
|
||||
return False, (
|
||||
"API-токен не может создавать пользователей (HTTP 403).\n"
|
||||
"В панели Remnawave создай новый API Token со scope "
|
||||
"<code>users:*</code> или <code>*</code>,\n"
|
||||
"либо укажи REMNAWAVE_USERNAME / REMNAWAVE_PASSWORD админа в .env."
|
||||
)
|
||||
return False, f"Проверка записи не удалась: {exc}"
|
||||
|
||||
|
||||
def friendly_remnawave_error(exc: BaseException) -> str:
|
||||
text = str(exc)
|
||||
if "403" in text or "Forbidden" in text:
|
||||
return (
|
||||
"Remnawave запретил операцию (HTTP 403).\n\n"
|
||||
"Скорее всего у API Token нет прав на запись пользователей.\n"
|
||||
"Что сделать:\n"
|
||||
"1) Панель → API Tokens → создай токен со scope <b>users:*</b> или <b>*</b>\n"
|
||||
"2) Вставь новый токен в <code>REMNAWAVE_TOKEN</code>\n"
|
||||
"либо задай логин/пароль админа:\n"
|
||||
"<code>REMNAWAVE_USERNAME</code> / <code>REMNAWAVE_PASSWORD</code>\n\n"
|
||||
f"<code>{exc}</code>"
|
||||
)
|
||||
return f"<code>{exc}</code>"
|
||||
Reference in New Issue
Block a user