把散落在代码里的真实部署值统一收进 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 未被误建)。
153 lines
4.4 KiB
JavaScript
153 lines
4.4 KiB
JavaScript
/**
|
||
* DSH 平台 — 脚本侧配置加载器(供 `scripts/` 下的 node 脚本使用)
|
||
*
|
||
* 与 `config/load.sh`(shell 侧)同一口径:
|
||
* **系统环境变量 > config/platform.env > 本模块的中性默认值**
|
||
*
|
||
* 用法(CJS):
|
||
* const cfg = require('../config/index.cjs')
|
||
* cfg.dataRoot() // 数据根
|
||
* cfg.stateDir() // <platformDir>/state
|
||
*
|
||
* 用法(ESM):
|
||
* import cfg from '../config/index.cjs'
|
||
*
|
||
* ⛔ 本模块**零副作用**(不建目录、不写文件);未配置文件时不报错。
|
||
*/
|
||
|
||
'use strict'
|
||
|
||
const fs = require('node:fs')
|
||
const os = require('node:os')
|
||
const path = require('node:path')
|
||
|
||
const ENV_FILE = process.env.DSH_CONFIG_FILE || path.join(__dirname, 'platform.env')
|
||
|
||
/** 读取 platform.env 到对象(缓存;解析失败返回空对象)。 */
|
||
let _fileCache = null
|
||
function fileEnv() {
|
||
if (_fileCache !== null) return _fileCache
|
||
const out = {}
|
||
try {
|
||
const text = fs.readFileSync(ENV_FILE, 'utf8')
|
||
for (const raw of text.split('\n')) {
|
||
const line = raw.trim()
|
||
if (line === '' || line.startsWith('#')) continue
|
||
const i = line.indexOf('=')
|
||
if (i <= 0) continue
|
||
const k = line.slice(0, i).trim()
|
||
let v = line.slice(i + 1).trim()
|
||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
||
v = v.slice(1, -1)
|
||
}
|
||
if (k !== '') out[k] = v
|
||
}
|
||
} catch {
|
||
/* 文件不存在 ⇒ 全部走系统 env / 中性默认 */
|
||
}
|
||
_fileCache = out
|
||
return out
|
||
}
|
||
|
||
/** 取一个键:系统 env 优先,其次配置文件。 */
|
||
function get(key) {
|
||
const fromEnv = process.env[key]
|
||
if (fromEnv !== undefined && fromEnv !== '') return fromEnv
|
||
const fromFile = fileEnv()[key]
|
||
return fromFile !== undefined && fromFile !== '' ? fromFile : undefined
|
||
}
|
||
|
||
const isWin = process.platform === 'win32'
|
||
|
||
/** 数据根(每用户 home/ws、平台库)。 */
|
||
function dataRoot() {
|
||
return get('DSHS_DATA_ROOT') || path.join(os.homedir(), '.dshs')
|
||
}
|
||
|
||
/** 平台私有目录的父目录(其下 state / backups / artifacts)。 */
|
||
function platformDir() {
|
||
return get('DSH_PLATFORM_DIR') || path.join(dataRoot(), 'platform')
|
||
}
|
||
|
||
/** 平台状态目录。 */
|
||
function stateDir() {
|
||
return get('DSH_PLATFORM_STATE_DIR') || path.join(platformDir(), 'state')
|
||
}
|
||
|
||
/** 平台备份目录。 */
|
||
function backupDir() {
|
||
return get('DSH_PLATFORM_BACKUP_DIR') || path.join(platformDir(), 'backups')
|
||
}
|
||
|
||
/** 平台产物目录。 */
|
||
function artifactDir() {
|
||
return get('DSH_PLATFORM_ARTIFACT_DIR') || path.join(platformDir(), 'artifacts')
|
||
}
|
||
|
||
/** 代码安装根(`lib/`、`scripts/` 所在)。 */
|
||
function installDir() {
|
||
return get('DSH_INSTALL_DIR') || path.resolve(__dirname, '..')
|
||
}
|
||
|
||
/** 平台库文件路径(SQLite;Postgres 时用 dbUrl())。 */
|
||
function dbFile() {
|
||
return get('DSHS_DB_FILE') || path.join(dataRoot(), 'dshs.db')
|
||
}
|
||
|
||
/** 平台库连接串(未配置 ⇒ undefined,表示走 SQLite)。 */
|
||
function dbUrl() {
|
||
return get('DSHS_DB_URL')
|
||
}
|
||
|
||
/** 全员共享只读技能目录(`DSH_BUNDLED_SKILL_DIR`)。 */
|
||
function bundledSkillsDir() {
|
||
return get('DSHS_BUNDLED_SKILL_DIR') || path.join(dataRoot(), 'bundled-skills')
|
||
}
|
||
|
||
/** 每用户数据目录的父目录。 */
|
||
function usersDir() {
|
||
return get('DSHS_USERS_DIR') || path.join(dataRoot(), 'users')
|
||
}
|
||
|
||
/** 覆盖网络注册表 / 节点目录。 */
|
||
function overlayDir() {
|
||
return get('DSHS_OVERLAY_REGISTRY_DIR') || path.join(dataRoot(), 'overlay')
|
||
}
|
||
|
||
/** 代码安装根下的相对路径拼接(如 `installPath('lib','cli.js')`)。 */
|
||
function installPath(...parts) {
|
||
return path.join(installDir(), ...parts)
|
||
}
|
||
|
||
/** 本机在 `dsh_hosts.id` 里的标识。 */
|
||
function hostId() {
|
||
return get('DSHS_CLUSTER_HOST_ID') || get('DSHS_HOST_ID') || ''
|
||
}
|
||
|
||
/** 取多个键,一次返回(便于脚本解构)。 */
|
||
function all() {
|
||
return {
|
||
dataRoot: dataRoot(),
|
||
platformDir: platformDir(),
|
||
stateDir: stateDir(),
|
||
backupDir: backupDir(),
|
||
artifactDir: artifactDir(),
|
||
installDir: installDir(),
|
||
dbFile: dbFile(),
|
||
dbUrl: dbUrl(),
|
||
hostId: hostId(),
|
||
bundledSkillsDir: bundledSkillsDir(),
|
||
usersDir: usersDir(),
|
||
overlayDir: overlayDir(),
|
||
sep: isWin ? '\\' : '/',
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
get, all,
|
||
dataRoot, platformDir, stateDir, backupDir, artifactDir,
|
||
installDir, installPath, dbFile, dbUrl, hostId,
|
||
bundledSkillsDir, usersDir, overlayDir,
|
||
ENV_FILE,
|
||
}
|