dsh-plugin-autosshfs
DeepSeek Harness 插件:用 sshfs 在 Linux / macOS 上挂载、卸载远端目录。
把 nrs-tools/lib/autosshfs.js 的 sshfs 挂载 / 卸载能力做成 DeepSeek Harness 插件:
入口是左侧栏的全局面板按钮(与「插件」「自动化任务」同一排),点击后在整个主界面打开独立页面
(不是设置页);点该行的固定按钮还能把它收进「更多」菜单。同一组操作以 autosshfs 工具暴露给 Agent。
- 配置文件仍然是
~/.autosshfs.json(可用AUTOSSHFS_CONFIG覆盖),位置与格式都不变, 与命令行版autosshfs共用同一份数据。 - 支持 Linux 与 macOS(Windows 不支持;没有 sshfs 的 macOS 环境会给出安装指引)。
- 所有操作(列表、挂载状态、增删改、挂载/卸载、依赖检测与自动安装)都在界面上完成, Agent 工具调用的是同一套逻辑。
功能
| 功能 | 界面 | Agent 工具 autosshfs |
|---|---|---|
| 查看全部配置项与实时挂载状态 | ✅ | action: "state" |
| 新增 / 编辑 / 删除配置项 | ✅ | add / update / remove |
| 启用 / 停用单项 | ✅ | enable |
| 挂载 / 卸载单项 | ✅ | mount / umount + name |
| 全部挂载 / 全部卸载 | ✅ | mount / umount(不带 name) |
| 查看单项详情(挂载来源、挂载选项等) | ✅ | detail |
| 系统依赖检测与自动安装 | ✅ | check(install 仅限界面,避免 Agent 改动机器权限) |
挂载选项与命令行版一致:port、IdentityFile、password_stdin(配置里有 password 时)、
allow_other + default_permissions、uid / gid、extraOptions。
相对命令行版的两处加固:
- 不再拼接 shell 字符串。所有命令以 argv 数组直接执行,主机名、路径、密码中的任何字符 都不会被 shell 解释。
- 挂载状态按挂载表精确匹配挂载点(Linux 读
/proc/self/mounts,macOS 解析mount), 不再用mount输出里includes(path)的子串比较,/mnt/a不会误判/mnt/ab。
此外:配置写入是「临时文件 + rename」的原子写,文件权限固定为 0600(配置里可能有密码)。
安装
这是一个纯 JavaScript 的 Harness bundle(package.json 里的 dsh.bundle.patch + dsh.client),
没有构建步骤,index.js / client.js / host/ 就是可以直接运行的源码。
方式零:从 npm 安装(发布到 npm 之后可用)
plugin_manager(action: "install_bundle", target: "@shudingbo/dsh-plugin-autosshfs")
这是唯一能享受插件页「检查更新 / 一键升级」的安装方式(详见文末「发布」与「升级」两节)。
方式一:直接从 GitHub 安装(推荐,未发布 npm 时也能用)
在 Harness 会话里让 plugin_manager 从仓库安装:
plugin_manager(action: "install_bundle", target: "github:shudingbo/dsh-plugin-autosshfs")
也可以把同一串 github:shudingbo/dsh-plugin-autosshfs 粘贴到 Harness Web 界面
插件 → 添加插件 的安装框里。安装结果里 application: applied 表示已生效。
本质上是 pnpm 的 git 依赖,所以在 profile 目录里手动安装等价于:
cd ~/.dsh/profiles/<profile>
pnpm add github:shudingbo/dsh-plugin-autosshfs
方式二:从本地目录安装(开发用)
git clone git@github.com:shudingbo/dsh-plugin-autosshfs.git
# 然后在 Harness 里:
plugin_manager(action: "install_bundle", target: "/绝对路径/dsh-plugin-autosshfs")
安装后
- 界面:左侧栏点击 Autosshfs 挂载 面板按钮,主界面会切换到独立挂载管理页
(
sidebar.panellist→main面板autosshfs)。不想让它常驻一行时,点该行右侧的固定按钮, 它会收进 更多 菜单(菜单里还带勾选标记,再点固定可放回)。 - 工具:Agent 可直接调用
autosshfs。 - 卸载:
plugin_manager(action: "remove_bundle", target: "@shudingbo/dsh-plugin-autosshfs")。
升级
GitHub 安装的插件没有"检查更新":插件页的版本区只对从 npm registry 安装的依赖开放
(Host 端 updatable 判定要求依赖是 registry spec),github: / link: 这类依赖会被判定为
不可更新,详情页不会出现「版本 / 检查更新」区块。DSH 本身也没有后台自动更新——任何更新都是
用户发起的一次操作。
更新 GitHub 安装的插件 = 让它重新解析一次 git 依赖(取默认分支的最新提交):
plugin_manager(action: "remove_bundle", target: "@shudingbo/dsh-plugin-autosshfs")
plugin_manager(action: "install_bundle", target: "github:shudingbo/dsh-plugin-autosshfs")
装完刷新页面即可;Host 侧模块改动需要重启 Harness 进程(或把插件禁用再启用)才会重新加载。 重新安装会按 bundle 默认重新选中并启用。
想控制升级节奏,可以固定到 tag 或提交(同样是先 remove_bundle,再用带 committish 的 spec 安装):
github:shudingbo/dsh-plugin-autosshfs#v1.0.1
github:shudingbo/dsh-plugin-autosshfs#<commit-sha>
已安装的包不能直接用
install_bundle覆盖:带 HMR 的 profile 会对"包里已存在的依赖"直接抛already-installed,所以升级一律是"先卸载、再安装"。
补充两点:
- 本地
link:安装是例外:它直接指向工作目录,改代码即生效,不存在"更新"这一步。 - 发布到 npm 之后:改用 npm 安装(
@shudingbo/dsh-plugin-autosshfs)即可在插件页看到版本列表与 一键升级(含失败回滚)——这是想要"点一下就更新"的唯一途径。
系统依赖(sshfs / FUSE)
插件会在「使用帮助 → 系统依赖」里显示依赖状态。Linux 上如果检测到包管理器、并且 sudo -n 可用
(免密 sudo),页面上的「自动安装」按钮会直接安装;否则页面展示需要手动执行的命令。
Linux
# Debian / Ubuntu
sudo apt-get install -y sshfs
# Fedora / RHEL
sudo dnf install -y fuse-sshfs # 或 yum install -y fuse-sshfs
# Arch
sudo pacman -S --noconfirm sshfs
# openSUSE
sudo zypper --non-interactive install sshfs
# Alpine
sudo apk add sshfs
非 root 用户使用 allow_other(默认开启,让 root 的 docker daemon 等能访问挂载点)前,
必须在 /etc/fuse.conf 中启用 user_allow_other:
sudo sed -i 's/^#\s*user_allow_other/user_allow_other/' /etc/fuse.conf
容器内还需要把 /dev/fuse 传入容器,并确保 FUSE 内核模块可用(sudo modprobe fuse)。
macOS
Homebrew 已不再提供 macOS 的 sshfs 公式,需要自行选一套 FUSE 实现:
# 方案 A:FUSE-T(无需内核扩展,较新 macOS 更省事)
brew tap macos-fuse-t/homebrew-cask
brew install --cask fuse-t
brew install fuse-t-sshfs
# 方案 B:macFUSE(内核扩展,需要重启并在「系统设置 → 隐私与安全性」中允许)
brew install --cask macfuse
brew install gromgit/fuse/sshfs-mac
macOS 的 allow_other 需要先在 FUSE 提供方的设置里允许其他用户;否则插件会自动退回「不带
allow_other」重新挂载,并在结果里给出警告(此时只有当前用户能访问该挂载点)。若需要
macFUSE 的额外选项,可在配置项的 extraOptions 里追加,例如 defer_permissions。
配置文件
默认 ~/.autosshfs.json,也可用环境变量 AUTOSSHFS_CONFIG 指定(两者都支持 ~ 展开)。
结构:
{
"list": [
{
"enable": true,
"name": "project",
"host": "example.com",
"port": 22,
"user": "me",
"password": "",
"IdentityFile": "~/.ssh/id_ed25519",
"remotePath": "/srv/project",
"mount": "/mnt/project",
"allowOther": true,
"uid": "1000",
"gid": "1000",
"extraOptions": ["reconnect", "ServerAliveInterval=15"]
}
]
}
字段说明:
| 字段 | 说明 |
|---|---|
name |
名称,界面与工具按它定位配置项;新建时会校验唯一性。历史配置若存在同名项,界面按「位置 + 名称」定位,不会误改到另一项 |
host / port / user |
SSH 目标,user 为空时使用当前本地用户 |
password |
可选;存在时用 -o password_stdin 通过 stdin 传入。明文保存在配置文件中,不建议长期使用,优先 IdentityFile / ssh-agent |
IdentityFile |
私钥路径,支持 ~/ |
remotePath |
远端目录 |
mount |
本地挂载点(绝对路径,不存在会自动创建) |
enable |
是否参与「全部挂载」;缺省视为 false,与命令行版的 mount 循环一致 |
allowOther |
是否附加 allow_other(默认 true);Linux 非 root 需要 user_allow_other,macOS 失败会自动退回 |
uid / gid |
透传 uid= / gid= |
extraOptions |
额外 -o 选项,数组或以逗号/空格分隔的字符串 |
未识别的字段在保存时会原样保留,方便手工扩展。
使用
界面
左侧栏面板按钮 → 主界面独立页面(整列空间,不是设置弹窗;可用面板行的固定按钮收进「更多」):
- 顶栏:标题、sshfs 版本徽标、
已挂载/总数徽标、配置文件路径,以及刷新 / 全部挂载 / 全部卸载 / 新增挂载四个操作。 - 依赖提示条:存在依赖问题(错误或警告)时出现,一键跳到「使用帮助」。
- 「挂载列表」页:搜索框(按名称 / 主机 / 挂载点过滤)、全部 / 已启用 / 已挂载三个筛选项、
计数;每条记录显示名称、状态徽标(已挂载 / 未挂载 / 已禁用 / 已存密码)、
user@host:remotePath、挂载点,以及挂载/卸载、启用开关、详情、编辑、删除(二次确认)。 点击「详情」就地展开挂载来源、文件系统、挂载选项、认证方式。 - 新增/编辑:居中弹层对话框,分「连接信息」「挂载选项」两组;Esc 或点击遮罩关闭; 校验失败会标红对应字段;未修改的密码不会被上传(留空 = 保持不变)。
- 「使用帮助」页:快速上手三步、系统依赖状态表(可「重新检测」/「自动安装」)、
Linux 与 macOS 安装命令、
user_allow_other开关命令、配置文件示例与字段说明、 Agent 调用示例、5 条常见问题、与命令行版共存说明。 - 操作结果(每个配置项的成败、失败原因、警告)显示在结果卡片里,便于排查。
- 界面全中文/英文双语,随 Harness 语言切换。
- 控件样式直接对齐
@deepseek-ai/dsh-client-ui-primitives:按钮用 ghost/outline/primary 三档 (primary 的底色与前景色取--dsw-alias-button-primary-fill/--dsw-alias-label-primary-foreground配对),开关用--dsw-alias-brand-primary+--dsw-alias-switch-thumb,因此深色 / 浅色主题下 文字都清晰可见。
Agent 工具
// 列出现状
{ "action": "state" }
// 挂载全部启用的配置
{ "action": "mount" }
// 只挂载某一项
{ "action": "mount", "name": "project" }
// 新增配置
{ "action": "add", "entry": { "name": "p", "host": "h", "user": "me", "remotePath": "/srv", "mount": "/mnt/p" } }
// 修改(只传要改的字段,password 省略=保持,传 ""=清除)
{ "action": "update", "name": "p", "entry": { "allowOther": false } }
// 同名配置项可改用位置定位(位置上的 name 不匹配时会退回按名称查找)
{ "action": "detail", "index": 4 }
// 查看依赖问题(自动安装只在界面上提供:它会改动机器权限)
{ "action": "check" }
返回值是 JSON:{ ok, action, data | error, summary },error.code / 结果项 code 是稳定标识,
hint / command 给出下一步可执行动作。
架构
dsh-plugin-autosshfs/
├── index.js Host 入口:注册 HTTP 路由与 Agent 工具,共用一个 service 实例
├── client.js Web 界面(main 面板页 + 侧栏面板图标,无 JSX)
├── host/
│ ├── service.js 唯一操作面:state/detail/add/update/remove/enable/mount/umount/install/check
│ ├── config.js ~/.autosshfs.json 读写(原子写、0600)、归一化、校验、脱敏
│ ├── sshfs.js 挂载/卸载命令构建、失败分类、卸载阶梯
│ ├── deps.js 依赖检测(sshfs/FUSE/sudo)与自动安装
│ ├── system.js 平台探测、可执行文件查找、挂载表解析、fuse.conf
│ ├── exec.js argv 方式执行命令(无 shell、带超时)
│ └── http.js POST /api/autosshfs/rpc(复用 /api 的浏览器信任与登录 Cookie 校验)
├── locale/{en,zh}.json 插件管理页显示的标题/描述
├── icon.svg 插件管理页图标
├── .github/workflows/ CI:Node 22/24 上跑 npm test
├── LICENSE MIT
├── .gitignore
└── test/ node:test 单元与集成测试(44 个用例)
界面与 Agent 工具都调用 host/service.js 的同一个 call(action, payload),不存在两套实现。
界面挂载点:客户端注册 main 键(autosshfs)与侧栏帧 sidebar.panellist 里的面板图标
(同一 id)。面板行由 shell 渲染——包括选中态、可访问标签和"固定/取消固定"控件(取消固定即进入
「更多」菜单),插件只提供图标。页面自己负责整列布局、滚动、弹层与帮助内容,主题色与控件样式
全部沿用 DSH 的 --dsw-alias-* 令牌与 primitives 的按钮 / 开关规格。
故障排查
| 现象 | 处理 |
|---|---|
| 页面显示「无法读取挂载列表」 | 插件 Host 端未加载或 /api/autosshfs/rpc 不可达:确认 bundle 已启用、当前 Harness 提供 Web UI;另外确认 webServer 与 connection 服务存在 |
allow_other 相关失败(Linux) |
按上文启用 /etc/fuse.conf 的 user_allow_other,或对该配置项关闭 allowOther |
| 卸载提示 busy | sudo fuser -vm <挂载点> 找到占用进程;插件会先尝试普通卸载,再 fusermount -u、fusermount -uz、umount -l |
| 认证失败 | 检查 user、IdentityFile 是否存在且权限正确(私钥 600),服务器的 authorized_keys 是否包含该公钥 |
| 首次连接报 host key | 先用 ssh 连接一次,或 ssh-keyscan -H <host> >> ~/.ssh/known_hosts |
| 自动安装未执行 | 需要免密 sudo(sudo -n true 成功)。否则按页面给出的命令在终端执行 |
发布(npm + GitHub Release)
包名 @shudingbo/dsh-plugin-autosshfs(scoped、公开)。
一次性准备
scope 归属:
@shudingbo必须是你自己的 npm 用户名,或你创建的 npm 组织。 npm 的用户名和组织名共用同一命名空间,不能给已有用户名再建同名组织。- npm 用户名就是
shudingbo→ 直接可用,不需要建组织; - 否则到 https://www.npmjs.com/org/create 建组织,或换成你拥有的 scope。
- npm 用户名就是
首次发布必须显式公开:scoped 包默认私有。仓库已在
package.json固定publishConfig.access = public与publishConfig.registry = https://registry.npmjs.org/。CI 认证:先 staged,再换 OIDC npm 从 2026 年起收紧 2FA:granular token 直接
npm publish会要求交互式 OTP(CI 里会失败EOTP),2FA-bypass token 也将在 2027-01 失去直接发布能力。所以本仓库按"有没有NPM_TOKEN"选择两种模式:模式 触发条件 行为 Staged 配置了 NPM_TOKENsecretCI 执行 npm stage publish,维护者用 2FA 批准后才真正上线Trusted Publishing 没有 NPM_TOKENCI 用 OIDC 直接 npm publish,同一轮还会创建 GitHub Release首次发布(包还不存在,无法配置 trusted publisher)走 Staged:
- 生成 granular token(权限选 Read and write (publish and stage),scope
@shudingbo),gh secret set NPM_TOKEN --repo shudingbo/dsh-plugin-autosshfs; - 打 tag 跑发布,CI 输出
staged with id <id>; - 本机
npm login后npm stage approve <id>(要 OTP),或到 npm 网站批准; - 包上线后配置 trusted publisher(二选一):
或在包设置页添加 Trusted Publisher:GitHub /npm trust github @shudingbo/dsh-plugin-autosshfs \ --file release.yml --repo shudingbo/dsh-plugin-autosshfs --allow-publish -yshudingbo/dsh-plugin-autosshfs/release.yml; gh secret delete NPM_TOKEN --repo shudingbo/dsh-plugin-autosshfs—— 之后每次打 tag 都是全自动发布(含 GitHub Release)。
- 生成 granular token(权限选 Read and write (publish and stage),scope
账号开启 2FA 不影响 CI 发布:staged 模式在批准时才校验 OTP,OIDC 模式由 GitHub 身份代替。
发一个版本
npm test
npm version patch --no-git-tag-version # 或 minor / major
git commit -am "chore(release): $(node -p "require('./package.json').version")"
git tag "v$(node -p "require('./package.json').version")"
git push origin main --tags
推送 tag 后 Release workflow 自动执行:
测试 → 校验 tag 与 package.json 版本一致 → 发布(见上表:无 NPM_TOKEN 时 npm publish 并创建
GitHub Release;有 NPM_TOKEN 时 npm stage publish,需 2FA 批准后再手动/重跑创建 Release)。
本地直接发(需先 npm login):npm publish;prepublishOnly 会先跑测试。
让 AI 帮你发
配置好上面的 token/secret 后,在会话里说一句「发布 1.0.2」即可,流程固定为:
npm test;2. 改package.json版本并提交;3.git tag v1.0.2+git push --tags;- 盯
gh run watch,把 npm 包地址与 GitHub Release 地址回报。
凭据只存在于 GitHub secret(或你本机 ~/.npmrc),不会进入会话与仓库。
多个 registry(npm 官方 + 公司内部)
- 本仓库提交了
.npmrc,只含@shudingbo:registry=https://registry.npmjs.org/, 没有任何 token:即使机器默认源指向公司 Verdaccio,@shudingbo/*的发布与安装仍走官方源。 - 你现有的
~/.npmrc(@cdyw/@ntypes→http://222.18.149.18:4873/)不受影响,按 scope 各走各的。 package.json的publishConfig.registry再兜一层,任何npm publish都不会误发到内部源。- 若希望内部 mirror 也能取到这个包:让 Verdaccio 配 uplink 代理 npmjs(只读拉取);
或另加一个使用
INTERNAL_NPM_TOKEN+--registry=<内部源>的 job。不要把内部 token 混进公开仓库的发布步骤。 - 公司网络拦截
registry.npmjs.org时,可在 CI 里设NPM_CONFIG_REGISTRY/ 代理; 本仓库零依赖,测试不需要网络。
开发
无第三方依赖、无构建步骤,克隆即可运行:
git clone git@github.com:shudingbo/dsh-plugin-autosshfs.git
cd dsh-plugin-autosshfs
npm test # node --test test/*.test.mjs(44 个用例,覆盖 Linux/macOS 两条路径)
测试覆盖:挂载表解析(含 /proc/self/mounts 八进制转义)、命令构建、失败分类、配置原子写与
权限、校验/去重、脱敏、service 全流程、依赖失败路径、Agent 工具 JSON 边界、HTTP 层的鉴权/方法/
内容类型/请求体上限/跨站拒绝,以及用 shell stub 模拟的成功挂载、allow_other 自动回退、卸载阶梯
与 busy 诊断。
插件改动后重新安装即可(本地目录安装是 link,改完刷新页面就能看到;改动 Host 侧代码时在
「插件」页把该插件关闭再打开即可重新加载)。GitHub 安装是 git 依赖,升级到新的提交需要
plugin_manager 重新安装或重启 Harness。
发布检查清单
npm test全绿;node --check index.js client.js host/*.jspackage.json的name与client.js里window.__ModuleLoader__.load({ id })一致(现在是@shudingbo/dsh-plugin-autosshfs)cordis.patch.yml的name与包名一致(YAML 里以@开头必须加引号)version与要推的 tag(vX.Y.Z)一致 —— CI 会校验- 不带任何真实主机、账号、密码或私钥;
~/.autosshfs.json已被.gitignore排除
No comments yet. Be the first to write one.