dsh-vps 1.4.4 → 1.5.1
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/README.en.md +9 -5
- package/README.md +9 -5
- package/bin/dsh-vps +161 -19
- package/gate/server.js +246 -4
- package/install.sh +5 -2
- package/package.json +1 -1
- package/uninstall.sh +3 -0
- package/versions.json +16 -2
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/deepseek-harness-vps" alt="MIT license"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/dsh-vps"><img src="https://img.shields.io/npm/v/dsh-vps" alt="npm version"></a>
|
|
10
10
|
<img src="https://img.shields.io/badge/platform-Ubuntu%2022.04%2B%20%2F%20Debian%2012%2B-blue" alt="Platform: Ubuntu 22.04+ / Debian 12+">
|
|
11
|
-
<img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.
|
|
11
|
+
<img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7--rc.1-4176E6" alt="DeepSeek Harness 0.1.7-rc.1">
|
|
12
12
|
<img src="https://img.shields.io/badge/runtime%20dependencies-0-brightgreen" alt="Runtime dependencies: 0">
|
|
13
13
|
<img src="https://img.shields.io/badge/Docker-not%20required-orange" alt="No Docker required">
|
|
14
14
|
<img src="https://img.shields.io/github/stars/AIcivilization/deepseek-harness-vps?style=social" alt="star">
|
|
@@ -128,18 +128,22 @@ If the final self-check reports that DSH is not ready yet, the setup link is sti
|
|
|
128
128
|
|
|
129
129
|
## Update
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
**DSH itself follows official releases.** The gateway checks npm for the newest official version (the higher of the `latest` and `next` channels) every 6 hours. When a newer one exists, a prompt appears in the bottom-right corner of the DSH page; click "Upgrade now" and confirm. It backs up, installs the new version and runs a self-check, rolling back to the previous version automatically if the check fails — about 1–3 minutes in all. "Later" silences that version. From the server you can also run:
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
sudo dsh-vps
|
|
134
|
+
sudo dsh-vps upgrade # the version verified by this project
|
|
135
|
+
sudo dsh-vps upgrade --latest # the latest official release (same as "Upgrade now")
|
|
136
|
+
sudo dsh-vps rollback # back to the previous version
|
|
135
137
|
```
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
**Updating the gateway itself** (login page, proxy):
|
|
138
140
|
|
|
139
141
|
```bash
|
|
140
|
-
sudo
|
|
142
|
+
sudo dsh-vps update-gate
|
|
141
143
|
```
|
|
142
144
|
|
|
145
|
+
Machines installed before v1.5.0: run the one-command install above once more to get the in-page upgrade prompt (it enters repair/update mode; accounts and data are kept).
|
|
146
|
+
|
|
143
147
|
## Uninstall
|
|
144
148
|
|
|
145
149
|
```bash
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/deepseek-harness-vps" alt="MIT 许可证"></a>
|
|
10
10
|
<a href="https://www.npmjs.com/package/dsh-vps"><img src="https://img.shields.io/npm/v/dsh-vps" alt="npm 版本"></a>
|
|
11
11
|
<img src="https://img.shields.io/badge/platform-Ubuntu%2022.04%2B%20%2F%20Debian%2012%2B-blue" alt="平台:Ubuntu 22.04+ / Debian 12+">
|
|
12
|
-
<img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.
|
|
12
|
+
<img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7--rc.1-4176E6" alt="DeepSeek Harness 0.1.7-rc.1">
|
|
13
13
|
<img src="https://img.shields.io/badge/runtime%20dependencies-0-brightgreen" alt="运行时依赖:0">
|
|
14
14
|
<img src="https://img.shields.io/badge/Docker-not%20required-orange" alt="无需 Docker">
|
|
15
15
|
<img src="https://img.shields.io/github/stars/AIcivilization/deepseek-harness-vps?style=social" alt="star">
|
|
@@ -138,18 +138,22 @@ curl -fsSL https://raw.githubusercontent.com/AIcivilization/deepseek-harness-vps
|
|
|
138
138
|
|
|
139
139
|
## 更新
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
**DSH 本身跟随官方版本升级。** 网关每 6 小时检查一次 npm 上的官方最新版(正式渠道 latest 与预览渠道 next 中较新的一个),有新版本时 DSH 页面右下角弹出提示,点「立即升级」并确认即可:自动备份 → 安装新版 → 自检,自检不通过自动回滚到原版本,全程约 1–3 分钟。点「稍后」则这个版本不再提示。也可以在服务器上手动执行:
|
|
142
142
|
|
|
143
143
|
```bash
|
|
144
|
-
sudo dsh-vps
|
|
144
|
+
sudo dsh-vps upgrade # 升到本项目验证过的版本
|
|
145
|
+
sudo dsh-vps upgrade --latest # 升到官方最新版(与页面上的「立即升级」相同)
|
|
146
|
+
sudo dsh-vps rollback # 切回上一个版本
|
|
145
147
|
```
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
**网关(登录页、代理)自身的更新**:
|
|
148
150
|
|
|
149
151
|
```bash
|
|
150
|
-
sudo
|
|
152
|
+
sudo dsh-vps update-gate
|
|
151
153
|
```
|
|
152
154
|
|
|
155
|
+
v1.5.0 之前安装的机器,重跑一次上方的一键安装命令即可获得页面升级提示(进入修复/更新模式,账号与数据保留)。
|
|
156
|
+
|
|
153
157
|
## 卸载
|
|
154
158
|
|
|
155
159
|
```bash
|
package/bin/dsh-vps
CHANGED
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
# tunnel 仅我可访问:SSH 本地端口转发(零安装,打印用法)
|
|
15
15
|
# backup 打包 DSH_HOME + state 到 backups/(保留最近 3 份)
|
|
16
16
|
# selfcheck 手动触发升级回归自检探针
|
|
17
|
+
# install-units 安装浏览器一键升级所需的 systemd 单元(install.sh / update-gate 调用)
|
|
18
|
+
# upgrade-run 执行浏览器发起的升级请求(由 dsh-vps-upgrade.service 以 root 调用)
|
|
17
19
|
|
|
18
|
-
set -
|
|
20
|
+
set -Eeuo pipefail
|
|
21
|
+
# set -e 会让脚本在任何一条命令失败时静默退出;这里把"在哪一行、哪条命令"打出来,
|
|
22
|
+
# 升级日志(state/upgrade.log)里就能直接看到原因,而不是戛然而止。
|
|
23
|
+
trap 'printf "\033[1;31m[dsh-vps]\033[0m 意外退出:第 %s 行 \`%s\` 返回 %s\n" "$LINENO" "$BASH_COMMAND" "$?" >&2' ERR
|
|
19
24
|
|
|
20
25
|
ROOT="${DSHVPS_ROOT:-/opt/dsh-vps}"
|
|
21
26
|
GATE_PORT="${GATE_PORT:-3100}"
|
|
@@ -30,9 +35,13 @@ VERSIONS_URL="${DSHVPS_VERSIONS_URL:-$RAW_BASE/versions.json}"
|
|
|
30
35
|
# ——2026-09-22 上游漏发 rc.3 的 dsh-client-ui-sidebar-documentpreview 就导致过 ETARGET。
|
|
31
36
|
# 同日 03:46 的 cordis 系列新版本同样与 rc.2 不兼容(HMR 服务缺失致启动崩溃),故取 03:40。
|
|
32
37
|
# 升级 latest_verified 时必须同步推后这个值。
|
|
33
|
-
DEFAULT_RESOLVE_BEFORE="${DSHVPS_RESOLVE_BEFORE:-2026-09-
|
|
38
|
+
DEFAULT_RESOLVE_BEFORE="${DSHVPS_RESOLVE_BEFORE:-2026-09-24T08:20:00Z}"
|
|
34
39
|
VERSIONS_CACHE="$ROOT/state/versions.cache.json"
|
|
35
40
|
NPM_REGISTRY="${DSHVPS_NPM_REGISTRY:-}"
|
|
41
|
+
# 安装时选了 --mirror cn:升级也走 npmmirror,与首次安装同源
|
|
42
|
+
if [[ -z "$NPM_REGISTRY" && -f "$ROOT/state/config.json" ]] && grep -q '"mirror": *"cn"' "$ROOT/state/config.json"; then
|
|
43
|
+
NPM_REGISTRY="https://registry.npmmirror.com"
|
|
44
|
+
fi
|
|
36
45
|
|
|
37
46
|
log() { printf '\033[1;32m[dsh-vps]\033[0m %s\n' "$*"; }
|
|
38
47
|
warn() { printf '\033[1;33m[dsh-vps]\033[0m %s\n' "$*" >&2; }
|
|
@@ -48,14 +57,25 @@ cmd_backup() {
|
|
|
48
57
|
require_root
|
|
49
58
|
mkdir -p "$ROOT/backups"
|
|
50
59
|
local ts="$ROOT/backups/dsh-vps-$(date +%Y%m%d-%H%M%S).tar.gz"
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
# DSH 运行中仍在写文件:GNU tar 遇到"读取期间文件有变化"返回 1,归档本身完整,只告警;
|
|
61
|
+
# 返回 2 才是真失败
|
|
62
|
+
local rc=0
|
|
63
|
+
tar -czf "$ts" -C / "${DSH_HOME_DIR#/}" "${ROOT#/}/state" 2>/dev/null || rc=$?
|
|
64
|
+
if [[ $rc -eq 1 ]]; then
|
|
65
|
+
warn "部分文件在备份期间有变动(DSH 运行中属正常),备份仍可用"
|
|
66
|
+
elif [[ $rc -ne 0 ]]; then
|
|
67
|
+
die "备份失败(tar 返回 $rc)"
|
|
68
|
+
fi
|
|
53
69
|
chmod 600 "$ts"
|
|
54
70
|
log "备份完成: $ts"
|
|
55
|
-
# 保留最近 3
|
|
71
|
+
# 保留最近 3 份。写成 if:`[[ ]] && ...` 作为函数最后一行时,条件不成立会让函数返回 1,
|
|
72
|
+
# 调用方在 set -e 下直接静默退出——首次升级(备份不足 4 份)就曾因此在备份后中止
|
|
56
73
|
local old
|
|
57
|
-
old=$(ls -1t "$ROOT"/backups/dsh-vps-*.tar.gz 2>/dev/null | tail -n +4)
|
|
58
|
-
[[ -n "$old" ]]
|
|
74
|
+
old=$(ls -1t "$ROOT"/backups/dsh-vps-*.tar.gz 2>/dev/null | tail -n +4 || true)
|
|
75
|
+
if [[ -n "$old" ]]; then
|
|
76
|
+
rm -f $old
|
|
77
|
+
log "已清理旧备份"
|
|
78
|
+
fi
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
## endregion
|
|
@@ -92,11 +112,21 @@ cmd_update_gate() {
|
|
|
92
112
|
rm -f "$sb"
|
|
93
113
|
warn "gate/site-block.js 未更新(保留现有版本)"
|
|
94
114
|
fi
|
|
115
|
+
# dsh-vps 自身:先写临时文件再 rename,正在执行的旧脚本仍持有旧 inode,不会读到半截新内容
|
|
116
|
+
local nb="$ROOT/bin/dsh-vps.update"
|
|
117
|
+
if curl -fsSL --max-time 30 "$RAW_BASE/bin/dsh-vps" -o "$nb" && bash -n "$nb"; then
|
|
118
|
+
chmod 755 "$nb"
|
|
119
|
+
mv "$nb" "$ROOT/bin/dsh-vps"
|
|
120
|
+
else
|
|
121
|
+
rm -f "$nb"
|
|
122
|
+
warn "bin/dsh-vps 未更新(保留现有版本)"
|
|
123
|
+
fi
|
|
95
124
|
log "重启 $SERVICE ..."
|
|
96
125
|
systemctl restart "$SERVICE"
|
|
97
126
|
sleep 3
|
|
98
|
-
#
|
|
99
|
-
|
|
127
|
+
# 以下都用新版 dsh-vps 执行:静态补丁(含升级提示条)与一键升级单元(幂等)
|
|
128
|
+
"$ROOT/bin/dsh-vps" ownshost on >/dev/null 2>&1 || true
|
|
129
|
+
"$ROOT/bin/dsh-vps" install-units || warn "一键升级单元安装失败"
|
|
100
130
|
if curl -fsS --max-time 3 "$HEALTH_URL" >/dev/null 2>&1; then
|
|
101
131
|
log "更新完成,gate 已就绪"
|
|
102
132
|
else
|
|
@@ -194,7 +224,7 @@ switch_version() { # $1=版本目录名
|
|
|
194
224
|
|
|
195
225
|
cmd_upgrade() {
|
|
196
226
|
require_root
|
|
197
|
-
local target="" latest_mode=0 assume_yes=0
|
|
227
|
+
local target="" latest_mode=0 assume_yes=0 verified_latest=0
|
|
198
228
|
while [[ $# -gt 0 ]]; do
|
|
199
229
|
case "$1" in
|
|
200
230
|
--latest) latest_mode=1; shift ;;
|
|
@@ -211,11 +241,44 @@ cmd_upgrade() {
|
|
|
211
241
|
local resolve_before="$DEFAULT_RESOLVE_BEFORE"
|
|
212
242
|
|
|
213
243
|
if [[ $latest_mode -eq 1 ]]; then
|
|
214
|
-
warn "--latest
|
|
244
|
+
warn "--latest:将安装官方最新版(npm latest 与 next 渠道中较新的一个),可能未经 dsh-vps 验证(有备份与自动回滚兜底)"
|
|
215
245
|
[[ $assume_yes -eq 1 ]] || { read -rp "确认继续?[y/N] " ans; [[ "$ans" == "y" || "$ans" == "Y" ]] || die "已取消"; }
|
|
216
|
-
local reg_arg=(
|
|
246
|
+
local reg_arg=(dist-tags --json)
|
|
217
247
|
[[ -n "$NPM_REGISTRY" ]] && reg_arg+=(--registry="$NPM_REGISTRY")
|
|
218
|
-
|
|
248
|
+
# 官方同时维护 latest(正式)与 next(预览)两个渠道,取其中版本号更高的;alpha 等内部渠道不取
|
|
249
|
+
target=$(npm view @deepseek-ai/dsh "${reg_arg[@]}" 2>/dev/null | node -e '
|
|
250
|
+
const t = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
251
|
+
const parse = (v) => { const [c, p] = v.split("-", 2); return { c: c.split(".").map(Number), p: p === undefined ? null : p.split(".") }; };
|
|
252
|
+
const cmp = (a, b) => {
|
|
253
|
+
const x = parse(a), y = parse(b);
|
|
254
|
+
for (let i = 0; i < 3; i++) if (x.c[i] !== y.c[i]) return x.c[i] - y.c[i];
|
|
255
|
+
if (x.p === null || y.p === null) return (x.p === null) - (y.p === null);
|
|
256
|
+
for (let i = 0; i < Math.max(x.p.length, y.p.length); i++) {
|
|
257
|
+
const p = x.p[i], q = y.p[i];
|
|
258
|
+
if (p === undefined || q === undefined) return p === undefined ? -1 : 1;
|
|
259
|
+
const pn = /^\d+$/.test(p), qn = /^\d+$/.test(q);
|
|
260
|
+
if (pn && qn && +p !== +q) return +p - +q;
|
|
261
|
+
if (pn !== qn) return pn ? -1 : 1;
|
|
262
|
+
if (p !== q) return p < q ? -1 : 1;
|
|
263
|
+
}
|
|
264
|
+
return 0;
|
|
265
|
+
};
|
|
266
|
+
const c = [t.latest, t.next].filter((v) => typeof v === "string" && /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(v));
|
|
267
|
+
if (!c.length) process.exit(1);
|
|
268
|
+
console.log(c.sort(cmp).pop());
|
|
269
|
+
') || die "无法查询 npm 最新版本"
|
|
270
|
+
# 官方最新版恰好是我们验证过的版本:用验证时冻结的依赖树,而不是当下重新解析
|
|
271
|
+
local vt
|
|
272
|
+
if vt=$(curl -fsSL --max-time 10 "$VERSIONS_URL" 2>/dev/null) \
|
|
273
|
+
&& [[ "$(printf '%s' "$vt" | sed -n 's/.*"latest_verified": *"\([^"]*\)".*/\1/p' | head -1)" == "$target" ]]; then
|
|
274
|
+
local vrb
|
|
275
|
+
vrb=$(printf '%s' "$vt" | sed -n 's/.*"resolve_before": *"\([^"]*\)".*/\1/p' | head -1)
|
|
276
|
+
if [[ -n "$vrb" ]]; then
|
|
277
|
+
resolve_before="$vrb"
|
|
278
|
+
verified_latest=1
|
|
279
|
+
log "$target 是已验证版本,按验证时的依赖树安装(冻结于 $vrb)"
|
|
280
|
+
fi
|
|
281
|
+
fi
|
|
219
282
|
elif [[ -z "$target" ]]; then
|
|
220
283
|
# 默认:已验证版本表的 latest_verified
|
|
221
284
|
local versions
|
|
@@ -248,7 +311,7 @@ cmd_upgrade() {
|
|
|
248
311
|
local args=(--no-audit --no-fund --loglevel=error)
|
|
249
312
|
[[ -n "$NPM_REGISTRY" ]] && args+=(--registry="$NPM_REGISTRY")
|
|
250
313
|
local frozen=1
|
|
251
|
-
[[ $latest_mode -eq 1 || "$resolve_before" == "none" ]] && frozen=0
|
|
314
|
+
[[ ($latest_mode -eq 1 && $verified_latest -eq 0) || "$resolve_before" == "none" ]] && frozen=0
|
|
252
315
|
[[ $frozen -eq 1 ]] && args+=(--before="$resolve_before")
|
|
253
316
|
if ! npm install --prefix "$prefix" "${args[@]}" "@deepseek-ai/dsh@$target"; then
|
|
254
317
|
if [[ $frozen -eq 1 ]]; then
|
|
@@ -279,7 +342,7 @@ cmd_upgrade() {
|
|
|
279
342
|
|
|
280
343
|
# 5. 清理:保留最近 2 个版本目录
|
|
281
344
|
local old_dirs
|
|
282
|
-
old_dirs=$(ls -1d "$ROOT"/dsh/*/ 2>/dev/null | grep -v current | sed 's|/$||' | sort -V | head -n -2)
|
|
345
|
+
old_dirs=$(ls -1d "$ROOT"/dsh/*/ 2>/dev/null | grep -v current | sed 's|/$||' | sort -V | head -n -2 || true)
|
|
283
346
|
for d in $old_dirs; do
|
|
284
347
|
[[ "$(basename "$d")" == "$cur" ]] && continue # 保底:至少留一个可回滚目标
|
|
285
348
|
rm -rf "$d" && log "已清理旧版本目录: $d"
|
|
@@ -292,7 +355,7 @@ cmd_rollback() {
|
|
|
292
355
|
cur=$(current_version)
|
|
293
356
|
local prev
|
|
294
357
|
prev=$(ls -1d "$ROOT"/dsh/*/ 2>/dev/null | grep -v current | sed 's|/$||' | xargs -n1 basename 2>/dev/null \
|
|
295
|
-
| grep -vx "$cur" | sort -V | tail -1)
|
|
358
|
+
| grep -vx "$cur" | sort -V | tail -1 || true)
|
|
296
359
|
[[ -n "$prev" ]] || die "没有可回滚的版本目录"
|
|
297
360
|
log "回滚: $cur -> $prev"
|
|
298
361
|
switch_version "$prev"
|
|
@@ -421,15 +484,21 @@ const MARK = "dsh-vps:ownshost";
|
|
|
421
484
|
const SNIPPET =
|
|
422
485
|
'<script data-dsh-vps="ownshost">/* ' + MARK +
|
|
423
486
|
" */window.__DSH_TRANSPORT__=Object.assign(window.__DSH_TRANSPORT__||{},{ownsHost:true});</script>";
|
|
487
|
+
// 升级提示条:同源脚本,由 gate 提供(/gate/ui.js)
|
|
488
|
+
const UI_MARK = "dsh-vps:ui";
|
|
489
|
+
const UI_SNIPPET = '<script data-dsh-vps="' + UI_MARK + '" src="/gate/ui.js" defer></script>';
|
|
424
490
|
let s = fs.readFileSync(file, "utf8");
|
|
425
491
|
const orig = file + ".orig";
|
|
426
492
|
if (mode === "off") {
|
|
427
493
|
if (fs.existsSync(orig)) fs.copyFileSync(orig, file);
|
|
428
|
-
else fs.writeFileSync(file, s.split("\n").filter((l) => !l.includes(MARK)).join("\n"));
|
|
494
|
+
else fs.writeFileSync(file, s.split("\n").filter((l) => !l.includes(MARK) && !l.includes(UI_MARK)).join("\n"));
|
|
429
495
|
console.log("off");
|
|
430
496
|
process.exit(0);
|
|
431
497
|
}
|
|
432
|
-
|
|
498
|
+
const missing = [];
|
|
499
|
+
if (!s.includes(MARK)) missing.push(SNIPPET);
|
|
500
|
+
if (!s.includes(UI_MARK)) missing.push(UI_SNIPPET);
|
|
501
|
+
if (missing.length === 0) {
|
|
433
502
|
console.log("present");
|
|
434
503
|
process.exit(0);
|
|
435
504
|
}
|
|
@@ -440,7 +509,7 @@ if (m === null) {
|
|
|
440
509
|
process.exit(1);
|
|
441
510
|
}
|
|
442
511
|
const at = m.index + m[0].length;
|
|
443
|
-
fs.writeFileSync(file, s.slice(0, at) + "\n " +
|
|
512
|
+
fs.writeFileSync(file, s.slice(0, at) + missing.map((x) => "\n " + x).join("") + s.slice(at));
|
|
444
513
|
console.log("patched");
|
|
445
514
|
NODE
|
|
446
515
|
node "$tmp" "$idx" "$mode" || rc=$?
|
|
@@ -883,6 +952,77 @@ EOF
|
|
|
883
952
|
|
|
884
953
|
## endregion
|
|
885
954
|
|
|
955
|
+
## region: 浏览器一键升级
|
|
956
|
+
|
|
957
|
+
# 页面上点「立即升级」→ gate(dsh 用户,无特权)只写 state/upgrade.request →
|
|
958
|
+
# systemd path 单元看到文件 → 以 root 跑 dsh-vps upgrade-run。网页拿不到 root,
|
|
959
|
+
# 能触发的只有这一条固定命令:升级到 npm 官方 latest(备份 → 自检 → 失败自动回滚)。
|
|
960
|
+
UPGRADE_REQUEST="$ROOT/state/upgrade.request"
|
|
961
|
+
UPGRADE_STATUS="$ROOT/state/upgrade.status.json"
|
|
962
|
+
UPGRADE_LOG="$ROOT/state/upgrade.log"
|
|
963
|
+
|
|
964
|
+
cmd_install_units() {
|
|
965
|
+
require_root
|
|
966
|
+
cat >/etc/systemd/system/dsh-vps-upgrade.path <<UNIT
|
|
967
|
+
# dsh-vps:监听浏览器发起的 DSH 升级请求(由 dsh-vps install-units 生成)
|
|
968
|
+
[Unit]
|
|
969
|
+
Description=dsh-vps: watch for browser-initiated DSH upgrade requests
|
|
970
|
+
|
|
971
|
+
[Path]
|
|
972
|
+
PathExists=$UPGRADE_REQUEST
|
|
973
|
+
Unit=dsh-vps-upgrade.service
|
|
974
|
+
|
|
975
|
+
[Install]
|
|
976
|
+
WantedBy=multi-user.target
|
|
977
|
+
UNIT
|
|
978
|
+
cat >/etc/systemd/system/dsh-vps-upgrade.service <<UNIT
|
|
979
|
+
# dsh-vps:执行浏览器发起的 DSH 升级(由 dsh-vps install-units 生成)
|
|
980
|
+
[Unit]
|
|
981
|
+
Description=dsh-vps: run a DSH upgrade requested from the browser
|
|
982
|
+
|
|
983
|
+
[Service]
|
|
984
|
+
Type=oneshot
|
|
985
|
+
ExecStart=$ROOT/bin/dsh-vps upgrade-run
|
|
986
|
+
TimeoutStartSec=30min
|
|
987
|
+
UNIT
|
|
988
|
+
systemctl daemon-reload
|
|
989
|
+
systemctl enable --now dsh-vps-upgrade.path >/dev/null 2>&1 || die "启用 dsh-vps-upgrade.path 失败"
|
|
990
|
+
log "浏览器一键升级已启用(dsh-vps-upgrade.path)"
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
write_upgrade_status() { # $1=state $2=from $3=to
|
|
994
|
+
local owner
|
|
995
|
+
owner=$(stat -c %U "$ROOT/state" 2>/dev/null || echo dsh)
|
|
996
|
+
printf '{"state":"%s","from":"%s","to":"%s","at":%s}\n' "$1" "$2" "$3" "$(date +%s)000" >"$UPGRADE_STATUS"
|
|
997
|
+
chown "$owner" "$UPGRADE_STATUS" 2>/dev/null || true
|
|
998
|
+
chmod 600 "$UPGRADE_STATUS"
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
cmd_upgrade_run() {
|
|
1002
|
+
require_root
|
|
1003
|
+
# 先删请求文件:path 单元在文件仍存在时会再次触发
|
|
1004
|
+
[[ -f "$UPGRADE_REQUEST" ]] || exit 0
|
|
1005
|
+
rm -f "$UPGRADE_REQUEST"
|
|
1006
|
+
local from to rc=0 owner
|
|
1007
|
+
from=$(current_version)
|
|
1008
|
+
write_upgrade_status running "$from" ""
|
|
1009
|
+
owner=$(stat -c %U "$ROOT/state" 2>/dev/null || echo dsh)
|
|
1010
|
+
: >"$UPGRADE_LOG"
|
|
1011
|
+
chown "$owner" "$UPGRADE_LOG" 2>/dev/null || true
|
|
1012
|
+
chmod 600 "$UPGRADE_LOG"
|
|
1013
|
+
# 独立进程执行:写在 `|| rc=$?` 里的函数/子 shell 会让 set -e 失效,出错也不停
|
|
1014
|
+
"$ROOT/bin/dsh-vps" upgrade --latest --yes >>"$UPGRADE_LOG" 2>&1 || rc=$?
|
|
1015
|
+
[[ $rc -eq 0 ]] || printf '[dsh-vps] 升级进程退出码 %s(原因见上一行)\n' "$rc" >>"$UPGRADE_LOG"
|
|
1016
|
+
to=$(current_version)
|
|
1017
|
+
if [[ $rc -eq 0 ]]; then
|
|
1018
|
+
write_upgrade_status success "$from" "$to"
|
|
1019
|
+
else
|
|
1020
|
+
write_upgrade_status failed "$from" "$to"
|
|
1021
|
+
fi
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
## endregion
|
|
1025
|
+
|
|
886
1026
|
usage() {
|
|
887
1027
|
cat <<'EOF'
|
|
888
1028
|
dsh-vps 管理命令
|
|
@@ -913,6 +1053,8 @@ reset-admin) shift; cmd_reset_admin "$@" ;;
|
|
|
913
1053
|
setup-url) cmd_setup_url ;;
|
|
914
1054
|
backup) cmd_backup ;;
|
|
915
1055
|
selfcheck) cmd_selfcheck ;;
|
|
1056
|
+
install-units) cmd_install_units ;;
|
|
1057
|
+
upgrade-run) cmd_upgrade_run ;;
|
|
916
1058
|
vpn) shift; cmd_vpn "$@" ;;
|
|
917
1059
|
tunnel) cmd_tunnel ;;
|
|
918
1060
|
"" | -h | --help) usage ;;
|
package/gate/server.js
CHANGED
|
@@ -94,6 +94,15 @@ const OWNS_HOST_SNIPPET =
|
|
|
94
94
|
`<script data-dsh-vps="ownshost">/* ${OWNS_HOST_MARK} */` +
|
|
95
95
|
"window.__DSH_TRANSPORT__=Object.assign(window.__DSH_TRANSPORT__||{},{ownsHost:true});</script>";
|
|
96
96
|
let ownshostLogged = false;
|
|
97
|
+
// 升级提示条:同源脚本 /gate/ui.js。bin/dsh-vps 的静态补丁同样写入它,两边共用同一标记。
|
|
98
|
+
const UI_MARK = "dsh-vps:ui";
|
|
99
|
+
const UI_SNIPPET = `<script data-dsh-vps="${UI_MARK}" src="/gate/ui.js" defer></script>`;
|
|
100
|
+
|
|
101
|
+
// DSH 版本检测:跟随官方最新版(npm latest 与 next 渠道中较新的一个)。页面上确认后,gate 只写 state/upgrade.request,
|
|
102
|
+
// 由 root 的 dsh-vps-upgrade.path/.service 执行升级(gate 自身无权改 /opt/dsh-vps/dsh)。
|
|
103
|
+
const DSH_PACKAGE = "@deepseek-ai/dsh";
|
|
104
|
+
const UPDATE_CHECK_INTERVAL_MS = 6 * 3_600_000;
|
|
105
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
97
106
|
|
|
98
107
|
// DSH 的若干特权端点(dsh-market 的 restart / backup 导出 / self-uninstall)要求
|
|
99
108
|
// "直连回环":只要出现 x-forwarded-for / x-real-ip / forwarded 任一头,
|
|
@@ -859,15 +868,18 @@ function collectAndInject(upRes, res, status, respHeaders) {
|
|
|
859
868
|
return;
|
|
860
869
|
}
|
|
861
870
|
// 静态文件补丁(bin/dsh-vps ownshost on)已经在页面里时不再重复注入
|
|
862
|
-
|
|
871
|
+
const missing = [];
|
|
872
|
+
if (!body.includes(OWNS_HOST_MARK)) missing.push(OWNS_HOST_SNIPPET);
|
|
873
|
+
if (!body.includes(UI_MARK)) missing.push(UI_SNIPPET);
|
|
874
|
+
if (missing.length) {
|
|
863
875
|
// 只有完整的 HTML 文档才改写;找不到 <head> 说明不是文档(片段/JSON 误标),原样转发
|
|
864
876
|
const at = body.search(/<head\b[^>]*>/i);
|
|
865
877
|
if (at !== -1) {
|
|
866
878
|
const end = body.indexOf(">", at) + 1;
|
|
867
|
-
body = body.slice(0, end) +
|
|
879
|
+
body = body.slice(0, end) + missing.join("") + body.slice(end);
|
|
868
880
|
if (!ownshostLogged) {
|
|
869
881
|
ownshostLogged = true;
|
|
870
|
-
log("injected
|
|
882
|
+
log("injected page snippets by proxy (静态文件补丁未生效,建议 sudo dsh-vps ownshost on)");
|
|
871
883
|
}
|
|
872
884
|
}
|
|
873
885
|
}
|
|
@@ -1601,6 +1613,229 @@ async function handleSetup(req, res) {
|
|
|
1601
1613
|
res.end();
|
|
1602
1614
|
}
|
|
1603
1615
|
|
|
1616
|
+
//#region DSH 版本检测与浏览器一键升级
|
|
1617
|
+
|
|
1618
|
+
const update = { latest: null, checkedAt: 0, error: null };
|
|
1619
|
+
|
|
1620
|
+
function upgradeRequestPath() {
|
|
1621
|
+
return path.join(STATE_DIR, "upgrade.request");
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function currentDshVersion() {
|
|
1625
|
+
try {
|
|
1626
|
+
const v = fs.readlinkSync(path.join(GATE_HOME, "dsh", "current"));
|
|
1627
|
+
return SEMVER_PATTERN.test(v) ? v : null;
|
|
1628
|
+
} catch {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/** semver 比较(含预发布段):a>b 返回正数。 */
|
|
1634
|
+
function compareVersions(a, b) {
|
|
1635
|
+
const split = (v) => {
|
|
1636
|
+
const [core, pre] = v.split("-", 2);
|
|
1637
|
+
return { core: core.split(".").map(Number), pre: pre === void 0 ? null : pre.split(".") };
|
|
1638
|
+
};
|
|
1639
|
+
const x = split(a);
|
|
1640
|
+
const y = split(b);
|
|
1641
|
+
for (let i = 0; i < 3; i++) if (x.core[i] !== y.core[i]) return x.core[i] - y.core[i];
|
|
1642
|
+
if (x.pre === null || y.pre === null) return (x.pre === null) - (y.pre === null);
|
|
1643
|
+
for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
|
|
1644
|
+
const p = x.pre[i];
|
|
1645
|
+
const q = y.pre[i];
|
|
1646
|
+
if (p === void 0 || q === void 0) return p === void 0 ? -1 : 1;
|
|
1647
|
+
const pn = /^\d+$/.test(p);
|
|
1648
|
+
const qn = /^\d+$/.test(q);
|
|
1649
|
+
if (pn && qn && Number(p) !== Number(q)) return Number(p) - Number(q);
|
|
1650
|
+
if (pn !== qn) return pn ? -1 : 1;
|
|
1651
|
+
if (p !== q) return p < q ? -1 : 1;
|
|
1652
|
+
}
|
|
1653
|
+
return 0;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/** 官方 latest(正式)与 next(预览)两个渠道中版本号更高的一个;alpha 等内部渠道不取。 */
|
|
1657
|
+
function newestDshVersion() {
|
|
1658
|
+
return new Promise((resolve) => {
|
|
1659
|
+
const url = `${npmRegistry()}/-/package/${encodeURIComponent(DSH_PACKAGE).replace(/^%40/, "@")}/dist-tags`;
|
|
1660
|
+
const req = https.get(url, { headers: { accept: "application/json" }, timeout: 10_000 }, (res) => {
|
|
1661
|
+
const chunks = [];
|
|
1662
|
+
res.on("data", (c) => chunks.push(c));
|
|
1663
|
+
res.on("end", () => {
|
|
1664
|
+
try {
|
|
1665
|
+
const tags = res.statusCode === 200 ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : {};
|
|
1666
|
+
const candidates = [tags.latest, tags.next].filter((v) => typeof v === "string" && SEMVER_PATTERN.test(v));
|
|
1667
|
+
resolve(candidates.length ? candidates.sort(compareVersions).pop() : null);
|
|
1668
|
+
} catch {
|
|
1669
|
+
resolve(null);
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
1673
|
+
req.on("timeout", () => req.destroy());
|
|
1674
|
+
req.on("error", () => resolve(null));
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
async function checkDshLatest() {
|
|
1679
|
+
const v = await newestDshVersion();
|
|
1680
|
+
update.checkedAt = Date.now();
|
|
1681
|
+
if (v) {
|
|
1682
|
+
if (v !== update.latest) log(`dsh latest on registry: ${v} (running ${currentDshVersion() || "unknown"})`);
|
|
1683
|
+
update.latest = v;
|
|
1684
|
+
update.error = null;
|
|
1685
|
+
} else {
|
|
1686
|
+
update.error = "无法查询 npm 最新版本";
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function readUpgradeStatus() {
|
|
1691
|
+
try {
|
|
1692
|
+
const st = JSON.parse(fs.readFileSync(path.join(STATE_DIR, "upgrade.status.json"), "utf8"));
|
|
1693
|
+
return st && typeof st.state === "string" ? st : null;
|
|
1694
|
+
} catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function updateInfo() {
|
|
1700
|
+
const current = currentDshVersion();
|
|
1701
|
+
const latest = update.latest;
|
|
1702
|
+
return {
|
|
1703
|
+
current,
|
|
1704
|
+
latest,
|
|
1705
|
+
available: Boolean(current && latest && compareVersions(latest, current) > 0),
|
|
1706
|
+
checkedAt: update.checkedAt || null,
|
|
1707
|
+
error: update.error,
|
|
1708
|
+
requested: fs.existsSync(upgradeRequestPath()),
|
|
1709
|
+
status: readUpgradeStatus(),
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/** GET:更新状态;POST:请求升级到官方最新版(写请求文件,交给 root 的 path 单元)。 */
|
|
1714
|
+
async function handleUpdate(req, res, user) {
|
|
1715
|
+
const json = (status, body) => {
|
|
1716
|
+
res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" });
|
|
1717
|
+
res.end(JSON.stringify(body));
|
|
1718
|
+
};
|
|
1719
|
+
if (req.method === "GET") return json(200, updateInfo());
|
|
1720
|
+
if (req.method !== "POST") return json(405, { error: "method not allowed" });
|
|
1721
|
+
// 触发的是 root 操作:必须是本站页面发起(fetch POST 必带 Origin)
|
|
1722
|
+
const origin = req.headers.origin;
|
|
1723
|
+
let sameOrigin = false;
|
|
1724
|
+
try {
|
|
1725
|
+
sameOrigin = origin !== void 0 && new URL(origin).host === requestAuthority(req.headers);
|
|
1726
|
+
} catch {
|
|
1727
|
+
/* 非法 Origin */
|
|
1728
|
+
}
|
|
1729
|
+
if (!sameOrigin || String(req.headers["sec-fetch-site"] || "") === "cross-site") return json(403, { error: "cross-origin request refused" });
|
|
1730
|
+
await checkDshLatest(); // 以点击时的最新结果为准
|
|
1731
|
+
const info = updateInfo();
|
|
1732
|
+
if (info.requested || (info.status && info.status.state === "running")) return json(409, { error: "升级已在进行中" });
|
|
1733
|
+
if (!info.available) return json(409, { error: "已是最新版本" });
|
|
1734
|
+
fs.writeFileSync(upgradeRequestPath(), JSON.stringify({ target: info.latest, from: info.current, by: user, at: Date.now() }) + "\n", { mode: 0o600 });
|
|
1735
|
+
log(`upgrade requested from browser by ${user}: ${info.current} -> ${info.latest}`);
|
|
1736
|
+
return json(202, { ok: true, from: info.current, to: info.latest });
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// 注入 DSH 页面的升级提示条(同源脚本,无外部依赖)。
|
|
1740
|
+
// 只做提示与确认;真正的升级由 root 服务执行,失败会自动回滚。
|
|
1741
|
+
const UI_JS = `(function () {
|
|
1742
|
+
if (window.top !== window || document.getElementById("dshvps-update")) return;
|
|
1743
|
+
var KEY = "dshvps-update-dismissed";
|
|
1744
|
+
function store(k, v) { try { if (v === undefined) return localStorage.getItem(k); localStorage.setItem(k, v); } catch (e) { return null; } }
|
|
1745
|
+
var box, timer, busy = false, asked = false;
|
|
1746
|
+
function el(tag, css, text) { var n = document.createElement(tag); if (css) n.style.cssText = css; if (text) n.textContent = text; return n; }
|
|
1747
|
+
var BTN = "margin-left:8px;padding:5px 12px;border-radius:7px;border:1px solid #2a3547;cursor:pointer;font:inherit;";
|
|
1748
|
+
function render(msg, actions) {
|
|
1749
|
+
if (!box) {
|
|
1750
|
+
box = el("div", "position:fixed;right:16px;bottom:16px;z-index:2147483647;max-width:min(420px,calc(100vw - 32px));" +
|
|
1751
|
+
"padding:12px 14px;border-radius:10px;background:#11161f;color:#dbe2ea;border:1px solid #2a3547;" +
|
|
1752
|
+
"box-shadow:0 8px 30px rgba(0,0,0,.35);font:13px/1.6 system-ui,-apple-system,'Segoe UI',sans-serif");
|
|
1753
|
+
box.id = "dshvps-update";
|
|
1754
|
+
document.body.appendChild(box);
|
|
1755
|
+
}
|
|
1756
|
+
box.textContent = "";
|
|
1757
|
+
box.appendChild(el("div", "", msg));
|
|
1758
|
+
if (actions && actions.length) {
|
|
1759
|
+
var row = el("div", "margin-top:8px;text-align:right");
|
|
1760
|
+
actions.forEach(function (a) {
|
|
1761
|
+
var b = el("button", BTN + (a.primary ? "background:#2563eb;color:#fff;border-color:#2563eb" : "background:#0d1219;color:#dbe2ea"), a.label);
|
|
1762
|
+
b.onclick = a.onClick;
|
|
1763
|
+
row.appendChild(b);
|
|
1764
|
+
});
|
|
1765
|
+
box.appendChild(row);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function hide() { if (box) { box.remove(); box = null; } }
|
|
1769
|
+
function get() { return fetch("/gate/update", { cache: "no-store", credentials: "same-origin" }).then(function (r) { if (!r.ok) throw new Error(String(r.status)); return r.json(); }); }
|
|
1770
|
+
function poll() {
|
|
1771
|
+
get().then(function (u) {
|
|
1772
|
+
var st = u.status;
|
|
1773
|
+
if (u.requested || (st && st.state === "running")) {
|
|
1774
|
+
render("正在升级 DeepSeek Harness" + (u.latest ? " 到 " + u.latest : "") + "…(约 1–3 分钟,期间页面会短暂不可用,请勿关闭)");
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
if (st && asked && st.state === "success") {
|
|
1778
|
+
render("升级完成(" + st.from + " → " + st.to + "),正在刷新…");
|
|
1779
|
+
clearInterval(timer);
|
|
1780
|
+
setTimeout(function () { location.reload(); }, 1500);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (st && asked && st.state === "failed") {
|
|
1784
|
+
clearInterval(timer);
|
|
1785
|
+
render("升级未成功,已自动回滚到 " + (st.to || st.from) + ",当前可继续使用。详情:sudo cat /opt/dsh-vps/state/upgrade.log", [{ label: "知道了", onClick: hide }]);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
}).catch(function () {
|
|
1789
|
+
if (asked) render("正在升级,DeepSeek Harness 重启中…");
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
function start() {
|
|
1793
|
+
if (busy) return;
|
|
1794
|
+
if (!window.confirm("升级期间 DeepSeek Harness 会重启,约 1–3 分钟不可用。\\n升级前自动备份;新版本自检不通过会自动回滚到当前版本。\\n\\n确认升级?")) return;
|
|
1795
|
+
busy = true;
|
|
1796
|
+
fetch("/gate/update", { method: "POST", credentials: "same-origin" }).then(function (r) {
|
|
1797
|
+
return r.json().then(function (b) { if (!r.ok) throw new Error(b.error || String(r.status)); return b; });
|
|
1798
|
+
}).then(function () {
|
|
1799
|
+
asked = true;
|
|
1800
|
+
store("dshvps-update-asked", String(Date.now()));
|
|
1801
|
+
render("已提交升级请求,等待开始…");
|
|
1802
|
+
timer = setInterval(poll, 3000);
|
|
1803
|
+
}).catch(function (e) {
|
|
1804
|
+
busy = false;
|
|
1805
|
+
render("无法开始升级:" + e.message, [{ label: "关闭", onClick: hide }]);
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
function init() {
|
|
1809
|
+
// 升级过程中刷新了页面:继续跟进,完成后给出结果
|
|
1810
|
+
var t = Number(store("dshvps-update-asked") || 0);
|
|
1811
|
+
asked = t > 0 && Date.now() - t < 30 * 60000;
|
|
1812
|
+
get().then(function (u) {
|
|
1813
|
+
var st = u.status;
|
|
1814
|
+
if (u.requested || (st && st.state === "running")) { asked = true; timer = setInterval(poll, 3000); poll(); return; }
|
|
1815
|
+
if (asked && st && st.at > t) { store("dshvps-update-asked", "0"); if (st.state === "failed") poll(); return; }
|
|
1816
|
+
if (!u.available || store(KEY) === u.latest) return;
|
|
1817
|
+
render("DeepSeek Harness 有新版本 " + u.latest + "(当前 " + u.current + ")", [
|
|
1818
|
+
{ label: "稍后", onClick: function () { store(KEY, u.latest); hide(); } },
|
|
1819
|
+
{ label: "立即升级", primary: true, onClick: start },
|
|
1820
|
+
]);
|
|
1821
|
+
}).catch(function () {});
|
|
1822
|
+
}
|
|
1823
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
|
|
1824
|
+
else init();
|
|
1825
|
+
})();
|
|
1826
|
+
`;
|
|
1827
|
+
|
|
1828
|
+
function handleUiJs(req, res) {
|
|
1829
|
+
res.writeHead(200, {
|
|
1830
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
1831
|
+
"cache-control": "no-cache",
|
|
1832
|
+
"x-content-type-options": "nosniff",
|
|
1833
|
+
});
|
|
1834
|
+
res.end(req.method === "HEAD" ? void 0 : UI_JS);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
//#endregion
|
|
1838
|
+
|
|
1604
1839
|
/** 升级回归自检(dsh-vps upgrade 调用):健康 + token 兑换 + 登录态 credentials/describe 探针。 */
|
|
1605
1840
|
async function handleSelfcheck(req, res) {
|
|
1606
1841
|
const report = {
|
|
@@ -1673,6 +1908,7 @@ function main() {
|
|
|
1673
1908
|
if (pathname === "/logout") return handleLogout(req, res);
|
|
1674
1909
|
if (pathname === "/gate/health") return handleHealthGuarded(req, res);
|
|
1675
1910
|
if (pathname === "/gate/selfcheck") return handleSelfcheckGuarded(req, res);
|
|
1911
|
+
if (pathname === "/gate/ui.js") return handleUiJs(req, res);
|
|
1676
1912
|
if (pathname === "/setup") return handleSetup(req, res);
|
|
1677
1913
|
if (!loadAdmin()) {
|
|
1678
1914
|
// 尚未完成初始设置:浏览器导航导向导,/api 保持 401
|
|
@@ -1690,10 +1926,12 @@ function main() {
|
|
|
1690
1926
|
sendText(res, 503, "gate not configured: admin account missing (run `dsh-vps reset-admin`)");
|
|
1691
1927
|
return;
|
|
1692
1928
|
}
|
|
1693
|
-
|
|
1929
|
+
const user = sessionUser(req);
|
|
1930
|
+
if (!user) {
|
|
1694
1931
|
denyUnauthenticated(req, res, pathname);
|
|
1695
1932
|
return;
|
|
1696
1933
|
}
|
|
1934
|
+
if (pathname === "/gate/update") return handleUpdate(req, res, user);
|
|
1697
1935
|
if (pathname.startsWith(MARKET_PREFIX) && !marketRequestSameOrigin(req)) {
|
|
1698
1936
|
sendText(res, 403, "cross-origin market request refused by gate");
|
|
1699
1937
|
return;
|
|
@@ -1715,6 +1953,10 @@ function main() {
|
|
|
1715
1953
|
|
|
1716
1954
|
spawnDshWithPreflight();
|
|
1717
1955
|
|
|
1956
|
+
// DSH 新版本检测:启动 1 分钟后查一次,之后每 6 小时一次
|
|
1957
|
+
setTimeout(() => checkDshLatest().catch(() => {}), 60_000).unref();
|
|
1958
|
+
setInterval(() => checkDshLatest().catch(() => {}), UPDATE_CHECK_INTERVAL_MS).unref();
|
|
1959
|
+
|
|
1718
1960
|
// DSH Cookie 续期:剩余有效期 < 24h 时用同一 launchToken 重新兑换
|
|
1719
1961
|
setInterval(() => {
|
|
1720
1962
|
if (dsh.cookie && dsh.cookie.expiresAt <= Date.now()) {
|
package/install.sh
CHANGED
|
@@ -16,7 +16,7 @@ set -euo pipefail
|
|
|
16
16
|
|
|
17
17
|
## region: 常量与参数
|
|
18
18
|
|
|
19
|
-
DSH_VERSION="0.1.
|
|
19
|
+
DSH_VERSION="0.1.7-rc.1" # 钉住版本(设计文档 §10 已验证版本表,勿随意改)
|
|
20
20
|
# 只钉顶层包版本是不够的:DSH 各子包的依赖是 ^0.1.5-rc.2 这类浮动范围,上游一发新的
|
|
21
21
|
# 预发布波次,解析结果就整体漂上去。2026-09-22 上游发了 0.1.5-rc.3 波次,但漏发了
|
|
22
22
|
# dsh-client-ui-sidebar-documentpreview,于是 ETARGET 装不上。用 --before 把解析冻结在
|
|
@@ -24,8 +24,9 @@ DSH_VERSION="0.1.5-rc.2" # 钉住版本(设计文档 §10 已验证版本表
|
|
|
24
24
|
# 注意:同一天 03:46 上游还先发了一批 cordis 系列(cordis 4.0.3 / cordis-plugin-hmr 1.0.18 等),
|
|
25
25
|
# 与 rc.2 不兼容——web profile 启动即报 "user patch-layer watching requires the Cordis HMR service"。
|
|
26
26
|
# 冻结点必须早于这一批,所以是 03:40 而不是 05:00。
|
|
27
|
+
# 之后升到 0.1.7-rc.1(npm next 渠道),冻结点 2026-09-24T08:20Z——该时刻解析出的依赖树已逐包比对、实测通过。
|
|
27
28
|
# 设为 none 可关闭冻结。
|
|
28
|
-
DSH_RESOLVE_BEFORE="${DSH_RESOLVE_BEFORE:-2026-09-
|
|
29
|
+
DSH_RESOLVE_BEFORE="${DSH_RESOLVE_BEFORE:-2026-09-24T08:20:00Z}"
|
|
29
30
|
INSTALL_ROOT="/opt/dsh-vps"
|
|
30
31
|
DSH_USER="dsh"
|
|
31
32
|
DSH_HOME_DIR="/home/dsh/.dsh"
|
|
@@ -391,6 +392,8 @@ EOF
|
|
|
391
392
|
systemctl enable dsh-gate >/dev/null 2>&1
|
|
392
393
|
systemctl restart dsh-gate
|
|
393
394
|
log "dsh-gate.service 已启动(DSH_TRUSTED_HOST=${trusted})"
|
|
395
|
+
# 浏览器一键升级:DSH 有新版本时页面提示,确认后由 root 服务执行升级
|
|
396
|
+
"$INSTALL_ROOT/bin/dsh-vps" install-units || warn "一键升级单元安装失败(不影响使用,可稍后 sudo dsh-vps install-units)"
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
## endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Ship DeepSeek Harness to your own VPS: one command installs a zero-dependency login gateway, automatic HTTPS through Caddy, systemd supervision, and optional WireGuard-only access.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
package/uninstall.sh
CHANGED
|
@@ -108,6 +108,9 @@ log "步骤 2/6:停止并移除 $SERVICE"
|
|
|
108
108
|
systemctl stop "$SERVICE" 2>/dev/null || true
|
|
109
109
|
systemctl disable "$SERVICE" 2>/dev/null || true
|
|
110
110
|
rm -f "$UNIT_FILE"
|
|
111
|
+
# 浏览器一键升级单元
|
|
112
|
+
systemctl disable --now dsh-vps-upgrade.path 2>/dev/null || true
|
|
113
|
+
rm -f /etc/systemd/system/dsh-vps-upgrade.path /etc/systemd/system/dsh-vps-upgrade.service
|
|
111
114
|
systemctl daemon-reload 2>/dev/null || true
|
|
112
115
|
systemctl reset-failed "$SERVICE" 2>/dev/null || true
|
|
113
116
|
|
package/versions.json
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
"latest_verified": "0.1.
|
|
3
|
-
"resolve_before": "2026-09-
|
|
2
|
+
"latest_verified": "0.1.7-rc.1",
|
|
3
|
+
"resolve_before": "2026-09-24T08:20:00Z",
|
|
4
4
|
"updated_at": "2026-09-24",
|
|
5
5
|
"versions": [
|
|
6
|
+
{
|
|
7
|
+
"dsh": "0.1.7-rc.1",
|
|
8
|
+
"min_gate": "1.5.0",
|
|
9
|
+
"verified_at": "2026-09-24",
|
|
10
|
+
"resolve_before": "2026-09-24T08:20:00Z",
|
|
11
|
+
"notes": "npm next 渠道。会话兑换 / 向导写 API Key / 升级自检 / ownsHost(网页端逻辑未变)/ 插件市场经网关安装 全部实测通过"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"dsh": "0.1.5-rc.3",
|
|
15
|
+
"min_gate": "1.4.4",
|
|
16
|
+
"verified_at": "2026-09-24",
|
|
17
|
+
"resolve_before": "2026-09-24T08:00:00Z",
|
|
18
|
+
"notes": "会话兑换 / 向导写 API Key / 升级自检 / ownsHost 补丁 / 插件市场经网关安装 全部实测通过"
|
|
19
|
+
},
|
|
6
20
|
{
|
|
7
21
|
"dsh": "0.1.5-rc.2",
|
|
8
22
|
"min_gate": "0.1.0",
|