From 43df9bff48a053defc0de7e68e5aaf3568e993c5 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:33:29 +0300 Subject: [PATCH 1/7] Update remnawave_api.py --- app/external/remnawave_api.py | 40 ++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/app/external/remnawave_api.py b/app/external/remnawave_api.py index 1e8d069f..ec553efb 100644 --- a/app/external/remnawave_api.py +++ b/app/external/remnawave_api.py @@ -1,6 +1,7 @@ import asyncio import json import ssl +import base64 from datetime import datetime, timedelta from typing import Dict, List, Optional, Union, Any from urllib.parse import urlparse @@ -103,12 +104,15 @@ class RemnaWaveAPIError(Exception): class RemnaWaveAPI: - def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None): + def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None, + username: Optional[str] = None, password: Optional[str] = None): self.base_url = base_url.rstrip('/') self.api_key = api_key - self.secret_key = secret_key + self.secret_key = secret_key + self.username = username + self.password = password self.session: Optional[aiohttp.ClientSession] = None - self.authenticated = False + self.authenticated = False def _detect_connection_type(self) -> str: parsed = urlparse(self.base_url) @@ -129,14 +133,9 @@ class RemnaWaveAPI: return "local" return "external" - - async def __aenter__(self): - conn_type = self._detect_connection_type() - - logger.info(f"Подключение к Remnawave: {self.base_url} (тип: {conn_type})") - + + def _prepare_auth_headers(self) -> Dict[str, str]: headers = { - 'Authorization': f'Bearer {self.api_key}', 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Forwarded-Proto': 'https', @@ -144,6 +143,27 @@ class RemnaWaveAPI: 'X-Real-IP': '127.0.0.1' } + if self.username and self.password: + import base64 + credentials = f"{self.username}:{self.password}" + encoded_credentials = base64.b64encode(credentials.encode()).decode() + headers['X-Api-Key'] = f"Basic {encoded_credentials}" + logger.debug("Используем Basic Auth в X-Api-Key заголовке") + else: + headers['X-Api-Key'] = self.api_key + logger.debug("Используем API ключ в X-Api-Key заголовке") + + headers['Authorization'] = f'Bearer {self.api_key}' + + return headers + + async def __aenter__(self): + conn_type = self._detect_connection_type() + + logger.info(f"Подключение к Remnawave: {self.base_url} (тип: {conn_type})") + + headers = self._prepare_auth_headers() + cookies = None if self.secret_key: if ':' in self.secret_key: From c390d1839972cc573cfbcd847d5f4f52b290bc21 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:34:16 +0300 Subject: [PATCH 2/7] Update config.py --- app/config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/config.py b/app/config.py index 0c29f2a7..f69ddd61 100644 --- a/app/config.py +++ b/app/config.py @@ -32,6 +32,10 @@ class Settings(BaseSettings): REMNAWAVE_API_URL: str REMNAWAVE_API_KEY: str REMNAWAVE_SECRET_KEY: Optional[str] = None + + REMNAWAVE_USERNAME: Optional[str] = None + REMNAWAVE_PASSWORD: Optional[str] = None + REMNAWAVE_AUTH_TYPE: str = "api_key" TRIAL_DURATION_DAYS: int = 3 TRIAL_TRAFFIC_LIMIT_GB: int = 10 @@ -238,6 +242,16 @@ class Settings(BaseSettings): except (ValueError, AttributeError): return [] + + def get_remnawave_auth_params(self) -> Dict[str, Optional[str]]: + return { + "base_url": self.REMNAWAVE_API_URL, + "api_key": self.REMNAWAVE_API_KEY, + "secret_key": self.REMNAWAVE_SECRET_KEY, + "username": self.REMNAWAVE_USERNAME, + "password": self.REMNAWAVE_PASSWORD, + "auth_type": self.REMNAWAVE_AUTH_TYPE + } def get_autopay_warning_days(self) -> List[int]: try: From a936a97e5e88ee563e38ab81fe93a0b1015fe075 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:34:44 +0300 Subject: [PATCH 3/7] Update remnawave_service.py --- app/services/remnawave_service.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py index 8a200215..8d0974b6 100644 --- a/app/services/remnawave_service.py +++ b/app/services/remnawave_service.py @@ -23,10 +23,13 @@ logger = logging.getLogger(__name__) class RemnaWaveService: def __init__(self): + auth_params = settings.get_remnawave_auth_params() self.api = RemnaWaveAPI( - base_url=settings.REMNAWAVE_API_URL, - api_key=settings.REMNAWAVE_API_KEY, - secret_key=settings.REMNAWAVE_SECRET_KEY + base_url=auth_params["base_url"], + api_key=auth_params["api_key"], + secret_key=auth_params["secret_key"], + username=auth_params["username"], + password=auth_params["password"] ) def _parse_remnawave_date(self, date_str: str) -> datetime: From 301b4124ef6c1ec03e6c16bc23d5569550d3fad7 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:36:17 +0300 Subject: [PATCH 4/7] Update .env.example --- .env.example | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index d3c906cc..d19db529 100644 --- a/.env.example +++ b/.env.example @@ -34,9 +34,17 @@ REDIS_URL=redis://redis:6379/0 # ===== REMNAWAVE API ===== REMNAWAVE_API_URL=https://panel.example.com -REMNAWAVE_API_KEY= -# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD -REMNAWAVE_SECRET_KEY=your_secret_key_here +REMNAWAVE_API_KEY=your_api_key_here + +# Тип авторизации: "api_key", "basic_auth" +REMNAWAVE_AUTH_TYPE=api_key + +# Для панелей с Basic Auth (опционально) +REMNAWAVE_USERNAME= +REMNAWAVE_PASSWORD= + +# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD +REMNAWAVE_SECRET_KEY= # ========= ПОДПИСКИ ========= # ===== ТРИАЛ ПОДПИСКА ===== From ba25ec7ad0074ecee1c6087719d6d6e94512bd60 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:44:16 +0300 Subject: [PATCH 5/7] Update subscription_service.py --- app/services/subscription_service.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/services/subscription_service.py b/app/services/subscription_service.py index 087d3a3c..2f7ff453 100644 --- a/app/services/subscription_service.py +++ b/app/services/subscription_service.py @@ -23,9 +23,13 @@ logger = logging.getLogger(__name__) class SubscriptionService: def __init__(self): + auth_params = settings.get_remnawave_auth_params() self.api = RemnaWaveAPI( - base_url=settings.REMNAWAVE_API_URL, - api_key=settings.REMNAWAVE_API_KEY + base_url=auth_params["base_url"], + api_key=auth_params["api_key"], + secret_key=auth_params["secret_key"], + username=auth_params["username"], + password=auth_params["password"] ) async def create_remnawave_user( From 5275f4fbeb3e3cba2b2c402a7fe25006632b034e Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:52:28 +0300 Subject: [PATCH 6/7] Update maintenance_service.py --- app/services/maintenance_service.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/services/maintenance_service.py b/app/services/maintenance_service.py index 7ad8a3da..ae8f4fb9 100644 --- a/app/services/maintenance_service.py +++ b/app/services/maintenance_service.py @@ -264,7 +264,14 @@ class MaintenanceService: self._is_checking = True self._status.last_check = datetime.utcnow() - api = RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) + auth_params = settings.get_remnawave_auth_params() + api = RemnaWaveAPI( + base_url=auth_params["base_url"], + api_key=auth_params["api_key"], + secret_key=auth_params["secret_key"], + username=auth_params["username"], + password=auth_params["password"] + ) async with api: is_connected = await test_api_connection(api) From a7868787a402c4bd9943e364ce02bfef967a08a4 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 13 Sep 2025 07:53:39 +0300 Subject: [PATCH 7/7] Update remnawave_service.py --- app/services/remnawave_service.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py index 8d0974b6..32c4e7eb 100644 --- a/app/services/remnawave_service.py +++ b/app/services/remnawave_service.py @@ -354,7 +354,7 @@ class RemnaWaveService: async def update_squad_inbounds(self, squad_uuid: str, inbound_uuids: List[str]) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: data = { 'uuid': squad_uuid, 'inbounds': inbound_uuids @@ -876,7 +876,7 @@ class RemnaWaveService: async def get_squad_details(self, squad_uuid: str) -> Optional[Dict]: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: squad = await api.get_internal_squad_by_uuid(squad_uuid) if squad: return { @@ -893,7 +893,7 @@ class RemnaWaveService: async def add_all_users_to_squad(self, squad_uuid: str) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: response = await api._make_request('POST', f'/api/internal-squads/{squad_uuid}/bulk-actions/add-users') return response.get('response', {}).get('eventSent', False) except Exception as e: @@ -902,7 +902,7 @@ class RemnaWaveService: async def remove_all_users_from_squad(self, squad_uuid: str) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: response = await api._make_request('DELETE', f'/api/internal-squads/{squad_uuid}/bulk-actions/remove-users') return response.get('response', {}).get('eventSent', False) except Exception as e: @@ -911,7 +911,7 @@ class RemnaWaveService: async def delete_squad(self, squad_uuid: str) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: response = await api.delete_internal_squad(squad_uuid) return response except Exception as e: @@ -920,7 +920,7 @@ class RemnaWaveService: async def get_all_inbounds(self) -> List[Dict]: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: response = await api._make_request('GET', '/api/config-profiles/inbounds') inbounds_data = response.get('response', {}).get('inbounds', []) @@ -941,7 +941,7 @@ class RemnaWaveService: async def rename_squad(self, squad_uuid: str, new_name: str) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: data = { 'uuid': squad_uuid, 'name': new_name @@ -954,7 +954,7 @@ class RemnaWaveService: async def create_squad(self, name: str, inbound_uuids: List[str]) -> bool: try: - async with RemnaWaveAPI(settings.REMNAWAVE_API_URL, settings.REMNAWAVE_API_KEY) as api: + async with self.api as api: squad = await api.create_internal_squad(name, inbound_uuids) return squad is not None except Exception as e: