feat(overlay): 覆盖网络线 序㉔–㊸ 补提交 —— 源码与已部署产物对齐
把这批「已 scp 到 47 / 106 生产并在跑、但一直未入 git」的实现补进版本库
(其中 P2 的全部新源码此前一直是 untracked)。
分棒内容:
- 序㉔ 内容分发块级寻址:src/net/relay/content/{runtime,source}.ts
- 序㉖ 骨干稳定选路(jitter):src/net/relay/{index,network}.ts、src/web/server.ts
- 序㉘ 组密钥加密(GCM 确定性):src/net/relay/content/{runtime,source}.ts
- 序㉛ P1 一键加入 + 分组准入:src/net/relay/{join,registry}.ts、
src/web/routes/overlay-nodes.ts、scripts/overlay-node-{join,admit}.cjs、
test/overlay-join.test.mjs
- 序㊵/㊶ 直连打洞 + peer 档:src/net/relay/direct/{candidate,index,punch}.ts、
scripts/overlay-direct-probe.cjs、test/overlay-direct.test.mjs
- 序㊷/㊸ 观测面:scripts/overlay-probe.cjs、scripts/overlay-failover-drill.cjs
零回归三件套(2026-09-18 16:2x 提交前复跑,全绿):
- npm test 201 tests / pass 200 / fail 0 / skipped 1(Node v22.22.2)
- overlay-probe.cjs --table 28 PASS / 0 SKIP / 0 FAIL
- overlay-failover-drill.cjs --scene all --table 12 PASS / 0 SKIP / 0 FAIL
⛔ 未纳入:_tmp_seq24/、_tmp_seq40/、_中间产物_待清理/(本机临时产物,仍 untracked)
🔴 未实施:D8 云安全组乙-1(待人工在云控制台落地,见
交接单_覆盖网络直连与P2P_20260918.md §8.11)
This commit is contained in:
1 parent
09ce76f3af
commit
c452013129
18 files changed
+6604
-78
No files matched your search
@@ -41,8 +41,12 @@
|
||||
* 3. **`systemctl is-active` 在 inactive 时退出码 = 3** ⇒ `execFileSync` 会抛 ⇒ 首轮**中断在幕1 半路**
|
||||
* 且把 47 的 relay 留在停用态。凡是**读状态**的远端命令一律 `|| true`。
|
||||
*
|
||||
* ## 运行(cwd = 工作区根;⛔ 阈值零硬编码)
|
||||
* ## 运行(🆕 序㊸:**cwd 不再受限**;⛔ 阈值零硬编码)
|
||||
* `node "D:/github/dsh_shenxian/scripts/overlay-failover-drill.cjs" [--scene 1|2|3|all]`
|
||||
* ⚠️ **参数表定位已与 cwd 解耦**(从代码仓根 / 任意目录都可跑):候选目录链 = `--table` > `--dir`
|
||||
* > `DSHS_OVERLAY_TABLE_DIR` > **注册文件**(缺省 `~/.dshs/overlay-table-dir`)> `cwd` > 脚本目录及上两级。
|
||||
* 🔴 `--scene trace` / `--scene sample` 的**明细落盘**仍按 `cwd` 写 `_中间产物_待清理/seq9-trace/`
|
||||
* (那两幕是取证子命令,⛔ 不是本条约的范围)。
|
||||
*
|
||||
* ⚠️ **本脚本会真停生产单元**(`dshs-relay`);`finally` 里**一律复原**(失败也复原)。
|
||||
*/
|
||||
@@ -51,6 +55,7 @@
|
||||
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
/** 参数表文件名(⛔ 不写死日期数字)。 */
|
||||
@@ -61,6 +66,14 @@ const KEY_ONLY_RE = /^`([A-Z0-9_]+)`$/
|
||||
|
||||
/* ─────────── 参数表装载(与 overlay-probe.cjs 同款;⛔ 脚本内无魔数) ─────────── */
|
||||
|
||||
/**
|
||||
* 🆕 **序㊸:参数表定位与 cwd 解耦**(与 `overlay-probe.cjs` **同款**,改动理由见该文件同名注释)。
|
||||
* 候选目录链 = `--table` > `--dir` > `DSHS_OVERLAY_TABLE_DIR` > **注册文件**
|
||||
* (`DSHS_OVERLAY_TABLE_REGISTRY`,缺省 `~/.dshs/overlay-table-dir`,一行一个目录、`#` 注释)
|
||||
* > `cwd` > 脚本自身目录及上两级。
|
||||
* 🔴 **"恰好 1 个才合法"保留且更严**:任一候选目录 ≥2 份 ⇒ 立即报错;跨目录合计 ≥2 份 ⇒ 也报错。
|
||||
* ⛔ 一份都没找到 ⇒ 报错并列出**搜过的目录**,⛔ **不取缺省**。
|
||||
*/
|
||||
function resolveTablePath(argv) {
|
||||
const argOf = (name) => {
|
||||
const i = argv.indexOf(name)
|
||||
@@ -68,12 +81,78 @@ function resolveTablePath(argv) {
|
||||
}
|
||||
const explicit = argOf('--table')
|
||||
if (explicit !== undefined) return explicit
|
||||
const dir = argOf('--dir') ?? process.env.DSHS_OVERLAY_TABLE_DIR ?? process.cwd()
|
||||
const hits = fs.readdirSync(dir).filter((n) => TABLE_RE.test(n))
|
||||
if (hits.length !== 1) {
|
||||
throw new Error(`在 ${dir} 下按 /${TABLE_RE.source}/ 找到 ${hits.length} 个参数表(要求恰好 1 个)`)
|
||||
const registry = readTableRegistry()
|
||||
const out = []
|
||||
const push = (d) => {
|
||||
if (typeof d === 'string' && d.trim() !== '') out.push(path.resolve(d.trim()))
|
||||
}
|
||||
return path.join(dir, hits[0])
|
||||
push(argOf('--dir'))
|
||||
push(process.env.DSHS_OVERLAY_TABLE_DIR)
|
||||
for (const d of registry.dirs) push(d)
|
||||
push(process.cwd())
|
||||
push(__dirname)
|
||||
push(path.join(__dirname, '..'))
|
||||
push(path.join(__dirname, '..', '..'))
|
||||
const seen = new Set()
|
||||
const dirs = []
|
||||
for (const d of out) {
|
||||
if (seen.has(d)) continue
|
||||
seen.add(d)
|
||||
dirs.push(d)
|
||||
}
|
||||
const hits = []
|
||||
const scanned = []
|
||||
for (const dir of dirs) {
|
||||
if (!fs.existsSync(dir)) continue
|
||||
let names
|
||||
try {
|
||||
names = fs.readdirSync(dir).filter((n) => TABLE_RE.test(n))
|
||||
} catch (err) {
|
||||
continue // 目录存在但读不了(权限等)⇒ 不算"扫过",也不冒充"没有"
|
||||
}
|
||||
scanned.push(dir)
|
||||
if (names.length > 1) {
|
||||
throw new Error(
|
||||
`在 ${dir} 下按 /${TABLE_RE.source}/ 找到 ${names.length} 个参数表(要求恰好 1 个):${names.join(' , ')}`,
|
||||
)
|
||||
}
|
||||
if (names.length === 1) hits.push(path.join(dir, names[0]))
|
||||
}
|
||||
if (hits.length === 0) {
|
||||
throw new Error(
|
||||
`在 ${scanned.length} 个候选目录下按 /${TABLE_RE.source}/ **一份都没找到**(要求恰好 1 个)\n` +
|
||||
` 搜过的目录:\n${scanned.map((d) => ` · ${d}`).join('\n')}\n` +
|
||||
` 注册文件:${registry.file}${registry.readError === null ? `(${registry.dirs.length} 条)` : `(读取失败:${registry.readError})`}\n` +
|
||||
` ⇒ 处置(三选一):① \`--table <file>\` 直接指定 ② 把参数表所在目录(一行一个)写进上面的注册文件 ③ 在参数表所在目录下运行`,
|
||||
)
|
||||
}
|
||||
if (hits.length > 1) {
|
||||
throw new Error(
|
||||
`跨候选目录共找到 ${hits.length} 个参数表(要求恰好 1 个,⛔ 不猜哪一份是对的):\n` +
|
||||
`${hits.map((h) => ` · ${h}`).join('\n')}`,
|
||||
)
|
||||
}
|
||||
return hits[0]
|
||||
}
|
||||
|
||||
/** 读**注册文件**(一行一个目录)。⛔ 读不到就返回**读取失败原因**(⛔ 不冒充"没有")。 */
|
||||
function readTableRegistry() {
|
||||
const envFile = process.env.DSHS_OVERLAY_TABLE_REGISTRY
|
||||
const file =
|
||||
typeof envFile === 'string' && envFile.trim() !== ''
|
||||
? envFile.trim()
|
||||
: path.join(os.homedir(), '.dshs', 'overlay-table-dir')
|
||||
let text
|
||||
try {
|
||||
text = fs.readFileSync(file, 'utf8')
|
||||
} catch (err) {
|
||||
return { file, dirs: [], readError: (err && err.code) || String(err) }
|
||||
}
|
||||
const dirs = text
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.replace(/#.*$/, '').trim())
|
||||
.filter((l) => l !== '')
|
||||
return { file, dirs, readError: null }
|
||||
}
|
||||
|
||||
const cleanValue = (raw) => String(raw).replace(/[*`]/g, '').trim()
|
||||
@@ -173,7 +252,9 @@ async function main() {
|
||||
const sshTo = r.num('SSH_TIMEOUT_MS')
|
||||
const h47 = r.need('SSH_TARGET_47')
|
||||
const h106 = r.need('SSH_TARGET_106')
|
||||
const relayUnit = r.need('DRILL_RELAY_UNIT')
|
||||
// ⚠️ 序㊳ 改名:`DRILL_RELAY_UNIT` → `RELAY_UNIT_NAME`(它记的是"两台机的 relay 单元名"这一
|
||||
// **事实**、并非演练专用坐标 —— 探针 `OBS-08` 也读它 ⇒ 原 `DRILL_` 前缀与用途不相称)。
|
||||
const relayUnit = r.need('RELAY_UNIT_NAME')
|
||||
const managerUnit = r.need('DRILL_MANAGER_UNIT')
|
||||
const deadlineMs = r.num('RELAY_FAILOVER_DEADLINE_MS')
|
||||
const pollMs = r.num('DRILL_POLL_MS')
|
||||
|
||||
Reference in new issue
Block a user