dsh-plugin-mobile-gateway 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  - WebSocket:`/ws/mobile`
10
10
  - 局域网:`ws://<局域网 IP>:3081/ws/mobile`
11
- - 公网:`wss://<公网 IP>/ws/mobile`
11
+ - Linux 服务器公网:`wss://<公网 IP>/ws/mobile`
12
12
  - 协议文档:[PROTOCOL.md](PROTOCOL.md)
13
13
 
14
14
  ## 配套 iOS 客户端
@@ -30,7 +30,7 @@
30
30
 
31
31
  ## 安装插件
32
32
 
33
- 前提:已经安装 `dsh` CLI,并能正常启动 `dsh web`。
33
+ 前提:已经安装 `dsh` CLI `pnpm`,并能正常启动 `dsh web`。可先执行 `pnpm --version` 确认当前用户的环境能够找到 `pnpm`。
34
34
 
35
35
  局域网使用只需安装插件:
36
36
 
@@ -38,12 +38,14 @@
38
38
  dsh plugin --profile web add dsh-plugin-mobile-gateway@latest
39
39
  ```
40
40
 
41
- 需要公网接入时,推荐执行统一初始化命令。它会安装/更新插件,并请求一次 sudo 权限安装系统 Helper:
41
+ 需要在 Linux 服务器通过公网 IP 接入时,推荐执行统一初始化命令。它会安装/更新插件,并请求一次 sudo 权限安装系统 Helper:
42
42
 
43
43
  ```bash
44
44
  npx --yes dsh-plugin-mobile-gateway@latest init
45
45
  ```
46
46
 
47
+ `init` 会把当前正在执行的 npm 包精确版本安装到 DSH profile,并仅对该版本跳过 pnpm 的新版本等待期,确保插件与 Helper 版本一致。
48
+
47
49
  安装后停止并重新启动 WebUI:
48
50
 
49
51
  ```bash
@@ -66,35 +68,41 @@ dsh web
66
68
 
67
69
  如果系统防火墙拦截连接,只允许私有网络访问 TCP `3081`。不要把 3081 开放到公网。
68
70
 
69
- ## 公网 IP 配对
71
+ ## Linux 服务器公网 IP 配对
72
+
73
+ > [!NOTE]
74
+ > 本文所说的“公网 IP 配对”特指 Linux 服务器。一键公网安装从 `v0.6.4` 开始提供,适用于带固定公网 IPv4 的 Ubuntu/Debian 服务器。服务器需要已经安装 Node.js、`pnpm` 和 `dsh` CLI;当前尚不支持 CentOS。
70
75
 
71
- 适用于带固定公网 IPv4 的 Ubuntu/Debian 服务器(其他发行版本可自行尝试)。Node.js 通过 nvm 安装时,也使用下面的命令。
76
+ ### 1. 准备公网端口
72
77
 
73
- ### 1. 配置端口
78
+ 在云厂商控制台复制服务器的公网 IPv4,并在安全组中放行入站 TCP `80` 和 `443`。不要将 DSH WebUI 端口或 TCP `3081` 开放到公网。
74
79
 
75
- 从云厂商控制台复制服务器的公网 IPv4,并在安全组中放行入站 TCP `80` 和 `443`。
80
+ ### 2. 一键初始化
76
81
 
77
- 不要开放 TCP `3080` `3081`!!!
78
- 不要开放 TCP `3080` 和 `3081`!!!
79
- 不要开放 TCP `3080` 和 `3081`!!!
82
+ 先确认普通 DSH 用户可以直接调用 `pnpm`:
80
83
 
81
- ### 2. 配置公网入口
84
+ ```bash
85
+ pnpm --version
86
+ ```
82
87
 
83
- 执行一次初始化(已经执行过可跳过):
88
+ 然后使用同一个普通用户执行(不要使用 `root` 或 `sudo npx`):
84
89
 
85
90
  ```bash
91
+ npm_config_registry=https://registry.npmjs.org \
86
92
  npx --yes dsh-plugin-mobile-gateway@latest init
87
93
  ```
88
94
 
