DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Service Portal — DeepSeek Harness 插件(DSH Plugin)
← Plugins
S

dsh-service-portal

Service Portal

对外服务门户:装好即用,设置页内网/公网二维码扫码进控制台管理服务与路由(cloudflared 隧道 + 口令登录,内网免密可开密码、外网必须口令)。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:dingminhua/dsh-service-portal#c4b2f07e55156cea285810e3971bf66b910bdfe7
README兼容性版本

兼容性与来源证明

Service Portal 以 dsh-service-portal 发布,当前版本为 0.8.8。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/10/16

版本

0.8.8stable
2026/10/16
0.8.5stable
2026/10/16

相关插件

正在加载相关插件…

最新版
0.8.8
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 1
周下载
0
最近提交
2026/10/16
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录

相关插件

继续浏览 security-access 分类下经过校验的插件。

Experimental Auto Review@deepseek-ai/dsh-experimental-auto-review针对每个工具的 LLM 授权审查,适用于 DeepSeek Harness Auto 权限预设Cc Safety Netcc-safety-net编码代理 CLI 钩子——阻止破坏性命令和访问机密文件Doctor@linxin666/dsh-doctorDSH 配置档案的事务性救援模式,配备受监督的启动器、隔离的恢复容器、确定性修复、健康监控以及本地 Web 恢复控制台Pocketdsh-pocket把 DeepSeek Harness 装进你的口袋:一个包、一个设置页,手机扫码即同步访问电脑上的 DSH(局域网 + 公网,实时同屏)。

README

dsh-service-portal

dsh-service-portal

一个随机域名 + 一个随机口令,把本机的服务按需对外开放。

English · 安装 · 工作原理 · 服务清单 · 更新日志 · 问题反馈

dsh-service-portal(对外服务门户) 是一个 DeepSeek Harness (DSH) 插件。 它把任意本机服务按需暴露到外网:cloudflared 建一个随机域名,一道口令登录 (只需口令,没有账号)守住入口,设置页一键开关并显示连接与口令。

它最初长在 ld-rl-poker 里(为了把训练配置台/模型查看台放到外网),但门户本身与德州扑克无关—— 任何本地服务都能用同一套「一个域名 + 一个口令 + 按需挂载」。

