把散落在代码里的真实部署值统一收进 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 未被误建)。
177 lines
8.0 KiB
JavaScript
177 lines
8.0 KiB
JavaScript
/**
|
||
* 覆盖网络 S0 · 可达性 / 会合接口单测(纯函数,不连网、不起进程)。
|
||
* 运行:node --test test/reachability.test.mjs(已含在 npm test / npm verify 中)
|
||
*
|
||
* ## 这个测试的职责
|
||
* S0 的唯一验收标准是「**行为零变化**」。所以本文件的核心断言是
|
||
* **「可达性解析前后的 agent 基址逐字相等」** —— 把现网 `dsh_hosts` 的真实两行
|
||
* 原样写进判据里(**不靠肉眼、不靠人工比对**):
|
||
*
|
||
* | hostId | dsh_hosts.endpoint(2026-09-16 实测) | 真实语义 |
|
||
* |---|---|---|
|
||
* | `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'
|
||
|
||
import {
|
||
agentBaseUrl,
|
||
agentBaseUrlOf,
|
||
parseReachability,
|
||
toEndpoint,
|
||
VIA_LOCAL,
|
||
VIA_MANAGER_SSH,
|
||
} from '../lib/net/reachability.js'
|
||
import { LocalRendezvous, ManagerSshRendezvous, RendezvousRegistry } from '../lib/net/rendezvous.js'
|
||
|
||
/** 现网真实两条(2026-09-16 在 47 上 `SELECT id, endpoint FROM dsh_hosts` 实测)。 */
|
||
const LIVE_HOSTS = [
|
||
{ 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 取址与可达性取址逐字相等', () => {
|
||
for (const h of LIVE_HOSTS) {
|
||
// 旧路径(今天生产用的):直接把 endpoint 当 agent 基址
|
||
const legacy = h.endpoint.replace(/\/$/, '')
|
||
// 新路径(S0 起的唯一入口):没给 reachability ⇒ 必须回退到同一个值
|
||
assert.equal(agentBaseUrlOf({ hostId: h.hostId, agentUrl: h.endpoint }), legacy, h.hostId)
|
||
}
|
||
})
|
||
|
||
test('parseReachability → agentBaseUrl 对现网两行是往返恒等的', () => {
|
||
for (const h of LIVE_HOSTS) {
|
||
const reach = parseReachability(h.hostId, h.endpoint, h.via)
|
||
assert.equal(agentBaseUrl(reach), h.endpoint, `${h.hostId} 往返不一致`)
|
||
assert.equal(toEndpoint(reach), h.endpoint, `${h.hostId} toEndpoint 不一致`)
|
||
assert.equal(reach.scheme, 'http')
|
||
assert.deepEqual(
|
||
{ hostId: reach.hostId, networkId: reach.networkId, via: reach.via, address: reach.address },
|
||
// 裸 hostId ⇒ 落运维网(P0-3 的过渡期兼容:旧调用方一个字都不用改)
|
||
{ hostId: h.hostId, networkId: 'ops', via: h.via, address: h.endpoint.slice('http://'.length) },
|
||
)
|
||
}
|
||
})
|
||
|
||
test('parseReachability:https / 裸 host:port / 尾斜杠 三种兼容面', () => {
|
||
assert.deepEqual(parseReachability('h', 'https://a.example:8443', 'x'), {
|
||
hostId: 'h',
|
||
networkId: 'ops',
|
||
via: 'x',
|
||
address: 'a.example:8443',
|
||
scheme: 'https',
|
||
})
|
||
// 没写 scheme ⇒ 按 http(与 fetch 的补全行为一致)
|
||
assert.deepEqual(parseReachability('h', '10.0.0.5:19000', 'x'), {
|
||
hostId: 'h',
|
||
networkId: 'ops',
|
||
via: 'x',
|
||
address: '10.0.0.5:19000',
|
||
scheme: 'http',
|
||
})
|
||
assert.equal(agentBaseUrl(parseReachability('h', 'http://127.0.0.1:19000/', 'x')), 'http://127.0.0.1:19000')
|
||
})
|
||
|
||
test('可达性优先于旧 agentUrl', () => {
|
||
const host = {
|
||
hostId: 'w-2',
|
||
agentUrl: 'http://stale.example:1',
|
||
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')
|
||
})
|
||
|
||
test('两者皆缺 ⇒ 抛错(禁止静默打到空地址)', () => {
|
||
assert.throws(() => agentBaseUrlOf({ hostId: 'w-x' }), /既无 reachability 也无 agentUrl/)
|
||
assert.throws(() => agentBaseUrlOf({ hostId: 'w-x', agentUrl: '' }), /既无 reachability 也无 agentUrl/)
|
||
})
|
||
|
||
test('LocalRendezvous:命中给 local,未命中回 undefined 不抛', async () => {
|
||
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-1'), {
|
||
hostId: 'w-1',
|
||
networkId: 'ops',
|
||
via: VIA_LOCAL,
|
||
address: '127.0.0.1:19100',
|
||
scheme: 'http',
|
||
})
|
||
assert.equal(await rv.resolve('w-2'), undefined)
|
||
})
|
||
|
||
test('ManagerSshRendezvous:解析出的基址必须等于现网 endpoint(S2 迁移判据)', async () => {
|
||
const table = new Map([
|
||
['w-2', '127.0.0.1:19000'],
|
||
['w-1', '127.0.0.1:19100'],
|
||
])
|
||
const rv = new ManagerSshRendezvous({
|
||
target: '[email protected]:32022',
|
||
addressOf: (id) => table.get(id),
|
||
})
|
||
assert.equal(rv.id, VIA_MANAGER_SSH)
|
||
assert.equal(rv.dialTarget(), '[email protected]:32022')
|
||
for (const h of LIVE_HOSTS) {
|
||
const resolved = await rv.resolve(h.hostId)
|
||
assert.equal(agentBaseUrl(resolved), h.endpoint, `${h.hostId} 迁移后基址变了`)
|
||
}
|
||
})
|
||
|
||
test('RendezvousRegistry:按 via 取实现', () => {
|
||
const local = new LocalRendezvous(() => undefined)
|
||
const ssh = new ManagerSshRendezvous({ target: 't', addressOf: () => undefined })
|
||
const reg = new RendezvousRegistry([local, ssh])
|
||
assert.equal(reg.get(VIA_LOCAL), local)
|
||
assert.equal(reg.get(VIA_MANAGER_SSH), ssh)
|
||
assert.equal(reg.get('relay:backbone-1'), undefined)
|
||
assert.deepEqual(reg.ids().sort(), [VIA_LOCAL, VIA_MANAGER_SSH].sort())
|
||
})
|
||
|
||
// ── S2 验收判据 ────────────────────────────────────────────────────────────
|
||
// 「迁移前 `agentUrl`」必须 **逐条逐字等于** 「迁移后 `resolve()` 的结果」。
|
||
// 这里把 `hostsProvider` 的真实接线原样复刻一遍(先读 via → 选实现 → 解析 → 拼基址),
|
||
// 两条数据用现网实测值写死 ⇒ **不靠肉眼、不靠人工比对**。
|
||
test('S2:via → Rendezvous → Reachability 后取址与旧 agentUrl 逐条相等', async () => {
|
||
// 现网 `dsh_hosts` 真实两行(2026-09-16 实测;`via` = 回填后的目标值)
|
||
const rows = LIVE_HOSTS.map((h) => ({ id: h.hostId, endpoint: h.endpoint, via: h.via }))
|
||
// P0-3:控制面的键 = **逻辑名**(现网全在 `ops` ⇒ 与裸 id 等价)
|
||
const nameOf = (h) => `ops/${h.id}`
|
||
|
||
const hostAddresses = new Map()
|
||
const rendezvous = new RendezvousRegistry([
|
||
new LocalRendezvous((id) => hostAddresses.get(id)),
|
||
new ManagerSshRendezvous({ target: 'ssh://[email protected]:32022', addressOf: (id) => hostAddresses.get(id) }),
|
||
])
|
||
// hostsProvider 第一遍:同步地址表
|
||
for (const row of rows) {
|
||
hostAddresses.set(nameOf(row), parseReachability(nameOf(row), row.endpoint, row.via).address)
|
||
}
|
||
|
||
for (const row of rows) {
|
||
const impl = rendezvous.get(row.via) ?? rendezvous.get(VIA_MANAGER_SSH)
|
||
assert.notEqual(impl, undefined, `${row.id} 的 via=${row.via} 必须能选到实现`)
|
||
const reach = await impl.resolve(nameOf(row))
|
||
assert.notEqual(reach, undefined, `${row.id} 必须能解析出可达性`)
|
||
assert.equal(reach.via, row.via, `${row.id} 解析后 via 变了`)
|
||
assert.equal(reach.hostId, row.id, `${row.id} 解析出的 hostId 必须是**裸** id`)
|
||
assert.equal(reach.networkId, 'ops', `${row.id} 解析出的网络段必须是 ops`)
|
||
// 判据:新路径拼出来的基址 == 旧路径直接用的 endpoint
|
||
assert.equal(agentBaseUrlOf({ hostId: row.id, reachability: reach }), row.endpoint, `${row.id} 取址变了`)
|
||
assert.equal(agentBaseUrlOf({ hostId: row.id, agentUrl: row.endpoint }), row.endpoint, `${row.id} 旧路径变了`)
|
||
}
|
||
})
|
||
|
||
test('S2:via 认不出来 ⇒ 回退 manager-ssh,不抛(过渡期要能跑)', async () => {
|
||
const hostAddresses = new Map([['w-x', '10.0.0.9:19000']])
|
||
const rendezvous = new RendezvousRegistry([
|
||
new LocalRendezvous((id) => hostAddresses.get(id)),
|
||
new ManagerSshRendezvous({ target: '', addressOf: (id) => hostAddresses.get(id) }),
|
||
])
|
||
const impl = rendezvous.get('relay:not-deployed-yet') ?? rendezvous.get(VIA_MANAGER_SSH)
|
||
const reach = await impl.resolve('w-x')
|
||
assert.equal(reach.via, VIA_MANAGER_SSH)
|
||
assert.equal(agentBaseUrlOf({ hostId: 'w-x', reachability: reach }), 'http://10.0.0.9:19000')
|
||
})
|