@geekbeer/minion 3.42.3 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/win/minion-cli.ps1 +123 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.42.3",
3
+ "version": "3.43.0",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -7,7 +7,7 @@
7
7
  # minion-cli-win setup # Install software & register services (admin required)
8
8
  # minion-cli-win configure --hq-url https://... --minion-id <UUID> --api-token <TOKEN>
9
9
  # minion-cli-win uninstall [--keep-data] # Remove agent (admin required)
10
- # minion-cli-win start | stop | restart | status | health | daemons | diagnose | version | help
10
+ # minion-cli-win start | stop [--force] | restart | status | health | daemons | diagnose | version | help
11
11
 
12
12
  # Parse arguments manually to avoid issues with npm wrapper passing $args as array
13
13
  $Command = 'help'
@@ -16,6 +16,7 @@ $MinionId = ''
16
16
  $ApiToken = ''
17
17
  $SetupTunnel = $false
18
18
  $KeepData = $false
19
+ $Force = $false
19
20
 
20
21
  $i = 0
21
22
  while ($i -lt $args.Count) {
@@ -28,6 +29,7 @@ while ($i -lt $args.Count) {
28
29
  '^--api-token$' { $i++; if ($i -lt $args.Count) { $ApiToken = [string]$args[$i] } }
29
30
  '^--setup-tunnel$' { $SetupTunnel = $true }
30
31
  '^--keep-data$' { $KeepData = $true }
32
+ '^--force$' { $Force = $true }
31
33
  '^(-h|--help)$' { $Command = 'help' }
32
34
  }
33
35
  $i++
@@ -40,10 +42,15 @@ $ErrorActionPreference = 'Stop'
40
42
  # Require Administrator for service management commands
41
43
  # ============================================================
42
44
  $adminRequired = @('setup', 'uninstall')
43
- if ($Command -in $adminRequired) {
45
+ $needsAdmin = $Command -in $adminRequired
46
+ # `stop --force` rewrites NSSM AppExit config and may need to kill processes
47
+ # owned by the service account → requires Administrator.
48
+ if ($Command -eq 'stop' -and $Force) { $needsAdmin = $true }
49
+ if ($needsAdmin) {
44
50
  $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
45
51
  if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
46
- Write-Host "ERROR: '$Command' requires Administrator privileges." -ForegroundColor Red
52
+ $label = if ($Command -eq 'stop' -and $Force) { 'stop --force' } else { $Command }
53
+ Write-Host "ERROR: '$label' requires Administrator privileges." -ForegroundColor Red
47
54
  Write-Host " Right-click PowerShell and select 'Run as administrator'." -ForegroundColor Yellow
48
55
  exit 1
49
56
  }
@@ -440,6 +447,108 @@ function Stop-MinionService {
440
447
  Write-Host "minion-agent service stopped"
441
448
  }
442
449
 
450
+ function Stop-MinionServiceForce {
451
+ # Force-stop path used when `stop` (graceful) is broken — typically after a
452
+ # corrupted update where .env is unreadable, the agent API is unresponsive,
453
+ # or NSSM keeps respawning node.exe and locking package files.
454
+ #
455
+ # Steps:
456
+ # 1. Disable NSSM auto-restart on every minion service (so sc.exe stop sticks)
457
+ # 2. sc.exe stop each service
458
+ # 3. Kill remaining helper processes (tvnserver/websockify/cloudflared)
459
+ # 4. Kill any node.exe whose CommandLine references a minion script
460
+ # 5. Restore NSSM AppExit Restart so subsequent `start` behaves normally
461
+ Write-Host ""
462
+ Write-Host "=========================================" -ForegroundColor Yellow
463
+ Write-Host " Stop --force: bypassing graceful shutdown" -ForegroundColor Yellow
464
+ Write-Host "=========================================" -ForegroundColor Yellow
465
+ Write-Host ""
466
+
467
+ $services = @('minion-agent', 'minion-websockify', 'minion-cloudflared', 'minion-vnc')
468
+ $nssmAvailable = $NssmPath -and (Test-Path $NssmPath)
469
+
470
+ # Step 1: Disable NSSM auto-restart so sc.exe stop is not undone.
471
+ if ($nssmAvailable) {
472
+ foreach ($svc in $services) {
473
+ $state = Get-ServiceState $svc
474
+ if (-not $state) { continue }
475
+ Invoke-Nssm set $svc AppExit Default Exit | Out-Null
476
+ Invoke-Nssm set $svc AppThrottle 0 | Out-Null
477
+ Write-Detail "Disabled auto-restart for $svc"
478
+ }
479
+ } else {
480
+ Write-Warn "NSSM not found — skipping AppExit override (services may auto-restart)"
481
+ }
482
+
483
+ # Step 2: Stop each service via sc.exe (works without admin if SDDL granted).
484
+ foreach ($svc in $services) {
485
+ $state = Get-ServiceState $svc
486
+ if (-not $state) { continue }
487
+ if ($state -eq 'STOPPED') {
488
+ Write-Detail "$svc already stopped"
489
+ continue
490
+ }
491
+ sc.exe stop $svc 2>&1 | Out-Null
492
+ for ($i = 0; $i -lt 10; $i++) {
493
+ $s = Get-ServiceState $svc
494
+ if ($s -eq 'STOPPED') { break }
495
+ Start-Sleep -Seconds 1
496
+ }
497
+ $final = Get-ServiceState $svc
498
+ if ($final -eq 'STOPPED') {
499
+ Write-Detail "$svc stopped"
500
+ } else {
501
+ Write-Warn "$svc state=$final (sc.exe stop did not complete in 10s)"
502
+ }
503
+ }
504
+
505
+ # Step 3: Kill helper processes that may have been spawned outside NSSM.
506
+ foreach ($name in @('tvnserver', 'websockify', 'cloudflared')) {
507
+ $procs = Get-Process -Name $name -ErrorAction SilentlyContinue
508
+ if ($procs) {
509
+ $procs | Stop-Process -Force -ErrorAction SilentlyContinue
510
+ Write-Detail "Killed $name ($($procs.Count) process(es))"
511
+ }
512
+ }
513
+
514
+ # Step 4: Kill node.exe processes that hold the package files. We match on
515
+ # CommandLine so we don't touch unrelated node processes the user is running.
516
+ $nodePattern = 'minion|@geekbeer\\minion|terminal-server|workflow-runner|routine-runner|wsl-session-server'
517
+ try {
518
+ $nodeProcs = Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction Stop |
519
+ Where-Object { $_.CommandLine -match $nodePattern }
520
+ foreach ($p in $nodeProcs) {
521
+ try {
522
+ Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop
523
+ Write-Detail "Killed node.exe pid=$($p.ProcessId)"
524
+ } catch {
525
+ Write-Warn "Failed to kill node.exe pid=$($p.ProcessId): $($_.Exception.Message)"
526
+ }
527
+ }
528
+ if (-not $nodeProcs) {
529
+ Write-Detail "No matching node.exe processes"
530
+ }
531
+ } catch {
532
+ Write-Warn "Could not enumerate node processes: $($_.Exception.Message)"
533
+ }
534
+
535
+ # Step 5: Restore AppExit Restart so a subsequent `start` behaves normally.
536
+ if ($nssmAvailable) {
537
+ foreach ($svc in $services) {
538
+ $state = Get-ServiceState $svc
539
+ if (-not $state) { continue }
540
+ Invoke-Nssm set $svc AppExit Default Restart | Out-Null
541
+ Invoke-Nssm set $svc AppThrottle 1500 | Out-Null
542
+ }
543
+ Write-Detail "Restored NSSM auto-restart settings"
544
+ }
545
+
546
+ Write-Host ""
547
+ Write-Host "Force-stop complete. If files are still locked, run:" -ForegroundColor Cyan
548
+ Write-Host " Get-Process | Where-Object { `$_.Modules.FileName -like '*\@geekbeer\minion\*' } | Select Id,ProcessName" -ForegroundColor Gray
549
+ Write-Host ""
550
+ }
551
+
443
552
  function Restart-MinionService {
444
553
  Stop-MinionService
445
554
  Start-Sleep -Seconds 2
@@ -1673,7 +1782,9 @@ switch ($Command) {
1673
1782
  'reconfigure' { Invoke-Configure } # alias for backwards compatibility
1674
1783
  'uninstall' { Invoke-Uninstall }
1675
1784
  'start' { Start-MinionService }
1676
- 'stop' { Stop-MinionService }
1785
+ 'stop' {
1786
+ if ($Force) { Stop-MinionServiceForce } else { Stop-MinionService }
1787
+ }
1677
1788
  'restart' { Restart-MinionService }
1678
1789
  'status' { Show-Status }
1679
1790
  'health' { Show-Health }
@@ -1692,7 +1803,8 @@ switch ($Command) {
1692
1803
  Write-Host "Commands (no admin required):"
1693
1804
  Write-Host " configure Connect to HQ, deploy skills, start services"
1694
1805
  Write-Host " start Start the minion-agent service"
1695
- Write-Host " stop Stop the minion-agent service"
1806
+ Write-Host " stop Stop the minion-agent service (graceful)"
1807
+ Write-Host " stop --force Force-stop all minion services & processes (admin required)"
1696
1808
  Write-Host " restart Restart the minion-agent service"
1697
1809
  Write-Host " status Show agent service status"
1698
1810
  Write-Host " health Check agent health endpoint"
@@ -1709,5 +1821,11 @@ switch ($Command) {
1709
1821
  Write-Host ""
1710
1822
  Write-Host "Uninstall options:"
1711
1823
  Write-Host " --keep-data Preserve .env file"
1824
+ Write-Host ""
1825
+ Write-Host "Stop options:"
1826
+ Write-Host " --force Disable NSSM auto-restart, sc.exe stop all services,"
1827
+ Write-Host " kill remaining helpers (tvnserver/websockify/cloudflared)"
1828
+ Write-Host " and node.exe processes that lock package files."
1829
+ Write-Host " Use when graceful stop fails (e.g. corrupted update)."
1712
1830
  }
1713
1831
  }