在绿联 NAS 上部署 DeepSeek DSH

最近给绿联 DXP4800 Plus 装上了 DeepSeek DSH,也把这套部署配置整理成了一个独立仓库:SenLief/dsh-nas。

这次想留下的,除了安装命令,还有把一个默认面向本机使用的工具放到 NAS 上之后,需要处理的几个细节:浏览器怎么从局域网访问、模型设置能不能保存、项目文件落在哪个目录,以及手机打开后是否方便操作。

NAS 上运行的是什么

DSH 是 DeepSeek Harness。这里部署的是官方 npm 包 @deepseek-ai/dsh 提供的 Web 服务,模型通过 API 调用。NAS 负责运行服务、保存配置和工作文件,安装过程不会下载或在 NAS 上运行 DeepSeek 模型权重。

仓库记录的已验证版本是 0.2.0-rc.2,镜像面向 linux/amd64 构建。本文涉及的参数限制和补丁也围绕这个版本展开,后续版本应以仓库更新为准。

整个部署过程可以理解为:GitHub Actions 安装官方 npm 包、应用 NAS 适配补丁并构建镜像,验证通过后推送到 GHCR;NAS 再通过 Docker Compose 拉取运行。这样,主程序和依赖的安装留在构建阶段,日常重启只需要启动已有容器。

先把环境封装进镜像

镜像基于 Node 24 和 Debian,使用多阶段构建。在构建阶段安装 DSH,并准备 native 依赖可能需要的编译工具;运行阶段保留 bash、git、curl、Python 3、ripgrep、bubblewrap 和 pnpm 等工具。

容器以 UID/GID 为 1000:1000 的 node 用户运行。Compose 还配置了健康检查、日志轮转、自动重启,并移除了额外 Linux capabilities。

这里有一个容易忽略的区别:镜像中带有这些基础工具,不代表所有扩展都已经具备运行条件。Office/PDF 转换、浏览器自动化等功能可能还需要补充依赖,Linux 命令沙箱能否正常使用也取决于 NAS 内核和 Docker 的限制。

具体构建配置放在仓库的 Dockerfile 中。

在 NAS 上用 Compose 启动

使用已经构建好的镜像时,NAS 侧只需要准备仓库中的 compose.yaml 和 .env.example。把它们放进专用的应用目录,然后复制环境变量模板:

cp .env.example .env

我的配置以局域网访问为前提,参数如下。其中 IP 是示例,需要替换为 NAS 的真实地址:

DSH_IMAGE=ghcr.io/senlief/dsh-nas:latest
NAS_IP=192.168.1.20
DSH_PORT=3080
DSH_DISABLE_BROWSER_AUTH=1
WORKSPACE_DIR=./workspace
DSH_EXTRA_TRUSTED_HOSTS=
TZ=Asia/Shanghai

NAS_IP 填 NAS 的局域网 IPv4,不填容器 IP,也不带 http://。Compose 会把服务端口发布到这个指定地址上。

WORKSPACE_DIR 是 NAS 上保存项目文件的位置。默认使用应用目录下的 workspace,也可以改为实际存在的绝对路径;无论宿主机路径如何变化,容器内统一使用 /workspace。

通过 SSH 进入应用目录,为新建的专用工作目录配置权限,再启动服务:

mkdir -p workspace
sudo chown 1000:1000 workspace
sudo chmod 700 workspace

docker compose config --quiet
docker compose pull
docker compose up -d
docker compose logs --tail=100 dsh

这里的授权命令只针对新建目录。如果使用已有 NAS 共享目录,应通过 UGOS 的权限或 ACL 管理给 UID 1000 写入权限,避免直接递归修改整个共享目录的所有权。如果修改了 WORKSPACE_DIR,也要对实际挂载的目录配置权限。

也可以在 UGOS Docker 应用中导入 Compose 项目,确保 .env 和 compose.yaml 位于同一个项目目录。完整配置见仓库的 compose.yaml。

服务启动后,在浏览器中打开:

http://192.168.1.20:3080/

进入“设置 → 模型”,填写自己的 DeepSeek API Key 并保存,然后选择工作区开始使用。密钥随配置保存在持久化用户目录中,无须放进 Compose,也不参与 GitHub 镜像构建。

从本机访问改到局域网访问

这次适配中比较关键的部分,是处理发布包对本机访问的假设。

首先是监听地址。0.2.0-rc.2 的 CLI 不接受常见的 --host 0.0.0.0 写法,而 Web 服务插件原始 schema 对地址也有限制。仓库在构建时调整相关校验,入口脚本在启动时查找容器的具体 IPv4,再将这个地址传给 DSH。

因此,日志里即使出现 127.0.0.1,浏览器仍应使用 NAS 的实际 IP 和映射端口访问。发布包固定打印的地址不等于局域网访问入口。

其次是网页登录。为了在内网直接打开页面,定制镜像加入了:

DSH_DISABLE_BROWSER_AUTH=1

这是本仓库通过补丁实现的开关,不是官方 npm 包原生提供的参数。Compose 默认开启免登录;直接运行镜像而不使用这份 Compose 时,镜像本身默认保留登录。

