nox-openclaw-hunter 1.0.0 → 1.0.1

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.
Files changed (43) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -12
  3. package/dist/banner.d.ts +6 -0
  4. package/dist/banner.d.ts.map +1 -0
  5. package/dist/banner.js +31 -0
  6. package/dist/banner.js.map +1 -0
  7. package/dist/branding.d.ts +4 -4
  8. package/dist/branding.d.ts.map +1 -1
  9. package/dist/branding.js +26 -10
  10. package/dist/branding.js.map +1 -1
  11. package/dist/commands/export.d.ts.map +1 -1
  12. package/dist/commands/export.js +800 -58
  13. package/dist/commands/export.js.map +1 -1
  14. package/dist/detector/cli-binary.d.ts +7 -3
  15. package/dist/detector/cli-binary.d.ts.map +1 -1
  16. package/dist/detector/cli-binary.js +31 -22
  17. package/dist/detector/cli-binary.js.map +1 -1
  18. package/dist/detector/detection-config.d.ts.map +1 -1
  19. package/dist/detector/detection-config.js +3 -0
  20. package/dist/detector/detection-config.js.map +1 -1
  21. package/dist/detector/docker.js +1 -1
  22. package/dist/detector/docker.js.map +1 -1
  23. package/dist/detector/process.js +1 -1
  24. package/dist/detector/process.js.map +1 -1
  25. package/dist/isolator/lockdown.d.ts.map +1 -1
  26. package/dist/isolator/lockdown.js +11 -0
  27. package/dist/isolator/lockdown.js.map +1 -1
  28. package/dist/mdm/templates/detect.sh.d.ts.map +1 -1
  29. package/dist/mdm/templates/detect.sh.js +5 -1
  30. package/dist/mdm/templates/detect.sh.js.map +1 -1
  31. package/dist/mdm/templates/enforce.sh.d.ts.map +1 -1
  32. package/dist/mdm/templates/enforce.sh.js +5 -1
  33. package/dist/mdm/templates/enforce.sh.js.map +1 -1
  34. package/dist/platform/darwin.d.ts.map +1 -1
  35. package/dist/platform/darwin.js +15 -2
  36. package/dist/platform/darwin.js.map +1 -1
  37. package/dist/platform/linux.d.ts.map +1 -1
  38. package/dist/platform/linux.js +9 -1
  39. package/dist/platform/linux.js.map +1 -1
  40. package/dist/platform/windows.d.ts.map +1 -1
  41. package/dist/platform/windows.js +8 -5
  42. package/dist/platform/windows.js.map +1 -1
  43. package/package.json +5 -5
