mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-04 20:25:16 +03:00
feat(amneziawg): add frontend support and fix a Go->Zod generator gap
Wires the amneziawg protocol through the panel UI the same way every
other protocol is registered: a Zod settings schema (nested
{server, clients}, matching the Go JSON exactly), the protocol enum,
the inbound-form's per-protocol fields component and its
tab-visibility allowlist, the default-settings factory, the client
schema dispatcher, and the sniffing-capability exclusion (no Xray
inbound exists for amneziawg, same as mtproto).
Client key/allowedIPs fields are reused rather than duplicated: since
AmneziaWG clients are wire-identical to WireGuard clients (same
model.Client fields), ClientFormModal renders one shared field block
for both, switching only the visible label by which protocol is
active. The private-key input also gets a live public-key sync via a
new useEffect, because unlike WireGuard's Xray-native inbound (which
re-derives its public key at runtime and never stores one),
AmneziaWG's server.publicKey is a real persisted field the Go backend
reads directly — free-typing a new private key without this would
silently save a mismatched keypair.
Adds a downloadable per-client .conf (amneziawgConfig.ts, mirroring
wireguardConfig.ts) with the obfuscation lines, and an
InboundOption.AwgServer field on the Go side so the config builder
gets the full server block in one round trip.
Along the way, running tools/openapigen surfaced a real bug: it
doesn't flatten anonymously-embedded Go structs the way encoding/json
does, so ServerSettings embedding Obfuscation20 produced a Zod schema
with a nested `obfuscation20` key that never matches the real wire
JSON. Fixed by un-embedding (flat fields + an accessor method) and
registering internal/amneziawg in the generator's own package list,
which had been silently emitting a dangling schema reference.
English and Russian translations are complete; the other 10 locale
files still fall back to English for the new keys.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ export const ProtocolSchema = z.enum([
|
||||
'tunnel',
|
||||
'tun',
|
||||
'mtproto',
|
||||
'amneziawg',
|
||||
]);
|
||||
export type Protocol = z.infer<typeof ProtocolSchema>;
|
||||
|
||||
@@ -33,4 +34,5 @@ export const Protocols = Object.freeze({
|
||||
TUNNEL: 'tunnel',
|
||||
TUN: 'tun',
|
||||
MTPROTO: 'mtproto',
|
||||
AMNEZIAWG: 'amneziawg',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// AntD InputNumber emits null (not undefined) when the user clears it, and
|
||||
// the form store hands that null straight to safeParse on submit — a bare
|
||||
// .optional() would reject it and block the save.
|
||||
const optionalClearedInt = (schema: z.ZodNumber) =>
|
||||
z.preprocess((v) => (v == null ? undefined : v), schema.optional());
|
||||
|
||||
// An AmneziaWG client (multi-client model). Field-for-field identical to
|
||||
// WireguardClientSchema — the panel's generic ClientRecord already has these
|
||||
// exact keys (privateKey/publicKey/preSharedKey/allowedIPs/keepAlive), so
|
||||
// bulk operations, the QR modal and subscriptions all work unmodified. Keys
|
||||
// are optional on the wire — the backend generates them when absent.
|
||||
export const AmneziawgClientSchema = z.object({
|
||||
privateKey: z.string().optional(),
|
||||
publicKey: z.string().optional(),
|
||||
preSharedKey: z.string().optional(),
|
||||
allowedIPs: z.array(z.string()).default([]),
|
||||
keepAlive: optionalClearedInt(z.number().int().min(0)),
|
||||
email: z.string().min(1),
|
||||
limitIp: z.number().int().min(0).default(0),
|
||||
totalGB: z.number().int().min(0).default(0),
|
||||
expiryTime: z.number().int().default(0),
|
||||
enable: z.boolean().default(true),
|
||||
tgId: z.union([z.number(), z.string()]).transform((v) => Number(v) || 0).default(0),
|
||||
subId: z.string().default(''),
|
||||
comment: z.string().default(''),
|
||||
reset: z.number().int().min(0).default(0),
|
||||
created_at: z.number().int().optional(),
|
||||
updated_at: z.number().int().optional(),
|
||||
});
|
||||
export type AmneziawgClient = z.infer<typeof AmneziawgClientSchema>;
|
||||
|
||||
// Server-wide AmneziaWG 2.0 obfuscation parameters and tunnel identity,
|
||||
// mirroring internal/amneziawg.ServerSettings on the Go side exactly (same
|
||||
// field names) — the listen port is not duplicated here, it's the inbound's
|
||||
// own port like every other protocol. H1-H4 blank falls back to the classic
|
||||
// 1/2/3/4 magic header on save; I1 blank omits the 2.0-only CPS signature
|
||||
// packet (a 1.x-compatible config).
|
||||
export const AmneziawgServerSchema = z.object({
|
||||
privateKey: z.string().optional(),
|
||||
publicKey: z.string().optional(),
|
||||
subnetIp: z.string().default('10.8.1.0'),
|
||||
subnetCidr: z.number().int().min(1).max(32).default(24),
|
||||
mtu: optionalClearedInt(z.number().int().min(1)),
|
||||
primaryDns: z.string().default('8.8.8.8'),
|
||||
secondaryDns: z.string().default('8.8.4.4'),
|
||||
externalInterface: z.string().default(''),
|
||||
jc: z.number().int().min(0).default(5),
|
||||
jmin: z.number().int().min(0).default(10),
|
||||
jmax: z.number().int().min(0).default(50),
|
||||
s1: z.number().int().min(0).default(30),
|
||||
s2: z.number().int().min(0).default(45),
|
||||
s3: z.number().int().min(0).max(64).default(10),
|
||||
s4: z.number().int().min(0).max(32).default(5),
|
||||
h1: z.string().default(''),
|
||||
h2: z.string().default(''),
|
||||
h3: z.string().default(''),
|
||||
h4: z.string().default(''),
|
||||
i1: z.string().default(''),
|
||||
});
|
||||
export type AmneziawgServer = z.infer<typeof AmneziawgServerSchema>;
|
||||
|
||||
export const AmneziawgInboundSettingsSchema = z.object({
|
||||
server: AmneziawgServerSchema,
|
||||
clients: z.array(AmneziawgClientSchema).default([]),
|
||||
});
|
||||
export type AmneziawgInboundSettings = z.infer<typeof AmneziawgInboundSettingsSchema>;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AmneziawgInboundSettingsSchema } from './amneziawg';
|
||||
import { HttpInboundSettingsSchema } from './http';
|
||||
import { HysteriaInboundSettingsSchema } from './hysteria';
|
||||
import { MixedInboundSettingsSchema } from './mixed';
|
||||
@@ -12,6 +13,7 @@ import { VlessInboundSettingsSchema } from './vless';
|
||||
import { VmessInboundSettingsSchema } from './vmess';
|
||||
import { WireguardInboundSettingsSchema } from './wireguard';
|
||||
|
||||
export * from './amneziawg';
|
||||
export * from './http';
|
||||
export * from './hysteria';
|
||||
export * from './mixed';
|
||||
@@ -41,5 +43,6 @@ export const InboundSettingsSchema = z.discriminatedUnion('protocol', [
|
||||
z.object({ protocol: z.literal('tunnel'), settings: TunnelInboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('tun'), settings: TunInboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('mtproto'), settings: MtprotoInboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('amneziawg'), settings: AmneziawgInboundSettingsSchema }),
|
||||
]);
|
||||
export type InboundSettings = z.infer<typeof InboundSettingsSchema>;
|
||||
|
||||
Reference in New Issue
Block a user