@geekbeer/minion 3.5.1 → 3.5.6
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/minion-cli.ps1 +156 -163
package/package.json
CHANGED
package/win/minion-cli.ps1
CHANGED
|
@@ -35,8 +35,6 @@ while ($i -lt $args.Count) {
|
|
|
35
35
|
|
|
36
36
|
$ErrorActionPreference = 'Stop'
|
|
37
37
|
|
|
38
|
-
# Load System.Web for password generation (used in setup)
|
|
39
|
-
Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue
|
|
40
38
|
|
|
41
39
|
# ============================================================
|
|
42
40
|
# Require Administrator for service management commands
|
|
@@ -280,7 +278,7 @@ function Invoke-HealthCheck {
|
|
|
280
278
|
function Assert-NssmAvailable {
|
|
281
279
|
if (-not $NssmPath -or -not (Test-Path $NssmPath)) {
|
|
282
280
|
Write-Error "NSSM not found. Expected at: $vendorNssm"
|
|
283
|
-
Write-Host " Reinstall the package: npm install -g @geekbeer/minion" -ForegroundColor Yellow
|
|
281
|
+
Write-Host " Reinstall the package (admin PowerShell): npm install -g @geekbeer/minion" -ForegroundColor Yellow
|
|
284
282
|
exit 1
|
|
285
283
|
}
|
|
286
284
|
}
|
|
@@ -392,7 +390,7 @@ function Restart-MinionService {
|
|
|
392
390
|
# ============================================================
|
|
393
391
|
|
|
394
392
|
function Invoke-Setup {
|
|
395
|
-
$totalSteps =
|
|
393
|
+
$totalSteps = 11
|
|
396
394
|
|
|
397
395
|
# Minionization warning
|
|
398
396
|
Write-Host ""
|
|
@@ -403,7 +401,6 @@ function Invoke-Setup {
|
|
|
403
401
|
|
|
404
402
|
Write-Host " This setup will:" -ForegroundColor Yellow
|
|
405
403
|
Write-Host " - Install and configure software (Node.js, Claude Code, VNC)"
|
|
406
|
-
Write-Host " - Create dedicated 'minion' service account"
|
|
407
404
|
Write-Host " - Register Windows Services via NSSM"
|
|
408
405
|
Write-Host " - Configure firewall rules"
|
|
409
406
|
Write-Host ""
|
|
@@ -431,6 +428,14 @@ function Invoke-Setup {
|
|
|
431
428
|
|
|
432
429
|
# Save setup user's SID for SDDL grants (so non-admin can control services later)
|
|
433
430
|
$setupUserSid = ([System.Security.Principal.WindowsIdentity]::GetCurrent().User).Value
|
|
431
|
+
# Also resolve target user's SID (may differ from setup user when run from admin account)
|
|
432
|
+
$targetUserName = Split-Path $TargetUserProfile -Leaf
|
|
433
|
+
try {
|
|
434
|
+
$targetUserSid = (New-Object System.Security.Principal.NTAccount($targetUserName)).Translate(
|
|
435
|
+
[System.Security.Principal.SecurityIdentifier]).Value
|
|
436
|
+
} catch {
|
|
437
|
+
$targetUserSid = $null
|
|
438
|
+
}
|
|
434
439
|
New-Item -Path $DataDir -ItemType Directory -Force | Out-Null
|
|
435
440
|
[System.IO.File]::WriteAllText((Join-Path $DataDir '.setup-user-sid'), $setupUserSid)
|
|
436
441
|
# Save target user profile so configure/uninstall can find it
|
|
@@ -571,74 +576,8 @@ function Invoke-Setup {
|
|
|
571
576
|
Write-Host " Please run 'claude' in a terminal to complete the authentication process." -ForegroundColor Yellow
|
|
572
577
|
Write-Host ""
|
|
573
578
|
|
|
574
|
-
# Step 4: Create
|
|
575
|
-
Write-Step 4 $totalSteps "Creating
|
|
576
|
-
$MinionSvcUser = 'minion'
|
|
577
|
-
$MinionSvcUserFull = ".\$MinionSvcUser"
|
|
578
|
-
$minionUserExists = [bool](Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue)
|
|
579
|
-
if ($minionUserExists) {
|
|
580
|
-
Write-Detail "Service account '$MinionSvcUser' already exists"
|
|
581
|
-
} else {
|
|
582
|
-
# Generate a random password (service account — not used interactively)
|
|
583
|
-
$svcPassword = [System.Web.Security.Membership]::GeneratePassword(24, 4)
|
|
584
|
-
$securePassword = ConvertTo-SecureString $svcPassword -AsPlainText -Force
|
|
585
|
-
New-LocalUser -Name $MinionSvcUser -Password $securePassword -Description 'Minion Agent Service Account' -PasswordNeverExpires -UserMayNotChangePassword -AccountNeverExpires | Out-Null
|
|
586
|
-
# Deny interactive/remote logon (service-only account)
|
|
587
|
-
& net localgroup "Users" $MinionSvcUser /delete 2>$null
|
|
588
|
-
Write-Detail "Service account '$MinionSvcUser' created (non-interactive)"
|
|
589
|
-
}
|
|
590
|
-
# Store password for NSSM ObjectName configuration
|
|
591
|
-
if (-not $minionUserExists) {
|
|
592
|
-
# Save password to a protected file for NSSM service registration
|
|
593
|
-
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
594
|
-
New-Item -Path (Split-Path $svcPasswordFile) -ItemType Directory -Force | Out-Null
|
|
595
|
-
[System.IO.File]::WriteAllText($svcPasswordFile, $svcPassword)
|
|
596
|
-
# Restrict file access to current user only
|
|
597
|
-
$acl = Get-Acl $svcPasswordFile
|
|
598
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
599
|
-
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
600
|
-
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name, 'FullControl', 'Allow')
|
|
601
|
-
$acl.AddAccessRule($adminRule)
|
|
602
|
-
Set-Acl $svcPasswordFile $acl
|
|
603
|
-
Write-Detail "Service account credentials stored"
|
|
604
|
-
} else {
|
|
605
|
-
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
606
|
-
if (Test-Path $svcPasswordFile) {
|
|
607
|
-
$svcPassword = [System.IO.File]::ReadAllText($svcPasswordFile).Trim()
|
|
608
|
-
} else {
|
|
609
|
-
# Re-generate password for existing account (reset)
|
|
610
|
-
$svcPassword = [System.Web.Security.Membership]::GeneratePassword(24, 4)
|
|
611
|
-
$securePassword = ConvertTo-SecureString $svcPassword -AsPlainText -Force
|
|
612
|
-
Set-LocalUser -Name $MinionSvcUser -Password $securePassword
|
|
613
|
-
New-Item -Path (Split-Path $svcPasswordFile) -ItemType Directory -Force | Out-Null
|
|
614
|
-
[System.IO.File]::WriteAllText($svcPasswordFile, $svcPassword)
|
|
615
|
-
$acl = Get-Acl $svcPasswordFile
|
|
616
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
617
|
-
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
618
|
-
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name, 'FullControl', 'Allow')
|
|
619
|
-
$acl.AddAccessRule($adminRule)
|
|
620
|
-
Set-Acl $svcPasswordFile $acl
|
|
621
|
-
Write-Detail "Service account password reset and stored"
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
# Grant 'Log on as a service' right to the minion user
|
|
625
|
-
$tempCfg = Join-Path $env:TEMP 'minion-secedit.cfg'
|
|
626
|
-
$tempDb = Join-Path $env:TEMP 'minion-secedit.sdb'
|
|
627
|
-
& secedit /export /cfg $tempCfg /areas USER_RIGHTS 2>$null
|
|
628
|
-
$cfgContent = Get-Content $tempCfg -Raw
|
|
629
|
-
if ($cfgContent -match 'SeServiceLogonRight\s*=\s*(.*)') {
|
|
630
|
-
$existing = $Matches[1]
|
|
631
|
-
if ($existing -notmatch $MinionSvcUser) {
|
|
632
|
-
$cfgContent = $cfgContent -replace "(SeServiceLogonRight\s*=\s*)(.*)", "`$1`$2,$MinionSvcUser"
|
|
633
|
-
[System.IO.File]::WriteAllText($tempCfg, $cfgContent)
|
|
634
|
-
& secedit /configure /db $tempDb /cfg $tempCfg /areas USER_RIGHTS 2>$null
|
|
635
|
-
Write-Detail "Granted 'Log on as a service' right to '$MinionSvcUser'"
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
Remove-Item $tempCfg, $tempDb -Force -ErrorAction SilentlyContinue
|
|
639
|
-
|
|
640
|
-
# Step 5: Create config directory and default .env
|
|
641
|
-
Write-Step 5 $totalSteps "Creating config directory..."
|
|
579
|
+
# Step 4: Create config directory and default .env
|
|
580
|
+
Write-Step 4 $totalSteps "Creating config directory..."
|
|
642
581
|
New-Item -Path $DataDir -ItemType Directory -Force | Out-Null
|
|
643
582
|
New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
|
|
644
583
|
if (-not (Test-Path $EnvFile)) {
|
|
@@ -653,16 +592,8 @@ function Invoke-Setup {
|
|
|
653
592
|
Write-Detail "$EnvFile already exists, preserving"
|
|
654
593
|
}
|
|
655
594
|
|
|
656
|
-
#
|
|
657
|
-
$
|
|
658
|
-
$minionRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
659
|
-
$MinionSvcUser, 'Modify', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
|
660
|
-
$minionAcl.AddAccessRule($minionRule)
|
|
661
|
-
Set-Acl $DataDir $minionAcl
|
|
662
|
-
Write-Detail "Granted '$MinionSvcUser' access to $DataDir"
|
|
663
|
-
|
|
664
|
-
# Step 6: Install node-pty (required for Windows terminal management)
|
|
665
|
-
Write-Step 6 $totalSteps "Installing terminal support (node-pty)..."
|
|
595
|
+
# Step 5: Install node-pty (required for Windows terminal management)
|
|
596
|
+
Write-Step 5 $totalSteps "Installing terminal support (node-pty)..."
|
|
666
597
|
$minionPkgDir = $CliDir
|
|
667
598
|
if (Test-Path $minionPkgDir) {
|
|
668
599
|
Push-Location $minionPkgDir
|
|
@@ -696,22 +627,22 @@ function Invoke-Setup {
|
|
|
696
627
|
}
|
|
697
628
|
else {
|
|
698
629
|
Write-Warn "Minion package not found at $minionPkgDir"
|
|
699
|
-
Write-Host " Please run: npm install -g @geekbeer/minion"
|
|
630
|
+
Write-Host " Please run (admin PowerShell): npm install -g @geekbeer/minion"
|
|
700
631
|
}
|
|
701
632
|
|
|
702
633
|
# Step 7: Verify NSSM
|
|
703
|
-
Write-Step
|
|
634
|
+
Write-Step 6 $totalSteps "Verifying NSSM..."
|
|
704
635
|
Assert-NssmAvailable
|
|
705
636
|
$nssmVersion = Invoke-Nssm version
|
|
706
637
|
Write-Detail "NSSM available: $NssmPath ($nssmVersion)"
|
|
707
638
|
|
|
708
639
|
# Step 8: Register Windows Services via NSSM
|
|
709
|
-
Write-Step
|
|
640
|
+
Write-Step 7 $totalSteps "Registering Windows Services..."
|
|
710
641
|
|
|
711
642
|
$serverJs = Join-Path $minionPkgDir 'win\server.js'
|
|
712
643
|
if (-not (Test-Path $serverJs)) {
|
|
713
644
|
Write-Error "server.js not found at $serverJs"
|
|
714
|
-
Write-Host " Please run: npm install -g @geekbeer/minion"
|
|
645
|
+
Write-Host " Please run (admin PowerShell): npm install -g @geekbeer/minion"
|
|
715
646
|
exit 1
|
|
716
647
|
}
|
|
717
648
|
$nodePath = (Get-Command node).Source
|
|
@@ -743,13 +674,15 @@ function Invoke-Setup {
|
|
|
743
674
|
Invoke-Nssm set minion-agent Start SERVICE_AUTO_START
|
|
744
675
|
Invoke-Nssm set minion-agent DisplayName "Minion Agent"
|
|
745
676
|
Invoke-Nssm set minion-agent Description "GeekBeer Minion AI Agent Service"
|
|
746
|
-
#
|
|
747
|
-
Invoke-Nssm set minion-agent ObjectName $MinionSvcUserFull $svcPassword
|
|
677
|
+
# Runs as LocalSystem (NSSM default). USERPROFILE/HOME env vars point to target user's profile.
|
|
748
678
|
Grant-ServiceControlToUser 'minion-agent' $setupUserSid
|
|
749
|
-
|
|
679
|
+
if ($targetUserSid -and $targetUserSid -ne $setupUserSid) {
|
|
680
|
+
Grant-ServiceControlToUser 'minion-agent' $targetUserSid
|
|
681
|
+
}
|
|
682
|
+
Write-Detail "minion-agent service registered (runs as LocalSystem)"
|
|
750
683
|
|
|
751
|
-
# Step 9: Install and configure TightVNC (runs as
|
|
752
|
-
Write-Step
|
|
684
|
+
# Step 9: Install and configure TightVNC (runs as logon task in user session for desktop capture)
|
|
685
|
+
Write-Step 8 $totalSteps "Setting up TightVNC Server..."
|
|
753
686
|
$vncSystemPath = 'C:\Program Files\TightVNC\tvnserver.exe'
|
|
754
687
|
$vncPortableDir = Join-Path $DataDir 'tightvnc'
|
|
755
688
|
$vncPortablePath = Join-Path $vncPortableDir 'PFiles\TightVNC\tvnserver.exe'
|
|
@@ -798,31 +731,32 @@ function Invoke-Setup {
|
|
|
798
731
|
}
|
|
799
732
|
|
|
800
733
|
# Configure TightVNC registry (localhost-only, no VNC auth)
|
|
801
|
-
|
|
734
|
+
# Write to both HKCU (for user-session -run mode) and HKLM (fallback)
|
|
802
735
|
if ($vncExePath) {
|
|
803
|
-
|
|
804
|
-
|
|
736
|
+
foreach ($vncRegPath in @('HKCU:\Software\TightVNC\Server', 'HKLM:\Software\TightVNC\Server')) {
|
|
737
|
+
if (-not (Test-Path $vncRegPath)) {
|
|
738
|
+
New-Item -Path $vncRegPath -Force | Out-Null
|
|
739
|
+
}
|
|
740
|
+
Set-ItemProperty -Path $vncRegPath -Name 'LoopbackOnly' -Value 1 -Type DWord
|
|
741
|
+
Set-ItemProperty -Path $vncRegPath -Name 'AllowLoopback' -Value 1 -Type DWord
|
|
742
|
+
Set-ItemProperty -Path $vncRegPath -Name 'UseVncAuthentication' -Value 0 -Type DWord
|
|
743
|
+
Set-ItemProperty -Path $vncRegPath -Name 'UseControlAuthentication' -Value 0 -Type DWord
|
|
744
|
+
Set-ItemProperty -Path $vncRegPath -Name 'RfbPort' -Value 5900 -Type DWord
|
|
805
745
|
}
|
|
806
|
-
|
|
807
|
-
Set-ItemProperty -Path $vncRegPath -Name 'AllowLoopback' -Value 1 -Type DWord
|
|
808
|
-
Set-ItemProperty -Path $vncRegPath -Name 'UseVncAuthentication' -Value 0 -Type DWord
|
|
809
|
-
Set-ItemProperty -Path $vncRegPath -Name 'UseControlAuthentication' -Value 0 -Type DWord
|
|
810
|
-
Set-ItemProperty -Path $vncRegPath -Name 'RfbPort' -Value 5900 -Type DWord
|
|
746
|
+
Write-Detail "TightVNC registry configured (HKCU + HKLM)"
|
|
811
747
|
|
|
812
|
-
#
|
|
748
|
+
# Remove legacy NSSM service if present (VNC now runs as logon task)
|
|
813
749
|
Invoke-Nssm stop minion-vnc
|
|
814
750
|
Invoke-Nssm remove minion-vnc confirm
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
Grant-ServiceControlToUser 'minion-vnc' $setupUserSid
|
|
821
|
-
Write-Detail "minion-vnc service registered"
|
|
751
|
+
|
|
752
|
+
# Register VNC as logon task (must run in user session for desktop capture)
|
|
753
|
+
schtasks /Delete /TN "MinionVNC" /F 2>$null
|
|
754
|
+
schtasks /Create /TN "MinionVNC" /TR "'$vncExePath' -run" /SC ONLOGON /RL HIGHEST /F | Out-Null
|
|
755
|
+
Write-Detail "TightVNC registered as logon task (user session, not service)"
|
|
822
756
|
}
|
|
823
757
|
|
|
824
758
|
# Step 10: Setup websockify (runs as LocalSystem, paired with VNC)
|
|
825
|
-
Write-Step
|
|
759
|
+
Write-Step 9 $totalSteps "Setting up websockify..."
|
|
826
760
|
[array]$wsCmd = Get-WebsockifyCommand
|
|
827
761
|
if (-not $wsCmd) {
|
|
828
762
|
# Ensure Python is installed
|
|
@@ -878,7 +812,7 @@ function Invoke-Setup {
|
|
|
878
812
|
}
|
|
879
813
|
|
|
880
814
|
if ($wsCmd -and $vncExePath) {
|
|
881
|
-
# Register websockify as NSSM service
|
|
815
|
+
# Register websockify as NSSM service (no dependency on minion-vnc — VNC runs as logon task)
|
|
882
816
|
Invoke-Nssm stop minion-websockify
|
|
883
817
|
Invoke-Nssm remove minion-websockify confirm
|
|
884
818
|
if ($wsCmd.Count -eq 1) {
|
|
@@ -888,19 +822,21 @@ function Invoke-Setup {
|
|
|
888
822
|
$wsArgs = ($wsCmd[1..($wsCmd.Count-1)] + @('6080', 'localhost:5900')) -join ' '
|
|
889
823
|
Invoke-Nssm install minion-websockify $wsCmd[0] $wsArgs
|
|
890
824
|
}
|
|
891
|
-
Invoke-Nssm set minion-websockify DependOnService minion-vnc
|
|
892
825
|
Invoke-Nssm set minion-websockify Start SERVICE_AUTO_START
|
|
893
826
|
Invoke-Nssm set minion-websockify DisplayName "Minion Websockify"
|
|
894
827
|
Invoke-Nssm set minion-websockify Description "WebSocket proxy for VNC (6080 -> 5900)"
|
|
895
828
|
Invoke-Nssm set minion-websockify AppRestartDelay 3000
|
|
896
829
|
Grant-ServiceControlToUser 'minion-websockify' $setupUserSid
|
|
897
|
-
|
|
830
|
+
if ($targetUserSid -and $targetUserSid -ne $setupUserSid) {
|
|
831
|
+
Grant-ServiceControlToUser 'minion-websockify' $targetUserSid
|
|
832
|
+
}
|
|
833
|
+
Write-Detail "minion-websockify service registered"
|
|
898
834
|
} else {
|
|
899
835
|
Write-Warn "websockify not available, VNC WebSocket proxy will not be registered"
|
|
900
836
|
}
|
|
901
837
|
|
|
902
838
|
# Step 11: Disable screensaver, lock screen, and sleep
|
|
903
|
-
Write-Step
|
|
839
|
+
Write-Step 10 $totalSteps "Disabling screensaver, lock screen, and sleep..."
|
|
904
840
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveActive -Value '0'
|
|
905
841
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value '0'
|
|
906
842
|
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name SCRNSAVE.EXE -Value ''
|
|
@@ -914,7 +850,7 @@ function Invoke-Setup {
|
|
|
914
850
|
Write-Detail "Sleep and monitor timeout disabled"
|
|
915
851
|
|
|
916
852
|
# Configure firewall rules
|
|
917
|
-
Write-Step
|
|
853
|
+
Write-Step 11 $totalSteps "Configuring firewall rules..."
|
|
918
854
|
$fwRules = @(
|
|
919
855
|
@{ Name = 'Minion Agent'; Port = 8080 },
|
|
920
856
|
@{ Name = 'Minion Terminal'; Port = 7681 },
|
|
@@ -930,25 +866,73 @@ function Invoke-Setup {
|
|
|
930
866
|
}
|
|
931
867
|
}
|
|
932
868
|
|
|
933
|
-
# Grant
|
|
934
|
-
$
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
869
|
+
# Grant target user access to admin's minion package and bin links (so target user can run minion-cli-win)
|
|
870
|
+
$adminNpmBin = Split-Path (Get-Command minion-cli-win -ErrorAction SilentlyContinue).Source -ErrorAction SilentlyContinue
|
|
871
|
+
if (-not $adminNpmBin) {
|
|
872
|
+
$adminNpmBin = & npm config get prefix 2>$null
|
|
873
|
+
}
|
|
874
|
+
if ($adminNpmBin -and ($adminNpmBin -ne (Join-Path $TargetUserProfile 'AppData\Roaming\npm'))) {
|
|
875
|
+
$targetUserName = Split-Path $TargetUserProfile -Leaf
|
|
876
|
+
|
|
877
|
+
# Grant ReadAndExecute on bin links (minion-cli-win.cmd, hq-win.cmd, etc.)
|
|
878
|
+
$binFiles = Get-ChildItem -Path $adminNpmBin -Filter '*minion*' -ErrorAction SilentlyContinue
|
|
879
|
+
$binFiles += Get-ChildItem -Path $adminNpmBin -Filter '*hq-win*' -ErrorAction SilentlyContinue
|
|
880
|
+
foreach ($f in $binFiles) {
|
|
881
|
+
icacls $f.FullName /grant "${targetUserName}:(RX)" /Q 2>$null | Out-Null
|
|
882
|
+
}
|
|
883
|
+
# Grant ReadAndExecute on the @geekbeer/minion package directory (recursive)
|
|
884
|
+
$minionPkgDir = Join-Path (Join-Path $adminNpmBin 'node_modules') '@geekbeer\minion'
|
|
885
|
+
if (Test-Path $minionPkgDir) {
|
|
886
|
+
icacls $minionPkgDir /grant "${targetUserName}:(OI)(CI)RX" /T /Q 2>$null | Out-Null
|
|
887
|
+
Write-Detail "Granted target user read access to $minionPkgDir"
|
|
888
|
+
}
|
|
889
|
+
# Grant traverse access on ancestor directories so the path is reachable
|
|
890
|
+
# e.g., C:\Users\yunoda -> AppData -> Roaming -> npm -> node_modules -> @geekbeer
|
|
891
|
+
$traverseDirs = @(
|
|
892
|
+
$adminNpmBin,
|
|
893
|
+
(Join-Path $adminNpmBin 'node_modules'),
|
|
894
|
+
(Join-Path $adminNpmBin 'node_modules\@geekbeer')
|
|
895
|
+
)
|
|
896
|
+
# Walk up from npm bin dir to drive root to grant traverse (list+read) on each
|
|
897
|
+
$walkDir = $adminNpmBin
|
|
898
|
+
while ($walkDir) {
|
|
899
|
+
$parent = Split-Path $walkDir -Parent
|
|
900
|
+
if (-not $parent -or $parent -eq $walkDir) { break }
|
|
901
|
+
$traverseDirs += $parent
|
|
902
|
+
$walkDir = $parent
|
|
903
|
+
# Stop at drive root
|
|
904
|
+
if ($parent.Length -le 3) { break }
|
|
905
|
+
}
|
|
906
|
+
foreach ($dir in ($traverseDirs | Select-Object -Unique)) {
|
|
907
|
+
if (Test-Path $dir) {
|
|
908
|
+
# Grant only traverse + list (no recursive, no inherit)
|
|
909
|
+
icacls $dir /grant "${targetUserName}:(RX)" /Q 2>$null | Out-Null
|
|
910
|
+
}
|
|
911
|
+
}
|
|
942
912
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
913
|
+
# Add admin's npm bin to target user's PATH
|
|
914
|
+
$targetUserSid = $null
|
|
915
|
+
try {
|
|
916
|
+
$targetUserSid = (New-Object System.Security.Principal.NTAccount($targetUserName)).Translate(
|
|
917
|
+
[System.Security.Principal.SecurityIdentifier]).Value
|
|
918
|
+
} catch {}
|
|
919
|
+
if ($targetUserSid) {
|
|
920
|
+
$regPath = "Registry::HKEY_USERS\$targetUserSid\Environment"
|
|
921
|
+
if (Test-Path $regPath) {
|
|
922
|
+
$currentPath = (Get-ItemProperty -Path $regPath -Name PATH -ErrorAction SilentlyContinue).PATH
|
|
923
|
+
if ($currentPath -and $currentPath -notlike "*$adminNpmBin*") {
|
|
924
|
+
Set-ItemProperty -Path $regPath -Name PATH -Value "$currentPath;$adminNpmBin"
|
|
925
|
+
Write-Detail "Added $adminNpmBin to target user's PATH"
|
|
926
|
+
} elseif (-not $currentPath) {
|
|
927
|
+
Set-ItemProperty -Path $regPath -Name PATH -Value $adminNpmBin
|
|
928
|
+
Write-Detail "Set target user's PATH to $adminNpmBin"
|
|
929
|
+
} else {
|
|
930
|
+
Write-Detail "Target user's PATH already contains $adminNpmBin"
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
Write-Warn "Target user's registry not loaded. User must log in and re-run setup, or manually add $adminNpmBin to PATH."
|
|
934
|
+
}
|
|
935
|
+
}
|
|
952
936
|
}
|
|
953
937
|
|
|
954
938
|
Write-Host ""
|
|
@@ -958,8 +942,8 @@ function Invoke-Setup {
|
|
|
958
942
|
Write-Host ""
|
|
959
943
|
Write-Host "Services registered (not yet started):"
|
|
960
944
|
Write-Host " minion-agent - AI Agent (port 8080)"
|
|
961
|
-
Write-Host " minion-vnc - TightVNC Server (port 5900)"
|
|
962
945
|
Write-Host " minion-websockify - WebSocket proxy (port 6080)"
|
|
946
|
+
Write-Host " MinionVNC (task) - TightVNC (port 5900, starts at logon)"
|
|
963
947
|
Write-Host ""
|
|
964
948
|
Write-Host "Next step: Connect to HQ (run as regular user):" -ForegroundColor Yellow
|
|
965
949
|
Write-Host " minion-cli-win configure ``"
|
|
@@ -1001,12 +985,12 @@ function Invoke-Uninstall {
|
|
|
1001
985
|
}
|
|
1002
986
|
Write-Host ""
|
|
1003
987
|
|
|
1004
|
-
$totalSteps =
|
|
988
|
+
$totalSteps = 6
|
|
1005
989
|
|
|
1006
990
|
# Step 1: Stop and remove all NSSM services
|
|
1007
991
|
Write-Step 1 $totalSteps "Stopping and removing services..."
|
|
1008
992
|
if ($NssmPath -and (Test-Path $NssmPath)) {
|
|
1009
|
-
foreach ($svc in @('minion-cloudflared', 'minion-websockify', 'minion-
|
|
993
|
+
foreach ($svc in @('minion-cloudflared', 'minion-websockify', 'minion-agent')) {
|
|
1010
994
|
$status = Invoke-Nssm status $svc
|
|
1011
995
|
if ($status) {
|
|
1012
996
|
Invoke-Nssm stop $svc
|
|
@@ -1016,6 +1000,12 @@ function Invoke-Uninstall {
|
|
|
1016
1000
|
}
|
|
1017
1001
|
}
|
|
1018
1002
|
|
|
1003
|
+
# Remove VNC logon task and legacy NSSM service
|
|
1004
|
+
schtasks /Delete /TN "MinionVNC" /F 2>$null
|
|
1005
|
+
Invoke-Nssm stop minion-vnc
|
|
1006
|
+
Invoke-Nssm remove minion-vnc confirm
|
|
1007
|
+
Write-Detail "VNC logon task and legacy service removed"
|
|
1008
|
+
|
|
1019
1009
|
# Also stop legacy processes
|
|
1020
1010
|
Stop-Process -Name tvnserver -Force -ErrorAction SilentlyContinue
|
|
1021
1011
|
Stop-Process -Name websockify -Force -ErrorAction SilentlyContinue
|
|
@@ -1024,7 +1014,7 @@ function Invoke-Uninstall {
|
|
|
1024
1014
|
|
|
1025
1015
|
# Step 2: Remove firewall rules
|
|
1026
1016
|
Write-Step 2 $totalSteps "Removing firewall rules..."
|
|
1027
|
-
foreach ($ruleName in @('Minion Agent', 'Minion VNC')) {
|
|
1017
|
+
foreach ($ruleName in @('Minion Agent', 'Minion Terminal', 'Minion VNC')) {
|
|
1028
1018
|
$existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
|
1029
1019
|
if ($existing) {
|
|
1030
1020
|
Remove-NetFirewallRule -DisplayName $ruleName
|
|
@@ -1095,24 +1085,15 @@ function Invoke-Uninstall {
|
|
|
1095
1085
|
Write-Detail "Removed rules: core.md"
|
|
1096
1086
|
}
|
|
1097
1087
|
|
|
1098
|
-
#
|
|
1099
|
-
Write-Step 6 $totalSteps "Removing service account..."
|
|
1100
|
-
$MinionSvcUser = 'minion'
|
|
1101
|
-
if (Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue) {
|
|
1102
|
-
Remove-LocalUser -Name $MinionSvcUser
|
|
1103
|
-
Write-Detail "Removed local user '$MinionSvcUser'"
|
|
1104
|
-
} else {
|
|
1105
|
-
Write-Detail "Service account '$MinionSvcUser' not found, skipping"
|
|
1106
|
-
}
|
|
1107
|
-
# Remove stored service password
|
|
1088
|
+
# Clean up legacy service account and password file (from v3.1.0-v3.4.x)
|
|
1108
1089
|
$svcPasswordFile = Join-Path $DataDir '.svc-password'
|
|
1109
1090
|
if (Test-Path $svcPasswordFile) {
|
|
1110
1091
|
Remove-Item $svcPasswordFile -Force
|
|
1111
|
-
Write-Detail "Removed service credentials file"
|
|
1092
|
+
Write-Detail "Removed legacy service credentials file"
|
|
1112
1093
|
}
|
|
1113
1094
|
|
|
1114
|
-
# Step
|
|
1115
|
-
Write-Step
|
|
1095
|
+
# Step 6: Remove Cloudflare Tunnel configuration
|
|
1096
|
+
Write-Step 6 $totalSteps "Removing Cloudflare Tunnel configuration..."
|
|
1116
1097
|
$cfConfigDir = Join-Path $TargetUserProfile '.cloudflared'
|
|
1117
1098
|
if (Test-Path $cfConfigDir) {
|
|
1118
1099
|
Remove-Item $cfConfigDir -Recurse -Force
|
|
@@ -1280,13 +1261,24 @@ function Invoke-Configure {
|
|
|
1280
1261
|
# Start services (uses sc.exe — works without admin via SDDL)
|
|
1281
1262
|
$startStep = if ($SetupTunnel) { $totalSteps - 2 } else { $totalSteps - 2 }
|
|
1282
1263
|
Write-Step ($totalSteps - 1) $totalSteps "Starting services..."
|
|
1283
|
-
# Start VNC
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1264
|
+
# Start VNC (logon task — runs in user session for desktop capture)
|
|
1265
|
+
$vncProcess = Get-Process -Name tvnserver -ErrorAction SilentlyContinue
|
|
1266
|
+
if (-not $vncProcess) {
|
|
1267
|
+
$vncSystemPath = 'C:\Program Files\TightVNC\tvnserver.exe'
|
|
1268
|
+
$vncPortablePath = Join-Path $DataDir 'tightvnc\PFiles\TightVNC\tvnserver.exe'
|
|
1269
|
+
$vncExe = if (Test-Path $vncSystemPath) { $vncSystemPath } elseif (Test-Path $vncPortablePath) { $vncPortablePath } else { $null }
|
|
1270
|
+
if ($vncExe) {
|
|
1271
|
+
Start-Process -FilePath $vncExe -ArgumentList '-run'
|
|
1272
|
+
Write-Detail "TightVNC started (user session)"
|
|
1289
1273
|
}
|
|
1274
|
+
} else {
|
|
1275
|
+
Write-Detail "TightVNC already running"
|
|
1276
|
+
}
|
|
1277
|
+
# Start websockify service
|
|
1278
|
+
$wsState = Get-ServiceState 'minion-websockify'
|
|
1279
|
+
if ($wsState -and $wsState -ne 'RUNNING') {
|
|
1280
|
+
sc.exe start minion-websockify 2>&1 | Out-Null
|
|
1281
|
+
Write-Detail "minion-websockify started"
|
|
1290
1282
|
}
|
|
1291
1283
|
Start-MinionService
|
|
1292
1284
|
|
|
@@ -1343,7 +1335,7 @@ function Show-Status {
|
|
|
1343
1335
|
}
|
|
1344
1336
|
|
|
1345
1337
|
function Show-Daemons {
|
|
1346
|
-
foreach ($svc in @('minion-agent', 'minion-
|
|
1338
|
+
foreach ($svc in @('minion-agent', 'minion-websockify', 'minion-cloudflared')) {
|
|
1347
1339
|
$state = Get-ServiceState $svc
|
|
1348
1340
|
if ($state) {
|
|
1349
1341
|
Write-Host "${svc}: $state"
|
|
@@ -1351,6 +1343,13 @@ function Show-Daemons {
|
|
|
1351
1343
|
Write-Host "${svc}: not installed"
|
|
1352
1344
|
}
|
|
1353
1345
|
}
|
|
1346
|
+
# VNC runs as logon task, not NSSM service
|
|
1347
|
+
$vncProc = Get-Process -Name tvnserver -ErrorAction SilentlyContinue
|
|
1348
|
+
if ($vncProc) {
|
|
1349
|
+
Write-Host "vnc (task): RUNNING (PID $($vncProc[0].Id))"
|
|
1350
|
+
} else {
|
|
1351
|
+
Write-Host "vnc (task): not running"
|
|
1352
|
+
}
|
|
1354
1353
|
}
|
|
1355
1354
|
|
|
1356
1355
|
function Show-Health {
|
|
@@ -1383,13 +1382,7 @@ function Show-Diagnose {
|
|
|
1383
1382
|
Write-Host ""
|
|
1384
1383
|
|
|
1385
1384
|
Write-Host "Service Account:" -ForegroundColor Yellow
|
|
1386
|
-
|
|
1387
|
-
$svcUser = Get-LocalUser -Name $MinionSvcUser -ErrorAction SilentlyContinue
|
|
1388
|
-
if ($svcUser) {
|
|
1389
|
-
Write-Host " User: $MinionSvcUser (Enabled: $($svcUser.Enabled))"
|
|
1390
|
-
} else {
|
|
1391
|
-
Write-Host " User: NOT FOUND (services run as LocalSystem)" -ForegroundColor Yellow
|
|
1392
|
-
}
|
|
1385
|
+
Write-Host " Runs as: LocalSystem"
|
|
1393
1386
|
Write-Host ""
|
|
1394
1387
|
|
|
1395
1388
|
Write-Host "NSSM:" -ForegroundColor Yellow
|