dsh-mesh-access 1.5.2 → 1.5.4

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/CHANGELOG.md CHANGED
@@ -203,3 +203,15 @@
203
203
 
204
204
  - 告诉 AI 这套流程来自 DSH 插件 dsh-mesh-access(npm 包名 / GitHub 仓库 / 离线 tgz),
205
205
  操作结果在左侧「组网访问」面板查看,并指引优先读 docs/AI-SETUP.md。
206
+
207
+ ## 1.5.3 — 重启后自动恢复组网端口
208
+
209
+ - start 成功后把端口记进侧车(meshPort);手动 stop 会清除记录。
210
+ - dsh web 启动时若侧车有记录,自动把组网口开回来(轮询等待上游端口与组网网卡就绪,
211
+ 最多 5 分钟;期间用户手动开过就不再干预)。解决升级/重启后手机突然 502 的问题。
212
+
213
+ ## 1.5.4 — 面板可临时停用 billion-context
214
+
215
+ - 新增「远程卡顿?临时停用 billion-context」卡片:一键写入/移除 profile 的
216
+ cordis.patch.yml 停用补丁(带标记,恢复时精确删除),并结束现存守护进程。
217
+ - 适用场景:组网/远程连接卡顿时减负;重启 dsh web 后彻底不加载,随时可恢复。
package/lib/client.js CHANGED
@@ -68,6 +68,8 @@ window.__ModuleLoader__.load({
68
68
  status: () => fetchJson('/api/dsh-mesh-access/status', {}, 6000),
69
69
  tunnel: () => fetchJson('/api/dsh-mesh-access/tunnel', {}, 8000),
70
70
  auth: () => fetchJson('/api/dsh-mesh-access/auth', {}, 8000),
71
+ perf: () => fetchJson('/api/dsh-mesh-access/perf', {}, 6000),
72
+ perfPost: (body) => fetchJson('/api/dsh-mesh-access/perf', { method: 'POST', body: JSON.stringify(body) }, 20000),
71
73
  authPost: (body) => fetchJson('/api/dsh-mesh-access/auth', { method: 'POST', body: JSON.stringify(body) }, 20000),
72
74
  tunnelAction: (action) =>
73
75
  fetchJson('/api/dsh-mesh-access/tunnel', {
@@ -103,6 +105,8 @@ window.__ModuleLoader__.load({
103
105
  const [authBusy, setAuthBusy] = react.useState(false)
104
106
  const [authErr, setAuthErr] = react.useState(null)
105
107
  const [authForm, setAuthForm] = react.useState({ username: '', password: '' })
108
+ const [perf, setPerf] = react.useState(null)
109
+ const [perfBusy, setPerfBusy] = react.useState(false)
106
110
  const [tab, setTab] = react.useState('guide')
107
111
  const [authMsg, setAuthMsg] = react.useState('')
108
112
  const [busy, setBusy] = react.useState(false)
@@ -129,6 +133,10 @@ window.__ModuleLoader__.load({
129
133
  .auth()
130
134
  .then((a) => setAuth(a))
131
135
  .catch(() => {})
136
+ api
137
+ .perf()
138
+ .then((pf) => setPerf(pf))
139
+ .catch(() => {})
132
140
  }, [])
133
141
 
134
142
  react.useEffect(() => {
@@ -152,6 +160,15 @@ window.__ModuleLoader__.load({
152
160
  .finally(() => setTunnelBusy(false))
153
161
  }
154
162
 
163
+ const runPerf = async (action) => {
164
+ setPerfBusy(true)
165
+ try {
166
+ const r = await api.perfPost({ action })
167
+ if (!r.ok) { window.alert(r.error || '操作失败'); return }
168
+ window.alert(r.note || '完成')
169
+ refresh()
170
+ } catch (e) { window.alert('操作失败:' + (e && e.message)) } finally { setPerfBusy(false) }
171
+ }
155
172
  const runConfig = () => {
156
173
  const payload = Object.assign({ action: 'config' }, form)
157
174
  setTunnelBusy(true)
@@ -529,6 +546,18 @@ window.__ModuleLoader__.load({
529
546
  })(),
530
547
  ),
531
548
  h(
549
+ Card, { title: '远程卡顿?临时停用 billion-context(可选)' },
550
+ h('div', { style: S.hint },
551
+ '组网/远程连接特别卡时,可临时停用 billion-context 插件(它会为每个会话开本地守护进程,远程场景下是纯开销)。停用后上下文压缩等会话功能不可用,远程更流畅。'),
552
+ perf ? h('div', { style: S.row },
553
+ h('span', { style: S.hint },
554
+ perf.bcDisabled ? '当前:已停用(重启 dsh web 后彻底不加载)' : ('当前:运行中' + (perf.bcDaemons ? '(守护 ' + perf.bcDaemons + ' 个)' : ''))),
555
+ perf.bcDisabled
556
+ ? h('button', { style: S.btn, disabled: perfBusy, onClick: () => runPerf('enable-bc') }, '恢复')
557
+ : h('button', { style: S.btnPrimary, disabled: perfBusy, onClick: () => { if (window.confirm('停用 billion-context?上下文压缩等会话功能将不可用,重启 dsh web 后彻底生效。')) runPerf('disable-bc') } }, '临时停用'),
558
+ ) : h('div', { style: S.hint }, '状态读取中…'),
559
+ ),
560
+ h(
532
561
  Card, { title: '第一次用星空组网?' },
533
562
  h('div', { style: S.hint }, '三步走,不用记 IP,也不用改任何配置。'),
534
563
  h('div', { style: S.row },
package/lib/index.js CHANGED
@@ -17,9 +17,11 @@
17
17
  */
18
18
  import os from 'node:os'
19
19
  import fs from 'node:fs'
20
+ import path from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
22
+ import { execSync } from 'node:child_process'
20
23
  import http from 'node:http'
21
24
  import net from 'node:net'
22
- import path from 'node:path'
23
25
  import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'
24
26
  import { spawn } from 'node:child_process'
25
27
 
@@ -577,7 +579,10 @@ transport.useEncryption = true
577
579
  if (starting) return starting
578
580
  starting = run(listenPort, mesh, target)
579
581
  try {
580
- return await starting
582
+ const out = await starting
583
+ // 记住「上次开着的组网口」,dsh web 重启后自动恢复(1.5.3)
584
+ if (out && out.ok) writeSidecar({ ...readSidecar(), meshPort: listenPort, meshPortAt: new Date().toISOString() })
585
+ return out
581
586
  } finally {
582
587
  starting = null
583
588
  }
@@ -705,12 +710,68 @@ transport.useEncryption = true
705
710
  const server = state.server
706
711
  state.server = null
707
712
  state.startedAt = null
713
+ // 手动关掉就不再自动恢复(1.5.3)
714
+ const side = readSidecar()
715
+ if (side.meshPort) writeSidecar({ ...side, meshPort: null, meshPortAt: null })
708
716
  if (!server) return { ok: true, ...snapshot() }
709
717
  await closeServer(server)
710
718
  state.error = null
711
719
  return { ok: true, ...snapshot() }
712
720
  }
713
721
 
722
+ // ===== 自动恢复(1.5.3):dsh web 重启后,把上次开着的组网口自动开回来 =====
723
+ // 不阻塞启动:轮询等两件事就绪——上游端口绑定、组网网卡出现(开机时 TAP 可能晚几秒)。
724
+ // 任何一次 start 失败(含网卡一直没出现)都静默放弃,等用户回面板手动开。
725
+ {
726
+ const wanted = readSidecar().meshPort
727
+ if (wanted) {
728
+ let tries = 0
729
+ const timer = setInterval(() => {
730
+ tries += 1
731
+ if (state.server || tries > 60) return clearInterval(timer) // 已开(手动)或等了 5 分钟
732
+ if (!upstreamPort()) return
733
+ start(Number(wanted))
734
+ .then((out) => { if (out?.ok) { clearInterval(timer); ctx.logger?.info?.('dsh-mesh-access: 已自动恢复组网端口 ' + wanted) } })
735
+ .catch(() => {})
736
+ }, 5000)
737
+ }
738
+ }
739
+
740
+ /* —— billion-context 临时停用(组网/远程卡顿时的减负开关)—— */
741
+ const BC_MARKER = '# dsh-mesh-access managed (perf toggle)'
742
+ const profileRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
743
+ const cordisPatch = () => path.join(profileRoot, 'cordis.patch.yml')
744
+ const bcDisabled = () => {
745
+ try { return fs.readFileSync(cordisPatch(), 'utf8').includes(BC_MARKER) } catch { return false }
746
+ }
747
+ const bcDaemonPids = () => {
748
+ try {
749
+ const out = execSync("pgrep -f 'billion-context/dist/index.js start' || true", { encoding: 'utf8' })
750
+ return out.trim() ? out.trim().split('\n') : []
751
+ } catch { return [] }
752
+ }
753
+ const bcDisable = () => {
754
+ const f = cordisPatch()
755
+ let text = ''
756
+ try { text = fs.readFileSync(f, 'utf8') } catch { text = '' }
757
+ if (!text.includes(BC_MARKER)) {
758
+ text = text.replace(/\s+$/, '\n') + `\n${BC_MARKER}\n- id: billion-context\n disabled: true\n`
759
+ fs.writeFileSync(f, text)
760
+ }
761
+ for (const pid of bcDaemonPids()) { try { process.kill(Number(pid), 'SIGTERM') } catch {} }
762
+ return { ok: true, disabled: true, note: '已写入停用补丁并结束 billion-context 守护;重启 dsh web 后彻底不加载' }
763
+ }
764
+ const bcEnable = () => {
765
+ const f = cordisPatch()
766
+ try {
767
+ let text = fs.readFileSync(f, 'utf8')
768
+ const re = new RegExp('\\n?' + BC_MARKER.replace(/[()]/g, '\\$&') + '\\n- id: billion-context\\n\\s+disabled: true\\n?', 'g')
769
+ text = text.replace(re, '\n').replace(/\s+$/, '\n')
770
+ fs.writeFileSync(f, text)
771
+ } catch {}
772
+ return { ok: true, disabled: false, note: '已移除停用补丁;重启 dsh web 后恢复加载' }
773
+ }
774
+
714
775
  ctx.effect(() => {
715
776
  const routes = [
716
777
  {
@@ -735,6 +796,24 @@ transport.useEncryption = true
735
796
  return json(res, 400, { ok: false, error: "action 只能是 'start' 或 'stop'" })
736
797
  },
737
798
  },
799
+ {
800
+ kind: 'exact',
801
+ path: '/api/dsh-mesh-access/perf',
802
+ handler: async (req, res) => {
803
+ if (req.method === 'GET') {
804
+ return json(res, 200, { ok: true, bcDisabled: bcDisabled(), bcDaemons: bcDaemonPids().length })
805
+ }
806
+ if (req.method === 'POST') {
807
+ let body = {}
808
+ try { body = JSON.parse((await readBody(req)) || '{}') } catch { return json(res, 400, { ok: false, error: 'body 不是合法 JSON' }) }
809
+ const action = String(body.action ?? '')
810
+ if (action === 'disable-bc') return json(res, 200, bcDisable())
811
+ if (action === 'enable-bc') return json(res, 200, bcEnable())
812
+ return json(res, 400, { ok: false, error: "action 只能是 'disable-bc' 或 'enable-bc'" })
813
+ }
814
+ return json(res, 405, { ok: false, error: 'GET/POST only' })
815
+ },
816
+ },
738
817
  {
739
818
  kind: 'exact',
740
819
  path: '/api/dsh-mesh-access/auth',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mesh-access",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Control your DSH from your phone: opens a port on the mesh/VPN interface from inside the running server process, so a phone browser gets the same instance and the same session list — no SSH, no second dsh. Also reachable from another PC, a GUI NAS, or a TV box.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",