diff --git a/shell/dsh_service.md b/shell/dsh_service.md new file mode 100644 index 0000000..4069196 --- /dev/null +++ b/shell/dsh_service.md @@ -0,0 +1,212 @@ +# 将 `npx @deepseek-ai/dsh web` 以系统服务方式启动 + +> 目标:让 DeepSeek Harness Web GUI(默认 `http://127.0.0.1:3080`)随系统自动启动、后台常驻、崩溃可自愈。 +> 本文档含完整步骤、脚本与三种方案(Windows 任务计划程序 / NSSM 服务 / Linux systemd)。 + +## 0. 背景事实(已核对源码) + +- `dsh web` 是 `dsh --profile web` 的别名;它只启动一个前台 HTTP 服务,**不会自动打开浏览器**,适合作为服务运行。 +- 应用参数只有三个: + - `--host `:仅允许 `127.0.0.1`(`0.0.0.0` 被故意拒绝,防远程代码执行); + - `--port `:默认 `3080`,传 `0` 由系统分配; + - `--trusted-host `:浏览器信任白名单,可重复。 +- 进程的**当前工作目录(cwd)就是默认 workspace 根**(sandbox 的 `workspaceRoot`),服务必须显式设置工作目录。 +- 数据根目录默认是 `~/.dsh`(按账户 homedir 计算)。服务账户不同 → `DSH_HOME` 不同 → 凭据/会话/设置全变,务必显式设置 `DSH_HOME`。 +- 凭据来源:`DEEPSEEK_API_KEY` 环境变量,或 `$DSH_HOME/.credentials.yaml`(Web「设置 → Models」页面写入的那个)。 +- 服务进程无终端,权限审批(approval)会挂在 Web 界面里等用户点击,属正常行为。 + +## 1. 核心原则 + +1. **不要直接把 `npx` 塞进服务**:`npx` 走带哈希的 npm 缓存,服务账户下 PATH/缓存经常对不上。先全局安装,再用 `node.exe` 直接调 CLI 入口。 +2. **工作目录必须设**:设为实际 workspace 根(如 `D:\DSH`)。 +3. **显式设置 `DSH_HOME`**:指向用户现有 `~/.dsh` 并确保服务账户可读写,或让服务以用户账户运行。 +4. **准备凭据**:`DEEPSEEK_API_KEY` 环境变量或 `$DSH_HOME/.credentials.yaml`。 + +### 本机路径表 + +| 项 | 路径 | +|---|---| +| node.exe | `D:\sdk\nodejs\node.exe` | +| 全局 npm 前缀 | `D:\sdk\nodejs` | +| 全局包入口 | `D:\dsh-service\global\node_modules\@deepseek-ai\dsh\lib\bin.js`(离线副本,含 `D:\dsh-service\global\dsh.cmd` shim) | +| 默认 DSH_HOME | `C:\Users\cnphp\.dsh` | +| 服务目录 | `D:\dsh-service\`(启动脚本、systemd 参考文件、日志) | + +### 前置准备(管理员 PowerShell) + +```powershell +# 方式一:正常全局安装(本机代理下可能失败,见常见坑 6) +npm install -g @deepseek-ai/dsh + +# 方式二:使用离线副本(本机已就绪,与当前 GUI 同版本 0.1.0-rc.6) +D:\dsh-service\global\dsh.cmd web --help # 验证可运行 +``` + +## 2. 方案 A:Windows 任务计划程序(最简单,无需额外工具) + +适合「开机/登录后自动跑」。`-AtStartup` 需要管理员;`-AtLogOn` 不需要。 + +### 步骤 + +1. 保存启动脚本 `D:\dsh-service\start-dsh-web.cmd`(内容见第 5 节,内含环境变量与端口守护)。 +2. 在管理员(或普通用户)PowerShell 执行下面的注册命令。 +3. `Start-ScheduledTask -TaskName DSHWeb` 立即测试。 + +### 注册脚本(登录后自动运行,交互式账户,无需密码) + +```powershell +$action = New-ScheduledTaskAction -Execute "cmd.exe" ` + -Argument '/c "D:\dsh-service\start-dsh-web.cmd"' ` + -WorkingDirectory "D:\DSH" +$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME" +$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) ` + -ExecutionTimeLimit 0 -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries +$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited +Register-ScheduledTask -TaskName "DSHWeb" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force +Start-ScheduledTask -TaskName "DSHWeb" +``` + +### 注册脚本(开机即起、不登录也运行,管理员) + +需要用户密码(`-LogonType Password`)或以 SYSTEM 运行(`-LogonType ServiceAccount -UserId SYSTEM`,此时 `DSH_HOME` 必须显式指向用户 `.dsh` 且 SYSTEM 有读写权限——本机已具备): + +```powershell +$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest +Register-ScheduledTask -TaskName "DSHWeb" -Action $action -Trigger (New-ScheduledTaskTrigger -AtStartup) -Settings $settings -Principal $principal -Force +``` + +## 3. 方案 B:NSSM 真·Windows 服务(推荐:开机即起 + 崩溃自愈) + +### 步骤 + +1. 安装 NSSM(任选其一): + ```powershell + winget install NSSM.NSSM --accept-package-agreements --accept-source-agreements # 已在本机执行成功 + # 或官网下载 https://nssm.cc/release/nssm-2.24.zip 解压(nssm.cc 在本机网络下直连不稳,优先 winget) + ``` +2. 管理员 PowerShell 执行下方安装/配置命令(逐条)。 +3. `nssm start DSHWeb` 启动服务。 + +### 安装与配置命令 + +```powershell +nssm install DSHWeb "D:\sdk\nodejs\node.exe" "D:\dsh-service\global\node_modules\@deepseek-ai\dsh\lib\bin.js web --port 3080" +nssm set DSHWeb AppDirectory "D:\DSH" # workspace 根,必设 +nssm set DSHWeb AppEnvironmentExtra DSH_HOME=C:\Users\cnphp\.dsh DEEPSEEK_API_KEY=sk-你的key +nssm set DSHWeb AppStdout "D:\dsh-service\stdout.log" # 日志(含 URL 行) +nssm set DSHWeb AppStderr "D:\dsh-service\stderr.log" +nssm set DSHWeb AppExit Default Restart # 非零退出自动重启 +nssm set DSHWeb Start SERVICE_AUTO_START +nssm start DSHWeb +``` + +> 说明: +> - 默认以 LocalSystem 运行;本机 `C:\Users\cnphp\.dsh` 的 ACL 已含 `NT AUTHORITY\SYSTEM:(OI)(CI)(F)`,无需额外授权。 +> - 想以用户账户运行:`nssm set DSHWeb ObjectName ".\cnphp" "你的Windows密码"`(需要密码)。 +> - 与方案 A 同开时的端口冲突由 `start-dsh-web.cmd` 里的端口守护解决:谁先占 3080 谁服务,另一个静默退出,不会重启循环。 +> - 若 `DEEPSEEK_API_KEY` 未设,DSH 会从 `%DSH_HOME%\.credentials.yaml` 读取,可省略该环境变量。 + +## 4. 方案 C:Linux systemd + +参考文件已生成:`D:\dsh-service\dsh-web.service`。 + +```ini +# /etc/systemd/system/dsh-web.service +[Unit] +Description=DeepSeek Harness Web +After=network.target + +[Service] +Type=simple +User=youruser +WorkingDirectory=/path/to/workspace +Environment=DSH_HOME=/home/youruser/.dsh +# Environment=DEEPSEEK_API_KEY=sk-xxx +ExecStart=/usr/bin/node /usr/lib/node_modules/@deepseek-ai/dsh/lib/bin.js web --port 3080 +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +```bash +npm i -g @deepseek-ai/dsh +sudo cp dsh-web.service /etc/systemd/system/ +sudo systemctl enable --now dsh-web +``` + +## 5. 启动脚本 `D:\dsh-service\start-dsh-web.cmd`(完整内容) + +```cmd +@echo off +rem ============================================================================ +rem DeepSeek Harness Web - service launcher +rem Design: both the NSSM service and the scheduled task point at this script; +rem a port guard makes them coexist: whoever reaches port 3080 first serves it, +rem the other exits silently (0 = no restart loop). +rem ============================================================================ + +set DSH_HOME=C:\Users\cnphp\.dsh + +rem Optional: uncomment to force the key (default: loaded from %DSH_HOME%\.credentials.yaml) +rem set DEEPSEEK_API_KEY=sk-xxxx + +rem --- Port guard: if 3080 is already listening, exit quietly --- +powershell -NoProfile -ExecutionPolicy Bypass -Command "try { if (Get-NetTCPConnection -LocalPort 3080 -State Listen -ErrorAction Stop) { exit 0 } } catch { }" + +cd /d D:\DSH +"D:\sdk\nodejs\node.exe" "D:\dsh-service\global\node_modules\@deepseek-ai\dsh\lib\bin.js" web --port 3080 +``` + +> 若走 NSSM 直接跑 node(不经脚本),则端口守护缺失,需保证同一时刻只有一种方式在运行。 + +## 6. 验证 + +```powershell +Get-Service DSHWeb # 或 nssm status DSHWeb +Get-ScheduledTask -TaskName DSHWeb # State = Ready +Invoke-WebRequest http://127.0.0.1:3080 -UseBasicParsing # 返回 200 即 OK +Get-Content D:\dsh-service\stdout.log -Tail 20 # 应看到 "dsh web: http://127.0.0.1:3080" +``` + +## 7. 维护 / 卸载 + +```powershell +# 方案 B(NSSM) +nssm restart DSHWeb # 重启 +nssm stop DSHWeb; nssm remove DSHWeb confirm # 卸载 + +# 方案 A(任务计划程序) +Stop-ScheduledTask -TaskName DSHWeb +Unregister-ScheduledTask -TaskName DSHWeb +``` + +## 8. 常见坑 + +1. **端口冲突**:不要和正在运行的 npx 实例同时占用 3080(服务监听失败会以非零状态退出)。先停旧实例(`Get-Process node` 排查),或重启机器后服务自动接管。 +2. **工作目录**:不设置时 DSH 把 cwd 当 workspace 根,可能落到 `C:\Windows\System32`,务必设置 `AppDirectory` / `WorkingDirectory`。 +3. **账户与 DSH_HOME**:服务账户改变会导致 `~/.dsh` 换位置,凭据、会话、设置全部"消失"。要么显式 `DSH_HOME`,要么服务跑在用户账户下。 +4. **局域网访问**:`--host 0.0.0.0` 被 DSH 故意拒绝;需要局域网访问时保持 `127.0.0.1` 并用 `--trusted-host` 加白名单。 +5. **权限审批**:服务无终端,approval 会挂在 Web 界面里等待点击,不是故障。 +6. **npm 全局安装失败(`Exit handler never called!` / ECONNRESET)**:本机 npm 使用 `registry.npmmirror.com` 镜像,但环境变量 `HTTPS_PROXY=socks5://127.0.0.1:7890`——**npm 不支持 socks5 协议**,会把镜像连接全部重置导致安装失败。修正:7890 是 mixed 端口(HTTP/SOCKS5 都收),改用 HTTP 协议代理再装: + ```powershell + $env:HTTP_PROXY='http://127.0.0.1:7890'; $env:HTTPS_PROXY='http://127.0.0.1:7890' + npm install -g @deepseek-ai/dsh + ``` + (直连 npmmirror 在本机也不通,必须走代理。) + **兜底方案(本机已采用)**:`npm install -g` 在代理下反复崩溃(`Exit handler never called!` —— 单包 tarball 下载正常,npm 并发拉取时代理必崩)。把已完整可用的 npx 缓存复制到稳定目录即可离线使用: + ```powershell + robocopy "$env:LOCALAPPDATA\npm-cache\_npx\\node_modules" 'D:\dsh-service\global\node_modules' /E + # 验证: + "D:\sdk\nodejs\node.exe" 'D:\dsh-service\global\node_modules\@deepseek-ai\dsh\lib\bin.js' web --help + ``` + +## 附录:本机当前执行状态(2026-xx-xx 会话) + +- ✅ `D:\dsh-service\start-dsh-web.cmd` 已生成 +- ✅ `D:\dsh-service\dsh-web.service`(Linux 参考)已生成 +- ✅ 方案 A:任务计划程序 `DSHWeb` 已注册(AtLogOn 交互式,State=Ready) +- ✅ 方案 B:NSSM 已通过 `winget install NSSM.NSSM` 安装(命令别名 `nssm` 已添加) +- ✅ 全局 CLI 已就绪:`D:\dsh-service\global\node_modules\@deepseek-ai\dsh\lib\bin.js`(离线复制自 npx 缓存,版本 0.1.0-rc.6,已验证 `web --help` 正常;`npm install -g` 两次均因代理问题失败,见常见坑 6) +- ⏸ 端口 3080 当前被会话中的 npx 实例占用,服务/任务注册后未启动;下次开机(无 npx 实例)由 NSSM 或任务自动接管 +- 备注:nssm.cc 在本机网络(socks5 代理 127.0.0.1:7890)下直连下载被重置,请用 winget 方式安装 NSSM