This commit is contained in:
commit
5ec99ce227
668 files changed
+164448
No files matched your search
@@ -0,0 +1,74 @@
|
||||
# config/ — 平台部署配置
|
||||
|
||||
本目录集中存放**部署相关的值**。源码里不再写任何真实部署值,一律从这里(或同名环境变量)读取。
|
||||
|
||||
## 文件
|
||||
|
||||
| 文件 | 是否入库 | 说明 |
|
||||
|---|---|---|
|
||||
| `platform.env.example` | ✅ 入库 | 模板,只有占位符。复制成 `platform.env` 后填写 |
|
||||
| `platform.env` | ⛔ **不入库** | 本机真实值。已被 `.gitignore` 排除;也不会进入开源导出 |
|
||||
| `load.sh` | ✅ 入库 | shell 加载器,供 `scripts/` 下的脚本 `source` |
|
||||
|
||||
## 怎么用
|
||||
|
||||
**首次配置**
|
||||
|
||||
```sh
|
||||
cp config/platform.env.example config/platform.env
|
||||
${EDITOR:-vi} config/platform.env
|
||||
```
|
||||
|
||||
**shell 脚本里引用**
|
||||
|
||||
```sh
|
||||
. "$(dirname "$0")/../config/load.sh"
|
||||
echo "$DSH_PLATFORM_DIR/state" # → 平台状态目录
|
||||
```
|
||||
|
||||
`load.sh` 找不到文件时不报错,脚本继续用系统 env 或自身默认值。
|
||||
|
||||
**TypeScript 里引用**
|
||||
|
||||
`src/config.ts` 负责解析,业务代码只读 `ServerConfig` 上的字段,不直接读 `process.env`:
|
||||
|
||||
```ts
|
||||
const cfg = resolveConfig()
|
||||
cfg.platformDir // <platform-dir>
|
||||
cfg.stateDir // <platform-dir>/state
|
||||
cfg.backupDir // <platform-dir>/backups
|
||||
cfg.installDir // 代码安装根
|
||||
```
|
||||
|
||||
## 优先级
|
||||
|
||||
**系统环境变量 > `config/platform.env` > 代码内中性默认值**
|
||||
|
||||
- systemd drop-in(`/etc/systemd/system/dshs.service.d/*.conf`)与 `/etc/dshs.env` 属"系统环境变量",优先级最高;
|
||||
- `load.sh` 逐键判断,**已由系统 env 提供的键不会被文件覆盖**;
|
||||
- 代码内默认值一律是**中性值**(不含任何真实域名、地址、路径),只保证"不配也能起"。
|
||||
|
||||
> ⚠️ 注意:dshs 的 systemd drop-in 里同名键会**压掉** `/etc/dshs.env` —— 两个地方都写同一个键时,以 drop-in 为准。
|
||||
|
||||
## 键一览
|
||||
|
||||
| 键 | 含义 | 中性默认(代码内) |
|
||||
|---|---|---|
|
||||
| `DSHS_DATA_ROOT` | 数据根(每用户 home/ws、平台库) | `~/.dshs` |
|
||||
| `DSH_PLATFORM_DIR` | 平台私有目录的父目录 | `<dataRoot>/platform` |
|
||||
| `DSH_INSTALL_DIR` | 代码安装根(`lib/`、`scripts/`) | 模块相对路径推导 |
|
||||
| `DSHS_BUNDLED_PLUGIN_DIR` | 共享只读插件包库(节点一份;每节点需自建 0755 root:root) | `<dataRoot>/bundled-plugins` |
|
||||
| `DSHS_BASE_DOMAIN` | 对外域名 | 空(子域功能关闭) |
|
||||
| `DSHS_COOKIE_DOMAIN` | 会话 cookie 域 | 空(host-only) |
|
||||
| `DSHS_OVERLAY_BOOTSTRAP_SEEDS` | 覆盖网络引导种子,逗号分隔 | 空(功能关闭) |
|
||||
| `DSHS_OVERLAY_SIGNER_KEY_FILE` | 在线签名者私钥(签发 per-device grant) | `/etc/dshs/overlay-signer-key.pem` |
|
||||
| `DSHS_RELAY_KEYS_FILE` | relay HMAC 密钥表(控制面写、relay 热加载) | `/etc/dshs/relay-keys.json` |
|
||||
| `DESKTOP_GRANT_TTL_HOURS` | 设备 grant 租约(小时) | 24 |
|
||||
| `DESKTOP_GRANT_MAX_DEVICES` | 每用户设备数上限 | 10 |
|
||||
| `DSHS_INSTANCE_GRANT_RENEW_MS` | 服务器实例设备凭据的定时续签间隔(毫秒;`0` = 关) | 300000 |
|
||||
| `DSHS_CLUSTER_HOST_ID` | 本机在 `dsh_hosts.id` 里的标识 | 空 |
|
||||
| `DSHS_CLUSTER_AGENT_TOKEN` | Worker 注册凭据 | 空 |
|
||||
| `DSHS_TUNNEL_TARGET` | 反向隧道目标 | 空(隧道关闭) |
|
||||
| `DSH_HOST_PUBLIC_IP` / `DSH_HOST_LAN_IP` | 出网护栏要封的本机地址 | 空(只封回环) |
|
||||
|
||||
派生字段(不用单独配):`stateDir` = `$DSH_PLATFORM_DIR/state`、`backupDir` = `$DSH_PLATFORM_DIR/backups`、`artifactDir` = `$DSH_PLATFORM_DIR/artifacts`。
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# DSH 平台 — 配置加载器(供 scripts/ 下的 shell 脚本 source)
|
||||
# ----------------------------------------------------------------------------
|
||||
# 用法(脚本开头):
|
||||
# . "$(cd "$(dirname "$0")/.." && pwd)/config/load.sh"
|
||||
#
|
||||
# 加载后可用(全部已 export):
|
||||
# DSHS_DATA_ROOT 数据根
|
||||
# DSH_PLATFORM_DIR 平台私有目录父目录
|
||||
# DSH_INSTALL_DIR 代码安装根
|
||||
# DSH_STATE_DIR = $DSH_PLATFORM_DIR/state (派生)
|
||||
# DSH_BACKUP_DIR = $DSH_PLATFORM_DIR/backups (派生)
|
||||
# DSH_ARTIFACT_DIR = $DSH_PLATFORM_DIR/artifacts (派生)
|
||||
#
|
||||
# 优先级:系统环境变量 > config/platform.env > 这里的**中性默认值**
|
||||
# ⛔ 中性默认值不含任何真实部署路径;配置文件缺失时**发告警**而不是静默用错路径。
|
||||
# ============================================================================
|
||||
|
||||
_dsh_load_platform_env() {
|
||||
local f="${DSH_CONFIG_FILE:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/platform.env}"
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "[config] 警告:未找到 $f —— 将只用系统环境变量与中性默认值" >&2
|
||||
return 0
|
||||
fi
|
||||
local line key val
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
''|'#'*) continue ;;
|
||||
*=*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
key="${line%%=*}"
|
||||
val="${line#*=}"
|
||||
key="$(printf '%s' "$key" | tr -d '[:space:]')"
|
||||
[ -n "$key" ] || continue
|
||||
case "$val" in
|
||||
\"*\"|\'*\') val="${val%?}"; val="${val#?}" ;;
|
||||
*) val="$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')" ;;
|
||||
esac
|
||||
# 系统 env 已有该键 ⇒ 不覆盖(系统 env 优先级更高)
|
||||
if [ -n "$(printenv "$key" 2>/dev/null)" ]; then
|
||||
continue
|
||||
fi
|
||||
export "$key=$val"
|
||||
done < "$f"
|
||||
}
|
||||
|
||||
_dsh_load_platform_env
|
||||
unset -f _dsh_load_platform_env 2>/dev/null || true
|
||||
|
||||
# --- 中性默认值(仅当仍未设置时才填)--------------------------------------
|
||||
export DSHS_DATA_ROOT="${DSHS_DATA_ROOT:-$HOME/.dshs}"
|
||||
export DSH_PLATFORM_DIR="${DSH_PLATFORM_DIR:-$DSHS_DATA_ROOT/platform}"
|
||||
export DSH_INSTALL_DIR="${DSH_INSTALL_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||
|
||||
# --- 派生目录(统一在这里算,⛔ 别在脚本里各写一套)------------------------
|
||||
export DSH_STATE_DIR="${DSH_PLATFORM_STATE_DIR:-$DSH_PLATFORM_DIR/state}"
|
||||
export DSH_BACKUP_DIR="${DSH_PLATFORM_BACKUP_DIR:-$DSH_PLATFORM_DIR/backups}"
|
||||
export DSH_ARTIFACT_DIR="${DSH_PLATFORM_ARTIFACT_DIR:-$DSH_PLATFORM_DIR/artifacts}"
|
||||
|
||||
# --- 常用组合 -------------------------------------------------------------
|
||||
export DSH_DB_FILE="${DSHS_DB_FILE:-$DSHS_DATA_ROOT/dshs.db}"
|
||||
export DSH_USERS_DIR="${DSHS_USERS_DIR:-$DSHS_DATA_ROOT/users}"
|
||||
|
||||
# --- 本机 / 对端地址(出网护栏、双机脚本用;留空 = 不填该项)---------------
|
||||
export DSH_HOST_PUBLIC_IP="${DSH_HOST_PUBLIC_IP:-}"
|
||||
export DSH_HOST_LAN_IP="${DSH_HOST_LAN_IP:-}"
|
||||
export DSH_PEER_HOST_ID="${DSH_PEER_HOST_ID:-}"
|
||||
export DSH_PEER_PUBLIC_IP="${DSH_PEER_PUBLIC_IP:-}"
|
||||
export DSH_PEER_AGENT_TOKEN="${DSH_PEER_AGENT_TOKEN:-}"
|
||||
|
||||
# --- 控制面 PG ------------------------------------------------------------
|
||||
export DSHS_PG_HOST="${DSHS_PG_HOST:-127.0.0.1}"
|
||||
export DSHS_PG_PORT="${DSHS_PG_PORT:-5432}"
|
||||
export DSHS_PG_USER="${DSHS_PG_USER:-dshs}"
|
||||
export DSHS_PG_DB="${DSHS_PG_DB:-dshs}"
|
||||
export DSHS_PG_PASSWORD="${DSHS_PG_PASSWORD:-}"
|
||||
export DSH_PG_DATA_DIR="${DSH_PG_DATA_DIR:-$DSHS_DATA_ROOT-pg}"
|
||||
@@ -0,0 +1,96 @@
|
||||
# ============================================================================
|
||||
# DSH 平台 — 部署配置模板
|
||||
# ----------------------------------------------------------------------------
|
||||
# 用法:cp config/platform.env.example config/platform.env → 按本机实际填写
|
||||
# · platform.env 已被 .gitignore 排除,**不会入库、不会进入开源导出**
|
||||
# · 代码里不含任何真实部署值;所有部署相关的值都从本文件(或同名 env)读取
|
||||
# · 也可以用系统级 env 覆盖(systemd drop-in / /etc/dshs.env),优先级见 config/README.md
|
||||
# ============================================================================
|
||||
|
||||
# --- 数据根:每用户 home / ws / 平台库 都在这下面 -------------------------
|
||||
DSHS_DATA_ROOT=/var/lib/dshs
|
||||
|
||||
# --- 平台私有目录的父目录:其下放 state / backups / artifacts -------------
|
||||
# 代码里的 stateDir / backupDir / artifactDir 都由它派生,见 src/config.ts
|
||||
DSH_PLATFORM_DIR=/opt/dsh
|
||||
|
||||
# --- 代码安装根(lib/ 与 scripts/ 所在)----------------------------------
|
||||
DSH_INSTALL_DIR=/opt/dshs
|
||||
|
||||
# --- 共享只读插件包库(节点一份;默认 <DSHS_DATA_ROOT>/bundled-plugins)----
|
||||
# 每个节点各自建目录:`install -d -m 0755 -o root -g root <该路径>`。
|
||||
# 实例内以 `--ro-bind-try` 只读挂入;留空 = 关闭该功能。
|
||||
DSHS_BUNDLED_PLUGIN_DIR=/var/lib/dshs/bundled-plugins
|
||||
|
||||
# --- 对外域名 -------------------------------------------------------------
|
||||
DSHS_BASE_DOMAIN=example.com
|
||||
DSHS_COOKIE_DOMAIN=.example.com
|
||||
|
||||
# --- 监听端口 / 隔离方式 --------------------------------------------------
|
||||
DSHS_PORT=3080
|
||||
DSHS_ISOLATION_MODE=account
|
||||
DSHS_BASE_UID=100000
|
||||
|
||||
# --- 部署形态:local | cluster | k8s --------------------------------------
|
||||
DSHS_DEPLOY_MODE=local
|
||||
DSHS_CLUSTER_HOST_ID=<host-id>
|
||||
DSHS_CLUSTER_INSTANCE_HOST=127.0.0.1
|
||||
DSHS_CLUSTER_WORKER_DATA_ROOT=/var/lib/dshs
|
||||
|
||||
# --- 覆盖网络引导种子(逗号分隔;留空 = 功能关闭)------------------------
|
||||
DSHS_OVERLAY_BOOTSTRAP_SEEDS=
|
||||
|
||||
# --- 覆盖网络 · 设备登录接入(序㊻ S0)------------------------------------
|
||||
# 在线签名者私钥(签发 per-device grant 用)。⚠️ 只读、0600;留空 = 取代码内缺省路径。
|
||||
DSHS_OVERLAY_SIGNER_KEY_FILE=
|
||||
# relay HMAC 密钥表(控制面签发设备时写入;relay 侧**热加载**本文件)。
|
||||
# ⚠️ 与 relay 单元的 DSHS_RELAY_KEYS_FILE **必须是同一个文件**,否则签了也不生效。
|
||||
DSHS_RELAY_KEYS_FILE=
|
||||
# 设备 grant 租约(小时)与每用户设备上限。
|
||||
DESKTOP_GRANT_TTL_HOURS=24
|
||||
DESKTOP_GRANT_MAX_DEVICES=10
|
||||
|
||||
# --- 会话并存的每用户上限(序㊼ S1 / E3)----------------------------------
|
||||
# 同一账号**同时**可持有的平台会话数上限;超限淘汰 `created_at` 最小者并落
|
||||
# `audit_log: session_evicted`。留空 = 取代码内中性默认 20。
|
||||
# ⚠️ 它只约束**平台会话**;设备 grant 配额是上面的 DESKTOP_GRANT_MAX_DEVICES。
|
||||
DSHS_MAX_SESSIONS_PER_USER=20
|
||||
|
||||
# --- 服务器实例的设备凭据(序㊼ S4/S5)------------------------------------
|
||||
# 实例(role=main)设备凭据的**定时续签**扫描间隔(毫秒)。控制面按此间隔扫
|
||||
# `overlay_devices`(kind='instance' 且 status='active'),租约剩余不足 TTL 一半即重签。
|
||||
# 留空 = 取代码内中性默认 300000(5 分钟);0 = 关闭定时续签(只剩"实例启动时检查")。
|
||||
# ⚠️ 实例凭据的**文件**落在实例 home(`.overlay-device.json`),平台不留私钥副本。
|
||||
# ⚠️ 与浏览器会话的 TTL(DSHS_SESSION_TTL)无关:实例常驻靠**设备租约**续签。
|
||||
DSHS_INSTANCE_GRANT_RENEW_MS=300000
|
||||
|
||||
# --- 本机地址(出网护栏脚本用;填自己的公网/内网地址)--------------------
|
||||
DSH_HOST_PUBLIC_IP=<server-public-ip>
|
||||
DSH_HOST_LAN_IP=<host-lan-ip>
|
||||
|
||||
# --- 控制面 PG(cluster 形态用)-------------------------------------------
|
||||
DSHS_PG_HOST=127.0.0.1
|
||||
DSHS_PG_PORT=<pg-port>
|
||||
DSHS_PG_USER=dshs
|
||||
DSHS_PG_DB=dshs
|
||||
DSHS_PG_PASSWORD=<pg-password>
|
||||
DSH_PG_DATA_DIR=/var/lib/dsh-pg
|
||||
|
||||
# --- Worker 注册凭据(cluster 形态必填)----------------------------------
|
||||
DSHS_CLUSTER_AGENT_TOKEN=<worker-token>
|
||||
|
||||
# --- 反向隧道目标(可选;留空 = 隧道关闭)--------------------------------
|
||||
# 形如 root@<host>:<port> 或 ssh://root@<host>:<port>
|
||||
DSHS_TUNNEL_TARGET=
|
||||
|
||||
# --- 平台库连接(cluster 形态用 PG;local 形态留空走 SQLite)-------------
|
||||
DSHS_DB_URL=
|
||||
|
||||
# --- 注册验证外发(留空 = 该能力关闭)-----------------------------------
|
||||
DSHS_MAIL_DRIVER=
|
||||
DSHS_MAIL_API_KEY=
|
||||
DSHS_MAIL_FROM=
|
||||
DSHS_MAIL_FROM_NAME=
|
||||
DSHS_TURNSTILE_SITE_KEY=
|
||||
DSHS_TURNSTILE_SECRET=
|
||||
DSHS_TURNSTILE_HOSTNAMES=
|
||||
Reference in new issue
Block a user