nox-openclaw-hunter 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) 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 +979 -104
  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/config.d.ts.map +1 -1
  19. package/dist/detector/config.js +88 -3
  20. package/dist/detector/config.js.map +1 -1
  21. package/dist/detector/detection-config.d.ts.map +1 -1
  22. package/dist/detector/detection-config.js +3 -0
  23. package/dist/detector/detection-config.js.map +1 -1
  24. package/dist/detector/docker.js +1 -1
  25. package/dist/detector/docker.js.map +1 -1
  26. package/dist/detector/process.d.ts.map +1 -1
  27. package/dist/detector/process.js +12 -1
  28. package/dist/detector/process.js.map +1 -1
  29. package/dist/enforcer/file-remover.d.ts +9 -0
  30. package/dist/enforcer/file-remover.d.ts.map +1 -1
  31. package/dist/enforcer/file-remover.js +76 -0
  32. package/dist/enforcer/file-remover.js.map +1 -1
  33. package/dist/enforcer/index.d.ts.map +1 -1
  34. package/dist/enforcer/index.js +19 -8
  35. package/dist/enforcer/index.js.map +1 -1
  36. package/dist/enforcer/service-stopper.d.ts +1 -0
  37. package/dist/enforcer/service-stopper.d.ts.map +1 -1
  38. package/dist/enforcer/service-stopper.js +7 -6
  39. package/dist/enforcer/service-stopper.js.map +1 -1
  40. package/dist/isolator/lockdown.d.ts.map +1 -1
  41. package/dist/isolator/lockdown.js +11 -0
  42. package/dist/isolator/lockdown.js.map +1 -1
  43. package/dist/mdm/templates/detect.ps1.d.ts.map +1 -1
  44. package/dist/mdm/templates/detect.ps1.js +69 -38
  45. package/dist/mdm/templates/detect.ps1.js.map +1 -1
  46. package/dist/mdm/templates/detect.sh.d.ts.map +1 -1
  47. package/dist/mdm/templates/detect.sh.js +5 -1
  48. package/dist/mdm/templates/detect.sh.js.map +1 -1
  49. package/dist/mdm/templates/enforce.ps1.d.ts.map +1 -1
  50. package/dist/mdm/templates/enforce.ps1.js +38 -25
  51. package/dist/mdm/templates/enforce.ps1.js.map +1 -1
  52. package/dist/mdm/templates/enforce.sh.d.ts.map +1 -1
  53. package/dist/mdm/templates/enforce.sh.js +5 -1
  54. package/dist/mdm/templates/enforce.sh.js.map +1 -1
  55. package/dist/platform/darwin.d.ts.map +1 -1
  56. package/dist/platform/darwin.js +132 -27
  57. package/dist/platform/darwin.js.map +1 -1
  58. package/dist/platform/linux.d.ts.map +1 -1
  59. package/dist/platform/linux.js +9 -1
  60. package/dist/platform/linux.js.map +1 -1
  61. package/dist/platform/windows.d.ts.map +1 -1
  62. package/dist/platform/windows.js +8 -5
  63. package/dist/platform/windows.js.map +1 -1
  64. 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
  }
@@ -140,26 +432,30 @@ $ErrorActionPreference = "SilentlyContinue"
140
432
  $OpenClawFound = $false
141
433
  $Details = @()
142
434
  ${webhookSection}
435
+ # All known variants: openclaw, moltbot, clawdbot, clawbot
436
+ $Variants = @("openclaw", "moltbot", "clawdbot", "clawbot")
437
+
143
438
  # Check common installation paths
144
- $CliPaths = @(
145
- "$env:LOCALAPPDATA\\Programs\\openclaw\\openclaw.exe",
146
- "$env:ProgramFiles\\OpenClaw\\openclaw.exe",
147
- "$env:ProgramFiles(x86)\\OpenClaw\\openclaw.exe"
148
- )
439
+ $CliPaths = @()
440
+ foreach ($variant in $Variants) {
441
+ $CliPaths += "$env:LOCALAPPDATA\\Programs\\$variant\\$variant.exe"
442
+ $CliPaths += "$env:ProgramFiles\\$variant\\$variant.exe"
443
+ $CliPaths += "$env:ProgramFiles(x86)\\$variant\\$variant.exe"
444
+ }
149
445
 
150
446
  foreach ($path in $CliPaths) {
151
447
  if (Test-Path $path) {
152
448
  $OpenClawFound = $true
153
449
  $Details += "CLI found at $path"
154
- break
155
450
  }
156
451
  }
157
452
 
158
- # Check config directory
159
- $ConfigPaths = @(
160
- "$env:USERPROFILE\\.openclaw",
161
- "$env:APPDATA\\OpenClaw"
162
- )
453
+ # Check config directories (all variants)
454
+ $ConfigPaths = @()
455
+ foreach ($variant in $Variants) {
456
+ $ConfigPaths += "$env:USERPROFILE\\.$variant"
457
+ $ConfigPaths += "$env:APPDATA\\$variant"
458
+ }
163
459
 