免登录意味着能访问这个端口的内网用户都可以操作 DSH,因此这套配置适用于可信局域网,不应直接将免登录端口映射到公网。补丁仍保留了 API 的 Host/Origin 校验。

如果需要恢复 token/Cookie 登录,把值改成 0,再运行:

docker compose up -d

首次访问时,从日志获取带 /?token=... 的链接,将地址换为 NAS 的 IP 和端口,并保留 token 即可。

页面打开后,模型设置还要能保存

另一个需要单独处理的问题,是通过 NAS IP 访问时的设置持久化。

在 0.2.0-rc.2 原始前端中,只有 localhost 或回环访问才使用服务器设置。换成 NAS IP 后,模型设置可能出现:

加载提供商目录失败: settings are unavailable in this browser

这类错误与 API Key 是否正确无关,原因在于前端选择设置存储方式的逻辑。

仓库补丁让受信任的局域网访问也使用服务器上的持久化配置,同时保留服务端的 Host/Origin 检查。这样,通过 NAS 地址设置的模型信息才可以保存,并在刷新页面后继续使用。

Compose 会自动信任 NAS_IP。如果另加访问域名,需要在 DSH_EXTRA_TRUSTED_HOSTS 中补充,多个值用逗号分隔,不带协议和路径;反向代理还应保留原始 Host,并转发 WebSocket。

相关修改集中在 patch-browser-auth.cjs。补丁会检查安装版本和目标代码结构,匹配失败时中止构建。

把项目文件和 DSH 自身数据分开

部署到 NAS 后,我希望项目文件能直接出现在指定目录中,方便查看和备份。因此,Compose 使用两处持久化存储:

内容 容器内位置 NAS 侧存储
配置、凭据、会话、插件和缓存 /home/node 命名卷 dsh_home
项目实际文件 /workspace WORKSPACE_DIR 指定的目录

只设置 Docker 的 WORKDIR=/workspace 还不够。原始版本的目录选择器仍默认打开用户主目录,首次自动创建工作区也有自己的路径逻辑。

仓库通过补丁和 DSH_WORKSPACE_ROOT=/workspace 统一这两个入口。首次自动工作区位于 /workspace/default-workspace;新建 /workspace/my-project 时,对应文件就会出现在 NAS 的 workspace/my-project 中。

用户主目录仍然保留为 /home/node,让配置、凭据和缓存继续留在命名卷内。

如果旧工作区已经建在 /home/node/项目名,升级不会自动搬动文件。应先停止相关任务,复制具体项目目录到工作目录、配置权限,再在页面中添加新的 /workspace/项目名。已有会话仍关联旧路径,需要单独留意。

顺手补上手机布局

既然服务常驻在 NAS 上,手机浏览器也是一个自然的访问入口。仓库为宽度不超过 768px 的页面加入了移动端适配:

  • 左侧导航改为默认收起的抽屉,为主界面留出空间。
  • 顶部统一放置左抽屉、新建会话、搜索和右抽屉入口。
  • 设置页使用全屏单列布局,分类放到顶部横向切换。
  • 工作区菜单居中,并根据可视视口变化调整布局,照顾软键盘弹出后的输入区域。

下面两张图来自仓库的独立测试环境,展示的是应用补丁后的实际前端页面,未配置真实模型密钥。

DSH 移动端主界面:顶部工具栏与居中的输入区域

DSH 移动端设置:顶部分类导航与单列内容布局

这些修改由构建时的前端补丁和 CSS 注入,桌面端继续使用上游布局。Actions 会检查多种手机宽度、桌面设置页、抽屉交互及模拟软键盘视口变化;真实 iOS、Android 设备上的键盘和浏览器行为仍需要在设备上确认。

更多页面可查看仓库的手机界面说明。

镜像自动跟进,NAS 手动更新

为了减少后续重复构建的工作,仓库配置了每 6 小时检查一次官方 npm latest 标签。

定时任务先解析出精确版本,再与 GHCR 当前 latest 镜像记录的版本比较。版本相同就跳过构建;版本变化或镜像不存在时,才构建并验证。这里跟随的是 npm 发布者设置的 latest 标签,其中也可能包含预发布版本。

验证包含免登录和 token 登录、健康检查、native 终端、工作区路径、局域网模型设置读写和持久化,以及手机布局等内容。设置测试使用无效测试密钥,不调用真实模型服务。补丁或测试失败时停止发布,保留此前成功的镜像。具体流程见 GitHub Actions 工作流。

自动发布镜像不会自动更新 NAS 上的容器。 NAS 侧仍由自己选择更新时间:

docker compose pull
docker compose up -d

前端补丁更新后,如果需要强制重建容器,可以执行:

docker compose up -d --force-recreate

随后强制刷新浏览器,避免继续使用旧的前端资源。

备份时应先停止服务,同时备份 dsh_home 命名卷和工作目录。尤其要记住:docker compose down -v 会删除命名卷,其中包括配置和凭据。

这次整理成仓库后,安装、适配和更新都有了可以回看的依据。以后重装 NAS 或调整目录时,可以从同一套配置重新部署;遇到上游变化,也能沿着补丁和验证流程继续维护。

项目地址:SenLief/dsh-nas。