@twinsearth/dsh-2xray 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.8 — 2026-10-29
4
+
5
+ ### 修复
6
+
7
+ - `cn-direct` 模式设置 `routing.domainStrategy: IPIfNonMatch`:未被 `geosite:cn` 命中的域名会解析为 IP 再匹配 `geoip:cn`,国内站点不再漏判。
8
+ - `XRAY_LOCATION_ASSET` 显式传入 `run` 与 `run -test` 的 spawn 环境:服务启动器会清理环境,之前 geodata 路径可能丢失,导致 `geoip:cn`/`geosite:cn` 静默失效。
9
+ - 启动与 `xray_validate_config` 前都先复制 `geoip.dat`/`geosite.dat` 到数据目录;缺失时日志明确告警。
10
+
3
11
  ## 0.2.7 — 2026-10-29
4
12
 
5
13
  ### 新增
package/lib/supervisor.js CHANGED
@@ -279,7 +279,9 @@ export class Supervisor {
279
279
  argv: [binary.path, 'run', '-c', configPath],
280
280
  cwd: this.dataDir,
281
281
  graceMs: Math.max(500, lifecycle.stopGraceMs),
282
- env: {},
282
+ // geodata is copied next to the managed config; without this env the
283
+ // scrubbed service environment makes geoip:cn/geosite:cn fail silently.
284
+ env: this.dataDir ? { XRAY_LOCATION_ASSET: this.dataDir } : {},
283
285
  })
284
286
  } catch (error) {
285
287
  this.#setState('failed', 'spawn-failed', String(error))
@@ -455,7 +457,7 @@ export class Supervisor {
455
457
  argv: [binaryPath, 'run', '-test', '-c', configPath],
456
458
  cwd: this.dataDir ?? process.cwd(),
457
459
  graceMs: 2000,
458
- env: {},
460
+ env: this.dataDir ? { XRAY_LOCATION_ASSET: this.dataDir } : {},
459
461
  })
460
462
  } catch (error) {
461
463
  return { ok: false, detail: String(error) }
@@ -503,6 +505,11 @@ export class Supervisor {
503
505
  }
504
506
  }
505
507
 
508
+ const routing = this.config.routing ?? {}
509
+ if (routing.mode === 'cn-direct' || routing.geositeCn || routing.blockAds) {
510
+ ensureXrayAssets({ binaryPath: binary.path, dataDir: this.dataDir, logger: this.#logger })
511
+ }
512
+
506
513
  const built = buildXrayConfig(this.config)
507
514
  const text = JSON.stringify(built, null, 2)
508
515
  const file = join(this.dataDir, 'conf', `validate-${Date.now()}-${process.pid}.json`)
@@ -231,7 +231,9 @@ export function buildXrayConfig(config) {
231
231
  inbounds,
232
232
  outbounds,
233
233
  routing: {
234
- domainStrategy: 'AsIs',
234
+ // cn-direct relies on resolving unmatched domains to match geoip:cn;
235
+ // global/whitelist stay AsIs so no extra DNS lookups are introduced.
236
+ domainStrategy: mode === 'cn-direct' ? 'IPIfNonMatch' : 'AsIs',
235
237
  rules,
236
238
  ...(balancers.length > 0 ? { balancers } : {}),
237
239
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/dsh-2xray",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "private": false,
5
5
  "description": "DSH-2Xray: supervises xray-core and routes DSH process-wide HTTP(S) traffic through its local inbound, with optional multi-node gRPC control plane",
6
6
  "type": "module",
@@ -0,0 +1,35 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { ensureXrayAssets } from '../lib/assets.js'
7
+
8
+ test('geodata next to a v2rayN binary is copied into the managed data dir', () => {
9
+ const root = mkdtempSync(join(tmpdir(), 'dsh-xray-assets-'))
10
+ try {
11
+ const bin = join(root, 'v2rayN-windows-64', 'bin')
12
+ const xrayDir = join(bin, 'xray')
13
+ mkdirSync(xrayDir, { recursive: true })
14
+ writeFileSync(join(bin, 'geoip.dat'), 'geoip')
15
+ writeFileSync(join(bin, 'geosite.dat'), 'geosite')
16
+ const dataDir = join(root, 'data')
17
+ const result = ensureXrayAssets({ binaryPath: join(xrayDir, 'xray.exe'), dataDir })
18
+ assert.deepEqual(result.copied.sort(), ['geoip.dat', 'geosite.dat'])
19
+ assert.equal(existsSync(join(dataDir, 'geoip.dat')), true)
20
+ assert.equal(existsSync(join(dataDir, 'geosite.dat')), true)
21
+ } finally {
22
+ rmSync(root, { recursive: true, force: true })
23
+ }
24
+ })
25
+
26
+ test('missing geodata is reported rather than silently ignored', () => {
27
+ const root = mkdtempSync(join(tmpdir(), 'dsh-xray-assets-'))
28
+ try {
29
+ const result = ensureXrayAssets({ binaryPath: join(root, 'xray.exe'), dataDir: join(root, 'data') })
30
+ assert.deepEqual(result.copied, [])
31
+ assert.deepEqual(result.missing.sort(), ['geoip.dat', 'geosite.dat'])
32
+ } finally {
33
+ rmSync(root, { recursive: true, force: true })
34
+ }
35
+ })
@@ -26,6 +26,7 @@ test('global mode keeps the proxy catch-all and private direct rule', () => {
26
26
  const { config, errors } = base()
27
27
  assert.deepEqual(errors, [])
28
28
  const rules = rulesOf(config)
29
+ assert.equal(buildXrayConfig(config).routing.domainStrategy, 'AsIs')
29
30
  assert.ok(rules.some((rule) => Array.isArray(rule.ip) && rule.ip.includes('10.0.0.0/8')))
30
31
  const catchAll = rules[rules.length - 1]
31
32
  assert.equal(catchAll.network, 'tcp,udp')
@@ -35,6 +36,7 @@ test('global mode keeps the proxy catch-all and private direct rule', () => {
35
36
  test('cn-direct adds geosite:cn and geoip:cn direct rules', () => {
36
37
  const { config } = base({ routing: { mode: 'cn-direct' } })
37
38
  const rules = rulesOf(config)
39
+ assert.equal(buildXrayConfig(config).routing.domainStrategy, 'IPIfNonMatch')
38
40
  assert.ok(rules.some((rule) => rule.domain?.includes('geosite:cn')))
39
41
  assert.ok(rules.some((rule) => rule.ip?.includes('geoip:cn')))
40
42
  assert.equal(rules[rules.length - 1].outboundTag, 'proxy')