claw-subagent-service 0.0.38 → 0.0.39

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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  # OpenClaw 服务停止脚本
4
4
  # 用法: ./stop.sh [选项]
5
+ # 支持 systemd 和 Docker(无 systemd)双模式
5
6
 
6
7
  set -e
7
8
 
@@ -27,12 +28,146 @@ log_error() {
27
28
  # 服务名称
28
29
  SERVICE_NAME="openclaw-gateway.service"
29
30
 
30
- # 检查服务是否存在
31
- check_service() {
31
+ # 检测是否在 Docker 环境(无 systemd)
32
+ is_docker() {
33
+ if ! command -v systemctl &>/dev/null; then
34
+ return 0 # 无 systemctl,认为是 Docker
35
+ fi
36
+ return 1
37
+ }
38
+
39
+ # 端口号
40
+ PORT="18789"
41
+
42
+ # 检查端口是否监听
43
+ check_port() {
44
+ local port=$1
45
+ if command -v netstat &>/dev/null; then
46
+ netstat -tnlp 2>/dev/null | grep -q ":$port "
47
+ else
48
+ # 降级:直接检查进程
49
+ [ -n "$(get_openclaw_pid)" ]
50
+ fi
51
+ }
52
+
53
+ # 获取 openclaw 进程 PID
54
+ get_openclaw_pid() {
55
+ # 只通过端口查找进程(最可靠,避免僵尸进程和误匹配)
56
+ if command -v netstat &>/dev/null; then
57
+ local pid
58
+ pid=$(netstat -tnlp 2>/dev/null | grep ":18789 " | head -1 | awk '{print $7}' | cut -d'/' -f1)
59
+ if [ -n "$pid" ]; then
60
+ echo "$pid"
61
+ return
62
+ fi
63
+ fi
64
+
65
+ # 没有监听端口,返回空(不再使用 pgrep 避免误匹配)
66
+ echo ""
67
+ }
68
+
69
+ # Docker 模式:停止服务
70
+ stop_docker() {
71
+ local pid
72
+ pid=$(get_openclaw_pid)
73
+
74
+ # 检查服务状态
75
+ log_info "检查 OpenClaw 服务状态..."
76
+ if [ -z "$pid" ]; then
77
+ log_warn "OpenClaw 服务未在运行。"
78
+ exit 0
79
+ fi
80
+
81
+ log_info "OpenClaw 服务正在运行(PID: $pid),准备停止..."
82
+
83
+ # 停止服务:先发送 SIGTERM(优雅停止)
84
+ log_info "正在停止 OpenClaw 服务..."
85
+ kill "$pid" &>/dev/null || true
86
+
87
+ # 等待服务完全停止(最多 10 秒)
88
+ local elapsed=0
89
+ while [ $elapsed -lt 10 ]; do
90
+ if [ -z "$(get_openclaw_pid)" ]; then
91
+ log_info "OpenClaw 服务停止成功!"
92
+ log_info "服务已成功停止。"
93
+ log_info "Success"
94
+ exit 0
95
+ fi
96
+ sleep 1
97
+ elapsed=$((elapsed + 1))
98
+ done
99
+
100
+ # 如果还在运行,强制停止(SIGKILL)
101
+ log_warn "服务未在 10 秒内停止,正在强制停止..."
102
+ kill -9 "$pid" &>/dev/null || true
103
+
104
+ # 等待进程消失(最多 3 秒)
105
+ elapsed=0
106
+ while [ $elapsed -lt 3 ]; do
107
+ if [ -z "$(get_openclaw_pid)" ]; then
108
+ break
109
+ fi
110
+ sleep 1
111
+ elapsed=$((elapsed + 1))
112
+ done
113
+
114
+ # 验证:进程不存在或端口已关闭即为成功
115
+ # 使用端口检测作为辅助,避免僵尸进程导致误判
116
+ local current_pid
117
+ current_pid=$(get_openclaw_pid)
118
+ if [ -z "$current_pid" ]; then
119
+ log_info "OpenClaw 服务已强制停止。"
120
+ log_info "服务已成功停止。"
121
+ log_info "Success"
122
+ elif ! check_port "$PORT"; then
123
+ # 端口已关闭但进程可能还在(僵尸状态),也认为成功
124
+ log_info "OpenClaw 服务已停止(端口已关闭)。"
125
+ log_info "服务已成功停止。"
126
+ log_info "Success"
127
+ else
128
+ log_error "OpenClaw 服务停止失败!进程仍然存在且端口仍在监听。"
129
+ exit 1
130
+ fi
131
+ }
132
+
133
+ # Systemd 模式:停止服务
134
+ stop_systemd() {
135
+ # 检查服务是否存在
32
136
  if ! systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null; then
33
137
  log_error "服务 $SERVICE_NAME 不存在。"
34
138
  exit 1
35
139
  fi
140
+
141
+ # 检查服务状态
142
+ log_info "检查 OpenClaw 服务状态..."
143
+ if systemctl --user is-active --quiet "$SERVICE_NAME"; then
144
+ log_info "OpenClaw 服务正在运行,准备停止..."
145
+ else
146
+ log_warn "OpenClaw 服务未在运行。"
147
+ exit 0
148
+ fi
149
+
150
+ # 停止服务
151
+ log_info "正在停止 OpenClaw 服务..."
152
+
153
+ if systemctl --user stop "$SERVICE_NAME"; then
154
+ log_info "OpenClaw 服务停止成功!"
155
+
156
+ # 等待服务完全停止
157
+ sleep 2
158
+
159
+ # 验证服务状态
160
+ if systemctl --user is-active --quiet "$SERVICE_NAME"; then
161
+ log_warn "服务可能仍在运行,请检查进程。"
162
+ systemctl --user status "$SERVICE_NAME" --no-pager
163
+ else
164
+ log_info "服务已成功停止。"
165
+ log_info "Success"
166
+ fi
167
+ else
168
+ log_error "OpenClaw 服务停止失败!"
169
+ exit 1
170
+ fi
36
171
  }
37
172
 
38
173
  # 显示帮助信息
@@ -55,10 +190,10 @@ main() {
55
190
  local force=""
56
191
 
57
192
  # 解析命令行参数
58
- while [[ $# -gt 0 ]]; do
193
+ while [ $# -gt 0 ]; do
59
194
  case $1 in
60
195
  -f|--force)
61
- force="--force"
196
+ force="1"
62
197
  shift
63
198
  ;;
64
199
  -h|--help)
@@ -73,40 +208,12 @@ main() {
73
208
  esac
74
209
  done
75
210
 
76
- # 检查服务是否存在
77
- check_service
78
-
79
- # 检查服务状态
80
- log_info "检查 OpenClaw 服务状态..."
81
- if systemctl --user is-active --quiet "$SERVICE_NAME"; then
82
- log_info "OpenClaw 服务正在运行,准备停止..."
83
- else
84
- log_warn "OpenClaw 服务未在运行。"
85
- exit 0
86
- fi
87
-
88
- # 停止服务
89
- log_info "正在停止 OpenClaw 服务..."
90
-
91
- if systemctl --user stop "$SERVICE_NAME"; then
92
- log_info "OpenClaw 服务停止成功!"
93
-
94
- # 等待服务完全停止
95
- sleep 2
96
-
97
- # 验证服务状态
98
- if systemctl --user is-active --quiet "$SERVICE_NAME"; then
99
- log_warn "服务可能仍在运行,请检查进程。"
100
- systemctl --user status "$SERVICE_NAME" --no-pager
101
- else
102
- log_info "服务已成功停止。"
103
- log_info "Success"
104
- fi
211
+ if is_docker; then
212
+ stop_docker
105
213
  else
106
- log_error "OpenClaw 服务停止失败!"
107
- exit 1
214
+ stop_systemd
108
215
  fi
109
216
  }
110
217
 
111
218
  # 执行主函数
112
- main "$@"
219
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -83,6 +83,21 @@ function installAndStartService() {
83
83
 
84
84
  svc.on('install', () => {
85
85
  console.log('[postinstall] 服务注册成功,正在启动...');
86
+
87
+ // 延迟 2 秒确保服务注册到 SCM,再设置开机自启 + 崩溃恢复
88
+ setTimeout(() => {
89
+ const cmdFailure = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
90
+ exec(cmdFailure, (err) => {
91
+ if (err) console.error(`[postinstall] 设置恢复策略失败: ${err.message}`);
92
+ else console.log('[postinstall] 恢复策略已设置:崩溃后自动重启');
93
+ });
94
+
95
+ exec(`sc.exe config "${SERVICE_NAME}" start= auto`, (err) => {
96
+ if (err) console.error(`[postinstall] 设置自动启动失败: ${err.message}`);
97
+ else console.log('[postinstall] 启动类型已设为:自动');
98
+ });
99
+ }, 2000);
100
+
86
101
  svc.start();
87
102
  });
88
103
 
@@ -0,0 +1,6 @@
1
+ <configuration>
2
+ <startup>
3
+ <supportedRuntime version="v2.0.50727" />
4
+ <supportedRuntime version="v4.0" />
5
+ </startup>
6
+ </configuration>
@@ -0,0 +1,33 @@
1
+ <service>
2
+ <id>clawsubagentservice.exe</id>
3
+ <name>claw-subagent-service</name>
4
+ <description>OpenClaw Guard</description>
5
+ <executable>C:\Program Files\nodejs\node.exe</executable>
6
+ <argument>--harmony</argument>
7
+ <argument>--max_old_space_size=4096</argument>
8
+ <argument>D:\A-DM\dm-im\silent-service\node_modules\node-windows\lib\wrapper.js</argument>
9
+ <argument>--file</argument>
10
+ <argument>D:\A-DM\dm-im\silent-service\service\daemon.js</argument>
11
+ <argument>--scriptoptions=</argument>
12
+ <argument>--log</argument>
13
+ <argument>claw-subagent-service wrapper</argument>
14
+ <argument>--grow</argument>
15
+ <argument>0.25</argument>
16
+ <argument>--wait</argument>
17
+ <argument>1</argument>
18
+ <argument>--maxrestarts</argument>
19
+ <argument>3</argument>
20
+ <argument>--abortonerror</argument>
21
+ <argument>n</argument>
22
+ <argument>--stopparentfirst</argument>
23
+ <argument>undefined</argument>
24
+ <logmode>rotate</logmode>
25
+ <stoptimeout>30sec</stoptimeout>
26
+ <env name="undefined" value="undefined"/>
27
+ <serviceaccount>
28
+ <domain>XULIN</domain>
29
+ <user>LocalSystem</user>
30
+ <password></password>
31
+ </serviceaccount>
32
+ <workingdirectory>D:\A-DM\dm-im\silent-service</workingdirectory>
33
+ </service>
@@ -11,20 +11,33 @@ const SYSTEM_PROMPT = `你是 虾说app的 openclaw 运维助手智能体,职
11
11
  3. **做备份**:改配置前自动备份到 config.json.bak.时间戳
12
12
 
13
13
  ## 常用运维指令
14
- | 命令 | 功能 |
15
- |------|------|
16
- | \`openclaw doctor\` | 诊断并自动修复问题 |
17
- | \`openclaw doctor --fix\` | 强制修复配置错误 |
18
- | \`openclaw status\` | 查看整体运行状态 |
19
- | \`openclaw dashboard\` | 启动 Web 控制台(默认端口 18789) |
20
- | \`openclaw gateway start/restart\` | 启动/重启 Gateway |
21
- | \`openclaw models list\` | 查看可用模型 |
22
- | \`openclaw channels status\` | 查看通讯平台连接状态 |
23
- | \`openclaw logs --follow\` | 实时查看运行日志 |
24
- | \`openclaw onboard --install-daemon\` | 首次配置向导 |
14
+
15
+ | 命令 | 功能 | 容器内可用 |
16
+ |------|------|-----------|
17
+ | \`openclaw doctor\` | 诊断并自动修复问题 | ✅ |
18
+ | \`openclaw doctor --fix\` | 强制修复配置错误 | ✅ |
19
+ | \`openclaw status\` | 查看整体运行状态 | |
20
+ | \`openclaw models list\` | 查看可用模型 | |
21
+ | \`openclaw channels status\` | 查看通讯平台连接状态 | ✅ |
22
+ | \`openclaw logs --follow\` | 实时查看运行日志 | ✅ |
23
+ | \`openclaw gateway\` | **前台启动 Gateway**(端口 18789) | ✅ 推荐 |
24
+ | \`openclaw gateway --port 18789 --host 0.0.0.0 --verbose\` | 指定参数前台启动 | ✅ |
25
+ | \`openclaw onboard\` | 首次配置向导(交互式) | ✅ |
26
+
27
+ ## Docker容器内不可用(需要 systemd)
28
+
29
+ | 命令 | 容器内替代方案 |
30
+ |------|---------|
31
+ | \`openclaw gateway start\` | 改用 \`openclaw gateway\` 前台运行 |
32
+ | \`openclaw gateway restart\` | 先 \`pkill -f "openclaw gateway"\` 再重新启动 |
33
+ | \`openclaw onboard --install-daemon\` | 改用 \`nohup openclaw gateway &\` 后台运行 |
34
+ | \`systemctl --user start openclaw-gateway.service\` | 容器无 systemd,用 \`nohup\` |
35
+
36
+ ## 后台运行方案(容器内)nohup 后台运行
37
+ nohup openclaw gateway --port 18789 --host 0.0.0.0 --verbose > /var/log/openclaw.log 2>&1 &
25
38
 
26
39
  ## 修不好的时候,查资料顺序
27
- 1. \`openclaw <命令> --help\` 先看本地帮助
40
+ 1. \`openclaw --help\` 先看本地帮助
28
41
  2. https://docs.openclaw.ai 官方文档
29
42
  3. https://github.com/openclaw/openclaw/issues GitHub 搜报错关键词
30
43
 
@@ -32,6 +45,8 @@ const SYSTEM_PROMPT = `你是 虾说app的 openclaw 运维助手智能体,职
32
45
  - 改配置必须先备份
33
46
  - 执行命令后汇报结果,别沉默
34
47
  - 不知道就查资料,别瞎猜
48
+ - 看到 \`systemctl\` 相关报错,立即切换为 \`nohup\` 方案, 因为docker容器内是没有systemctl的。
49
+ - 超过6分钟 没有修复好就停下来,报告你遇到的问题,不要无限循环的进行修复。
35
50
  - 不要对外透漏你是什么模型,不要说你是opencode,对外你就说你是 虾说运维大模型;
36
51
  `;
37
52
 
@@ -93,12 +108,46 @@ async function getOrCreateGatewaySession(fallbackSessionId) {
93
108
  throw new Error('无法获取或创建有效的 session ID');
94
109
  }
95
110
 
111
+ async function ensureOpencodeRunning(log) {
112
+ try {
113
+ // 快速检查 4096 端口
114
+ await axios.get('http://127.0.0.1:4096/global/health', { timeout: 3000 });
115
+ return true;
116
+ } catch {
117
+ log('WARN', 'OpenCode 服务未运行,准备启动...');
118
+
119
+ const { exec } = require('child_process');
120
+
121
+ // 使用 nohup 启动,脱离终端
122
+ exec('nohup opencode serve --port 4096 --hostname 127.0.0.1 > /tmp/opencode.log 2>&1 &');
123
+
124
+ // 等待 8 秒让服务启动
125
+ await new Promise(resolve => setTimeout(resolve, 8000));
126
+
127
+ // 再次检查
128
+ try {
129
+ await axios.get('http://127.0.0.1:4096/global/health', { timeout: 3000 });
130
+ log('INFO', 'OpenCode 服务已启动');
131
+ return true;
132
+ } catch {
133
+ log('ERROR', 'OpenCode 服务启动失败');
134
+ return false;
135
+ }
136
+ }
137
+ }
138
+
96
139
  async function forwardChatMessage(sessionId, content, onDelta, logFn, timeoutMs = 600000) {
97
140
  const log = (level, message) => {
98
141
  console.log(`[CHAT-DEBUG] ${level}: ${message}`);
99
142
  if (logFn) logFn(level, message);
100
143
  };
101
144
 
145
+ // 确保 opencode 在运行
146
+ const isRunning = await ensureOpencodeRunning(log);
147
+ if (!isRunning) {
148
+ throw new Error('OpenCode 服务无法启动,请检查环境');
149
+ }
150
+
102
151
  log('DEBUG', `原始 sessionId: ${sessionId}`);
103
152
  const realSessionId = await getOrCreateGatewaySession(sessionId);
104
153
  log('DEBUG', `实际 sessionId: ${realSessionId}`);
@@ -117,77 +166,101 @@ async function forwardChatMessage(sessionId, content, onDelta, logFn, timeoutMs
117
166
 
118
167
  log('DEBUG', `请求体包含 system 参数: ${!!requestBody.system}`);
119
168
 
120
- const response = await axios.post(url, requestBody, {
121
- headers: { 'Content-Type': 'application/json' },
122
- timeout: timeoutMs
123
- });
169
+ try {
170
+ const response = await axios.post(url, requestBody, {
171
+ headers: { 'Content-Type': 'application/json' },
172
+ timeout: timeoutMs
173
+ });
124
174
 
125
- const result = response.data || {};
126
- log('DEBUG', `响应状态: ${response.status}`);
127
- log('DEBUG', `响应数据 keys: ${Object.keys(result).join(', ')}`);
128
- log('DEBUG', `响应数据: ${JSON.stringify(result).substring(0, 500)}`);
175
+ const result = response.data || {};
176
+ log('DEBUG', `响应状态: ${response.status}`);
177
+ log('DEBUG', `响应数据 keys: ${Object.keys(result).join(', ')}`);
178
+ log('DEBUG', `响应数据: ${JSON.stringify(result).substring(0, 500)}`);
129
179
 
130
- const parts = result.parts || [];
131
- const info = result.info || {};
180
+ const parts = result.parts || [];
181
+ const info = result.info || {};
132
182
 
133
- let fullContent = '';
183
+ let fullContent = '';
134
184
 
135
- for (const key of ['text', 'content', 'response', 'output', 'message', 'reply']) {
136
- const val = result[key];
137
- if (val && typeof val === 'string') {
138
- fullContent = val;
139
- log('DEBUG', `从顶层字段 ${key} 提取到内容`);
140
- break;
185
+ for (const key of ['text', 'content', 'response', 'output', 'message', 'reply']) {
186
+ const val = result[key];
187
+ if (val && typeof val === 'string') {
188
+ fullContent = val;
189
+ log('DEBUG', `从顶层字段 ${key} 提取到内容`);
190
+ break;
191
+ }
141
192
  }
142
- }
143
193
 
144
- if (!fullContent) {
145
- for (const part of parts) {
146
- const text = part.text || part.content || part.value || '';
147
- const partType = part.type || '';
148
- if (text && ['text', 'assistant', 'message', 'response', ''].includes(partType)) {
149
- fullContent += String(text);
194
+ if (!fullContent) {
195
+ for (const part of parts) {
196
+ const text = part.text || part.content || part.value || '';
197
+ const partType = part.type || '';
198
+ if (text && ['text', 'assistant', 'message', 'response', ''].includes(partType)) {
199
+ fullContent += String(text);
200
+ }
150
201
  }
202
+ if (fullContent) log('DEBUG', `从 parts 中提取到内容, 长度: ${fullContent.length}`);
151
203
  }
152
- if (fullContent) log('DEBUG', `从 parts 中提取到内容, 长度: ${fullContent.length}`);
153
- }
154
204
 
155
- if (!fullContent) {
156
- for (const part of parts) {
157
- for (const key of ['text', 'content', 'value', 'output']) {
158
- const val = part[key];
159
- if (val && typeof val === 'string') {
160
- fullContent += val;
161
- break;
205
+ if (!fullContent) {
206
+ for (const part of parts) {
207
+ for (const key of ['text', 'content', 'value', 'output']) {
208
+ const val = part[key];
209
+ if (val && typeof val === 'string') {
210
+ fullContent += val;
211
+ break;
212
+ }
162
213
  }
163
214
  }
215
+ if (fullContent) log('DEBUG', `从所有 parts 兜底提取到内容, 长度: ${fullContent.length}`);
164
216
  }
165
- if (fullContent) log('DEBUG', `从所有 parts 兜底提取到内容, 长度: ${fullContent.length}`);
166
- }
167
217
 
168
- if (!fullContent) {
169
- const infoText = info.text || info.content || '';
170
- if (infoText) fullContent = infoText;
171
- }
218
+ if (!fullContent) {
219
+ const infoText = info.text || info.content || '';
220
+ if (infoText) fullContent = infoText;
221
+ }
172
222
 
173
- if (!fullContent) {
174
- log('ERROR', `Gateway 返回空内容, parts 数量: ${parts.length}`);
175
- throw new Error('Gateway 返回空内容');
176
- }
223
+ if (!fullContent) {
224
+ log('ERROR', `Gateway 返回空内容, parts 数量: ${parts.length}`);
225
+ throw new Error('Gateway 返回空内容');
226
+ }
177
227
 
178
- log('DEBUG', `总内容长度: ${fullContent.length}, 开始模拟流式发送`);
179
- const chunkSize = 50;
180
- for (let i = 0; i < fullContent.length; i += chunkSize) {
181
- const chunk = fullContent.slice(i, i + chunkSize);
182
- await onDelta(chunk);
183
- // 仅在非测试环境添加延迟
184
- if (process.env.NODE_ENV !== 'test') {
185
- await new Promise((resolve) => setTimeout(resolve, 30));
228
+ log('DEBUG', `总内容长度: ${fullContent.length}, 开始模拟流式发送`);
229
+ const chunkSize = 50;
230
+ for (let i = 0; i < fullContent.length; i += chunkSize) {
231
+ const chunk = fullContent.slice(i, i + chunkSize);
232
+ await onDelta(chunk);
233
+ // 仅在非测试环境添加延迟
234
+ if (process.env.NODE_ENV !== 'test') {
235
+ await new Promise((resolve) => setTimeout(resolve, 30));
236
+ }
186
237
  }
187
- }
188
- log('DEBUG', '流式发送完成');
238
+ log('DEBUG', '流式发送完成');
189
239
 
190
- return fullContent;
240
+ return fullContent;
241
+ } catch (error) {
242
+ const msg = error instanceof Error ? error.message : String(error);
243
+ log('ERROR', `Gateway 请求异常: ${msg}`);
244
+
245
+ // 检测是否是超时错误(Gateway 卡死)
246
+ if (msg.includes('timeout') || msg.includes('ECONNABORTED') || msg.includes('ETIMEDOUT')) {
247
+ log('WARN', 'Gateway 超时,准备使用 nohup 重启...');
248
+
249
+ const { exec } = require('child_process');
250
+
251
+ // 1. 杀掉现有进程
252
+ exec('pkill -f "opencode serve"');
253
+ await new Promise(resolve => setTimeout(resolve, 5000));
254
+
255
+ // 2. 使用 nohup 重新启动
256
+ exec('nohup opencode serve --port 4096 --hostname 127.0.0.1 > /tmp/opencode.log 2>&1 &');
257
+ await new Promise(resolve => setTimeout(resolve, 8000));
258
+
259
+ throw new Error('OpenCode 服务已重启,请稍后重试');
260
+ }
261
+
262
+ throw error;
263
+ }
191
264
  }
192
265
 
193
266
  module.exports = {
package/service/worker.js CHANGED
@@ -15,6 +15,7 @@ const { startOpencodeService, stopOpencodeService } = require('./modules/opencod
15
15
 
16
16
  const log = createLogger('worker');
17
17
  const PORT = process.env.SILENT_SERVICE_PORT ? parseInt(process.env.SILENT_SERVICE_PORT, 10) : 28765;
18
+ const HOST = process.env.SILENT_SERVICE_HOST || '127.0.0.1';
18
19
 
19
20
 
20
21
  // 捕获所有异常,强制打印到控制台(绕过 logger)
@@ -54,9 +55,20 @@ function findPidOnPort(port) {
54
55
  }
55
56
  }
56
57
  } else {
57
- const out = execSync(`lsof -i :${port} -t 2>/dev/null`, { encoding: 'utf8', timeout: 5000 });
58
- const pid = parseInt(out.trim(), 10);
59
- if (!isNaN(pid)) return pid;
58
+ // 优先尝试 lsof,再兜底 ss / fuser / netstat(适配精简 Docker 镜像)
59
+ const commands = [
60
+ `lsof -i :${port} -t 2>/dev/null`,
61
+ `fuser ${port}/tcp 2>/dev/null`,
62
+ `ss -tlnp 2>/dev/null | grep ":${port}" | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p'`,
63
+ `netstat -tlnp 2>/dev/null | grep ":${port}" | sed -n 's/.*\\/\\([0-9]*\\).*/\\1/p'`,
64
+ ];
65
+ for (const cmd of commands) {
66
+ try {
67
+ const out = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
68
+ const pid = parseInt(out.split('\n')[0], 10);
69
+ if (!isNaN(pid) && pid > 0) return pid;
70
+ } catch { continue; }
71
+ }
60
72
  }
61
73
  } catch { /* port is free */ }
62
74
  return null;
@@ -475,15 +487,15 @@ server.on('error', (err) => {
475
487
  setTimeout(() => {
476
488
  log.info(`[WORKER] 重新尝试监听端口 ${PORT}...`);
477
489
  server.close(() => {});
478
- server.listen(PORT, '127.0.0.1');
490
+ server.listen(PORT, HOST);
479
491
  }, 2000);
480
492
  return;
481
493
  }
482
494
  log.error(`[WORKER] HTTP 服务错误: ${err.message}`);
483
495
  });
484
496
 
485
- server.listen(PORT, '127.0.0.1', () => {
486
- log.info(`[WORKER] HTTP 服务已启动: http://127.0.0.1:${PORT}/health`);
497
+ server.listen(PORT, HOST, () => {
498
+ log.info(`[WORKER] HTTP 服务已启动: http://${HOST}:${PORT}/health`);
487
499
  });
488
500
 
489
501
  process.on('message', (msg) => {