dsh-tailnet-admin
把 Tailnet / 反向代理页面当作「本机」来用:让 DSH 的设置页在非回环地址上可用,并按需关掉浏览器会话校验 (Host/Origin 栅栏保持不动)。
⚠️ 这个插件会动到认证边界:两个开关默认都关着,装上去不改变任何行为。打开第二个之前,请先读 安全边界。
它解决什么
从 https://dsh.example.ts.net(任何 Tailnet 服务名或反向代理域名)打开 DSH 时会撞上两件独立的事:
① 设置页用不了:模型与提供商目录、凭据、配置文件编辑器报
settings are unavailable in this browser。原因是 DSH 的浏览器端只把两种情况当作「本机」——由桌面壳
注入的 globalThis.__DSH_TRANSPORT__ = { ownsHost: true },或页面自身的 location.hostname 属于回环
(localhost / 127.0.0.0/8 / [::1])。域名进来的页面两者都不满足,于是设置走「进程内」持久化并直接
报不可用。这条判定在浏览器端,服务端的 --trusted-host 管不到它。
② 每次重启都要重新换 token:宿主的 /api 判定是「Host/Origin 栅栏 + 浏览器会话认证」两层。认证那层
要一个由 ?token= 换来的、按 authority 绑定的 cookie;token 每次启动都重新生成,cookie 换地址即失效。
插件把这两件事拆成两个独立开关:pageHosts 解决 ①,disableBrowserAuth 解决 ②。为什么这么默认、
代价是什么,见 docs/internals.md。
安装
前提:Node ^22.19.0 || >=24.0.0(与官方 DSH 一致)。
1. 装包
npx @deepseek-ai/dsh plugin --profile web add @he0119/dsh-tailnet-admin
发布产物里带着编译好的 lib/,安装时不需要授权任何构建脚本。本地改代码时直接装仓库目录(先
pnpm run build):
npx @deepseek-ai/dsh plugin --profile web add link:/path/to/dsh-tailnet-admin
2. 注册为 bundle
dsh plugin add 只做 pnpm add。DSH 启动时只读 profile 清单里的 dsh.profile.bundles,所以还要把
包名加进去:
// ~/.dsh/profiles/web/package.json
"dsh": {
"profile": {
"bundles": [
// …
"@he0119/dsh-tailnet-admin"
]
}
}
用图形界面也行:设置 → 插件里会列出「已安装但未启用」的包,启用一次做的是同一件事。
改完重启 DSH(systemd 部署的话:systemctl --user restart dsh-web)。启动日志里会出现
[dsh-tailnet-admin] … 那两行,说明插件到位了。
3. 打开开关
# ~/.dsh/profiles/web/cordis.patch.yml
- id: tailnet-admin
name: '@he0119/dsh-tailnet-admin'
config:
pageHosts:
- .ts.net # 也可以写精确主机名,或 `*`
disableBrowserAuth: true # 默认 false;打开前先读「安全边界」
开关
| 开关 | 环境变量 | 默认 | 作用 |
|---|---|---|---|
pageHosts |
DSH_TAILNET_ADMIN_PAGE_HOSTS |
[] |
哪些页面主机按「本机」处理。. 开头 = 后缀匹配(.ts.net 命中 a.ts.net,不命中裸 ts.net);* = 全部 |
disableBrowserAuth |
DSH_TAILNET_ADMIN_DISABLE_AUTH |
false |
是否关闭浏览器会话校验(token/cookie) |
- 环境变量优先于配置,两者都支持:环境变量适合放在 systemd unit 这类运维位置,配置跟着 profile 走。
- 环境变量里的主机表是逗号分隔的;给空串等于显式清空(覆盖配置里的值)。
- 布尔量只认
1/true/on/yes(不分大小写);写错的值按「没开」处理 —— 反向解析会把一个手滑 变成「关掉认证」。
systemd 部署的例子:
[Service]
Environment=DSH_TAILNET_ADMIN_PAGE_HOSTS=.ts.net
Environment=DSH_TAILNET_ADMIN_DISABLE_AUTH=1
Environment=DSHW_ADMIN_HOSTS=dsh.example.ts.net
ExecStart=%h/.npm/_npx/<hash>/node_modules/.bin/dsh web --host 127.0.0.1 --port 3080 \
--trusted-host dsh.example.ts.net --no-open
--trusted-host(或 profile 里 connection 插件的trustedHosts)是另一条独立要求:/api的 Host/Origin 栅栏只认回环或声明过的 authority。只开本插件的开关、不给--trusted-host,请求会在栅栏 那一步就被 403 掉。
安全边界(必读)
disableBrowserAuth: true 关掉的是「谁可以进这个界面」的认证。打开之后:
- 任何能打开这个页面的人就等于拿到这台机器的控制权 —— 读写文件、执行命令、动用你在 DSH 里配置的 API key。暴露面取决于 Tailscale ACL / 反向代理的访问控制,不再取决于 token。
- 保留下来的是 Host/Origin 栅栏:只认回环 Host 或
--trusted-host/trustedHosts声明过的 authority,并拒绝Sec-Fetch-Site: cross-site。它挡的是 DNS 重绑定与跨站请求,不挡「知道地址的人」。 - 反过来,
pageHosts只影响浏览器端的设置持久化与界面可用性,它不改变服务端的任何判定。
建议的组合:pageHosts 按需开,disableBrowserAuth 只在真的被 token 反复打断时开;开了就顺手确认一下
Tailscale ACL 只放行你自己的设备。想撤回:把环境变量删掉(或配置改回 false),重启即可 —— 没有任何状态
需要清理。
排障
| 现象 | 原因 |
|---|---|
设置页仍报 settings are unavailable in this browser |
pageHosts 没命中当前页面的主机名(后缀匹配要带点:.ts.net),或改完没重启 DSH |
| 请求直接 403 | Host/Origin 栅栏没过:给 dsh web 加 --trusted-host <authority> |
仍然要 ?token= |
disableBrowserAuth 没开,或环境变量写成了 ture 这类不认的值(只认 1/true/on/yes) |
启动日志里没有 [dsh-tailnet-admin] |
包名没写进 dsh.profile.bundles,或包没装上(dsh --profile web --dump-config | grep tailnet-admin 可确认) |
| 配置项没生效 | 同名环境变量存在时以它为准 |
开发
构建、测试、以及「装进一个专用 profile 再看页面」的手动路径写在 docs/development.md;为什么这么实现、踩过什么坑在 docs/internals.md;发布流程(含首版为什么必须手动发)在 docs/releasing.md。
pnpm install # 会顺带跑 prepare,也就是一次完整构建
pnpm test # 单元测试(离线)
pnpm run typecheck
pnpm run build
No comments yet. Be the first to write one.