diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 8b3649907..8030a06d4 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -467,6 +467,7 @@ export const EXAMPLES: Record = { "xver": 0 }, "InboundOption": { + "awgServer": null, "enable": true, "id": 1, "listen": "", @@ -646,6 +647,28 @@ export const EXAMPLES: Record = { "tlsVersion": "1.3", "x25519": true }, + "ServerSettings": { + "externalInterface": "", + "h1": "", + "h2": "", + "h3": "", + "h4": "", + "i1": "", + "jc": 0, + "jmax": 0, + "jmin": 0, + "mtu": 0, + "primaryDns": "", + "privateKey": "", + "publicKey": "", + "s1": 0, + "s2": 0, + "s3": 0, + "s4": 0, + "secondaryDns": "", + "subnetCidr": 0, + "subnetIp": "" + }, "Setting": { "id": 0, "key": "", diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 8ea931c04..510ee0a98 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -1784,7 +1784,8 @@ export const SCHEMAS: Record = { "mixed", "tunnel", "tun", - "mtproto" + "mtproto", + "amneziawg" ], "example": "vless", "type": "string" @@ -1927,6 +1928,15 @@ export const SCHEMAS: Record = { }, "InboundOption": { "properties": { + "awgServer": { + "allOf": [ + { + "$ref": "#/components/schemas/ServerSettings" + } + ], + "description": "AwgServer carries the full AmneziaWG server block (keys, subnet,\nobfuscation params) so the clients page can render a downloadable\nper-client .conf without a second round trip.", + "nullable": true + }, "enable": { "example": true, "type": "boolean" @@ -2763,6 +2773,92 @@ export const SCHEMAS: Record = { ], "type": "object" }, + "ServerSettings": { + "description": "ServerSettings is the \"server\" block of an AmneziaWG inbound's Settings\nJSON: the interface-level configuration shared by every client/peer. The\nlisten port is deliberately not duplicated here — it lives on the inbound\nrow itself (Inbound.Port), like every other protocol.", + "properties": { + "externalInterface": { + "description": "ExternalInterface is the host NIC PostUp/PostDown NAT rules attach to.\nEmpty means auto-detect.", + "type": "string" + }, + "h1": { + "type": "string" + }, + "h2": { + "type": "string" + }, + "h3": { + "type": "string" + }, + "h4": { + "type": "string" + }, + "i1": { + "type": "string" + }, + "jc": { + "description": "Obfuscation20's fields, repeated flat (not embedded) rather than\nnested under their own key: encoding/json would happily inline an\nembedded Obfuscation20 the same way, but the frontend's Go-\u003eZod/TS\ngenerator (tools/openapigen) does not — it emits a genuinely nested\n`obfuscation20` object, which would silently diverge from the real\nwire JSON. See Obfuscation() below for the manager-facing conversion.", + "type": "integer" + }, + "jmax": { + "type": "integer" + }, + "jmin": { + "type": "integer" + }, + "mtu": { + "type": "integer" + }, + "primaryDns": { + "description": "PrimaryDNS/SecondaryDNS seed the DNS line of downloadable client\nconfigs; the server's own interface never sets one (see BuildClientConfig).", + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "publicKey": { + "type": "string" + }, + "s1": { + "type": "integer" + }, + "s2": { + "type": "integer" + }, + "s3": { + "type": "integer" + }, + "s4": { + "type": "integer" + }, + "secondaryDns": { + "type": "string" + }, + "subnetCidr": { + "type": "integer" + }, + "subnetIp": { + "type": "string" + } + }, + "required": [ + "h1", + "h2", + "h3", + "h4", + "jc", + "jmax", + "jmin", + "privateKey", + "publicKey", + "s1", + "s2", + "s3", + "s4", + "subnetCidr", + "subnetIp" + ], + "type": "object" + }, "Setting": { "description": "Setting stores key-value configuration settings for the 3x-ui panel.", "properties": { diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index c93b746fc..9717ec53a 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -3,6 +3,7 @@ export type OnlineAPISupport = number; export type ProcessState = string; export type Protocol = string; export type SubLinkProvider = unknown; +export type ensureAction = number; export type staticEgressResolver = string; export type transportBits = number; @@ -448,6 +449,7 @@ export interface InboundFallback { } export interface InboundOption { + awgServer?: ServerSettings | null; enable: boolean; id: number; listen?: string; @@ -628,6 +630,29 @@ export interface RealityScanResult { x25519: boolean; } +export interface ServerSettings { + externalInterface?: string; + h1: string; + h2: string; + h3: string; + h4: string; + i1?: string; + jc: number; + jmax: number; + jmin: number; + mtu?: number; + primaryDns?: string; + privateKey: string; + publicKey: string; + s1: number; + s2: number; + s3: number; + s4: number; + secondaryDns?: string; + subnetCidr: number; + subnetIp: string; +} + export interface Setting { id: number; key: string; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index f54d0c5fe..35b602a85 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -12,6 +12,9 @@ export type Protocol = z.infer; export const SubLinkProviderSchema = z.unknown(); export type SubLinkProvider = z.infer; +export const ensureActionSchema = z.number().int(); +export type ensureAction = z.infer; + export const staticEgressResolverSchema = z.string(); export type staticEgressResolver = z.infer; @@ -440,7 +443,7 @@ export const InboundSchema = z.object({ nodeId: z.number().int().nullable().optional(), originNodeGuid: z.string().optional(), port: z.number().int().min(0).max(65535), - protocol: z.enum(['vmess', 'vless', 'trojan', 'shadowsocks', 'wireguard', 'hysteria', 'http', 'mixed', 'tunnel', 'tun', 'mtproto']), + protocol: z.enum(['vmess', 'vless', 'trojan', 'shadowsocks', 'wireguard', 'hysteria', 'http', 'mixed', 'tunnel', 'tun', 'mtproto', 'amneziawg']), remark: z.string(), settings: z.unknown(), shareAddr: z.string(), @@ -476,6 +479,7 @@ export const InboundFallbackSchema = z.object({ export type InboundFallback = z.infer; export const InboundOptionSchema = z.object({ + awgServer: z.lazy(() => ServerSettingsSchema).nullable().optional(), enable: z.boolean(), id: z.number().int(), listen: z.string().optional(), @@ -665,6 +669,30 @@ export const RealityScanResultSchema = z.object({ }); export type RealityScanResult = z.infer; +export const ServerSettingsSchema = z.object({ + externalInterface: z.string().optional(), + h1: z.string(), + h2: z.string(), + h3: z.string(), + h4: z.string(), + i1: z.string().optional(), + jc: z.number().int(), + jmax: z.number().int(), + jmin: z.number().int(), + mtu: z.number().int().optional(), + primaryDns: z.string().optional(), + privateKey: z.string(), + publicKey: z.string(), + s1: z.number().int(), + s2: z.number().int(), + s3: z.number().int(), + s4: z.number().int(), + secondaryDns: z.string().optional(), + subnetCidr: z.number().int(), + subnetIp: z.string(), +}); +export type ServerSettings = z.infer; + export const SettingSchema = z.object({ id: z.number().int(), key: z.string(), diff --git a/frontend/src/lib/xray/inbound-defaults.ts b/frontend/src/lib/xray/inbound-defaults.ts index 1ec9bf3bf..b0bf915a1 100644 --- a/frontend/src/lib/xray/inbound-defaults.ts +++ b/frontend/src/lib/xray/inbound-defaults.ts @@ -1,5 +1,6 @@ import { RandomUtil, Wireguard } from '@/utils'; +import type { AmneziawgInboundSettings } from '@/schemas/protocols/inbound/amneziawg'; import type { HttpInboundSettings } from '@/schemas/protocols/inbound/http'; import type { HysteriaClient, HysteriaInboundSettings } from '@/schemas/protocols/inbound/hysteria'; import type { MixedInboundSettings } from '@/schemas/protocols/inbound/mixed'; @@ -274,6 +275,43 @@ export function createDefaultWireguardInboundSettings( }; } +// AmneziaWG is multi-client, like WireGuard, and uses the same Curve25519 +// keypair format — Wireguard.generateKeypair() works unchanged. Unlike +// WireGuard's Xray-native inbound, the server's publicKey is a real +// persisted field here (the Go backend reads it directly rather than +// re-deriving it), so it's seeded alongside privateKey. The obfuscation +// parameters (jc/jmin/.../i1) use the same starting values the Go backend's +// own generator range-checks against; the user (or the backend's own +// defaulting on save) can randomize/edit them further — see +// internal/amneziawg.GenerateObfuscation20 on the Go side. +export function createDefaultAmneziawgInboundSettings(): AmneziawgInboundSettings { + const kp = Wireguard.generateKeypair(); + return { + server: { + privateKey: kp.privateKey, + publicKey: kp.publicKey, + subnetIp: '10.8.1.0', + subnetCidr: 24, + primaryDns: '8.8.8.8', + secondaryDns: '8.8.4.4', + externalInterface: '', + jc: 5, + jmin: 10, + jmax: 50, + s1: 30, + s2: 45, + s3: 10, + s4: 5, + h1: '', + h2: '', + h3: '', + h4: '', + i1: '', + }, + clients: [], + }; +} + // Protocol-aware dispatch over every inbound-settings factory. Mirrors // the legacy `Inbound.Settings.getSettings(protocol)` dispatcher, but // returns a plain Zod-parsable object instead of a class instance. @@ -290,7 +328,8 @@ export type AnyInboundSettings = | TunInboundSettings | TunnelInboundSettings | WireguardInboundSettings - | MtprotoInboundSettings; + | MtprotoInboundSettings + | AmneziawgInboundSettings; export function createDefaultInboundSettings(protocol: string): AnyInboundSettings | null { switch (protocol) { @@ -305,6 +344,7 @@ export function createDefaultInboundSettings(protocol: string): AnyInboundSettin case 'tun': return createDefaultTunInboundSettings(); case 'wireguard': return createDefaultWireguardInboundSettings(); case 'mtproto': return createDefaultMtprotoInboundSettings(); + case 'amneziawg': return createDefaultAmneziawgInboundSettings(); default: return null; } } diff --git a/frontend/src/lib/xray/inbound-form-adapter.ts b/frontend/src/lib/xray/inbound-form-adapter.ts index 4362655c3..1eb23a967 100644 --- a/frontend/src/lib/xray/inbound-form-adapter.ts +++ b/frontend/src/lib/xray/inbound-form-adapter.ts @@ -1,6 +1,7 @@ import type { InboundFormValues, ShareAddrStrategy, TrafficReset } from '@/schemas/forms/inbound-form'; import type { InboundSettings } from '@/schemas/protocols/inbound'; import { + AmneziawgClientSchema, HysteriaClientSchema, MtprotoClientSchema, ShadowsocksClientSchema, @@ -252,6 +253,7 @@ function clientSchemaForProtocol(protocol: string): z.ZodType | null { case 'hysteria': return HysteriaClientSchema; case 'wireguard': return WireguardClientSchema; case 'mtproto': return MtprotoClientSchema; + case 'amneziawg': return AmneziawgClientSchema; default: return null; } } diff --git a/frontend/src/lib/xray/protocol-capabilities.ts b/frontend/src/lib/xray/protocol-capabilities.ts index 9568ea111..7d8fb7a91 100644 --- a/frontend/src/lib/xray/protocol-capabilities.ts +++ b/frontend/src/lib/xray/protocol-capabilities.ts @@ -67,10 +67,11 @@ export function canEnableStream(values: { protocol: string }): boolean { return STREAM_PROTOCOLS.includes(values.protocol); } -// mtproto is served by an external mtg process, not Xray, so the Xray sniffing -// block does not apply to it. Every other inbound supports sniffing. +// mtproto and amneziawg are served by an external process/interface, not +// Xray, so the Xray sniffing block does not apply to either. Every other +// inbound supports sniffing. export function canEnableSniffing(values: { protocol: string }): boolean { - return values.protocol !== 'mtproto'; + return values.protocol !== 'mtproto' && values.protocol !== 'amneziawg'; } // Vision seed applies only when XTLS Vision (TCP/TLS) flow is selected diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx index b7673053d..11962a984 100644 --- a/frontend/src/pages/clients/ClientFormModal.tsx +++ b/frontend/src/pages/clients/ClientFormModal.tsx @@ -39,7 +39,7 @@ const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL); const VMESS_SECURITY_OPTIONS = ['auto', 'aes-128-gcm', 'chacha20-poly1305'] as const; const MULTI_CLIENT_PROTOCOLS = new Set([ - 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'wireguard', 'mtproto', + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'wireguard', 'mtproto', 'amneziawg', ]); const CLIENT_FORM_MODAL_Z_INDEX = 1000; @@ -306,6 +306,14 @@ export default function ClientFormModal({ return ids; }, [inbounds]); + const amneziawgIds = useMemo(() => { + const ids = new Set(); + for (const row of inbounds || []) { + if (row && row.protocol === 'amneziawg') ids.add(row.id); + } + return ids; + }, [inbounds]); + const mtprotoIds = useMemo(() => { const ids = new Set(); for (const row of inbounds || []) { @@ -357,6 +365,11 @@ export default function ClientFormModal({ [inboundIds, wireguardIds], ); + const showAmneziawg = useMemo( + () => (inboundIds || []).some((id) => amneziawgIds.has(id)), + [inboundIds, amneziawgIds], + ); + const showMtproto = useMemo( () => (inboundIds || []).some((id) => mtprotoIds.has(id)), [inboundIds, mtprotoIds], @@ -528,7 +541,11 @@ export default function ClientFormModal({ clientPayload.reverse = { tag: reverseTagValue }; } - if (showWireguard) { + if (showWireguard || showAmneziawg) { + // AmneziaWG peers are wire-identical to WireGuard peers (same + // privateKey/publicKey/preSharedKey/allowedIPs fields on model.Client), + // so both protocols share this one field set — see wgPrivateKey etc. + // below and the AmneziaWG-labeled variants of the same inputs. clientPayload.privateKey = values.wgPrivateKey; clientPayload.publicKey = values.wgPublicKey; if (values.wgPreSharedKey) { @@ -846,9 +863,11 @@ export default function ClientFormModal({ /> )} - {showWireguard && ( + {(showWireguard || showAmneziawg) && ( <> - + } onClick={regenerateWireguardKeys} /> - + - + - + )} diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx index fd2121e27..4bc80448e 100644 --- a/frontend/src/pages/clients/ClientQrModal.tsx +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -7,6 +7,7 @@ import { LinkTags, linkMetaText, parseLinkParts } from '@/lib/xray/link-label'; import { QrPanel } from '@/pages/inbounds/qr'; import type { ClientRecord, InboundOption } from '@/hooks/useClients'; import { buildWireguardClientConfig, findWireguardInbound, isWireguardClient } from './wireguardConfig'; +import { buildAmneziaWGClientConfig, findAmneziaWGInbound, isAmneziaWGClient } from './amneziawgConfig'; interface SubSettings { enable: boolean; @@ -59,7 +60,13 @@ export default function ClientQrModal({ return buildWireguardClientConfig(client, wgInbound, window.location.hostname, subSettings?.publicHost ?? ''); }, [client, wgInbound, subSettings?.publicHost]); - const hasAnything = !!subLink || !!subJsonLink || !!wgConfigText || links.length > 0; + const awgInbound = useMemo(() => findAmneziaWGInbound(client, inboundsById), [client, inboundsById]); + const awgConfigText = useMemo(() => { + if (!client || !awgInbound || !isAmneziaWGClient(client)) return ''; + return buildAmneziaWGClientConfig(client, awgInbound, window.location.hostname, subSettings?.publicHost ?? ''); + }, [client, awgInbound, subSettings?.publicHost]); + + const hasAnything = !!subLink || !!subJsonLink || !!wgConfigText || !!awgConfigText || links.length > 0; useEffect(() => { if (!open || !client?.subId) { @@ -135,8 +142,21 @@ export default function ClientQrModal({ ), }); } + if (awgConfigText) { + out.push({ + key: 'awg-config', + label: {t('pages.clients.amneziaWgConfig')}, + children: ( + + ), + }); + } return out; - }, [subLink, subJsonLink, wgConfigText, links, client?.email, t]); + }, [subLink, subJsonLink, wgConfigText, awgConfigText, links, client?.email, t]); useEffect(() => { if (!open) { diff --git a/frontend/src/pages/clients/amneziawgConfig.ts b/frontend/src/pages/clients/amneziawgConfig.ts new file mode 100644 index 000000000..d083494d8 --- /dev/null +++ b/frontend/src/pages/clients/amneziawgConfig.ts @@ -0,0 +1,73 @@ +import { formatInboundLabel } from '@/lib/inbounds/label'; +import { preferPublicHost, resolveShareHost } from '@/lib/xray/inbound-link'; +import type { ClientRecord, InboundOption } from '@/hooks/useClients'; + +// AmneziaWG clients are wire-identical to WireGuard clients (same +// privateKey/publicKey/allowedIPs/preSharedKey/keepAlive fields on +// model.Client — see wireguardConfig.ts's isWireguardClient), so this duck +// type can't tell the two protocols apart on its own; findAmneziaWGInbound's +// protocol==='amneziawg' filter below is what actually disambiguates. +export function isAmneziaWGClient(client: ClientRecord | null | undefined): boolean { + if (!client) return false; + return !!(client.privateKey || client.publicKey || client.allowedIPs || client.preSharedKey || client.keepAlive); +} + +export function findAmneziaWGInbound( + client: ClientRecord | null | undefined, + inboundsById: Record, +): InboundOption | undefined { + return (client?.inboundIds || []) + .map((id) => inboundsById[id]) + .find((ib) => ib?.protocol === 'amneziawg'); +} + +// h4Line renders one H magic-header line, matching the Go backend's +// hOrDefault fallback (blank -> the classic 1/2/3/4 WireGuard message type). +function hLine(key: string, value: string | undefined, fallback: string): string { + return `${key} = ${value && value.trim() !== '' ? value : fallback}`; +} + +export function buildAmneziaWGClientConfig( + client: ClientRecord, + inbound: InboundOption | undefined, + host = window.location.hostname, + publicHost = '', +): string { + const server = inbound?.awgServer; + const endpointHost = resolveShareHost(inbound ?? {}, inbound?.nodeAddress ?? '', preferPublicHost(host, publicHost)); + const address = client.allowedIPs || '10.8.1.2/32'; + const endpoint = `${endpointHost}:${inbound?.port || ''}`; + const inboundName = inbound ? formatInboundLabel(inbound.tag, inbound.remark) : ''; + const remark = [inboundName, client.email, client.comment].filter(Boolean).join(' - '); + + const dnsParts = [server?.primaryDns, server?.secondaryDns].filter((v) => !!v && v.trim() !== ''); + const lines = [ + '[Interface]', + `PrivateKey = ${client.privateKey || client.password || ''}`, + `Address = ${address}`, + ]; + if (dnsParts.length > 0) lines.push(`DNS = ${dnsParts.join(', ')}`); + if (server?.mtu && server.mtu > 0) lines.push(`MTU = ${server.mtu}`); + + // AmneziaWG obfuscation parameters — must match the server's values. + lines.push(`Jc = ${server?.jc ?? 5}`); + lines.push(`Jmin = ${server?.jmin ?? 10}`); + lines.push(`Jmax = ${server?.jmax ?? 50}`); + lines.push(`S1 = ${server?.s1 ?? 30}`); + lines.push(`S2 = ${server?.s2 ?? 45}`); + if (server?.s3) lines.push(`S3 = ${server.s3}`); + if (server?.s4) lines.push(`S4 = ${server.s4}`); + lines.push(hLine('H1', server?.h1, '1')); + lines.push(hLine('H2', server?.h2, '2')); + lines.push(hLine('H3', server?.h3, '3')); + lines.push(hLine('H4', server?.h4, '4')); + if (server?.i1) lines.push(`I1 = ${server.i1}`); + + lines.push(''); + if (remark) lines.push(`# ${remark}`); + lines.push('[Peer]', `PublicKey = ${server?.publicKey || ''}`); + if (client.preSharedKey) lines.push(`PresharedKey = ${client.preSharedKey}`); + lines.push('AllowedIPs = 0.0.0.0/0, ::/0', `Endpoint = ${endpoint}`); + if (client.keepAlive && client.keepAlive > 0) lines.push(`PersistentKeepalive = ${client.keepAlive}`); + return lines.join('\n'); +} diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index a7fecd507..17110cee4 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -57,6 +57,7 @@ import './InboundFormModal.css'; import { AdvancedAllEditor, AdvancedSliceEditor } from './advanced-editors'; import { formatInboundIssue, formatInboundValidation } from './formatValidationError'; import { + AmneziawgFields, HttpFields, HysteriaFields, MixedFields, @@ -306,6 +307,31 @@ export default function InboundFormModal({ setV('settings.secretKey', kp.privateKey); }; + // AmneziaWG uses the same Curve25519 keys as WireGuard, just nested under + // settings.server instead of flat on settings — see amneziawg.ts. 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, so it must be kept in + // sync even when the user free-types a new private key instead of using + // the regenerate button. + const awgPrivateKey = useWatch({ control, name: 'settings.server.privateKey' }); + const awgPubKey = typeof awgPrivateKey === 'string' && awgPrivateKey.length > 0 + ? Wireguard.generateKeypair(awgPrivateKey).publicKey + : ''; + + useEffect(() => { + if (protocol === Protocols.AMNEZIAWG) { + setV('settings.server.publicKey', awgPubKey); + } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [awgPubKey, protocol]); + + const regenInboundAwg = () => { + const kp = Wireguard.generateKeypair(); + setV('settings.server.privateKey', kp.privateKey); + setV('settings.server.publicKey', kp.publicKey); + }; + const matchesVlessAuth = ( block: { id?: string; label?: string } | undefined | null, authId: string, @@ -650,6 +676,8 @@ export default function InboundFormModal({ <> {protocol === Protocols.WIREGUARD && } + {protocol === Protocols.AMNEZIAWG && } + {protocol === Protocols.TUN && } {protocol === Protocols.TUNNEL && } @@ -952,6 +980,7 @@ export default function InboundFormModal({ Protocols.TUN, Protocols.WIREGUARD, Protocols.MTPROTO, + Protocols.AMNEZIAWG, ] as string[]).includes(protocol) || isFallbackHost ? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab, forceRender: true }] : []), diff --git a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx new file mode 100644 index 000000000..2c780a8c9 --- /dev/null +++ b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx @@ -0,0 +1,95 @@ +import { useTranslation } from 'react-i18next'; +import { Button, Form, Input, InputNumber, Space } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; + +import { FormField } from '@/components/form/rhf'; + +interface AmneziawgFieldsProps { + awgPubKey: string; + regenInboundAwg: () => void; +} + +export default function AmneziawgFields({ awgPubKey, regenInboundAwg }: AmneziawgFieldsProps) { + const { t } = useTranslation(); + return ( + <> + + + + + +