89
- 然后启动或重新启动:
95
+ 该命令会安装或更新插件,并请求一次 sudo 权限安装 Nginx、Certbot、系统 Helper 和证书续期定时器。`init` 会确保 DSH profile 与 Helper 使用同一个精确版本。
96
+
97
+ 完成后启动或重新启动 WebUI:
90
98
 
91
99
  ```bash
92
100
  dsh web
93
101
  ```
94
102
 
95
- ### 3. 打开远程 WebUI(使用端口转发,VSCode等IDE自带)
103
+ ### 3. 打开远程 WebUI
96
104
 
97
- 如果 WebUI 运行在远程服务器,在自己的电脑执行:
105
+ 优先使用 VS Code、Cursor 等 IDE 自带的端口转发。也可以在自己的电脑执行:
98
106
 
99
107
  ```bash
100
108
  ssh -N -L <本地端口>:127.0.0.1:<DSH 实际端口> <服务器用户名>@<服务器公网 IP>
@@ -106,18 +114,67 @@ ssh -N -L <本地端口>:127.0.0.1:<DSH 实际端口> <服务器用户名>@<服
106
114
  http://127.0.0.1:<本地端口>
107
115
  ```
108
116
 
109
- ### 4. 使用 WebUI 配对
117
+ ### 4. UI 配置公网入口
118
+
119
+ 打开左侧的“移动设备”,在“公网接入”填写云厂商控制台提供的公网 IPv4,然后点击“配置公网接入”或“更新公网配置”。Helper 会自动读取当前 `dsh web` 端口并配置 Nginx、TLS 证书和 `wss://<公网 IP>/ws/mobile`。
110
120
 
111
- 1. 打开“移动设备”,在“公网接入”中填写云厂商控制台提供的公网 IPv4。
112
- 2. 点击“配置公网接入”。Helper 会自动使用当前 `dsh web` 端口配置 Nginx 和证书。
113
- 3. 开启“允许移动设备连接”,保持“设备鉴权”开启。
114
- 4. 填写设备名称并点击“生成配对二维码”。
115
- 5. iPhone 打开“设备认证”并扫描二维码。
116
- 6. WebUI 的可信设备显示“在线”后即完成。
121
+ <p align="center">
122
+ <img src="docs/assets/public-access-ui.png" alt="在移动设备面板配置公网接入" width="420">
123
+ </p>
124
+
125
+ ### 5. 配对移动设备
126
+
127
+ 1. 开启“允许移动设备连接”,保持“设备鉴权”开启。
128
+ 2. 填写设备名称并点击“生成配对二维码”。
129
+ 3. iPhone 打开“设备认证”并扫描二维码。
130
+ 4. WebUI 的可信设备显示“在线”后即完成。
117
131
 
118
132
  二维码只能使用一次,并会在 5 分钟后过期;超时后在 WebUI 重新生成即可。
119
133
 
