diff --git a/app/handlers/admin/remnawave.py b/app/handlers/admin/remnawave.py
index 17acf74f..e63c1c80 100644
--- a/app/handlers/admin/remnawave.py
+++ b/app/handlers/admin/remnawave.py
@@ -2295,12 +2295,6 @@ async def show_sync_options(
callback_data="sync_all_users",
)
],
- [
- types.InlineKeyboardButton(
- text="🔄 Обратная синхронизация",
- callback_data="sync_to_panel",
- )
- ],
[
types.InlineKeyboardButton(
text="⚙️ Настройки автосинхронизации",
@@ -2317,94 +2311,6 @@ async def show_sync_options(
await callback.answer()
-@admin_required
-@error_handler
-async def sync_users_to_panel(
- callback: types.CallbackQuery,
- db_user: User,
- db: AsyncSession
-):
-
- progress_text = """
-🔄 Выполняется обратная синхронизация...
-
-📋 Этапы:
-• Загрузка ВСЕХ пользователей из бота
-• Проверка существования в панели Remnawave
-• Создание новых пользователей в панели
-• Обновление данных существующих пользователей
-• Настройка подписок и трафика
-
-⏳ Пожалуйста, подождите...
-"""
-
- await callback.message.edit_text(progress_text, reply_markup=None)
-
- remnawave_service = RemnaWaveService()
- stats = await remnawave_service.sync_users_to_panel(db)
-
- total_operations = stats['created'] + stats['updated']
-
- if stats['errors'] == 0:
- status_emoji = "✅"
- status_text = "успешно завершена"
- elif stats['errors'] < total_operations:
- status_emoji = "⚠️"
- status_text = "завершена с предупреждениями"
- else:
- status_emoji = "❌"
- status_text = "завершена с ошибками"
-
- text = f"""
-{status_emoji} Обратная синхронизация {status_text}
-
-📊 Результат:
-• 🆕 Создано в панели: {stats['created']}
-• 🔄 Обновлено в панели: {stats['updated']}
-• ❌ Ошибок: {stats['errors']}
-"""
-
- if stats['errors'] > 0:
- text += f"""
-
-⚠️ Внимание:
-Некоторые операции завершились с ошибками.
-Проверьте логи для получения подробной информации.
-"""
-
- text += f"""
-
-💡 Рекомендации:
-• Обратная синхронизация выполнена
-• Все пользователи из бота теперь в панели Remnawave
-• Подписки пользователей синхронизированы (если есть)
-"""
-
- keyboard = []
-
- if stats['errors'] > 0:
- keyboard.append([
- types.InlineKeyboardButton(
- text="🔄 Повторить синхронизацию",
- callback_data="sync_to_panel"
- )
- ])
-
- keyboard.extend([
- [
- types.InlineKeyboardButton(text="📊 Статистика системы", callback_data="admin_rw_system"),
- types.InlineKeyboardButton(text="🌐 Ноды", callback_data="admin_rw_nodes")
- ],
- [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_remnawave")]
- ])
-
- await callback.message.edit_text(
- text,
- reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard)
- )
- await callback.answer()
-
-
@admin_required
@error_handler
async def show_auto_sync_settings(
@@ -3214,7 +3120,6 @@ def register_handlers(dp: Dispatcher):
dp.callback_query.register(manage_node, F.data.startswith("node_restart_"))
dp.callback_query.register(restart_all_nodes, F.data == "admin_restart_all_nodes")
dp.callback_query.register(show_sync_options, F.data == "admin_rw_sync")
- dp.callback_query.register(sync_users_to_panel, F.data == "sync_to_panel")
dp.callback_query.register(show_auto_sync_settings, F.data == "admin_rw_auto_sync")
dp.callback_query.register(toggle_auto_sync_setting, F.data == "remnawave_auto_sync_toggle")
dp.callback_query.register(prompt_auto_sync_schedule, F.data == "remnawave_auto_sync_times")
diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py
index 3ed94d57..4c066533 100644
--- a/app/services/remnawave_service.py
+++ b/app/services/remnawave_service.py
@@ -147,12 +147,6 @@ class RemnaWaveService:
password=auth_params.get("password")
)
- def _gb_to_bytes(self, gb: int) -> int:
- """Конвертирует гигабайты в байты"""
- if gb == 0:
- return 0
- return gb * 1024 * 1024 * 1024
-
@property
def is_configured(self) -> bool:
return self._config_error is None
@@ -2506,132 +2500,5 @@ class RemnaWaveService:
"api_url": settings.REMNAWAVE_API_URL,
"attempts_used": attempts,
}
-
- async def sync_users_to_panel(self, db: AsyncSession, limit: Optional[int] = None) -> Dict[str, int]:
- """
- Синхронизирует пользователей бота в RemnaWave панель (бот → RemnaWave)
- """
- from datetime import datetime, timedelta
- from sqlalchemy import select
- from sqlalchemy.orm import selectinload
- from app.database.models import User, Subscription
- from app.external.remnawave_api import UserStatus, TrafficLimitStrategy
-
- stats = {"created": 0, "updated": 0, "errors": 0, "total_processed": 0}
-
- try:
- # Получаем всех пользователей с их подписками
- users_query = select(User).options(selectinload(User.subscription))
-
- if limit:
- users_query = users_query.limit(limit)
-
- result = await db.execute(users_query)
- users = result.scalars().unique().all()
-
- logger.info(f"🚀 Начинаем синхронизацию {len(users)} пользователей в RemnaWave")
-
- async with self.get_api_client() as api:
- for user in users:
- stats["total_processed"] += 1
-
- try:
- # Проверяем, есть ли уже пользователь в RemnaWave
- existing_users = await api.get_user_by_telegram_id(user.telegram_id)
-
- if existing_users:
- # Пользователь уже существует - обновляем его данные
- remnawave_user = existing_users[0]
- update_kwargs = {
- 'uuid': remnawave_user.uuid,
- 'description': settings.format_remnawave_user_description(
- full_name=user.full_name,
- username=user.username,
- telegram_id=user.telegram_id
- )
- }
-
- # Если у пользователя есть активная подписка, обновляем и её параметры
- if hasattr(user, 'subscription') and user.subscription:
- from app.services.subscription_service import get_traffic_reset_strategy
-
- update_kwargs.update({
- 'status': UserStatus.ACTIVE if user.subscription.end_date > datetime.utcnow() else UserStatus.DISABLED,
- 'expire_at': user.subscription.end_date,
- 'traffic_limit_bytes': self._gb_to_bytes(user.subscription.traffic_limit_gb),
- 'traffic_limit_strategy': get_traffic_reset_strategy(),
- 'active_internal_squads': user.subscription.connected_squads
- })
-
- # Устанавливаем лимит устройств
- from app.utils.subscription_utils import resolve_hwid_device_limit_for_payload
- hwid_limit = resolve_hwid_device_limit_for_payload(user.subscription)
- if hwid_limit is not None:
- update_kwargs['hwid_device_limit'] = hwid_limit
-
- await api.update_user(**update_kwargs)
- stats["updated"] += 1
-
- logger.info(f"🔄 Пользователь {user.telegram_id} обновлен в RemnaWave")
-
- else:
- # Создаем нового пользователя в RemnaWave
- username = settings.format_remnawave_username(
- full_name=user.full_name,
- username=user.username,
- telegram_id=user.telegram_id,
- )
-
- # Подготовим параметры для создания
- create_kwargs = {
- 'username': username,
- 'telegram_id': user.telegram_id,
- 'description': settings.format_remnawave_user_description(
- full_name=user.full_name,
- username=user.username,
- telegram_id=user.telegram_id
- )
- }
-
- # Если у пользователя есть активная подписка, используем её данные
- if hasattr(user, 'subscription') and user.subscription:
- from app.services.subscription_service import get_traffic_reset_strategy
-
- create_kwargs.update({
- 'status': UserStatus.ACTIVE if user.subscription.end_date > datetime.utcnow() else UserStatus.DISABLED,
- 'expire_at': user.subscription.end_date,
- 'traffic_limit_bytes': self._gb_to_bytes(user.subscription.traffic_limit_gb),
- 'traffic_limit_strategy': get_traffic_reset_strategy(),
- 'active_internal_squads': user.subscription.connected_squads
- })
-
- # Устанавливаем лимит устройств
- from app.utils.subscription_utils import resolve_hwid_device_limit_for_payload
- hwid_limit = resolve_hwid_device_limit_for_payload(user.subscription)
- if hwid_limit is not None:
- create_kwargs['hwid_device_limit'] = hwid_limit
- else:
- # Для пользователей без подписки - создаем с базовыми параметрами
- create_kwargs.update({
- 'status': UserStatus.DISABLED, # По умолчанию отключаем
- 'expire_at': datetime.utcnow() + timedelta(days=30), # 30 дней по умолчанию
- 'traffic_limit_bytes': 0, # 0 трафика по умолчанию
- 'traffic_limit_strategy': TrafficLimitStrategy.NO_RESET
- })
-
- await api.create_user(**create_kwargs)
- stats["created"] += 1
-
- logger.info(f"✅ Пользователь {user.telegram_id} создан в RemnaWave")
-
- except Exception as user_error:
- logger.error(f"❌ Ошибка синхронизации пользователя {user.telegram_id}: {user_error}")
- stats["errors"] += 1
-
- logger.info(f"✅ Синхронизация завершена. Создано: {stats['created']}, Обновлено: {stats['updated']}, Ошибок: {stats['errors']}")
- return stats
-
- except Exception as e:
- logger.error(f"❌ Ошибка синхронизации пользователей в RemnaWave: {e}")
- raise e
+
diff --git a/app/webapi/routes/remnawave.py b/app/webapi/routes/remnawave.py
index 7dd2d54e..421db6d3 100644
--- a/app/webapi/routes/remnawave.py
+++ b/app/webapi/routes/remnawave.py
@@ -428,13 +428,12 @@ async def sync_from_panel(
async def sync_to_panel(
_: Any = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
- limit: int = Query(None, ge=1, le=10000, description="Ограничение на количество пользователей для синхронизации")
) -> RemnaWaveGenericSyncResponse:
service = _get_service()
_ensure_service_configured(service)
- stats = await service.sync_users_to_panel(db, limit=limit)
- detail = f"Синхронизация в панель выполнена (лимит: {limit if limit else 'все'})"
+ stats = await service.sync_users_to_panel(db)
+ detail = "Синхронизация в панель выполнена"
return RemnaWaveGenericSyncResponse(success=True, detail=detail, data=stats)
diff --git a/docker-compose.yml b/docker-compose.yml
index 128831d1..0ad610be 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ services:
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- - remnawave-network
+ - bot_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-remnawave_user} -d ${POSTGRES_DB:-remnawave_bot}"]
interval: 30s
@@ -27,7 +27,7 @@ services:
volumes:
- redis_data:/data
networks:
- - remnawave-network
+ - bot_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
@@ -74,7 +74,7 @@ services:
ports:
- "${WEB_API_PORT:-8080}:8080"
networks:
- - remnawave-network
+ - bot_network
healthcheck:
test: ["CMD-SHELL", "python -c 'import requests; requests.get(\"http://localhost:8080/health\", timeout=5)' || exit 1"]
interval: 60s
@@ -89,7 +89,9 @@ volumes:
driver: local
networks:
- remnawave-network:
- name: remnawave-network
+ bot_network:
driver: bridge
- external: true
+ ipam:
+ config:
+ - subnet: 172.20.0.0/16
+ gateway: 172.20.0.1