@sstar/embedlink_agent 0.1.0 → 0.2.0

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.
@@ -1065,5 +1065,19 @@ export async function startGrpcServer(cfg, identity) {
1065
1065
  };
1066
1066
  tryBindPort(cfg.grpc.port);
1067
1067
  });
1068
+ // @grpc/grpc-js 内部会为底层 Http2Server 注册多个 close 监听器(按服务/生命周期),
1069
+ // 在 Node 默认 maxListeners=10 时可能触发 MaxListenersExceededWarning。
1070
+ // 这里针对内部 Http2Server 提升阈值,避免误报“内存泄漏”告警。
1071
+ try {
1072
+ const http2Servers = server.http2Servers;
1073
+ if (http2Servers && typeof http2Servers.values === 'function') {
1074
+ for (const s of http2Servers.values()) {
1075
+ if (s && typeof s.setMaxListeners === 'function') {
1076
+ s.setMaxListeners(64);
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+ catch { }
1068
1082
  return server;
1069
1083
  }
@@ -499,13 +499,41 @@ export async function startWebServer(cfg) {
499
499
  reused: true,
500
500
  });
501
501
  }
502
+ // 先准备 SSH 鉴权材料(避免 probe 阶段因未加载私钥而误报认证失败)
503
+ const c = await ensureConfigLoaded();
504
+ const requestedPassword = typeof password === 'string' && password.trim() ? String(password) : undefined;
505
+ const enablePasswordFallback = c.ssh?.enablePasswordFallback !== false;
506
+ const effectivePassword = requestedPassword || (enablePasswordFallback ? sshPasswordSecret.value : undefined);
507
+ let privateKey = uploadedKeyMem;
508
+ if (keySource === 'default' && !privateKey) {
509
+ try {
510
+ if (c.ssh?.defaultKeyPath) {
511
+ privateKey = await fs.readFile(c.ssh.defaultKeyPath);
512
+ }
513
+ }
514
+ catch (e) {
515
+ console.debug('Failed to read default SSH key:', e?.message || String(e));
516
+ }
517
+ }
518
+ // 无鉴权材料时直接报错,避免 ssh2 报 “All configured authentication methods failed”
519
+ if (!privateKey && !effectivePassword) {
520
+ return res.status(400).json({
521
+ error: 'missing ssh authentication (password/privateKey)',
522
+ code: 'EL_SSH_AUTH_MISSING',
523
+ suggestions: [
524
+ '请上传私钥或在配置中设置 ssh.defaultKeyPath',
525
+ '或在界面填写 password / 设置环境变量 SSH_PASSWORD',
526
+ ],
527
+ });
528
+ }
502
529
  // Probe existing mapping for this user+agent on remote; if active, refuse
503
530
  const agentId = cfg.agentId || 'agent';
504
531
  const probe = await probeRemoteExistingTunnel({
505
532
  host,
506
533
  sshPort: Number(port),
507
534
  user,
508
- password,
535
+ password: effectivePassword,
536
+ privateKey,
509
537
  agentId,
510
538
  });
511
539
  if (probe.active && probe.port) {
@@ -522,19 +550,6 @@ export async function startWebServer(cfg) {
522
550
  });
523
551
  }
524
552
  await cleanupTunnel('probe_existing_tunnel');
525
- // 确保私钥已正确加载
526
- let privateKey = uploadedKeyMem;
527
- if (keySource === 'default' && !privateKey) {
528
- try {
529
- const c = await loadConfig();
530
- if (c.ssh?.defaultKeyPath) {
531
- privateKey = await fs.readFile(c.ssh.defaultKeyPath);
532
- }
533
- }
534
- catch (e) {
535
- console.debug('Failed to read default SSH key:', e?.message || String(e));
536
- }
537
- }
538
553
  // First try requested port, else fallback to username-hash mapping
539
554
  const userName = os.userInfo().username || 'user';
540
555
  const basePort = remotePort ? Number(remotePort) : hashPort(user);
@@ -554,7 +569,7 @@ export async function startWebServer(cfg) {
554
569
  host,
555
570
  port: Number(port),
556
571
  user,
557
- password: password || sshPasswordSecret.value,
572
+ password: effectivePassword,
558
573
  privateKey,
559
574
  bindAddr,
560
575
  bindPort: Number(p),
@@ -575,7 +590,14 @@ export async function startWebServer(cfg) {
575
590
  if (!bound)
576
591
  throw new Error(portBindingError || 'bind failed');
577
592
  lastRemotePort = bound;
578
- lastParams = { host, port: Number(port), user, password, bindAddr, keySource };
593
+ lastParams = {
594
+ host,
595
+ port: Number(port),
596
+ user,
597
+ password: effectivePassword,
598
+ bindAddr,
599
+ keySource,
600
+ };
579
601
  // 添加清理监听器:在Server关闭时自动清理隧道
580
602
  addTunnelCleanupListener(async () => {
581
603
  await cleanupTunnel('server_shutdown');
@@ -2235,7 +2257,7 @@ async function writeRemoteAgentJson(tunnel, info) {
2235
2257
  }
2236
2258
  }
2237
2259
  async function probeRemoteExistingTunnel(params) {
2238
- const { host, sshPort, user, password } = params;
2260
+ const { host, sshPort, user, password, privateKey } = params;
2239
2261
  const ssh2 = await import('ssh2');
2240
2262
  const client = new ssh2.Client();
2241
2263
  return await new Promise(async (resolve) => {
@@ -2271,7 +2293,7 @@ async function probeRemoteExistingTunnel(params) {
2271
2293
  return finish({ active: false });
2272
2294
  }
2273
2295
  catch (e) {
2274
- console.warn('Failed to probe remote agent.json:', e);
2296
+ console.warn('Failed to probe remote agent.json:', e?.message || String(e));
2275
2297
  return finish({ active: false });
2276
2298
  }
2277
2299
  })
@@ -2279,6 +2301,6 @@ async function probeRemoteExistingTunnel(params) {
2279
2301
  console.warn('Failed to probe remote agent.json:', err?.message || String(err));
2280
2302
  return finish({ active: false });
2281
2303
  })
2282
- .connect({ host, port: sshPort, username: user, password });
2304
+ .connect({ host, port: sshPort, username: user, password, privateKey });
2283
2305
  });
2284
2306
  }