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:
1 parent
9c2e7975ac
commit
452924d89c
100 files changed
+1167
-453
No files matched your search
@@ -8,8 +8,8 @@
|
||||
*
|
||||
* | hostId | endpoint | 真实语义 |
|
||||
* |---|---|---|
|
||||
* | `w-47` | `http://127.0.0.1:19100` | **直连本机**(Manager 与 worker 同机,node 直接监听) |
|
||||
* | `w-106` | `http://127.0.0.1:19000` | **经 47 上 sshd 的反向隧道落点**(隧道的副作用) |
|
||||
* | `<host-a>` | `http://127.0.0.1:<worker-port-a>` | **直连本机**(Manager 与 worker 同机,node 直接监听) |
|
||||
* | `<host-b>` | `http://127.0.0.1:<worker-port-b>` | **经 47 上 sshd 的反向隧道落点**(隧道的副作用) |
|
||||
*
|
||||
* ⇒ 换会合 / 中继组件时,表里**无法表达**「via(经哪个中继)+ 真实可达地址」,
|
||||
* 只能改表;越晚改代价越大(会合中继拆分方案 §2 C3)。
|
||||
@@ -17,7 +17,7 @@
|
||||
* `Reachability` 把这件事显式化:`via` 指向一个 `Rendezvous` 实现,`address` 是真实地址。
|
||||
*
|
||||
* ## P0-3:为什么要带 `networkId`
|
||||
* 地址是**网内**的:`127.0.0.1:19000` 在 `ops` 里指向 `w-106` 的 relay 落点,在 `u:5` 里
|
||||
* 地址是**网内**的:`127.0.0.1:<worker-port-b>` 在 `ops` 里指向 `<host-b>` 的 relay 落点,在 `u:5` 里
|
||||
* 可能指另一台。只带 `hostId` 的重达性描述**在多网下是有歧义的**,而歧义会以
|
||||
* "打到另一张网的同名节点"这种最贵的形态暴露(静默串网)。⇒ 本类型**必带**网络维度,
|
||||
* `parseReachability()` 从**逻辑名**(`<network_id>/<hostId>`)里取它,调用方不许自己拼。
|
||||
@@ -117,7 +117,7 @@ export function agentBaseUrlOf(host: HostAddressable): string {
|
||||
* `<network_id>/<hostId>`:网络段由**本函数**切出来(`parseLogicalName`),调用方不碰。
|
||||
* ⚠️ **裸 `hostId` 仍兼容**(⇒ 落 `ops`):过渡期不破坏现网调用方与既有单测。
|
||||
*
|
||||
* 强制面:`endpoint` 历史上是完整 URL(`http://127.0.0.1:19000`),也容忍裸
|
||||
* 强制面:`endpoint` 历史上是完整 URL(`http://127.0.0.1:<worker-port-b>`),也容忍裸
|
||||
* `host:port` —— 没写 scheme 时按 `http` 处理,与 `RemoteSpawner` 原先"直接把它当
|
||||
* fetch 基址"的行为一致(fetch 会补 `http://`)。
|
||||
*/
|
||||
@@ -151,7 +151,7 @@ export function toEndpoint(reach: Reachability): string {
|
||||
*
|
||||
* 为什么需要:relay 为每个注册端口在**它自己的回环**上开一条监听,回环口号由 `listen(0)`
|
||||
* 动态分配 ⇒ Manager 拿不到、也推不出,只能拿「要拨的端口号」去 relay 的 `/status` 里查。
|
||||
* 而那个号码就住在 `dsh_hosts.endpoint` 里(`http://127.0.0.1:19000`)⇒ 统一在这里剥出来,
|
||||
* 而那个号码就住在 `dsh_hosts.endpoint` 里(`http://127.0.0.1:<worker-port-b>`)⇒ 统一在这里剥出来,
|
||||
* 别在调用方各写一遍 `split(':')`(IPv6 字面量会切错)。
|
||||
*/
|
||||
export function addressPort(address: string): number | undefined {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 覆盖网络 · **序④(443/TCP 兜底)· L1「去 CF」** —— 地址覆盖(直连目标 IP + 保持 SNI = 域名)。
|
||||
*
|
||||
* ## 它解决的唯一问题
|
||||
* 兜底入口 `relay-direct.ai1net.com` 与主入口 `ai1net.com` **同属 `*.ai1net.com`**,
|
||||
* 兜底入口 `relay-direct.<base-domain>` 与主入口 `<base-domain>` **同属 `*.{base-domain}`**,
|
||||
* 而该泛解析被 Cloudflare 代理 ⇒ **两者都指向 CF**。所以"多了一条入口"并不等于
|
||||
* "CF 不可用时还能连":解析层仍然把客户端送到 CF。本模块把**逐字列出的域名**的解析结果
|
||||
* **钉到指定 IP** ⇒ TCP 直连该 IP,而 TLS **SNI 仍等于 URL 里的域名**(证书校验照旧,不降级)。
|
||||
@@ -23,7 +23,7 @@
|
||||
*
|
||||
* ## 配置(**独立配置项**;⛔ 不塞进 URL、⛔ 不进签名目录 —— 交接单 §4.1-5)
|
||||
* ```
|
||||
* DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.ai1net.com=47.77.182.89
|
||||
* DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.<base-domain>=<server-public-ip>
|
||||
* ```
|
||||
* 逗号多值;同一域名**先出现者生效**(后写的静默覆盖会让"为什么不是我以为的 IP"更难排查)。
|
||||
*
|
||||
|
||||
@@ -71,7 +71,7 @@ export type WebSocketCtor = new (url: string) => WebSocketLike
|
||||
export type RelayClientState = 'idle' | 'connecting' | 'handshaking' | 'up' | 'backoff' | 'queued' | 'stopped'
|
||||
|
||||
export interface RelayClientOptions {
|
||||
/** relay 的 WebSocket 地址:`ws://127.0.0.1:20080/dshs-relay`(R1)或 `wss://<域名>/dshs-relay`(R2)。 */
|
||||
/** relay 的 WebSocket 地址:`ws://127.0.0.1:<relay-port>/dshs-relay`(R1)或 `wss://<域名>/dshs-relay`(R2)。 */
|
||||
url: string
|
||||
hostId: string
|
||||
/**
|
||||
|
||||
@@ -239,7 +239,7 @@ export class RelayDialer {
|
||||
/**
|
||||
* ⛔ `DIAL.target` 是**裸 hostId**,不含网络段(服务端会显式拼上**拨号方自己**那张网,
|
||||
* 见 `server.ts#onDial`)。键从 P0-3 起是逻辑名 ⇒ 这里**必须**剥掉网络段再发。
|
||||
* 漏剥的表现极具误导性:服务端按 `logicalName('ops', 'ops/w-106')` = `ops/ops/w-106` 找会话,
|
||||
* 漏剥的表现极具误导性:服务端按 `logicalName('ops', 'ops/<host-b>')` = `ops/ops/<host-b>` 找会话,
|
||||
* 找不到 ⇒ 回 `target-offline`("节点离线"),而节点其实**好好在册** ——
|
||||
* 实测踩过一次(2026-09-17 07:32 线上,`refused: 8 / streamsOpened: 0`)。
|
||||
*/
|
||||
|
||||
@@ -74,16 +74,21 @@ const MAX_ENTRY_LEN = 512
|
||||
const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex')
|
||||
|
||||
/**
|
||||
* **内置种子的字面量**(引导链的常量位)。已持证书的门户域名、**不新增域名成本**。
|
||||
* ⚠️ `config.ts` 属**基础层**、不许 import 本模块 ⇒ 那边另有一份同样的字面量,
|
||||
* **内置种子的常量位**。
|
||||
* ⛔ 刻意留空 —— 种子是**具体部署的入口地址**,属部署相关值,
|
||||
* 一律由 `DSHS_OVERLAY_BOOTSTRAP_SEEDS` 提供(见 `config/platform.env`)。
|
||||
* 代码内不留真实域名 / IP;未配置 ⇒ 引导链为空、不自动取址。
|
||||
* ⚠️ `config.ts` 属**基础层**、不许 import 本模块 ⇒ 那边另有一份同语义常量,
|
||||
* **改动必须两处同改**(与 `db/types.ts` 的 `DEFAULT_HOST_NETWORK` 同一纪律)。
|
||||
*/
|
||||
export const DEFAULT_OVERLAY_SEED = 'https://ai1net.com/dshs-relay'
|
||||
export const DEFAULT_OVERLAY_SEED = ''
|
||||
|
||||
/** 从环境变量取种子;**没配** ⇒ 用内置常量位。 */
|
||||
/** 从环境变量取种子;**没配** ⇒ 用内置常量位(空 ⇒ 返回空列表)。 */
|
||||
export function overlayEnvSeeds(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
const raw = env.DSHS_OVERLAY_BOOTSTRAP_SEEDS
|
||||
if (raw === undefined) return [DEFAULT_OVERLAY_SEED]
|
||||
if (raw === undefined || raw.trim() === '') {
|
||||
return DEFAULT_OVERLAY_SEED === '' ? [] : [DEFAULT_OVERLAY_SEED]
|
||||
}
|
||||
return [...new Set(raw.split(',').map((s) => s.trim()).filter((s) => s !== ''))]
|
||||
}
|
||||
|
||||
@@ -321,7 +326,7 @@ export function buildDirectoryDocument(input: {
|
||||
/**
|
||||
* 引导地址 → **取目录的 URL**:同 origin、路径固定为 {@link DIRECTORY_PATH}。
|
||||
*
|
||||
* 约定:"**引导地址 = 中继入口同源**"(D3:种子就是 `https://ai1net.com/dshs-relay`,
|
||||
* 约定:"**引导地址 = 中继入口同源**"(D3:种子就是 `https://<base-domain>/dshs-relay`,
|
||||
* 已持证书、不新增域名)⇒ 目录端点只是同一台机器上的另一个路径。
|
||||
* 已经是目录地址(path 相同)⇒ 原样返回,便于"目录里直接写目录 URL"。
|
||||
*/
|
||||
@@ -462,7 +467,7 @@ function entryIdentity(u: URL): string {
|
||||
/**
|
||||
* 选出**可以对外公布**的中继 / 引导地址。
|
||||
*
|
||||
* ⛔ 回环与私网一律剔除:目录是**公网可读**的 —— 公布 `127.0.0.1:20080` 对客户端毫无用处,
|
||||
* ⛔ 回环与私网一律剔除:目录是**公网可读**的 —— 公布 `127.0.0.1:<relay-port>` 对客户端毫无用处,
|
||||
* 还白送一份内网拓扑。对齐红线「**权限只准收窄**」(这里收窄的是**暴露面**)。
|
||||
* 同一语义身份只保留**首次出现**的那条(⇒ `wss://` 写法优先于同源的 `https://` 写法)。
|
||||
*/
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
* ## 格式(**向后兼容,旧配置一字不改照旧可用**)
|
||||
* ```json
|
||||
* {
|
||||
* "w-47": "fc6c…7d01", // 旧写法:裸 hostId ⇒ 运维网 ops
|
||||
* "<host-a>": "fc6c…7d01", // 旧写法:裸 hostId ⇒ 运维网 ops
|
||||
* "manager": { "secret": "515d…3b6ea" }, // 新写法:显式给出 secret
|
||||
* "u:5/pc-1": "aa11…77aa", // 带网:与 u:9/pc-1 互不干扰(网络取自**键**)
|
||||
* "u:9/pc-1": { "secret": "bb22…88bb" }
|
||||
* }
|
||||
* ```
|
||||
* 内联形式(便于 env 传入,**不建议**用于生产,因为 env 会进 `ps`/journald):
|
||||
* `w-47:<64hex>,ops/manager:<64hex>,u:5/pc-1:<64hex>`
|
||||
* `<host-a>:<64hex>,ops/manager:<64hex>,u:5/pc-1:<64hex>`
|
||||
*
|
||||
* ⚠️ 名字段一律走 `network.ts#parseLogicalName`(**唯一入口**)—— 它同时接受
|
||||
* `网/hostId`、`网:hostId` 与旧形态裸 `hostId`,且**网络 id 非法就抛**。
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* node lib/net/relay/main.js --port 20080 --keys-file /etc/dshs/relay-keys.json --base 20000 --span 1000
|
||||
*
|
||||
* # 客户端(worker 侧拨出;R1 用 `ssh -L` 把远端的回环口引到本机来拨)
|
||||
* node lib/net/relay/main.js --client --url ws://127.0.0.1:20080/dshs-relay \
|
||||
* --host w-47 --keys-file /etc/dshs/relay-keys.json --ports 20000
|
||||
* node lib/net/relay/main.js --client --url ws://127.0.0.1:<relay-port>/dshs-relay \
|
||||
* --host <host-a> --keys-file /etc/dshs/relay-keys.json --ports 20000
|
||||
* ```
|
||||
*
|
||||
* ⚠️ **本文件暂不读 `src/config.ts`**:R1 阶段 relay 是**独立可选单元**,且 `src/config.ts`
|
||||
@@ -93,7 +93,7 @@ function parseArgs(argv: readonly string[]): Args {
|
||||
'usage: main.js [--client --url <ws> --host <id> --ports <a,b>] [--network <id>] [--port n] [--keys-file p] [--base n] [--span n] [--max-hosts n]\n' +
|
||||
' 容量准入:--max-hosts(0 = 不限);满载时新节点收到 at-capacity + retryAfterMs 并**排队等待**,已在册节点重连优先。\n' +
|
||||
' 网维度(P0-1):--network 缺省 ops;拨号方白名单 DSHS_RELAY_DIALERS 接受 "ops:manager" / "manager"(旧写法)两种。\n' +
|
||||
` 引导(P0-2):客户端**不带 --url** 时按「缓存目录 > 内置种子」取址;内置种子 = ${DEFAULT_OVERLAY_SEED}\n` +
|
||||
` 引导(P0-2):客户端**不带 --url** 时按「缓存目录 > 内置种子」取址;内置种子 = ${DEFAULT_OVERLAY_SEED === '' ? '(未配置 ⇒ 引导链为空)' : DEFAULT_OVERLAY_SEED}\n` +
|
||||
' (env 显式 = --url / DSHS_RELAY_URL,**压制引导链**;受信目录公钥 = DSHS_OVERLAY_DIR_PUBKEYS)。\n',
|
||||
)
|
||||
process.exit(0)
|
||||
|
||||
@@ -36,7 +36,7 @@ export const OPS_NETWORK = 'ops'
|
||||
const TENANT_NET_RE = /^u:[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/
|
||||
const GENERIC_NET_RE = /^[a-z0-9][a-z0-9_.-]{0,63}$/
|
||||
|
||||
/** hostId 的合法形状(`w-47` / `w-106` / `manager` …)。 */
|
||||
/** hostId 的合法形状(`<host-a>` / `<host-b>` / `manager` …)。 */
|
||||
const HOST_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/
|
||||
|
||||
/** 逻辑名的**规范分隔符**。`<network_id>/<hostId>` —— `network_id` 不含 `/`,故**首个** `/` 即分隔点。 */
|
||||
@@ -123,7 +123,7 @@ export function logicalName(network: string, hostId: string): string {
|
||||
* 反解一个逻辑名 / 配置项。
|
||||
*
|
||||
* ## 三条分隔规则(顺序即优先级)
|
||||
* 1. 含 `/` ⇒ 按**首个** `/` 切(`ops/manager` · `u:5/w-106`)—— 规范形态;
|
||||
* 1. 含 `/` ⇒ 按**首个** `/` 切(`ops/manager` · `u:5/<host-b>`)—— 规范形态;
|
||||
* 2. 否则含 `:` ⇒ 按**最后一个** `:` 切(`ops:manager` · `u:5:manager`)——
|
||||
* 为的是兼容运维习惯的 `network:hostId` 写法;⚠️ 必须从**右**切:`u:5` 里的 `:` 属于网络 id;
|
||||
* 3. 都不含 ⇒ **旧形态**(R5 时代的扁平 `hostId`)⇒ 落在 `ops`,**旧配置照旧可用**。
|
||||
|
||||
@@ -28,6 +28,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
|
||||
import { dirname } from 'node:path'
|
||||
|
||||
import { OPS_NETWORK, assertNetworkId, isHostId, logicalName, networkKindOf } from './network.js'
|
||||
import { installDir } from '../../platform-paths.js'
|
||||
import { verifySignedPayload, type IdentityReason } from './identity.js'
|
||||
|
||||
// ── 邀请(准入)凭据 ─────────────────────────────────────────────────────────
|
||||
@@ -477,7 +478,9 @@ export function deriveDropIn(
|
||||
const hosts = [...(deriveDialers(reg, [network]).get(network) ?? new Set<string>())].sort()
|
||||
// 逻辑名形态(`<网>/<hostId>`)—— `normalizeDialers` 的**规范形态**;⛔ 不用 `网:hostId` 旧式写法。
|
||||
const entries = hosts.map((h) => logicalName(network, h))
|
||||
const libDir = opts.libDir ?? '/opt/dsh-relay'
|
||||
// relay 代码安装根:**部署相关 ⇒ 不写死**(`DSH_RELAY_LIB_DIR` 可显式指定,
|
||||
// 缺省取本进程的安装根 —— relay 进程跑在自己的安装目录下,即为正确值)。
|
||||
const libDir = opts.libDir ?? process.env.DSH_RELAY_LIB_DIR ?? installDir()
|
||||
const unit = opts.unit ?? 'dshs-relay'
|
||||
return [
|
||||
`# 由控制面**派生**(${unit} · network=${network})—— 源 = 网注册表里该网的 approved 集合`,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* ## 与其他两个实现的关系(同一 `Rendezvous` 接口,可共存、可逐个切换)
|
||||
* | 实现 | `via` | Manager 侧看到的地址 | 数据面 |
|
||||
* |---|---|---|---|
|
||||
* | `LocalRendezvous` | `local` | `127.0.0.1:19100`(同机直连) | 无中转 |
|
||||
* | `ManagerSshRendezvous` | `manager-ssh` | `127.0.0.1:19000`(**sshd 反向隧道落点**) | Manager 主机上的 sshd |
|
||||
* | `LocalRendezvous` | `local` | `127.0.0.1:<worker-port-a>`(同机直连) | 无中转 |
|
||||
* | `ManagerSshRendezvous` | `manager-ssh` | `127.0.0.1:<worker-port-b>`(**sshd 反向隧道落点**) | Manager 主机上的 sshd |
|
||||
* | **`RelayRendezvous`** | `relay` | `127.0.0.1:<relay 动态分配>`(**relay 开了回环监听**) | relay 的一条出向 wss |
|
||||
*
|
||||
* 三者对 Manager 侧**同形**(都是 `host:port`)⇒ 换实现不动调用方,这是 S0 抽 `Reachability`
|
||||
@@ -24,7 +24,7 @@ import type { AddressLookup, Rendezvous } from '../rendezvous.js'
|
||||
import { parseLogicalName } from './network.js'
|
||||
|
||||
export interface RelayRendezvousOptions {
|
||||
/** relay 自身的拨号目标(诊断 / 管理面展示用),如 `wss://dsh.ai1net.com/dshs-relay`。 */
|
||||
/** relay 自身的拨号目标(诊断 / 管理面展示用),如 `wss://dsh.<base-domain>/dshs-relay`。 */
|
||||
dialTargetUrl: string
|
||||
/**
|
||||
* **逻辑名** → `host:port` 的查表函数(会合实现不直接连 DB,与另两个实现一致)。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
* `HELLO` 的 MAC 输入刻意保持 `${hostId}|${ts}|${nonce}|${portsCsv}` **一字不改**:现网 47 / 106
|
||||
* 上跑的是旧客户端,改 MAC 公式 = 硬断(必须两端同时升级)。而网络维度的**真判据在服务端**
|
||||
* (白名单按网络分桶 + 同网校验),`network` 只是"我属于哪张网"的声明 —— 声明错了也不会多拿到
|
||||
* 任何东西:想拨 `ops/w-106` 就得有一个**在 `ops` 桶里的 hostId 密钥**。⇒ 安全性不依赖这个字段,
|
||||
* 任何东西:想拨 `ops/<host-b>` 就得有一个**在 `ops` 桶里的 hostId 密钥**。⇒ 安全性不依赖这个字段,
|
||||
* 而兼容性(旧客户端不声明 `network` ⇒ 按 `ops` 处理)正好是**存量全部落在运维网**的现网事实。
|
||||
*
|
||||
* ## 稳定性(本轮重点)
|
||||
@@ -520,7 +520,7 @@ export interface RelayStatus {
|
||||
* 序⑤(观测最小集):`DIAL` 的**判别器计数**。
|
||||
*
|
||||
* 为什么需要它:443 单 §12 留下的教训原文是「**静默失效靠判别器定位**」,判别器就是
|
||||
* 「relay 到底有没有 `DIAL`」—— 今天它**只存在于日志行**(`DIAL manager -> w-106:21000 ok`),
|
||||
* 「relay 到底有没有 `DIAL`」—— 今天它**只存在于日志行**(`DIAL manager -> <host-b>:21000 ok`),
|
||||
* 脚本无法断言 ⇒ 观测最小集缺了最关键的一条。
|
||||
*
|
||||
* 为什么不复用 `refused`:`refused` 是**所有**拒绝的合计(`HELLO` 越界、端口越界、`DIAL`…),
|
||||
|
||||
@@ -329,7 +329,7 @@ export class RelayFailoverSupervisor {
|
||||
*
|
||||
* 两条触发路径共用本函数:**健康巡检**(`tick()` 已按冷却过滤候选)与
|
||||
* **「目录地址变了」**(`refreshOverlay` 直接调 `replace`,**它不看冷却**)。
|
||||
* 首轮真机实测(11:43:26):`wss://106… -> wss://ai1net.com…` —— 而 `ai1net.com` 十几分钟前
|
||||
* 首轮真机实测(11:43:26):`wss://106… -> wss://<base-domain>…` —— 而 `<base-domain>` 十几分钟前
|
||||
* **刚被冷却**,只是 `refreshOverlay` 的周期到了、按"地址变了"又把它换回来
|
||||
* ⇒ **抖动抑制形同不存在**(D5 的意图被另一条路径绕开)。
|
||||
* ⇒ 统一在这一处把关:**directory 路径**上,冷却期内的目标**一律不换**。
|
||||
@@ -353,7 +353,7 @@ export class RelayFailoverSupervisor {
|
||||
/**
|
||||
* 🔴 **失败的候选也必须进冷却** —— 这是真机上想清楚才补上的一条(不是理论洁癖):
|
||||
*
|
||||
* 生产目录的 `relays[]` = `[ai1net.com(47), relay-direct.ai1net.com(47), 106]`
|
||||
* 生产目录的 `relays[]` = `[<base-domain>(47), relay-direct.<base-domain>(47), 106]`
|
||||
* ——**前两条落在同一台机器上**。杀 47 时,若只排除"当前 url"、不排除"刚试失败的候选",
|
||||
* 那么每次巡检都会**卡在候选②上反复失败**,**永远推进不到候选③(106)** ⇒
|
||||
* 链虽然"不再退化成单点",却依然**换不过去**。
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*
|
||||
* ## 现状与目标
|
||||
* 今天"会合 + 中继"**不是一个组件,而是 Manager 主机上 sshd 的副作用**:
|
||||
* 会合点 = `47.77.182.89:32022`,中继落点 = Manager 的 `127.0.0.1`。
|
||||
* 会合点 = `<server-public-ip>:<ssh-port>`,中继落点 = Manager 的 `127.0.0.1`。
|
||||
* 本模块的作用是**先把接口抽出来**,让 SSH 隧道退化成"第一个可替换实现"
|
||||
* —— ⛔ 这一步**不换协议**,只换绑定与寻址(换 WireGuard / TURN 属远期)。
|
||||
*
|
||||
@@ -48,7 +48,7 @@ export interface Rendezvous {
|
||||
*/
|
||||
export type AddressLookup = (name: string) => string | undefined
|
||||
|
||||
/** 同机直连:Manager 能直接连到 worker 的端口,不经任何中转(`w-47` 就是这一类)。 */
|
||||
/** 同机直连:Manager 能直接连到 worker 的端口,不经任何中转(`<host-a>` 就是这一类)。 */
|
||||
export class LocalRendezvous implements Rendezvous {
|
||||
readonly id = VIA_LOCAL
|
||||
|
||||
@@ -81,7 +81,7 @@ export class ManagerSshRendezvous implements Rendezvous {
|
||||
|
||||
constructor(
|
||||
private readonly opts: {
|
||||
/** 会合点的 SSH 目标,如 `root@47.77.182.89:32022`。 */
|
||||
/** 会合点的 SSH 目标,如 `root@<server-public-ip>:<ssh-port>`。 */
|
||||
target: string
|
||||
addressOf: AddressLookup
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user