dsh-desktop-linux
Linux desktop shell for DeepSeek Harness — standalone window, tray LED (done/ask/fail), 60fps breathing bar, self-signed cookie auth, autostart, GitHub auto-update
- Stars
- 0
- Language
- JavaScript
- Created
- Sep 17, 2026
- Updated
- Sep 18, 2026
Introduction
DSH Desktop (Linux)
DeepSeek Harness 的 Linux 桌面客户端 — 独立窗口 · 托盘 · 开机自启 · 自动更新
它是什么
dsh-desktop-linux 是 DeepSeek Harness
Web GUI 的 Electron 桌面壳。它把 DSH Web 服务 (http://127.0.0.1:3080) 装进一个独立的桌面窗口,
提供:
- 🪟 独立窗口 + 系统托盘
- 🎨 托盘图标三套皮肤(蓝/黑/白),右键菜单切换
- 🔔 任务状态信号 → 托盘光条(完成 / 提问 / 失败)
- 🚀 开机自启(XDG autostart)
- 🔐 自动认证:免 token 打开 DSH Web(自签名 cookie)
- 🔄 自动更新:检测 GitHub Releases 后台静默升级
一键安装(推荐)
一行命令装好所有依赖(DSH 后端 + GUI 客户端插件 + 可选 systemd 守护
- 可选开机自启 + 首次启动落 credentials):
curl -fsSL https://raw.githubusercontent.com/TerebiSAMA/dsh-desktop-linux/main/install.sh | bash -s -- --with-systemd
可选 flag:
| flag | 作用 |
|---|---|
--with-systemd | 安装并启用 systemd 用户单元(DSH Web 后台守护 + 失败自动重启) |
--with-autostart | 安装 XDG autostart(开机启动桌面端) |
--no-launch | 不自动启动 dsh web(自己控制时机) |
跑完后用对应的方式启动桌面端(双击图标 / ./DSH-Desktop-*.AppImage
/ sudo dnf install ./...rpm —— 见下文"安装"章节)。脚本可以重复跑,
每一步都做幂等检查,不会重复覆盖。
安装成功后桌面会自动生成一个图标(~/Desktop/dsh-desktop-linux.desktop,
Exec 字段会自动检测 /opt/...、/usr/bin/...、AppImage 等常见安装路径)——
headless 服务器(没有 ~/Desktop)会跳过这步,其他平台图标都可正常启动。
启动流程(傻瓜式)
双击图标后,桌面端会按下面顺序自动把一切准备好:
- 探测
http://127.0.0.1:3080,通了就直接进 GUI - 没通 → 试
systemctl --user start dsh-web(你装了用户单元的话) - 还起不来 → 在 PATH 里找
dsh - 没有
dsh→ 跑npm install -g @deepseek-ai/dsh(失败会试pkexec提权安装) - 装好后跑
dsh web,轮询等待端口就绪 - 都失败 → 错误页显示手动安装步骤和最近错误日志,可以一键复制
前提:系统里有 node 和 npm(提权安装还需要 pkexec,多数发行版自带)。
可选:预先手动准备
桌面端本身是壳,但会自动安装和启动后端,所以通常不需要任何手工操作。 如果你想接管:
- 从 npm 安装并跑
dsh webnpm install -g @deepseek-ai/dsh dsh web - 或从源码树跑(参考上游
deepseek-ai/deepseek-harness文档) - 或用 systemd 用户单元持续托管(见
extra/systemd/dsh-web.service)
首次启动前请确保 dsh web 至少启动过一次 —— 它会在 ~/.dsh/.credentials.yaml 里写入
浏览器会话密钥,桌面端靠它免 token 认证。
安装
当前仓库未发布预编译包,请从源码运行(CI 已就绪,会在未来出 AppImage / deb / rpm 时自动发到 Releases 页面)。
从源码运行(推荐)
git clone https://github.com/TerebiSAMA/dsh-desktop-linux.git
cd dsh-desktop-linux
npm install
npm start
npm start 会启动 Electron 开发者模式;GUI 修改会热重载,但主进程
改动需手动重启。
自己打包(可选)
npm run pack # 只解包运行,不出安装包
npm run dist # 出 AppImage / deb / rpm 到 dist/
npm run dist 依赖 electron-builder 在本机工具链(rpm-build / fakeroot 等)。
想体验预编译包可以在自己的 fork 里跑 Actions 工作流 .github/workflows/release.yml,
产物会出现在 Release 页面。
局域网访问(把 dsh 从局域网内暴露给其他机器)
DSH 上游默认只绑 127.0.0.1:3080,且 npm 缓存版本硬编码拒绝绑定 0.0.0.0。
本仓库提供了一个零依赖 Node 反向代理(tools/dsh-lan-proxy.js),
监听 0.0.0.0:<LAN_PORT>(默认 5080),把请求转发到本机 127.0.0.1:3080,
并自己重新种 cookie——因为 dsh 的浏览器 cookie 是按 Host 头签的,
直接转发会被 host fence 挡掉。
安装(systemd 用户单元)
把代理脚本和 token watcher 装到系统:
# 把代理和 token watcher 放到标准位置
sudo install -m755 tools/dsh-lan-proxy.js /usr/local/lib/dsh-desktop-linux/dsh-lan-proxy.js
sudo install -m755 tools/dsh-lan-proxy-env.sh /usr/local/bin/dsh-lan-proxy-env
sudo mkdir -p /usr/local/lib/dsh-desktop-linux
# 把 systemd 单元装到用户目录
mkdir -p ~/.config/systemd/user
cp extra/systemd/dsh-lan-proxy.service ~/.config/systemd/user/
cp extra/systemd/dsh-lan-proxy-env.service ~/.config/systemd/user/
# 改两个占位符:DSH_LAN_HOST、DSH_ALLOWED_REMOTE
$EDITOR ~/.config/systemd/user/dsh-lan-proxy.service
# Environment=DSH_LAN_HOST=192.168.x.y ← 这台机器的 LAN IP
# Environment=DSH_ALLOWED_REMOTE=192.168.x.z ← 允许访问的客户端 IP(逗号分隔)
# 让 dsh 信任 LAN 端的来源(drop-in)
mkdir -p ~/.config/systemd/user/dsh-web.service.d
cp extra/systemd/dsh-web.service.d/public-bind.conf.example \
~/.config/systemd/user/dsh-web.service.d/public-bind.conf
$EDITOR ~/.config/systemd/user/dsh-web.service.d/public-bind.conf
# 把 <LAN_IP> 替换成这台机器的 LAN IP(必须和 DSH_LAN_HOST 一致)
systemctl --user daemon-reload
systemctl --user enable --now dsh-lan-proxy-env.service
systemctl --user enable --now dsh-lan-proxy.service
systemctl --user enable --now dsh-web.service # 已装过的就 restart
firewalld 限制访问源 IP(强烈推荐)
只允许上面 DSH_ALLOWED_REMOTE 里的客户端访问 5080:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.x.z" port port="5080" protocol="tcp" accept'
sudo firewall-cmd --reload
不放心可以再加一条拒绝规则兜底(先接受后拒绝 → 默认拒绝):
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="5080" protocol="tcp" reject'
sudo firewall-cmd --reload
工作原理(一句话)
代理监听 0.0.0.0:5080 → 第一次接到请求时,代理内部用 Host: <LAN_IP>:5080
头去 127.0.0.1:3080/?token=<token> 让 dsh 自己签一个 cookie → 缓存它,
后续请求都注入这个 cookie 并把 Host/Origin/Referer 重写到 LAN authority
→ dsh 看到的就像"同一台机器的同一浏览器会话"。/api/remote.mux 的 WebSocket
升级 101 响应完整透传。dsh-web 重启换 token 时,dsh-lan-proxy-env.service
从 systemd journal 抓新 token 写到 /run/user/<uid>/dsh-lan-proxy.env,
代理下次请求会自动重新种 cookie,无需人工介入。
配置参数
代理通过环境变量配置(systemd 单元的 Environment= 行):
| 变量 | 默认 | 说明 |
|---|---|---|
DSH_LAN_HOST | 127.0.0.1 | LAN 端看到的 IP(必填为你机器的 LAN IP) |
DSH_LAN_PORT | 5080 | LAN 端口 |
DSH_UPSTREAM | http://127.0.0.1:3080 | dsh 上游 |
DSH_TOKEN_FILE | /run/user/%U/dsh-lan-proxy.env | token 文件(由 watcher 维护) |
DSH_COOKIE_TTL_MS | 600000 | cookie 复用窗口(10 分钟) |
DSH_ALLOWED_REMOTE | "" | 逗号分隔允许的客户端 IP;空 = 全允许(务必配置) |
DSH_ALLOW_FILE | "" | 备选白名单文件路径(每行一个 IP),GUI 编辑器用的就是这个 |
在 GUI 设置里管理 IP 白名单
仓库里的 plugins/dsh-client-ui-lan-allowlist/ 是一个 web 客户端插件,
装上后在 DSH GUI 设置 → "LAN 访问 — IP 白名单" 分区(位置在"插件商店"
下方)会出现一个文本框:
- 每行一个 IPv4 / IPv6 / CIDR(
#开头是注释) - "保存"按钮写入
~/.dsh/profiles/web/dsh-lan-proxy-allow.txt - 代理每 2s 检测这个文件,保存后 ≤2s 自动生效,不用重启服务
- "重启代理"按钮可立即生效(
systemctl --user restart dsh-lan-proxy.service)
这个白名单和 DSH_ALLOWED_REMOTE(systemd 单元硬编码)叠加:任一文件
匹配就放行。GUI 适合日常增删,systemd 变量适合"长期固定"白名单。
安装:
mkdir -p ~/.dsh/profiles/web/node_modules/@deepseek-ai/dsh-client-ui-lan-allowlist/lib
cp plugins/dsh-client-ui-lan-allowlist/package.json \
~/.dsh/profiles/web/node_modules/@deepseek-ai/dsh-client-ui-lan-allowlist/
cp plugins/dsh-client-ui-lan-allowlist/lib/*.js \
~/.dsh/profiles/web/node_modules/@deepseek-ai/dsh-client-ui-lan-allowlist/lib/
# 在 cordis.patch.yml 追加:
# - insert:
# - id: ui-lan-allowlist
# name: '@deepseek-ai/dsh-client-ui-lan-allowlist'
需要桌面端 v0.2.1+(preload 暴露了 dshDesktop.lanAllowlist / restartLanProxy
IPC,老版本会调用失败)。
安全提醒
- 这个代理不处理 HTTPS——公网/不受信任的网段使用前请套一层 TLS 终结 (caddy / nginx / stunnel),否则 cookie 和会话内容明文传输。
DSH_ALLOWED_REMOTE必须配置——空字符串等于放行所有 IP。- firewalld 是第二道防线,强烈推荐配合 IP 白名单一起用。
- 代理不会修改 dsh 数据,只是转发;如果局域网里有人滥用,删除其
DSH_ALLOWED_REMOTE条目即可立即断供。
自动更新
桌面端启动后会定期检查 GitHub Releases,发现新版本会在托盘弹通知,
并在下次重启时自动升级(electron-updater)。
若想关闭自动更新:托盘菜单没有提供开关,当前版本需要在退出前断网。 后续版本会加 UI 开关。
开发
git clone https://github.com/TerebiSAMA/dsh-desktop-linux.git
cd dsh-desktop-linux
npm install
npm start
构建本地包:
npm run pack # 不打包成安装包,只解包运行
npm run dist # 出 AppImage + deb + rpm 到 dist/
项目结构
src/
main.js # 主进程(窗口、托盘、自启、自认证 cookie)
preload.js # 渲染进程桥(白名单 IPC)
error.html # 服务失联时的兜底页
assets/
*.png # 应用图标 + 托盘各状态帧
tools/
gen-tray-assets.py # 托盘图标生成脚本(蓝/黑/白 × 状态 × DPI)
plugins/ # DSH GUI 客户端插件备份(非桌面端代码,仅作参考/重装)
install.sh # 一键安装脚本(后端 + 插件 + systemd + autostart)
.github/workflows/
release.yml # 出 AppImage / deb / rpm
extra/
systemd/ # 用户级 systemd 单元模板(可选)
autostart/ # XDG autostart 模板(可选)
常见问题
双击图标弹出"systemctl --user status dsh-web"
意味着 DSH Web 服务没在运行。检查:
systemctl --user status dsh-web # 如果装了单元
curl http://127.0.0.1:3080/ # 直接探活
如果服务未启动但 npm 装了 @deepseek-ai/dsh,直接跑 dsh web 即可。
桌面端也会在探测失败时自动尝试拉起服务(systemd 单元 → PATH 兜底)。
自认证 cookie 没生效
打开桌面前需要先手动跑过至少一次 dsh web,让浏览器会话密钥落地到
~/.dsh/.credentials.yaml。如果密钥丢了,删这个文件后重启 dsh web 即可。
服务启动时把默认浏览器也打开了
如果是用上游 npm 包直接 dsh web 起的,可以在服务文件里加 --no-open,
桌面端就不会跟你的 Firefox 抢焦点了。extra/systemd/dsh-web.service
已经默认带上了 --no-open。
协议
MIT — see LICENSE.