mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-09 21:00:58 +00:00
feat(wireguard): multi-client support
WireGuard inbounds now manage per-client peers using xray-core's native WireGuard users (AddUser/RemoveUser). Each client lives in settings.clients (canonical, like every other protocol) and is projected to peers[] only when emitting the xray config, at level 0 so the dispatcher's per-user traffic/online counters work with no extra plumbing. Backend: internal/util/wireguard gains KeyToHex (base64 to hex for the gRPC path), PublicKeyFromPrivate and GenerateWireguardPSK; xray/api.go builds a wireguard account in AddUser with hex keys (RemoveUser already worked); client CRUD generates a keypair and allocates a unique tunnel address per client and never rotates keys on edit; an idempotent migration converts legacy settings.peers into managed clients; WireGuard is included in the raw subscription. Frontend: WireGuard in the add-client modal with keys on the credential tab, client schema, per-client QR/link/.conf, inbound form reduced to server settings; i18n added across 13 locales. Fix: guard the settings[clients] assertion in add/update so a legacy WireGuard inbound stored without a clients key no longer panics.
This commit is contained in:
@@ -263,24 +263,20 @@ export interface WireguardInboundSeed {
|
||||
mtu?: number;
|
||||
secretKey?: string;
|
||||
noKernelTun?: boolean;
|
||||
peerPrivateKey?: string;
|
||||
}
|
||||
|
||||
// WireGuard is multi-client now: a new inbound holds only the server identity
|
||||
// (secretKey/mtu) and starts with no clients. Clients (peers) are added later
|
||||
// through the client modal, which generates each one's keypair and a unique
|
||||
// tunnel address. peers stays empty for backward-compatible parsing.
|
||||
export function createDefaultWireguardInboundSettings(
|
||||
seed: WireguardInboundSeed = {},
|
||||
): WireguardInboundSettings {
|
||||
const peerKp = seed.peerPrivateKey
|
||||
? { privateKey: seed.peerPrivateKey, publicKey: Wireguard.generateKeypair(seed.peerPrivateKey).publicKey }
|
||||
: Wireguard.generateKeypair();
|
||||
return {
|
||||
mtu: seed.mtu ?? 1420,
|
||||
secretKey: seed.secretKey ?? Wireguard.generateKeypair().privateKey,
|
||||
peers: [{
|
||||
privateKey: peerKp.privateKey,
|
||||
publicKey: peerKp.publicKey,
|
||||
allowedIPs: ['10.0.0.2/32'],
|
||||
keepAlive: 0,
|
||||
}],
|
||||
peers: [],
|
||||
clients: [],
|
||||
noKernelTun: seed.noKernelTun ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TrojanClientSchema,
|
||||
VlessClientSchema,
|
||||
VmessClientSchema,
|
||||
WireguardClientSchema,
|
||||
} from '@/schemas/protocols/inbound';
|
||||
import type { StreamSettings } from '@/schemas/api/inbound';
|
||||
import type { Sniffing } from '@/schemas/primitives';
|
||||
@@ -234,6 +235,7 @@ function clientSchemaForProtocol(protocol: string): z.ZodType | null {
|
||||
case 'trojan': return TrojanClientSchema;
|
||||
case 'shadowsocks': return ShadowsocksClientSchema;
|
||||
case 'hysteria': return HysteriaClientSchema;
|
||||
case 'wireguard': return WireguardClientSchema;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,14 +1126,30 @@ export interface GenWireguardFanoutInput {
|
||||
fallbackHostname: string;
|
||||
}
|
||||
|
||||
// WireGuard is multi-client: each client is one accepted peer. The canonical
|
||||
// store is settings.clients; legacy single-config inbounds (pre-migration) are
|
||||
// still rendered from settings.peers. Both carry the privateKey/allowedIPs/
|
||||
// preSharedKey/keepAlive the link and .conf need, so they project to the same
|
||||
// peer shape and reuse genWireguardLink/genWireguardConfig unchanged.
|
||||
function wgRenderPeers(settings: WireguardInboundSettings): WireguardInboundPeer[] {
|
||||
const clients = settings.clients ?? [];
|
||||
if (clients.length > 0) {
|
||||
return clients.map((c) => ({ ...c, publicKey: c.publicKey ?? '' }));
|
||||
}
|
||||
return settings.peers;
|
||||
}
|
||||
|
||||
export function genWireguardLinks(input: GenWireguardFanoutInput): string {
|
||||
const { inbound, remark = '', hostOverride = '', fallbackHostname } = input;
|
||||
if (inbound.protocol !== 'wireguard') return '';
|
||||
const addr = resolveAddr(inbound, hostOverride, fallbackHostname);
|
||||
const sep = '-';
|
||||
return inbound.settings.peers
|
||||
const baseSettings = inbound.settings as WireguardInboundSettings;
|
||||
const peers = wgRenderPeers(baseSettings);
|
||||
const settings: WireguardInboundSettings = { ...baseSettings, peers };
|
||||
return peers
|
||||
.map((p, i) => genWireguardLink({
|
||||
settings: inbound.settings as WireguardInboundSettings,
|
||||
settings,
|
||||
address: addr,
|
||||
port: inbound.port,
|
||||
remark: `${remark}${sep}${i + 1}${wgPeerCommentSuffix(p)}`,
|
||||
@@ -1147,9 +1163,12 @@ export function genWireguardConfigs(input: GenWireguardFanoutInput): string {
|
||||
if (inbound.protocol !== 'wireguard') return '';
|
||||
const addr = resolveAddr(inbound, hostOverride, fallbackHostname);
|
||||
const sep = '-';
|
||||
return inbound.settings.peers
|
||||
const baseSettings = inbound.settings as WireguardInboundSettings;
|
||||
const peers = wgRenderPeers(baseSettings);
|
||||
const settings: WireguardInboundSettings = { ...baseSettings, peers };
|
||||
return peers
|
||||
.map((p, i) => genWireguardConfig({
|
||||
settings: inbound.settings as WireguardInboundSettings,
|
||||
settings,
|
||||
address: addr,
|
||||
port: inbound.port,
|
||||
remark: `${remark}${sep}${i + 1}${wgPeerCommentSuffix(p)}`,
|
||||
|
||||
Reference in New Issue
Block a user