@geekbeer/minion 3.42.2 → 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.
- package/package.json +1 -1
- package/win/lib/process-manager.js +157 -23
- package/win/minion-cli.ps1 +123 -5
package/package.json
CHANGED
|
@@ -80,16 +80,29 @@ function buildGracefulStopBlock(agentPort, apiToken) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Generate a temporary PowerShell script for updating the agent
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
83
|
+
* Generate a temporary PowerShell script for updating the agent.
|
|
84
|
+
*
|
|
85
|
+
* Flow:
|
|
86
|
+
* 1. End MinionWSL scheduled task (holds Fastify/ajv file handles via
|
|
87
|
+
* wsl-session-server.js in user session — previously caused EBUSY /
|
|
88
|
+
* partial installs that left node_modules/ajv/dist/refs/data.json
|
|
89
|
+
* missing and bricked the next startup).
|
|
90
|
+
* 2. Graceful shutdown via HTTP API (offline heartbeat).
|
|
91
|
+
* 3. Stop minion-agent via NSSM and WAIT until service is actually
|
|
92
|
+
* Stopped (poll sc.exe, not a fixed Start-Sleep).
|
|
93
|
+
* 4. Kill stray node.exe processes that still reference the minion
|
|
94
|
+
* package dir (workflow/routine runners, claude-code children).
|
|
95
|
+
* 5. Run npm install -g, then verify integrity by actually requiring
|
|
96
|
+
* fastify + the package.json from the installed location. If the
|
|
97
|
+
* install silently produced a broken tree, we detect it here
|
|
98
|
+
* (instead of bouncing an unstartable service).
|
|
99
|
+
* 6. On persistent integrity failure, wipe the package dir to force a
|
|
100
|
+
* clean reinstall on the next attempt.
|
|
101
|
+
* 7. Start minion-agent. Re-run the MinionWSL task if it existed.
|
|
102
|
+
* 8. Remove the temporary updater service (self-cleanup in finally).
|
|
89
103
|
*
|
|
90
|
-
*
|
|
91
|
-
* so it runs independently of the minion-agent service
|
|
92
|
-
* the agent's stop/restart cycle.
|
|
104
|
+
* The script is registered as a temporary NSSM service ("minion-update")
|
|
105
|
+
* so it runs independently of the minion-agent service.
|
|
93
106
|
*
|
|
94
107
|
* @param {string} npmInstallCmd - The npm install command to run
|
|
95
108
|
* @param {string} nssmPath - Absolute path to nssm.exe
|
|
@@ -111,6 +124,12 @@ function buildUpdateScript(npmInstallCmd, nssmPath, scriptName = 'update-agent',
|
|
|
111
124
|
? `${npmInstallCmd} --prefix "${npmPrefix}"`
|
|
112
125
|
: npmInstallCmd
|
|
113
126
|
|
|
127
|
+
// Absolute path to the installed package dir (used for integrity check
|
|
128
|
+
// and, if integrity keeps failing, for a forced clean reinstall).
|
|
129
|
+
const pkgDir = npmPrefix
|
|
130
|
+
? path.join(npmPrefix, 'node_modules', '@geekbeer', 'minion')
|
|
131
|
+
: ''
|
|
132
|
+
|
|
114
133
|
const gracefulStop = buildGracefulStopBlock(agentPort, apiToken)
|
|
115
134
|
|
|
116
135
|
const ps1 = [
|
|
@@ -121,38 +140,153 @@ function buildUpdateScript(npmInstallCmd, nssmPath, scriptName = 'update-agent',
|
|
|
121
140
|
`# Use nssm.exe from data dir (copied there to avoid EBUSY on package files)`,
|
|
122
141
|
`$nssm = '${path.join(dataDir, 'nssm.exe')}'`,
|
|
123
142
|
`$logFile = '${logPath}'`,
|
|
143
|
+
`$pkgDir = '${pkgDir}'`,
|
|
124
144
|
`function Log($msg) { "$(Get-Date -Format o) $msg" | Out-File -Append $logFile }`,
|
|
145
|
+
``,
|
|
146
|
+
`# Poll until the service reaches Stopped (or timeout).`,
|
|
147
|
+
`function Wait-ServiceStopped($name, $timeoutSec) {`,
|
|
148
|
+
` $deadline = (Get-Date).AddSeconds($timeoutSec)`,
|
|
149
|
+
` while ((Get-Date) -lt $deadline) {`,
|
|
150
|
+
` $svc = Get-Service -Name $name -ErrorAction SilentlyContinue`,
|
|
151
|
+
` if (-not $svc -or $svc.Status -eq 'Stopped') { return $true }`,
|
|
152
|
+
` Start-Sleep -Milliseconds 500`,
|
|
153
|
+
` }`,
|
|
154
|
+
` return $false`,
|
|
155
|
+
`}`,
|
|
156
|
+
``,
|
|
157
|
+
`# Kill any node.exe whose command line references the minion package`,
|
|
158
|
+
`# (stale workflow/routine runners, wsl-session-entry children, etc.).`,
|
|
159
|
+
`# These hold file handles on node_modules and break npm install on Windows.`,
|
|
160
|
+
`function Kill-PackageProcesses {`,
|
|
161
|
+
` try {`,
|
|
162
|
+
` Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |`,
|
|
163
|
+
` Where-Object { $_.CommandLine -and ($_.CommandLine -match '@geekbeer.minion' -or $_.CommandLine -match 'wsl-session-entry') } |`,
|
|
164
|
+
` ForEach-Object {`,
|
|
165
|
+
` Log "Killing stray node.exe PID $($_.ProcessId): $($_.CommandLine)"`,
|
|
166
|
+
` Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue`,
|
|
167
|
+
` }`,
|
|
168
|
+
` } catch { Log "Kill-PackageProcesses error: $_" }`,
|
|
169
|
+
`}`,
|
|
170
|
+
``,
|
|
171
|
+
`# Load fastify + @geekbeer/minion/package.json from the installed tree.`,
|
|
172
|
+
`# Catches the "npm reports success but files are missing" failure mode`,
|
|
173
|
+
`# (the ajv/data.json crash we have been chasing).`,
|
|
174
|
+
`function Test-PackageIntegrity {`,
|
|
175
|
+
` if (-not $pkgDir -or -not (Test-Path $pkgDir)) {`,
|
|
176
|
+
` Log "Integrity check: pkgDir missing ($pkgDir)"`,
|
|
177
|
+
` return $false`,
|
|
178
|
+
` }`,
|
|
179
|
+
` try {`,
|
|
180
|
+
` Push-Location $pkgDir`,
|
|
181
|
+
` $out = & node -e "require('fastify');require('./package.json');console.log('OK')" 2>&1`,
|
|
182
|
+
` $code = $LASTEXITCODE`,
|
|
183
|
+
` Pop-Location`,
|
|
184
|
+
` if ($code -eq 0 -and "$out" -match 'OK') {`,
|
|
185
|
+
` Log "Integrity check passed"`,
|
|
186
|
+
` return $true`,
|
|
187
|
+
` }`,
|
|
188
|
+
` Log "Integrity check failed (exit $code): $out"`,
|
|
189
|
+
` return $false`,
|
|
190
|
+
` } catch {`,
|
|
191
|
+
` try { Pop-Location } catch {}`,
|
|
192
|
+
` Log "Integrity check error: $_"`,
|
|
193
|
+
` return $false`,
|
|
194
|
+
` }`,
|
|
195
|
+
`}`,
|
|
196
|
+
``,
|
|
125
197
|
`Log 'Update started'`,
|
|
198
|
+
`$wslTaskExists = $false`,
|
|
126
199
|
`try {`,
|
|
200
|
+
` # 1. Stop MinionWSL scheduled task so wsl-session-server.js releases`,
|
|
201
|
+
` # Fastify/ajv file handles before npm install.`,
|
|
202
|
+
` try {`,
|
|
203
|
+
` $null = Get-ScheduledTask -TaskName 'MinionWSL' -ErrorAction Stop`,
|
|
204
|
+
` $wslTaskExists = $true`,
|
|
205
|
+
` Log 'Ending MinionWSL scheduled task...'`,
|
|
206
|
+
` schtasks /End /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" }`,
|
|
207
|
+
` } catch {`,
|
|
208
|
+
` Log 'MinionWSL task not registered (skipping)'`,
|
|
209
|
+
` }`,
|
|
210
|
+
``,
|
|
211
|
+
` # 2. Ask the agent to flush an offline heartbeat before we yank it.`,
|
|
127
212
|
` Log 'Requesting graceful shutdown...'`,
|
|
128
213
|
gracefulStop,
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
`
|
|
132
|
-
`
|
|
214
|
+
``,
|
|
215
|
+
` # 3. Stop the agent service and WAIT for it to actually be Stopped.`,
|
|
216
|
+
` Log 'Stopping minion-agent service via NSSM...'`,
|
|
217
|
+
` & $nssm stop minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
|
|
218
|
+
` if (-not (Wait-ServiceStopped 'minion-agent' 30)) {`,
|
|
219
|
+
` Log 'WARNING: minion-agent did not reach Stopped within 30s'`,
|
|
220
|
+
` } else {`,
|
|
221
|
+
` Log 'minion-agent stopped'`,
|
|
222
|
+
` }`,
|
|
223
|
+
` # Give Windows a moment to release kernel file handles after exit.`,
|
|
224
|
+
` Start-Sleep -Seconds 2`,
|
|
225
|
+
``,
|
|
226
|
+
` # 4. Kill any straggler node.exe that still hold package files.`,
|
|
227
|
+
` Kill-PackageProcesses`,
|
|
228
|
+
` Start-Sleep -Seconds 1`,
|
|
229
|
+
``,
|
|
230
|
+
` # 5. Retry npm install + integrity check up to 5 times.`,
|
|
133
231
|
` Log 'Installing package...'`,
|
|
134
232
|
` $installed = $false`,
|
|
135
233
|
` for ($attempt = 1; $attempt -le 5; $attempt++) {`,
|
|
234
|
+
` Log "npm install attempt $attempt..."`,
|
|
136
235
|
` $out = & cmd /c "${fullNpmCmd} 2>&1"`,
|
|
137
|
-
`
|
|
138
|
-
`
|
|
139
|
-
` $
|
|
140
|
-
`
|
|
236
|
+
` $npmExit = $LASTEXITCODE`,
|
|
237
|
+
` if ($npmExit -ne 0) {`,
|
|
238
|
+
` Log "npm install attempt $attempt failed (exit $npmExit): $out"`,
|
|
239
|
+
` } else {`,
|
|
240
|
+
` Log "npm install attempt $attempt exit 0"`,
|
|
241
|
+
` if (Test-PackageIntegrity) {`,
|
|
242
|
+
` $installed = $true`,
|
|
243
|
+
` break`,
|
|
244
|
+
` }`,
|
|
141
245
|
` }`,
|
|
142
|
-
` Log "npm install attempt $attempt failed (exit $LASTEXITCODE): $out"`,
|
|
143
246
|
` if ($attempt -lt 5) {`,
|
|
247
|
+
` # Before retrying, re-kill stragglers and (on later attempts) wipe`,
|
|
248
|
+
` # the package dir so npm is forced to lay it down from scratch.`,
|
|
249
|
+
` Kill-PackageProcesses`,
|
|
250
|
+
` if ($attempt -ge 2 -and $pkgDir -and (Test-Path $pkgDir)) {`,
|
|
251
|
+
` Log "Wiping package dir for clean reinstall: $pkgDir"`,
|
|
252
|
+
` try { Remove-Item -Recurse -Force $pkgDir -ErrorAction Stop }`,
|
|
253
|
+
` catch { Log "Remove-Item failed: $_" }`,
|
|
254
|
+
` }`,
|
|
255
|
+
` if ($attempt -ge 3) {`,
|
|
256
|
+
` Log 'Running npm cache verify...'`,
|
|
257
|
+
` & cmd /c 'npm cache verify 2>&1' | ForEach-Object { Log "npm cache: $_" }`,
|
|
258
|
+
` }`,
|
|
144
259
|
` Log "Retrying in 10 seconds..."`,
|
|
145
260
|
` Start-Sleep -Seconds 10`,
|
|
146
261
|
` }`,
|
|
147
262
|
` }`,
|
|
148
|
-
` if (-not $installed) { throw "npm install failed after 5 attempts" }`,
|
|
149
|
-
|
|
150
|
-
`
|
|
263
|
+
` if (-not $installed) { throw "npm install / integrity check failed after 5 attempts" }`,
|
|
264
|
+
``,
|
|
265
|
+
` # 6. Start the agent service.`,
|
|
266
|
+
` Log 'Starting minion-agent service...'`,
|
|
267
|
+
` & $nssm start minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
|
|
268
|
+
``,
|
|
269
|
+
` # 7. Re-run MinionWSL if it was registered.`,
|
|
270
|
+
` if ($wslTaskExists) {`,
|
|
271
|
+
` Log 'Running MinionWSL scheduled task...'`,
|
|
272
|
+
` schtasks /Run /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" }`,
|
|
273
|
+
` }`,
|
|
274
|
+
``,
|
|
151
275
|
` Log 'Update completed successfully'`,
|
|
152
276
|
`} catch {`,
|
|
153
277
|
` Log "Update failed: $_"`,
|
|
154
|
-
`
|
|
155
|
-
`
|
|
278
|
+
` # Do NOT blindly start a possibly-broken agent. If the package is`,
|
|
279
|
+
` # intact (verify passes), start it — otherwise leave it down so the`,
|
|
280
|
+
` # failure surfaces via missed heartbeats instead of a crash loop.`,
|
|
281
|
+
` if (Test-PackageIntegrity) {`,
|
|
282
|
+
` Log 'Package looks intact — starting minion-agent anyway.'`,
|
|
283
|
+
` & $nssm start minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
|
|
284
|
+
` if ($wslTaskExists) {`,
|
|
285
|
+
` try { schtasks /Run /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" } } catch {}`,
|
|
286
|
+
` }`,
|
|
287
|
+
` } else {`,
|
|
288
|
+
` Log 'Package integrity check failed — NOT starting minion-agent. Manual recovery required.'`,
|
|
289
|
+
` }`,
|
|
156
290
|
`} finally {`,
|
|
157
291
|
` Log 'Cleaning up updater service...'`,
|
|
158
292
|
` & $nssm stop minion-update confirm 2>$null`,
|
package/win/minion-cli.ps1
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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' {
|
|
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
|
}
|