feat(config): 涉密内容外置到配置目录(档案 140)

把散落在代码里的真实部署值统一收进 config/,代码改为引用配置,
使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。

新增 config/:platform.env.example(模板)· load.sh(shell 加载器)·
index.cjs(node 加载器)· README.md(键一览与优先级)。
真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。

TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用):
platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。
config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由
DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径,
src/** 注释中性化 116 行/53 文件。

scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径
一律改从配置取;web/wake.html 的注册域白名单改为运行时从
location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737
保留值,并把「内置种子必须为空」固化为回归断言。

取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有);
全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归
(/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
This commit is contained in:
admin committed 2026-09-19 15:12:19 +08:00
1 parent 9c2e7975ac
commit 452924d89c
100 files changed
+1167 -453

No files matched your search

+1 -1
View File
@@ -60,7 +60,7 @@ function renderBrand({ search = '', cookie = '', language = 'en-US' } = {}) {
addEventListener: () => {},
},
navigator: { language, languages: [language] },
location: { search, href: `https://ai1net.com/login.html${search}`, reload: () => {} },
location: { search, href: `https://example.net/login.html${search}`, reload: () => {} },
window: {},
}
sandbox.window = sandbox
+4 -4
View File
@@ -33,16 +33,16 @@ import {
*/
const REAL_DESC =
'/usr/bin/bwrap --ro-bind /usr /usr --tmpfs /etc ' +
'--bind /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0/tmp /tmp ' +
'--bind /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0 /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0 ' +
'--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/tmp /tmp ' +
'--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 ' +
'--unshare-pid ' +
'--chdir /var/lib/dshs/users/4092b965-2f68-4977-9989-68b3966f7df0/ws ' +
'--chdir /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/ws ' +
'-- setpriv --reuid 100002 --regid 100002 --clear-groups ' +
'/usr/bin/dsh --profile web --host 127.0.0.1 --port 21000'
const UID = 100002
const USER = '4092b965-2f68-4977-9989-68b3966f7df0'
const FOLDER = `/var/lib/dshs/users/${USER}/ws`
const FOLDER = `/var/lib/dsh-test/users/${USER}/ws`
/* ── R1–R5:scope 名解析(⛔ 只认本平台自己的形态) ─────────────────────────── */
+5 -5
View File
@@ -116,7 +116,7 @@ test('A2 parseReachability 的第一个参数是**逻辑名**:网络段由它
assert.equal(r.networkId, U_A)
assert.equal(agentBaseUrl(r), 'http://127.0.0.1:19000', '取址与 S0/S2 逐字一致(网络维度不进地址)')
// 裸 hostId 仍兼容 ⇒ 落 ops(过渡期:现网所有调用方一个字都不用改)
assert.equal(parseReachability('w-47', 'http://127.0.0.1:19100', VIA_LOCAL).networkId, OPS_NETWORK)
assert.equal(parseReachability('w-1', 'http://127.0.0.1:19100', VIA_LOCAL).networkId, OPS_NETWORK)
// 非法网络段 ⇒ **抛**(配错别伪装成"这张网里没有它")
assert.throws(() => parseReachability('UPPER/w-1', 'http://x:1', VIA_LOCAL), /网络段/)
})
@@ -148,17 +148,17 @@ test('A3 两张网各有一台同名 w-1 ⇒ 解析各查各的(键是逻辑
/* ─────────── A4:via=relay 时禁止回落到 endpoint ─────────── */
test('A4 via=relay 且解析不出落点 ⇒ **抛**(不许回落到 endpoint = relay 落点)', () => {
const relayHost = { hostId: 'w-106', agentUrl: 'http://127.0.0.1:19000', via: VIA_RELAY }
const relayHost = { hostId: 'w-2', agentUrl: 'http://127.0.0.1:19000', via: VIA_RELAY }
assert.throws(() => agentBaseUrlOf(relayHost), /拒绝回落到 endpoint/)
// 一旦解析成功 ⇒ 照常取址,且**优先**于 endpoint
const ok = {
...relayHost,
reachability: { hostId: 'w-106', networkId: OPS_NETWORK, via: VIA_RELAY, address: '127.0.0.1:42067', scheme: 'http' },
reachability: { hostId: 'w-2', networkId: OPS_NETWORK, via: VIA_RELAY, address: '127.0.0.1:42067', scheme: 'http' },
}
assert.equal(agentBaseUrlOf(ok), 'http://127.0.0.1:42067')
// 非 relay 语义(同机直连 / ssh 隧道)照旧回落 endpoint —— 这条不能被误伤
assert.equal(agentBaseUrlOf({ hostId: 'w-47', agentUrl: 'http://127.0.0.1:19100', via: VIA_LOCAL }), 'http://127.0.0.1:19100')
assert.equal(agentBaseUrlOf({ hostId: 'w-106', agentUrl: 'http://127.0.0.1:19000' }), 'http://127.0.0.1:19000')
assert.equal(agentBaseUrlOf({ hostId: 'w-1', agentUrl: 'http://127.0.0.1:19100', via: VIA_LOCAL }), 'http://127.0.0.1:19100')
assert.equal(agentBaseUrlOf({ hostId: 'w-2', agentUrl: 'http://127.0.0.1:19000' }), 'http://127.0.0.1:19000')
})
/* ─────────── A5:控制面侧的跨网门(RelayDialer 口池) ─────────── */
+29 -24
View File
@@ -126,7 +126,7 @@ function refusingFetch(calls) {
test('B1 纯函数:载荷稳定、路径派生、地址清洗', () => {
// 同源约定:引导地址(中继入口)→ 目录端点 = 同 origin + 固定路径
assert.equal(directoryUrlFor('https://ai1net.com/dshs-relay'), `https://ai1net.com${DIRECTORY_PATH}`)
assert.equal(directoryUrlFor('https://example.net/dshs-relay'), `https://example.net${DIRECTORY_PATH}`)
assert.equal(directoryUrlFor('http://127.0.0.1:8080/dshs-relay'), `http://127.0.0.1:8080${DIRECTORY_PATH}`)
// 已经是目录地址 ⇒ 原样
assert.equal(directoryUrlFor(`https://a.example${DIRECTORY_PATH}`), `https://a.example${DIRECTORY_PATH}`)
@@ -134,7 +134,7 @@ test('B1 纯函数:载荷稳定、路径派生、地址清洗', () => {
assert.equal(directoryUrlFor('wss://a.example/dshs-relay'), `https://a.example${DIRECTORY_PATH}`)
// 中继地址归一化:只改协议、⛔ 不动 path(`/dshs-relay` 是 nginx location 的判据)
assert.equal(toRelayUrl('https://ai1net.com/dshs-relay'), 'wss://ai1net.com/dshs-relay')
assert.equal(toRelayUrl('https://example.net/dshs-relay'), 'wss://example.net/dshs-relay')
assert.equal(toRelayUrl('http://127.0.0.1:20080/dshs-relay'), 'ws://127.0.0.1:20080/dshs-relay')
assert.equal(toRelayUrl('wss://a.example/x'), 'wss://a.example/x')
assert.equal(toRelayUrl('file:///etc/passwd'), undefined, '非 http/ws 协议必须拒绝')
@@ -178,20 +178,21 @@ test('B1 纯函数:载荷稳定、路径派生、地址清洗', () => {
'http://100.64.7.7/dshs-relay',
'http://relaybox/dshs-relay',
'http://[::1]/dshs-relay',
'https://ai1net.com/dshs-relay',
'https://example.net/dshs-relay',
'https://relay.example.com/dshs-relay',
]),
['https://ai1net.com/dshs-relay', 'https://relay.example.com/dshs-relay'],
['https://example.net/dshs-relay', 'https://relay.example.com/dshs-relay'],
)
// 常量位:只锚一条、指向**已持证书的门户**(第二地域留空 ⇒ 不新增域名成本)
assert.equal(DEFAULT_OVERLAY_SEED, 'https://ai1net.com/dshs-relay')
// ⛔ 内置种子**刻意留空** —— 种子是**具体部署的入口地址**,一律由配置提供
// (`DSHS_OVERLAY_BOOTSTRAP_SEEDS`,见 `config/platform.env`)⇒ 代码内不留真实域名。
assert.equal(DEFAULT_OVERLAY_SEED, '', '内置种子不得写死生产域名')
// 语义去重:`wss://host/dshs-relay` 与 `https://host/dshs-relay` 是**同一个端点**(都走 443)
// ⇒ 目录里只该出现第一条(否则读目录的人会以为有两个中继)
assert.deepEqual(
publicRelayEntries(['wss://ai1net.com/dshs-relay', 'https://ai1net.com/dshs-relay']),
['wss://ai1net.com/dshs-relay'],
publicRelayEntries(['wss://example.net/dshs-relay', 'https://example.net/dshs-relay']),
['wss://example.net/dshs-relay'],
)
// …而 `http://`(80)与 `wss://`(443)**不是**同一个端点 ⇒ 两条都留
assert.deepEqual(publicRelayEntries(['wss://a.example/x', 'http://a.example/x']), [
@@ -210,7 +211,7 @@ test('B1 纯函数:载荷稳定、路径派生、地址清洗', () => {
test('B2 验签:正例通过;改内容 / 换密钥 / 无受信密钥一律拒绝', () => {
const keys = makeKeys()
const other = makeKeys()
const origin = 'https://ai1net.com/dshs-relay'
const origin = 'https://example.net/dshs-relay'
const { doc, sig } = signedDoc(origin, keys.privatePem)
// 正例:PEM 与**裸 32 字节 hex**两条解析路径都要能验
@@ -248,7 +249,7 @@ test('B2 验签:正例通过;改内容 / 换密钥 / 无受信密钥一律
test('B3 决策:env 压制一切 / 新鲜缓存不联网 / 取不到则降级', async () => {
const keys = makeKeys()
const seed = 'https://ai1net.com/dshs-relay'
const seed = 'https://example.net/dshs-relay'
// ① env 显式 ⇒ 压制引导链(**一次网络都不发**)
{
@@ -279,7 +280,7 @@ test('B3 决策:env 压制一切 / 新鲜缓存不联网 / 取不到则降级'
fetchImpl: refusingFetch(calls),
})
assert.equal(r.source, 'cache')
assert.equal(r.url, 'wss://ai1net.com/dshs-relay')
assert.equal(r.url, 'wss://example.net/dshs-relay')
assert.deepEqual(calls, [], '新鲜缓存不许联网')
} finally {
rmSync(dir, { recursive: true, force: true })
@@ -298,7 +299,7 @@ test('B3 决策:env 压制一切 / 新鲜缓存不联网 / 取不到则降级'
fetchImpl: refusingFetch(calls),
})
assert.equal(r.source, 'seed-fallback')
assert.equal(r.url, 'wss://ai1net.com/dshs-relay')
assert.equal(r.url, 'wss://example.net/dshs-relay')
assert.ok(calls.length >= 1, '应当尝试过取目录')
assert.equal(existsSync(file), false, '取不到目录**不许**留下缓存')
} finally {
@@ -320,7 +321,7 @@ test('B3 决策:env 压制一切 / 新鲜缓存不联网 / 取不到则降级'
fetchImpl: refusingFetch([]),
})
assert.equal(r.source, 'stale-cache')
assert.equal(r.url, 'wss://ai1net.com/dshs-relay')
assert.equal(r.url, 'wss://example.net/dshs-relay')
// 缓存**被改坏 / 换了密钥** ⇒ 当作没有缓存(读也要验签)
assert.equal(readCachedDirectory(file, [makeKeys().publicPem], Date.now()), undefined)
} finally {
@@ -496,13 +497,16 @@ test('B6 签名不对的目录:既不写缓存也不采用(有旧缓存则
test('B7 端点契约:只公布公网地址、签名可被受信公钥验过、无密钥即不可用', async () => {
const keys = makeKeys()
// 夹具用引导地址(RFC 2606 保留域):**不等于**内置种子常量 ——
// 后者刻意留空(生产入口地址一律由配置提供),所以这里必须自带一个公网形态的夹具。
const FIXTURE_SEED = 'https://relay.example.net/dshs-relay'
// 服务端逻辑:候选 = 显式中继入口 + 种子;**过滤回环/私网**后再组装
const relays = publicRelayEntries(['ws://127.0.0.1:20080/dshs-relay', DEFAULT_OVERLAY_SEED])
const bootstrap = publicRelayEntries([DEFAULT_OVERLAY_SEED])
const relays = publicRelayEntries(['ws://127.0.0.1:20080/dshs-relay', FIXTURE_SEED])
const bootstrap = publicRelayEntries([FIXTURE_SEED])
const doc = buildDirectoryDocument({ relays, bootstrap, network: 'ops', now: Date.now() })
const sig = signDirectory(doc, keys.privatePem)
assert.deepEqual(relays, [DEFAULT_OVERLAY_SEED], '回环地址不得出现在目录里')
assert.deepEqual(relays, [FIXTURE_SEED], '回环地址不得出现在目录里')
assert.deepEqual(Object.keys(doc).sort(), [
'bootstrap',
'issuedAt',
@@ -539,7 +543,8 @@ test('S0 夹具自检:缓存读写是字节级可复现的(避免"测试夹
const { dir, file } = tmpCacheFile()
try {
const now = Date.now()
const { doc, sig } = signedDoc(DEFAULT_OVERLAY_SEED, keys.privatePem, { now })
const FIXTURE_SEED = 'https://relay.example.net/dshs-relay'
const { doc, sig } = signedDoc(FIXTURE_SEED, keys.privatePem, { now })
writeCachedDirectory(file, doc, sig, now)
const raw = readFileSync(file, 'utf8')
const parsed = JSON.parse(raw)
@@ -685,8 +690,8 @@ test('序④·L1-E 撤销后回到未配状态(可回滚)', async () => {
* **同源优先**(序④):没有它,「多一条兜底入口」落不成「CF / 门户 conf 挂时还能连」——
* `relays[]` 首位 = 主入口,客户端会一直去连它,兜底项永远轮不到。
*/
const FB_MAIN = 'https://ai1net.com/dshs-relay'
const FB_ALT = 'https://relay-direct.ai1net.com/dshs-relay'
const FB_MAIN = 'https://example.net/dshs-relay'
const FB_ALT = 'https://relay-direct.example.net/dshs-relay'
/** 造一份"两个入口都在"的目录,并只让**兜底 origin** 答得出(主 origin 抛错)。 */
function twoEntryDoc(keys) {
@@ -704,7 +709,7 @@ test('序④·L1-F 同源优先:主 origin 不可达时采用兜底 origin 的
const logs = []
const fetchImpl = async (url) => {
calls.push(String(url))
if (String(url).startsWith('https://ai1net.com/')) throw new Error('cf unreachable')
if (String(url).startsWith('https://example.net/')) throw new Error('cf unreachable')
return new Response(body, { status: 200, headers: { 'content-type': 'application/json' } })
}
const r = await resolveOverlayRelay({
@@ -717,14 +722,14 @@ test('序④·L1-F 同源优先:主 origin 不可达时采用兜底 origin 的
// ① 逐个 origin 试,主 origin 被拒后才到兜底
assert.equal(calls.length, 2)
assert.ok(
logs.some((l) => l.includes('拒绝 https://ai1net.com/dshs-overlay/bootstrap')),
logs.some((l) => l.includes('拒绝 https://example.net/dshs-overlay/bootstrap')),
'缺"逐 origin 拒绝原因"这一行',
)
// ② 采用的是**兜底项**,而不是 relays[] 首位(这是本单 D6 的实质判据)
assert.equal(r.source, 'seed-directory')
assert.equal(r.url, 'wss://relay-direct.ai1net.com/dshs-relay')
assert.equal(r.url, 'wss://relay-direct.example.net/dshs-relay')
assert.ok(
logs.some((l) => l.includes('同源优先') && l.includes('wss://relay-direct.ai1net.com/dshs-relay')),
logs.some((l) => l.includes('同源优先') && l.includes('wss://relay-direct.example.net/dshs-relay')),
'缺"为什么走了兜底"这一行(可解释性)',
)
})
@@ -742,6 +747,6 @@ test('序④·L1-G 主 origin 通时选择与今天逐字一致(relays[] 首
fetchImpl,
log: (l) => logs.push(l),
})
assert.equal(r.url, 'wss://ai1net.com/dshs-relay')
assert.equal(r.url, 'wss://example.net/dshs-relay')
assert.equal(logs.some((l) => l.includes('同源优先')), false, '首位命中时不该有多余日志')
})
+13 -13
View File
@@ -73,8 +73,8 @@ const serverPath = join(root, 'src', 'net', 'relay', 'server.ts')
const dialers = () =>
normalizeDialers(
new Map([
['ops', new Set(['manager', 'w-106'])],
['u:5', new Set(['w-106'])],
['ops', new Set(['manager', 'w-2'])],
['u:5', new Set(['w-2'])],
]),
)
@@ -89,7 +89,7 @@ async function deadPort() {
const candidate = (over = {}) =>
encodeDirectMessage({
hostId: 'w-106',
hostId: 'w-2',
network: 'ops',
addrs: [{ host: '10.0.0.9', port: PUNCH_PORT_BASE + 1 }],
ts: Date.now(),
@@ -126,7 +126,7 @@ test('T1 开关:缺省=开|开集/关集|非法值 ⇒ null(⛔ 不静
// ── T2 · 关闭 ⇒ 零 socket / 零候选 ──────────────────────────────────────────────
test('T2 关闭 ⇒ 零 UDP socket + 零候选;开启才真的开', async () => {
const ctxOf = { dialers: dialers(), from: { network: 'ops', hostId: 'w-106' }, selfHostId: 'manager' }
const ctxOf = { dialers: dialers(), from: { network: 'ops', hostId: 'w-2' }, selfHostId: 'manager' }
const off = new DirectPath({ switchState: resolveDirectSwitch({ [DIRECT_ENV_KEY]: 'false' }), deadlineMs: 300 })
const v = off.offerCandidate(candidate(), ctxOf)
assert.equal(v.ok, false)
@@ -153,7 +153,7 @@ test('T2 关闭 ⇒ 零 UDP socket + 零候选;开启才真的开', async ()
// ── T3 · 候选准入矩阵 ───────────────────────────────────────────────────────────
test('T3 候选准入:同网+白名单内接受;其余**逐类具名**拒绝;静默拒绝 = 0', () => {
const led = new CandidateLedger()
const inOps = { dialers: dialers(), from: { network: 'ops', hostId: 'w-106' }, selfHostId: 'manager' }
const inOps = { dialers: dialers(), from: { network: 'ops', hostId: 'w-2' }, selfHostId: 'manager' }
const expectOk = (raw, ctx = inOps) => {
const v = led.judge(raw, ctx)
assert.equal(v.ok, true, `应接受:${v.ok ? '' : v.reason} ${v.ok ? '' : v.detail}`)
@@ -168,7 +168,7 @@ test('T3 候选准入:同网+白名单内接受;其余**逐类具名**拒
expectOk(candidate())
expectOk(candidate({ addrs: [{ host: '10.0.0.9', port: PUNCH_PORT_BASE + 1 }, { host: '2001:db8::1', port: PUNCH_PORT_BASE + 2 }] }))
// 同名跨网**互不可见**:同一 hostId 在另一张网里是合法身份
expectOk(candidate({ network: 'u:5' }), { dialers: dialers(), from: { network: 'u:5', hostId: 'w-106' }, selfHostId: 'w-106' })
expectOk(candidate({ network: 'u:5' }), { dialers: dialers(), from: { network: 'u:5', hostId: 'w-2' }, selfHostId: 'w-2' })
expectReject(candidate({ network: 'u:5' }), 'cross-network')
expectReject(candidate({ hostId: 'w-999' }), 'not-self-candidate')
@@ -177,7 +177,7 @@ test('T3 候选准入:同网+白名单内接受;其余**逐类具名**拒
expectReject('{"kind":"DIRECT_CANDIDATE"}', 'bad-shape')
expectReject('not json at all', 'bad-shape')
expectReject(
JSON.stringify({ kind: 'DIRECT_CANDIDATE', hostId: 'w-106', network: 'ops', addrs: [{ host: '10.0.0.9', port: PUNCH_PORT_BASE + 1 }], nodeKey: 'deadbeef' }),
JSON.stringify({ kind: 'DIRECT_CANDIDATE', hostId: 'w-2', network: 'ops', addrs: [{ host: '10.0.0.9', port: PUNCH_PORT_BASE + 1 }], nodeKey: 'deadbeef' }),
'secret-field',
)
expectReject(candidate({ addrs: [{ host: 'example.com', port: 80 }] }), 'bad-address')
@@ -196,9 +196,9 @@ test('T3 候选准入:同网+白名单内接受;其余**逐类具名**拒
// 每一条拒绝**都有具名原因**
for (const r of snap.rejected) assert.ok(typeof r.reason === 'string' && r.reason !== '')
// 白名单是**按网络分桶**的:拿 ops 的桶查 u:5 的同名 hostId 必须为假
assert.equal(isAllowedDialer(dialers(), 'ops', 'w-106'), true)
assert.equal(isAllowedDialer(dialers(), 'u:5', 'w-106'), true)
assert.equal(isAllowedDialer(dialers(), 'u:7', 'w-106'), false)
assert.equal(isAllowedDialer(dialers(), 'ops', 'w-2'), true)
assert.equal(isAllowedDialer(dialers(), 'u:5', 'w-2'), true)
assert.equal(isAllowedDialer(dialers(), 'u:7', 'w-2'), false)
assert.equal(isAllowedDialer(dialers(), 'ops', 'w-999'), false)
// 地址形状:IPv4/IPv6 收,主机名不收
assert.equal(isValidAddress({ host: '10.0.0.9', port: 21100 }), true)
@@ -236,7 +236,7 @@ test('T4 准入策略**复用** server.ts 的那一条(⛔ 防两处写分叉
// ── T5 · 打洞成功路径(真 dgram + NAT 模拟) ────────────────────────────────────
test('T5 打洞成功路径:双向都成立才算直连(punchOk ≥ 1)', async () => {
const r = await runPunchPair({ aPeer: 'ops/w-47', bPeer: 'ops/w-106', deadlineMs: 2500 }, { sleep })
const r = await runPunchPair({ aPeer: 'ops/w-1', bPeer: 'ops/w-2', deadlineMs: 2500 }, { sleep })
assert.equal(r.a.bidirectional, true, `A 侧应双向成立:${r.a.detail}`)
assert.equal(r.b.bidirectional, true, `B 侧应双向成立:${r.b.detail}`)
assert.equal(r.a.reason, 'ok')
@@ -248,7 +248,7 @@ test('T5 打洞成功路径:双向都成立才算直连(punchOk ≥ 1)', a
// ── T6 · 单向不算直连 ───────────────────────────────────────────────────────────
test('T6 单向 ⇒ 判死 one-way(⛔ 不许把单向当成功)', async () => {
const r = await runPunchPair({ aPeer: 'ops/w-47', bPeer: 'ops/w-106', deadlineMs: 1200, oneWay: 'a' }, { sleep })
const r = await runPunchPair({ aPeer: 'ops/w-1', bPeer: 'ops/w-2', deadlineMs: 1200, oneWay: 'a' }, { sleep })
assert.equal(r.bidirectional, false)
assert.equal(r.a.reason, 'one-way', `A 侧(只能出不能进)应判 one-way:${r.a.detail}`)
assert.equal(r.a.bidirectional, false)
@@ -397,7 +397,7 @@ test('T10 提示文案必须**可行动**:三段齐 + 含开关键与关值'
// ⛔ 禁止只写"已启用直连"
assert.ok(!/^已启用直连$/.test(text.trim()))
// 编码侧的结构性防线:**只吃白名单字段** ⇒ 多给的任何字段(含凭据)都进不了载荷
const encoded = JSON.parse(encodeDirectMessage({ hostId: 'w-106', network: 'ops', addrs: [{ host: '10.0.0.9', port: 1 }], secret: 'x' }))
const encoded = JSON.parse(encodeDirectMessage({ hostId: 'w-2', network: 'ops', addrs: [{ host: '10.0.0.9', port: 1 }], secret: 'x' }))
assert.deepEqual(Object.keys(encoded).sort(), ['addrs', 'hostId', 'kind', 'network', 'ts'])
assert.equal(encoded.secret, undefined, '⛔ 载荷里不可能夹带凭据字段(构造侧结构性排除)')
})
+6 -6
View File
@@ -342,10 +342,10 @@ test('B12 指纹:每机一把 ⇒ 指纹互不相同;解析不出来就 unde
test('C1 旧写法(裸 hostId + hex 串)⇒ 归入运维网 ops(现网配置一字不改)', () => {
const s = randomBytes(32).toString('hex')
const map = parseKeysInline(`w-47:${s}`)
// 键 = **逻辑名**(序③)⇒ 裸 `w-47` 归一成 `ops/w-47`
assert.deepEqual(map.get('ops/w-47'), { network: OPS_NETWORK, secret: s })
assert.equal(describeKeyEntry('w-47', map.get('ops/w-47')), 'w-47', 'ops 下省略网络前缀(日志读法与 R5 一致)')
const map = parseKeysInline(`w-1:${s}`)
// 键 = **逻辑名**(序③)⇒ 裸 `w-1` 归一成 `ops/w-1`
assert.deepEqual(map.get('ops/w-1'), { network: OPS_NETWORK, secret: s })
assert.equal(describeKeyEntry('w-1', map.get('ops/w-1')), 'w-1', 'ops 下省略网络前缀(日志读法与 R5 一致)')
})
test('C2 新写法:`网/hostId:secret` 与 `网:hostId:secret` 都切得出网络(切点是**最后一个冒号**)', () => {
@@ -515,9 +515,9 @@ test('E1 keys 文件:新旧写法可**共存**(原地升级 ⇒ 可原子替
const s1 = randomBytes(32).toString('hex')
const s2 = randomBytes(32).toString('hex')
const file = join(dir, 'relay-keys.json')
writeFileSync(file, JSON.stringify({ 'w-47': s1, w106: { network: OPS_NETWORK, secret: s2 } }))
writeFileSync(file, JSON.stringify({ 'w-1': s1, w106: { network: OPS_NETWORK, secret: s2 } }))
const map = loadKeysFile(file)
assert.deepEqual(map.get('ops/w-47'), { network: OPS_NETWORK, secret: s1 })
assert.deepEqual(map.get('ops/w-1'), { network: OPS_NETWORK, secret: s1 })
assert.deepEqual(map.get('ops/w106'), { network: OPS_NETWORK, secret: s2 })
// 非法网络段 ⇒ **装载即抛**("配置错就炸",不变成运行期的静默拒绝)
+19 -19
View File
@@ -153,7 +153,7 @@ test('U1 逻辑名唯一入口:旧形态 / 新形态 / `u:<租户>` 里的冒
// ① 规范形态
assert.equal(logicalName('u:5', 'w-1'), 'u:5/w-1')
assert.deepEqual(parseLogicalName('ops/manager'), { network: 'ops', hostId: 'manager' })
assert.deepEqual(parseLogicalName('u:5/w-106'), { network: 'u:5', hostId: 'w-106' })
assert.deepEqual(parseLogicalName('u:5/w-2'), { network: 'u:5', hostId: 'w-2' })
// ② 兼容形态:`network:hostId`(运维习惯写法)
assert.deepEqual(parseLogicalName('ops:manager'), { network: 'ops', hostId: 'manager' })
@@ -163,7 +163,7 @@ test('U1 逻辑名唯一入口:旧形态 / 新形态 / `u:<租户>` 里的冒
// ③ 旧形态(R5 时代的扁平 hostId)⇒ 落在运维网,**旧配置照旧可用**
assert.deepEqual(parseLogicalName('manager'), { network: OPS_NETWORK, hostId: 'manager' })
assert.deepEqual(parseLogicalName('w-106'), { network: OPS_NETWORK, hostId: 'w-106' })
assert.deepEqual(parseLogicalName('w-2'), { network: OPS_NETWORK, hostId: 'w-2' })
// ④ 非法 ⇒ **抛**(配置错就炸,不静默变成"谁也没匹配上")
assert.throws(() => parseLogicalName('u:5'), /网络段/)
@@ -205,8 +205,8 @@ test('U2 旧客户端(不声明 network)+ 旧扁平白名单 ⇒ 一切照
const srv = new RelayServer({
port: 0,
keys: new Map([
['w-106', wSecret],
['w-47', w47Secret],
['w-2', wSecret],
['w-1', w47Secret],
['manager', mSecret],
]),
instancePortBase: BASE,
@@ -223,21 +223,21 @@ test('U2 旧客户端(不声明 network)+ 旧扁平白名单 ⇒ 一切照
})
// ① **旧客户端握手**:HELLO 里根本没有 network 字段(= 现网 47/106 上正在跑的那一版)
const { ws: rawWs, frame: ack } = await rawHello(url, 'w-106', wSecret, String(legacyPort), randomBytes(16).toString('hex'))
const { ws: rawWs, frame: ack } = await rawHello(url, 'w-2', wSecret, String(legacyPort), randomBytes(16).toString('hex'))
t.after(() => rawWs.close())
assert.ok(ack !== undefined, '旧客户端必须能注册(否则这次改动就是硬断)')
assert.equal(ack.type, MUX.HELLO_ACK, '旧客户端应拿到 HELLO_ACK')
const parsed = JSON.parse(ack.payload.toString('utf8'))
assert.equal(parsed.network, OPS_NETWORK, 'HELLO_ACK 应回显服务端认定的网 = ops')
assert.equal(parsed.name, logicalName(OPS_NETWORK, 'w-106'))
assert.ok(await waitFor(() => srv.isOnline('w-106')), '默认网络(ops)里应看到它')
assert.equal(parsed.name, logicalName(OPS_NETWORK, 'w-2'))
assert.ok(await waitFor(() => srv.isOnline('w-2')), '默认网络(ops)里应看到它')
assert.ok(
await waitFor(() => srv.localPortOf('w-106', legacyPort) !== undefined),
await waitFor(() => srv.localPortOf('w-2', legacyPort) !== undefined),
'旧客户端照样拿到回环落点(R1–R4 的行为不变)',
)
// ② 不传 `networkId` 的真 client = ops(默认值即现网事实);旧扁平白名单照旧拨得动
const worker = new RelayClient({ url, hostId: 'w-47', secret: w47Secret, ports: [servePort], log: () => {} })
const worker = new RelayClient({ url, hostId: 'w-1', secret: w47Secret, ports: [servePort], log: () => {} })
const dialer = new RelayClient({ url, hostId: 'manager', secret: mSecret, ports: [], dialer: true, log: () => {} })
worker.start()
dialer.start()
@@ -249,14 +249,14 @@ test('U2 旧客户端(不声明 network)+ 旧扁平白名单 ⇒ 一切照
assert.equal(worker.status().network, OPS_NETWORK, '不声明网络 ⇒ 默认 ops(不是空、不是 undefined)')
assert.ok(await waitFor(() => dialer.status().state === 'up'), '拨号方未注册')
assert.deepEqual(srv.status().dialers, ['manager'], '/status 的 dialers 仍按旧写法展示(不破坏既有消费者)')
const duplex = await dialer.openStream('w-47', servePort)
const duplex = await dialer.openStream('w-1', servePort)
assert.equal(await dialRoundTrip(duplex, 'legacy-ok', 'ops:'.length), 'ops:legacy-ok', '字节要真的过去')
duplex.destroy()
})
/* ─────────── U3:跨网隔离是**结构性**的(拒绝点在 relay) ─────────── */
test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay 拒,且**不泄露**目标在哪张网', async (t) => {
test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-2 ⇒ relay 拒,且**不泄露**目标在哪张网', async (t) => {
const wSecret = randomBytes(32).toString('hex')
const opsSecret = randomBytes(32).toString('hex')
const foreignSecret = randomBytes(32).toString('hex')
@@ -266,7 +266,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
const srv = new RelayServer({
port: 0,
keys: new Map([
['w-106', wSecret],
['w-2', wSecret],
['manager', opsSecret],
// 序③:`dt` 真正属于 U_TEST(密钥表说了算)⇒ 它能进自己的网,然后在 **DIAL 那一步**
// 被跨网判据挡住 —— 这才是本用例要测的那道门,而不是『压根没登记』那道。
@@ -286,7 +286,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
})
await srv.start()
const url = `ws://127.0.0.1:${srv.boundPort}${PATH}`
const worker = new RelayClient({ url, hostId: 'w-106', secret: wSecret, ports: [workerPort], log: () => {} })
const worker = new RelayClient({ url, hostId: 'w-2', secret: wSecret, ports: [workerPort], log: () => {} })
const foreign = new RelayClient({
url,
hostId: 'dt',
@@ -304,7 +304,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
echo.close()
await srv.stop()
})
const opsUp = await waitFor(() => srv.isOnline('w-106'))
const opsUp = await waitFor(() => srv.isOnline('w-2'))
assert.ok(opsUp, 'ops 侧 worker 未注册')
const foreignUp = await waitFor(() => srv.isOnline('dt', U_TEST))
assert.ok(foreignUp, `别网拨号方未注册;最近日志:${logs.slice(-10).join(' | ')}`)
@@ -313,8 +313,8 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
const nets = srv.status().networks
assert.deepEqual(
nets.find((n) => n.network === OPS_NETWORK)?.sessions,
['w-106'],
'ops 网里应只有 w-106',
['w-2'],
'ops 网里应只有 w-2',
)
assert.deepEqual(nets.find((n) => n.network === U_TEST)?.sessions, ['dt'], '别张网里应只有 dt')
@@ -322,7 +322,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
const before = srv.status().counters.refused
let foreignMsg = ''
await assert.rejects(
() => foreign.openStream('w-106', workerPort),
() => foreign.openStream('w-2', workerPort),
(err) => {
foreignMsg = err instanceof Error ? err.message : String(err)
return true
@@ -347,7 +347,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
)
// 目标侧没有任何流被建立(不是"建了再断")
assert.equal(
srv.status().endpoints.find((e) => e.hostId === 'w-106' && e.network === OPS_NETWORK && e.port === workerPort)?.streams,
srv.status().endpoints.find((e) => e.hostId === 'w-2' && e.network === OPS_NETWORK && e.port === workerPort)?.streams,
0,
'被拒的跨网拨号不得在目标侧留下流',
)
@@ -358,7 +358,7 @@ test('U3 跨网隔离:u:test-network 的合法拨号方拨 ops/w-106 ⇒ relay
ops.start()
t.after(() => ops.stop())
assert.ok(await waitFor(() => ops.status().state === 'up'), 'ops 拨号方未注册')
const duplex = await ops.openStream('w-106', workerPort)
const duplex = await ops.openStream('w-2', workerPort)
assert.equal(await dialRoundTrip(duplex, 'same-net', 'ops:'.length), 'ops:same-net')
duplex.destroy()
})
+15 -15
View File
@@ -9,8 +9,8 @@
*
* | hostId | dsh_hosts.endpoint(2026-09-16 实测) | 真实语义 |
* |---|---|---|
* | `w-106` | `http://127.0.0.1:19000` | 经 47 上 sshd 的反向隧道落点 |
* | `w-47` | `http://127.0.0.1:19100` | 同机直连(node 自己监听) |
* | `w-2` | `http://127.0.0.1:19000` | 经 47 上 sshd 的反向隧道落点 |
* | `w-1` | `http://127.0.0.1:19100` | 同机直连(node 自己监听) |
*/
import { test } from 'node:test'
import assert from 'node:assert/strict'
@@ -27,8 +27,8 @@ import { LocalRendezvous, ManagerSshRendezvous, RendezvousRegistry } from '../li
/** 现网真实两条(2026-09-16 在 47 上 `SELECT id, endpoint FROM dsh_hosts` 实测)。 */
const LIVE_HOSTS = [
{ hostId: 'w-106', endpoint: 'http://127.0.0.1:19000', via: VIA_MANAGER_SSH },
{ hostId: 'w-47', endpoint: 'http://127.0.0.1:19100', via: VIA_LOCAL },
{ hostId: 'w-2', endpoint: 'http://127.0.0.1:19000', via: VIA_MANAGER_SSH },
{ hostId: 'w-1', endpoint: 'http://127.0.0.1:19100', via: VIA_LOCAL },
]
test('S0 等价性:旧 agentUrl 取址与可达性取址逐字相等', () => {
@@ -75,9 +75,9 @@ test('parseReachability:https / 裸 host:port / 尾斜杠 三种兼容面', ()
test('可达性优先于旧 agentUrl', () => {
const host = {
hostId: 'w-106',
hostId: 'w-2',
agentUrl: 'http://stale.example:1',
reachability: { hostId: 'w-106', via: VIA_MANAGER_SSH, address: '127.0.0.1:19000', scheme: 'http' },
reachability: { hostId: 'w-2', via: VIA_MANAGER_SSH, address: '127.0.0.1:19000', scheme: 'http' },
}
assert.equal(agentBaseUrlOf(host), 'http://127.0.0.1:19000')
})
@@ -88,31 +88,31 @@ test('两者皆缺 ⇒ 抛错(禁止静默打到空地址)', () => {
})
test('LocalRendezvous:命中给 local,未命中回 undefined 不抛', async () => {
const table = new Map([['w-47', '127.0.0.1:19100']])
const table = new Map([['w-1', '127.0.0.1:19100']])
const rv = new LocalRendezvous((id) => table.get(id))
assert.equal(rv.id, VIA_LOCAL)
assert.equal(rv.dialTarget(), '(direct)')
assert.deepEqual(await rv.resolve('w-47'), {
hostId: 'w-47',
assert.deepEqual(await rv.resolve('w-1'), {
hostId: 'w-1',
networkId: 'ops',
via: VIA_LOCAL,
address: '127.0.0.1:19100',
scheme: 'http',
})
assert.equal(await rv.resolve('w-106'), undefined)
assert.equal(await rv.resolve('w-2'), undefined)
})
test('ManagerSshRendezvous:解析出的基址必须等于现网 endpoint(S2 迁移判据)', async () => {
const table = new Map([
['w-106', '127.0.0.1:19000'],
['w-47', '127.0.0.1:19100'],
['w-2', '127.0.0.1:19000'],
['w-1', '127.0.0.1:19100'],
])
const rv = new ManagerSshRendezvous({
target: 'root@47.77.182.89:32022',
target: 'root@203.0.113.10:32022',
addressOf: (id) => table.get(id),
})
assert.equal(rv.id, VIA_MANAGER_SSH)
assert.equal(rv.dialTarget(), 'root@47.77.182.89:32022')
assert.equal(rv.dialTarget(), 'root@203.0.113.10:32022')
for (const h of LIVE_HOSTS) {
const resolved = await rv.resolve(h.hostId)
assert.equal(agentBaseUrl(resolved), h.endpoint, `${h.hostId} 迁移后基址变了`)
@@ -142,7 +142,7 @@ test('S2:via → Rendezvous → Reachability 后取址与旧 agentUrl 逐条
const hostAddresses = new Map()
const rendezvous = new RendezvousRegistry([
new LocalRendezvous((id) => hostAddresses.get(id)),
new ManagerSshRendezvous({ target: 'ssh://root@47.77.182.89:32022', addressOf: (id) => hostAddresses.get(id) }),
new ManagerSshRendezvous({ target: 'ssh://root@203.0.113.10:32022', addressOf: (id) => hostAddresses.get(id) }),
])
// hostsProvider 第一遍:同步地址表
for (const row of rows) {
+15 -15
View File
@@ -161,9 +161,9 @@ test('retryAfter 人话格式化', () => {
})
test('mail: 正文含验证码与有效期,且明确告知"非本人操作请忽略"', () => {
const { subject, text } = renderVerificationMail({ to: '[email protected]', code: '123456', ttlMinutes: 10, brand: 'AI1NET' })
const { subject, text } = renderVerificationMail({ to: '[email protected]', code: '123456', ttlMinutes: 10, brand: 'EXAMPLE' })
assert.ok(subject.includes('123456'))
assert.ok(subject.includes('AI1NET'))
assert.ok(subject.includes('EXAMPLE'))
assert.ok(text.includes('123456'))
assert.ok(text.includes('10'))
assert.ok(text.includes('ignore this e-mail'))
@@ -210,8 +210,8 @@ test('mail: http 驱动把 URL / 鉴权头 / body 模板交给配置(换供应
apiUrl: `http://127.0.0.1:${port}/send`,
apiKey: 'sekret',
authHeader: 'x-api-key',
from: 'no-reply@ai1net.com',
fromName: 'AI1NET',
from: 'no-reply@example.net',
fromName: 'EXAMPLE',
bodyTemplate: '{"to":"{{to}}","subject":"{{subject}}","payload":{"code":"{{code}}"}}',
timeoutMs: 3000,
},
@@ -249,7 +249,7 @@ test('mail: 上游 5xx ⇒ 明确失败(不重试、不假装成功)', async
})
test('turnstile: 只填一把钥匙视为未配置(半配置不许当"已启用")', () => {
const base = { siteKey: 'k', secret: 's', action: 'signup', hostnames: ['ai1net.com'], timeoutMs: 1000 }
const base = { siteKey: 'k', secret: 's', action: 'signup', hostnames: ['example.net'], timeoutMs: 1000 }
assert.equal(turnstileEnabled(base), true)
assert.equal(turnstileEnabled({ ...base, secret: '' }), false)
assert.equal(turnstileEnabled({ ...base, siteKey: '' }), false)
@@ -260,8 +260,8 @@ test('turnstile: 只填一把钥匙视为未配置(半配置不许当"已启
test('turnstile: success 之后仍校 action 与 hostname(官方 canonical 三项)', async () => {
// 本地假 siteverify:按 path 决定回什么,从而逐组合断言判定逻辑
const cases = {
'/ok': { success: true, action: 'signup', hostname: 'ai1net.com' },
'/badaction': { success: true, action: 'login', hostname: 'ai1net.com' },
'/ok': { success: true, action: 'signup', hostname: 'example.net' },
'/badaction': { success: true, action: 'login', hostname: 'example.net' },
'/badhost': { success: true, action: 'signup', hostname: 'evil.example' },
'/nohost': { success: true, action: 'signup' },
'/fail': { success: false, 'error-codes': ['invalid-input-response'] },
@@ -277,7 +277,7 @@ test('turnstile: success 之后仍校 action 与 hostname(官方 canonical 三
const port = server.address().port
const settings = (path) => ({
siteKey: 'k', secret: 's', timeoutMs: 3000, action: 'signup',
hostnames: ['ai1net.com', 'www.ai1net.com'],
hostnames: ['example.net', 'www.example.net'],
verifyUrl: `http://127.0.0.1:${port}${path}`,
})
try {
@@ -301,21 +301,21 @@ test('turnstile: success 之后仍校 action 与 hostname(官方 canonical 三
})
test('config: 期望主机名归一 + 派生 + ⛔ 绝不自动加 localhost', () => {
assert.deepEqual(normalizeHostnames(['https://AI1net.com/', 'www.ai1net.com:443', ' ai1net.com ']), [
'ai1net.com', 'www.ai1net.com',
assert.deepEqual(normalizeHostnames(['https://EXAMPLE.net/', 'www.example.net:443', ' example.net ']), [
'example.net', 'www.example.net',
])
// 未配(undefined)⇒ 从 baseDomain 派生
assert.deepEqual(resolveTurnstileHostnames(undefined, 'ai1net.com'), ['ai1net.com', 'www.ai1net.com'])
assert.deepEqual(resolveTurnstileHostnames(undefined, 'example.net'), ['example.net', 'www.example.net'])
// 显式配 ⇒ 只用配的(可加旧域,旧域门户仍在线)
assert.deepEqual(
resolveTurnstileHostnames(['ai1net.com', 'alotbuy.com'], 'ai1net.com'),
['ai1net.com', 'alotbuy.com'],
resolveTurnstileHostnames(['example.net', 'example.org'], 'example.net'),
['example.net', 'example.org'],
)
// 显式空 ⇒ 关闭(不派生)
assert.deepEqual(resolveTurnstileHostnames([], 'ai1net.com'), [])
assert.deepEqual(resolveTurnstileHostnames([], 'example.net'), [])
// baseDomain 空 ⇒ 空(= 未配置完成 ⇒ 人机验证停用,不会静默放开)
assert.deepEqual(resolveTurnstileHostnames(undefined, ''), [])
assert.equal(normalizeHostnames(['localhost']).includes('ai1net.com'), false)
assert.equal(normalizeHostnames(['localhost']).includes('example.net'), false)
})
/* ── 编排层:真库 + log 驱动 ─────────────────────────────────────────────── */
+6 -6
View File
@@ -488,7 +488,7 @@ const TH8 = { minAttempts: 3, graceMs: 1000, cooldownMs: 60_000, deadlineMs: 30_
/**
* F12 · **目录路径没有豁免权**(E1 / D1)。
*
* 立项依据:真机 11:43:26 实测 `wss://106… -> wss://ai1net.com…` —— 只因"目录里的地址变了"
* 立项依据:真机 11:43:26 实测 `wss://106… -> wss://example.net…` —— 只因"目录里的地址变了"
* 就把刚被冷却的 47 换回来 ⇒ D5 的抖动抑制被另一条路径绕开。
*/
test('F12 目录路径无豁免权:origin=directory + 目标在冷却 ⇒ 必 skip(E1 / D1)', async () => {
@@ -858,12 +858,12 @@ test('O1 观测行格式锁定:固定 key 序 + count 为条数 + hosts
const obs = new RelayCandidateObservation('manager', (l) => lines.push(l), 0)
obs.record(
[
'wss://ai1net.com/dshs-relay',
'https://ai1net.com/other',
'wss://106.54.21.172/dshs-relay',
'wss://example.net/dshs-relay',
'https://example.net/other',
'wss://198.51.100.20/dshs-relay',
],
'cache',
'/var/lib/dshs/overlay/directory.json',
'/var/lib/dsh-test/overlay/directory.json',
)
assert.equal(lines.length, 1, '一次 record 写一行')
const line = lines[0]
@@ -876,7 +876,7 @@ test('O1 观测行格式锁定:固定 key 序 + count 为条数 + hosts
)
const snap = obs.snapshot()
assert.equal(snap.count, 3, 'count = 候选**条数**(⛔ 不按主机去重)')
assert.equal(snap.hosts, 2, 'hosts = 独立主机数(ai1net.com 的两条算同一台 —— scheme 不参与)')
assert.equal(snap.hosts, 2, 'hosts = 独立主机数(example.net 的两条算同一台 —— scheme 不参与)')
assert.equal(snap.source, 'cache')
assert.equal(snap.resolves, 1)
assert.equal(snap.unresolved, false)
+6 -6
View File
@@ -1044,7 +1044,7 @@ test('T20 序⑤ 判别器计数:DIAL 放行 / 策略拒绝 / 目标不可达
* # T23 · 拨号流**严格单向**(序 ⑭ · 数据面缺陷回归)
*
* ## 生产现象(用户可见)
* 任何 `via='relay'` 的 host(今天 = w-106)上,**同一条 keep-alive 连接的第 2 条** agent 请求
* 任何 `via='relay'` 的 host(今天 = w-2)上,**同一条 keep-alive 连接的第 2 条** agent 请求
* 必回 `400 clientError`(Fastify `clientError` 兜底)⇒ 用户 `POST /api/dsh/enter` 回 **500**
* ⇒ **"登录直达工作区"整体不可用**。
*
@@ -1870,14 +1870,14 @@ test('T35 P-2:`relayEndpointTarget` 四支判定(透传 / 拨号 / 快照 /
test('T36 P-2:键口径 —— 裸 `hostId` 必须经 `hostNameIndex` 换到逻辑名(⛔ 闭包不得再拿 hostId 当键)', async () => {
const idx = hostNameIndex([
{ id: 'w-47', networkId: 'ops' },
{ id: 'w-106', networkId: '' }, // 空 ⇒ 归属网取兜底(与 DB 列默认值同口径)
{ id: 'w-1', networkId: 'ops' },
{ id: 'w-2', networkId: '' }, // 空 ⇒ 归属网取兜底(与 DB 列默认值同口径)
{ id: 'd1', networkId: 'u:5' },
])
assert.equal(idx.get('w-47'), 'ops/w-47')
assert.equal(idx.get('w-106'), 'ops/w-106', '空 network_id 必须按兜底网补全(否则与 DB 行写的键不一致)')
assert.equal(idx.get('w-1'), 'ops/w-1')
assert.equal(idx.get('w-2'), 'ops/w-2', '空 network_id 必须按兜底网补全(否则与 DB 行写的键不一致)')
assert.equal(idx.get('d1'), 'u:5/d1', '跨网同 hostId 各算一台(P0-3)')
assert.equal(idx.get('ops/w-106'), undefined, '索引的键是**裸 hostId** ⇒ 拿逻辑名查不到(两侧口径必须显式转换)')
assert.equal(idx.get('ops/w-2'), undefined, '索引的键是**裸 hostId** ⇒ 拿逻辑名查不到(两侧口径必须显式转换)')
assert.equal(hostNameIndex([{ id: 'x', networkId: '' }], 'u:9').get('x'), 'u:9/x', '兜底网可注入(⛔ 不写死 ops)')
/**
+5 -5
View File
@@ -37,9 +37,9 @@ function fakeFetch(payload) {
/** `via='relay'` 的一台 worker:agent 口号 19000,relay 已把它映射到本机 38253。 */
const W106 = {
hostId: 'w-106',
hostId: 'w-2',
agentUrl: 'http://127.0.0.1:19000',
reachability: { hostId: 'w-106', via: 'relay', address: '127.0.0.1:38253', scheme: 'http' },
reachability: { hostId: 'w-2', via: 'relay', address: '127.0.0.1:38253', scheme: 'http' },
token: 'tok-106',
}
@@ -47,8 +47,8 @@ function make(opts = {}) {
return new RemoteSpawner({
agentUrl: 'http://127.0.0.1:19100',
token: 'tok-local',
defaultHostId: 'w-47',
hostIdFor: async () => 'w-106',
defaultHostId: 'w-1',
hostIdFor: async () => 'w-2',
hostsProvider: async () => [W106],
fetchImpl: fakeFetch({ running: true, host: '127.0.0.1', port: 21000 }),
...opts,
@@ -67,7 +67,7 @@ test('T1 翻译器被真的接上:传入的 hostId/endpoint 与返回值都要
})
assert.deepEqual(await s.endpointFor('u1'), { host: '127.0.0.1', port: 34241 })
// ★ 这一条就是首版漏赋值时唯一会红的断言:漏了 ⇒ seen 为空、返回 {21000}
assert.deepEqual(seen, [['w-106', { host: '127.0.0.1', port: 21000 }]])
assert.deepEqual(seen, [['w-2', { host: '127.0.0.1', port: 21000 }]])
})
test('T2 未给翻译器 ⇒ 原样透传(local / manager-ssh 的同号语义,行为零变化)', async () => {
+8 -8
View File
@@ -33,7 +33,7 @@ import { RemoteUserFs } from '../lib/fs/remote-user-fs.js'
/** Manager 自己那台(= 默认 / 回退 agent)—— 任何"打到这里"都是路由失败。 */
const DEFAULT_AGENT = 'http://127.0.0.1:19100'
/** 归属机:w-106 的 agent。 */
/** 归属机:w-2 的 agent。 */
const W106_AGENT = 'http://127.0.0.1:19000'
/** 记账用 fetch:记下每一发请求,永不真的出网。 */
@@ -74,7 +74,7 @@ test('U1 目录未命中:ensureHost 补齐后打到**归属机器**(不再
const dir = new Map() // 模拟 hostsProvider() 尚未填过的空目录
let ensured = 0
const { fs, fetchImpl } = mk({
hostIdFor: async () => 'w-106',
hostIdFor: async () => 'w-2',
agentFor: (h) => dir.get(h),
ensureHost: async (h) => {
ensured += 1
@@ -85,13 +85,13 @@ test('U1 目录未命中:ensureHost 补齐后打到**归属机器**(不再
await fs.listDir('u1', '')
assert.equal(ensured, 1, '未命中必须触发一次补齐')
assert.deepEqual(fetchImpl.calls, [`${W106_AGENT}/fs/list`], '必须打到 w-106,不许打默认机')
assert.deepEqual(fetchImpl.calls, [`${W106_AGENT}/fs/list`], '必须打到 w-2,不许打默认机')
})
test('U7 命中时**不**做补齐(正常路径零开销:不查库)', async () => {
let ensured = 0
const { fs, fetchImpl } = mk({
hostIdFor: async () => 'w-106',
hostIdFor: async () => 'w-2',
agentFor: () => ({ agentUrl: W106_AGENT, token: 'tok-106' }),
ensureHost: async () => {
ensured += 1
@@ -109,8 +109,8 @@ test('U7 命中时**不**做补齐(正常路径零开销:不查库)', asyn
test('U2 补齐后仍取不到地址:503 host_unresolved,且**零请求发往默认机**(写操作也拦住)', async () => {
let ensured = 0
const { fs, fetchImpl } = mk({
hostIdFor: async () => 'w-106',
agentFor: () => undefined, // 目录里始终没有 w-106
hostIdFor: async () => 'w-2',
agentFor: () => undefined, // 目录里始终没有 w-2
ensureHost: async () => {
ensured += 1
},
@@ -125,7 +125,7 @@ test('U2 补齐后仍取不到地址:503 host_unresolved,且**零请求发
test('U3 未提供 ensureHost(老调用点):未命中同样失败关闭,**不退化**为静默回退', async () => {
const { fs, fetchImpl } = mk({
hostIdFor: async () => 'w-106',
hostIdFor: async () => 'w-2',
agentFor: () => undefined,
// ensureHost 故意不传
})
@@ -136,7 +136,7 @@ test('U3 未提供 ensureHost(老调用点):未命中同样失败关闭,
test('U8 ensureHost 自己抛错(如查库失败):仍失败关闭,不吞成"默认机"', async () => {
const { fs, fetchImpl } = mk({
hostIdFor: async () => 'w-106',
hostIdFor: async () => 'w-2',
agentFor: () => undefined,
ensureHost: async () => {
throw new Error('pg is down')