From a748f799c709fd878858cb46e1ede4ba8155909a Mon Sep 17 00:00:00 2001 From: Sanaei Date: Thu, 12 Nov 2026 19:10:18 +0100 Subject: [PATCH] fix(xray): adopt freedom finalRules delay and logLevel xray-core 26.11.9 (#7329) renamed a final rule's blockDelay to delay, added logLevel, and now refuses either on a rule whose action is not block. A stored template with a blockDelay on an allow rule therefore stops the core at startup after the upgrade. A one-time seeder strips delay, blockDelay and logLevel from non-block rules of the template's freedom outbounds and moves blockDelay to delay on block rules. The outbound form reads both spellings (blockDelay wins, as in the core), writes delay, gains a log level control on block rules and emits neither key on an allow rule. --- .../src/lib/xray/outbound-form-adapter.ts | 16 ++- .../xray/outbounds/protocols/freedom.tsx | 34 ++++-- frontend/src/schemas/forms/outbound-form.ts | 8 +- .../src/schemas/protocols/outbound/freedom.ts | 3 + .../src/test/outbound-form-adapter.test.ts | 29 ++++- internal/database/db.go | 89 +++++++++++++++- ...freedom_final_rule_delay_migration_test.go | 100 ++++++++++++++++++ 7 files changed, 263 insertions(+), 16 deletions(-) create mode 100644 internal/database/freedom_final_rule_delay_migration_test.go diff --git a/frontend/src/lib/xray/outbound-form-adapter.ts b/frontend/src/lib/xray/outbound-form-adapter.ts index 41fdd1374..340fdb476 100644 --- a/frontend/src/lib/xray/outbound-form-adapter.ts +++ b/frontend/src/lib/xray/outbound-form-adapter.ts @@ -354,14 +354,23 @@ function freedomFromWire( network, port: asString(rr.port), ip: asArray(rr.ip).map((x) => asString(x)), - blockDelay: asString(rr.blockDelay), + // The deprecated blockDelay wins over delay, as it does in xray-core 26.11.9. + delay: asString(rr.blockDelay) || asString(rr.delay), + logLevel: asString(rr.logLevel), }; }); // Legacy ipsBlocked → finalRule(block) backfill if (finalRules.length === 0) { const ipsBlocked = asArray(raw.ipsBlocked).map((x) => asString(x)); if (ipsBlocked.length > 0) { - finalRules.push({ action: 'block', network: '', port: '', ip: ipsBlocked, blockDelay: '' }); + finalRules.push({ + action: 'block', + network: '', + port: '', + ip: ipsBlocked, + delay: '', + logLevel: '', + }); } } // Wire fragment is either missing or a populated object. Mirror the @@ -828,7 +837,8 @@ function freedomToWire(s: FreedomOutboundFormSettings) { network: r.network || undefined, port: r.port || undefined, ip: r.ip.length > 0 ? r.ip : undefined, - blockDelay: r.action === 'block' && r.blockDelay ? r.blockDelay : undefined, + delay: r.action === 'block' && r.delay ? r.delay : undefined, + logLevel: r.action === 'block' && r.logLevel ? r.logLevel : undefined, })) : undefined, }; diff --git a/frontend/src/pages/xray/outbounds/protocols/freedom.tsx b/frontend/src/pages/xray/outbounds/protocols/freedom.tsx index 5e4a7b253..d2d43da6b 100644 --- a/frontend/src/pages/xray/outbounds/protocols/freedom.tsx +++ b/frontend/src/pages/xray/outbounds/protocols/freedom.tsx @@ -6,6 +6,7 @@ import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; import { selectTagRender } from '@/components/form'; import { FormField } from '@/components/form/rhf'; +import { LOG_LEVELS } from '@/pages/xray/basics/constants'; import { activateOnKey } from '@/utils/a11y'; import { OutboundDomainStrategies } from '@/schemas/primitives'; @@ -220,7 +221,14 @@ export default function FreedomFields() { icon={} aria-label={t('add')} onClick={() => - appendFinalRule({ action: 'allow', network: '', port: '', ip: [], blockDelay: '' }) + appendFinalRule({ + action: 'allow', + network: '', + port: '', + ip: [], + delay: '', + logLevel: '', + }) } /> @@ -281,12 +289,24 @@ export default function FreedomFields() { /> {finalRulesValues[index]?.action === 'block' && ( - - - + <> + + + + +