claw-subagent-service 0.0.138 → 0.0.140

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.
@@ -184,11 +184,19 @@ restart_docker() {
184
184
  log_warn "服务未运行"
185
185
  fi
186
186
 
187
- # 如果正在运行,先停止
187
+ # 如果正在运行,先停止(包括进程存在但端口未监听的情况)
188
188
  if [ -n "$pid" ] || check_port "$PORT"; then
189
189
  log_info "正在停止 OpenClaw 服务..."
190
190
  if [ -n "$pid" ]; then
191
- kill "$pid" &>/dev/null || true
191
+ log_info "停止进程 $pid..."
192
+ kill -15 "$pid" &>/dev/null || true
193
+ sleep 2
194
+ # 检查是否还在运行
195
+ if ps -p "$pid" > /dev/null 2>&1; then
196
+ log_warn "进程 $pid 仍在运行,强制停止..."
197
+ kill -9 "$pid" &>/dev/null || true
198
+ sleep 1
199
+ fi
192
200
  fi
193
201
 
194
202
  # 等待停止(最多 10 秒),使用端口双重验证
@@ -204,10 +212,12 @@ restart_docker() {
204
212
  done
205
213
 
206
214
  # 如果还在运行,强制停止并使用备选方案
207
- if check_port "$PORT"; then
215
+ local current_pid_after_wait
216
+ current_pid_after_wait=$(get_openclaw_pid)
217
+ if [ -n "$current_pid_after_wait" ] || check_port "$PORT"; then
208
218
  log_warn "服务未在 10 秒内停止,正在强制停止..."
209
- if [ -n "$pid" ]; then
210
- kill -9 "$pid" &>/dev/null || true
219
+ if [ -n "$current_pid_after_wait" ]; then
220
+ kill -9 "$current_pid_after_wait" &>/dev/null || true
211
221
  fi
212
222
  pkill -9 -f "openclaw" &>/dev/null || true
213
223
  if command -v fuser &>/dev/null; then
@@ -216,8 +226,10 @@ restart_docker() {
216
226
  sleep 2
217
227
 
218
228
  # 最终验证
219
- if check_port "$PORT"; then
220
- log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
229
+ local final_pid
230
+ final_pid=$(get_openclaw_pid)
231
+ if [ -n "$final_pid" ] || check_port "$PORT"; then
232
+ log_error "OpenClaw 服务停止失败!进程或端口仍在运行。"
221
233
  exit 1
222
234
  fi
223
235
  fi
@@ -240,8 +252,16 @@ restart_docker() {
240
252
 
241
253
  log_info "正在启动 OpenClaw 服务..."
242
254
 
243
- # 使用 nohup 后台启动
244
- nohup openclaw gateway --port "$PORT" > "$log_file" 2>&1 &
255
+ # 使用 setsid 创建新会话,完全脱离父进程
256
+ # 这样即使父进程(Node.js)退出,openclaw 也不会被终止
257
+ log_info "使用 setsid 启动,确保进程脱离父进程..."
258
+ if command -v setsid &>/dev/null; then
259
+ setsid bash -c "openclaw gateway run --port $PORT" > "$log_file" 2>&1 &
260
+ else
261
+ # 如果没有 setsid,使用 nohup 作为后备
262
+ log_warn "setsid 不可用,使用 nohup 作为后备..."
263
+ nohup openclaw gateway run --port "$PORT" > "$log_file" 2>&1 &
264
+ fi
245
265
 
246
266
  log_info "OpenClaw 服务启动命令已发送(PID: $!)"
247
267
  log_info "日志文件: $log_file"
@@ -186,12 +186,25 @@ start_docker() {
186
186
  local pid
187
187
  pid=$(get_openclaw_pid)
188
188
  if [ -n "$pid" ]; then
189
- log_info "OpenClaw 服务已经在运行中。"
189
+ log_info "检测到 openclaw 进程 (PID: $pid)"
190
190
  if check_port "$PORT"; then
191
+ log_info "OpenClaw 服务已经在运行中。"
191
192
  log_info "控制界面访问地址: http://127.0.0.1:$PORT/"
193
+ log_info "Success"
194
+ exit 0
195
+ else
196
+ log_warn "进程存在但端口 $PORT 未监听,进程可能未正确启动或已崩溃"
197
+ log_warn "将停止现有进程并重新启动..."
198
+ # 停止现有进程
199
+ kill -15 "$pid" 2>/dev/null || true
200
+ sleep 2
201
+ # 检查是否还在运行
202
+ if ps -p "$pid" > /dev/null 2>&1; then
203
+ log_warn "进程仍在运行,强制停止..."
204
+ kill -9 "$pid" 2>/dev/null || true
205
+ sleep 1
206
+ fi
192
207
  fi
193
- log_info "Success"
194
- exit 0
195
208
  fi
196
209
 
197
210
  # 检查 openclaw 命令是否存在
@@ -288,13 +301,21 @@ start_docker() {
288
301
  # 注意:openclaw gateway 可能使用不同的参数名
289
302
  log_info "尝试启动: openclaw gateway run --port $PORT"
290
303
 
291
- # 使用 nohup 后台启动,将输出重定向到日志文件
292
- nohup openclaw gateway run --port "$PORT" > "$log_file" 2>&1 &
304
+ # 使用 setsid 创建新会话,完全脱离父进程
305
+ # 这样即使父进程(Node.js)退出,openclaw 也不会被终止
306
+ log_info "使用 setsid 启动,确保进程脱离父进程..."
307
+ if command -v setsid &>/dev/null; then
308
+ setsid bash -c "openclaw gateway run --port $PORT" > "$log_file" 2>&1 &
309
+ else
310
+ # 如果没有 setsid,使用 nohup 作为后备
311
+ log_warn "setsid 不可用,使用 nohup 作为后备..."
312
+ nohup openclaw gateway run --port "$PORT" > "$log_file" 2>&1 &
313
+ fi
314
+ local started_pid=$!
315
+ log_info "启动的进程 PID: $started_pid"
293
316
 
294
317
  # 等待 15 秒检查进程是否启动(给更多时间初始化)
295
318
  sleep 15
296
- local started_pid=$!
297
- log_info "启动的进程 PID: $started_pid"
298
319
 
299
320
  # 检查进程是否存在
300
321
  if ! ps -p "$started_pid" > /dev/null 2>&1; then
@@ -1,496 +1,509 @@
1
- #!/bin/bash
2
-
3
- # OpenClaw 服务停止脚本
4
- # 用法: ./stop.sh [选项]
5
- # 支持 systemd 和 Docker(无 systemd)双模式
6
-
7
- # 注意:不使用 set -e,因为我们已经实现了完善的错误处理和验证逻辑
8
- # set -e 可能导致 pgrep/pidof 找不到进程时脚本意外退出
9
-
10
- # 调试模式:记录每条执行的命令(用于排查问题)
11
- # set -x
12
-
13
- # 颜色定义
14
- RED='\033[0;31m'
15
- GREEN='\033[0;32m'
16
- YELLOW='\033[1;33m'
17
- NC='\033[0m' # No Color
18
-
19
- # 日志函数
20
- log_info() {
21
- echo -e "${GREEN}[INFO]${NC} $1"
22
- }
23
-
24
- log_warn() {
25
- echo -e "${YELLOW}[WARN]${NC} $1"
26
- }
27
-
28
- log_error() {
29
- echo -e "${RED}[ERROR]${NC} $1"
30
- }
31
-
32
- # 服务名称
33
- SERVICE_NAME="openclaw-gateway.service"
34
-
35
- # 检测是否在 Docker 环境(无 systemd)
36
- # 注意:某些 Docker 镜像安装了 systemctl 命令但无法使用
37
- # 所以同时检查 systemd 是否实际运行
38
- is_docker() {
39
- # 方法1: 检查 systemctl 是否可用
40
- if ! command -v systemctl &>/dev/null; then
41
- return 0 # 无 systemctl,认为是 Docker
42
- fi
43
-
44
- # 方法2: 即使安装了 systemctl,检查 systemd 是否实际运行
45
- # 在 Docker 中,/run/systemd/system 通常不存在
46
- if [ ! -d "/run/systemd/system" ] && [ ! -d "/sys/fs/cgroup/systemd" ]; then
47
- return 0 # systemd 未运行,认为是 Docker
48
- fi
49
-
50
- # 方法3: 尝试执行 systemctl status,如果失败则认为是 Docker
51
- if ! systemctl status &>/dev/null; then
52
- return 0 # systemctl 无法使用,认为是 Docker
53
- fi
54
-
55
- return 1
56
- }
57
-
58
- # 端口号
59
- PORT="18789"
60
-
61
- # 检查端口是否监听
62
- # 注意:只检查端口,不检查进程。进程存在不等于端口在监听。
63
- check_port() {
64
- local port=$1
65
- if command -v ss &>/dev/null; then
66
- ss -tln 2>/dev/null | grep -q ":$port "
67
- return $?
68
- elif command -v netstat &>/dev/null; then
69
- netstat -tln 2>/dev/null | grep -q ":$port "
70
- return $?
71
- elif command -v lsof &>/dev/null; then
72
- lsof -i :$port 2>/dev/null | grep -q LISTEN
73
- return $?
74
- elif command -v fuser &>/dev/null; then
75
- fuser $port/tcp 2>/dev/null | grep -q '[0-9]'
76
- return $?
77
- fi
78
- # 如果所有工具都不可用,无法准确检查端口,保守返回 1(端口未监听)
79
- return 1
80
- }
81
-
82
- # 获取 openclaw 进程 PID
83
- get_openclaw_pid() {
84
- local port=18789
85
- local pid=""
86
-
87
- # 按优先级尝试多种工具(适配精简 Docker 镜像)
88
- # 方法1: lsof(最可靠)
89
- if command -v lsof &>/dev/null; then
90
- pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
91
- if [ -n "$pid" ]; then
92
- echo "$pid"
93
- return
94
- fi
95
- fi
96
-
97
- # 方法2: fuser
98
- if command -v fuser &>/dev/null; then
99
- pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
100
- if [ -n "$pid" ]; then
101
- echo "$pid"
102
- return
103
- fi
104
- fi
105
-
106
- # 方法3: ss
107
- if command -v ss &>/dev/null; then
108
- pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
109
- if [ -n "$pid" ]; then
110
- echo "$pid"
111
- return
112
- fi
113
- fi
114
-
115
- # 方法4: netstat
116
- if command -v netstat &>/dev/null; then
117
- pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
118
- if [ -n "$pid" ]; then
119
- echo "$pid"
120
- return
121
- fi
122
- fi
123
-
124
- # 方法5: 通过 /proc/net/tcp 查找(不需要外部工具)
125
- # 端口 18789 的十六进制 = 0x4965
126
- local hex_port="4965"
127
- for proc_dir in /proc/[0-9]*; do
128
- if [ -f "$proc_dir/net/tcp" ]; then
129
- # 检查该进程是否监听目标端口
130
- if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
131
- basename "$proc_dir"
132
- return
133
- fi
134
- fi
135
- done
136
-
137
- # 方法6: 通过进程名查找(当服务未监听预期端口时)
138
- # 使用 pgrep/pidof/ps 查找 openclaw 进程
139
- if command -v pgrep &>/dev/null; then
140
- pid=$(pgrep -f "openclaw" | head -1)
141
- if [ -n "$pid" ]; then
142
- echo "$pid"
143
- return
144
- fi
145
- fi
146
-
147
- if command -v pidof &>/dev/null; then
148
- pid=$(pidof openclaw | awk '{print $1}')
149
- if [ -n "$pid" ]; then
150
- echo "$pid"
151
- return
152
- fi
153
- fi
154
-
155
- # 最后尝试 ps
156
- pid=$(ps aux | grep -v grep | grep "openclaw" | head -1 | awk '{print $2}')
157
- if [ -n "$pid" ]; then
158
- echo "$pid"
159
- return
160
- fi
161
-
162
- echo ""
163
- }
164
-
165
- # Docker 模式:停止服务
166
- # 参数: $1 = force (1=强制停止, 空=优雅停止)
167
- stop_docker() {
168
- local force="$1"
169
- log_info "=== OpenClaw Docker 停止模式 ==="
170
- log_info "检查 OpenClaw 服务状态..."
171
-
172
- # 获取所有 openclaw 进程 PID
173
- local all_pids=""
174
- if command -v pgrep &>/dev/null; then
175
- all_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
176
- log_info "使用 pgrep 查找进程: $all_pids"
177
- elif command -v pidof &>/dev/null; then
178
- all_pids=$(pidof openclaw)
179
- log_info "使用 pidof 查找进程: $all_pids"
180
- else
181
- all_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
182
- log_info "使用 ps 查找进程: $all_pids"
183
- fi
184
-
185
- local pid
186
- pid=$(get_openclaw_pid)
187
-
188
- # 检查服务状态
189
- if [ -z "$pid" ] && [ -z "$all_pids" ]; then
190
- # 没有进程,检查端口
191
- if check_port "$PORT"; then
192
- log_warn "端口 $PORT 仍在监听,但无法获取 PID,尝试备选停止方案..."
193
- # 尝试通过 fuser 直接通过端口杀进程
194
- if command -v fuser &>/dev/null; then
195
- log_info "使用 fuser 通过端口停止服务..."
196
- fuser -k "${PORT}/tcp" &>/dev/null || true
197
- sleep 2
198
- fi
199
- # 尝试通过 pkill 停止 openclaw 相关进程
200
- if check_port "$PORT"; then
201
- log_info "使用 pkill 停止 openclaw 进程..."
202
- pkill -9 -f "openclaw" &>/dev/null || true
203
- sleep 2
204
- fi
205
- fi
206
-
207
- if ! check_port "$PORT" && [ -z "$(ps aux | grep -v grep | grep 'openclaw' | awk '{print $2}')" ]; then
208
- log_warn "OpenClaw 服务未在运行。"
209
- exit 0
210
- else
211
- log_error "OpenClaw 服务停止失败!"
212
- exit 1
213
- fi
214
- fi
215
-
216
- log_info "发现 OpenClaw 进程: $all_pids"
217
-
218
- # 如果不是强制模式,先尝试优雅停止
219
- if [ -z "$force" ]; then
220
- log_info "正在优雅停止 OpenClaw 服务..."
221
- # 尝试发送 SIGTERM
222
- for p in $all_pids; do
223
- log_info "执行: kill -15 $p"
224
- kill -15 "$p" 2>/dev/null || log_warn "kill -15 $p 失败"
225
- done
226
-
227
- # 等待 5 秒让服务优雅退出
228
- log_info "等待 5 秒让服务优雅退出..."
229
- sleep 5
230
-
231
- # 检查是否已停止
232
- local pid_after_graceful
233
- pid_after_graceful=$(get_openclaw_pid)
234
- if [ -z "$pid_after_graceful" ] && ! check_port "$PORT"; then
235
- log_info "OpenClaw 服务已通过优雅停止退出。"
236
- log_info "服务已成功停止。"
237
- log_info "Success"
238
- exit 0
239
- fi
240
-
241
- log_warn "优雅停止失败,服务仍在运行,切换到强制停止..."
242
- fi
243
-
244
- # 强制停止模式(直接发送 SIGKILL)
245
- log_info "正在强制停止 OpenClaw 服务(SIGKILL)..."
246
-
247
- # 获取最新的进程列表(因为优雅停止后可能有新进程)
248
- local current_pids=""
249
- if command -v pgrep &>/dev/null; then
250
- current_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
251
- elif command -v pidof &>/dev/null; then
252
- current_pids=$(pidof openclaw)
253
- else
254
- current_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
255
- fi
256
-
257
- log_info "当前 openclaw 进程: $current_pids"
258
-
259
- for p in $current_pids; do
260
- log_info "执行: kill -9 $p"
261
- kill -9 "$p" 2>/dev/null || log_warn "kill -9 $p 失败"
262
- done
263
- # 额外使用 pkill 确保所有相关进程都被停止
264
- log_info "执行: pkill -9 -f openclaw"
265
- pkill -9 -f "openclaw" 2>/dev/null || log_warn "pkill 失败"
266
- log_info "执行: killall -9 openclaw"
267
- killall -9 openclaw 2>/dev/null || log_warn "killall 失败"
268
-
269
- # 等待 2 秒让进程退出
270
- sleep 2
271
-
272
- # 连续监控模式:每秒检查并杀死看门狗重启的进程
273
- # 这样即使看门狗立即重启,也会被再次杀死
274
- log_info "进入连续监控模式(最多 20 秒),防止看门狗自动重启..."
275
- local elapsed=0
276
- local consecutive_empty=0
277
- while [ $elapsed -lt 20 ]; do
278
- sleep 1
279
-
280
- # 检查是否还有 openclaw 进程
281
- local remaining_pids=""
282
- if command -v pgrep &>/dev/null; then
283
- remaining_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
284
- elif command -v pidof &>/dev/null; then
285
- remaining_pids=$(pidof openclaw)
286
- else
287
- remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
288
- fi
289
-
290
- # 同时检查端口是否仍在监听
291
- local port_listening=false
292
- if check_port "$PORT"; then
293
- port_listening=true
294
- fi
295
-
296
- if [ -z "$remaining_pids" ] && [ "$port_listening" = false ]; then
297
- consecutive_empty=$((consecutive_empty + 1))
298
- log_info "第 $elapsed 秒: 无 openclaw 进程且端口未监听(连续 $consecutive_empty 次)"
299
- # 连续 3 秒没有进程且端口未监听,认为已停止
300
- if [ $consecutive_empty -ge 3 ]; then
301
- log_info "OpenClaw 服务已停止。(看门狗已放弃重启)"
302
- log_info "服务已成功停止。"
303
- log_info "Success"
304
- exit 0
305
- fi
306
- else
307
- consecutive_empty=0
308
- if [ -n "$remaining_pids" ]; then
309
- log_info "$elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
310
- for rp in $remaining_pids; do
311
- kill -9 "$rp" 2>/dev/null || true
312
- done
313
- fi
314
- if [ "$port_listening" = true ]; then
315
- log_info "第 $elapsed 秒: 端口 $PORT 仍在监听,尝试 fuser 终止..."
316
- if command -v fuser &>/dev/null; then
317
- fuser -k "${PORT}/tcp" 2>/dev/null || true
318
- fi
319
- fi
320
- pkill -9 -f "openclaw" 2>/dev/null || true
321
- killall -9 openclaw 2>/dev/null || true
322
- fi
323
-
324
- elapsed=$((elapsed + 1))
325
- done
326
-
327
- # 最终验证前,再执行一次全面清理
328
- log_info "执行最终清理..."
329
- pkill -9 -f "openclaw" 2>/dev/null || true
330
- killall -9 openclaw 2>/dev/null || true
331
-
332
- # 等待 2 秒
333
- sleep 2
334
-
335
- # 最终验证
336
- local remaining_pids=""
337
- if command -v pgrep &>/dev/null; then
338
- remaining_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
339
- elif command -v pidof &>/dev/null; then
340
- remaining_pids=$(pidof openclaw)
341
- else
342
- remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
343
- fi
344
-
345
- # 最终验证:显示当前所有进程
346
- log_info "最终验证: 当前 openclaw 进程: $remaining_pids"
347
- log_info "最终验证: 当前端口状态:"
348
- if command -v netstat &>/dev/null; then
349
- netstat -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
350
- elif command -v ss &>/dev/null; then
351
- ss -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
352
- fi
353
-
354
- # 最终验证:同时检查进程和端口
355
- local port_listening_final=false
356
- if check_port "$PORT"; then
357
- port_listening_final=true
358
- fi
359
-
360
- if [ -z "$remaining_pids" ] && [ "$port_listening_final" = false ]; then
361
- log_info "OpenClaw 服务已停止。"
362
- log_info "服务已成功停止。"
363
- log_info "Success"
364
- exit 0
365
- else
366
- if [ -n "$remaining_pids" ]; then
367
- log_error "OpenClaw 服务停止失败!进程仍然存在: $remaining_pids"
368
- fi
369
- if [ "$port_listening_final" = true ]; then
370
- log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
371
- fi
372
- exit 1
373
- fi
374
- }
375
-
376
- # Systemd 模式:停止服务
377
- # 参数: $1 = force (1=强制停止, 空=优雅停止)
378
- stop_systemd() {
379
- local force="$1"
380
-
381
- # 检查服务是否存在
382
- if ! systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null; then
383
- log_error "服务 $SERVICE_NAME 不存在。"
384
- exit 1
385
- fi
386
-
387
- # 检查服务状态
388
- log_info "检查 OpenClaw 服务状态..."
389
- if systemctl --user is-active --quiet "$SERVICE_NAME"; then
390
- log_info "OpenClaw 服务正在运行,准备停止..."
391
- else
392
- log_warn "OpenClaw 服务未在运行。"
393
- exit 0
394
- fi
395
-
396
- # 停止服务
397
- log_info "正在停止 OpenClaw 服务..."
398
-
399
- if systemctl --user stop "$SERVICE_NAME"; then
400
- log_info "OpenClaw 服务停止成功!"
401
-
402
- # 等待服务完全停止
403
- sleep 2
404
-
405
- # 验证服务状态
406
- if systemctl --user is-active --quiet "$SERVICE_NAME"; then
407
- if [ -n "$force" ]; then
408
- log_warn "服务仍在运行,执行强制停止..."
409
- # 获取进程 PID 并强制终止
410
- local main_pid
411
- main_pid=$(systemctl --user show "$SERVICE_NAME" --property=MainPID --value)
412
- if [ -n "$main_pid" ] && [ "$main_pid" != "0" ]; then
413
- log_info "强制终止进程 PID: $main_pid"
414
- kill -9 "$main_pid" 2>/dev/null || true
415
- fi
416
- # 同时清理所有 openclaw 相关进程
417
- pkill -9 -f "openclaw" 2>/dev/null || true
418
- killall -9 openclaw 2>/dev/null || true
419
-
420
- sleep 2
421
-
422
- if ! systemctl --user is-active --quiet "$SERVICE_NAME"; then
423
- log_info "服务已成功强制停止。"
424
- log_info "Success"
425
- exit 0
426
- else
427
- log_error "强制停止失败!服务仍在运行。"
428
- exit 1
429
- fi
430
- else
431
- log_warn "服务可能仍在运行,请检查进程。"
432
- systemctl --user status "$SERVICE_NAME" --no-pager
433
- fi
434
- else
435
- log_info "服务已成功停止。"
436
- log_info "Success"
437
- fi
438
- else
439
- log_error "OpenClaw 服务停止失败!"
440
- exit 1
441
- fi
442
- }
443
-
444
- # 显示帮助信息
445
- show_help() {
446
- echo "OpenClaw 服务停止脚本"
447
- echo ""
448
- echo "用法: $0 [选项]"
449
- echo ""
450
- echo "选项:"
451
- echo " -f, --force 强制停止服务"
452
- echo " -h, --help 显示此帮助信息"
453
- echo ""
454
- echo "示例:"
455
- echo " $0 正常停止服务"
456
- echo " $0 -f 强制停止服务"
457
- }
458
-
459
- # 主函数
460
- main() {
461
- local force=""
462
-
463
- # 检查 FORCE 环境变量(来自 script-executor 的强制停止模式)
464
- if [ "${FORCE:-}" = "1" ]; then
465
- force="1"
466
- log_info "检测到 FORCE=1 环境变量,启用强制停止模式"
467
- fi
468
-
469
- # 解析命令行参数
470
- while [ $# -gt 0 ]; do
471
- case $1 in
472
- -f|--force)
473
- force="1"
474
- shift
475
- ;;
476
- -h|--help)
477
- show_help
478
- exit 0
479
- ;;
480
- *)
481
- log_error "未知选项: $1"
482
- show_help
483
- exit 1
484
- ;;
485
- esac
486
- done
487
-
488
- if is_docker; then
489
- stop_docker "$force"
490
- else
491
- stop_systemd "$force"
492
- fi
493
- }
494
-
495
- # 执行主函数
496
- main "$@"
1
+ #!/bin/bash
2
+
3
+ # OpenClaw 服务停止脚本
4
+ # 用法: ./stop.sh [选项]
5
+ # 支持 systemd 和 Docker(无 systemd)双模式
6
+
7
+ # 注意:不使用 set -e,因为我们已经实现了完善的错误处理和验证逻辑
8
+ # set -e 可能导致 pgrep/pidof 找不到进程时脚本意外退出
9
+
10
+ # 调试模式:记录每条执行的命令(用于排查问题)
11
+ # set -x
12
+
13
+ # 颜色定义
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ NC='\033[0m' # No Color
18
+
19
+ # 日志函数
20
+ log_info() {
21
+ echo -e "${GREEN}[INFO]${NC} $1"
22
+ }
23
+
24
+ log_warn() {
25
+ echo -e "${YELLOW}[WARN]${NC} $1"
26
+ }
27
+
28
+ log_error() {
29
+ echo -e "${RED}[ERROR]${NC} $1"
30
+ }
31
+
32
+ # 服务名称
33
+ SERVICE_NAME="openclaw-gateway.service"
34
+
35
+ # 检测是否在 Docker 环境(无 systemd)
36
+ # 注意:某些 Docker 镜像安装了 systemctl 命令但无法使用
37
+ # 所以同时检查 systemd 是否实际运行
38
+ is_docker() {
39
+ # 方法1: 检查 systemctl 是否可用
40
+ if ! command -v systemctl &>/dev/null; then
41
+ return 0 # 无 systemctl,认为是 Docker
42
+ fi
43
+
44
+ # 方法2: 即使安装了 systemctl,检查 systemd 是否实际运行
45
+ # 在 Docker 中,/run/systemd/system 通常不存在
46
+ if [ ! -d "/run/systemd/system" ] && [ ! -d "/sys/fs/cgroup/systemd" ]; then
47
+ return 0 # systemd 未运行,认为是 Docker
48
+ fi
49
+
50
+ # 方法3: 尝试执行 systemctl status,如果失败则认为是 Docker
51
+ if ! systemctl status &>/dev/null; then
52
+ return 0 # systemctl 无法使用,认为是 Docker
53
+ fi
54
+
55
+ return 1
56
+ }
57
+
58
+ # 端口号
59
+ PORT="18789"
60
+
61
+ # 检查端口是否监听
62
+ # 注意:只检查端口,不检查进程。进程存在不等于端口在监听。
63
+ check_port() {
64
+ local port=$1
65
+ if command -v ss &>/dev/null; then
66
+ ss -tln 2>/dev/null | grep -q ":$port "
67
+ return $?
68
+ elif command -v netstat &>/dev/null; then
69
+ netstat -tln 2>/dev/null | grep -q ":$port "
70
+ return $?
71
+ elif command -v lsof &>/dev/null; then
72
+ lsof -i :$port 2>/dev/null | grep -q LISTEN
73
+ return $?
74
+ elif command -v fuser &>/dev/null; then
75
+ fuser $port/tcp 2>/dev/null | grep -q '[0-9]'
76
+ return $?
77
+ fi
78
+ # 如果所有工具都不可用,无法准确检查端口,保守返回 1(端口未监听)
79
+ return 1
80
+ }
81
+
82
+ # 获取 openclaw 进程 PID
83
+ get_openclaw_pid() {
84
+ local port=18789
85
+ local pid=""
86
+
87
+ # 按优先级尝试多种工具(适配精简 Docker 镜像)
88
+ # 方法1: lsof(最可靠)
89
+ if command -v lsof &>/dev/null; then
90
+ pid=$(lsof -i :${port} -t 2>/dev/null | head -1)
91
+ if [ -n "$pid" ]; then
92
+ echo "$pid"
93
+ return
94
+ fi
95
+ fi
96
+
97
+ # 方法2: fuser
98
+ if command -v fuser &>/dev/null; then
99
+ pid=$(fuser ${port}/tcp 2>/dev/null | tr -d ' ')
100
+ if [ -n "$pid" ]; then
101
+ echo "$pid"
102
+ return
103
+ fi
104
+ fi
105
+
106
+ # 方法3: ss
107
+ if command -v ss &>/dev/null; then
108
+ pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p')
109
+ if [ -n "$pid" ]; then
110
+ echo "$pid"
111
+ return
112
+ fi
113
+ fi
114
+
115
+ # 方法4: netstat
116
+ if command -v netstat &>/dev/null; then
117
+ pid=$(netstat -tnlp 2>/dev/null | grep ":${port} " | head -1 | awk '{print $7}' | cut -d'/' -f1)
118
+ if [ -n "$pid" ]; then
119
+ echo "$pid"
120
+ return
121
+ fi
122
+ fi
123
+
124
+ # 方法5: 通过 /proc/net/tcp 查找(不需要外部工具)
125
+ # 端口 18789 的十六进制 = 0x4965
126
+ local hex_port="4965"
127
+ for proc_dir in /proc/[0-9]*; do
128
+ if [ -f "$proc_dir/net/tcp" ]; then
129
+ # 检查该进程是否监听目标端口
130
+ if grep -q "[^0-9a-fA-F]${hex_port} " "$proc_dir/net/tcp" 2>/dev/null; then
131
+ basename "$proc_dir"
132
+ return
133
+ fi
134
+ fi
135
+ done
136
+
137
+ # 方法6: 通过进程名查找(当服务未监听预期端口时)
138
+ # 使用 pgrep/pidof/ps 查找 openclaw 进程
139
+ if command -v pgrep &>/dev/null; then
140
+ pid=$(pgrep -f "openclaw" | head -1)
141
+ if [ -n "$pid" ]; then
142
+ echo "$pid"
143
+ return
144
+ fi
145
+ fi
146
+
147
+ if command -v pidof &>/dev/null; then
148
+ pid=$(pidof openclaw | awk '{print $1}')
149
+ if [ -n "$pid" ]; then
150
+ echo "$pid"
151
+ return
152
+ fi
153
+ fi
154
+
155
+ # 最后尝试 ps
156
+ pid=$(ps aux | grep -v grep | grep "openclaw" | head -1 | awk '{print $2}')
157
+ if [ -n "$pid" ]; then
158
+ echo "$pid"
159
+ return
160
+ fi
161
+
162
+ echo ""
163
+ }
164
+
165
+ # Docker 模式:停止服务
166
+ # 参数: $1 = force (1=强制停止, 空=优雅停止)
167
+ stop_docker() {
168
+ local force="$1"
169
+ log_info "=== OpenClaw Docker 停止模式 ==="
170
+ log_info "检查 OpenClaw 服务状态..."
171
+
172
+ # 获取所有 openclaw 进程 PID
173
+ local all_pids=""
174
+ if command -v pgrep &>/dev/null; then
175
+ all_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
176
+ log_info "使用 pgrep 查找进程: $all_pids"
177
+ elif command -v pidof &>/dev/null; then
178
+ all_pids=$(pidof openclaw)
179
+ log_info "使用 pidof 查找进程: $all_pids"
180
+ else
181
+ all_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
182
+ log_info "使用 ps 查找进程: $all_pids"
183
+ fi
184
+
185
+ local pid
186
+ pid=$(get_openclaw_pid)
187
+
188
+ # 检查服务状态
189
+ if [ -z "$pid" ] && [ -z "$all_pids" ]; then
190
+ # 没有进程,检查端口
191
+ if check_port "$PORT"; then
192
+ log_warn "端口 $PORT 仍在监听,但无法获取 PID,尝试备选停止方案..."
193
+ # 尝试通过 fuser 直接通过端口杀进程
194
+ if command -v fuser &>/dev/null; then
195
+ log_info "使用 fuser 通过端口停止服务..."
196
+ fuser -k "${PORT}/tcp" &>/dev/null || true
197
+ sleep 2
198
+ fi
199
+ # 尝试通过 pkill 停止 openclaw 相关进程
200
+ if check_port "$PORT"; then
201
+ log_info "使用 pkill 停止 openclaw 进程..."
202
+ pkill -9 -f "openclaw" &>/dev/null || true
203
+ sleep 2
204
+ fi
205
+ fi
206
+
207
+ # 再次检查进程和端口
208
+ local final_check_pid
209
+ final_check_pid=$(get_openclaw_pid)
210
+ local final_check_port=false
211
+ if check_port "$PORT"; then
212
+ final_check_port=true
213
+ fi
214
+
215
+ if [ -z "$final_check_pid" ] && [ "$final_check_port" = false ]; then
216
+ log_warn "OpenClaw 服务未在运行。"
217
+ exit 0
218
+ else
219
+ if [ -n "$final_check_pid" ]; then
220
+ log_error "OpenClaw 服务停止失败!进程仍然存在: $final_check_pid"
221
+ fi
222
+ if [ "$final_check_port" = true ]; then
223
+ log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
224
+ fi
225
+ exit 1
226
+ fi
227
+ fi
228
+
229
+ log_info "发现 OpenClaw 进程: $all_pids"
230
+
231
+ # 如果不是强制模式,先尝试优雅停止
232
+ if [ -z "$force" ]; then
233
+ log_info "正在优雅停止 OpenClaw 服务..."
234
+ # 尝试发送 SIGTERM
235
+ for p in $all_pids; do
236
+ log_info "执行: kill -15 $p"
237
+ kill -15 "$p" 2>/dev/null || log_warn "kill -15 $p 失败"
238
+ done
239
+
240
+ # 等待 5 秒让服务优雅退出
241
+ log_info "等待 5 秒让服务优雅退出..."
242
+ sleep 5
243
+
244
+ # 检查是否已停止
245
+ local pid_after_graceful
246
+ pid_after_graceful=$(get_openclaw_pid)
247
+ if [ -z "$pid_after_graceful" ] && ! check_port "$PORT"; then
248
+ log_info "OpenClaw 服务已通过优雅停止退出。"
249
+ log_info "服务已成功停止。"
250
+ log_info "Success"
251
+ exit 0
252
+ fi
253
+
254
+ log_warn "优雅停止失败,服务仍在运行,切换到强制停止..."
255
+ fi
256
+
257
+ # 强制停止模式(直接发送 SIGKILL)
258
+ log_info "正在强制停止 OpenClaw 服务(SIGKILL)..."
259
+
260
+ # 获取最新的进程列表(因为优雅停止后可能有新进程)
261
+ local current_pids=""
262
+ if command -v pgrep &>/dev/null; then
263
+ current_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
264
+ elif command -v pidof &>/dev/null; then
265
+ current_pids=$(pidof openclaw)
266
+ else
267
+ current_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
268
+ fi
269
+
270
+ log_info "当前 openclaw 进程: $current_pids"
271
+
272
+ for p in $current_pids; do
273
+ log_info "执行: kill -9 $p"
274
+ kill -9 "$p" 2>/dev/null || log_warn "kill -9 $p 失败"
275
+ done
276
+ # 额外使用 pkill 确保所有相关进程都被停止
277
+ log_info "执行: pkill -9 -f openclaw"
278
+ pkill -9 -f "openclaw" 2>/dev/null || log_warn "pkill 失败"
279
+ log_info "执行: killall -9 openclaw"
280
+ killall -9 openclaw 2>/dev/null || log_warn "killall 失败"
281
+
282
+ # 等待 2 秒让进程退出
283
+ sleep 2
284
+
285
+ # 连续监控模式:每秒检查并杀死看门狗重启的进程
286
+ # 这样即使看门狗立即重启,也会被再次杀死
287
+ log_info "进入连续监控模式(最多 20 秒),防止看门狗自动重启..."
288
+ local elapsed=0
289
+ local consecutive_empty=0
290
+ while [ $elapsed -lt 20 ]; do
291
+ sleep 1
292
+
293
+ # 检查是否还有 openclaw 进程
294
+ local remaining_pids=""
295
+ if command -v pgrep &>/dev/null; then
296
+ remaining_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
297
+ elif command -v pidof &>/dev/null; then
298
+ remaining_pids=$(pidof openclaw)
299
+ else
300
+ remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
301
+ fi
302
+
303
+ # 同时检查端口是否仍在监听
304
+ local port_listening=false
305
+ if check_port "$PORT"; then
306
+ port_listening=true
307
+ fi
308
+
309
+ if [ -z "$remaining_pids" ] && [ "$port_listening" = false ]; then
310
+ consecutive_empty=$((consecutive_empty + 1))
311
+ log_info "第 $elapsed 秒: openclaw 进程且端口未监听(连续 $consecutive_empty 次)"
312
+ # 连续 3 秒没有进程且端口未监听,认为已停止
313
+ if [ $consecutive_empty -ge 3 ]; then
314
+ log_info "OpenClaw 服务已停止。(看门狗已放弃重启)"
315
+ log_info "服务已成功停止。"
316
+ log_info "Success"
317
+ exit 0
318
+ fi
319
+ else
320
+ consecutive_empty=0
321
+ if [ -n "$remaining_pids" ]; then
322
+ log_info "第 $elapsed 秒: 发现新进程 $remaining_pids,再次 kill..."
323
+ for rp in $remaining_pids; do
324
+ kill -9 "$rp" 2>/dev/null || true
325
+ done
326
+ fi
327
+ if [ "$port_listening" = true ]; then
328
+ log_info "第 $elapsed 秒: 端口 $PORT 仍在监听,尝试 fuser 终止..."
329
+ if command -v fuser &>/dev/null; then
330
+ fuser -k "${PORT}/tcp" 2>/dev/null || true
331
+ fi
332
+ fi
333
+ pkill -9 -f "openclaw" 2>/dev/null || true
334
+ killall -9 openclaw 2>/dev/null || true
335
+ fi
336
+
337
+ elapsed=$((elapsed + 1))
338
+ done
339
+
340
+ # 最终验证前,再执行一次全面清理
341
+ log_info "执行最终清理..."
342
+ pkill -9 -f "openclaw" 2>/dev/null || true
343
+ killall -9 openclaw 2>/dev/null || true
344
+
345
+ # 等待 2 秒
346
+ sleep 2
347
+
348
+ # 最终验证
349
+ local remaining_pids=""
350
+ if command -v pgrep &>/dev/null; then
351
+ remaining_pids=$(pgrep -f "openclaw" | tr '\n' ' ')
352
+ elif command -v pidof &>/dev/null; then
353
+ remaining_pids=$(pidof openclaw)
354
+ else
355
+ remaining_pids=$(ps aux | grep -v grep | grep "openclaw" | awk '{print $2}' | tr '\n' ' ')
356
+ fi
357
+
358
+ # 最终验证:显示当前所有进程
359
+ log_info "最终验证: 当前 openclaw 进程: $remaining_pids"
360
+ log_info "最终验证: 当前端口状态:"
361
+ if command -v netstat &>/dev/null; then
362
+ netstat -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
363
+ elif command -v ss &>/dev/null; then
364
+ ss -tlnp 2>/dev/null | grep ":${PORT} " || log_info "端口 ${PORT} 未监听"
365
+ fi
366
+
367
+ # 最终验证:同时检查进程和端口
368
+ local port_listening_final=false
369
+ if check_port "$PORT"; then
370
+ port_listening_final=true
371
+ fi
372
+
373
+ if [ -z "$remaining_pids" ] && [ "$port_listening_final" = false ]; then
374
+ log_info "OpenClaw 服务已停止。"
375
+ log_info "服务已成功停止。"
376
+ log_info "Success"
377
+ exit 0
378
+ else
379
+ if [ -n "$remaining_pids" ]; then
380
+ log_error "OpenClaw 服务停止失败!进程仍然存在: $remaining_pids"
381
+ fi
382
+ if [ "$port_listening_final" = true ]; then
383
+ log_error "OpenClaw 服务停止失败!端口 $PORT 仍在监听。"
384
+ fi
385
+ exit 1
386
+ fi
387
+ }
388
+
389
+ # Systemd 模式:停止服务
390
+ # 参数: $1 = force (1=强制停止, 空=优雅停止)
391
+ stop_systemd() {
392
+ local force="$1"
393
+
394
+ # 检查服务是否存在
395
+ if ! systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null; then
396
+ log_error "服务 $SERVICE_NAME 不存在。"
397
+ exit 1
398
+ fi
399
+
400
+ # 检查服务状态
401
+ log_info "检查 OpenClaw 服务状态..."
402
+ if systemctl --user is-active --quiet "$SERVICE_NAME"; then
403
+ log_info "OpenClaw 服务正在运行,准备停止..."
404
+ else
405
+ log_warn "OpenClaw 服务未在运行。"
406
+ exit 0
407
+ fi
408
+
409
+ # 停止服务
410
+ log_info "正在停止 OpenClaw 服务..."
411
+
412
+ if systemctl --user stop "$SERVICE_NAME"; then
413
+ log_info "OpenClaw 服务停止成功!"
414
+
415
+ # 等待服务完全停止
416
+ sleep 2
417
+
418
+ # 验证服务状态
419
+ if systemctl --user is-active --quiet "$SERVICE_NAME"; then
420
+ if [ -n "$force" ]; then
421
+ log_warn "服务仍在运行,执行强制停止..."
422
+ # 获取进程 PID 并强制终止
423
+ local main_pid
424
+ main_pid=$(systemctl --user show "$SERVICE_NAME" --property=MainPID --value)
425
+ if [ -n "$main_pid" ] && [ "$main_pid" != "0" ]; then
426
+ log_info "强制终止进程 PID: $main_pid"
427
+ kill -9 "$main_pid" 2>/dev/null || true
428
+ fi
429
+ # 同时清理所有 openclaw 相关进程
430
+ pkill -9 -f "openclaw" 2>/dev/null || true
431
+ killall -9 openclaw 2>/dev/null || true
432
+
433
+ sleep 2
434
+
435
+ if ! systemctl --user is-active --quiet "$SERVICE_NAME"; then
436
+ log_info "服务已成功强制停止。"
437
+ log_info "Success"
438
+ exit 0
439
+ else
440
+ log_error "强制停止失败!服务仍在运行。"
441
+ exit 1
442
+ fi
443
+ else
444
+ log_warn "服务可能仍在运行,请检查进程。"
445
+ systemctl --user status "$SERVICE_NAME" --no-pager
446
+ fi
447
+ else
448
+ log_info "服务已成功停止。"
449
+ log_info "Success"
450
+ fi
451
+ else
452
+ log_error "OpenClaw 服务停止失败!"
453
+ exit 1
454
+ fi
455
+ }
456
+
457
+ # 显示帮助信息
458
+ show_help() {
459
+ echo "OpenClaw 服务停止脚本"
460
+ echo ""
461
+ echo "用法: $0 [选项]"
462
+ echo ""
463
+ echo "选项:"
464
+ echo " -f, --force 强制停止服务"
465
+ echo " -h, --help 显示此帮助信息"
466
+ echo ""
467
+ echo "示例:"
468
+ echo " $0 正常停止服务"
469
+ echo " $0 -f 强制停止服务"
470
+ }
471
+
472
+ # 主函数
473
+ main() {
474
+ local force=""
475
+
476
+ # 检查 FORCE 环境变量(来自 script-executor 的强制停止模式)
477
+ if [ "${FORCE:-}" = "1" ]; then
478
+ force="1"
479
+ log_info "检测到 FORCE=1 环境变量,启用强制停止模式"
480
+ fi
481
+
482
+ # 解析命令行参数
483
+ while [ $# -gt 0 ]; do
484
+ case $1 in
485
+ -f|--force)
486
+ force="1"
487
+ shift
488
+ ;;
489
+ -h|--help)
490
+ show_help
491
+ exit 0
492
+ ;;
493
+ *)
494
+ log_error "未知选项: $1"
495
+ show_help
496
+ exit 1
497
+ ;;
498
+ esac
499
+ done
500
+
501
+ if is_docker; then
502
+ stop_docker "$force"
503
+ else
504
+ stop_systemd "$force"
505
+ fi
506
+ }
507
+
508
+ # 执行主函数
509
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.138",
3
+ "version": "0.0.140",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -247,10 +247,17 @@ class ScriptExecutor {
247
247
  console.log(`[ScriptExecutor-DEBUG] SHELL: ${process.env.SHELL}`);
248
248
  }
249
249
 
250
- const child = spawn(cmd, args, {
251
- detached: false,
250
+ // 对于启动脚本,使用 detached: true 允许子进程脱离父进程
251
+ // 这样 openclaw 进程不会在脚本结束后被终止
252
+ const isStartScript = scriptPath.includes('start');
253
+ const spawnOptions = {
254
+ detached: isStartScript, // 启动脚本使用 detached 模式
252
255
  windowsHide: true
253
- });
256
+ };
257
+
258
+ console.log(`[ScriptExecutor] spawn 选项: detached=${spawnOptions.detached}`);
259
+
260
+ const child = spawn(cmd, args, spawnOptions);
254
261
 
255
262
  let stdout = '';
256
263
  let stderr = '';