Files
dsh_shenxian/test/orchestrator-rehydrate.test.mjs
T
admin 452924d89c 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 未被误建)。
2026-09-19 15:12:19 +08:00

172 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 覆盖网络线 序 ㉕ · 「逐步拉起」单测 —— **纯函数面**(零 IO、零 systemd、零生产副作用)。
*
* ## 为什么只测纯函数
* 被替换掉的是「启动即 `cleanAllStaleScopes()`」。它的输入是 **OS 层既有 scope**
* (`systemctl list-units` + `systemctl show -p Description`),在 Windows 开发机上
* 拿不到 ⇒ 真机部分由 §6/S6 在 106 上验收。
* 但**最容易出错、也最致命**的那一段恰好是纯的 —— **把 `Description` 解析回实例三要素**:
* 解析错 ⇒ 后续替换时定位到别人的实例(跨租户最坏情形)。故这一段必须穷举式钉住。
*
* 另加两条**源码级守卫**(照 序⑦ `T38` 先例):认领逻辑必须真的接在构造函数上、
* 且⛔ 不许把「认领」写成「什么都不做」(那会让档案 30 的孤儿失控)。
*
* 运行:`node --test test/orchestrator-rehydrate.test.mjs`(⚠️ **刻意不进 `npm test`** ——
* 本序要求零回归基线 `npm test` 176/175/0/1 **逐字不变**,加进去会改测试总数)。
*
* @module test/orchestrator-rehydrate
*/
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { test } from 'node:test'
import {
decideScopeAction,
parseScopeDescription,
parseScopeUnitName,
} from '../lib/supervisor/orchestrator.js'
/**
* 夹具 = 106 上 **真实** 实例 `dsh-100002-ef8d1d12.scope` 的 `Description` 精简等价形态
* (保留全部关键 flag 与其真实相对顺序,省掉无关的 `--ro-bind-try` 白名单项)。
* ⚠️ 顺序刻意保留:`--chdir` 在 bwrap 段、`--profile/--port` 在 setpriv 之后。
*/
const REAL_DESC =
'/usr/bin/bwrap --ro-bind /usr /usr --tmpfs /etc ' +
'--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/tmp /tmp ' +
'--bind /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0 ' +
'--unshare-pid ' +
'--chdir /var/lib/dsh-test/users/4092b965-2f68-4977-9989-68b3966f7df0/ws ' +
'-- setpriv --reuid 100002 --regid 100002 --clear-groups ' +
'/usr/bin/dsh --profile web --host 127.0.0.1 --port 21000'
const UID = 100002
const USER = '4092b965-2f68-4977-9989-68b3966f7df0'
const FOLDER = `/var/lib/dsh-test/users/${USER}/ws`
/* ── R1–R5:scope 名解析(⛔ 只认本平台自己的形态) ─────────────────────────── */
test('R1 真机形态:dsh-100002-ef8d1d12.scope ⇒ uid 100002', () => {
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope'), 100002)
})
test('R2 ⛔ 大写 hex / 非 scope / 别的单元一律不认', () => {
assert.equal(parseScopeUnitName('dsh-100002-EF8D1D12.scope'), undefined)
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.service'), undefined)
assert.equal(parseScopeUnitName('dshs-relay.service'), undefined)
assert.equal(parseScopeUnitName('dsh-100002-ef8d1d12.scope.bak'), undefined)
assert.equal(parseScopeUnitName(''), undefined)
})
test('R3 ⛔ uid=0 / 缺段 一律不认(防误伤 systemd 自身与门户进程)', () => {
assert.equal(parseScopeUnitName('dsh-0-ef8d1d12.scope'), undefined)
assert.equal(parseScopeUnitName('dsh--ef8d1d12.scope'), undefined)
assert.equal(parseScopeUnitName('dsh-100002.scope'), undefined)
})
/* ── R6–R11:Description 解析(真机夹具) ──────────────────────────────────── */
test('R6 真机夹具:三要素全部解出且 userId 取自 --chdir(⛔ 不是取自 --bind)', () => {
const info = parseScopeDescription(REAL_DESC, UID)
assert.deepEqual(info, { userId: USER, role: 'main', port: 21000, folder: FOLDER })
})
test('R7 ⛔ uid 交叉校验不符 ⇒ 拒(scope 名与 argv 非同源 = 半截信息,必须停)', () => {
assert.equal(parseScopeDescription(REAL_DESC, 100003), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace('--reuid 100002', '--reuid 100009'), UID), undefined)
})
test('R8 ⛔ 缺 --profile 或 profile 非 web/headless ⇒ 拒(role 猜不得)', () => {
assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web ', ''), UID), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace('--profile web', '--profile weird'), UID), undefined)
})
test('R9 ⛔ main 缺 --port / 端口非数字 / 越界 ⇒ 拒', () => {
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', ''), UID), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port abc'), UID), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 0'), UID), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace('--port 21000', '--port 70000'), UID), undefined)
})
test('R10 ⛔ 缺 --chdir / 相对路径 / 路径里没有 /users/ 段 ⇒ 拒(拿不到 userId)', () => {
assert.equal(parseScopeDescription(REAL_DESC.replace('--chdir ' + FOLDER + ' ', ''), UID), undefined)
assert.equal(parseScopeDescription(REAL_DESC.replace(FOLDER, 'ws'), UID), undefined)
assert.equal(
parseScopeDescription(REAL_DESC.replace(FOLDER, '/srv/ws').replace(/\/var\/lib\/dshs\/users\//g, '/srv/'), UID),
undefined,
)
})
test('R11 watchdog:headless 且有端口 ⇒ 拒(不自洽);headless 无端口 ⇒ 解出但 role=watchdog', () => {
const headlessWithPort = REAL_DESC.replace('--profile web', '--profile headless')
assert.equal(parseScopeDescription(headlessWithPort, UID), undefined)
const headless = headlessWithPort.replace(' --port 21000', '')
const info = parseScopeDescription(headless, UID)
assert.equal(info?.role, 'watchdog')
assert.equal(info?.port, undefined)
assert.equal(info?.userId, USER)
})
/* ── R12–R15:处置判定(认领 vs 按旧行为停) ───────────────────────────────── */
test('R12 合法 main ⇒ adopt', () => {
const info = parseScopeDescription(REAL_DESC, UID)
assert.deepEqual(decideScopeAction(info, false), { kind: 'adopt' })
})
test('R13 ⛔ 同 uid 多 scope ⇒ 全部停(档案 30 的风险本体:多实例共 profile)', () => {
const info = parseScopeDescription(REAL_DESC, UID)
assert.deepEqual(decideScopeAction(info, true), { kind: 'stop', reason: 'dup-uid' })
})
test('R14 ⛔ 解析失败 ⇒ 停(宁可清掉,不留半截实例)', () => {
assert.deepEqual(decideScopeAction(undefined, false), { kind: 'stop', reason: 'unparsable' })
})
test('R15 ⛔ watchdog ⇒ 停(一次性 headless、无监听端口 ⇒ 无法确认健康,留着无收益)', () => {
const info = parseScopeDescription(REAL_DESC.replace('--profile web', '--profile headless').replace(' --port 21000', ''), UID)
assert.deepEqual(decideScopeAction(info, false), { kind: 'stop', reason: 'no-probe-target' })
})
/* ── R16–R18:源码级接线守卫(照 序⑦ T38 先例) ────────────────────────────── */
const SRC = await readFile(new URL('../src/supervisor/orchestrator.ts', import.meta.url), 'utf8')
test('R16 接线:构造函数必须调 rehydrateAdoptedScopes(⛔ 不是裸 cleanAllStaleScopes)', () => {
assert.ok(
/this\.portGuard = createPortGuard\(config\.portGuard\)[\s\S]{0,400}this\.rehydrateAdoptedScopes\(\)/.test(SRC),
'constructor 未接认领逻辑(仍是启动即清空)',
)
})
test('R17 ⛔ cleanAllStaleScopes 不许被删(回滚路径 + 非 account 回退都还在)', () => {
assert.ok(SRC.includes('private cleanAllStaleScopes(): void'))
assert.ok(
/private rehydrateAdoptedScopes\(\): void \{[\s\S]{0,400}this\.cleanAllStaleScopes\(\)/.test(SRC),
'非 account 回退丢了',
)
})
test('R18 ⛔ 认领不得写进 mains(写进去 ⇒ enter 复用分支拿不到 token ⇒ 503)', () => {
const body = SRC.slice(SRC.indexOf('private adoptOne('), SRC.indexOf('private probeAdopted('))
assert.ok(body.includes('this.adopted.set('), 'adoptOne 未登记到 adopted')
assert.ok(!body.includes('this.mains.set('), '⛔ adoptOne 把实例写进了 mains ⇒ 用户会被 503 挡住')
assert.ok(!body.includes('spawn('), '⛔ adoptOne 里出现了 spawn ⇒ 违反"不 spawn"')
const afterAdopt = body.slice(body.indexOf('this.adopted.set('))
assert.ok(!afterAdopt.includes('stopUnit('), '⛔ adopt 路径上还停了实例(认领应当只登记 + 探活)')
})
test('R19 ⛔ 认领/回收路径里不得出现凭据落盘(R11:安全维度不许净变差)', () => {
const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('/** 档案 30:清掉指定 uid'))
for (const banned of ['writeFileSync', 'appendFileSync', 'launchToken']) {
assert.ok(!body.includes(banned), `rehydrate 路径里出现了 ${banned}`)
}
})
test('R20 ⛔ 节流:认领必须逐条经 setTimeout 排队(不得同步一次全跑)', () => {
const body = SRC.slice(SRC.indexOf('private rehydrateAdoptedScopes('), SRC.indexOf('private scanExistingScopes('))
assert.ok(body.includes('setTimeout('), '认领没有节流')
assert.ok(SRC.includes('DSHS_REHYDRATE_STAGGER_MS'), '节流阈值不是可配的环境键')
assert.ok(SRC.includes('DSHS_REHYDRATE_PROBE_MS'), '探活超时不是可配的环境键')
})