🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

远程中继 xiaohanqing/dsh-relay

DeepSeek Harness
xiaohanqing20 天前有提交已提供中文文档扫描:含敏感能力

手机随时访问你自己的 DSH —— 流量走你自己的服务端,不依赖任何第三方云。

31.4/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/15 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add xiaohanqing/dsh-relay
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 20 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-relay-monorepo(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 08:09:56

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 79 处,展示前 54 条)
  • 写入本地文件
    packages/plugin/build-host.mjs:41
    await mkdir(dirname(outputPath), { recursive: true });
  • 写入本地文件
    packages/plugin/build-host.mjs:42
    await writeFile(outputPath, bundled, 'utf8');
  • 读取本地文件
    packages/plugin/lib/index.js:9256
    accessSync(p, fsConstants.X_OK);
  • 读取本地文件
    packages/plugin/lib/index.js:9257
    return existsSync(p);
  • 读取本地文件
    packages/plugin/lib/index.js:9267
    if (existsSync(p) && isExecutable(p)) return { bin: p };
  • 读取本地文件
    packages/plugin/lib/index.js:9278
    if (existsSync(p) && isExecutable(p)) return { bin: p };
  • 读取本地文件
    packages/plugin/lib/index.js:9378
    if (r.ok && existsSync2(dest) && statSync(dest).size >= MIN_ARCHIVE_BYTES) return { via: "curl" };
  • 读取本地文件
    packages/plugin/lib/index.js:9378
    if (r.ok && existsSync2(dest) && statSync(dest).size >= MIN_ARCHIVE_BYTES) return { via: "curl" };
  • 写入本地文件
    packages/plugin/lib/index.js:9398
    mkdirSync(work, { recursive: true });
  • 写入本地文件
    packages/plugin/lib/index.js:9428
    mkdirSync(managedBinDir(home), { recursive: true });
  • 写入本地文件
    packages/plugin/lib/index.js:9430
    renameSync(found, bin);
  • 写入本地文件
    packages/plugin/lib/index.js:9432
    copyFileSync(found, bin);
  • 写入本地文件
    packages/plugin/lib/index.js:9436
    chmodSync(bin, 493);
  • 删除本地文件递归
    packages/plugin/lib/index.js:9444
    rmSync(work, { recursive: true, force: true });
  • 写入本地文件
    packages/plugin/lib/index.js:9600
    mkdirSync2(dir, { recursive: true });
  • 删除本地文件
    packages/plugin/lib/index.js:10139
    rmSync2(autoMarkerPath, { force: true });
  • 发起网络请求
    packages/plugin/lib/index.js:10197
    res = await fetch(`${httpBase}/__relay/enroll`, {
  • 发起网络请求
    packages/plugin/lib/index.js:10229
    const res = await fetch(`${httpBase}/__relay/enroll/poll`, {
  • 删除本地文件
    packages/plugin/lib/index.js:10860
    rmSync3(settingsPath(), { force: true });
  • 读取本地文件
    packages/plugin/src/index.js:41
    const v = readFileSync(pinPath(kind), 'utf8').trim();
  • 写入本地文件
    packages/plugin/src/index.js:49
    mkdirSync(dirname(pinPath(kind)), { recursive: true });
  • 写入本地文件
    packages/plugin/src/index.js:50
    writeFileSync(pinPath(kind), value, { mode: 0o600 });
  • 读取本地文件
    packages/plugin/src/index.js:108
    try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version; } catch { return ''; }
  • 写入本地文件
    packages/plugin/src/service.mjs:111
    mkdirSync(dirname(autoMarkerPath), { recursive: true });
  • 写入本地文件
    packages/plugin/src/service.mjs:112
    writeFileSync(autoMarkerPath, JSON.stringify({ at: Date.now() }), 'utf8');
  • 删除本地文件
    packages/plugin/src/service.mjs:116
    try { rmSync(autoMarkerPath, { force: true }); } catch { }
  • 发起网络请求
    packages/plugin/src/service.mjs:177
    res = await fetch(`${httpBase}/__relay/enroll`, {
  • 发起网络请求
    packages/plugin/src/service.mjs:207
    const res = await fetch(`${httpBase}/__relay/enroll/poll`, {
  • 读取本地文件
    packages/plugin/src/service.mjs:427
    if (!existsSync(r.bin)) {
  • 读取本地文件
    packages/plugin/src/service.mjs:447
    readFileSync(autoMarkerPath, 'utf8');
  • 读取本地文件
    packages/plugin/src/settings.mjs:16
    const raw = JSON.parse(readFileSync(settingsPath(), 'utf8'));
  • 写入本地文件
    packages/plugin/src/settings.mjs:23
    mkdirSync(dirname(settingsPath()), { recursive: true });
  • 写入本地文件
    packages/plugin/src/settings.mjs:24
    writeFileSync(settingsPath(), JSON.stringify(s, null, 2), { mode: 0o600 });
  • 删除本地文件
    packages/plugin/src/settings.mjs:168
    try { rmSync(settingsPath(), { force: true }); return true; } catch { return false; }
  • 写入本地文件
    packages/plugin/src/tunnel.mjs:178
    try { mkdirSync(dir, { recursive: true }); } catch (err) {
  • 写入本地文件
    packages/plugin/src/tunnel.mjs:194
    try { writeFileSync(f.path, f.content, { mode: 0o600 }); } catch (err) {
  • 读取本地文件
    packages/server/src/server.mjs:22
    return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
  • 读取密钥环境变量
    packages/server/src/server.mjs:32
    token: (process.env.RELAY_TOKEN ?? '').trim(),
  • 读取密钥环境变量
    packages/server/src/server.mjs:33
    tokenFile: (process.env.RELAY_TOKEN_FILE ?? '').trim(),
  • 读取密钥环境变量
    packages/server/src/server.mjs:34
    adminPassword: (process.env.RELAY_ADMIN_PASSWORD ?? '').trim(),
  • 读取密钥环境变量
    packages/server/src/server.mjs:35
    adminPasswordFile: (process.env.RELAY_ADMIN_PASSWORD_FILE ?? '').trim(),
  • 读取密钥环境变量
    packages/server/src/server.mjs:39
    portalPassword: (process.env.RELAY_PORTAL_PASSWORD ?? '').trim(),
  • 读取密钥环境变量
    packages/server/src/server.mjs:41
    tlsKey: (process.env.RELAY_TLS_KEY ?? '').trim(),
  • 读取本地文件
    packages/server/src/server.mjs:84
    const raw = JSON.parse(readFileSync(cfg.clientsFile, 'utf8'));
  • 读取本地文件
    packages/server/src/server.mjs:91
    try { return readFileSync(cfg.tokenFile, 'utf8').trim(); } catch { return ''; }
  • 读取本地文件
    packages/server/src/server.mjs:188
    try { adminPassword = readFileSync(cfg.adminPasswordFile, 'utf8').trim(); } catch { adminPassword = ''; }
  • 写入本地文件
    packages/server/src/server.mjs:192
    writeFileSync(cfg.adminPasswordFile, `${adminPassword}\n`, { mode: 0o600 });
  • 读取本地文件
    packages/server/src/server.mjs:217
    const saved = JSON.parse(readFileSync(cfg.settingsFile, 'utf8'));
  • 写入本地文件
    packages/server/src/server.mjs:226
    writeFileSync(`${cfg.settingsFile}.tmp`, `${JSON.stringify(settings, null, 2)}\n`, { mode: 0o600 });
  • 写入本地文件
    packages/server/src/server.mjs:227
    renameSync(`${cfg.settingsFile}.tmp`, cfg.settingsFile);
  • 写入本地文件
    packages/server/src/server.mjs:365
    writeFileSync(`${cfg.clientsFile}.tmp`, `${body}\n`, { mode: 0o600 });
  • 写入本地文件
    packages/server/src/server.mjs:366
    renameSync(`${cfg.clientsFile}.tmp`, cfg.clientsFile);
  • 读取本地文件
    packages/server/src/server.mjs:389
    const ADMIN_HTML = readFileSync(new URL('./admin.html', import.meta.url), 'utf8');
  • 写入本地文件
    packages/server/src/server.mjs:618
    try { writeFileSync(cfg.adminPasswordFile, `${adminPassword}\n`, { mode: 0o600 }); persisted = true; } catch { }
扫描于 2026/11/3(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 手机访问自建DSH
  • 流量走自有服务端
  • 不依赖第三方云

README

由 DeepSeek 最新模型翻译生成
dsh-relay 在手机上随时随地访问你自己的 DeepSeek Harness(DSH)。跑 DSH 的电脑始终主动外连 一个由你掌控的入口——自建中继服务端,或你已经在用的 frp 服务。 电脑不开任何入站端口;断网、重启、换网络都自动恢复;中间人是你自己,而不是一家隧道云。 工作原理 手机 ──HTTPS──▶ 公网入口 你的电脑(DSH)(frps 或 │ ▲ 隧道,你的中继服务端) │ ▼ 插件主动外连 ────▶ dsh-relay 插件本地反向代理 :3082 └─▶ DSH web(127.0.0.1:3080) 插件侧把 Host/Origin 等请求头改写为回环形态,让 DSH 的回环信任栅栏看到的是一个本机客户端;同时处理浏览器会话握手、跨 NAT 保活 WebSocket,并在所有公网路径上强制访问密码。手机看到的界面与电脑完全一致、实时同步。
展开完整文档(README 剩余 1 节)
dsh-relay

在手机上随时随地访问你自己的 DeepSeek Harness(DSH)。跑 DSH 的电脑始终主动外连 一个由你掌控的入口——自建中继服务端,或你已经在用的 frp 服务。
电脑不开任何入站端口;断网、重启、换网络都自动恢复;中间人是你自己,而不是一家隧道云。

工作原理

手机 ──HTTPS──▶ 公网入口 你的电脑(DSH)(frps 或 │ ▲ 隧道,你的中继服务端) │ ▼ 插件主动外连 ────▶ dsh-relay 插件本地反向代理 :3082 └─▶ DSH web(127.0.0.1:3080)

插件侧把 Host/Origin 等请求头改写为回环形态,让 DSH 的回环信任栅栏看到的是一个本机客户端;同时处理浏览器会话握手、跨 NAT 保活 WebSocket,并在所有公网路径上强制访问密码。手机看到的界面与电脑完全一致、实时同步。

功能

- 两种公网入口,随你选
- frp 隧道——复用你(或朋友)已有的 frp 服务:只填 frps 地址和 token 两个必填项,一键「验证连接」真实登录一次 frps 确认可用后再启动;远程端口可留空(frps 随机分配,插件自动发现并生成二维码)
- 自建中继服务端——一个 Docker 容器;多租户(多台电脑、密钥互相隔离)、网页管理台、申请审批接入、密钥自动轮换
- 仅局域网——同一 WiFi 扫码直连,完全不走隧道
- 全链路自愈——有界指数退避重连(最坏路径回归测试盯守 RSS 与重连速率),服务端重启、电脑重启、网络切换后自动恢复
- 纵深安全——见安全

两种接入方式对比

| | 外接隧道 | 自建中继服务端 |
| 要部署服务端吗 | 不用 | 要(一个 Docker 容器) |
| 中间人是谁 | 隧道入口的运营方:你的 frps(=你),或隧道服务商 | 你自己 |
| 一个入口带多台电脑 | 取决于隧道工具 | 支持——多租户,密钥互相隔离 |
| 管理台 / 审批 / 密钥轮换 | — | 支持 |
| 适合谁 | 一台电脑、零额外部署 | 多台电脑、需要统一管理 |

快速开始

前提:一台跑着 DSH web 的电脑;开发参与需要 Node.js ≥ 22 与 pnpm。

1. 安装插件(在跑 DSH 的电脑上)——一条命令,直接从 GitHub 装:

dsh plugin --profile web add 'github:xiaohanqing/dsh-relay#path:packages/plugin'
重启 dsh web,设置页出现「DSH Relay」

以后升级:重跑同一条命令(或 dsh plugin --profile web update dsh-relay)。

2a. 复用已有的 frp 服务(大多数人的路径):

在插件设置里把接入方式切到「frp 隧道」,只填两个必填项:

- frps 服务器地址——如 frps.example.com:7000(端口可省略,默认 7000)
- 鉴权 token

点「验证连接」会真实连接一次 frps 并测试登录,通过后「启动隧道」即可。
远程端口选填(留空 = frps 随机分配,插件自动发现实际端口并生成二维码)。
手机打开展示的公网地址(或扫码),输入访问密码即可。

2b. 或者自建中继服务端:

cd packages/server
docker compose up -d --build
docker compose logs relay | grep "admin password" # 管理台初始密码

浏览器打开 https://<服务端>:8443/__relay/admin,然后在插件设置里点「一键接入」,回管理台批准即可。详细步骤见 docs/deploy-nas.md。

安全

- 三层门槛:服务端按客户端隔离的密钥串、所有公网路径强制的 8 位访问密码(带限速锁定)、独立的管理台密码
- 密钥自动轮换:中继密钥默认 30 天轮换,10 分钟宽限期;轮换通过已认证通道推送
- 不落盘:中继服务端只做字节搬运,不保存任何业务数据
- 明文链路警示:frp 纯端口映射的手机到 frps 一段是明文,访问密码经由该链路传输。
建议 frps 前加一层 HTTPS 反代;取舍详见 docs/tunnel.md

文档

- 外接隧道指南——支持的每种工具、要填什么、安全提示
- 中继服务端部署——TLS 证书、DDNS、端口转发、nginx 反代
- 协议规格——隧道协议 v1.1(自建服务端模式)
- 部署验收清单——部署后逐项自检
- 分包说明:插件 · 服务端

许可证

MIT
预览分享卡片长什么样 ↓xiaohanqing/dsh-relay
分享 · DeepSeek Harness Hub
xiaohanqing/dsh-relay

手机随时访问你自己的 DSH —— 流量走你自己的服务端,不依赖任何第三方云。

这是你的仓库?认领条目

回到顶部