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

Bump xtls/xray-core to 50231eaf (v26.7.11) and the three binary pins
(DockerInit.sh, release.yml x2) in lockstep.

Adapt the panel to the upstream changes:

- Shadowsocks "none"/"plain" and VMess "none"/"zero" were removed from
  the core. A migration rewrites stored none/plain SS methods to a
  supported cipher and none/zero VMess security to "auto" (on both the
  clients column and inbound settings JSON); the SS build-time heal does
  the same so a row injected after boot cannot brick startup. The removed
  values are dropped from every frontend option list, schema and adapter,
  and coerced to "auto" at the Go link/sub/Clash emit sites and both link
  importers. Fix the CipherType_NONE sentinel that no longer compiles.

- Unencrypted vless/trojan outbounds to a public address are now refused
  by the core. Validate outbounds through the vendored config loader when
  saving the xray template and when storing/merging outbound
  subscriptions, so one such outbound cannot keep the core from starting.

- New TCP finalmask type "xmc" (Minecraft mimicry): add it to the sub
  link allowlist, the frontend enum and the FinalMask form (hostname,
  usernames, required password), and document it.

- streamSettings gained a "method" alias for "network"; canonicalize it
  to "network" at inbound save time and in the form adapters/schema so a
  method-keyed config keeps its transport.

- New root "env" config key is passed through xray.Config, compared in
  Equals, and forces a restart in the hot diff.

- REALITY now defaults minClientVer to 26.3.27; update the form
  placeholder.
This commit is contained in:
MHSanaei
2026-07-12 00:30:47 +02:00
parent affcf6c422
commit 814cda3fb4
39 changed files with 709 additions and 69 deletions
@@ -81,6 +81,8 @@ function defaultTcpMaskSettings(type: string): Record<string, unknown> {
};
case 'header-custom':
return { clients: [], servers: [] };
case 'xmc':
return { hostname: '', usernames: [], password: RandomUtil.randomLowerAndNum(16) };
default:
return {};
}
@@ -294,6 +296,7 @@ function TcpMaskItem({
{ value: 'fragment', label: 'Fragment' },
{ value: 'header-custom', label: 'Header Custom' },
{ value: 'sudoku', label: 'Sudoku' },
{ value: 'xmc', label: 'XMC (Minecraft)' },
]}
/>
</Form.Item>
@@ -371,6 +374,41 @@ function TcpMaskItem({
/>
);
}
if (type === 'xmc') {
return (
<>
<Form.Item label="Hostname" name={[fieldName, 'settings', 'hostname']}>
<Input placeholder="Server address mimicked in the handshake" />
</Form.Item>
<Form.Item
label="Usernames"
name={[fieldName, 'settings', 'usernames']}
extra="Player names offered to probes; core defaults to Dream when empty."
>
<Select mode="tags" style={{ width: '100%' }} tokenSeparators={[',']} />
</Form.Item>
<Form.Item label="Password" required>
<Space.Compact block>
<Form.Item
name={[fieldName, 'settings', 'password']}
noStyle
rules={[{ required: true, message: 'Password is required' }]}
>
<Input placeholder="Obfuscation password" style={{ width: 'calc(100% - 32px)' }} />
</Form.Item>
<Button
icon={<ReloadOutlined />}
aria-label={t('regenerate')}
onClick={() => form.setFieldValue(
[...absolutePath, 'settings', 'password'],
RandomUtil.randomLowerAndNum(16),
)}
/>
</Space.Compact>
</Form.Item>
</>
);
}
return null;
}}
</Form.Item>
@@ -124,6 +124,10 @@ const NETWORK_SETTINGS_KEY: Record<string, string> = {
};
function healStreamNetworkKey(stream: Record<string, unknown>): void {
if (typeof stream.method === 'string' && stream.method !== '') {
stream.network = stream.method;
}
delete stream.method;
const network = typeof stream.network === 'string' ? stream.network : '';
const key = NETWORK_SETTINGS_KEY[network];
if (!key) return;
@@ -107,7 +107,7 @@ function vmessFromWire(raw: Raw): VmessOutboundFormSettings {
id: asString(u.id),
security: ((): VmessOutboundFormSettings['security'] => {
const s = asString(u.security);
const allowed = ['aes-128-gcm', 'chacha20-poly1305', 'auto', 'none', 'zero'];
const allowed = ['aes-128-gcm', 'chacha20-poly1305', 'auto'];
return (allowed.includes(s) ? s : 'auto') as VmessOutboundFormSettings['security'];
})(),
};
@@ -391,6 +391,10 @@ const XMUX_DEFAULTS = XHttpXmuxSchema.parse({});
function hydrateStreamForm(stream: Raw): OutboundStreamFormValues {
const next = { ...stream };
if (typeof next.method === 'string' && next.method !== '') {
next.network = next.method;
}
delete next.method;
const xh = next.xhttpSettings;
if (xh && typeof xh === 'object' && !Array.isArray(xh)) {
const xhttp = { ...(xh as Raw) };
@@ -360,6 +360,8 @@ export function parseVmessLink(link: string): Raw | null {
}
const port = Number(json.port) || 443;
const rawScy = (json.scy as string) || 'auto';
const userSecurity = rawScy === 'none' || rawScy === 'zero' ? 'auto' : rawScy;
return {
protocol: 'vmess',
tag: typeof json.ps === 'string' ? json.ps : '',
@@ -367,7 +369,7 @@ export function parseVmessLink(link: string): Raw | null {
vnext: [{
address: json.add ?? '',
port,
users: [{ id: json.id ?? '', security: (json.scy as string) || 'auto' }],
users: [{ id: json.id ?? '', security: userSecurity }],
}],
},
streamSettings: stream,