Просмотр исходного кода

feat(xray): update xray-core to v26.9.8 and adapt panel

Bump xtls/xray-core to 37ceb8b4b6 (v26.9.8) and the three binary pins
(DockerInit.sh, release.yml Linux + Windows) in lockstep. No deleted
symbols; the impact is entirely on the JSON config surface.

Outbound "proxySettings" is now refused by the config loader (moved to
streamSettings.sockopt.dialerProxy) and a freedom outbound rejects
sockopt.addressPortStrategy. Either key in a stored template would keep
the core from starting after the upgrade, so a new OutboundRemovedKeysFix
seeder rewrites xrayTemplateConfig once: proxySettings.tag becomes
sockopt.dialerProxy (an existing dialerProxy wins) and addressPortStrategy
is dropped from freedom outbounds. Template saves and outbound
subscriptions already run through the vendored loader, so the new
refusals surface there with the core's own message.

REALITY no longer applies a built-in minClientVer (26.3.27) when the
field is empty. The form placeholder and the min/max hints in all 13
locales now say that empty means no minimum.

New upstream keys the Zod schemas would otherwise strip, with form
support where a sibling field already had it:
- blackhole response type "custom" with base64 customResponseData
- realm finalmask ipMode (dual/v4/v6) and portMapping (UPnP / NAT-PMP)
- quicParams brutalDisableLossCompensation, disableChromeParrot,
  disableGSO, disableStatelessReset
- hysteria masquerade proxy xForwarded
- wireguard outbound remoteDNS
- routing rule localOS

freedom.domainStrategy is only deprecated upstream (auto-migrated to
sockopt.domainStrategy with a warning) and is left untouched.
Sanaei 8 часов назад
Родитель
Сommit
2ec6c73613
36 измененных файлов с 554 добавлено и 61 удалено
  1. 2 2
      .github/workflows/release.yml
  2. 1 1
      DockerInit.sh
  3. 54 0
      frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx
  4. 21 4
      frontend/src/lib/xray/outbound-form-adapter.ts
  5. 7 0
      frontend/src/pages/inbounds/form/protocols/hysteria.tsx
  6. 1 1
      frontend/src/pages/inbounds/form/security/reality.tsx
  7. 24 10
      frontend/src/pages/xray/outbounds/protocols/blackhole.tsx
  8. 3 0
      frontend/src/pages/xray/outbounds/protocols/wireguard.tsx
  9. 7 0
      frontend/src/pages/xray/outbounds/transport/hysteria.tsx
  10. 2 0
      frontend/src/schemas/forms/outbound-form.ts
  11. 6 6
      frontend/src/schemas/protocols/outbound/blackhole.ts
  12. 4 0
      frontend/src/schemas/protocols/stream/finalmask.ts
  13. 1 0
      frontend/src/schemas/protocols/stream/hysteria.ts
  14. 1 0
      frontend/src/schemas/routing.ts
  15. 17 0
      frontend/src/test/__snapshots__/finalmask.test.ts.snap
  16. 11 0
      frontend/src/test/__snapshots__/rule.test.ts.snap
  17. 12 0
      frontend/src/test/golden/fixtures/finalmask/quic-params-flags.json
  18. 5 0
      frontend/src/test/golden/fixtures/rule/local-os.json
  19. 48 0
      frontend/src/test/outbound-form-adapter.test.ts
  20. 9 3
      go.mod
  21. 31 6
      go.sum
  22. 104 1
      internal/database/db.go
  23. 117 0
      internal/database/outbound_removed_keys_migration_test.go
  24. 5 2
      internal/web/translation/ar-EG.json
  25. 5 2
      internal/web/translation/en-US.json
  26. 5 2
      internal/web/translation/es-ES.json
  27. 5 2
      internal/web/translation/fa-IR.json
  28. 5 2
      internal/web/translation/id-ID.json
  29. 5 2
      internal/web/translation/ja-JP.json
  30. 5 2
      internal/web/translation/pt-BR.json
  31. 5 2
      internal/web/translation/ru-RU.json
  32. 6 3
      internal/web/translation/tr-TR.json
  33. 5 2
      internal/web/translation/uk-UA.json
  34. 5 2
      internal/web/translation/vi-VN.json
  35. 5 2
      internal/web/translation/zh-CN.json
  36. 5 2
      internal/web/translation/zh-TW.json

+ 2 - 2
.github/workflows/release.yml

@@ -124,7 +124,7 @@ jobs:
           cd x-ui/bin
 
           # Download dependencies
-          Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.7.28/"
+          Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.9.8/"
           if [ "${{ matrix.platform }}" == "amd64" ]; then
             fetch ${Xray_URL}Xray-linux-64.zip
             unzip Xray-linux-64.zip
@@ -287,7 +287,7 @@ jobs:
           cd x-ui\bin
 
           # Download Xray for Windows
-          $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.7.28/"
+          $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.9.8/"
           Invoke-WebRequest @retry -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
           Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
           Remove-Item "Xray-windows-64.zip"

+ 1 - 1
DockerInit.sh

@@ -32,7 +32,7 @@ if [ -z "$MTG_MULTI_VER" ]; then
 fi
 mkdir -p build/bin
 cd build/bin
-curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.7.28/Xray-linux-${ARCH}.zip"
+curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.9.8/Xray-linux-${ARCH}.zip"
 unzip "Xray-linux-${ARCH}.zip"
 rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
 mv xray "xray-linux-${FNAME}"

+ 54 - 0
frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx

@@ -994,6 +994,36 @@ function UdpMaskItem({
                     placeholder="host:port"
                   />
                 </Form.Item>
+                <Form.Item label="IP Mode" name={[fieldName, 'settings', 'ipMode']}>
+                  <Select
+                    allowClear
+                    placeholder="dual"
+                    options={[
+                      { value: 'dual', label: 'Dual' },
+                      { value: 'v4', label: 'IPv4' },
+                      { value: 'v6', label: 'IPv6' },
+                    ]}
+                  />
+                </Form.Item>
+                <Form.Item
+                  label="Port Mapping (UPnP / NAT-PMP)"
+                  name={[fieldName, 'settings', 'portMapping', 'enabled']}
+                  valuePropName="checked"
+                >
+                  <Switch />
+                </Form.Item>
+                <Form.Item
+                  label="Mapping Timeout (s)"
+                  name={[fieldName, 'settings', 'portMapping', 'timeout']}
+                >
+                  <InputNumber min={0} placeholder="10 = default" />
+                </Form.Item>
+                <Form.Item
+                  label="Mapping Lifetime (s)"
+                  name={[fieldName, 'settings', 'portMapping', 'lifetime']}
+                >
+                  <InputNumber min={0} placeholder="600 = default" />
+                </Form.Item>
                 <Divider plain style={{ margin: '8px 0' }}>
                   TLS (optional)
                 </Divider>
@@ -1443,6 +1473,13 @@ function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormI
           <Form.Item label="Brutal Down" name={[...base, 'brutalDown']}>
             <Input placeholder="e.g. 100 mbps" />
           </Form.Item>
+          <Form.Item
+            label="Brutal Disable Loss Comp"
+            name={[...base, 'brutalDisableLossCompensation']}
+            valuePropName="checked"
+          >
+            <Switch />
+          </Form.Item>
         </>
       )}
 
@@ -1478,6 +1515,23 @@ function QuicParamsForm({ base, form }: { base: (string | number)[]; form: FormI
       >
         <Switch />
       </Form.Item>
+      <Form.Item
+        label="Disable Chrome Parrot"
+        name={[...base, 'disableChromeParrot']}
+        valuePropName="checked"
+      >
+        <Switch />
+      </Form.Item>
+      <Form.Item label="Disable GSO" name={[...base, 'disableGSO']} valuePropName="checked">
+        <Switch />
+      </Form.Item>
+      <Form.Item
+        label="Disable Stateless Reset"
+        name={[...base, 'disableStatelessReset']}
+        valuePropName="checked"
+      >
+        <Switch />
+      </Form.Item>
 
       <Form.Item label="Max Incoming Streams" name={[...base, 'maxIncomingStreams']}>
         <InputNumber min={8} placeholder="1024 = default" />

+ 21 - 4
frontend/src/lib/xray/outbound-form-adapter.ts

@@ -6,6 +6,7 @@ import type { Sniffing, SniffingDest } from '@/schemas/primitives';
 import type { OutboundDomainStrategy } from '@/schemas/protocols/outbound';
 
 import type {
+  BlackholeOutboundFormSettings,
   DnsOutboundFormSettings,
   DnsRuleForm,
   FreedomFinalRuleForm,
@@ -243,6 +244,9 @@ function wireguardFromWire(raw: Raw): WireguardOutboundFormSettings {
       return (allowed.includes(s) ? s : '') as WireguardOutboundFormSettings['domainStrategy'];
     })(),
     reserved: reservedArr.join(','),
+    remoteDNS: asArray(raw.remoteDNS)
+      .map((x) => asString(x))
+      .join(','),
     peers,
     noKernelTun: asBool(raw.noKernelTun),
   };
@@ -322,10 +326,13 @@ function freedomFromWire(raw: Raw): FreedomOutboundFormSettings {
   };
 }
 
