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

+1 -1
View File
@@ -15,7 +15,7 @@
* 越界即 `bad_path`)
* ② 启停其 DSH 实例 —— 与用户自己点「启动 / 停止」同一条 `supervisor` 路径
* 这与 `requireAdmin` 既有职能(审批 / 禁用 / 删除用户)同级;服务器层面 admin 本就能读
* `/var/lib/dshs/users/**`。**不扩大普通用户的能力面** —— 这些前缀下没有任何 `requireAuth` 版本。
* `<data-root>/users/**`。**不扩大普通用户的能力面** —— 这些前缀下没有任何 `requireAuth` 版本。
* @module dshs/web/routes/admin-user-ops
*/
+7 -4
View File
@@ -7,12 +7,15 @@
import type { FastifyPluginAsync } from 'fastify'
import { execFileSync, spawn } from 'node:child_process'
import { rm } from 'node:fs/promises'
import { join } from 'node:path'
import { requireAdmin } from '../middleware/authn.js'
import { userRoot } from '../../fs/workspace.js'
import { scriptPath, stateDir } from '../../platform-paths.js'
/** 档案 36:新用户审批通过后自动铺「功能插件」分区的脚本(幂等)。 */
/** 档案 36:新用户审批通过后自动铺「功能插件」分区的脚本(幂等)。
* 路径由**安装根**推导(`DSH_INSTALL_DIR` 可覆盖),⛔ 不写死绝对路径。 */
const ENSURE_BIZ_PLUGINS =
process.env.DSH_ENSURE_BIZ_PLUGINS ?? '/opt/dshs/scripts/ensure-biz-plugins.cjs'
process.env.DSH_ENSURE_BIZ_PLUGINS ?? scriptPath('ensure-biz-plugins.cjs')
/** 档案 138:「平台共享模型」逐用户授权的入参(只有开关本身)。 */
const sharedModelSchema = {
@@ -149,7 +152,7 @@ export const adminRoutes: FastifyPluginAsync = async (app) => {
/**
* 档案 47:实例共享运行时/工具清单(admin 只读)。
* 数据全部用 shell 取(避免为此新增 import):版本 = 直接执行二进制;
* 基线 = cat /opt/dsh/state/runtime-baseline.json;清单 = cat SHARED-TOOLS.md。
* 基线 = cat <platform-dir>/state/runtime-baseline.json;清单 = cat SHARED-TOOLS.md。
* 升级/卸载不在此做 —— 走 `scripts/install-*.sh`(幂等、带 sha256 校验),
* 升级后必须跑 `runtime-baseline.cjs --accept` 刷新基线(与档案 44 的版本冻结配套)。
*/
@@ -182,7 +185,7 @@ export const adminRoutes: FastifyPluginAsync = async (app) => {
let baseline: unknown = null
let drift: string[] = []
try {
baseline = JSON.parse(sh('cat', ['/opt/dsh/state/runtime-baseline.json'], '{}'))
baseline = JSON.parse(sh('cat', [join(stateDir(), 'runtime-baseline.json')], '{}'))
const v = (baseline as { versions?: Record<string, string> }).versions ?? {}
const num = (s: string): string => (s.match(/\d+\.\d+(\.\d+)?/) ?? [''])[0]
const pair: Array<[string, string]> = [
+3 -1
View File
@@ -14,6 +14,8 @@ import { AlreadyRunningError, CrashBreakerOpenError } from '../../supervisor/orc
import { renderPatch } from '../../supervisor/patch.js'
import { subdomainForUser } from '../../supervisor/proxy.js'
import { homeRoot, userRoot } from '../../fs/workspace.js'
import { join } from 'node:path'
import { stateDir } from '../../platform-paths.js'
import { latestSessionPreset } from '../../supervisor/session-preset.js'
const launchSchema = {
@@ -192,7 +194,7 @@ export const dshRoutes: FastifyPluginAsync = async (app) => {
// 档案 56 ②:实例能力清单(由 scripts/gen-capabilities.cjs 生成,与实例内 skill 同源)。
app.get('/api/capabilities', { preHandler: requireAuth }, async () => {
const { readFileSync } = await import('node:fs')
const file = process.env.DSH_CAPABILITIES_FILE ?? '/opt/dsh/state/capabilities.json'
const file = process.env.DSH_CAPABILITIES_FILE ?? join(stateDir(), 'capabilities.json')
try {
return JSON.parse(readFileSync(file, 'utf8'))
} catch {
+3 -1
View File
@@ -35,6 +35,8 @@ import {
} from '../../net/relay/direct/index.js'
import { loadRegistry, summarizeNetworks, listNodes } from '../../net/relay/registry.js'
import { requireAdmin } from '../middleware/authn.js'
import { join } from 'node:path'
import { dataRootDir } from '../../platform-paths.js'
/** 本机配置落点(`DSHS_OVERLAY_NODE_CONFIG` 可覆盖;缺省与 `join` 的 `--config` 一致)。 */
function nodeConfigFile(): string {
@@ -45,7 +47,7 @@ function nodeConfigFile(): string {
/** 注册表落点(`DSHS_OVERLAY_NODES_FILE` 可覆盖;缺省 = 参数表 `NODES_REGISTRY_FILE`)。 */
function registryFile(): string {
const v = process.env.DSHS_OVERLAY_NODES_FILE
return typeof v === 'string' && v.trim() !== '' ? v.trim() : '/var/lib/dshs/overlay/nodes.json'
return typeof v === 'string' && v.trim() !== '' ? v.trim() : join(dataRootDir(), 'overlay', 'nodes.json')
}
const fss = {