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:
admin committed 2026-09-19 15:12:19 +08:00
1 parent 9c2e7975ac
commit 452924d89c
100 files changed
+1167 -453

No files matched your search

+36 -8
View File
@@ -9,6 +9,8 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { homedir, hostname } from 'node:os'
import { join } from 'node:path'
import { installDir as installDirDefault, platformDir as platformDirDefault } from './platform-paths.js'
/** Isolation tier. `soft` = per-user home/workspace + sandbox (same OS user);
* `account` = per-user OS account via a setuid wrapper (Linux, needs root). */
export type IsolationMode = 'soft' | 'account'
@@ -32,6 +34,17 @@ export interface ServerConfig {
dbUrl?: string
/** Root under which per-user homes (`users/<id>/home`) and workspaces live. */
dataRoot: string
/** Parent of the platform-private dirs below. Deployment-varying ⇒ from config
* (`DSH_PLATFORM_DIR`), never a hardcoded absolute path. */
platformDir: string
/** Platform state dir (managed lists, capabilities, runtime baseline). */
stateDir: string
/** Platform backup dir (backups taken before the platform rewrites a user home file). */
backupDir: string
/** Platform artifact dir (plugin / product tarballs served to instances). */
artifactDir: string
/** Code install root (`lib/`、`scripts/` 所在);由本模块位置推导,无需配置。 */
installDir: string
/** Shared read-only skill directory for all users (injected as
* `DSH_BUNDLED_SKILL_DIR` into every spawned DSH); empty = feature off. */
bundledSkillDir: string
@@ -116,18 +129,18 @@ export interface ServerConfig {
clusterWorkerDataRoot: string
/**
* **worker 侧**会合地址(覆盖网络 S1):worker 主动拨入的 SSH 反向隧道落点。
* 形如 `ssh://root@47.77.182.89:32022`(也接受不带 scheme 的 `root@host:port`)。空 = 隧道关闭。
* 形如 `ssh://root@<server-public-ip>:<ssh-port>`(也接受不带 scheme 的 `root@host:port`)。空 = 隧道关闭。
* ⚠️ 与旧变量 `DSHS_TUNNEL_TARGET` **双路径并存**(新变量优先、旧变量兜底)⇒
* 删掉新 env 即回到旧路径,**零代码回滚**。
*/
clusterRendezvousUrl: string
/**
* **Manager 侧**用的自研中继入口(覆盖网络 R3),如 `wss://ai1net.com/dshs-relay`。
* **Manager 侧**用的自研中继入口(覆盖网络 R3),如 `wss://<base-domain>/dshs-relay`。
* 仅作管理面展示 / 诊断(`RelayRendezvous.dialTarget()`);空 = 该实现不注册。
*/
relayUrl: string
/**
* 中继的**状态查询地址**(覆盖网络 R3),如 `http://127.0.0.1:20080/status`。
* 中继的**状态查询地址**(覆盖网络 R3),如 `http://127.0.0.1:<relay-port>/status`。
*
* 为什么必须查它:relay 为每个注册端口在**它自己的回环**上开一条监听,端口号是
* `listen(0)` 动态分配的(实测 42067)⇒ **Manager 无法从 `dsh_hosts.endpoint` 推出来**,
@@ -258,6 +271,8 @@ export interface ConfigOverrides {
dbPath?: string
dbUrl?: string
dataRoot?: string
platformDir?: string
installDir?: string
bundledSkillDir?: string
dshCommand?: string[]
logLevel?: string
@@ -394,10 +409,14 @@ const DEFAULT_EMAIL_GUARD = {
}
/**
* 覆盖网络 P0-2:**内置种子**(引导链的常量位)。
* 锚在已持证书的门户域名上(**不新增域名**);第二地域**留位不填**。
*
* ⛔ 这里**刻意留空** —— 种子是具体部署的入口地址,属部署相关值,
* 一律由 `DSHS_OVERLAY_BOOTSTRAP_SEEDS` 提供(见 `config/platform.env`)。
* 代码内不留任何真实域名 / IP ⇒ 仓库副本换一个部署者也不会带出别人的地址。
* 未配置 ⇒ 引导链为空,覆盖网络不自动取址(可显式 `--url` 指定)。
* ⚠️ 目录端点路径由 `net/relay/directory.ts` 的 `DIRECTORY_PATH` 决定(同源约定)。
*/
const DEFAULT_OVERLAY_BOOTSTRAP_SEEDS = ['https://ai1net.com/dshs-relay']
const DEFAULT_OVERLAY_BOOTSTRAP_SEEDS: string[] = []
/** Load the encryption secret from env, or persist a generated one at
* `<dataRoot>/secret.key` (0600) so it survives restarts without setup. */
@@ -459,7 +478,7 @@ function normalizeAction(value: string | undefined): string {
/**
* 主机名归一:去掉协议 / 路径 / 端口 / 首尾点,转小写并去重。
* 为什么要容错:运维很容易把 env 写成 `https://ai1net.com/`(照抄 URL 的习惯),
* 为什么要容错:运维很容易把 env 写成 `https://<base-domain>/`(照抄 URL 的习惯),
* 而 CF 回显的是**裸主机名** ⇒ 不归一就是"配了却永远不匹配"的静默失效。
*/
export function normalizeHostnames(values: readonly string[]): string[] {
@@ -539,11 +558,15 @@ function toDeployMode(value: string | undefined): DeployMode | undefined {
/**
* Fold argv/env overrides over defaults. `dataRoot` defaults to
* `~/.dshs` (always writable for dev); production sets
* `DSHS_DATA_ROOT=/var/lib/dshs`.
* `DSHS_DATA_ROOT=<data-root>`.
*/
export function resolveConfig(overrides: ConfigOverrides = {}): ServerConfig {
const dataRoot =
overrides.dataRoot ?? process.env.DSHS_DATA_ROOT ?? join(homedir(), '.dshs')
// 部署相关的路径一律由 `platform-paths.ts` 统一解析(单一来源 ⇒ 见其模块头注)。
// 代码内**不含任何真实路径**;缺省值是中性值(`<数据根>/platform`)。
const platformDir = overrides.platformDir ?? platformDirDefault()
const installDir = overrides.installDir ?? installDirDefault()
const port = overrides.port ?? process.env.DSHS_PORT ?? DEFAULT_PORT
const dshBin = process.env.DSHS_DSH_BIN
const isolationMode =
@@ -562,6 +585,11 @@ export function resolveConfig(overrides: ConfigOverrides = {}): ServerConfig {
dbPath: overrides.dbPath ?? join(dataRoot, 'dshs.db'),
dbUrl: overrides.dbUrl ?? process.env.DSHS_DB_URL,
dataRoot,
platformDir,
stateDir: join(platformDir, 'state'),
backupDir: join(platformDir, 'backups'),
artifactDir: join(platformDir, 'artifacts'),
installDir,
bundledSkillDir:
overrides.bundledSkillDir ??
process.env.DSHS_BUNDLED_SKILL_DIR ??
@@ -670,7 +698,7 @@ export function resolveConfig(overrides: ConfigOverrides = {}): ServerConfig {
DEFAULT_INSTANCE_PORT_SPAN,
),
// 覆盖网络 P0-2:引导三级链(env 显式 > 缓存目录 > 内置种子)。
// 这一组**全部有安全默认**:不配任何东西 ⇒ 与今天行为一致(只认 env;种子地址就是现网地址)。
// 内置种子**为空**(部署相关值不入代码)⇒ 不配 env 时引导链为空、不自动取址。
overlayNetworkId: (overrides.overlayNetworkId ?? process.env.DSHS_OVERLAY_NETWORK_ID ?? 'ops').trim() || 'ops',
overlayBootstrapSeeds:
overrides.overlayBootstrapSeeds ??