feat(sub): let the panel set the JSON subscription DNS servers (#6485)

* feat(sub): let the panel set the JSON subscription DNS servers

A baked routing profile (#6402) carries only the DNS its preset defines, so an
operator who wants their own resolvers has to override the whole profile or
patch the subscription behind a proxy.

Add the subJsonDns setting: either a full xray dns block or a bare array of
servers. It wins over the profile's DNS while leaving the profile's routing
rules intact, and reaches per-inbound, balancer and info-node documents alike.

The value is validated with xray's own schema (internal/xray/dnsconf): a block
the client could not load is rejected when the settings are saved and ignored
with a warning at request time, instead of being baked into every document.
Both the sub server and the settings API share that validator, so a stored
value can never be silently dropped.

xray's Build() is deliberately not used for validation: it resolves geosite
tokens from the geodata files and would reject valid configs whenever those
are absent from the panel's working directory.

* style(dnsconf): drop the ineffectual initial map assignment

golangci's ineffassign flagged the zero-value map whose value both paths
overwrite: the object branch now assigns the decoded map directly.

* docs(sub): scope the DNS setting to the documents it rewrites

The Routing header mirrored to Happ/INCY keeps the routing profile's own
resolvers, so the setting description and the header-source comment now say
so instead of claiming the profile's DNS is replaced everywhere.

Also trims two comments in the new dnsconf package to the repo's two-line cap.
This commit is contained in:
DIMFLIX
2026-09-13 12:51:56 +03:00
committed by GitHub
parent aaa5e61cad
commit 2730e4d071
33 changed files with 709 additions and 5 deletions
+2
View File
@@ -88,6 +88,7 @@ export const EXAMPLES: Record<string, unknown> = {
"subInfoNodeEnable": false,
"subJsonAlwaysArray": false,
"subJsonAutoDetect": false,
"subJsonDns": "",
"subJsonEnable": false,
"subJsonFinalMask": "",
"subJsonMux": "",
@@ -229,6 +230,7 @@ export const EXAMPLES: Record<string, unknown> = {
"subInfoNodeEnable": false,
"subJsonAlwaysArray": false,
"subJsonAutoDetect": false,
"subJsonDns": "",
"subJsonEnable": false,
"subJsonFinalMask": "",
"subJsonMux": "",
+8
View File
@@ -282,6 +282,9 @@ export const SCHEMAS: Record<string, unknown> = {
"subJsonAutoDetect": {
"type": "boolean"
},
"subJsonDns": {
"type": "string"
},
"subJsonEnable": {
"type": "boolean"
},
@@ -519,6 +522,7 @@ export const SCHEMAS: Record<string, unknown> = {
"subInfoNodeEnable",
"subJsonAlwaysArray",
"subJsonAutoDetect",
"subJsonDns",
"subJsonEnable",
"subJsonFinalMask",
"subJsonMux",
@@ -870,6 +874,9 @@ export const SCHEMAS: Record<string, unknown> = {
"subJsonAutoDetect": {
"type": "boolean"
},
"subJsonDns": {
"type": "string"
},
"subJsonEnable": {
"type": "boolean"
},
@@ -1114,6 +1121,7 @@ export const SCHEMAS: Record<string, unknown> = {
"subInfoNodeEnable",
"subJsonAlwaysArray",
"subJsonAutoDetect",
"subJsonDns",
"subJsonEnable",
"subJsonFinalMask",
"subJsonMux",
+2
View File
@@ -96,6 +96,7 @@ export interface AllSetting {
subInfoNodeEnable: boolean;
subJsonAlwaysArray: boolean;
subJsonAutoDetect: boolean;
subJsonDns: string;
subJsonEnable: boolean;
subJsonFinalMask: string;
subJsonMux: string;
@@ -238,6 +239,7 @@ export interface AllSettingView {
subInfoNodeEnable: boolean;
subJsonAlwaysArray: boolean;
subJsonAutoDetect: boolean;
subJsonDns: string;
subJsonEnable: boolean;
subJsonFinalMask: string;
subJsonMux: string;
+2
View File
@@ -112,6 +112,7 @@ export const AllSettingSchema = z.object({
subInfoNodeEnable: z.boolean(),
subJsonAlwaysArray: z.boolean(),
subJsonAutoDetect: z.boolean(),
subJsonDns: z.string(),
subJsonEnable: z.boolean(),
subJsonFinalMask: z.string(),
subJsonMux: z.string(),
@@ -255,6 +256,7 @@ export const AllSettingViewSchema = z.object({
subInfoNodeEnable: z.boolean(),
subJsonAlwaysArray: z.boolean(),
subJsonAutoDetect: z.boolean(),
subJsonDns: z.string(),
subJsonEnable: z.boolean(),
subJsonFinalMask: z.string(),
subJsonMux: z.string(),
+1
View File
@@ -70,6 +70,7 @@ export class AllSetting {
subJsonMux = '';
subJsonRules = '';
subJsonRoutingRules = '';
subJsonDns = '';
subJsonFinalMask = '';
subJsonObservatory = '';
subThemeDir = '';
@@ -241,6 +241,18 @@ export default function SubscriptionFormatsTab({
autoSize={{ minRows: 2, maxRows: 6 }}
/>
</SettingListItem>
<SettingListItem
paddings="small"
title={t('pages.settings.subJsonDns')}
description={t('pages.settings.subJsonDnsDesc')}
>
<Input.TextArea
value={allSetting.subJsonDns}
placeholder='{"servers": ["https://dns.google/dns-query", "tls://1.1.1.1"]}'
onChange={(e) => updateSetting({ subJsonDns: e.target.value })}
autoSize={{ minRows: 2, maxRows: 6 }}
/>
</SettingListItem>
</Card>
)}
{allSetting.subClashEnable && (
+1
View File
@@ -75,6 +75,7 @@ export const AllSettingSchema = z
subJsonMux: z.string().optional(),
subJsonRules: z.string().optional(),
subJsonRoutingRules: z.string().optional(),
subJsonDns: z.string().optional(),
subJsonFinalMask: z.string().optional(),
subJsonObservatory: z.string().optional(),
subHideSettings: z.boolean().optional(),