feat(overlay): 覆盖网络线序①–⑮ 代码与测试产物入库
覆盖网络线累积产物(此前只在工作区、未入版本库):
- 新增 relay 子系统 src/net/relay/**(wire/duplex/server/client/dialer/switcher/directory/identity/keys/placement/network/addr-override/main/index)
- 新增 src/worker/relay-tunnel.ts、src/web/routes/overlay.ts
- 新增观测/演练脚本 overlay-probe、overlay-failover-drill、overlay-keyring、overlay-holepunch、overlay-jitter、overlay-wan、overlay-relaykey-add、relay-mem-calibrate
- 新增测试 12 个(relay / relay-failover / remote-spawner / instance-port / overlay-{network,auth,bootstrap,identity} / remote-user-fs 等)
验收基线:npm test = 162 pass / 0 fail / 1 skip;--scene all = 12 PASS / 0 SKIP / 0 FAIL;overlay-probe = 12/12
This commit is contained in:
1 parent
640813e84e
commit
146c3d25ef
53 files changed
+16187
-63
No files matched your search
+70
-15
@@ -28,7 +28,11 @@ import { userRoot } from '../fs/workspace.js'
|
||||
import { isUserFsErrorCode, UserFsError } from '../fs/user-fs.js'
|
||||
import { hashUid } from '../isolation.js'
|
||||
import { LocalSpawner } from '../supervisor/orchestrator.js'
|
||||
import { normalizeTunnelTarget, SshTunnel } from './tunnel.js'
|
||||
import { normalizeTunnelTarget, SshTunnel, type WorkerTunnel } from './tunnel.js'
|
||||
import { loadClientIdentity } from '../net/relay/identity.js'
|
||||
import { listOverlayRelayCandidates } from '../net/relay/directory.js'
|
||||
import { overlayEnvSeeds, overlayEnvTrustedKeys } from '../net/relay/directory.js'
|
||||
import { RelayTunnel, isRelayUrl } from './relay-tunnel.js'
|
||||
import type { Instance } from '../supervisor/spawner.js'
|
||||
|
||||
/** 绑定的头部名(Manager/agent 双方约定)。 */
|
||||
@@ -153,10 +157,12 @@ export function buildWorkerAgent(
|
||||
*
|
||||
* S1:会合地址**优先取 config**(`DSHS_RENDEZVOUS_URL` → 兜底 `DSHS_TUNNEL_TARGET`,在
|
||||
* `config.ts` 里单点解析);显式 `options.tunnelTarget` 仍是最优先(测试/嵌入用)。
|
||||
*
|
||||
* R4:**按 scheme 选传输实现** —— 裸 `user@host:port` / `ssh://` ⇒ `SshTunnel`(旧路,保留可秒回滚);
|
||||
* `ws://` / `wss://` ⇒ `RelayTunnel`(自研 relay,去掉对 sshd 的长期依赖)。两者同实现
|
||||
* `WorkerTunnel` 面 ⇒ 本段以下所有代码(自愈 / 对账 / `/healthz`)**与传输无关**。
|
||||
*/
|
||||
const tunnelTarget = normalizeTunnelTarget(
|
||||
options.tunnelTarget ?? config.clusterRendezvousUrl ?? '',
|
||||
)
|
||||
const rendezvousRaw = (options.tunnelTarget ?? config.clusterRendezvousUrl ?? '').trim()
|
||||
const staticPorts = [
|
||||
options.port,
|
||||
...(process.env.DSHS_TUNNEL_STATIC_PORTS ?? '')
|
||||
@@ -164,18 +170,67 @@ export function buildWorkerAgent(
|
||||
.map((v) => Number(v.trim()))
|
||||
.filter((v) => Number.isInteger(v) && v > 0),
|
||||
]
|
||||
const tunnel =
|
||||
tunnelTarget === ''
|
||||
const tunnel: WorkerTunnel | undefined =
|
||||
rendezvousRaw === ''
|
||||
? undefined
|
||||
: new SshTunnel({
|
||||
target: tunnelTarget,
|
||||
identity:
|
||||
options.tunnelIdentity ??
|
||||
process.env.DSHS_TUNNEL_IDENTITY ??
|
||||
`${process.env.HOME ?? '/root'}/.ssh/tunnel_ed25519`,
|
||||
controlPath: options.tunnelControlPath ?? `/tmp/dshs-tunnel-${options.hostId}.sock`,
|
||||
staticPorts,
|
||||
})
|
||||
: isRelayUrl(rendezvousRaw)
|
||||
? new RelayTunnel({
|
||||
url: rendezvousRaw,
|
||||
hostId: options.hostId,
|
||||
// 密钥必须显式给:**缺了就抛** —— 静默降级到别的传输会让"以为切过去了"变成假象。
|
||||
secret: (() => {
|
||||
const secret = (process.env.DSHS_RELAY_SECRET ?? '').trim()
|
||||
if (!/^[0-9a-fA-F]{64}$/.test(secret)) {
|
||||
throw new Error('DSHS_RENDEZVOUS_URL 用了 relay(ws/wss)⇒ 必须同时给 DSHS_RELAY_SECRET(64 hex)')
|
||||
}
|
||||
return secret
|
||||
})(),
|
||||
staticPorts,
|
||||
/**
|
||||
* 序⑦ · **C2 装配点:中继失败切流**。
|
||||
*
|
||||
* ⛔ **不改会合面**:起始地址仍然是上面那个 `rendezvousRaw`(= `DSHS_RENDEZVOUS_URL`
|
||||
* 的既有取值顺序,一行没动)。这里只补一件事 —— 起始那台**不健康**时,
|
||||
* 从**同一份引导链**(`listOverlayRelayCandidates`)里换到下一条。
|
||||
*
|
||||
* 为什么候选链不走 `rendezvousRaw`:那是"当前连哪台"的答案,不是"还有哪些台"的清单;
|
||||
* 而链是**已签名目录 / 内置种子**的产物 ⇒ ⛔ 不可能指向任意 url(引不出任意重定向)。
|
||||
* 链里取不到候选 ⇒ 什么都不做(D6),**行为与改造前逐字一致**。
|
||||
*/
|
||||
failover: {
|
||||
candidates: async () =>
|
||||
(
|
||||
await listOverlayRelayCandidates({
|
||||
envUrl: '',
|
||||
seeds: overlayEnvSeeds(),
|
||||
trustedKeys: overlayEnvTrustedKeys(),
|
||||
cacheFile: process.env.DSHS_OVERLAY_DIR_CACHE ?? '',
|
||||
log: (line: string) => console.error(line),
|
||||
})
|
||||
).urls,
|
||||
},
|
||||
/**
|
||||
* 序③:**本机节点身份**。配了 `DSHS_OVERLAY_NODE_KEY_FILE` + `..._NODE_GRANT_FILE`
|
||||
* 才会带上(缺省 = 只做 HMAC,过渡期形态不变)。
|
||||
* ⚠️ **配了却读不出来 ⇒ 起动即抛**(见 `identity.ts#loadClientIdentity`)——
|
||||
* 静默退化成"没身份"会让"凭据坏了"表现成"一切正常",等 relay 一开强制就整台失联。
|
||||
*/
|
||||
identity: loadClientIdentity({
|
||||
keyFile: config.overlayNodeKeyFile,
|
||||
grantFile: config.overlayNodeGrantFile,
|
||||
log: (line: string) => console.error(line),
|
||||
}),
|
||||
log: (line: string) => console.error(line),
|
||||
})
|
||||
: new SshTunnel({
|
||||
target: normalizeTunnelTarget(rendezvousRaw),
|
||||
identity:
|
||||
options.tunnelIdentity ??
|
||||
process.env.DSHS_TUNNEL_IDENTITY ??
|
||||
`${process.env.HOME ?? '/root'}/.ssh/tunnel_ed25519`,
|
||||
controlPath: options.tunnelControlPath ?? `/tmp/dshs-tunnel-${options.hostId}.sock`,
|
||||
staticPorts,
|
||||
})
|
||||
let tunnelReady = tunnel === undefined
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user