功能特性

  • 装好即用,不用先点开关 —— 插件加载后门户自动起来(只开内网,不会自动把本机 服务挂到公网)。打开设置页就能看到内网地址和二维码,扫一下就进。 外网暴露必须由你显式点「开启公网访问」。
  • 设置页就是 dsh-pocket 那个形态 —— 局域网、公网两个区块,各自带开关 + 二维码 + 地址 + 访问密码行 + 高级(手动选地址)。
  • 节点管理就在插件设置里(插件页 → 该插件 → 配置)—— 添加节点(名称 / 对外路径 / 本机端口 / 备注)、 开放 / 屏蔽(路由闸门)、编辑、删除、以及每个节点一个「访问」。 不用改配置文件、不用重启。
  • 只管路由,不管启停 —— 「屏蔽」只表示门户不再把这条路由转过去, 不会停止上游程序;上游由你自己的方式启动(插件不猜它的启动命令)。
  • 自带门户网页(只读) —— 扫码打开的是测试页 + 引导页: 列出已发布的节点,每个节点有「访问」(打开它)和「测试」 (就地探一次通不通并把结论写在卡片里,不用跳走):
    • 「通了:HTTP 200」= 门户转发成功且上游有响应;
    • 「不通:HTTP 502 … 请用它自己的方式启动那个程序」= 上游没起;
    • 「上游有响应,但它自己返回了 HTTP 502」= 上游在线、是它内部出错。 最后这条必须和上一条分开说 —— 状态码一样,把上游自己的错报成 「没启动」会让人去启动一个本来就在跑的程序。 没有任何设置功能 —— 要改配置请到 DSH 插件页里该插件自己的配置里。
  • 内网 + 外网两种访问 —— 同一张页面,两条入口:
    • 内网(http://192.168.x.x:8899)默认免密,同一 WiFi 下手机/平板直接打开。 可开「局域网访问密码」,也可以直接关掉整个局域网访问;
    • 外网(https://<随机>.trycloudflare.com)必须口令,人在外面也能用。
    • 两个开关改完立即生效,不用重启门户(重启会换掉隧道域名,代价太大)。
    • 本机访问永远免密 —— 否则你在电脑上连自己的门户都进不去。
  • 手机扫码即用 —— 设置页与门户页都显示二维码(内联 SVG,不依赖外部请求), 手机扫一下就到;页面按窄屏自适应。
  • 只需口令,不要账号 —— 自定义登录页(只有口令输入框),不弹浏览器原生 「账号 + 密码」框;口令可每次随机或你自己设固定值。
  • 登录限速 —— 单 IP 连续失败 5 次锁 60 秒,另有全局阈值防分布式撞码。
  • 服务状态一目了然 —— 每个服务显示 已启用/未启用/存活探测失败,未启用的不可点; 健康检查是真探测(TCP 连上游端口),不是标记位。
  • 控制检验(本仓库硬纪律)—— 26 个 Python 控制检验脚本 + 8 个端到端脚本 + 83 项 Node 检验;依赖缺失必须「跳过并返回 2」,不得静默通过。

界面

门户页(扫码打开的那个页面,只读:测试 + 引导):左边手机窄屏、中间深色、 右边宽屏。截图由 tools/shoot_portal.py 用真门户进程 + 无头 Chrome 实拍, 不是设计稿。

窄屏(浅色)窄屏(深色)宽屏
门户页 窄屏 浅色门户页 窄屏 深色门户页 宽屏

工作原理

                    ┌─────────── 门户进程(一个端口,默认 8899)───────────┐
 内网 http://192.168.x.x:8899 ──┤                                        │
                                ├──▶ 按 Host 分类:loopback / lan / public │
 外网 https://<随机>.trycloudflare.com ─┘    │                            │
                                            ├─ /            → 门户页(只读:测试 + 引导)
                                            ├─ /_auth/login → 口令登录(外网)
                                            ├─ /_api/*      → 门户 API(受 DSH 插件设置调用)
                                            └─ /<路由路径>/… → 转发到对应服务

认证强度按来源区分(照搬 dsh-pocket 的模型,见致谢):

Host 分类判定门户页 / 服务
loopbacklocalhost、127.x、::1免密
lanRFC1918 私网、CGNAT(100.64/10)、.local、单标签名免密
public其余一切(含你自建域名)必须口令

兜底方向是安全的一侧:认不出的 Host 一律当公网。反着写(只把 *.trycloudflare.com 当外网、其余当内网)会让人挂个自己的域名就绕过认证——dsh-pocket 踩过这个坑 (它的 issue #66),本项目直接按 fail-closed 实现。

安装

方式一:npm(发布后)

$dsh = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd"
& $dsh plugin --profile desktop add dsh-service-portal

方式二:link 本地插件(开发中)

$dsh = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd"
& $dsh plugin --profile desktop add "link:D:/DshProject/dsh-service-portal"

装完需要重启 DSH(插件在宿主进程里加载)。

若旧 profile 仍装有已删除的子插件 dsh-poker-viewer,先移除再装根插件: & $dsh plugin --profile desktop remove dsh-poker-viewer。

配置(DSH 设置页)

字段默认说明
infraPath本仓库根基础设施仓库:含 tools/ 与 runs/(隧道/代理/凭据)
repoPath空宿主仓库根:被开放的服务本体在这里。必须自己填
pythonPath空拉服务用的解释器;留空则自动选 .venv313 → .runtime → PATH
passwordModerandom口令形态:random 每次开启自动生成 / fixed 用固定口令
fixedPassword空passwordMode = fixed 时使用,至少 12 字符(保存时即校验)
portalPort8899门户端口。只接受 1–65535 的整数,非法值回落默认
autoStarttrue插件加载后自动启动门户(只开内网,不建隧道)
lanAuthEnabledfalse内网访问是否要口令(false = 免密)
webEnabledtrue是否挂载 /service-portal/api 路由

所有字段都是 .volatile():改设置即时生效,不需要重挂插件。 「局域网访问密码」「局域网访问」两个开关走门户的 /_api/settings, 改完立即生效且立即落盘,不受「运行中不可改」限制。 口令形态是安全相关设置:口令模式与固定口令在门户运行中不可改(先「关闭」再改), 避免出现「界面显示的口令」与「实际生效的口令」不一致。

为什么 autoStart 默认开、但只开内网

对齐 dsh-pocket 的「装好即用」:装了插件打开设置页就该看到内网地址和二维码, 而不是一片空白等着你点按钮。但外网暴露是另一回事 —— 那是把本机服务放到公网上, 必须由你显式决定(点「开启公网访问」),不能因为装了插件就自动发生。

口令的两种形态

random(默认)fixed
谁生成门户每次开启自动生成 16 字符随机口令你自己设定(≥12 字符)
何时变每次点「开启」都换只有你改它才变
泄漏后果只在本次开启期间有效,重启即作废长期有效,直到你改口令
适用分享给临时协作者、不想记口令常用设备,省去每次复制口令
安全代价需重新分发口令口令一旦外泄就是长期敞口

固定口令不会进入进程命令行(那会让本机任意进程通过 Win32_Process 读到它): 插件把它写进 runs/ 下的临时文件并以 --password-file 传路径,启动器读完即删。

HTTP 接口

挂在宿主 webServer 上(prefix 路由,路径 /service-portal/api):

方法路径作用
GET/service-portal/api/status当前状态:是否运行、模式、地址、口令、口令形态、端点列表
GET/service-portal/api等同 /status
GET/service-portal/api/settings口令形态设置(模式 + 固定口令 + 最小长度)
POST/service-portal/api/settings保存口令形态(body:{"passwordMode":"fixed","fixedPassword":"..."})
POST/service-portal/api/start启动(body 可选 {"singleDomain": true},默认单域名+路径)
POST/service-portal/api/stop停止

未知子路径返回 404(不拿 status 兜底——拼错的路径不许「看起来能用」)。

在插件设置里管理节点

在 DSH 里打开 设置 → 插件 → 找到 dsh-service-portal → 配置, 就在那里管理要对外发布的路由(注册在插件页的 plugins.bundle.config / plugins.row.config 槽位,与 workbuddy 同一形态;不会占用宿主设置的左侧导航)。 每个节点一条卡片:

操作说明
添加节点填 名称 / 对外路径(如 /ollama)/ 本机端口(如 127.0.0.1:11434)/ 备注
访问点了直接在新标签打开该路由(/<对外路径>/)。屏蔽时置灰不可点
开放 · 屏蔽路由闸门:开放 = 门户把请求转过去;屏蔽 = 不转(该路由立即 404)。不启停上游程序
编辑 / 删除改对外路径、上游、备注;删除只从注册表移除
状态绿=开放中,灰=已屏蔽;上游没响应时另给一行提示(不影响路由开放与否)

「屏蔽」不是"停服务" —— 上游程序照跑不误。本插件不负责启动/停止上游 (那需要知道它的启动命令、工作目录、就绪判定,是一整套配置,且容易做出 "以为在管、其实没管住"的假象)。上游请用它自己的方式启动。

改动立即落盘(runs/portal_registry.json)并生效,不用重启门户、不用重建隧道。

门户页面是只读的(扫码后的落地页):列出已发布的节点,每个节点两个动作 —— 「访问」(打开它)与「测试」(就地探一次,把通/不通与原因写在卡片里)。 那是测试页 + 引导页,没有任何设置功能。

「测试」的三种结论(真机用 Chrome 点过,三种都对且各不相同):

上游状态页面结论
活着通了:HTTP 200 —— 门户转发成功且上游有响应
没启动不通:HTTP 502 —— 门户转发过去了但上游没响应,请用它自己的方式启动
在线但自己 502上游有响应,但它自己返回了 HTTP 502 —— 上游在线,请看它自己的日志

最后两条状态码都是 502,只能靠正文区分(门户自己发的那条固定含 Bad Gateway 与 上游)。分开说是必须的:把上游自己的错报成「没启动」, 用户会去启动一个本来就在跑的程序。契约由 tools/check_portal_probe.py 钉住。

节点字段:

字段必填说明
key✓唯一标识,[a-zA-Z0-9_-]{1,32},不能与保留字冲突。从对外路径自动派生(/rltrain → rltrain),界面不必填
name✓显示名(≤64 字符,页面会 HTML 转义)
mount✓对外路径,以 / 开头;不能与其他开放项重复、不能占用 /_auth /_api
target✓本机上游 host:port
enabled路由是否开放(开放 / 屏蔽)
script / cwd / autoStart已废弃:老数据仍能读,但插件不再拿它们去启动任何进程

命令行也能直接改(等同设置页操作): POST /_api/services、POST /_api/services/<key>/open|block|delete|update (enable / disable 作为老别名仍可用)。

工具(也可脱离插件直接用)

文件作用
tools/portal_run.py门户启动器:起门户页 + 内网/外网双入口(不管上游进程)
tools/portal_proxy.py按 Host 分类认证的反向代理(内网免密 / 外网口令)
tools/portal_console.py门户页(只读:测试 + 引导)+ 二维码(纯 Python,无依赖)
tools/portal_registry.py服务注册表(运行时增删改查 + 原子落盘)
tools/portal_login.py口令登录 + 签名会话 cookie
tools/start_public_gui.py旧版启动器(静态清单 + 单/多域名两种形态,仍可用)
tools/tunnel_auth.py旧版认证代理(口令登录 + 多挂载点路径分流)
python -X utf8 tools/portal_run.py                 # 起门户(内网 + 外网隧道)
python -X utf8 tools/portal_run.py --lan-only      # 只开内网
python -X utf8 tools/portal_run.py --port 8899     # 指定端口
python -X utf8 tools/portal_run.py --status / --stop

控制检验

检查项数拦的 bug
tools/check_portal_parity.py126设置页三处对标:缺右上角求 Star 区块、局域网口令与公网共用、「自动选地址」只是显示不生效;未启动状态逻辑;排版(字号刻度、只引用真实主题令牌、动作与说明分层、警示块去重);公网密码自定义与警告提示;节点开关必须是"路由闸门"而不是启停上游、门户页只读无管理入口
tools/check_portal_stop_scope.py13--stop 顺手杀掉同端口上别人的进程(用户正在跑的门户被检验脚本干掉);停止后口令/候选被整份删掉
tools/check_portal_shutdown.py6门户自退接口:局域网/公网来源必须被拒(不能从手机把门户关掉)、GET 不生效、端口松了进程没退
tools/check_plugin_stop.py6设置页「停止门户」真能停:宿主派生进程杀不掉门户却报成功(沙箱/作业隔离),改走"请门户自己退"
tools/check_portal_settings_forward.py7设置页局域网三项(总开关 / 口令开关 / 手动选地址)被宿主静默忽略:返回 ok:true、toast 说改了,门户其实一点没变
tools/check_public_pin.py18公网密码自定义:新密码真能登录、旧密码真的作废(拿会话 cookie 当判据)、重启后仍在;安全负例:不能用局域网口令当公网密码、太短/空白被拒
tools/check_portal_service_api.py13节点接口:门户侧增删、开放/屏蔽(open/block)、enable/disable 老别名仍可用、restart 被明确拒绝;反例——门户没跑时转发必须返回 ok:false(上一轮的"静默忽略却弹成功"就是栽在这)
tools/check_portal_add_route.py12添加节点走宿主 HTTP 路由的端到端:真 createHandler + 真门户 + 真磁盘;开放/屏蔽、反例(restart 必须被拒、target 非法必须 400 + 明确 error)
tools/check_gate_does_not_stop_upstream.py17「屏蔽」不得停止上游进程(用户最关心的一条):起真上游进程,验屏蔽后 PID 仍活、绕过门户直连仍通、门户不再转发(404);删除节点也不动上游
tools/check_portal_status_running_services.py

端到端(真起门户进程,不是单元级)

脚本项数覆盖
test/acceptance_portal.py30页面增删改开关服务、路由改动生效、内网免密/外网口令、二维码/手机端/限速/健康检查真实性、三分类 fail-closed、依赖缺失返回 2
test/test_autostart.py4插件加载后门户自动跑起来(「装好即用」)、只开内网、内网地址可用
test/test_lan_address.py16网卡评分选地址(物理网卡优先、198.18.x 降权)、手动选地址立即生效、非法地址被拒、覆盖值落盘
test/test_lan_toggle.py10内网口令开关立即生效(不重启门户)、本机永远免密、公网不受影响
test/test_lan_switch.py6局域网访问总开关:关掉后内网要求口令、本机不受影响、重开恢复
test/test_public_link.py12真实公网链路:域名可达、公网首页是登录页、未登录拿不到 API 数据、内网免密、畸形 Host fail-closed
test/test_settings_data.py10设置页数据链:状态字段、二维码生成、二维码只给本门户地址(防图床)
test/test_host_buttons.py10走插件宿主 API(设置页按钮真正调用的路径):开启→状态→重复开启→停止;停止走的是"门户自退";收尾恢复用户原本在跑的门户

依赖缺失必须「跳过并返回 2」,不得静默通过 —— 这是本仓库的硬纪律。

检验不许动用户正在跑的门户:脚本一律用临时端口 + 临时 PORTAL_RUNS_DIR, 不碰用户真实的 runs/(那里面存着局域网口令和你注册的节点)。 这条是踩出来的:一句裸的 portal_run.py --stop 曾把用户的门户干掉, 用户看到的是「二维码和访问地址一起消失」。

$node = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\primary-runtime\dependencies\node\bin\node.exe"
& $node --test test/host.test.mjs      # 55 项宿主端控制检验
& $node --test test/client.test.mjs    # 28 项客户端分发契约检验

# Python 控制检验
python -X utf8 tools\check_portal_bind.py
python -X utf8 tools\check_portal_websocket.py
python -X utf8 tools\check_portal_integration.py
python -X utf8 tools\check_portal_registry.py
python -X utf8 tools\check_portal_console.py
python -X utf8 tools\check_portal_proxy.py
python -X utf8 tools\check_portal_auth.py
python -X utf8 tools\check_portal_stop_scope.py
python -X utf8 tools\check_portal_shutdown.py
python -X utf8 tools\check_plugin_stop.py
python -X utf8 tools\check_tunnel_auth.py
python -X utf8 tools\check_tunnel_domain.py
python -X utf8 tools\check_plugin.py

# 端到端(会真起门户进程,几十秒)
python -X utf8 test\acceptance_portal.py
python -X utf8 test\test_autostart.py
python -X utf8 test\test_lan_address.py
python -X utf8 test\test_lan_toggle.py
python -X utf8 test\test_lan_switch.py
python -X utf8 test\test_settings_data.py
python -X utf8 test\test_host_buttons.py   # 需要 DSH 在跑;宿主代码过期会跳过并返回 2

改样式时怎么看效果(人工看图,不是自动检验)

tools/render_panel_preview.mjs 把设置面板真渲染出来(直接 require lib/client.js,最小 React 替身 + 假 fetch),用无头 Chrome 截图肉眼核对:

node tools\render_panel_preview.mjs          # 打印地址
# 然后访问 /?s=running|public|off[&dark=1] 截图

它是人工工具,不进控制检验(要人看图,没法自动判过),而且有已知局限: 替身 React 的文本排版不真实(截图里标题与副标题会"看起来压在一起"), 只适合看结构顺序、颜色、胶囊形状、层级差别。局限写在文件头。

它现在能反映真实布局了(0.8.4 修的):替身工厂原来把 className 当成 字面量属性设上去(className="..."),而外观在 0.8.4 起改由注入的 CSS 类承担, 于是预览会画出一个和真实渲染完全不同的页面(两栏不并排、hover 没有)。 这种"预览骗人"比没有预览更糟,已改成映射 className → class。

看门户页(访问页)的效果

tools/shoot_portal.py 在临时端口用当前代码起一个真门户(真 HTTP、 真状态文件、真注册表),再用无头 Chrome 截三张图(窄屏 / 窄屏深色 / 宽屏):

python -X utf8 tools\shoot_portal.py     # 输出到 runs/shots/

为什么不直接截你线上那个 8899:那个进程里是改动前的 portal_console 模块 (Python 一 import 就固定),要看新页必须重启你的门户 —— 而重启会换掉 cloudflared 隧道域名(你发出去的地址就废了)。所以另起一个临时的来看。

外观:一份注入的 CSS,而不是内联 style

面板的外观(底色、描边、圆角、尺寸、hover、焦点环、窄屏媒体查询)都在 client/index.jsx 顶部的 CARD_CSS 里,模块加载时注入一个 <style> (照 workbuddy 的做法,按 data-plugin-css 查重做幂等)。

为什么不用内联 style:内联的优先级高于类选择器,连 :hover 也压不过它 —— 内联一旦写了底色,类里的 hover 就成了死代码。而"可点击元素要有按压反馈""键盘 焦点要可见""窄屏要塌单栏"这三条都只能靠 :hover / :focus-visible / @media 实现。所以按钮原语的 style 对象是空的,颜色与尺寸一律走 .dsp-* 类 (类名统一 dsp- 前缀,避免与宿主或其他插件撞车)。

安全边界(必须如实理解)

  • 口令是明文存在 runs/public_gui_credentials.json 并明文显示在插件设置页。
  • 拿到口令 == 能改对外发布的路由。持有效会话者可以调用 /api/services* 增删节点、开放或屏蔽路由。但它不能启停你的上游程序 —— 本插件已不做启停 (所以拿口令的人无法借这里去停 5090 上的训练)。对 ld-rl-poker 而言, 风险面从"能启停训练"缩小到"能改发布出去的路由"。
  • 局域网免密时,同 WiFi 上的人也能读改节点(/_api/services 不需要口令)。 这是"内网免密"体验的代价;不想承担就开「局域网访问密码」或关掉局域网访问。 节点管理搬到插件设置后,日常操作走宿主转发(127.0.0.1),不必再开着控制台页面。
  • 有些本地服务自身没有认证(两个 GUI、Ollama 等),挂出去就等于公开—— 门户这层口令是它们唯一的关卡。
  • 本插件不会碰 dsh-pocket 自己的隧道(目标 3081);--stop 只停自己记录的进程。
  • 登录后的会话是签名 cookie(HttpOnly,默认 12 小时到期):不必每次请求都带口令, 但这也意味着在这段时间内,那个浏览器就等于已授权。会话密钥随门户进程随机生成, 关掉门户即全体登出。
  • 选固定口令就要接受它的代价:门户重启后口令不变,一次外泄就是长期敞口。 只想临时分享就保持默认的 random。
  • 门户跑在本机 127.0.0.1 上,口令校验也发生在这一层;不要把入口端口直接暴露到公网, 否则认证层会被绕过(cloudflared 隧道才是唯一的对外通道)。

测试

$node = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\primary-runtime\dependencies\node\bin\node.exe"
& $node --test test/host.test.mjs      # 55 项宿主端控制检验
& $node --test test/client.test.mjs    # 28 项客户端分发契约检验

# 端到端验收:真起门户进程,逐条验证「内网免密/外网口令/页面管理服务/限速/健康检查」
python -X utf8 test\acceptance_portal.py    # 30 项

# 认证层与路由层的控制检验(纯标准库)
python -X utf8 tools\check_portal_auth.py    # 35 项:登录页/会话/认证前置
python -X utf8 tools\check_tunnel_auth.py    # 35 项:路由/前缀剥离/多挂载/502

与同族插件的关系

dsh-service-portal 是 LaoDing 插件家族的一员(家族品牌图标与 dsh-connect-workbuddy / dsh-connect-trae 等一致)。家族成员共享:

  • 设置卡片外观与 row.* 文案约定;
  • 手写 CommonJS 客户端分发格式(window.__ModuleLoader__.load),零构建依赖;
  • 「启动器,不是第二份实现」的复用纪律:隧道/认证等已完成并验收的实现不在插件里重写。

历史上门户功能曾是专用子插件 dsh-poker-viewer(ld-rl-poker 外网开关);2026-10-15 已将其功能吸收进根插件成为通用门户能力(服务清单驱动,不绑定任何具体服务), 子插件目录已删除。

未做

  • 固定自有域名:需要 named tunnel + ingress。现在每次启动都是随机 trycloudflare 域名。
  • cloudflared 自动下载:本项目复用本机已有的那份(dsh-pocket 会多镜像源下载)。
  • 服务健康检查定时刷新:现在是页面刷新时探测存活,没有后台定时器。

致谢

本项目的门户形态(门户网页、内网+外网双入口、按 Host 分类的认证强度)明确参照了:

shaobeichen/dsh-pocket

「把 DeepSeek Harness 装进你的口袋:一个包、一个设置页,手机扫码即同步访问电脑上的 DSH(局域网 + 公网,实时同屏)。」 —— GPL-2.0

我们从它学到的设计(逐条对应到本项目的实现):

它的做法我们的实现
classifyHost() 把 Host 分成 loopback / lan / public,按类别决定认证强度tools/portal_proxy.py::classify_host(),同一套三分类
fail-closed:认不出的 Host 一律当公网(它曾因反着写被绕过,issue #66)同样 fail-closed,并有 20 项分类控制检验(含「自建域名必须判为公网」)
公网始终要密码;局域网可开关;loopback 免密内网免密、外网必须口令(用户选定)
登录限速:单 IP 5 次失败锁 60s + 全局阈值RateLimiter,同样的阈值策略
cf-connecting-ip 只在源地址是 loopback 时信任client_ip(),同一条判定(防伪造绕过限速)
自定义登录页(不弹浏览器原生认证框)tools/portal_login.py
手机扫码即用(二维码 + 移动端适配)tools/portal_console.py(纯 Python 生成 SVG 二维码,不引入依赖)

同时说明清楚:本项目没有复制 dsh-pocket 的任何代码。

  • 它是 JavaScript(lib/proxy.mjs / lib/tunnel.mjs / client/),本项目的门户是独立的 Python 实现(tools/portal_*.py),两侧语言、结构与命名均不同;
  • 双方暴露的 HTTP 接口、口令存储格式、配置文件格式都不兼容;
  • 我们借用的是设计判断(尤其是 fail-closed 的兜底方向、限速阈值、IP 信任边界这类 「用血换来的结论」),实现是从零写的。

因此本项目的 MIT 许可不受其 GPL-2.0 传染:GPL 约束的是衍生作品的代码分发,而 「参考设计思路、独立实现」不构成衍生作品。我们仍在此明确致谢并标注其许可, 一是尊重原作者,二是让读者能自己判断。

如果将来要在本项目里直接搬运它的代码,那就必须整体转为 GPL-2.0 —— 请勿在未确认的情况下混入其源码片段。

其他

  • cloudflared(Apache-2.0)—— 公网隧道。
  • mexiaosqwq/dsh-web-mobile(MIT)—— dsh-pocket 的移动端适配移植自它,本项目未直接使用,但移动端设计的源头在此。
  • 插件家族的卡片样式与品牌图标来自 dingminhua/dsh-connect-workbuddy 等同族插件。

完整的第三方清单与许可要求见 THIRD_PARTY_NOTICES.md。

许可证

MIT,Copyright (c) 2026 LaoDing。第三方致谢见 THIRD_PARTY_NOTICES.md。

6
添加节点后设置页看不到:宿主原来从不去问活门户要服务清单(判据漏了 services),修后能读回;反例——门户没跑时必须空数组,不许编造
tools/check_shutdown_self_exit.py8第二次点「停止」失败:serve_forever 先起、关闭钩子晚装,那个窗口里 /_api/shutdown 回 503「门户还没就绪」,宿主退到 --stop 兜底而兜底在沙箱里杀不掉(stoppedBy=stop-command)。含源码顺序断言 + 实测「端口一在听就能自退」
tools/check_portal_lan_pin.py23局域网口令刷新/自定义;公网拿局域网口令能进来(安全边界)、候选列表没落盘
tools/check_portal_form.py8控制台表单提交被当成坏 JSON(页面上点「添加服务」永远失败)、表单没有 key 字段导致必然失败
tools/check_portal_tunnel.py11门户已在跑时「开启公网访问」什么都不做(幂等检查把建隧道也跳过了)、「关闭公网」把整个门户停掉
tools/check_portal_bind.py7只绑 127.0.0.1(手机永远连不上)、Windows SO_REUSEADDR 重复绑定(流量随机分流)、端口被占不换端口
tools/check_portal_websocket.py8升级头被过滤(握手失败)、101 响应没原样回传、握手后没有双向管道(实时数据不刷新)
tools/check_portal_integration.py—畸形 Host 被判成内网(garbage value 竟免密)、控制台链接双斜杠、局域网总开关只改界面不改认证
tools/check_portal_registry.py88路由重复/非法、key 冲突、改完不落盘(重启就丢)、坏文件崩溃
tools/check_portal_console.py41未启用服务可点、服务名里塞 <script>(存储型 XSS)、二维码不确定;测试页(按钮/结果容器/屏蔽节点禁用/不用 opaque-redirect)、页脚「鼓励一下」、美化令牌(语义变量、深浅色、44px 触控、焦点环、reduced-motion)
tools/check_portal_probe.py21门户「测试」按钮的服务端契约:上游活->200、上游死->门户 502 且正文含 Bad Gateway+上游(界面靠它区分是谁发的 502)、上游自己的 502 原样透传、302 透传。含真上游 + 真门户的端到端
tools/check_probe_button_browser.py4真机端到端:CDP 驱动真 Chrome 点门户页的「测试」按钮,逐节点核对结论(活的/死的/自崩的三种文案必须各不相同 —— 把上游自己的 502 报成「没启动」,用户会去启动一个本来就在跑的程序)
tools/check_portal_proxy.py—自建域名被误判成内网、认证未前置、限速失效、cf-connecting-ip 被伪造
tools/check_portal_auth.py35未登录可访问、会话被伪造/篡改/过期、口令进 cookie
tools/check_tunnel_auth.py35认证绕过、前缀没剥、上游错、未知前缀 200 兜底
tools/check_tunnel_domain.py10复用上一次的域名(追加日志里读到旧行)
tools/check_plugin.py41插件导出契约、服务已死却报「运行中」、口令形态非法