-function blackholeFromWire(raw: Raw) {
+function blackholeFromWire(raw: Raw): BlackholeOutboundFormSettings {
   const response = asObject(raw.response);
   const t = asString(response.type);
-  return { type: (t === 'none' || t === 'http' ? t : '') as '' | 'none' | 'http' };
+  return {
+    type: t === 'none' || t === 'http' || t === 'custom' ? t : '',
+    customResponseData: asString(response.customResponseData),
+  };
 }
 
 function dnsRuleFromWire(raw: unknown): DnsRuleForm {
@@ -585,6 +592,12 @@ function wireguardToWire(s: WireguardOutboundFormSettings) {
           .map((x) => Number(x.trim()))
           .filter((n) => Number.isFinite(n))
       : undefined,
+    remoteDNS: s.remoteDNS
+      ? s.remoteDNS
+          .split(',')
+          .map((x) => x.trim())
+          .filter(Boolean)
+      : undefined,
     peers: s.peers.map((p) => ({
       publicKey: p.publicKey,
       preSharedKey: p.psk.length > 0 ? p.psk : undefined,
@@ -631,8 +644,12 @@ function freedomToWire(s: FreedomOutboundFormSettings) {
   };
 }
 
-function blackholeToWire(s: { type: '' | 'none' | 'http' }) {
-  return { response: s.type ? { type: s.type } : undefined };
+function blackholeToWire(s: BlackholeOutboundFormSettings) {
+  if (!s.type) return { response: undefined };
+  if (s.type === 'custom') {
+    return { response: { type: s.type, customResponseData: s.customResponseData } };
+  }
+  return { response: { type: s.type } };
 }
 
 function dnsRuleToWire(r: DnsRuleForm) {

+ 7 - 0
frontend/src/pages/inbounds/form/protocols/hysteria.tsx

@@ -78,6 +78,13 @@ export default function HysteriaFields() {
               >
                 <Switch />
               </FormField>
+              <FormField
+                label={t('pages.inbounds.form.xForwarded')}
+                name={[...MASQ_PATH, 'xForwarded']}
+                valueProp="checked"
+              >
+                <Switch />
+              </FormField>
               <FormField
                 label={t('pages.inbounds.form.skipTlsVerify')}
                 name={[...MASQ_PATH, 'insecure']}

+ 1 - 1
frontend/src/pages/inbounds/form/security/reality.tsx

@@ -189,7 +189,7 @@ export default function RealityForm({
           },
         }}
       >
-        <Input placeholder="26.3.27" />
+        <Input placeholder="x.y.z" />
       </FormField>
       <FormField
         name={['streamSettings', 'realitySettings', 'maxClientVer']}

+ 24 - 10
frontend/src/pages/xray/outbounds/protocols/blackhole.tsx

@@ -1,19 +1,33 @@
 import { useTranslation } from 'react-i18next';
-import { Select } from 'antd';
+import { Input, Select } from 'antd';
+import { useFormContext, useWatch } from 'react-hook-form';
 
 import { FormField } from '@/components/form/rhf';
 
 export default function BlackholeFields() {
   const { t } = useTranslation();
+  const { control } = useFormContext();
+  const type = useWatch({ control, name: 'settings.type' }) as string | undefined;
   return (
-    <FormField label={t('pages.xray.outboundForm.responseType')} name={['settings', 'type']}>
-      <Select
-        options={[
-          { value: '', label: '(empty)' },
-          { value: 'none', label: 'none' },
-          { value: 'http', label: 'http' },
-        ]}
-      />
-    </FormField>
+    <>
+      <FormField label={t('pages.xray.outboundForm.responseType')} name={['settings', 'type']}>
+        <Select
+          options={[
+            { value: '', label: '(empty)' },
+            { value: 'none', label: 'none' },
+            { value: 'http', label: 'http' },
+            { value: 'custom', label: 'custom' },
+          ]}
+        />
+      </FormField>
+      {type === 'custom' && (
+        <FormField
+          label={t('pages.xray.outboundForm.customResponseData')}
+          name={['settings', 'customResponseData']}
+        >
+          <Input.TextArea rows={3} placeholder="SFRUUC8xLjEgNDAzIEZvcmJpZGRlbg0KDQo=" />
+        </FormField>
+      )}
+    </>
   );
 }

+ 3 - 0
frontend/src/pages/xray/outbounds/protocols/wireguard.tsx

@@ -99,6 +99,9 @@ export default function WireguardFields() {
       <FormField label={t('pages.xray.outboundForm.reserved')} name={['settings', 'reserved']}>
         <Input placeholder="comma-separated bytes, e.g. 1,2,3" />
       </FormField>
+      <FormField label={t('pages.xray.outboundForm.remoteDNS')} name={['settings', 'remoteDNS']}>
+        <Input placeholder="comma-separated, e.g. 1.1.1.1,2606:4700:4700::1111" />
+      </FormField>
       <Form.Item label={t('pages.inbounds.form.peers')}>
         <Button
           size="small"

+ 7 - 0
frontend/src/pages/xray/outbounds/transport/hysteria.tsx

@@ -79,6 +79,13 @@ export default function HysteriaForm() {
               >
                 <Switch />
               </FormField>
+              <FormField
+                label={t('pages.inbounds.form.xForwarded')}
+                name={[...MASQ, 'xForwarded']}
+                valueProp="checked"
+              >
+                <Switch />
+              </FormField>
               <FormField
                 label={t('pages.inbounds.form.skipTlsVerify')}
                 name={[...MASQ, 'insecure']}

+ 2 - 0
frontend/src/schemas/forms/outbound-form.ts

@@ -106,6 +106,7 @@ export const WireguardOutboundFormSettingsSchema = z.object({
   address: z.string().default(''),
   domainStrategy: z.union([WireguardDomainStrategySchema, z.literal('')]).default(''),
   reserved: z.string().default(''),
+  remoteDNS: z.string().default(''),
   peers: z.array(WireguardOutboundFormPeerSchema).default([]),
   noKernelTun: z.boolean().default(false),
 });
@@ -152,6 +153,7 @@ export type FreedomOutboundFormSettings = z.infer<typeof FreedomOutboundFormSett
 // adapter wraps as { response: { type } } on the wire and omits when empty.
 export const BlackholeOutboundFormSettingsSchema = z.object({
   type: z.union([BlackholeResponseTypeSchema, z.literal('')]).default(''),
+  customResponseData: z.string().default(''),
 });
 export type BlackholeOutboundFormSettings = z.infer<typeof BlackholeOutboundFormSettingsSchema>;
 

+ 6 - 6
frontend/src/schemas/protocols/outbound/blackhole.ts

@@ -1,13 +1,13 @@
 import { z } from 'zod';
 
-export const BlackholeResponseTypeSchema = z.enum(['none', 'http']);
+export const BlackholeResponseTypeSchema = z.enum(['none', 'http', 'custom']);
 export type BlackholeResponseType = z.infer<typeof BlackholeResponseTypeSchema>;
 
-// Blackhole drops traffic. `response.type` is the only knob — when set, Xray
-// returns the canned 403 HTTP response before closing; when omitted it
-// silently drops. The panel stores it as { response: { type } } or omits the
-// whole `response` key when type is empty.
+// `response.type` picks Xray's reply before closing: none (silent), http
+// (canned 403) or custom (base64 customResponseData). Omitted when empty.
 export const BlackholeOutboundSettingsSchema = z.object({
-  response: z.object({ type: BlackholeResponseTypeSchema }).optional(),
+  response: z
+    .object({ type: BlackholeResponseTypeSchema, customResponseData: z.string().optional() })
+    .optional(),
 });
 export type BlackholeOutboundSettings = z.infer<typeof BlackholeOutboundSettingsSchema>;

+ 4 - 0
frontend/src/schemas/protocols/stream/finalmask.ts

@@ -61,6 +61,7 @@ export const QuicParamsSchema = z.object({
   debug: z.boolean().optional(),
   brutalUp: z.string().optional(),
   brutalDown: z.string().optional(),
+  brutalDisableLossCompensation: z.boolean().optional(),
   udpHop: QuicUdpHopSchema.optional(),
   initStreamReceiveWindow: z.number().int().min(0).optional(),
   maxStreamReceiveWindow: z.number().int().min(0).optional(),
@@ -69,7 +70,10 @@ export const QuicParamsSchema = z.object({
   maxIdleTimeout: z.number().int().min(4).max(120).optional(),
   keepAlivePeriod: z.number().int().min(2).max(60).optional(),
   disablePathMTUDiscovery: z.boolean().optional(),
+  disableChromeParrot: z.boolean().optional(),
+  disableGSO: z.boolean().optional(),
   maxIncomingStreams: z.number().int().min(8).optional(),
+  disableStatelessReset: z.boolean().optional(),
 });
 export type QuicParams = z.infer<typeof QuicParamsSchema>;
 

+ 1 - 0
frontend/src/schemas/protocols/stream/hysteria.ts

@@ -15,6 +15,7 @@ export const HysteriaMasqueradeSchema = z.object({
   dir: z.string().default(''),
   url: z.string().default(''),
   rewriteHost: z.boolean().default(false),
+  xForwarded: z.boolean().optional(),
   insecure: z.boolean().default(false),
   content: z.string().default(''),
   headers: z.record(z.string(), z.string()).default({}),

+ 1 - 0
frontend/src/schemas/routing.ts

@@ -30,6 +30,7 @@ export const RuleObjectSchema = z.object({
   protocol: z.array(z.string()).optional(),
   attrs: z.record(z.string(), z.string()).optional(),
   process: z.array(z.string()).optional(),
+  localOS: z.array(z.string()).optional(),
   outboundTag: z.string().optional(),
   balancerTag: z.string().optional(),
   ruleTag: z.string().optional(),

+ 17 - 0
frontend/src/test/__snapshots__/finalmask.test.ts.snap

@@ -62,6 +62,23 @@ exports[`FinalMaskStreamSettingsSchema fixtures > parses quic-params byte-stably
 }
 `;
 
+exports[`FinalMaskStreamSettingsSchema fixtures > parses quic-params-flags byte-stably 1`] = `
+{
+  "quicParams": {
+    "brutalDisableLossCompensation": true,
+    "brutalDown": "100 mbps",
+    "brutalUp": "60 mbps",
+    "congestion": "brutal",
+    "disableChromeParrot": true,
+    "disableGSO": true,
+    "disablePathMTUDiscovery": false,
+    "disableStatelessReset": true,
+  },
+  "tcp": [],
+  "udp": [],
+}
+`;
+
 exports[`FinalMaskStreamSettingsSchema fixtures > parses realm-tls byte-stably 1`] = `
 {
   "tcp": [],

+ 11 - 0
frontend/src/test/__snapshots__/rule.test.ts.snap

@@ -74,6 +74,17 @@ exports[`RuleObjectSchema fixtures > parses full byte-stably 1`] = `
 }
 `;
 
+exports[`RuleObjectSchema fixtures > parses local-os byte-stably 1`] = `
+{
+  "localOS": [
+    "linux",
+    "darwin",
+  ],
+  "outboundTag": "direct",
+  "type": "field",
+}
+`;
+
 exports[`RuleObjectSchema fixtures > parses minimal byte-stably 1`] = `
 {
   "outboundTag": "direct",

+ 12 - 0
frontend/src/test/golden/fixtures/finalmask/quic-params-flags.json

@@ -0,0 +1,12 @@
+{
+  "quicParams": {
+    "congestion": "brutal",
+    "brutalUp": "60 mbps",
+    "brutalDown": "100 mbps",
+    "brutalDisableLossCompensation": true,
+    "disablePathMTUDiscovery": false,
+    "disableChromeParrot": true,
+    "disableGSO": true,
+    "disableStatelessReset": true
+  }
+}

+ 5 - 0
frontend/src/test/golden/fixtures/rule/local-os.json

@@ -0,0 +1,5 @@
+{
+  "type": "field",
+  "localOS": ["linux", "darwin"],
+  "outboundTag": "direct"
+}

+ 48 - 0
frontend/src/test/outbound-form-adapter.test.ts

@@ -264,6 +264,54 @@ describe('outbound-form-adapter: round-trip', () => {
     expect(withType.settings).toEqual({ response: { type: 'http' } });
   });
 
+  it('blackhole carries customResponseData only for the custom response type', () => {
+    const custom = formValuesToWirePayload(
+      rawOutboundToFormValues({
+        protocol: 'blackhole',
+        settings: { response: { type: 'custom', customResponseData: 'SFRUUC8xLjEgNDAz' } },
+      }),
+    );
+    expect(custom.settings).toEqual({
+      response: { type: 'custom', customResponseData: 'SFRUUC8xLjEgNDAz' },
+    });
+
+    const http = formValuesToWirePayload(
+      rawOutboundToFormValues({
+        protocol: 'blackhole',
+        settings: { response: { type: 'http', customResponseData: 'ignored' } },
+      }),
+    );
+    expect(http.settings).toEqual({ response: { type: 'http' } });
+  });
+
+  it('wireguard csv-joins remoteDNS on read and splits it on write', () => {
+    const wire = {
+      protocol: 'wireguard',
+      settings: {
+        secretKey: 'YFVmTVCBsLxXJCe4i+jK8PgD3S6vUqfZ4Zl0JVNDfHA=',
+        remoteDNS: ['1.1.1.1', '2606:4700:4700::1111'],
+        peers: [{ publicKey: 'pk', endpoint: 'wg.example.com:51820' }],
+      },
+    };
+    const form = rawOutboundToFormValues(wire);
+    if (form.protocol === 'wireguard') {
+      expect(form.settings.remoteDNS).toBe('1.1.1.1,2606:4700:4700::1111');
+    }
+    const back = formValuesToWirePayload(form);
+    expect((back.settings as { remoteDNS?: string[] }).remoteDNS).toEqual([
+      '1.1.1.1',
+      '2606:4700:4700::1111',
+    ]);
+
+    const unset = formValuesToWirePayload(
+      rawOutboundToFormValues({
+        protocol: 'wireguard',
+        settings: { secretKey: wire.settings.secretKey, peers: wire.settings.peers },
+      }),
+    );
+    expect((unset.settings as { remoteDNS?: string[] }).remoteDNS).toBeUndefined();
+  });
+
   it('dns rules normalize qType numeric strings, split domains, carry rCode', () => {
     const wire = {
       protocol: 'dns',

+ 9 - 3
go.mod

@@ -25,7 +25,7 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/valyala/fasthttp v1.73.0
 	github.com/xlzd/gotp v0.1.0
-	github.com/xtls/xray-core v1.260327.1-0.20260728075948-5ca6f4b7d4dc
+	github.com/xtls/xray-core v1.260327.1-0.20260908094724-37ceb8b4b65e
 	go.uber.org/atomic v1.11.0
 	golang.org/x/crypto v0.56.0
 	golang.org/x/net v0.58.0
@@ -44,7 +44,7 @@ require (
 require (
 	github.com/Azure/go-ntlmssp v0.1.1 // indirect
 	github.com/andybalholm/brotli v1.2.3 // indirect
-	github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 // indirect
+	github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e // indirect
 	github.com/bytedance/gopkg v0.1.4 // indirect
 	github.com/bytedance/sonic v1.15.3 // indirect
 	github.com/bytedance/sonic/loader v0.5.2 // indirect
@@ -58,20 +58,26 @@ require (
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gopacket v1.1.19 // indirect
 	github.com/gorilla/context v1.1.2 // indirect
 	github.com/gorilla/securecookie v1.1.2 // indirect
 	github.com/gorilla/sessions v1.4.0 // indirect
 	github.com/grbit/go-json v0.11.0 // indirect
+	github.com/huin/goupnp v1.2.0 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 	github.com/jackc/pgx/v5 v5.10.0 // indirect
 	github.com/jackc/puddle/v2 v2.2.2 // indirect
+	github.com/jackpal/go-nat-pmp v1.0.2 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/juju/ratelimit v1.0.2 // indirect
 	github.com/klauspost/cpuid/v2 v2.4.0 // indirect
+	github.com/koron/go-ssdp v0.0.4 // indirect
 	github.com/leodido/go-urn v1.5.0 // indirect
+	github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 // indirect
+	github.com/libp2p/go-netroute v0.2.1 // indirect
 	github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 // indirect
 	github.com/mattn/go-isatty v0.0.24 // indirect
 	github.com/miekg/dns v1.1.73 // indirect
@@ -98,7 +104,7 @@ require (
 	github.com/vishvananda/netlink v1.3.1 // indirect
 	github.com/vishvananda/netns v0.0.5 // indirect
 	github.com/wlynxg/anet v0.0.5 // indirect
-	github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f // indirect
+	github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 // indirect
 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 	go.mongodb.org/mongo-driver/v2 v2.8.2 // indirect
 	go4.org/netipx v0.0.0-20260823151212-3075585bcbeb // indirect

+ 31 - 6
go.sum

@@ -8,8 +8,8 @@ github.com/amnezia-vpn/amneziawg-go/v3 v3.1.20260828 h1:D8d8gGvwXcTxUIsE4z6F6vjy
 github.com/amnezia-vpn/amneziawg-go/v3 v3.1.20260828/go.mod h1:YoPc6qcOZqD7TXZ1xpedD8Sx3aSKsxN05ZqEFmXDNHk=
 github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA5NQ=
 github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
-github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkdYbw5JPGwBqIHs2J8tiEP7Py9lPqkN2I=
-github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
+github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
+github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
 github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
 github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
 github.com/bytedance/sonic v1.15.3 h1:P3akjLPBtV/i6bHC6LbcLjY3KuoOvfiqF8wFHeP5IhY=
@@ -73,6 +73,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
@@ -87,6 +89,8 @@ github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
 github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
 github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
 github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
+github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -95,6 +99,8 @@ github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
 github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
 github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
 github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
 github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
 github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
 github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -121,12 +127,18 @@ github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR
 github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
 github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
 github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
+github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
+github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
 github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
+github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 h1:YohuNPT/1k3VcThCQlBZ43PCPWPfMRS1zcxWBF2SLK8=
+github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8=
+github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
+github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
 github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 h1:eveIIGn4BGM3qknO74omf6HYr30/exH+eVUTuAgwjZ0=
 github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
 github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
@@ -217,10 +229,10 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
 github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
 github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
 github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
-github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
-github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
-github.com/xtls/xray-core v1.260327.1-0.20260728075948-5ca6f4b7d4dc h1:fkOkmgHWbF2Q8MdV9VxrsyxRz4OndcrUXUkh1ANBTg0=
-github.com/xtls/xray-core v1.260327.1-0.20260728075948-5ca6f4b7d4dc/go.mod h1:wukQoBGnQ6GaLTGuKwv8rCTgf80QxPj+6iznDZHQEWo=
+github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0 h1:rb+fKQFhz+5I2PPuQsNYxI5mUU840XWYtRF0ZBjvkws=
+github.com/xtls/reality v0.0.0-20260908062103-8cdf7bf9c7f0/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
+github.com/xtls/xray-core v1.260327.1-0.20260908094724-37ceb8b4b65e h1:yQS0pPlOBi8y6bHNXbqx9AYito2pAXgItwKK7Waqaw8=
+github.com/xtls/xray-core v1.260327.1-0.20260908094724-37ceb8b4b65e/go.mod h1:G9OqFEPNkwNoxywvEkdBncyfeytCzC31CBazgy3d9ic=
 github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -249,14 +261,24 @@ go4.org/netipx v0.0.0-20260823151212-3075585bcbeb h1:XBM4hvfwGAttkkiTIFfeigdfcL1
 go4.org/netipx v0.0.0-20260823151212-3075585bcbeb/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
 golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU=
 golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
 golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
 golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk=
 golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
 golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
 golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -264,10 +286,13 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
 golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
 golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
 golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
 golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
 golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7ikgyX57D+XvtcSnGylsLkOVhta/w=

+ 104 - 1
internal/database/db.go

@@ -1193,7 +1193,7 @@ func runSeeders(isUsersEmpty bool) error {
 	}
 
 	if empty && isUsersEmpty {
-		seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
+		seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "OutboundRemovedKeysFix", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
 		for _, name := range seeders {
 			if err := db.Create(&model.HistoryOfSeeders{SeederName: name}).Error; err != nil {
 				return err
@@ -1298,6 +1298,12 @@ func runSeeders(isUsersEmpty bool) error {
 		}
 	}
 
+	if !slices.Contains(seedersHistory, "OutboundRemovedKeysFix") {
+		if err := migrateOutboundRemovedKeys(); err != nil {
+			return err
+		}
+	}
+
 	if !slices.Contains(seedersHistory, "NodeInboundsAdopted") {
 		if err := seedNodeInboundsAdopted(); err != nil {
 			return err
@@ -1484,6 +1490,103 @@ func clearLegacyProxySettings() error {
 	})
 }
 
+func migrateOutboundRemovedKeys() error {
+	var setting model.Setting
+	err := db.Model(model.Setting{}).Where("key = ?", "xrayTemplateConfig").First(&setting).Error
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		return db.Create(&model.HistoryOfSeeders{SeederName: "OutboundRemovedKeysFix"}).Error
+	}
+	if err != nil {
+		return err
+	}
+
+	updated, changed, rErr := rewriteRemovedOutboundKeys(setting.Value)
+	if rErr != nil {
+		log.Printf("OutboundRemovedKeysFix: skip (invalid xrayTemplateConfig json): %v", rErr)
+		return db.Create(&model.HistoryOfSeeders{SeederName: "OutboundRemovedKeysFix"}).Error
+	}
+
+	return db.Transaction(func(tx *gorm.DB) error {
+		if changed {
+			if err := tx.Model(&model.Setting{}).Where("key = ?", "xrayTemplateConfig").
+				Update("value", updated).Error; err != nil {
+				return err
+			}
+		}
+		return tx.Create(&model.HistoryOfSeeders{SeederName: "OutboundRemovedKeysFix"}).Error
+	})
+}
+
+// rewriteRemovedOutboundKeys moves outbound proxySettings.tag to sockopt.dialerProxy
+// and drops freedom sockopt.addressPortStrategy: xray-core v26.9.8 refuses both.
+func rewriteRemovedOutboundKeys(raw string) (string, bool, error) {
+	if strings.TrimSpace(raw) == "" {
+		return raw, false, nil
+	}
+	var cfg map[string]any
+	if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
+		return raw, false, err
+	}
+	outbounds, ok := cfg["outbounds"].([]any)
+	if !ok {
+		return raw, false, nil
+	}
+	changed := false
+	for _, ob := range outbounds {
+		obj, ok := ob.(map[string]any)
+		if !ok {
+			continue
+		}
+		if proxySettings, present := obj["proxySettings"]; present {
+			ps, _ := proxySettings.(map[string]any)
+			if tag, _ := ps["tag"].(string); tag != "" {
+				sockopt := outboundSockopt(obj, true)
+				if current, _ := sockopt["dialerProxy"].(string); current == "" {
+					sockopt["dialerProxy"] = tag
+				}
+			}
+			delete(obj, "proxySettings")
+			changed = true
+		}
+		if proto, _ := obj["protocol"].(string); proto == "freedom" {
+			if sockopt := outboundSockopt(obj, false); sockopt != nil {
+				if _, present := sockopt["addressPortStrategy"]; present {
+					delete(sockopt, "addressPortStrategy")
+					changed = true
+				}
+			}
+		}
+	}
+	if !changed {
+		return raw, false, nil
+	}
+	out, err := json.MarshalIndent(cfg, "", "  ")
+	if err != nil {
+		return raw, false, err
+	}
+	return string(out), true, nil
+}
+
+func outboundSockopt(obj map[string]any, create bool) map[string]any {
+	stream, _ := obj["streamSettings"].(map[string]any)
+	if stream == nil {
+		if !create {
+			return nil
+		}
+		stream = map[string]any{}
+		obj["streamSettings"] = stream
+	}
+	sockopt, _ := stream["sockopt"].(map[string]any)
+	if sockopt == nil {
+		if !create {
+			return nil
+		}
+		sockopt = map[string]any{}
+		stream["sockopt"] = sockopt
+	}
+	return sockopt
+}
+
 func normalizeSettingPaths() error {
 	pathKeys := []string{"webBasePath", "subPath", "subJsonPath", "subClashPath"}
 	var rows []model.Setting

+ 117 - 0
internal/database/outbound_removed_keys_migration_test.go

@@ -0,0 +1,117 @@
+package database
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/mhsanaei/3x-ui/v3/internal/xray"
+)
+
+func TestRewriteRemovedOutboundKeys(t *testing.T) {
+	tests := []struct {
+		name         string
+		raw          string
+		wantChanged  bool
+		wantOutbound map[string]any
+	}{
+		{
+			name:        "proxySettings.tag becomes sockopt.dialerProxy",
+			raw:         `{"outbounds":[{"protocol":"vless","tag":"chain","settings":{},"proxySettings":{"tag":"hop","transportLayer":true}}]}`,
+			wantChanged: true,
+			wantOutbound: map[string]any{
+				"protocol": "vless", "tag": "chain", "settings": map[string]any{},
+				"streamSettings": map[string]any{"sockopt": map[string]any{"dialerProxy": "hop"}},
+			},
+		},
+		{
+			name:        "an existing dialerProxy wins over proxySettings",
+			raw:         `{"outbounds":[{"protocol":"vless","tag":"chain","proxySettings":{"tag":"hop"},"streamSettings":{"network":"tcp","sockopt":{"dialerProxy":"keep"}}}]}`,
+			wantChanged: true,
+			wantOutbound: map[string]any{
+				"protocol": "vless", "tag": "chain",
+				"streamSettings": map[string]any{"network": "tcp", "sockopt": map[string]any{"dialerProxy": "keep"}},
+			},
+		},
+		{
+			name:        "freedom drops sockopt.addressPortStrategy",
+			raw:         `{"outbounds":[{"protocol":"freedom","tag":"direct","streamSettings":{"sockopt":{"addressPortStrategy":"SrvPortOnly","tcpFastOpen":true}}}]}`,
+			wantChanged: true,
+			wantOutbound: map[string]any{
+				"protocol": "freedom", "tag": "direct",
+				"streamSettings": map[string]any{"sockopt": map[string]any{"tcpFastOpen": true}},
+			},
+		},
+		{
+			name:        "addressPortStrategy stays on other protocols",
+			raw:         `{"outbounds":[{"protocol":"vless","tag":"proxy","streamSettings":{"sockopt":{"addressPortStrategy":"SrvPortOnly"}}}]}`,
+			wantChanged: false,
+			wantOutbound: map[string]any{
+				"protocol": "vless", "tag": "proxy",
+				"streamSettings": map[string]any{"sockopt": map[string]any{"addressPortStrategy": "SrvPortOnly"}},
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			updated, changed, err := rewriteRemovedOutboundKeys(tc.raw)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if changed != tc.wantChanged {
+				t.Fatalf("changed = %v, want %v", changed, tc.wantChanged)
+			}
+			var cfg struct {
+				Outbounds []map[string]any `json:"outbounds"`
+			}
+			if err := json.Unmarshal([]byte(updated), &cfg); err != nil {
+				t.Fatalf("rewritten template is not JSON: %v", err)
+			}
+			if len(cfg.Outbounds) != 1 {
+				t.Fatalf("got %d outbounds, want 1", len(cfg.Outbounds))
+			}
+			got, _ := json.Marshal(cfg.Outbounds[0])
+			want, _ := json.Marshal(tc.wantOutbound)
+			if string(got) != string(want) {
+				t.Fatalf("outbound = %s, want %s", got, want)
+			}
+		})
+	}
+}
+
+func TestRewriteRemovedOutboundKeysSatisfiesCore(t *testing.T) {
+	raw := `{"outbounds":[{"protocol":"freedom","tag":"direct","settings":{},"proxySettings":{"tag":"hop"},"streamSettings":{"sockopt":{"addressPortStrategy":"SrvPortOnly"}}}]}`
+	var before struct {
+		Outbounds []json.RawMessage `json:"outbounds"`
+	}
+	if err := json.Unmarshal([]byte(raw), &before); err != nil {
+		t.Fatal(err)
+	}
+	if err := xray.ValidateOutboundConfig(before.Outbounds[0]); err == nil {
+		t.Fatal("expected the vendored core to refuse the legacy outbound")
+	}
+
+	updated, changed, err := rewriteRemovedOutboundKeys(raw)
+	if err != nil || !changed {
+		t.Fatalf("rewrite: changed=%v err=%v", changed, err)
+	}
+	var after struct {
+		Outbounds []json.RawMessage `json:"outbounds"`
+	}
+	if err := json.Unmarshal([]byte(updated), &after); err != nil {
+		t.Fatal(err)
+	}
+	if err := xray.ValidateOutboundConfig(after.Outbounds[0]); err != nil {
+		t.Fatalf("rewritten outbound still refused by xray-core: %v", err)
+	}
+}
+
+func TestRewriteRemovedOutboundKeysInvalidJSON(t *testing.T) {
+	_, changed, err := rewriteRemovedOutboundKeys("{not json")
+	if err == nil {
+		t.Fatal("expected an error for invalid JSON")
+	}
+	if changed {
+		t.Fatal("invalid JSON must not report a change")
+	}
+}

+ 5 - 2
internal/web/translation/ar-EG.json

@@ -501,6 +501,7 @@
         "type": "النوع",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "إعادة كتابة Host",
+        "xForwarded": "ترويسات X-Forwarded",
         "skipTlsVerify": "تخطي التحقق من TLS",
         "directory": "الدليل",
         "statusCode": "رمز الحالة",
@@ -602,8 +603,8 @@
         "maxTimeDiff": "أقصى فرق زمن (ms)",
         "minClientVer": "أدنى إصدار للعميل",
         "maxClientVer": "أقصى إصدار للعميل",
-        "minClientVerHint": "تركه فارغًا لا يعني بلا قيود: سيفرض Xray-core الحد الأدنى المدمج في إصدار النواة الذي تشغّله (26.3.27 في الإصدارات الحالية) ويرفض العملاء الذين يبلغون عن إصدار أقدم — بما في ذلك النوى الخارجية مثل Mihomo و sing-box. القيمة 1.0.0 تقبلها، مقابل السماح ببصمات TLS قديمة.",
-        "maxClientVerHint": "تركه فارغًا يعني بلا حد أقصى. إذا عُيّن، يجب ألا يقل عن الحد الأدنى الفعلي — أدنى إصدار للعميل، أو الحد الأدنى المدمج في Xray-core عندما يكون ذلك الحقل فارغًا — وإلا سيُرفض جميع العملاء.",
+        "minClientVerHint": "تركه فارغًا يعني بلا حد أدنى: يقبل Xray-core أي إصدار عميل، بما في ذلك النوى الخارجية مثل Mihomo وsing-box. عيّن إصدارًا لرفض العملاء الذين يبلغون عن إصدار أقدم.",
+        "maxClientVerHint": "تركه فارغًا يعني بلا حد أقصى. إذا عُيّن، يجب ألا يقل عن أدنى إصدار للعميل — وإلا سيُرفض جميع العملاء.",
         "clientVerInvalid": "يجب أن يتكون إصدار العميل من ثلاثة أرقام كحد أقصى مفصولة بنقاط، كل منها 0-255 (مثل 26.3.27)",
         "maxClientVerBelowMin": "أقصى إصدار للعميل يجب ألا يقل عن أدنى إصدار للعميل",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "وسم الوارد",
         "inboundTagPlaceholder": "وسم الوارد المستخدم في قواعد التوجيه",
         "responseType": "نوع الاستجابة",
+        "customResponseData": "استجابة مخصصة (base64)",
         "rewriteNetwork": "إعادة كتابة الشبكة",
         "unchanged": "(دون تغيير)",
         "unchangedAddress": "(دون تغيير) مثل 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "تأخير الحظر (ms)",
         "reverseSniffing": "Sniffing عكسي",
         "reserved": "محجوز",
+        "remoteDNS": "DNS بعيد",
         "minUploadInterval": "أدنى فاصل رفع (ms)",
         "maxUploadSizeBytes": "حجم الرفع الأقصى (بايت)",
         "uplinkChunkSize": "حجم chunk الرفع",

+ 5 - 2
internal/web/translation/en-US.json

@@ -501,6 +501,7 @@
         "type": "Type",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "Rewrite Host",
+        "xForwarded": "X-Forwarded headers",
         "skipTlsVerify": "Skip TLS verify",
         "directory": "Directory",
         "statusCode": "Status code",
@@ -614,8 +615,8 @@
         "maxTimeDiff": "Max Time Diff (ms)",
         "minClientVer": "Min Client Ver",
         "maxClientVer": "Max Client Ver",
-        "minClientVerHint": "Empty does not mean unrestricted: Xray-core then enforces the built-in minimum of the core build you run (26.3.27 in current releases) and rejects clients that report an older version — including third-party cores such as Mihomo and sing-box. Set 1.0.0 to accept them, at the cost of admitting outdated TLS fingerprints.",
-        "maxClientVerHint": "Empty means no upper limit. If set, it must not be lower than the effective minimum — Min Client Ver, or Xray-core's built-in minimum when that field is empty — otherwise every client is rejected.",
+        "minClientVerHint": "Empty means no minimum: Xray-core accepts every client version, including third-party cores such as Mihomo and sing-box. Set a version to reject clients that report an older one.",
+        "maxClientVerHint": "Empty means no upper limit. If set, it must not be lower than Min Client Ver — otherwise every client is rejected.",
         "clientVerInvalid": "Client version must be up to three dot-separated numbers, each 0-255 (e.g. 26.3.27)",
         "maxClientVerBelowMin": "Max Client Ver must not be lower than Min Client Ver",
         "shortIds": "Short IDs",
@@ -1731,6 +1732,7 @@
         "inboundTag": "Inbound tag",
         "inboundTagPlaceholder": "inbound tag used in routing rules",
         "responseType": "Response type",
+        "customResponseData": "Custom response (base64)",
         "rewriteNetwork": "Rewrite network",
         "unchanged": "(unchanged)",
         "unchangedAddress": "(unchanged) e.g. 1.1.1.1",
@@ -1743,6 +1745,7 @@
         "blockDelay": "Block delay (ms)",
         "reverseSniffing": "Reverse Sniffing",
         "reserved": "Reserved",
+        "remoteDNS": "Remote DNS",
         "minUploadInterval": "Min upload interval (ms)",
         "maxUploadSizeBytes": "Max upload size (bytes)",
         "uplinkChunkSize": "Uplink chunk size",

+ 5 - 2
internal/web/translation/es-ES.json

@@ -501,6 +501,7 @@
         "type": "Tipo",
         "upstreamUrl": "URL Upstream",
         "rewriteHost": "Reescribir Host",
+        "xForwarded": "Cabeceras X-Forwarded",
         "skipTlsVerify": "Saltar verificación TLS",
         "directory": "Directorio",
         "statusCode": "Código de estado",
@@ -623,8 +624,8 @@
         "maxTimeDiff": "Máx. diferencia de tiempo (ms)",
         "minClientVer": "Mín. versión cliente",
         "maxClientVer": "Máx. versión cliente",
-        "minClientVerHint": "Vacío no significa sin restricción: Xray-core aplica entonces el mínimo integrado de la build del núcleo en uso (26.3.27 en las versiones actuales) y rechaza a los clientes que reportan una versión anterior, incluidos núcleos de terceros como Mihomo y sing-box. Con 1.0.0 se aceptan, a costa de admitir huellas TLS obsoletas.",
-        "maxClientVerHint": "Vacío significa sin límite superior. Si se establece, no debe ser inferior al mínimo efectivo — la versión mínima del cliente o, si ese campo está vacío, el mínimo integrado de Xray-core — o todos los clientes serán rechazados.",
+        "minClientVerHint": "Vacío significa sin mínimo: Xray-core acepta cualquier versión de cliente, incluidos núcleos de terceros como Mihomo y sing-box. Establece una versión para rechazar a los clientes que informen una más antigua.",
+        "maxClientVerHint": "Vacío significa sin límite superior. Si se establece, no debe ser inferior a la versión mínima del cliente; de lo contrario, se rechaza a todos los clientes.",
         "clientVerInvalid": "La versión del cliente debe tener hasta tres números separados por puntos, cada uno 0-255 (p. ej. 26.3.27)",
         "maxClientVerBelowMin": "La versión máxima del cliente no debe ser inferior a la versión mínima",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Etiqueta de entrada",
         "inboundTagPlaceholder": "etiqueta de entrada usada en reglas de enrutamiento",
         "responseType": "Tipo de respuesta",
+        "customResponseData": "Respuesta personalizada (base64)",
         "rewriteNetwork": "Reescribir red",
         "unchanged": "(sin cambios)",
         "unchangedAddress": "(sin cambios) p. ej. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Retraso de bloqueo (ms)",
         "reverseSniffing": "Sniffing inverso",
         "reserved": "Reservado",
+        "remoteDNS": "DNS remoto",
         "minUploadInterval": "Intervalo mín. de subida (ms)",
         "maxUploadSizeBytes": "Tamaño máx. de subida (bytes)",
         "uplinkChunkSize": "Tamaño de chunk Uplink",

+ 5 - 2
internal/web/translation/fa-IR.json

@@ -501,6 +501,7 @@
         "type": "نوع",
         "upstreamUrl": "آدرس Upstream",
         "rewriteHost": "بازنویسی Host",
+        "xForwarded": "هدرهای X-Forwarded",
         "skipTlsVerify": "رد تایید TLS",
         "directory": "دایرکتوری",
         "statusCode": "کد وضعیت",
@@ -614,8 +615,8 @@
         "maxTimeDiff": "حداکثر اختلاف زمان (ms)",
         "minClientVer": "حداقل نسخه کلاینت",
         "maxClientVer": "حداکثر نسخه کلاینت",
-        "minClientVerHint": "خالی بودن به معنای بدون محدودیت نیست: در این حالت Xray-core حداقل داخلیِ نسخهٔ هسته‌ای را که اجرا می‌کنید (در نسخه‌های فعلی 26.3.27) اعمال می‌کند و کلاینت‌هایی را که نسخهٔ قدیمی‌تری اعلام می‌کنند رد می‌کند — از جمله هسته‌های شخص ثالث مانند Mihomo و sing-box. مقدار 1.0.0 آن‌ها را می‌پذیرد، به بهای پذیرش اثر انگشت‌های TLS قدیمی.",
-        "maxClientVerHint": "خالی یعنی بدون سقف. در صورت تنظیم، نباید از حداقلِ مؤثر — حداقل نسخه کلاینت، و در صورت خالی بودن آن فیلد، حداقل داخلی Xray-core — کمتر باشد، وگرنه همهٔ کلاینت‌ها رد می‌شوند.",
+        "minClientVerHint": "خالی یعنی بدون حداقل: Xray-core هر نسخهٔ کلاینتی را می‌پذیرد، از جمله هسته‌های شخص ثالث مانند Mihomo و sing-box. برای رد کردن کلاینت‌هایی که نسخهٔ قدیمی‌تری گزارش می‌دهند، یک نسخه تعیین کنید.",
+        "maxClientVerHint": "خالی یعنی بدون سقف. در صورت تنظیم، نباید از حداقل نسخه کلاینت کمتر باشد — در غیر این صورت همهٔ کلاینت‌ها رد می‌شوند.",
         "clientVerInvalid": "نسخهٔ کلاینت باید حداکثر سه عدد جداشده با نقطه باشد، هر یک 0-255 (مثلاً 26.3.27)",
         "maxClientVerBelowMin": "حداکثر نسخهٔ کلاینت نباید از حداقل نسخهٔ کلاینت کمتر باشد",
         "shortIds": "Short IDها",
@@ -1613,6 +1614,7 @@
         "inboundTag": "تگ ورودی",
         "inboundTagPlaceholder": "تگ ورودی استفاده‌شده در قوانین مسیریابی",
         "responseType": "نوع پاسخ",
+        "customResponseData": "پاسخ سفارشی (base64)",
         "rewriteNetwork": "بازنویسی شبکه",
         "unchanged": "(بدون تغییر)",
         "unchangedAddress": "(بدون تغییر) مثل 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "تأخیر بلاک (ms)",
         "reverseSniffing": "Sniffing معکوس",
         "reserved": "رزرو شده",
+        "remoteDNS": "DNS راه دور",
         "minUploadInterval": "حداقل بازه آپلود (ms)",
         "maxUploadSizeBytes": "حداکثر اندازه آپلود (بایت)",
         "uplinkChunkSize": "اندازه قطعه آپلینک",

+ 5 - 2
internal/web/translation/id-ID.json

@@ -501,6 +501,7 @@
         "type": "Tipe",
         "upstreamUrl": "URL Upstream",
         "rewriteHost": "Tulis ulang Host",
+        "xForwarded": "Header X-Forwarded",
         "skipTlsVerify": "Lewati verifikasi TLS",
         "directory": "Direktori",
         "statusCode": "Kode status",
@@ -602,8 +603,8 @@
         "maxTimeDiff": "Maks. selisih waktu (ms)",
         "minClientVer": "Min. versi klien",
         "maxClientVer": "Maks. versi klien",
-        "minClientVerHint": "Kosong bukan berarti tanpa batas: Xray-core akan memakai minimum bawaan dari build core yang dijalankan (26.3.27 pada rilis saat ini) dan menolak klien yang melaporkan versi lebih lama — termasuk core pihak ketiga seperti Mihomo dan sing-box. Isi 1.0.0 untuk menerimanya, dengan risiko mengizinkan sidik jari TLS yang usang.",
-        "maxClientVerHint": "Kosong berarti tanpa batas atas. Jika diisi, tidak boleh lebih rendah dari minimum efektif — versi klien minimum, atau minimum bawaan Xray-core saat kolom itu kosong — atau semua klien akan ditolak.",
+        "minClientVerHint": "Kosong berarti tanpa minimum: Xray-core menerima semua versi klien, termasuk core pihak ketiga seperti Mihomo dan sing-box. Isi versi untuk menolak klien yang melaporkan versi lebih lama.",
+        "maxClientVerHint": "Kosong berarti tanpa batas atas. Jika diisi, tidak boleh lebih rendah dari versi klien minimum — jika tidak, semua klien ditolak.",
         "clientVerInvalid": "Versi klien harus berupa maksimal tiga angka dipisah titik, masing-masing 0-255 (mis. 26.3.27)",
         "maxClientVerBelowMin": "Versi klien maksimum tidak boleh lebih rendah dari versi klien minimum",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Tag inbound",
         "inboundTagPlaceholder": "tag inbound yang digunakan dalam aturan routing",
         "responseType": "Tipe respons",
+        "customResponseData": "Respons kustom (base64)",
         "rewriteNetwork": "Tulis ulang jaringan",
         "unchanged": "(tidak berubah)",
         "unchangedAddress": "(tidak berubah) mis. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Penundaan blokir (ms)",
         "reverseSniffing": "Sniffing terbalik",
         "reserved": "Dicadangkan",
+        "remoteDNS": "DNS jarak jauh",
         "minUploadInterval": "Min. interval upload (ms)",
         "maxUploadSizeBytes": "Ukuran upload maks. (byte)",
         "uplinkChunkSize": "Ukuran chunk Uplink",

+ 5 - 2
internal/web/translation/ja-JP.json

@@ -522,6 +522,7 @@
         "type": "種類",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "Host 書き換え",
+        "xForwarded": "X-Forwarded ヘッダー",
         "skipTlsVerify": "TLS 検証をスキップ",
         "directory": "ディレクトリ",
         "statusCode": "ステータスコード",
@@ -623,8 +624,8 @@
         "maxTimeDiff": "最大時間差 (ms)",
         "minClientVer": "最小クライアントバージョン",
         "maxClientVer": "最大クライアントバージョン",
-        "minClientVerHint": "空欄は無制限ではありません。Xray-core は実行中のコアに組み込まれた最低バージョン(現行リリースでは 26.3.27)を適用し、それより古いバージョンを名乗るクライアント(Mihomo や sing-box などのサードパーティコアを含む)を拒否します。1.0.0 を設定すると許可されますが、古い TLS フィンガープリントも受け入れることになります。",
-        "maxClientVerHint": "空欄は上限なしを意味します。設定する場合は実効的な下限(最小クライアントバージョン。その欄が空欄の場合は Xray-core 組み込みの最低バージョン)を下回らないでください。下回るとすべてのクライアントが拒否されます。",
+        "minClientVerHint": "空欄は下限なしを意味します。Xray-core は Mihomo や sing-box などのサードパーティコアを含め、あらゆるクライアントバージョンを受け入れます。古いバージョンを申告するクライアントを拒否するにはバージョンを設定してください。",
+        "maxClientVerHint": "空欄は上限なしを意味します。設定する場合は最小クライアントバージョンを下回ってはいけません。下回るとすべてのクライアントが拒否されます。",
         "clientVerInvalid": "クライアントバージョンはドット区切りの数値(最大 3 つ、各 0-255)で指定してください(例:26.3.27)",
         "maxClientVerBelowMin": "最大クライアントバージョンは最小クライアントバージョンを下回れません",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "インバウンドタグ",
         "inboundTagPlaceholder": "ルーティングルールで使うインバウンドタグ",
         "responseType": "レスポンスタイプ",
+        "customResponseData": "カスタム応答(base64)",
         "rewriteNetwork": "ネットワーク書き換え",
         "unchanged": "(変更なし)",
         "unchangedAddress": "(変更なし) 例: 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "ブロック遅延 (ms)",
         "reverseSniffing": "逆 sniffing",
         "reserved": "予約",
+        "remoteDNS": "リモート DNS",
         "minUploadInterval": "最小アップロード間隔 (ms)",
         "maxUploadSizeBytes": "最大アップロードサイズ (バイト)",
         "uplinkChunkSize": "Uplink チャンクサイズ",

+ 5 - 2
internal/web/translation/pt-BR.json

@@ -522,6 +522,7 @@
         "type": "Tipo",
         "upstreamUrl": "URL Upstream",
         "rewriteHost": "Reescrever Host",
+        "xForwarded": "Cabeçalhos X-Forwarded",
         "skipTlsVerify": "Pular verificação TLS",
         "directory": "Diretório",
         "statusCode": "Código de status",
@@ -623,8 +624,8 @@
         "maxTimeDiff": "Máx. diferença de tempo (ms)",
         "minClientVer": "Mín. versão cliente",
         "maxClientVer": "Máx. versão cliente",
-        "minClientVerHint": "Vazio não significa sem restrição: o Xray-core aplica o mínimo embutido da build do núcleo em uso (26.3.27 nas versões atuais) e rejeita clientes que reportam uma versão mais antiga — incluindo núcleos de terceiros como Mihomo e sing-box. Definir 1.0.0 os aceita, ao custo de admitir impressões digitais TLS desatualizadas.",
-        "maxClientVerHint": "Vazio significa sem limite superior. Se definido, não deve ser menor que o mínimo efetivo — a versão mínima do cliente ou, se aquele campo estiver vazio, o mínimo embutido do Xray-core — ou todos os clientes serão rejeitados.",
+        "minClientVerHint": "Vazio significa sem mínimo: o Xray-core aceita qualquer versão de cliente, incluindo núcleos de terceiros como Mihomo e sing-box. Defina uma versão para rejeitar clientes que informem uma mais antiga.",
+        "maxClientVerHint": "Vazio significa sem limite superior. Se definido, não deve ser menor que a versão mínima do cliente; caso contrário, todos os clientes são rejeitados.",
         "clientVerInvalid": "A versão do cliente deve ter até três números separados por pontos, cada um 0-255 (ex.: 26.3.27)",
         "maxClientVerBelowMin": "A versão máxima do cliente não deve ser menor que a versão mínima",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Tag de entrada",
         "inboundTagPlaceholder": "tag de entrada usada em regras de roteamento",
         "responseType": "Tipo de resposta",
+        "customResponseData": "Resposta personalizada (base64)",
         "rewriteNetwork": "Reescrever rede",
         "unchanged": "(inalterado)",
         "unchangedAddress": "(inalterado) ex. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Atraso do bloqueio (ms)",
         "reverseSniffing": "Sniffing reverso",
         "reserved": "Reservado",
+        "remoteDNS": "DNS remoto",
         "minUploadInterval": "Intervalo mín. de upload (ms)",
         "maxUploadSizeBytes": "Tamanho máx. de upload (bytes)",
         "uplinkChunkSize": "Tamanho do chunk Uplink",

+ 5 - 2
internal/web/translation/ru-RU.json

@@ -522,6 +522,7 @@
         "type": "Тип",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "Переписать Host",
+        "xForwarded": "Заголовки X-Forwarded",
         "skipTlsVerify": "Пропустить TLS verify",
         "directory": "Директория",
         "statusCode": "Код статуса",
@@ -623,8 +624,8 @@
         "maxTimeDiff": "Макс. разница во времени (мс)",
         "minClientVer": "Мин. версия клиента",
         "maxClientVer": "Макс. версия клиента",
-        "minClientVerHint": "Пустое поле не означает «без ограничений»: Xray-core применит встроенный минимум используемой сборки ядра (26.3.27 в текущих релизах) и отклонит клиентов, сообщающих более старую версию, — включая сторонние ядра, такие как Mihomo и sing-box. Значение 1.0.0 разрешит их, но допустит устаревшие TLS-отпечатки.",
-        "maxClientVerHint": "Пустое поле — без верхнего предела. Если задано, значение не должно быть ниже действующего минимума — «Мин. версия клиента», а при пустом том поле — встроенного минимума Xray-core, иначе все клиенты будут отклонены.",
+        "minClientVerHint": "Пустое поле — без нижней границы: Xray-core принимает любую версию клиента, включая сторонние ядра вроде Mihomo и sing-box. Укажите версию, чтобы отклонять клиентов, сообщающих более старую.",
+        "maxClientVerHint": "Пустое поле — без верхнего предела. Если задано, значение не должно быть ниже минимальной версии клиента — иначе будут отклонены все клиенты.",
         "clientVerInvalid": "Версия клиента — до трёх чисел через точку, каждое 0-255 (например 26.3.27)",
         "maxClientVerBelowMin": "Макс. версия клиента не должна быть ниже минимальной версии клиента",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Тег входящего",
         "inboundTagPlaceholder": "тег входящего в правилах маршрутизации",
         "responseType": "Тип ответа",
+        "customResponseData": "Свой ответ (base64)",
         "rewriteNetwork": "Переписать сеть",
         "unchanged": "(без изменений)",
         "unchangedAddress": "(без изменений) напр. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Задержка блока (мс)",
         "reverseSniffing": "Обратный sniffing",
         "reserved": "Зарезервировано",
+        "remoteDNS": "Удалённый DNS",
         "minUploadInterval": "Мин. интервал загрузки (мс)",
         "maxUploadSizeBytes": "Макс. размер загрузки (байт)",
         "uplinkChunkSize": "Размер chunk Uplink",

+ 6 - 3
internal/web/translation/tr-TR.json

@@ -501,6 +501,7 @@
         "type": "Tip",
         "upstreamUrl": "Üst Sunucu URL (Upstream)",
         "rewriteHost": "Host'u Yeniden Yaz",
+        "xForwarded": "X-Forwarded başlıkları",
         "skipTlsVerify": "TLS Doğrulamayı Atla",
         "directory": "Dizin",
         "statusCode": "Durum Kodu",
@@ -602,8 +603,8 @@
         "maxTimeDiff": "Maks. Zaman Farkı (ms)",
         "minClientVer": "Min. Kullanıcı Sürümü",
         "maxClientVer": "Maks. Kullanıcı Sürümü",
-        "minClientVerHint": "Boş bırakmak sınırsız demek değildir: Xray-core, çalıştırdığınız çekirdek sürümünün yerleşik alt sınırını (güncel sürümlerde 26.3.27) uygular ve daha eski sürüm bildiren istemcileri reddeder — Mihomo ve sing-box gibi üçüncü taraf çekirdekler dahil. 1.0.0 girmek onları kabul eder; bedeli eski TLS parmak izlerine izin vermektir.",
-        "maxClientVerHint": "Boş, üst sınır yok demektir. Ayarlanırsa geçerli alt sınırın — Min. Kullanıcı Sürümü, o alan boşsa Xray-core'un yerleşik alt sınırı — altında olmamalıdır, aksi halde tüm istemciler reddedilir.",
+        "minClientVerHint": "Boş bırakmak alt sınır yok demektir: Xray-core, Mihomo ve sing-box gibi üçüncü taraf çekirdekler dahil her istemci sürümünü kabul eder. Daha eski sürüm bildiren istemcileri reddetmek için bir sürüm girin.",
+        "maxClientVerHint": "Boş, üst sınır yok demektir. Ayarlanırsa Min. İstemci Sürümü'nden düşük olmamalıdır; aksi halde tüm istemciler reddedilir.",
         "clientVerInvalid": "İstemci sürümü noktayla ayrılmış en fazla üç sayıdan oluşmalıdır, her biri 0-255 (örn. 26.3.27)",
         "maxClientVerBelowMin": "Maks. istemci sürümü, en düşük istemci sürümünün altında olamaz",
         "shortIds": "Kısa Kimlikler",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Gelen Bağlantı Etiketi",
         "inboundTagPlaceholder": "yönlendirme kurallarında kullanılan gelen bağlantı etiketi",
         "responseType": "Yanıt Tipi",
+        "customResponseData": "Özel yanıt (base64)",
         "rewriteNetwork": "Ağı Yeniden Yaz",
         "unchanged": "(değişmedi)",
         "unchangedAddress": "(değişmedi) örn. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Engelleme Gecikmesi (ms)",
         "reverseSniffing": "Ters Dinleme (Sniffing)",
         "reserved": "Ayrılmış",
+        "remoteDNS": "Uzak DNS",
         "minUploadInterval": "Min. Yükleme Aralığı (ms)",
         "maxUploadSizeBytes": "Maks. Yükleme Boyutu (bayt)",
         "uplinkChunkSize": "Uplink Chunk Boyutu",
@@ -2255,4 +2258,4 @@
     "statusDown": "ÇEVRİMDIŞI",
     "statusUp": "ÇEVRİMİÇİ"
   }
-}
+}

+ 5 - 2
internal/web/translation/uk-UA.json

@@ -501,6 +501,7 @@
         "type": "Тип",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "Переписати Host",
+        "xForwarded": "Заголовки X-Forwarded",
         "skipTlsVerify": "Пропустити TLS verify",
         "directory": "Каталог",
         "statusCode": "Код статусу",
@@ -602,8 +603,8 @@
         "maxTimeDiff": "Макс. різниця в часі (мс)",
         "minClientVer": "Мін. версія клієнта",
         "maxClientVer": "Макс. версія клієнта",
-        "minClientVerHint": "Порожнє поле не означає «без обмежень»: Xray-core застосує вбудований мінімум використовуваної збірки ядра (26.3.27 у поточних релізах) і відхилятиме клієнтів зі старішою версією — зокрема сторонні ядра, як-от Mihomo та sing-box. Значення 1.0.0 дозволить їх, але допустить застарілі TLS-відбитки.",
-        "maxClientVerHint": "Порожнє поле — без верхньої межі. Якщо задано, значення не має бути нижчим за чинний мінімум — «Мін. версія клієнта», а коли те поле порожнє — вбудований мінімум Xray-core, інакше всіх клієнтів буде відхилено.",
+        "minClientVerHint": "Порожнє поле — без нижньої межі: Xray-core приймає будь-яку версію клієнта, зокрема сторонні ядра на кшталт Mihomo та sing-box. Вкажіть версію, щоб відхиляти клієнтів, які повідомляють старішу.",
+        "maxClientVerHint": "Порожнє поле — без верхньої межі. Якщо задано, значення не має бути нижчим за мінімальну версію клієнта — інакше буде відхилено всіх клієнтів.",
         "clientVerInvalid": "Версія клієнта — до трьох чисел через крапку, кожне 0-255 (наприклад 26.3.27)",
         "maxClientVerBelowMin": "Макс. версія клієнта не має бути нижчою за мінімальну версію клієнта",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Тег вхідного",
         "inboundTagPlaceholder": "тег вхідного у правилах маршрутизації",
         "responseType": "Тип відповіді",
+        "customResponseData": "Власна відповідь (base64)",
         "rewriteNetwork": "Переписати мережу",
         "unchanged": "(без змін)",
         "unchangedAddress": "(без змін) напр. 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Затримка блоку (мс)",
         "reverseSniffing": "Зворотний sniffing",
         "reserved": "Зарезервовано",
+        "remoteDNS": "Віддалений DNS",
         "minUploadInterval": "Мін. інтервал завантаження (мс)",
         "maxUploadSizeBytes": "Макс. розмір завантаження (байт)",
         "uplinkChunkSize": "Розмір chunk Uplink",

+ 5 - 2
internal/web/translation/vi-VN.json

@@ -522,6 +522,7 @@
         "type": "Loại",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "Viết lại Host",
+        "xForwarded": "Header X-Forwarded",
         "skipTlsVerify": "Bỏ qua xác minh TLS",
         "directory": "Thư mục",
         "statusCode": "Mã trạng thái",
@@ -623,8 +624,8 @@
         "maxTimeDiff": "Chênh lệch thời gian tối đa (ms)",
         "minClientVer": "Phiên bản client tối thiểu",
         "maxClientVer": "Phiên bản client tối đa",
-        "minClientVerHint": "Để trống không có nghĩa là không giới hạn: Xray-core sẽ áp dụng mức tối thiểu tích hợp của bản core đang chạy (26.3.27 ở các bản phát hành hiện tại) và từ chối các client khai báo phiên bản cũ hơn — bao gồm các core bên thứ ba như Mihomo và sing-box. Đặt 1.0.0 để chấp nhận chúng, đổi lại là cho phép các dấu vân tay TLS lỗi thời.",
-        "maxClientVerHint": "Để trống nghĩa là không có giới hạn trên. Nếu đặt, không được thấp hơn mức tối thiểu đang có hiệu lực — phiên bản client tối thiểu, hoặc mức tối thiểu tích hợp của Xray-core khi ô đó để trống — nếu không mọi client đều bị từ chối.",
+        "minClientVerHint": "Để trống nghĩa là không có mức tối thiểu: Xray-core chấp nhận mọi phiên bản client, kể cả các core bên thứ ba như Mihomo và sing-box. Đặt một phiên bản để từ chối các client báo phiên bản cũ hơn.",
+        "maxClientVerHint": "Để trống nghĩa là không có giới hạn trên. Nếu đặt, không được thấp hơn phiên bản client tối thiểu — nếu không mọi client đều bị từ chối.",
         "clientVerInvalid": "Phiên bản client phải gồm tối đa ba số cách nhau bằng dấu chấm, mỗi số 0-255 (ví dụ 26.3.27)",
         "maxClientVerBelowMin": "Phiên bản client tối đa không được thấp hơn phiên bản client tối thiểu",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "Tag inbound",
         "inboundTagPlaceholder": "tag inbound dùng trong quy tắc định tuyến",
         "responseType": "Loại phản hồi",
+        "customResponseData": "Phản hồi tùy chỉnh (base64)",
         "rewriteNetwork": "Viết lại mạng",
         "unchanged": "(không đổi)",
         "unchangedAddress": "(không đổi) ví dụ 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "Trễ chặn (ms)",
         "reverseSniffing": "Sniffing ngược",
         "reserved": "Đã đặt trước",
+        "remoteDNS": "DNS từ xa",
         "minUploadInterval": "Khoảng upload tối thiểu (ms)",
         "maxUploadSizeBytes": "Kích thước upload tối đa (byte)",
         "uplinkChunkSize": "Kích thước chunk Uplink",

+ 5 - 2
internal/web/translation/zh-CN.json

@@ -521,6 +521,7 @@
         "type": "类型",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "重写 Host",
+        "xForwarded": "X-Forwarded 头",
         "skipTlsVerify": "跳过 TLS 验证",
         "directory": "目录",
         "statusCode": "状态码",
@@ -622,8 +623,8 @@
         "maxTimeDiff": "最大时间差 (ms)",
         "minClientVer": "最小客户端版本",
         "maxClientVer": "最大客户端版本",
-        "minClientVerHint": "留空不等于不限制:Xray-core 会改用所运行内核版本的内置最低值(当前版本为 26.3.27),拒绝自报版本更低的客户端——包括 Mihomo、sing-box 等第三方内核。填 1.0.0 可放行它们,代价是允许过时的 TLS 指纹。",
-        "maxClientVerHint": "留空表示无上限。若填写,不得低于实际生效的下限——最小客户端版本,该字段留空时则为 Xray-core 的内置最低值——否则所有客户端都会被拒绝。",
+        "minClientVerHint": "留空表示不设下限:Xray-core 接受任何客户端版本,包括 Mihomo、sing-box 等第三方内核。填写版本可拒绝自报版本更低的客户端。",
+        "maxClientVerHint": "留空表示无上限。若填写,不得低于最小客户端版本,否则所有客户端都会被拒绝。",
         "clientVerInvalid": "客户端版本须为最多三段以点分隔的数字,每段 0-255(例如 26.3.27)",
         "maxClientVerBelowMin": "最大客户端版本不得低于最小客户端版本",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "入站标签",
         "inboundTagPlaceholder": "用于路由规则的入站标签",
         "responseType": "响应类型",
+        "customResponseData": "自定义响应(base64)",
         "rewriteNetwork": "重写网络",
         "unchanged": "(未更改)",
         "unchangedAddress": "(未更改) 如 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "阻塞延迟 (ms)",
         "reverseSniffing": "反向 sniffing",
         "reserved": "保留",
+        "remoteDNS": "远程 DNS",
         "minUploadInterval": "最小上传间隔 (ms)",
         "maxUploadSizeBytes": "最大上传大小 (字节)",
         "uplinkChunkSize": "Uplink chunk 大小",

+ 5 - 2
internal/web/translation/zh-TW.json

@@ -501,6 +501,7 @@
         "type": "類型",
         "upstreamUrl": "Upstream URL",
         "rewriteHost": "改寫 Host",
+        "xForwarded": "X-Forwarded 標頭",
         "skipTlsVerify": "略過 TLS 驗證",
         "directory": "目錄",
         "statusCode": "狀態碼",
@@ -602,8 +603,8 @@
         "maxTimeDiff": "最大時間差 (ms)",
         "minClientVer": "最小客戶端版本",
         "maxClientVer": "最大客戶端版本",
-        "minClientVerHint": "留空不等於不限制:Xray-core 會改用所執行核心版本的內建最低值(目前版本為 26.3.27),拒絕自報版本較低的客戶端——包括 Mihomo、sing-box 等第三方核心。填 1.0.0 可放行它們,代價是允許過時的 TLS 指紋。",
-        "maxClientVerHint": "留空表示無上限。若填寫,不得低於實際生效的下限——最小客戶端版本,該欄位留空時則為 Xray-core 的內建最低值——否則所有客戶端都會被拒絕。",
+        "minClientVerHint": "留空表示不設下限:Xray-core 接受任何客戶端版本,包括 Mihomo、sing-box 等第三方核心。填寫版本可拒絕自報版本較低的客戶端。",
+        "maxClientVerHint": "留空表示無上限。若填寫,不得低於最小客戶端版本,否則所有客戶端都會被拒絕。",
         "clientVerInvalid": "客戶端版本須為最多三段以點分隔的數字,每段 0-255(例如 26.3.27)",
         "maxClientVerBelowMin": "最大客戶端版本不得低於最小客戶端版本",
         "shortIds": "Short IDs",
@@ -1613,6 +1614,7 @@
         "inboundTag": "入站標籤",
         "inboundTagPlaceholder": "用於路由規則的入站標籤",
         "responseType": "回應類型",
+        "customResponseData": "自訂回應(base64)",
         "rewriteNetwork": "改寫網路",
         "unchanged": "(未變更)",
         "unchangedAddress": "(未變更) 如 1.1.1.1",
@@ -1625,6 +1627,7 @@
         "blockDelay": "阻斷延遲 (ms)",
         "reverseSniffing": "反向 sniffing",
         "reserved": "保留",
+        "remoteDNS": "遠端 DNS",
         "minUploadInterval": "最小上傳間隔 (ms)",
         "maxUploadSizeBytes": "最大上傳大小 (位元組)",
         "uplinkChunkSize": "Uplink chunk 大小",