@mrrisega/dsh-remote 0.6.10-beta.1 → 0.6.10-beta.2
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.
|
@@ -135,11 +135,17 @@ const TOKEN = process.env.DSH_BRIDGE_TOKEN || "";
|
|
|
135
135
|
function machineUniqueId() {
|
|
136
136
|
if (process.env.DSH_BRIDGE_MACHINE_FP) return String(process.env.DSH_BRIDGE_MACHINE_FP).slice(0, 64);
|
|
137
137
|
if (process.platform === "darwin") {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
// ⚠️ 必须用**绝对路径**:由 launchd 拉起时 PATH 来自 plist(实测 /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin),
|
|
139
|
+
// **不含 /usr/sbin** —— 而 ioreg 恰好就在那里。用裸名会得到
|
|
140
|
+
// "/bin/sh: ioreg: command not found"(真机日志实测),于是这里静默返回 "",
|
|
141
|
+
// 指纹退化成 hostname 哈希:用户改一次主机名就被服务端当成新设备 —— 与 Windows 那条是同一个 bug。
|
|
142
|
+
for (const ioreg of ["/usr/sbin/ioreg", "ioreg"]) {
|
|
143
|
+
try {
|
|
144
|
+
const out = execSync(`${ioreg} -rd1 -c IOPlatformExpertDevice`, { encoding: "utf8", timeout: 5000 });
|
|
145
|
+
const m = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out);
|
|
146
|
+
if (m && m[1]) return m[1];
|
|
147
|
+
} catch { /* 换下一个候选 */ }
|
|
148
|
+
}
|
|
143
149
|
} else if (process.platform === "linux") {
|
|
144
150
|
for (const f of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
145
151
|
try {
|
|
@@ -157,14 +163,20 @@ function machineUniqueId() {
|
|
|
157
163
|
// · PowerShell 在受限语言模式 / AppLocker 的企业机器上可能被策略直接禁掉,
|
|
158
164
|
// 而拿不到指纹时只能退回 hostname(用户一改主机名就被当成新设备)—— 正是要修的问题;
|
|
159
165
|
// · 走 spawnSync 传数组参数,不经过 cmd.exe,因而没有引号/空格/中文路径的转义坑。
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
// 同样不赌 PATH:优先 System32 下的绝对路径,取不到再退回裸名(macOS 那条就是这么栽的)。
|
|
167
|
+
const regCandidates = [];
|
|
168
|
+
if (process.env.SystemRoot) regCandidates.push(`${process.env.SystemRoot}\\System32\\reg.exe`);
|
|
169
|
+
regCandidates.push("reg");
|
|
170
|
+
for (const reg of regCandidates) {
|
|
171
|
+
try {
|
|
172
|
+
const r = spawnSync(reg, ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
|
|
173
|
+
{ encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
174
|
+
const m = r && r.status === 0
|
|
175
|
+
? /MachineGuid\s+REG_SZ\s+([0-9a-fA-F-]{16,})/.exec(String(r.stdout || ""))
|
|
176
|
+
: null;
|
|
177
|
+
if (m && m[1]) return m[1];
|
|
178
|
+
} catch { /* 换下一个候选 */ }
|
|
179
|
+
}
|
|
168
180
|
}
|
|
169
181
|
return ""; // 读不到(如容器/受限环境) → 回退宿主名指纹
|
|
170
182
|
}
|
|
@@ -42,11 +42,13 @@ function loadMachineUniqueId({
|
|
|
42
42
|
assert.ok(!/稳定指纹/.test(src), "切片不应越过 machineUniqueId 的边界");
|
|
43
43
|
|
|
44
44
|
const calls = [];
|
|
45
|
+
const execCalls = [];
|
|
45
46
|
const fakeSpawnSync = (cmd, args, opts) => {
|
|
46
47
|
calls.push({ cmd, args, opts });
|
|
47
48
|
if (regThrows) throw new Error("spawn EPERM");
|
|
48
49
|
return reg;
|
|
49
50
|
};
|
|
51
|
+
const fakeExecSync = (cmd) => { execCalls.push(String(cmd)); return ioreg; };
|
|
50
52
|
const fakeFs = {
|
|
51
53
|
readFileSync: (f) => {
|
|
52
54
|
if (f in machineIdFiles) return machineIdFiles[f];
|
|
@@ -55,8 +57,8 @@ function loadMachineUniqueId({
|
|
|
55
57
|
};
|
|
56
58
|
const make = new Function("process", "execSync", "spawnSync", "fs", "os",
|
|
57
59
|
`${src}\nreturn machineUniqueId;`);
|
|
58
|
-
const fn = make({ platform, env },
|
|
59
|
-
return { fn, calls };
|
|
60
|
+
const fn = make({ platform, env }, fakeExecSync, fakeSpawnSync, fakeFs, {});
|
|
61
|
+
return { fn, calls, execCalls };
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
test("win32:机器指纹取注册表 MachineGuid(改主机名不再被当成新设备)", () => {
|
|
@@ -100,6 +102,9 @@ test("机器指纹:darwin/linux 两条老分支没有被改坏", () => {
|
|
|
100
102
|
ioreg: ' "IOPlatformUUID" = "AAAA-BBBB-CCCC"\n',
|
|
101
103
|
});
|
|
102
104
|
assert.equal(darwin.fn(), "AAAA-BBBB-CCCC", "darwin 仍读 ioreg 的 IOPlatformUUID");
|
|
105
|
+
assert.match(darwin.execCalls[0], /^\/usr\/sbin\/ioreg /,
|
|
106
|
+
"★必须用绝对路径调 ioreg:launchd 的 plist PATH 不含 /usr/sbin,用裸名会 command not found,",
|
|
107
|
+
);
|
|
103
108
|
|
|
104
109
|
const linux = loadMachineUniqueId({
|
|
105
110
|
platform: "linux",
|
|
@@ -108,6 +113,29 @@ test("机器指纹:darwin/linux 两条老分支没有被改坏", () => {
|
|
|
108
113
|
assert.equal(linux.fn(), "0123456789abcdef", "linux 仍读 /etc/machine-id(并去掉换行)");
|
|
109
114
|
});
|
|
110
115
|
|
|
116
|
+
test("★回归:子进程一律用绝对路径优先,不赌 PATH(launchd 的 PATH 不含 /usr/sbin 的真实事故)", () => {
|
|
117
|
+
// 事故(2026-09-22 真机日志):bridge 由 launchd 拉起,plist 给的 PATH 是
|
|
118
|
+
// /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin —— 没有 /usr/sbin,而 ioreg 在那里。
|
|
119
|
+
// 日志里出现 `/bin/sh: ioreg: command not found`,指纹**静默**退化成 hostname 哈希:
|
|
120
|
+
// 用户改一次主机名就被服务端当成新设备。这类"PATH 假设"必须用绝对路径堵死。
|
|
121
|
+
const mac = loadMachineUniqueId({ platform: "darwin", ioreg: 'x "IOPlatformUUID" = "U1"\n' });
|
|
122
|
+
mac.fn();
|
|
123
|
+
assert.match(mac.execCalls[0], /^\/usr\/sbin\/ioreg /, "darwin:首个候选必须是 /usr/sbin/ioreg");
|
|
124
|
+
|
|
125
|
+
// 绝对路径取不到时仍要退回裸名(别把别的机型的 macOS 弄死)
|
|
126
|
+
const macFallback = loadMachineUniqueId({ platform: "darwin", ioreg: "" });
|
|
127
|
+
assert.equal(macFallback.fn(), "", "两次都拿不到 → 空串(不是抛)");
|
|
128
|
+
assert.equal(macFallback.execCalls.length, 2, "应依次尝试绝对路径与裸名");
|
|
129
|
+
assert.match(macFallback.execCalls[1], /^ioreg /, "第二个候选是裸名");
|
|
130
|
+
|
|
131
|
+
// Windows 同理:SystemRoot 可用时优先 System32 的绝对路径
|
|
132
|
+
const win = loadMachineUniqueId({ platform: "win32", env: { SystemRoot: "C:\\Windows" } });
|
|
133
|
+
win.fn();
|
|
134
|
+
assert.equal(win.calls[0].cmd, "C:\\Windows\\System32\\reg.exe", "win32:应优先 System32 下的绝对路径");
|
|
135
|
+
assert.deepEqual(win.calls[0].args, ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"]);
|
|
136
|
+
assert.equal(win.calls[0].opts.windowsHide, true, "绝对路径候选同样必须 windowsHide");
|
|
137
|
+
});
|
|
138
|
+
|
|
111
139
|
test("机器指纹:拿不到机器唯一值时才退回宿主名哈希(这正是要修的退化路径)", () => {
|
|
112
140
|
// 断言 machineFingerprint 的兜底表达式仍然存在 —— 它是"改主机名 = 新设备"的直接来源,
|
|
113
141
|
// 修好 win32 分支后这条兜底只应在容器等极端环境生效。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.6.10-beta.
|
|
3
|
+
"version": "0.6.10-beta.2",
|
|
4
4
|
"description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -2453,7 +2453,7 @@ const PLUGIN_ID = "dsh-remote-web";
|
|
|
2453
2453
|
const PLUGIN_LEGACY_IDS = ["dsh-remote-ui"];
|
|
2454
2454
|
const PLUGIN_ALL_IDS = [PLUGIN_ID, ...PLUGIN_LEGACY_IDS];
|
|
2455
2455
|
/** 插件自身发布版本(与 dsh-remote 根包同步递增)。 */
|
|
2456
|
-
const PLUGIN_VERSION = "0.6.10-beta.
|
|
2456
|
+
const PLUGIN_VERSION = "0.6.10-beta.2";
|
|
2457
2457
|
const UPDATE_LOG = ".dsh-update.log";
|
|
2458
2458
|
const UPDATE_MARKER = ".dsh-update-running";
|
|
2459
2459
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-web",
|
|
3
|
-
"version": "0.6.10-beta.
|
|
3
|
+
"version": "0.6.10-beta.2",
|
|
4
4
|
"description": "公网远程控制 DeepSeek Harness(dsh web):安装即得专属加密地址,人在外面也能用手机访问电脑上的 dsh——无需同一局域网/WiFi、无需公网 IP 与内网穿透,全程加密;手机端 100% 还原电脑体验(对话/工具/审批/设置)。技术用户可选自建服务,流量走自己的服务器。Remote control DeepSeek Harness (dsh web) from anywhere over the public internet — install, get an encrypted URL, use it from your phone on any network (dual-half cordis plugin: Settings panel + same-origin /dsh-remote routes). (2026-09 由 dsh-remote-ui 更名 / renamed from dsh-remote-ui; dsh-remote 的 dsh web 插件半,不是纯 UI/皮肤插件)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|