From 2f87fc05f20f4bfcbc3d06bb852b3f744cb7c2c6 Mon Sep 17 00:00:00 2001 From: x0sina Date: Wed, 6 May 2026 14:50:06 +0330 Subject: [PATCH 1/3] feat(settings): add wireguard_enabled flag to general settings and update subscription logic - Introduced `wireguard_enabled` boolean in the General settings model to control WireGuard functionality. - Updated subscription operations to check the `wireguard_enabled` flag before processing WireGuard requests. - Enhanced user interface to include a toggle for enabling/disabling WireGuard in the settings dashboard. - Adjusted localization files to support new WireGuard settings descriptions. --- app/models/settings.py | 1 + app/operation/subscription.py | 10 +- app/settings/__init__.py | 10 ++ app/subscription/share.py | 5 + app/utils/wireguard.py | 17 ++ dashboard/public/statics/locales/en.json | 4 + dashboard/public/statics/locales/fa.json | 4 + dashboard/public/statics/locales/ru.json | 4 + dashboard/public/statics/locales/zh.json | 4 + .../src/components/dialogs/user-modal.tsx | 163 +++++++++--------- .../src/pages/_dashboard.settings.general.tsx | 28 ++- dashboard/src/service/api/index.ts | 1 + tests/api/conftest.py | 2 +- tests/api/test_user.py | 58 ++++++- 14 files changed, 228 insertions(+), 83 deletions(-) diff --git a/app/models/settings.py b/app/models/settings.py index ba20305dd..f4ae77dd9 100644 --- a/app/models/settings.py +++ b/app/models/settings.py @@ -288,6 +288,7 @@ def validate_recommended_apps(cls, v: list[Application]) -> list[Application]: class General(BaseModel): default_flow: XTLSFlows = Field(default=XTLSFlows.NONE) default_method: ShadowsocksMethods = Field(default=ShadowsocksMethods.CHACHA20_POLY1305) + wireguard_enabled: bool = Field(default=True) class SettingsSchema(BaseModel): diff --git a/app/operation/subscription.py b/app/operation/subscription.py index c8c59e3b9..54c4d26b4 100644 --- a/app/operation/subscription.py +++ b/app/operation/subscription.py @@ -13,7 +13,7 @@ from app.models.settings import Application, ConfigFormat, SubRule, Subscription as SubSettings from app.models.stats import Period, UserUsageStatsList from app.models.user import SubscriptionUserResponse, UsersResponseWithInbounds -from app.settings import subscription_settings +from app.settings import general_settings, subscription_settings from app.subscription.share import encode_title, generate_subscription, setup_format_variables from app.templates import render_template from config import template_settings @@ -299,6 +299,8 @@ async def user_subscription( client_type = matched_rule.target if matched_rule else None if client_type == ConfigFormat.block or not client_type: await self.raise_error(message="Client not supported", code=406) + if client_type == ConfigFormat.wireguard and not (await general_settings()).wireguard_enabled: + await self.raise_error(message="Client not supported", code=406) # Update user subscription info await user_sub_update(db, db_user.id, user_agent) @@ -351,6 +353,9 @@ async def user_subscription_with_client_type( """Provides a subscription link based on the specified client type (e.g., Clash, V2Ray).""" sub_settings: SubSettings = await subscription_settings() + if client_type == ConfigFormat.wireguard and not (await general_settings()).wireguard_enabled: + await self.raise_error(message="Client not supported", code=406) + if client_type == ConfigFormat.block or not getattr(sub_settings.manual_sub_request, client_type): await self.raise_error(message="Client not supported", code=406) db_user = await self.get_validated_sub(db, token=token) @@ -374,6 +379,9 @@ async def user_subscription_by_user( client_type: ConfigFormat, request_url: str = "", ): + if client_type == ConfigFormat.wireguard and not (await general_settings()).wireguard_enabled: + await self.raise_error(message="Client not supported", code=406) + if client_type == ConfigFormat.block: await self.raise_error(message="Client not supported", code=406) diff --git a/app/settings/__init__.py b/app/settings/__init__.py index 88f284cd5..3bae33ad3 100644 --- a/app/settings/__init__.py +++ b/app/settings/__init__.py @@ -59,6 +59,15 @@ async def subscription_settings() -> settings.Subscription: return validated_settings +@cached() +async def general_settings() -> settings.General: + async with GetDB() as db: + db_settings = await get_settings(db) + + validated_settings = settings.General.model_validate(db_settings.general) + return validated_settings + + async def refresh_caches() -> None: await telegram_settings.cache.clear() await discord_settings.cache.clear() @@ -66,6 +75,7 @@ async def refresh_caches() -> None: await notification_settings.cache.clear() await notification_enable.cache.clear() await subscription_settings.cache.clear() + await general_settings.cache.clear() async def handle_settings_message(_: dict): diff --git a/app/subscription/share.py b/app/subscription/share.py index 96a88c381..dca2a3f83 100644 --- a/app/subscription/share.py +++ b/app/subscription/share.py @@ -11,6 +11,7 @@ from app.db.models import UserStatus from app.models.subscription import SubscriptionInboundData from app.models.user import UsersResponseWithInbounds +from app.settings import general_settings from app.subscription.client_templates import subscription_client_templates, subscription_xray_templates from app.utils.system import get_public_ip, get_public_ipv6, readable_size @@ -350,6 +351,7 @@ async def process_inbounds_and_tags( ) -> list | str | bytes: proxy_settings = user.proxy_settings.dict() proxy_settings["_user_id"] = user.id + wireguard_enabled = (await general_settings()).wireguard_enabled hosts = await filter_hosts(list((await host_manager.get_hosts()).values()), user.status) if randomize_order and len(hosts) > 1: random.shuffle(hosts) @@ -365,6 +367,9 @@ def _resolve_host_xray_template_content(inbound: SubscriptionInboundData) -> str return xray_template_overrides.get(template_id) for host_data in hosts: + if host_data.protocol == "wireguard" and not wireguard_enabled: + continue + result = await process_host(host_data, format_variables, user.inbounds, proxy_settings) if not result: continue diff --git a/app/utils/wireguard.py b/app/utils/wireguard.py index 51815252e..fa1ae5dc1 100644 --- a/app/utils/wireguard.py +++ b/app/utils/wireguard.py @@ -12,6 +12,7 @@ from app.db.models import CoreConfig, CoreType, User from app.models.proxy import ProxyTable from app.node.sync import sync_users +from app.settings import general_settings from app.utils.crypto import generate_wireguard_keypair, get_wireguard_public_key from app.utils.ip_pool import ( WireGuardPeerIPAllocator, @@ -111,6 +112,9 @@ async def prepare_wireguard_proxy_settings( exclude_user_id: int | None = None, ) -> ProxyTable: """Prepare WireGuard proxy settings with key generation and global pool IP allocation.""" + if not (await general_settings()).wireguard_enabled: + return proxy_settings + wireguard_tags = await get_wireguard_tags_from_groups(groups) if not wireguard_tags: return proxy_settings @@ -144,6 +148,9 @@ async def prepare_wireguard_keys_only( Used when peer_ips haven't changed during user modification. Avoids expensive database scans for unchanged peer networks. """ + if not (await general_settings()).wireguard_enabled: + return proxy_settings + wireguard_tags = await get_wireguard_tags_from_groups(groups) if not wireguard_tags: return proxy_settings @@ -189,6 +196,16 @@ async def bulk_reallocate_wireguard_peer_ips( ``target_users`` should be the users allowed by bulk scope (group/admin/user filters). """ + if not (await general_settings()).wireguard_enabled: + return { + "wireguard_inbound_tags": 0, + "candidates": 0, + "updated": 0, + "dry_run": dry_run, + "sample_usernames": [], + "affected_users": 0, + } + wg_tags = await get_wireguard_inbound_tags_from_db(db) if not wg_tags: return { diff --git a/dashboard/public/statics/locales/en.json b/dashboard/public/statics/locales/en.json index 25a715b5d..816becd5d 100644 --- a/dashboard/public/statics/locales/en.json +++ b/dashboard/public/statics/locales/en.json @@ -148,6 +148,10 @@ "title": "Default Shadowsocks Method", "description": "Autofill the encryption method for new Shadowsocks users" }, + "wireguardEnabled": { + "title": "Enable WireGuard", + "description": "Generate WireGuard peer settings and include WireGuard hosts in subscriptions" + }, "errorLoadingSettings": "Error loading settings" }, "notifications": { diff --git a/dashboard/public/statics/locales/fa.json b/dashboard/public/statics/locales/fa.json index b3fa7c6ac..2d398dc10 100644 --- a/dashboard/public/statics/locales/fa.json +++ b/dashboard/public/statics/locales/fa.json @@ -425,6 +425,10 @@ "title": "روش پیش‌فرض Shadowsocks", "description": "پر کردن خودکار روش رمزنگاری برای کاربران جدید Shadowsocks" }, + "wireguardEnabled": { + "title": "Enable WireGuard", + "description": "Generate WireGuard peer settings and include WireGuard hosts in subscriptions" + }, "errorLoadingSettings": "خطا در بارگذاری تنظیمات" } }, diff --git a/dashboard/public/statics/locales/ru.json b/dashboard/public/statics/locales/ru.json index b3975890a..3b5d7ed7f 100644 --- a/dashboard/public/statics/locales/ru.json +++ b/dashboard/public/statics/locales/ru.json @@ -557,6 +557,10 @@ "title": "Метод Shadowsocks по умолчанию", "description": "Автоматически заполнять метод шифрования для новых пользователей Shadowsocks" }, + "wireguardEnabled": { + "title": "Enable WireGuard", + "description": "Generate WireGuard peer settings and include WireGuard hosts in subscriptions" + }, "errorLoadingSettings": "Ошибка загрузки настроек" } }, diff --git a/dashboard/public/statics/locales/zh.json b/dashboard/public/statics/locales/zh.json index d9459bb42..99c8d9a89 100644 --- a/dashboard/public/statics/locales/zh.json +++ b/dashboard/public/statics/locales/zh.json @@ -569,6 +569,10 @@ "title": "默认 Shadowsocks 加密方式", "description": "为新 Shadowsocks 用户自动填写加密方式" }, + "wireguardEnabled": { + "title": "Enable WireGuard", + "description": "Generate WireGuard peer settings and include WireGuard hosts in subscriptions" + }, "errorLoadingSettings": "加载设置时出错" }, "errorLoadingSettings": "加载设置时出错" diff --git a/dashboard/src/components/dialogs/user-modal.tsx b/dashboard/src/components/dialogs/user-modal.tsx index 1219aab74..6700d95fd 100644 --- a/dashboard/src/components/dialogs/user-modal.tsx +++ b/dashboard/src/components/dialogs/user-modal.tsx @@ -500,6 +500,7 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI enabled: isDialogOpen, refetchOnMount: true, }) + const wireguardEnabled = generalSettings?.wireguard_enabled ?? true const syncUserCacheFromApiResponse = (user: UserResponse, options?: { allowInsert?: boolean; notifySuccessCallback?: boolean }) => { upsertUserInUsersCache(queryClient, user, { allowInsert: options?.allowInsert ?? false }) @@ -2151,85 +2152,89 @@ function UserModal({ isDialogOpen, onOpenChange, form, editingUser, editingUserI ) }} /> - ( - - {t('userDialog.proxySettings.wireguardPrivateKey', { defaultValue: 'WireGuard Private key' })} - -
- { - field.onChange(e) - syncWireGuardPublicKey(e.target.value) - form.trigger('proxy_settings.wireguard.private_key') - handleFieldChange('proxy_settings.wireguard.private_key', e.target.value) - }} - /> - -
-
- -
- )} - /> - ( - - {t('userDialog.proxySettings.wireguardPublicKey', { defaultValue: 'WireGuard Public key' })} - - - - - - )} - /> - ( - - {t('userDialog.proxySettings.wireguardPeerIps', { defaultValue: 'WireGuard Peer IPs' })} - -