@@ -47,61 +47,353 @@ send_webhook() {
47
47
  # Generated by nox-openclaw-detector
48
48
  # https://nox.security
49
49
 
50
- set -euo pipefail
50
+ set -eo pipefail
51
+
52
+ # Set defaults for environment variables that may be unset when running as root
53
+ HOME="\${HOME:-/root}"
54
+ USER="\${USER:-root}"
51
55
 
52
56
  OPENCLAW_FOUND=0
53
57
  DETAILS=""
54
58
  ${webhookSection}
59
+
60
+ # Logging function
61
+ log() {
62
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
63
+ }
64
+
65
+ # Get all user home directories
66
+ get_all_user_homes() {
67
+ local homes=()
68
+
69
+ # macOS users
70
+ if [[ -d "/Users" ]]; then
71
+ for user_dir in /Users/*; do
72
+ if [[ -d "$user_dir" && "$user_dir" != "/Users/Shared" && "$user_dir" != "/Users/Guest" ]]; then
73
+ homes+=("$user_dir")
74
+ fi
75
+ done
76
+ fi
77
+
78
+ # Linux users
79
+ if [[ -d "/home" ]]; then
80
+ for user_dir in /home/*; do
81
+ if [[ -d "$user_dir" ]]; then
82
+ homes+=("$user_dir")
83
+ fi
84
+ done
85
+ fi
86
+
87
+ # Root home
88
+ if [[ -d "/root" ]]; then
89
+ homes+=("/root")
90
+ fi
91
+ if [[ -d "/var/root" ]]; then
92
+ homes+=("/var/root")
93
+ fi
94
+
95
+ echo "\${homes[@]}"
96
+ }
97
+
98
+ log "=========================================="
99
+ log "Nox OpenClaw Hunter - Detection Scan"
100
+ log "=========================================="
101
+ log "Hostname: $(hostname)"
102
+ log "Platform: $(uname -s) $(uname -m)"
103
+ log "User: $USER"
104
+ log "Date: $(date)"
105
+ log "=========================================="
106
+ log ""
107
+
55
108
  # Check if nox CLI is installed
109
+ log "Checking for nox CLI..."
56
110
  if command -v nox &>/dev/null; then
111
+ log " [INFO] nox CLI found, using for detection"
57
112
  if nox scan --quiet --json > /tmp/nox_scan_result.json 2>/dev/null; then
58
113
  SUMMARY=$(cat /tmp/nox_scan_result.json | grep -o '"summary":"[^"]*"' | cut -d'"' -f4 || echo "")
59
114
  if [[ "$SUMMARY" != "not-installed" ]]; then
60
115
  OPENCLAW_FOUND=1
61
116
  DETAILS="Detected via nox CLI: $SUMMARY"
117
+ log " [DETECTED] $DETAILS"
118
+ else
119
+ log " [CLEAN] No OpenClaw detected via nox CLI"
62
120
  fi
63
121
  rm -f /tmp/nox_scan_result.json
64
122
  fi
65
123
  ${webhookUrl ? 'send_webhook "$([[ $OPENCLAW_FOUND -eq 1 ]] && echo "detected" || echo "clean")" "$DETAILS"' : ''}
124
+
125
+ log ""
126
+ log "=========================================="
127
+ if [[ $OPENCLAW_FOUND -eq 1 ]]; then
128
+ log "RESULT: OpenClaw DETECTED"
129
+ else
130
+ log "RESULT: System CLEAN"
131
+ fi
132
+ log "=========================================="
66
133
  exit $OPENCLAW_FOUND
67
134
  fi
68
135
 
136
+ log " [INFO] nox CLI not found, using fallback detection"
137
+ log ""
138
+
69
139
  # Fallback: direct detection
140
+ # Scanning for: openclaw, clawbot, clawdbot, moltbot
70
141
 
71
- # Check CLI binary
72
- for cli_path in /usr/local/bin/openclaw /opt/homebrew/bin/openclaw /usr/bin/openclaw; do
73
- if [[ -f "$cli_path" ]]; then
74
- OPENCLAW_FOUND=1
75
- DETAILS="CLI found at $cli_path"
76
- break
77
- fi
142
+ # Check CLI binaries (system-wide and per-user)
143
+ log "Checking for CLI binaries (all locations)..."
144
+ CLI_FOUND=0
145
+
146
+ # System-wide locations
147
+ for cli_name in openclaw clawbot clawdbot moltbot; do
148
+ for bin_dir in /usr/local/bin /opt/homebrew/bin /usr/bin; do
149
+ cli_path="$bin_dir/$cli_name"
150
+ if [[ -f "$cli_path" ]]; then
151
+ OPENCLAW_FOUND=1
152
+ CLI_FOUND=1
153
+ DETAILS="$DETAILS; CLI found at $cli_path"
154
+ log " [DETECTED] CLI binary found at $cli_path"
155
+ fi
156
+ done
78
157
  done
79
158
 
80
- # Check config directory
81
- if [[ -d "$HOME/.openclaw" ]] || [[ -d "/Users/*/.openclaw" ]]; then
82
- OPENCLAW_FOUND=1
83
- DETAILS="$DETAILS; Config directory found"
159
+ # Per-user locations
160
+ for user_home in $(get_all_user_homes); do
161
+ for cli_name in openclaw clawbot clawdbot moltbot; do
162
+ for bin_dir in "$user_home/bin" "$user_home/.local/bin"; do
163
+ cli_path="$bin_dir/$cli_name"
164
+ if [[ -f "$cli_path" ]]; then
165
+ OPENCLAW_FOUND=1
166
+ CLI_FOUND=1
167
+ DETAILS="$DETAILS; CLI found at $cli_path"
168
+ log " [DETECTED] CLI binary found at $cli_path"
169
+ fi
170
+ done
171
+ done
172
+ done
173
+
174
+ if [[ $CLI_FOUND -eq 0 ]]; then
175
+ log " [CLEAN] No CLI binaries found"
84
176
  fi
85
177
 
86
- # Check macOS app bundle
87
- if [[ -d "/Applications/OpenClaw.app" ]]; then
88
- OPENCLAW_FOUND=1
89
- DETAILS="$DETAILS; App bundle found"
178
+ # Check config directories for ALL users
179
+ log ""
180
+ log "Checking for config directories (all users)..."
181
+ CONFIG_FOUND=0
182
+ for user_home in $(get_all_user_homes); do
183
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
184
+ config_path="$user_home/$config_name"
185
+ if [[ -d "$config_path" ]]; then
186
+ OPENCLAW_FOUND=1
187
+ CONFIG_FOUND=1
188
+ DETAILS="$DETAILS; Config directory found at $config_path"
189
+ log " [DETECTED] Config directory found at $config_path"
190
+ fi
191
+ done
192
+ done
193
+ if [[ $CONFIG_FOUND -eq 0 ]]; then
194
+ log " [CLEAN] No config directories found"
195
+ fi
196
+
197
+ # Check macOS app bundles (system-wide and per-user)
198
+ log ""
199
+ log "Checking for macOS app bundles (all locations)..."
200
+ APP_FOUND=0
201
+ for app_name in OpenClaw.app ClawBot.app ClawdBot.app MoltBot.app; do
202
+ # System-wide Applications
203
+ app_path="/Applications/$app_name"
204
+ if [[ -d "$app_path" ]]; then
205
+ OPENCLAW_FOUND=1
206
+ APP_FOUND=1
207
+ DETAILS="$DETAILS; App bundle found at $app_path"
208
+ log " [DETECTED] App bundle found at $app_path"
209
+ fi
210
+
211
+ # Per-user Applications folders
212
+ for user_home in $(get_all_user_homes); do
213
+ app_path="$user_home/Applications/$app_name"
214
+ if [[ -d "$app_path" ]]; then
215
+ OPENCLAW_FOUND=1
216
+ APP_FOUND=1
217
+ DETAILS="$DETAILS; App bundle found at $app_path"
218
+ log " [DETECTED] App bundle found at $app_path"
219
+ fi
220
+ done
221
+ done
222
+ if [[ $APP_FOUND -eq 0 ]]; then
223
+ log " [CLEAN] No app bundles found"
90
224
  fi
91
225
 
92
226
  # Check gateway port
227
+ log ""
228
+ log "Checking for gateway port 18789..."
93
229
  if command -v nc &>/dev/null && nc -z localhost 18789 2>/dev/null; then
94
230
  OPENCLAW_FOUND=1
95
231
  DETAILS="$DETAILS; Gateway port 18789 listening"
232
+ log " [DETECTED] Gateway port 18789 is listening"
233
+ elif command -v lsof &>/dev/null && lsof -i :18789 -sTCP:LISTEN &>/dev/null; then
234
+ OPENCLAW_FOUND=1
235
+ DETAILS="$DETAILS; Gateway port 18789 listening"
236
+ log " [DETECTED] Gateway port 18789 is listening"
237
+ else
238
+ log " [CLEAN] Gateway port 18789 not listening"
96
239
  fi
97
240
 
98
241
  # Check running processes
99
- if pgrep -x "openclaw" > /dev/null 2>&1 || pgrep -f "openclaw" > /dev/null 2>&1; then
100
- OPENCLAW_FOUND=1
101
- DETAILS="$DETAILS; Process running"
242
+ log ""
243
+ log "Checking for running processes (openclaw, clawbot, clawdbot, moltbot)..."
244
+ PROC_FOUND=0
245
+ for proc_name in openclaw clawbot clawdbot moltbot; do
246
+ if pgrep -x "$proc_name" > /dev/null 2>&1; then
247
+ OPENCLAW_FOUND=1
248
+ PROC_FOUND=1
249
+ PIDS=$(pgrep -x "$proc_name" | tr '\\n' ' ')
250
+ DETAILS="$DETAILS; Process $proc_name running (PIDs: $PIDS)"
251
+ log " [DETECTED] $proc_name process running (PIDs: $PIDS)"
252
+ elif pgrep -f "$proc_name" > /dev/null 2>&1; then
253
+ OPENCLAW_FOUND=1
254
+ PROC_FOUND=1
255
+ PIDS=$(pgrep -f "$proc_name" | tr '\\n' ' ')
256
+ DETAILS="$DETAILS; Process $proc_name running (PIDs: $PIDS)"
257
+ log " [DETECTED] $proc_name-related process running (PIDs: $PIDS)"
258
+ fi
259
+ done
260
+ if [[ $PROC_FOUND -eq 0 ]]; then
261
+ log " [CLEAN] No matching processes running"
262
+ fi
263
+
264
+ # Check launchd services (macOS) - all users
265
+ if [[ "$(uname)" == "Darwin" ]]; then
266
+ log ""
267
+ log "Checking for launchd services (all users)..."
268
+ LAUNCHD_FOUND=0
269
+
270
+ # System-wide LaunchAgents and LaunchDaemons
271
+ for plist_dir in /Library/LaunchAgents /Library/LaunchDaemons; do
272
+ if [[ -d "$plist_dir" ]]; then
273
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
274
+ for plist in "$plist_dir"/$pattern; do
275
+ if [[ -f "$plist" ]]; then
276
+ OPENCLAW_FOUND=1
277
+ LAUNCHD_FOUND=1
278
+ DETAILS="$DETAILS; LaunchAgent found: $plist"
279
+ log " [DETECTED] LaunchAgent/Daemon found: $plist"
280
+ fi
281
+ done
282
+ done
283
+ fi
284
+ done
285
+
286
+ # Per-user LaunchAgents
287
+ for user_home in $(get_all_user_homes); do
288
+ plist_dir="$user_home/Library/LaunchAgents"
289
+ if [[ -d "$plist_dir" ]]; then
290
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
291
+ for plist in "$plist_dir"/$pattern; do
292
+ if [[ -f "$plist" ]]; then
293
+ OPENCLAW_FOUND=1
294
+ LAUNCHD_FOUND=1
295
+ DETAILS="$DETAILS; LaunchAgent found: $plist"
296
+ log " [DETECTED] LaunchAgent/Daemon found: $plist"
297
+ fi
298
+ done
299
+ done
300
+ fi
301
+ done
302
+
303
+ if [[ $LAUNCHD_FOUND -eq 0 ]]; then
304
+ log " [CLEAN] No launchd services found"
305
+ fi
306
+ fi
307
+
308
+ # Check systemd services (Linux) - system and user level
309
+ if command -v systemctl &>/dev/null; then
310
+ log ""
311
+ log "Checking for systemd services (system and user level)..."
312
+ SYSTEMD_FOUND=0
313
+
314
+ # System-level services
315
+ for svc_name in openclaw clawbot clawdbot moltbot; do
316
+ if systemctl list-unit-files "$svc_name.service" &>/dev/null 2>&1; then
317
+ OPENCLAW_FOUND=1
318
+ SYSTEMD_FOUND=1
319
+ STATUS=$(systemctl is-active "$svc_name.service" 2>/dev/null || echo "inactive")
320
+ DETAILS="$DETAILS; Systemd service $svc_name found (status: $STATUS)"
321
+ log " [DETECTED] Systemd service $svc_name found (status: $STATUS)"
322
+ fi
323
+
324
+ # Check service file directly
325
+ for svc_path in /etc/systemd/system /usr/lib/systemd/system; do
326
+ if [[ -f "$svc_path/$svc_name.service" ]]; then
327
+ OPENCLAW_FOUND=1
328
+ SYSTEMD_FOUND=1
329
+ DETAILS="$DETAILS; Systemd service file found: $svc_path/$svc_name.service"
330
+ log " [DETECTED] Systemd service file found: $svc_path/$svc_name.service"
331
+ fi
332
+ done
333
+ done
334
+
335
+ # User-level systemd services
336
+ for user_home in $(get_all_user_homes); do
337
+ user_systemd="$user_home/.config/systemd/user"
338
+ if [[ -d "$user_systemd" ]]; then
339
+ for svc_name in openclaw clawbot clawdbot moltbot; do
340
+ if [[ -f "$user_systemd/$svc_name.service" ]]; then
341
+ OPENCLAW_FOUND=1
342
+ SYSTEMD_FOUND=1
343
+ DETAILS="$DETAILS; User systemd service found: $user_systemd/$svc_name.service"
344
+ log " [DETECTED] User systemd service found: $user_systemd/$svc_name.service"
345
+ fi
346
+ done
347
+ fi
348
+ done
349
+
350
+ if [[ $SYSTEMD_FOUND -eq 0 ]]; then
351
+ log " [CLEAN] No systemd services found"
352
+ fi
353
+ fi
354
+
355
+ # Check Docker
356
+ if command -v docker &>/dev/null; then
357
+ log ""
358
+ log "Checking for Docker artifacts..."
359
+ DOCKER_FOUND=0
360
+ for docker_name in openclaw clawbot clawdbot moltbot; do
361
+ CONTAINERS=$(docker ps -a --filter "name=$docker_name" --format "{{.Names}}" 2>/dev/null || true)
362
+ if [[ -n "$CONTAINERS" ]]; then
363
+ OPENCLAW_FOUND=1
364
+ DOCKER_FOUND=1
365
+ DETAILS="$DETAILS; Docker containers ($docker_name): $CONTAINERS"
366
+ log " [DETECTED] Docker containers found ($docker_name): $CONTAINERS"
367
+ fi
368
+ IMAGES=$(docker images --filter "reference=*$docker_name*" --format "{{.Repository}}:{{.Tag}}" 2>/dev/null || true)
369
+ if [[ -n "$IMAGES" ]]; then
370
+ OPENCLAW_FOUND=1
371
+ DOCKER_FOUND=1
372
+ DETAILS="$DETAILS; Docker images ($docker_name): $IMAGES"
373
+ log " [DETECTED] Docker images found ($docker_name): $IMAGES"
374
+ fi
375
+ done
376
+ if [[ $DOCKER_FOUND -eq 0 ]]; then
377
+ log " [CLEAN] No Docker artifacts found"
378
+ fi
102
379
  fi
103
380
  ${webhookUrl ? '\nsend_webhook "$([[ $OPENCLAW_FOUND -eq 1 ]] && echo "detected" || echo "clean")" "$DETAILS"' : ''}
104
381
 
382
+ # Final summary
383
+ log ""
384
+ log "=========================================="
385
+ log "SCAN SUMMARY"
386
+ log "=========================================="
387
+ if [[ $OPENCLAW_FOUND -eq 1 ]]; then
388
+ log "STATUS: OpenClaw DETECTED"
389
+ log "DETAILS: $DETAILS"
390
+ log "EXIT CODE: 1"
391
+ else
392
+ log "STATUS: System CLEAN - No OpenClaw installation detected"
393
+ log "EXIT CODE: 0"
394
+ fi
395
+ log "=========================================="
396
+
105
397
  exit $OPENCLAW_FOUND
106
398
  `;
107
399
  }
@@ -230,8 +522,13 @@ send_webhook() {
230
522
  #
231
523
  # WARNING: This script will remove OpenClaw installations!
232
524
  # Run with sudo for full purge capabilities.
525
+ # Exit code is always 0 if the script runs to completion - check logs for details.
526
+
527
+ set -o pipefail
233
528
 
234
- set -euo pipefail
529
+ # Set defaults for environment variables that may be unset when running as root
530
+ HOME="\${HOME:-/root}"
531
+ USER="\${USER:-root}"
235
532
 
236
533
  PURGE_RESULTS=""
237
534
  ${webhookSection}
@@ -240,68 +537,516 @@ log_action() {
240
537
  PURGE_RESULTS="$PURGE_RESULTS; $1"
241
538
  }
242
539
 
540
+ # Get all user home directories
541
+ get_all_user_homes() {
542
+ local homes=()
543
+
544
+ # macOS users
545
+ if [[ -d "/Users" ]]; then
546
+ for user_dir in /Users/*; do
547
+ if [[ -d "$user_dir" && "$user_dir" != "/Users/Shared" && "$user_dir" != "/Users/Guest" ]]; then
548
+ homes+=("$user_dir")
549
+ fi
550
+ done
551
+ fi
552
+
553
+ # Linux users
554
+ if [[ -d "/home" ]]; then
555
+ for user_dir in /home/*; do
556
+ if [[ -d "$user_dir" ]]; then
557
+ homes+=("$user_dir")
558
+ fi
559
+ done
560
+ fi
561
+
562
+ # Root home
563
+ if [[ -d "/root" ]]; then
564
+ homes+=("/root")
565
+ fi
566
+ if [[ -d "/var/root" ]]; then
567
+ homes+=("/var/root")
568
+ fi
569
+
570
+ echo "\${homes[@]}"
571
+ }
572
+
573
+ log_action "=========================================="
574
+ log_action "Nox OpenClaw Hunter - Purge (All Users)"
575
+ log_action "=========================================="
576
+
243
577
  # Check if running as root
244
- if [[ $EUID -ne 0 ]]; then
578
+ if [[ \${EUID:-$(id -u)} -ne 0 ]]; then
245
579
  echo "Warning: Not running as root. Some actions may fail."
580
+ echo "For complete purge across all users, run with sudo."
246
581
  fi
247
582
 
248
- # Kill running processes
249
- log_action "Killing OpenClaw processes..."
250
- pkill -9 -f "openclaw" 2>/dev/null || log_action "No processes to kill"
583
+ # STEP 1: Unload launchd services FIRST to prevent process respawning (macOS)
584
+ if [[ "$(uname)" == "Darwin" ]]; then
585
+ log_action "Unloading launchd services to prevent respawning..."
586
+
587
+ # System-wide LaunchAgents and LaunchDaemons
588
+ for plist_dir in /Library/LaunchAgents /Library/LaunchDaemons; do
589
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
590
+ for plist in "$plist_dir"/$pattern; do
591
+ if [[ -f "$plist" ]]; then
592
+ launchctl bootout system "$plist" 2>/dev/null || launchctl unload "$plist" 2>/dev/null || true
593
+ fi
594
+ done
595
+ done
596
+ done
597
+
598
+ # Per-user LaunchAgents - unload for all users
599
+ for user_home in $(get_all_user_homes); do
600
+ plist_dir="$user_home/Library/LaunchAgents"
601
+ username=$(basename "$user_home")
602
+ user_uid=$(id -u "$username" 2>/dev/null || echo "")
603
+ if [[ -d "$plist_dir" ]]; then
604
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
605
+ for plist in "$plist_dir"/$pattern; do
606
+ if [[ -f "$plist" ]]; then
607
+ if [[ -n "$user_uid" ]]; then
608
+ launchctl bootout "gui/$user_uid" "$plist" 2>/dev/null || true
609
+ launchctl bootout "user/$user_uid" "$plist" 2>/dev/null || true
610
+ fi
611
+ launchctl unload "$plist" 2>/dev/null || true
612
+ fi
613
+ done
614
+ done
615
+ fi
616
+ done
617
+ log_action "Launchd services unloaded"
618
+ fi
619
+
620
+ # STEP 1b: Stop systemd services FIRST to prevent respawning (Linux)
621
+ if command -v systemctl &>/dev/null; then
622
+ log_action "Stopping systemd services to prevent respawning..."
623
+ for svc_name in openclaw clawbot clawdbot moltbot; do
624
+ systemctl stop "$svc_name.service" 2>/dev/null || true
625
+ systemctl disable "$svc_name.service" 2>/dev/null || true
626
+ done
627
+
628
+ # User-level services
629
+ for user_home in $(get_all_user_homes); do
630
+ username=$(basename "$user_home")
631
+ for svc_name in openclaw clawbot clawdbot moltbot; do
632
+ sudo -u "$username" systemctl --user stop "$svc_name.service" 2>/dev/null || true
633
+ sudo -u "$username" systemctl --user disable "$svc_name.service" 2>/dev/null || true
634
+ done
635
+ done
636
+ log_action "Systemd services stopped"
637
+ fi
638
+
639
+ # STEP 2: Kill running processes
640
+ log_action "Killing processes (openclaw, clawbot, clawdbot, moltbot)..."
641
+ PROCS_KILLED=0
642
+ for proc_name in openclaw clawbot clawdbot moltbot; do
643
+ # Find PIDs first (exclude our own script)
644
+ PIDS=$(pgrep -x "$proc_name" 2>/dev/null || true)
645
+ if [[ -z "$PIDS" ]]; then
646
+ # Try matching command line if exact name match fails
647
+ PIDS=$(pgrep -f "/$proc_name\$" 2>/dev/null || true)
648
+ fi
649
+
650
+ if [[ -n "$PIDS" ]]; then
651
+ for pid in $PIDS; do
652
+ # Skip if it's our own process
653
+ if [[ "$pid" == "$$" ]]; then
654
+ continue
655
+ fi
656
+ kill -9 "$pid" 2>/dev/null && {
657
+ log_action " Killed $proc_name (PID: $pid)"
658
+ PROCS_KILLED=$((PROCS_KILLED + 1))
659
+ } || true
660
+ done
661
+ fi
662
+
663
+ # Also try pkill as backup
664
+ pkill -9 -x "$proc_name" 2>/dev/null || true
665
+
666
+ # On macOS, also try killall
667
+ if [[ "$(uname)" == "Darwin" ]]; then
668
+ killall -9 "$proc_name" 2>/dev/null || true
669
+ fi
670
+ done
671
+
672
+ # Wait briefly for processes to terminate
673
+ sleep 1
251
674
 
252
- # Stop and disable launchd service (macOS)
675
+ # Verify processes are gone
676
+ REMAINING=""
677
+ for proc_name in openclaw clawbot clawdbot moltbot; do
678
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
679
+ REMAINING="$REMAINING $proc_name"
680
+ fi
681
+ done
682
+
683
+ if [[ -n "$REMAINING" ]]; then
684
+ log_action "WARNING: Some processes may still be running:$REMAINING"
685
+ # Try one more aggressive kill
686
+ for proc_name in $REMAINING; do
687
+ pkill -9 -f "$proc_name" 2>/dev/null || true
688
+ done
689
+ sleep 1
690
+
691
+ # Final check
692
+ STILL_RUNNING=""
693
+ for proc_name in openclaw clawbot clawdbot moltbot; do
694
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
695
+ STILL_RUNNING="$STILL_RUNNING $proc_name($(pgrep -x "$proc_name" | tr '\\n' ' '))"
696
+ fi
697
+ done
698
+
699
+ if [[ -n "$STILL_RUNNING" ]]; then
700
+ log_action "ERROR: Failed to kill processes:$STILL_RUNNING - may require manual intervention"
701
+ else
702
+ log_action "Process kill completed (after retry)"
703
+ fi
704
+ else
705
+ if [[ $PROCS_KILLED -gt 0 ]]; then
706
+ log_action "Process kill completed ($PROCS_KILLED processes terminated)"
707
+ else
708
+ log_action "Process kill completed (no matching processes found)"
709
+ fi
710
+ fi
711
+
712
+ # Stop and disable launchd services (macOS) - ALL USERS
253
713
  if [[ "$(uname)" == "Darwin" ]]; then
254
- for plist in ~/Library/LaunchAgents/bot.molt.* /Library/LaunchAgents/bot.molt.* /Library/LaunchDaemons/bot.molt.*; do
255
- if [[ -f "$plist" ]]; then
256
- launchctl unload "$plist" 2>/dev/null || true
257
- rm -f "$plist"
258
- log_action "Removed launchd plist: $plist"
714
+ log_action "Removing launchd services (all users)..."
715
+ LAUNCHD_REMOVED=0
716
+ LAUNCHD_FAILED=0
717
+
718
+ # System-wide LaunchAgents and LaunchDaemons
719
+ for plist_dir in /Library/LaunchAgents /Library/LaunchDaemons; do
720
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
721
+ for plist in "$plist_dir"/$pattern; do
722
+ if [[ -f "$plist" ]]; then
723
+ launchctl unload "$plist" 2>/dev/null || true
724
+ rm -f "$plist" 2>/dev/null
725
+ if [[ -f "$plist" ]]; then
726
+ log_action " FAILED to remove launchd plist: $plist"
727
+ LAUNCHD_FAILED=$((LAUNCHD_FAILED + 1))
728
+ else
729
+ log_action " Removed launchd plist: $plist"
730
+ LAUNCHD_REMOVED=$((LAUNCHD_REMOVED + 1))
731
+ fi
732
+ fi
733
+ done
734
+ done
735
+ done
736
+
737
+ # Per-user LaunchAgents
738
+ for user_home in $(get_all_user_homes); do
739
+ plist_dir="$user_home/Library/LaunchAgents"
740
+ if [[ -d "$plist_dir" ]]; then
741
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
742
+ for plist in "$plist_dir"/$pattern; do
743
+ if [[ -f "$plist" ]]; then
744
+ launchctl unload "$plist" 2>/dev/null || true
745
+ rm -f "$plist" 2>/dev/null
746
+ if [[ -f "$plist" ]]; then
747
+ log_action " FAILED to remove launchd plist: $plist"
748
+ LAUNCHD_FAILED=$((LAUNCHD_FAILED + 1))
749
+ else
750
+ log_action " Removed launchd plist: $plist"
751
+ LAUNCHD_REMOVED=$((LAUNCHD_REMOVED + 1))
752
+ fi
753
+ fi
754
+ done
755
+ done
259
756
  fi
260
757
  done
758
+
759
+ if [[ $LAUNCHD_FAILED -gt 0 ]]; then
760
+ log_action "Launchd removal: $LAUNCHD_REMOVED removed, $LAUNCHD_FAILED failed"
761
+ elif [[ $LAUNCHD_REMOVED -gt 0 ]]; then
762
+ log_action "Launchd removal completed ($LAUNCHD_REMOVED services removed)"
763
+ else
764
+ log_action "Launchd removal completed (no launchd services found)"
765
+ fi
261
766
  fi
262
767
 
263
- # Stop and disable systemd service (Linux)
768
+ # Stop and disable systemd services (Linux) - system and user level
264
769
  if command -v systemctl &>/dev/null; then
265
- systemctl stop openclaw.service 2>/dev/null || true
266
- systemctl disable openclaw.service 2>/dev/null || true
267
- rm -f /etc/systemd/system/openclaw.service 2>/dev/null || true
770
+ log_action "Removing systemd services (system and user level)..."
771
+ SYSTEMD_REMOVED=0
772
+ SYSTEMD_FAILED=0
773
+
774
+ # System-level services
775
+ for svc_name in openclaw clawbot clawdbot moltbot; do
776
+ systemctl stop "$svc_name.service" 2>/dev/null || true
777
+ systemctl disable "$svc_name.service" 2>/dev/null || true
778
+
779
+ for svc_path in "/etc/systemd/system/$svc_name.service" "/usr/lib/systemd/system/$svc_name.service"; do
780
+ if [[ -f "$svc_path" ]]; then
781
+ rm -f "$svc_path" 2>/dev/null
782
+ if [[ -f "$svc_path" ]]; then
783
+ log_action " FAILED to remove systemd service: $svc_path"
784
+ SYSTEMD_FAILED=$((SYSTEMD_FAILED + 1))
785
+ else
786
+ log_action " Removed systemd service: $svc_path"
787
+ SYSTEMD_REMOVED=$((SYSTEMD_REMOVED + 1))
788
+ fi
789
+ fi
790
+ done
791
+ done
792
+
793
+ # User-level systemd services
794
+ for user_home in $(get_all_user_homes); do
795
+ user_systemd="$user_home/.config/systemd/user"
796
+ if [[ -d "$user_systemd" ]]; then
797
+ for svc_name in openclaw clawbot clawdbot moltbot; do
798
+ svc_path="$user_systemd/$svc_name.service"
799
+ if [[ -f "$svc_path" ]]; then
800
+ rm -f "$svc_path" 2>/dev/null
801
+ if [[ -f "$svc_path" ]]; then
802
+ log_action " FAILED to remove user systemd service: $svc_path"
803
+ SYSTEMD_FAILED=$((SYSTEMD_FAILED + 1))
804
+ else
805
+ log_action " Removed user systemd service: $svc_path"
806
+ SYSTEMD_REMOVED=$((SYSTEMD_REMOVED + 1))
807
+ fi
808
+ fi
809
+ done
810
+ fi
811
+ done
812
+
268
813
  systemctl daemon-reload 2>/dev/null || true
269
- log_action "Stopped and disabled systemd service"
814
+
815
+ if [[ $SYSTEMD_FAILED -gt 0 ]]; then
816
+ log_action "Systemd removal: $SYSTEMD_REMOVED removed, $SYSTEMD_FAILED failed"
817
+ elif [[ $SYSTEMD_REMOVED -gt 0 ]]; then
818
+ log_action "Systemd removal completed ($SYSTEMD_REMOVED services removed)"
819
+ else
820
+ log_action "Systemd removal completed (no systemd services found)"
821
+ fi
270
822
  fi
271
823
 
272
- # Remove CLI binary
273
- for cli_path in /usr/local/bin/openclaw /opt/homebrew/bin/openclaw /usr/bin/openclaw; do
274
- if [[ -f "$cli_path" ]]; then
275
- rm -f "$cli_path"
276
- log_action "Removed CLI: $cli_path"
277
- fi
824
+ # Remove CLI binaries (system-wide and per-user)
825
+ log_action "Removing CLI binaries (all locations)..."
826
+ CLI_REMOVED=0
827
+ CLI_FAILED=0
828
+
829
+ # System-wide locations
830
+ for cli_name in openclaw clawbot clawdbot moltbot; do
831
+ for bin_dir in /usr/local/bin /opt/homebrew/bin /usr/bin; do
832
+ cli_path="$bin_dir/$cli_name"
833
+ if [[ -f "$cli_path" ]]; then
834
+ rm -f "$cli_path" 2>/dev/null
835
+ if [[ -f "$cli_path" ]]; then
836
+ log_action " FAILED to remove CLI: $cli_path (permission denied)"
837
+ CLI_FAILED=$((CLI_FAILED + 1))
838
+ else
839
+ log_action " Removed CLI: $cli_path"
840
+ CLI_REMOVED=$((CLI_REMOVED + 1))
841
+ fi
842
+ fi
843
+ done
278
844
  done
279
845
 
280
- # Remove macOS app bundle
281
- if [[ -d "/Applications/OpenClaw.app" ]]; then
282
- rm -rf "/Applications/OpenClaw.app"
283
- log_action "Removed app bundle: /Applications/OpenClaw.app"
846
+ # Per-user locations
847
+ for user_home in $(get_all_user_homes); do
848
+ for cli_name in openclaw clawbot clawdbot moltbot; do
849
+ for bin_dir in "$user_home/bin" "$user_home/.local/bin"; do
850
+ cli_path="$bin_dir/$cli_name"
851
+ if [[ -f "$cli_path" ]]; then
852
+ rm -f "$cli_path" 2>/dev/null
853
+ if [[ -f "$cli_path" ]]; then
854
+ log_action " FAILED to remove CLI: $cli_path (permission denied)"
855
+ CLI_FAILED=$((CLI_FAILED + 1))
856
+ else
857
+ log_action " Removed CLI: $cli_path"
858
+ CLI_REMOVED=$((CLI_REMOVED + 1))
859
+ fi
860
+ fi
861
+ done
862
+ done
863
+ done
864
+
865
+ if [[ $CLI_FAILED -gt 0 ]]; then
866
+ log_action "CLI removal: $CLI_REMOVED removed, $CLI_FAILED failed"
867
+ elif [[ $CLI_REMOVED -gt 0 ]]; then
868
+ log_action "CLI removal completed ($CLI_REMOVED binaries removed)"
869
+ else
870
+ log_action "CLI removal completed (no CLI binaries found)"
284
871
  fi
285
872
 
286
- # Remove config directories
287
- for config_dir in ~/.openclaw /Users/*/.openclaw; do
288
- if [[ -d "$config_dir" ]]; then
289
- rm -rf "$config_dir"
290
- log_action "Removed config: $config_dir"
873
+ # Remove macOS app bundles (system-wide and per-user)
874
+ log_action "Removing app bundles (all locations)..."
875
+ APP_REMOVED=0
876
+ APP_FAILED=0
877
+
878
+ for app_name in OpenClaw.app ClawBot.app ClawdBot.app MoltBot.app; do
879
+ # System-wide Applications
880
+ if [[ -d "/Applications/$app_name" ]]; then
881
+ rm -rf "/Applications/$app_name" 2>/dev/null
882
+ if [[ -d "/Applications/$app_name" ]]; then
883
+ log_action " FAILED to remove app: /Applications/$app_name (permission denied or protected)"
884
+ APP_FAILED=$((APP_FAILED + 1))
885
+ else
886
+ log_action " Removed app bundle: /Applications/$app_name"
887
+ APP_REMOVED=$((APP_REMOVED + 1))
888
+ fi
291
889
  fi
890
+
891
+ # Per-user Applications
892
+ for user_home in $(get_all_user_homes); do
893
+ if [[ -d "$user_home/Applications/$app_name" ]]; then
894
+ rm -rf "$user_home/Applications/$app_name" 2>/dev/null
895
+ if [[ -d "$user_home/Applications/$app_name" ]]; then
896
+ log_action " FAILED to remove app: $user_home/Applications/$app_name (permission denied)"
897
+ APP_FAILED=$((APP_FAILED + 1))
898
+ else
899
+ log_action " Removed app bundle: $user_home/Applications/$app_name"
900
+ APP_REMOVED=$((APP_REMOVED + 1))
901
+ fi
902
+ fi
903
+ done
904
+ done
905
+
906
+ if [[ $APP_FAILED -gt 0 ]]; then
907
+ log_action "App bundle removal: $APP_REMOVED removed, $APP_FAILED failed"
908
+ elif [[ $APP_REMOVED -gt 0 ]]; then
909
+ log_action "App bundle removal completed ($APP_REMOVED bundles removed)"
910
+ else
911
+ log_action "App bundle removal completed (no app bundles found)"
912
+ fi
913
+
914
+ # Remove config directories for ALL USERS
915
+ log_action "Removing config directories (all users)..."
916
+ CONFIG_REMOVED=0
917
+ CONFIG_FAILED=0
918
+ for user_home in $(get_all_user_homes); do
919
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
920
+ config_path="$user_home/$config_name"
921
+ if [[ -d "$config_path" ]]; then
922
+ # On macOS: remove extended attributes and clear immutable flags
923
+ if [[ "$(uname)" == "Darwin" ]]; then
924
+ xattr -cr "$config_path" 2>/dev/null || true
925
+ chflags -R nouchg "$config_path" 2>/dev/null || true
926
+ fi
927
+ # On Linux: remove immutable attribute
928
+ if command -v chattr &>/dev/null; then
929
+ chattr -R -i "$config_path" 2>/dev/null || true
930
+ fi
931
+
932
+ # Remove all contents first, then the directory
933
+ find "$config_path" -type f -delete 2>/dev/null || true
934
+ find "$config_path" -type d -empty -delete 2>/dev/null || true
935
+ rm -rf "$config_path" 2>/dev/null || true
936
+
937
+ # If still exists, try moving to temp and deleting
938
+ if [[ -d "$config_path" ]]; then
939
+ temp_path="/tmp/.nox_purge_$$_$(basename "$config_path")"
940
+ mv "$config_path" "$temp_path" 2>/dev/null && rm -rf "$temp_path" 2>/dev/null || true
941
+ fi
942
+
943
+ # Verify removal
944
+ if [[ -d "$config_path" ]]; then
945
+ log_action " FAILED to remove config: $config_path (permission denied or protected)"
946
+ CONFIG_FAILED=$((CONFIG_FAILED + 1))
947
+ else
948
+ log_action " Removed config: $config_path"
949
+ CONFIG_REMOVED=$((CONFIG_REMOVED + 1))
950
+ fi
951
+ fi
952
+ done
292
953
  done
954
+ if [[ $CONFIG_FAILED -gt 0 ]]; then
955
+ log_action "Config removal: $CONFIG_REMOVED removed, $CONFIG_FAILED failed"
956
+ elif [[ $CONFIG_REMOVED -gt 0 ]]; then
957
+ log_action "Config removal completed ($CONFIG_REMOVED directories removed)"
958
+ else
959
+ log_action "Config removal completed (no config directories found)"
960
+ fi
293
961
 
294
962
  # Remove Docker containers and images
295
963
  if command -v docker &>/dev/null; then
296
- docker ps -a --filter "name=openclaw" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true
297
- docker images --filter "reference=*openclaw*" --format "{{.ID}}" | xargs -r docker rmi -f 2>/dev/null || true
964
+ log_action "Cleaning Docker artifacts..."
965
+ for docker_name in openclaw clawbot clawdbot moltbot; do
966
+ docker ps -a --filter "name=$docker_name" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true
967
+ docker images --filter "reference=*$docker_name*" --format "{{.ID}}" | xargs -r docker rmi -f 2>/dev/null || true
968
+ done
298
969
  log_action "Cleaned Docker artifacts"
299
970
  fi
300
971
 
972
+ # FINAL CLEANUP PASS - catch any respawned processes and recreated files
973
+ log_action "Running final cleanup pass..."
974
+ sleep 2
975
+
976
+ # Kill any respawned processes
977
+ RESPAWNED=0
978
+ for proc_name in openclaw clawbot clawdbot moltbot; do
979
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
980
+ pkill -9 -x "$proc_name" 2>/dev/null || true
981
+ killall -9 "$proc_name" 2>/dev/null || true
982
+ RESPAWNED=1
983
+ log_action " Killed respawned process: $proc_name"
984
+ fi
985
+ done
986
+
987
+ # Remove any recreated config directories (thorough removal)
988
+ for user_home in $(get_all_user_homes); do
989
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
990
+ config_path="$user_home/$config_name"
991
+ if [[ -d "$config_path" ]]; then
992
+ # On macOS: remove extended attributes and clear immutable flags
993
+ if [[ "$(uname)" == "Darwin" ]]; then
994
+ xattr -cr "$config_path" 2>/dev/null || true
995
+ chflags -R nouchg "$config_path" 2>/dev/null || true
996
+ fi
997
+ # On Linux: remove immutable attribute
998
+ if command -v chattr &>/dev/null; then
999
+ chattr -R -i "$config_path" 2>/dev/null || true
1000
+ fi
1001
+
1002
+ # Remove all contents first, then the directory
1003
+ find "$config_path" -type f -delete 2>/dev/null || true
1004
+ find "$config_path" -type d -empty -delete 2>/dev/null || true
1005
+ rm -rf "$config_path" 2>/dev/null || true
1006
+
1007
+ # If still exists, try moving to temp and deleting
1008
+ if [[ -d "$config_path" ]]; then
1009
+ temp_path="/tmp/.nox_purge_$$_$(basename "$config_path")"
1010
+ mv "$config_path" "$temp_path" 2>/dev/null && rm -rf "$temp_path" 2>/dev/null || true
1011
+ fi
1012
+
1013
+ if [[ -d "$config_path" ]]; then
1014
+ log_action " FAILED: Config directory recreated and cannot be removed: $config_path"
1015
+ else
1016
+ log_action " Removed recreated config directory: $config_path"
1017
+ fi
1018
+ fi
1019
+ done
1020
+ done
1021
+
1022
+ # Final verification
1023
+ FINAL_PROCS=""
1024
+ FINAL_CONFIGS=""
1025
+ for proc_name in openclaw clawbot clawdbot moltbot; do
1026
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
1027
+ FINAL_PROCS="$FINAL_PROCS $proc_name"
1028
+ fi
1029
+ done
1030
+ for user_home in $(get_all_user_homes); do
1031
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
1032
+ if [[ -d "$user_home/$config_name" ]]; then
1033
+ FINAL_CONFIGS="$FINAL_CONFIGS $user_home/$config_name"
1034
+ fi
1035
+ done
1036
+ done
1037
+
1038
+ if [[ -n "$FINAL_PROCS" || -n "$FINAL_CONFIGS" ]]; then
1039
+ log_action "WARNING: Some items could not be fully purged:"
1040
+ [[ -n "$FINAL_PROCS" ]] && log_action " Processes still running:$FINAL_PROCS"
1041
+ [[ -n "$FINAL_CONFIGS" ]] && log_action " Config directories still exist:$FINAL_CONFIGS"
1042
+ log_action "Manual intervention may be required"
1043
+ else
1044
+ log_action "Final cleanup pass completed - all items purged successfully"
1045
+ fi
1046
+
301
1047
  log_action "Purge complete"
302
1048
  ${webhookUrl ? '\nsend_webhook "purged" "$PURGE_RESULTS"' : ''}
303
1049
 
304
- echo "Purge complete. Results: $PURGE_RESULTS"
305
1050
  exit 0
306
1051
  `;
307
1052
  }
@@ -401,11 +1146,8 @@ if (Get-Command docker -ErrorAction SilentlyContinue) {
401
1146
  Log-Action "Cleaned Docker artifacts"
402
1147
  }
403
1148
 
404
- $ResultsString = $PurgeResults -join "; "
405
- ${webhookUrl ? '\nSend-Webhook -Status "purged" -Details $ResultsString' : ''}
406
-
407
- Write-Host ""
408
- Write-Host "Purge complete. Results: $ResultsString"
1149
+ ${webhookUrl ? '$ResultsString = $PurgeResults -join "; "\nSend-Webhook -Status "purged" -Details $ResultsString\n' : ''}
1150
+ Log-Action "Purge complete"
409
1151
  exit 0
410
1152
  `;
411
1153
  }