覆盖网络线 S0+S1 落地:可达性单一入口 + 会合地址出 env
S0(本机):新增 src/net/reachability.ts(Reachability 描述 + agentBaseUrlOf 唯一取值入口)、src/net/rendezvous.ts、docs/architecture.md(四层划分 + 依赖方向 + R1-R4 判据)、scripts/check-layering.mjs + layering-baseline.json、test/reachability.test.mjs;src/supervisor/remote-spawner.ts 与 src/web/server.ts 改为统一走 agentBaseUrlOf(),agentUrl 降级为可选旧字段(向后兼容)。 S1(本机 + 106):新增 DSHS_RENDEZVOUS_URL 出 env(src/config.ts 解析 clusterRendezvousUrl,优先级 overrides > 新变量 > DSHS_TUNNEL_TARGET 兜底 > 空),src/worker/tunnel.ts 新增 normalizeTunnelTarget()、src/worker/agent.ts 改读新配置 ⇒ 双路径并存、可零代码回滚(删掉新 env 即走旧路径)。106 已上线,健康检查 tunnel.ready=true。 package.json 增加 check:layering 脚本并接入 verify;README 登记分层文档。 验收:npm run build 通过;npm test 44/44;check:layering 无新增违规(基线 5 条)。
This commit is contained in:
1 parent
08219c99da
commit
640813e84e
13 files changed
+660
-18
No files matched your search
@@ -18,14 +18,26 @@
|
||||
* @module dshs/supervisor/remote-spawner
|
||||
*/
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { agentBaseUrlOf, type Reachability } from '../net/reachability.js'
|
||||
import { AGENT_TOKEN_HEADER } from '../worker/agent.js'
|
||||
import type { Endpoint, Instance, Spawner, UserStatus } from './spawner.js'
|
||||
|
||||
/** 一台 worker 的接入信息。 */
|
||||
/**
|
||||
* 一台 worker 的接入信息。
|
||||
*
|
||||
* ⚠️ `agentUrl` 与 `reachability` **至少要有一个** —— 取址一律走 `agentBaseUrlOf()`
|
||||
* (唯一入口,别在调用点自己拼字符串):
|
||||
* · `reachability` = S0 引入的**可达性描述**,比 `agentUrl` 多一层语义 ——
|
||||
* **经谁中转**(`via`)。现网两类 host 的 `endpoint` 字符串同形但语义完全不同
|
||||
* (`w-47` 是直连本机、`w-106` 是 Manager 上的隧道落点),只有它能表达。
|
||||
* · `agentUrl` = 旧字段,保留向后兼容。
|
||||
*/
|
||||
export interface ClusterHost {
|
||||
hostId: string
|
||||
/** agent 基址。 */
|
||||
agentUrl: string
|
||||
/** agent 基址(旧字段;与 `reachability` 至少给一个)。 */
|
||||
agentUrl?: string
|
||||
/** **可达性**:经谁中转 + 真实地址。给了就优先于 `agentUrl`(S2 起由 `dsh_hosts.via` 驱动)。 */
|
||||
reachability?: Reachability
|
||||
/** 与 agent 约定的共享密钥。 */
|
||||
token: string
|
||||
/** 代理时使用的主机(同机 1a = `127.0.0.1`;跨机填 Worker 内网 IP)。 */
|
||||
@@ -81,15 +93,17 @@ export class RemoteSpawner implements Spawner {
|
||||
private directoryLoadedAt = 0
|
||||
|
||||
constructor(options: RemoteSpawnerOptions) {
|
||||
// ⚠️ 这里**不再做** `replace(/\/$/,'')` 归一化 —— 归一化统一在 `agentBaseUrlOf()`
|
||||
// 里做(幂等)。存归一化值会让"可达性与 agentUrl 两套表示"混在一起,S2 之后难拆。
|
||||
this.defaultHost = {
|
||||
hostId: options.defaultHostId ?? 'local',
|
||||
agentUrl: options.agentUrl.replace(/\/$/, ''),
|
||||
agentUrl: options.agentUrl,
|
||||
token: options.token,
|
||||
instanceHost: options.instanceHost ?? '127.0.0.1',
|
||||
}
|
||||
this.hosts.set(this.defaultHost.hostId, this.defaultHost)
|
||||
for (const host of options.hosts ?? []) {
|
||||
this.hosts.set(host.hostId, { ...host, agentUrl: host.agentUrl.replace(/\/$/, '') })
|
||||
this.hosts.set(host.hostId, { ...host })
|
||||
}
|
||||
this.timeoutMs = options.timeoutMs ?? 10_000
|
||||
this.doFetch = options.fetchImpl ?? fetch
|
||||
@@ -110,7 +124,7 @@ export class RemoteSpawner implements Spawner {
|
||||
this.directoryLoadedAt = Date.now()
|
||||
try {
|
||||
for (const host of await this.hostsProvider()) {
|
||||
this.hosts.set(host.hostId, { ...host, agentUrl: host.agentUrl.replace(/\/$/, '') })
|
||||
this.hosts.set(host.hostId, { ...host })
|
||||
}
|
||||
this.hosts.set(this.defaultHost.hostId, this.defaultHost)
|
||||
} catch {
|
||||
@@ -177,7 +191,7 @@ export class RemoteSpawner implements Spawner {
|
||||
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
|
||||
if (attempt > 0) await new Promise((r) => setTimeout(r, RETRY_DELAYS_MS[attempt - 1]))
|
||||
try {
|
||||
const res = await this.doFetch(`${host.agentUrl}${path}`, {
|
||||
const res = await this.doFetch(`${agentBaseUrlOf(host)}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
[AGENT_TOKEN_HEADER]: host.token,
|
||||
@@ -314,7 +328,7 @@ export class RemoteSpawner implements Spawner {
|
||||
touch(userId: string): void {
|
||||
void this.hostFor(userId)
|
||||
.then((host) =>
|
||||
this.doFetch(`${host.agentUrl}/touch/${encodeURIComponent(userId)}`, {
|
||||
this.doFetch(`${agentBaseUrlOf(host)}/touch/${encodeURIComponent(userId)}`, {
|
||||
method: 'POST',
|
||||
headers: { [AGENT_TOKEN_HEADER]: host.token },
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user