diff --git a/app/core/hosts.py b/app/core/hosts.py index ca8bdd745..70b9c78ba 100644 --- a/app/core/hosts.py +++ b/app/core/hosts.py @@ -105,7 +105,7 @@ async def _prepare_subscription_inbound_data( transport_config=TCPTransportConfig(path="", host=[]), mux_settings=None, wireguard_public_key=inbound_config.get("public_key", ""), - wireguard_pre_shared_key=inbound_config.get("pre_shared_key", None), + wireguard_pre_shared_key=inbound_config.get("pre_shared_key") or "", wireguard_local_address=inbound_config.get("address", []) or [], wireguard_allowed_ips=allowed_ips, wireguard_keepalive=keepalive, diff --git a/app/core/xray.py b/app/core/xray.py index 40e76dddf..62bcb3874 100644 --- a/app/core/xray.py +++ b/app/core/xray.py @@ -9,7 +9,7 @@ from app.models.core import CoreType from app.models.protocol import ProxyProtocol -from app.utils.crypto import get_cert_SANs, get_x25519_public_key +from app.utils.crypto import get_cert_SANs, get_wireguard_public_key, get_x25519_public_key def _protocols_from_inbounds_by_tag(inbounds_by_tag: dict[str, dict]) -> frozenset[ProxyProtocol]: @@ -419,8 +419,31 @@ def _resolve_inbounds(self): self._read_inbound(inbound) self._protocols = _protocols_from_inbounds_by_tag(self._inbounds_by_tag) + def _read_wireguard_inbound(self, inbound: dict): + """Register a WireGuard-in-Xray inbound so its tag appears in /api/inbounds.""" + wg = inbound.get("settings", {}) + secret_key = wg.get("secretKey", "") + try: + public_key = get_wireguard_public_key(secret_key) if secret_key else "" + except Exception: + public_key = "" + settings = self._create_base_settings(inbound) + settings["listen_port"] = inbound.get("port") + settings["public_key"] = public_key + settings["address"] = wg.get("address", []) + settings["mtu"] = wg.get("mtu", 1420) + settings["network"] = "udp" + tag = inbound["tag"] + if tag not in self._inbounds: + self._inbounds.append(tag) + self._inbounds_by_tag[tag] = settings + def _read_inbound(self, inbound: dict): """Read an inbound and its settings.""" + if inbound["protocol"] == "wireguard": + self._read_wireguard_inbound(inbound) + return + if inbound["protocol"] not in ("vmess", "vless", "trojan", "shadowsocks", "hysteria"): return diff --git a/app/jobs/record_usages.py b/app/jobs/record_usages.py index 7a38e11d6..71e347fd1 100644 --- a/app/jobs/record_usages.py +++ b/app/jobs/record_usages.py @@ -488,6 +488,10 @@ def _process_users_stats_response(stats_response): Process stats response (CPU-bound operation) - runs in thread pool. Pure function designed for thread-safe execution. Returns tuple: (validated_params, invalid_uids) for logging outside thread. + + Every inbound (including WireGuard) reports stats keyed by the user's + email, which the panel sets to the plain `{id}`, so no translation is + needed here. """ params = defaultdict(int) for stat in filter(attrgetter("value"), stats_response.stats): @@ -498,7 +502,7 @@ def _process_users_stats_response(stats_response): for uid, value in params.items(): try: validated_params.append({"uid": int(uid), "value": value}) - except ValueError, TypeError: + except (ValueError, TypeError): invalid_uids.append(uid) return validated_params, invalid_uids diff --git a/dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx b/dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx index 703dc3ed6..5623b3d46 100644 --- a/dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx +++ b/dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx @@ -1237,9 +1237,10 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo let tag = watchedProtocol let network = watchedTransport if (watchedProtocol === 'shadowsocks') network = watchedShadowsocksNetwork + if (isWireguardInboundProtocol(watchedProtocol)) network = 'udp' if (network) tag += `${tagSeparator}${network}` - if (watchedSecurity && watchedSecurity !== 'none') tag += `${tagSeparator}${watchedSecurity}` + if (!isWireguardInboundProtocol(watchedProtocol) && watchedSecurity && watchedSecurity !== 'none') tag += `${tagSeparator}${watchedSecurity}` if (watchedPort) tag += `${tagSeparator}${watchedPort}` tag = tag.toUpperCase() @@ -1625,7 +1626,10 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo setDuplicateTagError(tagValue) return } - if (inbound.protocol !== 'unmanaged' && 'transport' in inbound && 'security' in inbound) { + if (isWireguardInboundProtocol(inbound.protocol)) { + const ok = await form.trigger(['protocol', 'tag', 'port', 'wgSecretKey'], { shouldFocus: true }) + if (!ok) return + } else if (inbound.protocol !== 'unmanaged' && 'transport' in inbound && 'security' in inbound) { const fields = ['protocol', 'tag', 'port', ...(form.getValues('security') === 'reality' ? realityInboundZodTriggerFieldNames() : [])] const ok = await form.trigger(fields, { shouldFocus: true }) if (!ok) return @@ -1635,9 +1639,6 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo } else if (inbound.protocol === 'tun') { const ok = await form.trigger(['protocol', 'tag', 'tunName', 'tunMtu'], { shouldFocus: true }) if (!ok) return - } else if (inbound.protocol === 'wireguard') { - const ok = await form.trigger(['protocol', 'tag', 'port', 'wgSecretKey'], { shouldFocus: true }) - if (!ok) return } if (!validateWireguardInboundForCommit(inbound)) return let nextInbound: Inbound = inbound @@ -2501,7 +2502,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
{t('coreEditor.inbound.unmanagedHint', { defaultValue: 'This inbound is managed as raw JSON in Advanced.' })}
)} - {inbound && inbound.protocol !== 'unmanaged' && 'transport' in inbound && 'security' in inbound && ( + {inbound && inbound.protocol !== 'unmanaged' && !isWireguardInboundProtocol(inbound.protocol) && 'transport' in inbound && 'security' in inbound && (