164
460
  foreach ($path in $ConfigPaths) {
165
461
  if (Test-Path $path) {
@@ -168,11 +464,13 @@ foreach ($path in $ConfigPaths) {
168
464
  }
169
465
  }
170
466
 
171
- # Check running processes
172
- $processes = Get-Process -Name "*openclaw*" -ErrorAction SilentlyContinue
173
- if ($processes) {
174
- $OpenClawFound = $true
175
- $Details += "Process running: $($processes.Name -join ', ')"
467
+ # Check running processes (all variants)
468
+ foreach ($variant in $Variants) {
469
+ $processes = Get-Process -Name "*$variant*" -ErrorAction SilentlyContinue
470
+ if ($processes) {
471
+ $OpenClawFound = $true
472
+ $Details += "Process running: $($processes.Name -join ', ')"
473
+ }
176
474
  }
177
475
 
178
476
  # Check gateway port
@@ -182,11 +480,13 @@ if ($portCheck.TcpTestSucceeded) {
182
480
  $Details += "Gateway port 18789 listening"
183
481
  }
184
482
 
185
- # Check Windows service
186
- $service = Get-Service -Name "*openclaw*" -ErrorAction SilentlyContinue
187
- if ($service) {
188
- $OpenClawFound = $true
189
- $Details += "Service found: $($service.Name) ($($service.Status))"
483
+ # Check Windows services (all variants)
484
+ foreach ($variant in $Variants) {
485
+ $service = Get-Service -Name "*$variant*" -ErrorAction SilentlyContinue
486
+ if ($service) {
487
+ $OpenClawFound = $true
488
+ $Details += "Service found: $($service.Name) ($($service.Status))"
489
+ }
190
490
  }
191
491
 
192
492
  $DetailsString = $Details -join "; "
@@ -230,8 +530,13 @@ send_webhook() {
230
530
  #
231
531
  # WARNING: This script will remove OpenClaw installations!
232
532
  # Run with sudo for full purge capabilities.
533
+ # Exit code is always 0 if the script runs to completion - check logs for details.
233
534
 
234
- set -euo pipefail
535
+ set -o pipefail
536
+
537
+ # Set defaults for environment variables that may be unset when running as root
538
+ HOME="\${HOME:-/root}"
539
+ USER="\${USER:-root}"
235
540
 
236
541
  PURGE_RESULTS=""
237
542
  ${webhookSection}
@@ -240,68 +545,576 @@ log_action() {
240
545
  PURGE_RESULTS="$PURGE_RESULTS; $1"
241
546
  }
242
547
 
548
+ # Get all user home directories
549
+ get_all_user_homes() {
550
+ local homes=()
551
+
552
+ # macOS users
553
+ if [[ -d "/Users" ]]; then
554
+ for user_dir in /Users/*; do
555
+ if [[ -d "$user_dir" && "$user_dir" != "/Users/Shared" && "$user_dir" != "/Users/Guest" ]]; then
556
+ homes+=("$user_dir")
557
+ fi
558
+ done
559
+ fi
560
+
561
+ # Linux users
562
+ if [[ -d "/home" ]]; then
563
+ for user_dir in /home/*; do
564
+ if [[ -d "$user_dir" ]]; then
565
+ homes+=("$user_dir")
566
+ fi
567
+ done
568
+ fi
569
+
570
+ # Root home
571
+ if [[ -d "/root" ]]; then
572
+ homes+=("/root")
573
+ fi
574
+ if [[ -d "/var/root" ]]; then
575
+ homes+=("/var/root")
576
+ fi
577
+
578
+ echo "\${homes[@]}"
579
+ }
580
+
581
+ log_action "=========================================="
582
+ log_action "Nox OpenClaw Hunter - Purge (All Users)"
583
+ log_action "=========================================="
584
+
243
585
  # Check if running as root
244
- if [[ $EUID -ne 0 ]]; then
586
+ if [[ \${EUID:-$(id -u)} -ne 0 ]]; then
245
587
  echo "Warning: Not running as root. Some actions may fail."
588
+ echo "For complete purge across all users, run with sudo."
246
589
  fi
247
590
 
248
- # Kill running processes
249
- log_action "Killing OpenClaw processes..."
250
- pkill -9 -f "openclaw" 2>/dev/null || log_action "No processes to kill"
591
+ # STEP 1: Unload launchd services FIRST to prevent process respawning (macOS)
592
+ if [[ "$(uname)" == "Darwin" ]]; then
593
+ log_action "Unloading launchd services to prevent respawning..."
594
+
595
+ # System-wide LaunchAgents and LaunchDaemons
596
+ for plist_dir in /Library/LaunchAgents /Library/LaunchDaemons; do
597
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
598
+ for plist in "$plist_dir"/$pattern; do
599
+ if [[ -f "$plist" ]]; then
600
+ launchctl bootout system "$plist" 2>/dev/null || launchctl unload "$plist" 2>/dev/null || true
601
+ fi
602
+ done
603
+ done
604
+ done
605
+
606
+ # Per-user LaunchAgents - unload for all users
607
+ for user_home in $(get_all_user_homes); do
608
+ plist_dir="$user_home/Library/LaunchAgents"
609
+ username=$(basename "$user_home")
610
+ user_uid=$(id -u "$username" 2>/dev/null || echo "")
611
+ if [[ -d "$plist_dir" ]]; then
612
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
613
+ for plist in "$plist_dir"/$pattern; do
614
+ if [[ -f "$plist" ]]; then
615
+ if [[ -n "$user_uid" ]]; then
616
+ launchctl bootout "gui/$user_uid" "$plist" 2>/dev/null || true
617
+ launchctl bootout "user/$user_uid" "$plist" 2>/dev/null || true
618
+ fi
619
+ launchctl unload "$plist" 2>/dev/null || true
620
+ fi
621
+ done
622
+ done
623
+ fi
624
+ done
625
+ log_action "Launchd services unloaded"
626
+ fi
627
+
628
+ # STEP 1b: Stop systemd services FIRST to prevent respawning (Linux)
629
+ if command -v systemctl &>/dev/null; then
630
+ log_action "Stopping systemd services to prevent respawning..."
631
+ for svc_name in openclaw clawbot clawdbot moltbot; do
632
+ systemctl stop "$svc_name.service" 2>/dev/null || true
633
+ systemctl disable "$svc_name.service" 2>/dev/null || true
634
+ done
635
+
636
+ # User-level services
637
+ for user_home in $(get_all_user_homes); do
638
+ username=$(basename "$user_home")
639
+ for svc_name in openclaw clawbot clawdbot moltbot; do
640
+ sudo -u "$username" systemctl --user stop "$svc_name.service" 2>/dev/null || true
641
+ sudo -u "$username" systemctl --user disable "$svc_name.service" 2>/dev/null || true
642
+ done
643
+ done
644
+ log_action "Systemd services stopped"
645
+ fi
646
+
647
+ # STEP 2: Kill running processes
648
+ log_action "Killing processes (openclaw, clawbot, clawdbot, moltbot)..."
649
+ PROCS_KILLED=0
650
+ for proc_name in openclaw clawbot clawdbot moltbot; do
651
+ # Find PIDs first (exclude our own script)
652
+ PIDS=$(pgrep -x "$proc_name" 2>/dev/null || true)
653
+ if [[ -z "$PIDS" ]]; then
654
+ # Try matching command line if exact name match fails
655
+ PIDS=$(pgrep -f "/$proc_name\$" 2>/dev/null || true)
656
+ fi
657
+
658
+ if [[ -n "$PIDS" ]]; then
659
+ for pid in $PIDS; do
660
+ # Skip if it's our own process
661
+ if [[ "$pid" == "$$" ]]; then
662
+ continue
663
+ fi
664
+ kill -9 "$pid" 2>/dev/null && {
665
+ log_action " Killed $proc_name (PID: $pid)"
666
+ PROCS_KILLED=$((PROCS_KILLED + 1))
667
+ } || true
668
+ done
669
+ fi
670
+
671
+ # Also try pkill as backup
672
+ pkill -9 -x "$proc_name" 2>/dev/null || true
673
+
674
+ # On macOS, also try killall
675
+ if [[ "$(uname)" == "Darwin" ]]; then
676
+ killall -9 "$proc_name" 2>/dev/null || true
677
+ fi
678
+ done
679
+
680
+ # Wait briefly for processes to terminate
681
+ sleep 1
682
+
683
+ # Verify processes are gone
684
+ REMAINING=""
685
+ for proc_name in openclaw clawbot clawdbot moltbot; do
686
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
687
+ REMAINING="$REMAINING $proc_name"
688
+ fi
689
+ done
251
690
 
252
- # Stop and disable launchd service (macOS)
691
+ if [[ -n "$REMAINING" ]]; then
692
+ log_action "WARNING: Some processes may still be running:$REMAINING"
693
+ # Try one more aggressive kill
694
+ for proc_name in $REMAINING; do
695
+ pkill -9 -f "$proc_name" 2>/dev/null || true
696
+ done
697
+ sleep 1
698
+
699
+ # Final check
700
+ STILL_RUNNING=""
701
+ for proc_name in openclaw clawbot clawdbot moltbot; do
702
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
703
+ STILL_RUNNING="$STILL_RUNNING $proc_name($(pgrep -x "$proc_name" | tr '\\n' ' '))"
704
+ fi
705
+ done
706
+
707
+ if [[ -n "$STILL_RUNNING" ]]; then
708
+ log_action "ERROR: Failed to kill processes:$STILL_RUNNING - may require manual intervention"
709
+ else
710
+ log_action "Process kill completed (after retry)"
711
+ fi
712
+ else
713
+ if [[ $PROCS_KILLED -gt 0 ]]; then
714
+ log_action "Process kill completed ($PROCS_KILLED processes terminated)"
715
+ else
716
+ log_action "Process kill completed (no matching processes found)"
717
+ fi
718
+ fi
719
+
720
+ # Stop and disable launchd services (macOS) - ALL USERS
253
721
  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"
722
+ log_action "Removing launchd services (all users)..."
723
+ LAUNCHD_REMOVED=0
724
+ LAUNCHD_FAILED=0
725
+
726
+ # System-wide LaunchAgents and LaunchDaemons
727
+ for plist_dir in /Library/LaunchAgents /Library/LaunchDaemons; do
728
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
729
+ for plist in "$plist_dir"/$pattern; do
730
+ if [[ -f "$plist" ]]; then
731
+ launchctl unload "$plist" 2>/dev/null || true
732
+ rm -f "$plist" 2>/dev/null
733
+ if [[ -f "$plist" ]]; then
734
+ log_action " FAILED to remove launchd plist: $plist"
735
+ LAUNCHD_FAILED=$((LAUNCHD_FAILED + 1))
736
+ else
737
+ log_action " Removed launchd plist: $plist"
738
+ LAUNCHD_REMOVED=$((LAUNCHD_REMOVED + 1))
739
+ fi
740
+ fi
741
+ done
742
+ done
743
+ done
744
+
745
+ # Per-user LaunchAgents
746
+ for user_home in $(get_all_user_homes); do
747
+ plist_dir="$user_home/Library/LaunchAgents"
748
+ if [[ -d "$plist_dir" ]]; then
749
+ for pattern in "bot.molt.*" "*openclaw*" "*clawbot*" "*clawdbot*" "*moltbot*"; do
750
+ for plist in "$plist_dir"/$pattern; do
751
+ if [[ -f "$plist" ]]; then
752
+ launchctl unload "$plist" 2>/dev/null || true
753
+ rm -f "$plist" 2>/dev/null
754
+ if [[ -f "$plist" ]]; then
755
+ log_action " FAILED to remove launchd plist: $plist"
756
+ LAUNCHD_FAILED=$((LAUNCHD_FAILED + 1))
757
+ else
758
+ log_action " Removed launchd plist: $plist"
759
+ LAUNCHD_REMOVED=$((LAUNCHD_REMOVED + 1))
760
+ fi
761
+ fi
762
+ done
763
+ done
259
764
  fi
260
765
  done
766
+
767
+ if [[ $LAUNCHD_FAILED -gt 0 ]]; then
768
+ log_action "Launchd removal: $LAUNCHD_REMOVED removed, $LAUNCHD_FAILED failed"
769
+ elif [[ $LAUNCHD_REMOVED -gt 0 ]]; then
770
+ log_action "Launchd removal completed ($LAUNCHD_REMOVED services removed)"
771
+ else
772
+ log_action "Launchd removal completed (no launchd services found)"
773
+ fi
261
774
  fi
262
775
 
263
- # Stop and disable systemd service (Linux)
776
+ # Stop and disable systemd services (Linux) - system and user level
264
777
  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
778
+ log_action "Removing systemd services (system and user level)..."
779
+ SYSTEMD_REMOVED=0
780
+ SYSTEMD_FAILED=0
781
+
782
+ # System-level services
783
+ for svc_name in openclaw clawbot clawdbot moltbot; do
784
+ systemctl stop "$svc_name.service" 2>/dev/null || true
785
+ systemctl disable "$svc_name.service" 2>/dev/null || true
786
+
787
+ for svc_path in "/etc/systemd/system/$svc_name.service" "/usr/lib/systemd/system/$svc_name.service"; do
788
+ if [[ -f "$svc_path" ]]; then
789
+ rm -f "$svc_path" 2>/dev/null
790
+ if [[ -f "$svc_path" ]]; then
791
+ log_action " FAILED to remove systemd service: $svc_path"
792
+ SYSTEMD_FAILED=$((SYSTEMD_FAILED + 1))
793
+ else
794
+ log_action " Removed systemd service: $svc_path"
795
+ SYSTEMD_REMOVED=$((SYSTEMD_REMOVED + 1))
796
+ fi
797
+ fi
798
+ done
799
+ done
800
+
801
+ # User-level systemd services
802
+ for user_home in $(get_all_user_homes); do
803
+ user_systemd="$user_home/.config/systemd/user"
804
+ if [[ -d "$user_systemd" ]]; then
805
+ for svc_name in openclaw clawbot clawdbot moltbot; do
806
+ svc_path="$user_systemd/$svc_name.service"
807
+ if [[ -f "$svc_path" ]]; then
808
+ rm -f "$svc_path" 2>/dev/null
809
+ if [[ -f "$svc_path" ]]; then
810
+ log_action " FAILED to remove user systemd service: $svc_path"
811
+ SYSTEMD_FAILED=$((SYSTEMD_FAILED + 1))
812
+ else
813
+ log_action " Removed user systemd service: $svc_path"
814
+ SYSTEMD_REMOVED=$((SYSTEMD_REMOVED + 1))
815
+ fi
816
+ fi
817
+ done
818
+ fi
819
+ done
820
+
268
821
  systemctl daemon-reload 2>/dev/null || true
269
- log_action "Stopped and disabled systemd service"
822
+
823
+ if [[ $SYSTEMD_FAILED -gt 0 ]]; then
824
+ log_action "Systemd removal: $SYSTEMD_REMOVED removed, $SYSTEMD_FAILED failed"
825
+ elif [[ $SYSTEMD_REMOVED -gt 0 ]]; then
826
+ log_action "Systemd removal completed ($SYSTEMD_REMOVED services removed)"
827
+ else
828
+ log_action "Systemd removal completed (no systemd services found)"
829
+ fi
270
830
  fi
271
831
 
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
832
+ # Remove CLI binaries (system-wide and per-user)
833
+ log_action "Removing CLI binaries (all locations)..."
834
+ CLI_REMOVED=0
835
+ CLI_FAILED=0
836
+
837
+ # System-wide locations
838
+ for cli_name in openclaw clawbot clawdbot moltbot; do
839
+ for bin_dir in /usr/local/bin /opt/homebrew/bin /usr/bin; do
840
+ cli_path="$bin_dir/$cli_name"
841
+ if [[ -f "$cli_path" ]]; then
842
+ rm -f "$cli_path" 2>/dev/null
843
+ if [[ -f "$cli_path" ]]; then
844
+ log_action " FAILED to remove CLI: $cli_path (permission denied)"
845
+ CLI_FAILED=$((CLI_FAILED + 1))
846
+ else
847
+ log_action " Removed CLI: $cli_path"
848
+ CLI_REMOVED=$((CLI_REMOVED + 1))
849
+ fi
850
+ fi
851
+ done
278
852
  done
279
853
 
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"
854
+ # Per-user locations
855
+ for user_home in $(get_all_user_homes); do
856
+ for cli_name in openclaw clawbot clawdbot moltbot; do
857
+ for bin_dir in "$user_home/bin" "$user_home/.local/bin"; do
858
+ cli_path="$bin_dir/$cli_name"
859
+ if [[ -f "$cli_path" ]]; then
860
+ rm -f "$cli_path" 2>/dev/null
861
+ if [[ -f "$cli_path" ]]; then
862
+ log_action " FAILED to remove CLI: $cli_path (permission denied)"
863
+ CLI_FAILED=$((CLI_FAILED + 1))
864
+ else
865
+ log_action " Removed CLI: $cli_path"
866
+ CLI_REMOVED=$((CLI_REMOVED + 1))
867
+ fi
868
+ fi
869
+ done
870
+ done
871
+ done
872
+
873
+ if [[ $CLI_FAILED -gt 0 ]]; then
874
+ log_action "CLI removal: $CLI_REMOVED removed, $CLI_FAILED failed"
875
+ elif [[ $CLI_REMOVED -gt 0 ]]; then
876
+ log_action "CLI removal completed ($CLI_REMOVED binaries removed)"
877
+ else
878
+ log_action "CLI removal completed (no CLI binaries found)"
284
879
  fi
285
880
 
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"
881
+ # Remove macOS app bundles (system-wide and per-user)
882
+ log_action "Removing app bundles (all locations)..."
883
+ APP_REMOVED=0
884
+ APP_FAILED=0
885
+
886
+ for app_name in OpenClaw.app ClawBot.app ClawdBot.app MoltBot.app; do
887
+ # System-wide Applications
888
+ if [[ -d "/Applications/$app_name" ]]; then
889
+ rm -rf "/Applications/$app_name" 2>/dev/null
890
+ if [[ -d "/Applications/$app_name" ]]; then
891
+ log_action " FAILED to remove app: /Applications/$app_name (permission denied or protected)"
892
+ APP_FAILED=$((APP_FAILED + 1))
893
+ else
894
+ log_action " Removed app bundle: /Applications/$app_name"
895
+ APP_REMOVED=$((APP_REMOVED + 1))
896
+ fi
291
897
  fi
898
+
899
+ # Per-user Applications
900
+ for user_home in $(get_all_user_homes); do
901
+ if [[ -d "$user_home/Applications/$app_name" ]]; then
902
+ rm -rf "$user_home/Applications/$app_name" 2>/dev/null
903
+ if [[ -d "$user_home/Applications/$app_name" ]]; then
904
+ log_action " FAILED to remove app: $user_home/Applications/$app_name (permission denied)"
905
+ APP_FAILED=$((APP_FAILED + 1))
906
+ else
907
+ log_action " Removed app bundle: $user_home/Applications/$app_name"
908
+ APP_REMOVED=$((APP_REMOVED + 1))
909
+ fi
910
+ fi
911
+ done
292
912
  done
293
913
 
914
+ if [[ $APP_FAILED -gt 0 ]]; then
915
+ log_action "App bundle removal: $APP_REMOVED removed, $APP_FAILED failed"
916
+ elif [[ $APP_REMOVED -gt 0 ]]; then
917
+ log_action "App bundle removal completed ($APP_REMOVED bundles removed)"
918
+ else
919
+ log_action "App bundle removal completed (no app bundles found)"
920
+ fi
921
+
922
+ # Remove config directories for ALL USERS
923
+ log_action "Removing config directories (all users)..."
924
+ CONFIG_REMOVED=0
925
+ CONFIG_FAILED=0
926
+ for user_home in $(get_all_user_homes); do
927
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
928
+ config_path="$user_home/$config_name"
929
+ if [[ -d "$config_path" ]]; then
930
+ # On macOS: remove extended attributes and clear immutable flags
931
+ if [[ "$(uname)" == "Darwin" ]]; then
932
+ xattr -cr "$config_path" 2>/dev/null || true
933
+ chflags -R nouchg "$config_path" 2>/dev/null || true
934
+ fi
935
+ # On Linux: remove immutable attribute
936
+ if command -v chattr &>/dev/null; then
937
+ chattr -R -i "$config_path" 2>/dev/null || true
938
+ fi
939
+
940
+ # Remove all contents first, then the directory
941
+ find "$config_path" -type f -delete 2>/dev/null || true
942
+ find "$config_path" -type d -empty -delete 2>/dev/null || true
943
+ rm -rf "$config_path" 2>/dev/null || true
944
+
945
+ # If still exists, try moving to temp and deleting
946
+ if [[ -d "$config_path" ]]; then
947
+ temp_path="/tmp/.nox_purge_$$_$(basename "$config_path")"
948
+ mv "$config_path" "$temp_path" 2>/dev/null && rm -rf "$temp_path" 2>/dev/null || true
949
+ fi
950
+
951
+ # Verify removal
952
+ if [[ -d "$config_path" ]]; then
953
+ log_action " FAILED to remove config: $config_path (permission denied or protected)"
954
+ CONFIG_FAILED=$((CONFIG_FAILED + 1))
955
+ else
956
+ log_action " Removed config: $config_path"
957
+ CONFIG_REMOVED=$((CONFIG_REMOVED + 1))
958
+ fi
959
+ fi
960
+ done
961
+ done
962
+ if [[ $CONFIG_FAILED -gt 0 ]]; then
963
+ log_action "Config removal: $CONFIG_REMOVED removed, $CONFIG_FAILED failed"
964
+ elif [[ $CONFIG_REMOVED -gt 0 ]]; then
965
+ log_action "Config removal completed ($CONFIG_REMOVED directories removed)"
966
+ else
967
+ log_action "Config removal completed (no config directories found)"
968
+ fi
969
+
294
970
  # Remove Docker containers and images
295
971
  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
972
+ log_action "Cleaning Docker artifacts..."
973
+ for docker_name in openclaw clawbot clawdbot moltbot; do
974
+ docker ps -a --filter "name=$docker_name" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true
975
+ docker images --filter "reference=*$docker_name*" --format "{{.ID}}" | xargs -r docker rmi -f 2>/dev/null || true
976
+ done
298
977
  log_action "Cleaned Docker artifacts"
299
978
  fi
300
979
 
980
+ # Remove npm global packages (all variants)
981
+ log_action "Removing npm global packages..."
982
+ NPM_REMOVED=0
983
+ NPM_FAILED=0
984
+
985
+ # Get npm global directory
986
+ if command -v npm &>/dev/null; then
987
+ NPM_ROOT=$(npm root -g 2>/dev/null || echo "")
988
+ if [[ -n "$NPM_ROOT" && -d "$NPM_ROOT" ]]; then
989
+ for pkg_name in openclaw clawbot clawdbot moltbot; do
990
+ pkg_path="$NPM_ROOT/$pkg_name"
991
+ if [[ -d "$pkg_path" ]]; then
992
+ rm -rf "$pkg_path" 2>/dev/null
993
+ if [[ -d "$pkg_path" ]]; then
994
+ log_action " FAILED to remove npm package: $pkg_path"
995
+ NPM_FAILED=$((NPM_FAILED + 1))
996
+ else
997
+ log_action " Removed npm package: $pkg_path"
998
+ NPM_REMOVED=$((NPM_REMOVED + 1))
999
+ fi
1000
+ fi
1001
+ done
1002
+ fi
1003
+ fi
1004
+
1005
+ # Also check nvm directories for all users
1006
+ for user_home in $(get_all_user_homes); do
1007
+ nvm_dir="$user_home/.nvm/versions/node"
1008
+ if [[ -d "$nvm_dir" ]]; then
1009
+ for node_version in "$nvm_dir"/*; do
1010
+ if [[ -d "$node_version/lib/node_modules" ]]; then
1011
+ for pkg_name in openclaw clawbot clawdbot moltbot; do
1012
+ pkg_path="$node_version/lib/node_modules/$pkg_name"
1013
+ if [[ -d "$pkg_path" ]]; then
1014
+ rm -rf "$pkg_path" 2>/dev/null
1015
+ # Also remove the symlinked binary
1016
+ bin_path="$node_version/bin/$pkg_name"
1017
+ rm -f "$bin_path" 2>/dev/null
1018
+ if [[ -d "$pkg_path" ]]; then
1019
+ log_action " FAILED to remove npm package: $pkg_path"
1020
+ NPM_FAILED=$((NPM_FAILED + 1))
1021
+ else
1022
+ log_action " Removed npm package: $pkg_path"
1023
+ NPM_REMOVED=$((NPM_REMOVED + 1))
1024
+ fi
1025
+ fi
1026
+ done
1027
+ fi
1028
+ done
1029
+ fi
1030
+ done
1031
+
1032
+ if [[ $NPM_FAILED -gt 0 ]]; then
1033
+ log_action "npm removal: $NPM_REMOVED removed, $NPM_FAILED failed"
1034
+ elif [[ $NPM_REMOVED -gt 0 ]]; then
1035
+ log_action "npm removal completed ($NPM_REMOVED packages removed)"
1036
+ else
1037
+ log_action "npm removal completed (no npm packages found)"
1038
+ fi
1039
+
1040
+ # FINAL CLEANUP PASS - catch any respawned processes and recreated files
1041
+ log_action "Running final cleanup pass..."
1042
+ sleep 2
1043
+
1044
+ # Kill any respawned processes
1045
+ RESPAWNED=0
1046
+ for proc_name in openclaw clawbot clawdbot moltbot; do
1047
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
1048
+ pkill -9 -x "$proc_name" 2>/dev/null || true
1049
+ killall -9 "$proc_name" 2>/dev/null || true
1050
+ RESPAWNED=1
1051
+ log_action " Killed respawned process: $proc_name"
1052
+ fi
1053
+ done
1054
+
1055
+ # Remove any recreated config directories (thorough removal)
1056
+ for user_home in $(get_all_user_homes); do
1057
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
1058
+ config_path="$user_home/$config_name"
1059
+ if [[ -d "$config_path" ]]; then
1060
+ # On macOS: remove extended attributes and clear immutable flags
1061
+ if [[ "$(uname)" == "Darwin" ]]; then
1062
+ xattr -cr "$config_path" 2>/dev/null || true
1063
+ chflags -R nouchg "$config_path" 2>/dev/null || true
1064
+ fi
1065
+ # On Linux: remove immutable attribute
1066
+ if command -v chattr &>/dev/null; then
1067
+ chattr -R -i "$config_path" 2>/dev/null || true
1068
+ fi
1069
+
1070
+ # Remove all contents first, then the directory
1071
+ find "$config_path" -type f -delete 2>/dev/null || true
1072
+ find "$config_path" -type d -empty -delete 2>/dev/null || true
1073
+ rm -rf "$config_path" 2>/dev/null || true
1074
+
1075
+ # If still exists, try moving to temp and deleting
1076
+ if [[ -d "$config_path" ]]; then
1077
+ temp_path="/tmp/.nox_purge_$$_$(basename "$config_path")"
1078
+ mv "$config_path" "$temp_path" 2>/dev/null && rm -rf "$temp_path" 2>/dev/null || true
1079
+ fi
1080
+
1081
+ if [[ -d "$config_path" ]]; then
1082
+ log_action " FAILED: Config directory recreated and cannot be removed: $config_path"
1083
+ else
1084
+ log_action " Removed recreated config directory: $config_path"
1085
+ fi
1086
+ fi
1087
+ done
1088
+ done
1089
+
1090
+ # Final verification
1091
+ FINAL_PROCS=""
1092
+ FINAL_CONFIGS=""
1093
+ for proc_name in openclaw clawbot clawdbot moltbot; do
1094
+ if pgrep -x "$proc_name" >/dev/null 2>&1; then
1095
+ FINAL_PROCS="$FINAL_PROCS $proc_name"
1096
+ fi
1097
+ done
1098
+ for user_home in $(get_all_user_homes); do
1099
+ for config_name in .openclaw .clawbot .clawdbot .moltbot; do
1100
+ if [[ -d "$user_home/$config_name" ]]; then
1101
+ FINAL_CONFIGS="$FINAL_CONFIGS $user_home/$config_name"
1102
+ fi
1103
+ done
1104
+ done
1105
+
1106
+ if [[ -n "$FINAL_PROCS" || -n "$FINAL_CONFIGS" ]]; then
1107
+ log_action "WARNING: Some items could not be fully purged:"
1108
+ [[ -n "$FINAL_PROCS" ]] && log_action " Processes still running:$FINAL_PROCS"
1109
+ [[ -n "$FINAL_CONFIGS" ]] && log_action " Config directories still exist:$FINAL_CONFIGS"
1110
+ log_action "Manual intervention may be required"
1111
+ else
1112
+ log_action "Final cleanup pass completed - all items purged successfully"
1113
+ fi
1114
+
301
1115
  log_action "Purge complete"
302
1116
  ${webhookUrl ? '\nsend_webhook "purged" "$PURGE_RESULTS"' : ''}
303
1117
 
304
- echo "Purge complete. Results: $PURGE_RESULTS"
305
1118
  exit 0
306
1119
  `;
307
1120
  }
@@ -344,31 +1157,39 @@ function Send-Webhook {
344
1157
  $ErrorActionPreference = "SilentlyContinue"
345
1158
  $PurgeResults = @()
346
1159
  ${webhookSection}
1160
+ # All known variants: openclaw, moltbot, clawdbot, clawbot
1161
+ $Variants = @("openclaw", "moltbot", "clawdbot", "clawbot")
1162
+
347
1163
  function Log-Action {
348
1164
  param([string]$Message)
349
1165
  Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message"
350
1166
  $script:PurgeResults += $Message
351
1167
  }
352
1168
 
353
- # Kill running processes
354
- Log-Action "Killing OpenClaw processes..."
355
- Get-Process -Name "*openclaw*" | Stop-Process -Force -ErrorAction SilentlyContinue
1169
+ # Kill running processes (all variants)
1170
+ Log-Action "Killing processes (all variants)..."
1171
+ foreach ($variant in $Variants) {
1172
+ Get-Process -Name "*$variant*" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
1173
+ }
356
1174
  Log-Action "Processes terminated"
357
1175
 
358
- # Stop and remove Windows service
359
- $service = Get-Service -Name "*openclaw*" -ErrorAction SilentlyContinue
360
- if ($service) {
361
- Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue
362
- sc.exe delete $service.Name 2>$null
363
- Log-Action "Stopped and removed service: $($service.Name)"
1176
+ # Stop and remove Windows services (all variants)
1177
+ foreach ($variant in $Variants) {
1178
+ $services = Get-Service -Name "*$variant*" -ErrorAction SilentlyContinue
1179
+ foreach ($service in $services) {
1180
+ Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue
1181
+ sc.exe delete $service.Name 2>$null
1182
+ Log-Action "Stopped and removed service: $($service.Name)"
1183
+ }
364
1184
  }
365
1185
 
366
- # Remove CLI binaries
367
- $CliPaths = @(
368
- "$env:LOCALAPPDATA\\Programs\\openclaw",
369
- "$env:ProgramFiles\\OpenClaw",
370
- "$env:ProgramFiles(x86)\\OpenClaw"
371
- )
1186
+ # Remove CLI binaries (all variants)
1187
+ $CliPaths = @()
1188
+ foreach ($variant in $Variants) {
1189
+ $CliPaths += "$env:LOCALAPPDATA\\Programs\\$variant"
1190
+ $CliPaths += "$env:ProgramFiles\\$variant"
1191
+ $CliPaths += "$env:ProgramFiles(x86)\\$variant"
1192
+ }
372
1193
 
373
1194
  foreach ($path in $CliPaths) {
374
1195
  if (Test-Path $path) {
@@ -377,11 +1198,12 @@ foreach ($path in $CliPaths) {
377
1198
  }
378
1199
  }
379
1200
 
380
- # Remove config directories
381
- $ConfigPaths = @(
382
- "$env:USERPROFILE\\.openclaw",
383
- "$env:APPDATA\\OpenClaw"
384
- )
1201
+ # Remove config directories (all variants)
1202
+ $ConfigPaths = @()
1203
+ foreach ($variant in $Variants) {
1204
+ $ConfigPaths += "$env:USERPROFILE\\.$variant"
1205
+ $ConfigPaths += "$env:APPDATA\\$variant"
1206
+ }
385
1207
 
386
1208
  foreach ($path in $ConfigPaths) {
387
1209
  if (Test-Path $path) {
@@ -390,22 +1212,75 @@ foreach ($path in $ConfigPaths) {
390
1212
  }
391
1213
  }
392
1214
 
393
- # Remove scheduled tasks
394
- Get-ScheduledTask -TaskName "*openclaw*" | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
1215
+ # Remove npm global packages (all variants)
1216
+ Log-Action "Removing npm global packages..."
1217
+ if (Get-Command npm -ErrorAction SilentlyContinue) {
1218
+ $npmRoot = (npm root -g 2>$null)
1219
+ if ($npmRoot -and (Test-Path $npmRoot)) {
1220
+ foreach ($variant in $Variants) {
1221
+ $pkgPath = Join-Path $npmRoot $variant
1222
+ if (Test-Path $pkgPath) {
1223
+ Remove-Item -Path $pkgPath -Recurse -Force -ErrorAction SilentlyContinue
1224
+ Log-Action "Removed npm package: $pkgPath"
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ # Also check nvm directories for all users (Windows nvm-windows)
1231
+ $userProfiles = Get-ChildItem "C:\\Users" -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notin @("Public", "Default", "Default User", "All Users") }
1232
+ foreach ($profile in $userProfiles) {
1233
+ $nvmDir = Join-Path $profile.FullName ".nvm"
1234
+ if (Test-Path $nvmDir) {
1235
+ Get-ChildItem $nvmDir -Directory -ErrorAction SilentlyContinue | ForEach-Object {
1236
+ $nodeModules = Join-Path $_.FullName "node_modules"
1237
+ if (Test-Path $nodeModules) {
1238
+ foreach ($variant in $Variants) {
1239
+ $pkgPath = Join-Path $nodeModules $variant
1240
+ if (Test-Path $pkgPath) {
1241
+ Remove-Item -Path $pkgPath -Recurse -Force -ErrorAction SilentlyContinue
1242
+ Log-Action "Removed npm package: $pkgPath"
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+ # Also check AppData for nvm-windows
1249
+ $nvmWinDir = Join-Path $profile.FullName "AppData\\Roaming\\nvm"
1250
+ if (Test-Path $nvmWinDir) {
1251
+ Get-ChildItem $nvmWinDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "^v\\d" } | ForEach-Object {
1252
+ $nodeModules = Join-Path $_.FullName "node_modules"
1253
+ if (Test-Path $nodeModules) {
1254
+ foreach ($variant in $Variants) {
1255
+ $pkgPath = Join-Path $nodeModules $variant
1256
+ if (Test-Path $pkgPath) {
1257
+ Remove-Item -Path $pkgPath -Recurse -Force -ErrorAction SilentlyContinue
1258
+ Log-Action "Removed npm package: $pkgPath"
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+ Log-Action "npm package removal completed"
1266
+
1267
+ # Remove scheduled tasks (all variants)
1268
+ foreach ($variant in $Variants) {
1269
+ Get-ScheduledTask -TaskName "*$variant*" -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
1270
+ }
395
1271
  Log-Action "Removed scheduled tasks"
396
1272
 
397
- # Clean Docker (if installed)
1273
+ # Clean Docker (if installed) - all variants
398
1274
  if (Get-Command docker -ErrorAction SilentlyContinue) {
399
- docker ps -a --filter "name=openclaw" --format "{{.ID}}" | ForEach-Object { docker rm -f $_ 2>$null }
400
- docker images --filter "reference=*openclaw*" --format "{{.ID}}" | ForEach-Object { docker rmi -f $_ 2>$null }
1275
+ foreach ($variant in $Variants) {
1276
+ docker ps -a --filter "name=$variant" --format "{{.ID}}" 2>$null | ForEach-Object { docker rm -f $_ 2>$null }
1277
+ docker images --filter "reference=*$variant*" --format "{{.ID}}" 2>$null | ForEach-Object { docker rmi -f $_ 2>$null }
1278
+ }
401
1279
  Log-Action "Cleaned Docker artifacts"
402
1280
  }
403
1281
 
404
- $ResultsString = $PurgeResults -join "; "
405
- ${webhookUrl ? '\nSend-Webhook -Status "purged" -Details $ResultsString' : ''}
406
-
407
- Write-Host ""
408
- Write-Host "Purge complete. Results: $ResultsString"
1282
+ ${webhookUrl ? '$ResultsString = $PurgeResults -join "; "\nSend-Webhook -Status "purged" -Details $ResultsString\n' : ''}
1283
+ Log-Action "Purge complete"
409
1284
  exit 0
410
1285
  `;
411
1286
  }