120
- ## 公网入口管理
134
+ ## Windows / macOS 家用电脑远程连接
135
+
136
+ 家用电脑通常没有固定公网 IP,不建议配置路由器端口转发。可以使用 Tailscale 长期连接,或使用 Cloudflare Quick Tunnel 临时调试。两种方式都转发到插件专用的 `3081` 端口,不会公开 DSH WebUI。
137
+
138
+ 使用前先启动 `dsh web`,并在“移动设备”面板开启“允许移动设备连接”和“设备鉴权”。
139
+
140
+ ### Tailscale(推荐长期使用)
141
+
142
+ 1. 在电脑和 iPhone 安装 [Tailscale](https://tailscale.com/download),并登录同一个 Tailnet。
143
+ 2. 在 Windows PowerShell 或 macOS 终端执行:
144
+
145
+ ```bash
146
+ tailscale serve --bg 3081
147
+ ```
148
+
149
+ 3. 执行 `tailscale serve status` 查看生成的 `https://<设备名>.<tailnet>.ts.net` 地址。
150
+ 4. 将地址改为 `wss://<设备名>.<tailnet>.ts.net/ws/mobile`,填入 WebUI 的“WebSocket 地址”,再生成二维码配对。
151
+
152
+ Tailscale Serve 只允许同一 Tailnet 中符合访问规则的设备连接,并自动提供 HTTPS。可用 `tailscale serve reset` 停止转发。参见 [Tailscale Serve 文档](https://tailscale.com/docs/reference/tailscale-cli/serve)。
153
+
154
+ ### Cloudflare Quick Tunnel(仅临时调试)
155
+
156
+ 1. 安装 [cloudflared](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/)。
157
+ 2. 在 Windows PowerShell 或 macOS 终端执行:
158
+
159
+ ```bash
160
+ cloudflared tunnel --url http://127.0.0.1:3081
161
+ ```
162
+
163
+ 3. 命令行会显示随机的 `https://<随机名称>.trycloudflare.com` 地址。
164
+ 4. 将地址改为 `wss://<随机名称>.trycloudflare.com/ws/mobile`,填入 WebUI 的“WebSocket 地址”,再生成二维码配对。
165
+
166
+ 保持该命令运行;停止命令后隧道立即失效。Quick Tunnel 的地址每次可能变化,且没有可用性保证,不适合正式或长期使用。公网调试时必须保持“设备鉴权”开启。参见 [Cloudflare Quick Tunnel 文档](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)。
167
+
168
+ ## 网关配置方式总览
169
+
170
+ | 使用场景 | 推荐入口 | iOS WebSocket 地址 | 需要的额外配置 | 端口与鉴权 |
171
+ |---|---|---|---|---|
172
+ | 同一局域网 | 插件局域网入口 | `ws://<电脑局域网 IP>:3081/ws/mobile` | 无需 Helper 或 Nginx;电脑与 iPhone 位于可互访的局域网 | 仅对私有网络放行 TCP `3081`;保持鉴权开启 |
173
+ | 本机 iOS 模拟器 | DSH WebUI 本地入口 | `ws://127.0.0.1:<DSH WebUI 端口>/ws/mobile` | 无需 Helper、Nginx 或独立的 `3081` 端口 | 不开放任何外部端口;仅 Debug 时可关闭鉴权 |
174
+ | Linux 公网服务器 | 插件 Helper + Nginx + TLS | `wss://<服务器公网 IPv4>/ws/mobile` | 执行 `init`,再从 WebUI 填写公网 IPv4 | 云安全组放行 TCP `80/443`;不要公开 DSH 端口和 `3081`;必须鉴权 |
175
+ | 家用 Windows / macOS | Tailscale Serve;临时调试可用 Quick Tunnel | `wss://<Tailscale 域名>/ws/mobile` 或 `wss://<随机名称>.trycloudflare.com/ws/mobile` | 隧道转发到 `127.0.0.1:3081`,将生成的地址填入 WebUI | 无需路由器端口转发;保持鉴权开启 |
176
+
177
+ ## Linux 服务器公网入口管理
121
178
 
122
179
  查看状态:
123
180
 
@@ -160,8 +217,8 @@ sudo env "PATH=$PATH" npx --yes dsh-plugin-mobile-gateway@latest remove-helper
160
217
  | WebUI 没有“移动设备” | 确认安装在 `web` profile,并完整重启 `dsh web` |
161
218
  | iOS 收到 `503` | 回到 WebUI 开启“允许移动设备连接” |
162
219
  | iOS 收到 `401` | 在 WebUI 重新生成二维码并配对 |
163
- | 公网连接超时 | 检查云安全组、服务器防火墙和 TCP `80/443` |
164
- | 公网地址没有自动显示 | 确认已执行 `setup --ip <公网 IP>`,然后重启 `dsh web` |
220
+ | Linux 服务器公网连接超时 | 检查云安全组、服务器防火墙和 TCP `80/443` |
221
+ | Linux 服务器公网地址没有显示 | 在“移动设备 公网接入”填写公网 IPv4 并点击更新 |
165
222
  | 需要查看服务端日志 | 执行 `tail -f /tmp/mobile-gateway.log` |
166
223
 
167
224
  ## 源码开发
package/bin/setup-ip.mjs CHANGED
@@ -23,6 +23,21 @@ const HELPER_INSTALL = '/usr/local/libexec/dsh-mobile-gateway-helper'
23
23
  const HELPER_SERVICE = '/etc/systemd/system/dsh-mobile-gateway-helper.service'
24
24
  const HELPER_SOCKET = '/run/dsh-mobile-gateway/helper.sock'
25
25
 
26
+ function currentPackageSpec() {
27
+ const manifest = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
28
+ if (typeof manifest.name !== 'string' || !manifest.name || typeof manifest.version !== 'string' || !manifest.version) {
29
+ throw new Error('package manifest is missing name or version')
30
+ }
31
+ return `${manifest.name}@${manifest.version}`
32
+ }
33
+
34
+ function pluginInstallArgs(packageSpec = currentPackageSpec()) {
35
+ return [
36
+ 'plugin', '--profile', 'web', 'add', packageSpec,
37
+ `--config.minimum-release-age-exclude=${packageSpec}`,
38
+ ]
39
+ }
40
+
26
41
  function printHelp() {
27
42
  console.log(`Usage:
28
43
  dsh-plugin-mobile-gateway setup [--ip <public IPv4>] [--port 3080] [--email <address>] [--yes]
@@ -134,7 +149,8 @@ async function init() {
134
149
  if (!commandExists('dsh') || !commandExists('sudo')) {
135
150
  throw new Error('init requires dsh and sudo on PATH')
136
151
  }
137
- run('dsh', ['plugin', '--profile', 'web', 'add', 'dsh-plugin-mobile-gateway@latest'])
152
+ const packageSpec = currentPackageSpec()
153
+ run('dsh', pluginInstallArgs(packageSpec))
138
154
  const script = fileURLToPath(import.meta.url)
139
155
  run('sudo', ['env', `PATH=${process.env.PATH || ''}`, process.execPath, script, 'setup-helper'])
140
156
  console.log('\nInitialization completed. Start or restart DSH with: dsh web')
@@ -419,9 +435,11 @@ async function remove(options) {
419
435
  export {
420
436
  assertPublicIpv4,
421
437
  certName,
438
+ currentPackageSpec,
422
439
  nginxHttpConfig,
423
440
  nginxTlsConfig,
424
441
  parseArgs,
442
+ pluginInstallArgs,
425
443
  helperService,
426
444
  renewalService,
427
445
  renewalTimer,
Binary file
package/lib/index.mjs CHANGED
@@ -689,7 +689,9 @@ async function handleQuery(api, typertGateway, agentDefaultModel, msg) {
689
689
  const execution = await typertGateway.invoke({
690
690
  namespace: 'commands',
691
691
  method: 'execute',
692
- args: { agentId: sessionId.value, line: '/permission ' + name },
692
+ // commands/execute 的当前 Typert descriptor 要求 images 字段始终存在;
693
+ // 权限斜杠命令没有附件,因此显式传空数组。
694
+ args: { agentId: sessionId.value, line: '/permission ' + name, images: [] },
693
695
  signal: new AbortController().signal,
694
696
  })
695
697
  if (execution === undefined || execution === null) {
@@ -1178,7 +1180,12 @@ const plugin = {
1178
1180
  }
1179
1181
  }
1180
1182
 
1181
- if (gatewayEnabled) setGatewayEnabled(true, 'enabled by startup config')
1183
+ // Startup-config enablement (gatewayEnabled: true in cordis.patch.yml) is a
1184
+ // standing-service decision made by the operator: do NOT arm the no-device
1185
+ // auto-close. That safety net stays attached to the ephemeral panel toggle,
1186
+ // where it belongs. gatewayEnabled is already true from options at this point,
1187
+ // so nothing else is needed here beyond the log line.
1188
+ if (gatewayEnabled) log('mobile gateway enabled by startup config: standing mode, no auto-close timer')
1182
1189
 
1183
1190
  log(`applying: path=${wsPath}, webServer.port=${webServer.port}, gatewayEnabled=${gatewayEnabled}, requireAuth=${requireAuth}, devices=${registry.count()}`)
1184
1191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-mobile-gateway",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "update readme",
5
5
  "main": "lib/index.mjs",
6
6
  "files": [