dsh-vps 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +215 -0
- package/README.md +225 -0
- package/bin/dsh-vps +920 -0
- package/caddy/Caddyfile.template +10 -0
- package/gate/server.js +1718 -0
- package/gate/site-block.js +96 -0
- package/install.sh +504 -0
- package/package.json +57 -0
- package/scripts/bootstrap.mjs +54 -0
- package/uninstall.sh +196 -0
- package/units/dsh-gate.service +43 -0
- package/versions.json +13 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 把当前这份 npm 包里自带的安装脚本跑起来——不联网抓取,装的就是这个版本的内容。
|
|
3
|
+
// install.sh 自带“本地文件优先”逻辑,所以这里只要把工作目录指向包根目录即可。
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { dirname, join } from 'node:path'
|
|
7
|
+
import { existsSync } from 'node:fs'
|
|
8
|
+
|
|
9
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
10
|
+
const scripts = {
|
|
11
|
+
install: join(root, 'install.sh'),
|
|
12
|
+
uninstall: join(root, 'uninstall.sh'),
|
|
13
|
+
}
|
|
14
|
+
const usage = `dsh-vps-install — 用 npm 包里的脚本安装/卸载 dsh-vps
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
npx dsh-vps-install install --domain <域名> [--mirror cn]
|
|
18
|
+
npx dsh-vps-install uninstall [--yes]
|
|
19
|
+
|
|
20
|
+
等价的传统方式(不需要 npm):
|
|
21
|
+
curl -fsSL https://raw.githubusercontent.com/AIcivilization/deepseek-harness-vps/main/install.sh | sudo bash -s -- --domain <域名>
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2)
|
|
25
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
26
|
+
process.stdout.write(usage)
|
|
27
|
+
process.exit(args.length === 0 ? 1 : 0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const action = args[0]
|
|
31
|
+
const target = scripts[action]
|
|
32
|
+
if (!target) {
|
|
33
|
+
process.stderr.write(`未知子命令: ${action}\n\n${usage}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
if (!existsSync(target)) {
|
|
37
|
+
process.stderr.write(`缺少脚本: ${target}(npm 包不完整,请重新安装 dsh-vps)\n`)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rest = args.slice(1)
|
|
42
|
+
const elevated = typeof process.getuid === 'function' && process.getuid() === 0
|
|
43
|
+
const command = elevated ? 'bash' : 'sudo'
|
|
44
|
+
const argv = elevated ? [target, ...rest] : ['--', 'bash', target, ...rest]
|
|
45
|
+
if (!elevated) {
|
|
46
|
+
process.stdout.write('[dsh-vps] 需要 root 权限,将通过 sudo 执行,可能会提示输入密码。\n')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = spawnSync(command, argv, { stdio: 'inherit', shell: false })
|
|
50
|
+
if (result.error) {
|
|
51
|
+
process.stderr.write(`执行失败: ${result.error.message}\n`)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
process.exit(result.status ?? 1)
|
package/uninstall.sh
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dsh-vps 卸载脚本
|
|
3
|
+
#
|
|
4
|
+
# 用法:
|
|
5
|
+
# curl -fsSL https://raw.githubusercontent.com/AIcivilization/deepseek-harness-vps/main/uninstall.sh \
|
|
6
|
+
# | sudo bash -s -- --yes
|
|
7
|
+
# # 或在仓库克隆目录内:
|
|
8
|
+
# sudo bash uninstall.sh [--yes] [--keep-data] [--purge-caddy]
|
|
9
|
+
#
|
|
10
|
+
# 目标 OS:Ubuntu 22.04+ / Debian 12+
|
|
11
|
+
#
|
|
12
|
+
# 顺序:备份 → 停服务 → 移除 unit 与安装目录 → 移除 Caddy 站点块 → 清 DSH 数据目录。
|
|
13
|
+
# 任何一步失败都会就地停下(set -euo pipefail),不做半吊子清理。
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
## region: 常量与参数
|
|
18
|
+
|
|
19
|
+
INSTALL_ROOT="/opt/dsh-vps"
|
|
20
|
+
DSH_USER="dsh"
|
|
21
|
+
DSH_HOME_DIR="/home/dsh/.dsh"
|
|
22
|
+
SERVICE="dsh-gate"
|
|
23
|
+
UNIT_FILE="/etc/systemd/system/dsh-gate.service"
|
|
24
|
+
CADDY_SITE_FILE="/etc/caddy/dsh-site.conf"
|
|
25
|
+
CADDYFILE="/etc/caddy/Caddyfile"
|
|
26
|
+
|
|
27
|
+
ASSUME_YES=0
|
|
28
|
+
KEEP_DATA=0
|
|
29
|
+
PURGE_CADDY=0
|
|
30
|
+
|
|
31
|
+
log() { printf '\033[1;32m[uninstall]\033[0m %s\n' "$*"; }
|
|
32
|
+
warn() { printf '\033[1;33m[uninstall]\033[0m %s\n' "$*" >&2; }
|
|
33
|
+
die() { printf '\033[1;31m[uninstall]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
34
|
+
|
|
35
|
+
usage() {
|
|
36
|
+
cat <<'EOF'
|
|
37
|
+
dsh-vps 卸载脚本
|
|
38
|
+
用法: sudo bash uninstall.sh [--yes] [--keep-data] [--purge-caddy]
|
|
39
|
+
--yes 跳过交互确认(脚本化调用时用)
|
|
40
|
+
--keep-data 保留 DSH 数据目录 /home/dsh/.dsh 与系统用户 dsh
|
|
41
|
+
--purge-caddy 连 Caddy 软件包与 apt 源一起移除(默认只删本产品的站点块)
|
|
42
|
+
EOF
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
while [[ $# -gt 0 ]]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--yes) ASSUME_YES=1; shift ;;
|
|
48
|
+
--keep-data) KEEP_DATA=1; shift ;;
|
|
49
|
+
--purge-caddy) PURGE_CADDY=1; shift ;;
|
|
50
|
+
-h | --help) usage; exit 0 ;;
|
|
51
|
+
*) die "未知参数: $1(--help 查看用法)" ;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
[[ $EUID -eq 0 ]] || die "需要 root(sudo bash uninstall.sh ...)"
|
|
56
|
+
|
|
57
|
+
## endregion
|
|
58
|
+
|
|
59
|
+
## region: 步骤 1:确认
|
|
60
|
+
|
|
61
|
+
if [[ $ASSUME_YES -eq 0 ]]; then
|
|
62
|
+
cat <<EOF
|
|
63
|
+
即将移除 dsh-vps:
|
|
64
|
+
- 服务 ${SERVICE}(停止并禁用,删除 ${UNIT_FILE})
|
|
65
|
+
- 安装目录 ${INSTALL_ROOT}(含 gate 代码、state、备份)
|
|
66
|
+
- 命令 /usr/local/bin/dsh-vps
|
|
67
|
+
- Caddy 站点 $CADDY_SITE_FILE$([ $PURGE_CADDY -eq 1 ] && echo "(并移除 caddy 软件包)")
|
|
68
|
+
- 隧道 /etc/wireguard/wg0.conf(仅当它是 dsh-vps vpn 创建的;你自己的 wg0 不动)
|
|
69
|
+
- DSH 数据 $DSH_HOME_DIR$([ $KEEP_DATA -eq 1 ] && echo "(--keep-data:保留)" || echo "(删除)")
|
|
70
|
+
- 系统用户 $DSH_USER$([ $KEEP_DATA -eq 1 ] && echo "(--keep-data:保留)" || echo "(保留,重装可复用)")
|
|
71
|
+
|
|
72
|
+
删除前会先打包备份到 /root/dsh-vps-uninstall-<时间戳>.tar.gz
|
|
73
|
+
EOF
|
|
74
|
+
read -rp "确认卸载?输入 yes 继续: " ans
|
|
75
|
+
[[ "$ans" == "yes" ]] || die "已取消"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
## endregion
|
|
79
|
+
|
|
80
|
+
## region: 步骤 2:备份
|
|
81
|
+
|
|
82
|
+
log "步骤 1/6:备份"
|
|
83
|
+
mkdir -p /root
|
|
84
|
+
local_ts="dsh-vps-uninstall-$(date +%Y%m%d-%H%M%S).tar.gz"
|
|
85
|
+
BACKUP="/root/$local_ts"
|
|
86
|
+
targets=()
|
|
87
|
+
# 注意:set -e 下不能写 `[[ -d x ]] && arr+=(x)`——条件为假时整条列表返回非 0 会直接退出
|
|
88
|
+
if [[ -d "$INSTALL_ROOT/state" ]]; then targets+=("${INSTALL_ROOT#/}/state"); fi
|
|
89
|
+
if [[ -d "$INSTALL_ROOT/backups" ]]; then targets+=("${INSTALL_ROOT#/}/backups"); fi
|
|
90
|
+
if [[ -d "$DSH_HOME_DIR" ]]; then targets+=("${DSH_HOME_DIR#/}"); fi
|
|
91
|
+
# Caddyfile 下一步会被改写,先连同站点块一起进备份
|
|
92
|
+
if [[ -f "$CADDYFILE" ]]; then targets+=("${CADDYFILE#/}"); fi
|
|
93
|
+
if [[ -f "$CADDY_SITE_FILE" ]]; then targets+=("${CADDY_SITE_FILE#/}"); fi
|
|
94
|
+
if [[ ${#targets[@]} -gt 0 ]]; then
|
|
95
|
+
tar -czf "$BACKUP" -C / "${targets[@]}" 2>/dev/null || warn "部分文件无法打包(继续卸载)"
|
|
96
|
+
chmod 600 "$BACKUP"
|
|
97
|
+
log "备份完成: $BACKUP"
|
|
98
|
+
else
|
|
99
|
+
BACKUP=""
|
|
100
|
+
log "没有可备份的内容"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
## endregion
|
|
104
|
+
|
|
105
|
+
## region: 步骤 3:停服务、移除 unit
|
|
106
|
+
|
|
107
|
+
log "步骤 2/6:停止并移除 $SERVICE"
|
|
108
|
+
systemctl stop "$SERVICE" 2>/dev/null || true
|
|
109
|
+
systemctl disable "$SERVICE" 2>/dev/null || true
|
|
110
|
+
rm -f "$UNIT_FILE"
|
|
111
|
+
systemctl daemon-reload 2>/dev/null || true
|
|
112
|
+
systemctl reset-failed "$SERVICE" 2>/dev/null || true
|
|
113
|
+
|
|
114
|
+
# gate 拉起的 DSH 子进程随 gate 停止而退出;残留则兜底清理
|
|
115
|
+
pkill -u "$DSH_USER" -f "dsh/lib/bin.js" 2>/dev/null || true
|
|
116
|
+
pkill -u "$DSH_USER" -f "gate/server.js" 2>/dev/null || true
|
|
117
|
+
|
|
118
|
+
## endregion
|
|
119
|
+
|
|
120
|
+
## region: 步骤 4:删文件
|
|
121
|
+
|
|
122
|
+
log "步骤 3/6:移除安装目录与命令"
|
|
123
|
+
rm -rf "$INSTALL_ROOT"
|
|
124
|
+
rm -f /usr/local/bin/dsh-vps
|
|
125
|
+
|
|
126
|
+
## endregion
|
|
127
|
+
|
|
128
|
+
## region: 步骤 5:Caddy
|
|
129
|
+
|
|
130
|
+
log "步骤 4/6:移除 Caddy 站点块"
|
|
131
|
+
if [[ -f "$CADDY_SITE_FILE" ]]; then
|
|
132
|
+
rm -f "$CADDY_SITE_FILE"
|
|
133
|
+
fi
|
|
134
|
+
if [[ -f "$CADDYFILE" ]] && grep -qF "import $CADDY_SITE_FILE" "$CADDYFILE"; then
|
|
135
|
+
# install.sh 覆盖前留下的原文件(Caddyfile.bak.<时间戳>):取最新一份不含本产品 import 的还原;
|
|
136
|
+
# 没有则换成占位文件,避免 Caddy 因 import 缺失而启动失败。
|
|
137
|
+
orig=""
|
|
138
|
+
for f in $(ls -1t "$CADDYFILE".bak.* 2>/dev/null); do
|
|
139
|
+
if ! grep -qF "import $CADDY_SITE_FILE" "$f"; then orig="$f"; break; fi
|
|
140
|
+
done
|
|
141
|
+
if [[ -n "$orig" ]]; then
|
|
142
|
+
cp "$orig" "$CADDYFILE" && log "已还原安装前的 Caddyfile(来自 $orig)" \
|
|
143
|
+
|| warn "Caddyfile 还原失败,请手工检查 $CADDYFILE"
|
|
144
|
+
else
|
|
145
|
+
printf '# Caddyfile 已被 dsh-vps 卸载脚本重置(原内容已备份进 %s)\n' "${BACKUP:-(无备份)}" >"$CADDYFILE" \
|
|
146
|
+
|| warn "Caddyfile 重置失败,请手工检查 $CADDYFILE"
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
if command -v caddy >/dev/null 2>&1; then
|
|
150
|
+
systemctl reload caddy 2>/dev/null || systemctl restart caddy 2>/dev/null || true
|
|
151
|
+
fi
|
|
152
|
+
if [[ $PURGE_CADDY -eq 1 ]]; then
|
|
153
|
+
systemctl stop caddy 2>/dev/null || true
|
|
154
|
+
systemctl disable caddy 2>/dev/null || true
|
|
155
|
+
apt-get purge -y caddy >/dev/null 2>&1 || warn "caddy 卸载失败(可能不是 apt 安装的)"
|
|
156
|
+
rm -f /etc/apt/sources.list.d/caddy-stable.list /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
157
|
+
log "已移除 caddy"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
## endregion
|
|
161
|
+
|
|
162
|
+
## region: 步骤 5:隧道
|
|
163
|
+
|
|
164
|
+
log "步骤 5/6:移除隧道"
|
|
165
|
+
if [[ -f /etc/wireguard/wg0.conf ]] && ! grep -q "dsh-vps" /etc/wireguard/wg0.conf; then
|
|
166
|
+
log "wg0.conf 不是 dsh-vps 创建的,保留不动"
|
|
167
|
+
elif [[ -f /etc/wireguard/wg0.conf ]]; then
|
|
168
|
+
systemctl stop wg-quick@wg0 2>/dev/null || true
|
|
169
|
+
systemctl disable wg-quick@wg0 2>/dev/null || true
|
|
170
|
+
rm -f /etc/wireguard/wg0.conf
|
|
171
|
+
log "已移除 wg0(设备里的客户端配置请自行删掉)"
|
|
172
|
+
else
|
|
173
|
+
log "没有隧道,跳过"
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
## endregion
|
|
177
|
+
|
|
178
|
+
## region: 步骤 6:DSH 数据
|
|
179
|
+
|
|
180
|
+
log "步骤 6/6:处理 DSH 数据目录"
|
|
181
|
+
if [[ $KEEP_DATA -eq 1 ]]; then
|
|
182
|
+
log "保留 ${DSH_HOME_DIR}(--keep-data)"
|
|
183
|
+
else
|
|
184
|
+
rm -rf "$DSH_HOME_DIR"
|
|
185
|
+
log "已删除 $DSH_HOME_DIR"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
## endregion
|
|
189
|
+
|
|
190
|
+
cat <<EOF
|
|
191
|
+
|
|
192
|
+
卸载完成。
|
|
193
|
+
备份:${BACKUP:-(无)}
|
|
194
|
+
重装:
|
|
195
|
+
curl -fsSL https://raw.githubusercontent.com/AIcivilization/deepseek-harness-vps/main/install.sh | sudo bash -s -- --domain <你的域名> --mirror cn
|
|
196
|
+
EOF
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# dsh-vps systemd unit 模板
|
|
2
|
+
# install.sh 将 __NODE_BIN__ 替换为实际 node 路径后安装到 /etc/systemd/system/dsh-gate.service
|
|
3
|
+
#
|
|
4
|
+
# 只有一个服务:gate 以父进程身份 spawn 并管理 DSH 子进程(捕获 stdout launchToken 所必需)。
|
|
5
|
+
# 运行环境变量(GATE_HOME / DSH_BIN / DSH_HOME / DSH_TRUSTED_HOST)集中在
|
|
6
|
+
# /opt/dsh-vps/state/gate.env —— 向导(M3)改域名时只需重写 gate.env 并
|
|
7
|
+
# `systemctl restart dsh-gate`,unit 本身保持静态(见设计文档 4.4)。
|
|
8
|
+
|
|
9
|
+
[Unit]
|
|
10
|
+
Description=dsh-vps login gateway (manages DeepSeek Harness)
|
|
11
|
+
After=network-online.target
|
|
12
|
+
Wants=network-online.target
|
|
13
|
+
|
|
14
|
+
[Service]
|
|
15
|
+
Type=simple
|
|
16
|
+
User=dsh
|
|
17
|
+
EnvironmentFile=/opt/dsh-vps/state/gate.env
|
|
18
|
+
ExecStart=__NODE_BIN__ /opt/dsh-vps/gate/server.js
|
|
19
|
+
Restart=always
|
|
20
|
+
RestartSec=2
|
|
21
|
+
TimeoutStopSec=10
|
|
22
|
+
|
|
23
|
+
# 加固:只取不改变文件系统视图的那些。
|
|
24
|
+
# 刻意不设 ProtectSystem=strict / ProtectHome —— gate 会 spawn `dsh plugin add`
|
|
25
|
+
# (pnpm)与 DSH 自身,它们要写 /home/dsh 下的缓存与数据目录;一旦被隐藏,
|
|
26
|
+
# 表现是插件装不上、DSH 起不来,比不加更难排查。等有了真机回归再考虑收紧。
|
|
27
|
+
NoNewPrivileges=true
|
|
28
|
+
PrivateTmp=true
|
|
29
|
+
PrivateDevices=true
|
|
30
|
+
ProtectProc=invisible
|
|
31
|
+
ProtectKernelTunables=true
|
|
32
|
+
ProtectKernelModules=true
|
|
33
|
+
ProtectControlGroups=true
|
|
34
|
+
ProtectClock=true
|
|
35
|
+
ProtectHostname=true
|
|
36
|
+
RestrictSUIDSGID=true
|
|
37
|
+
RestrictNamespaces=true
|
|
38
|
+
LockPersonality=true
|
|
39
|
+
# 服务以 dsh 用户运行,本不需要任何 capability;显式清空,进程无法再取得特权
|
|
40
|
+
CapabilityBoundingSet=
|
|
41
|
+
|
|
42
|
+
[Install]
|
|
43
|
+
WantedBy=multi-user.target
|
package/versions.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"latest_verified": "0.1.5-rc.2",
|
|
3
|
+
"resolve_before": "2026-09-22T03:40:00Z",
|
|
4
|
+
"updated_at": "2026-09-24",
|
|
5
|
+
"versions": [
|
|
6
|
+
{
|
|
7
|
+
"dsh": "0.1.5-rc.2",
|
|
8
|
+
"min_gate": "0.1.0",
|
|
9
|
+
"verified_at": "2026-09-19",
|
|
10
|
+
"notes": "M1-M3 全流程实测通过(登录/特权 RPC/WebSocket/向导/域名变更/升级自检)"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|