terminal-bridge-setup 3.3.0 → 3.3.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.
package/bin/setup.mjs CHANGED
@@ -42,6 +42,33 @@ const step = (n, total, msg) => console.log(`\n${c.bold(c.cyan(`[${n}/${total}]`
42
42
  const STEPS = 5;
43
43
 
44
44
  // ===================== 步骤 1:释放文件 =====================
45
+ // 升级安装前停掉旧代理实例:读 .proxy.pid 杀进程(跨平台 process.kill)。
46
+ // 不停的话旧实例一直占 8787,新代理启动即 EADDRINUSE 异常退出(Windows 实测踩过)
47
+ function stopOldProxy() {
48
+ const pidFile = join(INSTALL_DIR, "proxy", ".proxy.pid");
49
+ if (!existsSync(pidFile)) return;
50
+ let pid;
51
+ try {
52
+ pid = Number(String(readFileSync(pidFile, "utf8")).trim());
53
+ } catch { return; }
54
+ if (!Number.isInteger(pid) || pid <= 0) return;
55
+ try {
56
+ process.kill(pid, "SIGTERM");
57
+ console.log(c.yellow(` 已停止旧代理实例 (pid ${pid})`));
58
+ // 给旧进程一点退出时间,避免端口未释放
59
+ const deadline = Date.now() + 2000;
60
+ while (Date.now() < deadline) {
61
+ try { process.kill(pid, 0); Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100); }
62
+ catch { break; } // 进程已退出
63
+ }
64
+ } catch (err) {
65
+ // ESRCH=进程不存在(正常);EPERM=权限不足(尽力而为,不阻断安装)
66
+ if (err.code !== "ESRCH") {
67
+ console.log(c.yellow(` 旧代理 (pid ${pid}) 停止失败(${err.code || err.message}),如新代理启动报端口占用请手动结束 node 进程`));
68
+ }
69
+ }
70
+ }
71
+
45
72
  function releaseFiles() {
46
73
  step(1, STEPS, `释放文件到 ${c.dim(INSTALL_DIR)}`);
47
74
 
@@ -54,10 +81,12 @@ function releaseFiles() {
54
81
  if (existsSync(INSTALL_DIR)) {
55
82
  if (isForce) {
56
83
  console.log(c.yellow(" 已存在 ~/.terminal-bridge/,--force 模式:覆盖"));
84
+ stopOldProxy();
57
85
  rmSync(INSTALL_DIR, { recursive: true, force: true });
58
86
  } else {
59
87
  // 不强制覆盖时,仍然更新文件(保留 .proxy.pid/.proxy.log 等运行时产物)
60
88
  console.log(c.yellow(" ~/.terminal-bridge/ 已存在,将更新文件(运行时产物保留)"));
89
+ stopOldProxy();
61
90
  }
62
91
  }
63
92
 
@@ -78,14 +107,19 @@ function releaseFiles() {
78
107
 
79
108
  // ===================== 步骤 2:装代理依赖 =====================
80
109
  function installProxyDeps() {
81
- step(2, STEPS, "安装代理依赖 (ws)");
110
+ step(2, STEPS, "安装代理依赖 (ws + @msgpack/msgpack)");
82
111
 
83
- // 如果 node_modules/ws 已存在(从包里带出来的),跳过
112
+ // 跳过条件:两个依赖都存在才跳过。只查 ws 会漏掉 @msgpack/msgpack——
113
+ // 旧版本装的 node_modules 只有 ws,升级后代理启动即 ERR_MODULE_NOT_FOUND
84
114
  const wsPath = join(INSTALL_DIR, "proxy", "node_modules", "ws");
85
- if (existsSync(wsPath)) {
86
- ok("依赖已存在,跳过");
115
+ const msgpackPath = join(INSTALL_DIR, "proxy", "node_modules", "@msgpack", "msgpack");
116
+ if (existsSync(wsPath) && existsSync(msgpackPath)) {
117
+ ok("依赖已存在(ws + @msgpack/msgpack),跳过");
87
118
  return;
88
119
  }
120
+ if (existsSync(wsPath) && !existsSync(msgpackPath)) {
121
+ console.log(c.yellow(" 检测到 ws 存在但 @msgpack/msgpack 缺失(旧版本残留),补装"));
122
+ }
89
123
 
90
124
  const proxyDir = join(INSTALL_DIR, "proxy");
91
125
 
@@ -124,7 +158,13 @@ function installProxyDeps() {
124
158
  console.error("");
125
159
  fail("npm install 失败(见上方排查建议)");
126
160
  }
127
- ok("依赖安装完成");
161
+ // 装完验证两个依赖真实落盘(防 npm 静默半装/registry 缺包)
162
+ if (!existsSync(wsPath) || !existsSync(msgpackPath)) {
163
+ console.error(c.red(" ✗ 依赖验证失败:ws 或 @msgpack/msgpack 未落盘"));
164
+ console.error(c.yellow(` 手动修复:cd "${join(INSTALL_DIR, "proxy")}" && npm install`));
165
+ fail("依赖安装不完整");
166
+ }
167
+ ok("依赖安装完成(已验证 ws + @msgpack/msgpack)");
128
168
  }
129
169
 
130
170
  // ===================== 步骤 3:注册 native host =====================
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "JumpServer 终端桥接",
4
- "version": "3.3.0",
4
+ "version": "3.3.2",
5
5
  "description": "终端桥接:JumpServer / Arthas / Yearning,让 Agent 能通过浏览器执行命令、收结果与查数据",
6
6
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsnqR6PcFUueZwYria79tVbstvjk+tM7PpvIXILm5xbd6bAdjDIhzg3lsnKioVfvxjfvT+s6vJsiOYa9ojVZyJMFc5m/05TYqr770ovYwQmz0e88fmiy6dUoSulbtKvBCSLbN6OOL7u+ul8ixLZ/HautxSmou/eNgAFPmhE+4UueE7wfCqcgMYvjLvEzlqTVumMW+5LKw9YsRk6WhHPghY1a3MVUn3eQOWXBtQTEUy3wBM3v4wHxLwDeinVOR4f/P87IlUNo84C5DeimoFit0qCj3K04hS8MIYCLCYZc3v9ftRJDJBkAoah6Eaqj7JajbS3KvLR1ctOYfDhubgmURQIDAQAB",
7
7
  "permissions": ["debugger", "tabs", "alarms", "webNavigation", "nativeMessaging", "scripting", "downloads", "storage"],
@@ -13,7 +13,6 @@
13
13
 
14
14
  import { WebSocketServer } from "ws";
15
15
  import { randomBytes } from "node:crypto";
16
- import { decode as msgpackDecode } from "@msgpack/msgpack";
17
16
  import { writeFileSync, unlinkSync } from "node:fs";
18
17
  import { fileURLToPath } from "node:url";
19
18
 
@@ -34,6 +33,17 @@ const PROBE_LOG = process.env.PROBE_LOG !== "0"; // 默认开探针日志
34
33
 
35
34
  const TAG = "[proxy]";
36
35
 
36
+ // msgpack 只有 Yearning 结果解析用到,终端桥接(JumpServer/Arthas)不需要。
37
+ // 按需动态加载:缺依赖时降级为告警 + Yearning 功能不可用,而不是整个代理
38
+ // 启动崩溃(Windows 用户 npm install 缺包时曾把终端功能一起拖死,实测踩过)。
39
+ let msgpackDecode = null;
40
+ try {
41
+ ({ decode: msgpackDecode } = await import("@msgpack/msgpack"));
42
+ } catch {
43
+ console.warn(TAG, "⚠ @msgpack/msgpack 未安装:Yearning 查询结果解析不可用(终端命令不受影响)");
44
+ console.warn(TAG, "⚠ 修复:cd ~/.terminal-bridge/proxy && npm install");
45
+ }
46
+
37
47
  // ===================== 客户端管理 =====================
38
48
  // 一个端点两类客户端:插件(唯一)和 Agent(多个)。
39
49
  // 我们不严格区分谁连进来,靠消息 type 路由。
@@ -151,6 +161,7 @@ function feedYearningWaiters(payload) {
151
161
  const opcode = payload && payload.opcode;
152
162
  const data = payload && payload.data;
153
163
  if (opcode !== 2 || typeof data !== "string") return;
164
+ if (!msgpackDecode) return; // msgpack 缺失时 Yearning 解析不可用(启动时已告警)
154
165
  let obj = null;
155
166
  try {
156
167
  obj = msgpackDecode(Buffer.from(data, "base64"));
@@ -917,6 +928,17 @@ function finalizeOutput(entry) {
917
928
 
918
929
  // ===================== WebSocket 服务 =====================
919
930
  const wss = new WebSocketServer({ host: HOST, port: PORT });
931
+ // 绑定失败(典型:升级安装时旧代理实例还在占端口)必须给出可读原因再退出,
932
+ // 否则 Windows 上表现为"node 抛异常退出",用户无从下手
933
+ wss.on("error", (err) => {
934
+ if (err && err.code === "EADDRINUSE") {
935
+ console.error(TAG, `端口 ${PORT} 已被占用:很可能有一个旧代理实例还在运行。`);
936
+ console.error(TAG, "请先停止旧代理(插件 popup「停止代理」,或任务管理器结束对应 node 进程)后重试。");
937
+ } else {
938
+ console.error(TAG, "代理启动失败:", err && (err.stack || err.message || err));
939
+ }
940
+ process.exit(1);
941
+ });
920
942
 
921
943
  wss.on("connection", (ws, req) => {
922
944
  const url = req.url || "/ssh";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-bridge-setup",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "description": "一次性安装器:释放终端桥接(JumpServer / Arthas / Yearning)的本地代理 + Chrome 插件,并注册 native messaging host。让 Agent 能通过浏览器 xterm 终端执行命令并拿回输出。",
5
5
  "license": "MIT",
6
6
  "author": "encorearon",