amneziawg.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { z } from 'zod';
  2. // AntD InputNumber emits null (not undefined) when the user clears it, and
  3. // the form store hands that null straight to safeParse on submit — a bare
  4. // .optional() would reject it and block the save.
  5. const optionalClearedInt = (schema: z.ZodNumber) =>
  6. z.preprocess((v) => (v == null ? undefined : v), schema.optional());
  7. // Same null-absorbing preprocess for fields that keep a schema default:
  8. // clearing the InputNumber refills the default instead of blocking the save.
  9. const clearedToDefault = <T extends z.ZodType>(schema: T) =>
  10. z.preprocess((v) => (v == null ? undefined : v), schema);
  11. // An AmneziaWG client (multi-client model). Same key/address fields as
  12. // WireguardClientSchema — the panel's generic ClientRecord already has those
  13. // exact keys (privateKey/publicKey/preSharedKey/allowedIPs/keepAlive), so
  14. // bulk operations, the QR modal and subscriptions all work unmodified — plus
  15. // one AmneziaWG-only addition, forwardedPorts (WireGuard's Xray-native
  16. // inbound has no host-level iptables layer to hang per-client DNAT off of).
  17. // Keys are optional on the wire — the backend generates them when absent.
  18. export const AmneziawgClientSchema = z.object({
  19. privateKey: z.string().optional(),
  20. publicKey: z.string().optional(),
  21. preSharedKey: z.string().optional(),
  22. allowedIPs: z.array(z.string()).default([]),
  23. keepAlive: optionalClearedInt(z.number().int().min(0)),
  24. forwardedPorts: z.string().default(''),
  25. email: z.string().min(1),
  26. limitIp: z.number().int().min(0).default(0),
  27. totalGB: z.number().int().min(0).default(0),
  28. expiryTime: z.number().int().default(0),
  29. enable: z.boolean().default(true),
  30. tgId: z
  31. .union([z.number(), z.string()])
  32. .transform((v) => Number(v) || 0)
  33. .default(0),
  34. subId: z.string().default(''),
  35. comment: z.string().default(''),
  36. reset: z.number().int().min(0).default(0),
  37. created_at: z.number().int().optional(),
  38. updated_at: z.number().int().optional(),
  39. });
  40. export type AmneziawgClient = z.infer<typeof AmneziawgClientSchema>;
  41. // Server-wide AmneziaWG 3.1 obfuscation parameters and tunnel identity,
  42. // mirroring internal/amneziawg.ServerSettings on the Go side exactly (same
  43. // field names) — the listen port is not duplicated here, it's the inbound's
  44. // own port like every other protocol. H1-H4 blank falls back to the classic
  45. // 1/2/3/4 magic header on save; blank optional fields omit their feature
  46. // from the rendered config.
  47. export const AmneziawgServerSchema = z.object({
  48. privateKey: z.string().optional(),
  49. publicKey: z.string().optional(),
  50. subnetIp: z.string().default('10.8.1.0'),
  51. subnetCidr: clearedToDefault(z.number().int().min(1).max(32).default(24)),
  52. mtu: optionalClearedInt(z.number().int().min(1)),
  53. primaryDns: z.string().default('8.8.8.8'),
  54. secondaryDns: z.string().default('8.8.4.4'),
  55. externalInterface: z.string().default(''),
  56. ipv6Enabled: z.boolean().default(false),
  57. ipv6Subnet: z.string().default(''),
  58. ipv6ExternalInterface: z.string().default(''),
  59. // routeThroughXray is vestigial on the Go side (see ServerSettings' own
  60. // doc comment) -- the embedded relay is always on, this field is read by
  61. // nothing. Kept here anyway, with no corresponding form control, purely so
  62. // z.object's default unknown-key stripping doesn't silently drop it from
  63. // an existing stored settings blob on the next save.
  64. routeThroughXray: z.boolean().default(false).optional(),
  65. // Upper bounds match amneziawg-go's own UAPI parsers (device/uapi.go):
  66. // jc/jmin/jmax are uint32, s1-s4 uint16. Wider values make IpcSet fail.
  67. jc: clearedToDefault(z.number().int().min(0).max(4294967295).default(5)),
  68. jmin: clearedToDefault(z.number().int().min(0).max(4294967295).default(10)),
  69. jmax: clearedToDefault(z.number().int().min(0).max(4294967295).default(50)),
  70. s1: clearedToDefault(z.number().int().min(0).max(65535).default(30)),
  71. s2: clearedToDefault(z.number().int().min(0).max(65535).default(45)),
  72. s3: clearedToDefault(z.number().int().min(0).max(64).default(10)),
  73. s4: clearedToDefault(z.number().int().min(0).max(32).default(5)),
  74. h1: z.string().default(''),
  75. h2: z.string().default(''),
  76. h3: z.string().default(''),
  77. h4: z.string().default(''),
  78. i1: z.string().default(''),
  79. i2: z.string().default(''),
  80. i3: z.string().default(''),
  81. i4: z.string().default(''),
  82. i5: z.string().default(''),
  83. headerProtectionKey: z.string().default(''),
  84. contentPaddingAddition: z.string().default(''),
  85. rekeyAfterTime: z.string().default(''),
  86. rekeyTimeout: z.string().default(''),
  87. rejectAfterTime: z.string().default(''),
  88. keepaliveTimeout: z.string().default(''),
  89. maxHandshakeAttempts: z.string().default(''),
  90. randomTrailers: z.boolean().default(false),
  91. disableCookies: z.boolean().default(false),
  92. });
  93. export type AmneziawgServer = z.infer<typeof AmneziawgServerSchema>;
  94. export const AmneziawgInboundSettingsSchema = z.object({
  95. server: AmneziawgServerSchema,
  96. clients: z.array(AmneziawgClientSchema).default([]),
  97. });
  98. export type AmneziawgInboundSettings = z.infer<typeof AmneziawgInboundSettingsSchema>;