2025-11-03 12:45:09 -07:00
|
|
|
function Show-Header {
|
|
|
|
|
Clear-Host
|
|
|
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
|
|
|
Write-Host " UNIFIED SHEEPIT LAUNCHER" -ForegroundColor Cyan
|
|
|
|
|
Write-Host "====================================" -ForegroundColor Cyan
|
|
|
|
|
Write-Host
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 16:45:30 -07:00
|
|
|
$SheepItJarUrls = @(
|
|
|
|
|
'https://www.sheepit-renderfarm.com/media/applet/client-latest.php',
|
|
|
|
|
'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
|
|
|
|
)
|
2025-11-05 16:19:24 -07:00
|
|
|
$script:SheepItUsername = $null
|
|
|
|
|
$script:SheepItRenderKey = $null
|
|
|
|
|
|
|
|
|
|
function Initialize-SheepItCredentials {
|
|
|
|
|
if (-not $script:SheepItUsername) {
|
|
|
|
|
$script:SheepItUsername = "RaincloudTheDragon"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $script:SheepItRenderKey) {
|
|
|
|
|
$script:SheepItRenderKey = "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-03 12:45:09 -07:00
|
|
|
|
|
|
|
|
$workers = @(
|
2025-11-05 16:19:24 -07:00
|
|
|
@{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $true },
|
|
|
|
|
@{ ID = 2; Name = "blender-boss"; SSHArgs = "-t blender-boss"; Enabled = $true },
|
|
|
|
|
@{ ID = 3; Name = "max"; SSHArgs = "-t max"; Enabled = $true },
|
|
|
|
|
@{ ID = 4; Name = "masterbox"; SSHArgs = "-t masterbox"; Enabled = $true },
|
|
|
|
|
@{ ID = 5; Name = "echo"; SSHArgs = "-t echo"; Enabled = $true },
|
|
|
|
|
@{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; Enabled = $true }
|
2025-11-03 12:45:09 -07:00
|
|
|
)
|
|
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
$script:ControllerScriptBase64 = $null
|
|
|
|
|
$script:AttachHelperScriptBase64 = $null
|
2025-11-26 18:18:13 -07:00
|
|
|
$script:WorkerBasePathCache = @{}
|
|
|
|
|
|
|
|
|
|
function Build-SshArgsFromParts {
|
|
|
|
|
param(
|
|
|
|
|
[pscustomobject]$Parts,
|
|
|
|
|
[switch]$Interactive
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$args = @('-o','ServerAliveInterval=60','-o','ServerAliveCountMax=30')
|
|
|
|
|
if ($Interactive -and $Parts.RequestPty) {
|
|
|
|
|
$args += '-t'
|
|
|
|
|
}
|
|
|
|
|
elseif (-not $Interactive) {
|
|
|
|
|
$args += '-T'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$args += $Parts.Options
|
|
|
|
|
|
|
|
|
|
if ($Parts.Port) {
|
|
|
|
|
$args += '-p'
|
|
|
|
|
$args += $Parts.Port
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$args += $Parts.Host
|
|
|
|
|
return $args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Build-ScpArgsFromParts {
|
|
|
|
|
param(
|
|
|
|
|
[pscustomobject]$Parts
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$args = @('-o','ServerAliveInterval=60','-o','ServerAliveCountMax=30')
|
|
|
|
|
$args += $Parts.Options
|
|
|
|
|
if ($Parts.Port) {
|
|
|
|
|
$args += '-P'
|
|
|
|
|
$args += $Parts.Port
|
|
|
|
|
}
|
|
|
|
|
return $args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-SshArgs {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[switch]$Interactive
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$parts = Get-WorkerConnectionParts -RawArgs $Worker.SSHArgs -DefaultHost $Worker.Name
|
|
|
|
|
return Build-SshArgsFromParts -Parts $parts -Interactive:$Interactive
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-WorkerBasePath {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[pscustomobject]$ConnectionParts = $null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ($script:WorkerBasePathCache.ContainsKey($Worker.Name)) {
|
|
|
|
|
return $script:WorkerBasePathCache[$Worker.Name]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $ConnectionParts) {
|
|
|
|
|
$ConnectionParts = Get-WorkerConnectionParts -RawArgs $Worker.SSHArgs -DefaultHost $Worker.Name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sshArgs = Build-SshArgsFromParts -Parts $ConnectionParts -Interactive:$false
|
|
|
|
|
$scriptBlock = "`$ProgressPreference='SilentlyContinue'; [Environment]::GetFolderPath('LocalApplicationData')"
|
|
|
|
|
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptBlock))
|
|
|
|
|
$remoteCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded"
|
|
|
|
|
$output = & ssh @sshArgs $remoteCmd
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
throw "Unable to determine LocalAppData on $($Worker.Name)."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$base = ($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Last 1).Trim()
|
|
|
|
|
if (-not $base) {
|
|
|
|
|
throw "Unable to read LocalAppData path on $($Worker.Name)."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$final = Join-Path $base 'UnifiedWorkers'
|
|
|
|
|
$script:WorkerBasePathCache[$Worker.Name] = $final
|
|
|
|
|
return $final
|
|
|
|
|
}
|
2025-11-25 14:51:22 -07:00
|
|
|
|
|
|
|
|
function Get-FileBase64 {
|
|
|
|
|
param([string]$Path)
|
|
|
|
|
[Convert]::ToBase64String([IO.File]::ReadAllBytes($Path))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-ControllerScriptBase64 {
|
|
|
|
|
if (-not $script:ControllerScriptBase64) {
|
|
|
|
|
$controllerPath = Join-Path $PSScriptRoot 'remote_worker_controller.ps1'
|
|
|
|
|
$script:ControllerScriptBase64 = Get-FileBase64 -Path $controllerPath
|
|
|
|
|
}
|
|
|
|
|
return $script:ControllerScriptBase64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-AttachHelperScriptBase64 {
|
|
|
|
|
if (-not $script:AttachHelperScriptBase64) {
|
|
|
|
|
$helperPath = Join-Path $PSScriptRoot 'remote_worker_attach.ps1'
|
|
|
|
|
$script:AttachHelperScriptBase64 = Get-FileBase64 -Path $helperPath
|
|
|
|
|
}
|
|
|
|
|
return $script:AttachHelperScriptBase64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ConvertTo-Base64Unicode {
|
|
|
|
|
param([string]$Content)
|
|
|
|
|
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Content))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-WorkerSshArgs {
|
|
|
|
|
param([string]$RawArgs)
|
|
|
|
|
if ([string]::IsNullOrWhiteSpace($RawArgs)) {
|
|
|
|
|
return @()
|
|
|
|
|
}
|
|
|
|
|
return $RawArgs -split '\s+' | Where-Object { $_.Trim().Length -gt 0 }
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
function Get-WorkerConnectionParts {
|
|
|
|
|
param(
|
|
|
|
|
[string]$RawArgs,
|
|
|
|
|
[string]$DefaultHost
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$tokens = Get-WorkerSshArgs -RawArgs $RawArgs
|
|
|
|
|
$options = New-Object System.Collections.Generic.List[string]
|
|
|
|
|
$targetHost = $null
|
|
|
|
|
$port = $null
|
|
|
|
|
$requestPty = $false
|
|
|
|
|
$optionsWithArgs = @('-i','-o','-c','-D','-E','-F','-I','-J','-L','-l','-m','-O','-Q','-R','-S','-W','-w')
|
|
|
|
|
|
|
|
|
|
for ($i = 0; $i -lt $tokens.Count; $i++) {
|
|
|
|
|
$token = $tokens[$i]
|
|
|
|
|
if ($token -eq '-t' -or $token -eq '-tt') {
|
|
|
|
|
$requestPty = $true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($token -eq '-p' -and ($i + 1) -lt $tokens.Count) {
|
|
|
|
|
$port = $tokens[$i + 1]
|
|
|
|
|
$i++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($token.StartsWith('-')) {
|
|
|
|
|
$options.Add($token)
|
|
|
|
|
if ($optionsWithArgs -contains $token -and ($i + 1) -lt $tokens.Count) {
|
|
|
|
|
$options.Add($tokens[$i + 1])
|
|
|
|
|
$i++
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $targetHost) {
|
|
|
|
|
$targetHost = $token
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$options.Add($token)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $targetHost) {
|
|
|
|
|
$targetHost = $DefaultHost
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [pscustomobject]@{
|
|
|
|
|
Host = $targetHost
|
|
|
|
|
Options = $options.ToArray()
|
|
|
|
|
Port = $port
|
|
|
|
|
RequestPty = $requestPty
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
function Invoke-RemotePowerShell {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
2025-11-26 18:18:13 -07:00
|
|
|
[string]$Script,
|
|
|
|
|
[switch]$Interactive
|
2025-11-25 14:51:22 -07:00
|
|
|
)
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
$parts = Get-WorkerConnectionParts -RawArgs $Worker.SSHArgs -DefaultHost $Worker.Name
|
|
|
|
|
if (-not $parts.Host) {
|
|
|
|
|
throw "Unable to determine SSH host for $($Worker.Name)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sshBaseArgs = Build-SshArgsFromParts -Parts $parts -Interactive:$Interactive
|
|
|
|
|
$remoteBasePath = Get-WorkerBasePath -Worker $Worker -ConnectionParts $parts
|
|
|
|
|
$localTemp = [System.IO.Path]::GetTempFileName() + '.ps1'
|
|
|
|
|
Set-Content -Path $localTemp -Value $Script -Encoding UTF8
|
|
|
|
|
|
|
|
|
|
$remoteTmpDir = Join-Path $remoteBasePath 'tmp'
|
|
|
|
|
$remoteScriptWin = Join-Path $remoteTmpDir ("script-{0}.ps1" -f ([guid]::NewGuid().ToString()))
|
|
|
|
|
$remoteScriptScp = $remoteScriptWin -replace '\\','/'
|
|
|
|
|
$remoteTarget = "{0}:{1}" -f $parts.Host, ('"'+$remoteScriptScp+'"')
|
|
|
|
|
|
|
|
|
|
$ensureScript = "New-Item -ItemType Directory -Path '$remoteTmpDir' -Force | Out-Null"
|
|
|
|
|
$ensureCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand " + [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($ensureScript))
|
|
|
|
|
& ssh @sshBaseArgs $ensureCmd
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
Remove-Item $localTemp -ErrorAction SilentlyContinue
|
|
|
|
|
return $LASTEXITCODE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$scpArgs = Build-ScpArgsFromParts -Parts $parts
|
|
|
|
|
$scpArgs += $localTemp
|
|
|
|
|
$scpArgs += $remoteTarget
|
|
|
|
|
|
|
|
|
|
& scp @scpArgs
|
|
|
|
|
$scpExit = $LASTEXITCODE
|
|
|
|
|
Remove-Item $localTemp -ErrorAction SilentlyContinue
|
|
|
|
|
if ($scpExit -ne 0) {
|
|
|
|
|
return $scpExit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$execCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$remoteScriptWin`""
|
|
|
|
|
& ssh @sshBaseArgs $execCmd
|
|
|
|
|
$execExit = $LASTEXITCODE
|
|
|
|
|
|
|
|
|
|
$cleanupScript = "Remove-Item -LiteralPath '$remoteScriptWin' -ErrorAction SilentlyContinue"
|
|
|
|
|
$cleanupCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand " + [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cleanupScript))
|
|
|
|
|
& ssh @sshBaseArgs $cleanupCmd | Out-Null
|
2025-11-25 14:51:22 -07:00
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
return $execExit
|
2025-11-25 14:51:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Ensure-ControllerDeployed {
|
|
|
|
|
param([object]$Worker)
|
|
|
|
|
$controllerBase64 = Get-ControllerScriptBase64
|
2025-11-26 18:18:13 -07:00
|
|
|
$script = @"
|
|
|
|
|
`$ProgressPreference = 'SilentlyContinue'
|
|
|
|
|
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
2025-11-25 14:51:22 -07:00
|
|
|
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
|
|
|
|
`$controllerPath = Join-Path `$dataRoot 'controller.ps1'
|
2025-11-26 18:18:13 -07:00
|
|
|
[IO.File]::WriteAllBytes(`$controllerPath, [Convert]::FromBase64String('$controllerBase64'))
|
|
|
|
|
"@
|
2025-11-25 14:51:22 -07:00
|
|
|
Invoke-RemotePowerShell -Worker $Worker -Script $script | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Ensure-AttachHelperDeployed {
|
|
|
|
|
param([object]$Worker)
|
|
|
|
|
$helperBase64 = Get-AttachHelperScriptBase64
|
2025-11-26 18:18:13 -07:00
|
|
|
$script = @"
|
|
|
|
|
`$ProgressPreference = 'SilentlyContinue'
|
|
|
|
|
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
2025-11-25 14:51:22 -07:00
|
|
|
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
|
|
|
|
`$attachPath = Join-Path `$dataRoot 'attach-helper.ps1'
|
2025-11-26 18:18:13 -07:00
|
|
|
[IO.File]::WriteAllBytes(`$attachPath, [Convert]::FromBase64String('$helperBase64'))
|
|
|
|
|
"@
|
2025-11-25 14:51:22 -07:00
|
|
|
Invoke-RemotePowerShell -Worker $Worker -Script $script | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-EnsureWorkerScript {
|
|
|
|
|
param(
|
|
|
|
|
[string]$WorkerName,
|
|
|
|
|
[string]$WorkerType,
|
|
|
|
|
[string]$PayloadBase64
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
$payload = @{
|
|
|
|
|
WorkerName = $WorkerName
|
|
|
|
|
WorkerType = $WorkerType
|
|
|
|
|
PayloadBase64 = $PayloadBase64
|
|
|
|
|
} | ConvertTo-Json -Compress
|
|
|
|
|
|
|
|
|
|
$payloadBase64 = ConvertTo-Base64Unicode -Content $payload
|
|
|
|
|
|
|
|
|
|
return @"
|
|
|
|
|
`$ProgressPreference = 'SilentlyContinue'
|
|
|
|
|
`$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('$payloadBase64')))
|
|
|
|
|
`$workerName = `$params.WorkerName
|
|
|
|
|
`$workerType = `$params.WorkerType
|
|
|
|
|
`$payloadBase64 = `$params.PayloadBase64
|
|
|
|
|
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
|
|
|
|
`$instanceRoot = Join-Path (Join-Path `$dataRoot `$workerType) `$workerName
|
|
|
|
|
`$metaPath = Join-Path `$instanceRoot 'state\worker-info.json'
|
|
|
|
|
`$controllerPath = Join-Path `$dataRoot 'controller.ps1'
|
2025-11-25 14:51:22 -07:00
|
|
|
|
|
|
|
|
if (-not (Test-Path `$controllerPath)) {
|
|
|
|
|
throw "Controller missing at `$controllerPath"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
`$shouldStart = `$true
|
|
|
|
|
if (Test-Path `$metaPath) {
|
|
|
|
|
try {
|
|
|
|
|
`$meta = Get-Content `$metaPath -Raw | ConvertFrom-Json
|
|
|
|
|
if (`$meta.Status -eq 'running' -and `$meta.WorkerPid) {
|
|
|
|
|
if (Get-Process -Id `$meta.WorkerPid -ErrorAction SilentlyContinue) {
|
|
|
|
|
Write-Host "Worker `$workerName already running (PID `$($meta.WorkerPid))."
|
|
|
|
|
`$shouldStart = `$false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Host "Failed to read metadata. Controller will restart worker." -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (`$shouldStart) {
|
2025-11-26 18:18:13 -07:00
|
|
|
`$pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
|
|
|
|
|
if (`$pwsh) {
|
|
|
|
|
`$psExe = `$pwsh.Source
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-11-25 14:51:22 -07:00
|
|
|
`$psExe = (Get-Command powershell -ErrorAction Stop).Source
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
`$controllerArgs = @(
|
|
|
|
|
'-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',
|
|
|
|
|
'-File',"`$controllerPath",
|
|
|
|
|
'-WorkerName',"`$workerName",
|
|
|
|
|
'-WorkerType',"`$workerType",
|
|
|
|
|
'-PayloadBase64',"`$payloadBase64"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Start-Process -FilePath `$psExe -ArgumentList `$controllerArgs -WindowStyle Hidden | Out-Null
|
|
|
|
|
Write-Host "Worker `$workerName started under controller." -ForegroundColor Green
|
|
|
|
|
}
|
2025-11-26 18:18:13 -07:00
|
|
|
"@
|
2025-11-25 14:51:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Ensure-PersistentWorker {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[string]$WorkerType,
|
|
|
|
|
[string]$PayloadScript
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Ensure-ControllerDeployed -Worker $Worker
|
|
|
|
|
$payloadBase64 = ConvertTo-Base64Unicode -Content $PayloadScript
|
|
|
|
|
$ensureScript = Get-EnsureWorkerScript -WorkerName $Worker.Name -WorkerType $WorkerType -PayloadBase64 $payloadBase64
|
|
|
|
|
Invoke-RemotePowerShell -Worker $Worker -Script $ensureScript | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
function Test-WorkerMetadataExists {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[string]$WorkerType
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$payload = @{
|
|
|
|
|
WorkerName = $Worker.Name
|
|
|
|
|
WorkerType = $WorkerType
|
|
|
|
|
} | ConvertTo-Json -Compress
|
|
|
|
|
$payloadBase64 = ConvertTo-Base64Unicode -Content $payload
|
|
|
|
|
|
|
|
|
|
$script = @"
|
|
|
|
|
`$ProgressPreference = 'SilentlyContinue'
|
|
|
|
|
`$params = ConvertFrom-Json ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('$payloadBase64')))
|
|
|
|
|
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
|
|
|
|
`$instanceRoot = Join-Path (Join-Path `$dataRoot `$params.WorkerType) `$params.WorkerName
|
|
|
|
|
`$metaPath = Join-Path `$instanceRoot 'state\worker-info.json'
|
|
|
|
|
if (Test-Path `$metaPath) {
|
|
|
|
|
exit 0
|
|
|
|
|
}
|
|
|
|
|
exit 1
|
|
|
|
|
"@
|
|
|
|
|
|
|
|
|
|
$result = Invoke-RemotePowerShell -Worker $Worker -Script $script
|
|
|
|
|
return ($result -eq 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Wait-WorkerMetadata {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[string]$WorkerType,
|
|
|
|
|
[int]$TimeoutSeconds = 30
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
|
|
|
|
|
while ([DateTime]::UtcNow -lt $deadline) {
|
|
|
|
|
if (Test-WorkerMetadataExists -Worker $Worker -WorkerType $WorkerType) {
|
|
|
|
|
return $true
|
|
|
|
|
}
|
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $false
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
function Invoke-WorkerAttach {
|
|
|
|
|
param(
|
|
|
|
|
[object]$Worker,
|
|
|
|
|
[string]$WorkerType,
|
|
|
|
|
[switch]$CommandOnly,
|
|
|
|
|
[string]$Command
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Ensure-AttachHelperDeployed -Worker $Worker
|
|
|
|
|
$paramsBlock = @("-WorkerName","$($Worker.Name)","-WorkerType","$WorkerType")
|
|
|
|
|
if ($CommandOnly) {
|
|
|
|
|
$paramsBlock += "-CommandOnly"
|
|
|
|
|
}
|
|
|
|
|
if ($Command) {
|
|
|
|
|
$paramsBlock += "-Command"
|
|
|
|
|
$paramsBlock += $Command
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
$parts = Get-WorkerConnectionParts -RawArgs $Worker.SSHArgs -DefaultHost $Worker.Name
|
|
|
|
|
$sshArgs = Build-SshArgsFromParts -Parts $parts -Interactive:(!$CommandOnly)
|
|
|
|
|
$remoteBase = Get-WorkerBasePath -Worker $Worker -ConnectionParts $parts
|
|
|
|
|
$remoteHelper = Join-Path $remoteBase 'attach-helper.ps1'
|
|
|
|
|
$quotedArgs = ($paramsBlock | ForEach-Object { '"' + ($_ -replace '"','""') + '"' }) -join ' '
|
|
|
|
|
$remoteCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$remoteHelper`" $quotedArgs"
|
2025-11-25 14:51:22 -07:00
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
& ssh @sshArgs $remoteCmd
|
2025-11-25 14:51:22 -07:00
|
|
|
}
|
|
|
|
|
|
2025-11-03 12:45:09 -07:00
|
|
|
function Get-RemoteSheepItCommand {
|
2025-11-05 16:19:24 -07:00
|
|
|
param(
|
|
|
|
|
[string]$RenderKey,
|
|
|
|
|
[string]$Username
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$safeKey = $RenderKey -replace "'", "''"
|
|
|
|
|
$safeUser = $Username -replace "'", "''"
|
2025-11-03 12:45:09 -07:00
|
|
|
|
2025-11-05 16:45:30 -07:00
|
|
|
$urlLiteral = '@(' + (($SheepItJarUrls | ForEach-Object { "'$_'" }) -join ', ') + ')'
|
|
|
|
|
|
2025-11-03 12:45:09 -07:00
|
|
|
@"
|
2025-11-26 18:18:13 -07:00
|
|
|
`$ProgressPreference = 'SilentlyContinue'
|
2025-11-05 16:19:24 -07:00
|
|
|
`$ErrorActionPreference = 'Stop'
|
2025-11-05 15:18:49 -07:00
|
|
|
|
2025-11-05 16:19:24 -07:00
|
|
|
try {
|
|
|
|
|
`$appData = [Environment]::GetFolderPath('ApplicationData')
|
|
|
|
|
`$sheepDir = Join-Path `$appData 'sheepit'
|
|
|
|
|
if (-not (Test-Path `$sheepDir)) {
|
|
|
|
|
New-Item -Path `$sheepDir -ItemType Directory -Force | Out-Null
|
2025-11-05 15:18:49 -07:00
|
|
|
}
|
2025-11-05 16:19:24 -07:00
|
|
|
|
|
|
|
|
`$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
2025-11-05 16:45:30 -07:00
|
|
|
`$urls = $urlLiteral
|
|
|
|
|
`$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
|
|
|
|
|
|
|
|
|
if (Test-Path `$jarPath) {
|
|
|
|
|
Write-Host "SheepIt client already present at `$jarPath. Skipping download." -ForegroundColor Green
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
`$downloaded = `$false
|
|
|
|
|
|
|
|
|
|
foreach (`$url in `$urls) {
|
|
|
|
|
Write-Host "Downloading SheepIt client from `$url..." -ForegroundColor Cyan
|
|
|
|
|
try {
|
|
|
|
|
Invoke-WebRequest -Uri `$url -OutFile `$jarPath -UseBasicParsing -Headers `$headers
|
|
|
|
|
`$downloaded = `$true
|
|
|
|
|
Write-Host "Download complete." -ForegroundColor Green
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
Write-Host ("Download failed from {0}: {1}" -f `$url, `$_.Exception.Message) -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not `$downloaded) {
|
|
|
|
|
throw 'Unable to download SheepIt client from any known URL.'
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-05 16:19:24 -07:00
|
|
|
|
|
|
|
|
Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
2025-11-19 10:50:23 -07:00
|
|
|
|
|
|
|
|
# Check and fix problematic environment variables that can cause boot class path errors
|
|
|
|
|
`$envVarsFixed = `$false
|
|
|
|
|
|
|
|
|
|
# Check JAVA_HOME - invalid values like '\' or empty can cause issues
|
|
|
|
|
if (`$env:JAVA_HOME) {
|
|
|
|
|
if (`$env:JAVA_HOME -eq '\' -or `$env:JAVA_HOME.Trim() -eq '' -or -not (Test-Path `$env:JAVA_HOME)) {
|
|
|
|
|
Write-Host "Warning: Invalid JAVA_HOME detected ('`$env:JAVA_HOME'). Temporarily unsetting..." -ForegroundColor Yellow
|
|
|
|
|
Remove-Item Env:\JAVA_HOME -ErrorAction SilentlyContinue
|
|
|
|
|
`$envVarsFixed = `$true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Check JAVA_TOOL_OPTIONS - invalid values can cause boot class path errors
|
|
|
|
|
if (`$env:JAVA_TOOL_OPTIONS) {
|
|
|
|
|
if (`$env:JAVA_TOOL_OPTIONS -eq '\' -or `$env:JAVA_TOOL_OPTIONS.Trim() -eq '') {
|
|
|
|
|
Write-Host "Warning: Invalid JAVA_TOOL_OPTIONS detected ('`$env:JAVA_TOOL_OPTIONS'). Temporarily unsetting..." -ForegroundColor Yellow
|
|
|
|
|
Remove-Item Env:\JAVA_TOOL_OPTIONS -ErrorAction SilentlyContinue
|
|
|
|
|
`$envVarsFixed = `$true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (`$envVarsFixed) {
|
|
|
|
|
Write-Host "Environment variables fixed. Proceeding with Java launch..." -ForegroundColor Green
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Check Java version
|
|
|
|
|
try {
|
|
|
|
|
`$javaOutput = java -version 2>&1
|
|
|
|
|
`$javaVersion = `$javaOutput | Select-Object -First 1
|
|
|
|
|
Write-Host "Java version: `$javaVersion" -ForegroundColor Gray
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
Write-Host "Warning: Could not determine Java version" -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Set-Location `$sheepDir
|
|
|
|
|
|
|
|
|
|
# Use -XX:+IgnoreUnrecognizedVMOptions to handle any incompatible JVM args
|
|
|
|
|
# This flag helps with Java 9+ compatibility issues
|
|
|
|
|
`$javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', `$jarPath,
|
|
|
|
|
'-ui', 'text', '--log-stdout', '--verbose',
|
|
|
|
|
'-gpu', 'OPTIX_0', '-login', '${safeUser}', '-password', '${safeKey}')
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
& java @javaArgs
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
Write-Host ('Java execution error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
|
|
|
|
Write-Host "If the error persists, try reinstalling Java (Temurin 21 recommended)." -ForegroundColor Yellow
|
|
|
|
|
throw
|
|
|
|
|
}
|
2025-11-05 16:19:24 -07:00
|
|
|
}
|
|
|
|
|
|
2025-11-26 18:18:13 -07:00
|
|
|
catch {
|
|
|
|
|
Write-Host ('Error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
|
|
|
|
Write-Host ('Stack trace: {0}' -f `$_.ScriptStackTrace) -ForegroundColor DarkRed
|
|
|
|
|
}
|
|
|
|
|
"@
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
function Ensure-SheepItWorkerController {
|
|
|
|
|
param([object]$Worker)
|
|
|
|
|
Initialize-SheepItCredentials
|
|
|
|
|
$payloadScript = Get-RemoteSheepItCommand -RenderKey $script:SheepItRenderKey -Username $script:SheepItUsername
|
|
|
|
|
Ensure-PersistentWorker -Worker $Worker -WorkerType 'sheepit' -PayloadScript $payloadScript
|
2025-11-05 16:19:24 -07:00
|
|
|
}
|
|
|
|
|
|
2025-11-03 12:45:09 -07:00
|
|
|
function Start-SheepItWorker {
|
|
|
|
|
param([object]$Worker)
|
2025-11-25 14:51:22 -07:00
|
|
|
Ensure-SheepItWorkerController -Worker $Worker
|
2025-11-26 18:18:13 -07:00
|
|
|
if (-not (Wait-WorkerMetadata -Worker $Worker -WorkerType 'sheepit' -TimeoutSeconds 20)) {
|
|
|
|
|
Write-Host "Worker metadata did not appear on $($Worker.Name). Check controller logs." -ForegroundColor Red
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-11-25 14:51:22 -07:00
|
|
|
Invoke-WorkerAttach -Worker $Worker -WorkerType 'sheepit'
|
2025-11-03 12:45:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Start-AllSheepIt {
|
2025-11-05 16:19:24 -07:00
|
|
|
Initialize-SheepItCredentials
|
2025-11-25 14:51:22 -07:00
|
|
|
foreach ($worker in $workers | Where-Object { $_.Enabled }) {
|
|
|
|
|
Ensure-SheepItWorkerController -Worker $worker
|
2025-11-03 12:45:09 -07:00
|
|
|
}
|
2025-11-25 14:51:22 -07:00
|
|
|
Write-Host "All enabled SheepIt workers ensured running under controllers." -ForegroundColor Green
|
|
|
|
|
Write-Host "Use the attach option to monitor any worker." -ForegroundColor Cyan
|
|
|
|
|
Read-Host "Press Enter to continue" | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Send-SheepItCommandAll {
|
|
|
|
|
param([string]$CommandText)
|
2025-11-03 12:45:09 -07:00
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
foreach ($worker in $workers | Where-Object { $_.Enabled }) {
|
|
|
|
|
Write-Host "[$($worker.Name)] Sending '$CommandText'..." -ForegroundColor Gray
|
|
|
|
|
Invoke-WorkerAttach -Worker $worker -WorkerType 'sheepit' -CommandOnly -Command $CommandText
|
2025-11-03 12:45:09 -07:00
|
|
|
}
|
2025-11-25 14:51:22 -07:00
|
|
|
Write-Host "Command '$CommandText' dispatched to all enabled workers." -ForegroundColor Green
|
|
|
|
|
Read-Host "Press Enter to continue" | Out-Null
|
|
|
|
|
}
|
2025-11-03 12:45:09 -07:00
|
|
|
|
2025-11-25 14:51:22 -07:00
|
|
|
|
|
|
|
|
|
2025-11-03 12:45:09 -07:00
|
|
|
function Select-SheepItWorker {
|
|
|
|
|
while ($true) {
|
|
|
|
|
Show-Header
|
|
|
|
|
Write-Host "Select a system:" -ForegroundColor Magenta
|
|
|
|
|
|
|
|
|
|
foreach ($worker in $workers) {
|
2025-11-05 16:19:24 -07:00
|
|
|
$status = if ($worker.Enabled) { "ready" } else { "disabled" }
|
2025-11-03 12:45:09 -07:00
|
|
|
$color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
|
|
|
|
Write-Host ("{0}. {1} ({2})" -f $worker.ID, $worker.Name, $status) -ForegroundColor $color
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "B. Back" -ForegroundColor Yellow
|
|
|
|
|
$selection = Read-Host "Choose system"
|
|
|
|
|
|
|
|
|
|
if ($selection -match '^[Bb]$') {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($selection -match '^\d+$') {
|
|
|
|
|
$id = [int]$selection
|
|
|
|
|
$worker = $workers | Where-Object { $_.ID -eq $id }
|
|
|
|
|
|
|
|
|
|
if ($worker) {
|
|
|
|
|
Start-SheepItWorker -Worker $worker
|
|
|
|
|
Write-Host
|
|
|
|
|
Read-Host "Press Enter to return to the main menu" | Out-Null
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "Unknown selection." -ForegroundColor Red
|
|
|
|
|
Start-Sleep -Seconds 1
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Write-Host "Invalid input." -ForegroundColor Red
|
|
|
|
|
Start-Sleep -Seconds 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 16:19:24 -07:00
|
|
|
Initialize-SheepItCredentials
|
|
|
|
|
|
2025-11-03 12:45:09 -07:00
|
|
|
while ($true) {
|
|
|
|
|
Show-Header
|
|
|
|
|
Write-Host "Main Menu:" -ForegroundColor Magenta
|
2025-11-25 14:51:22 -07:00
|
|
|
Write-Host "1. Launch/Attach SheepIt on a single system" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "2. Ensure all ready systems are running" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "3. Pause all workers" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "4. Resume all workers" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "5. Quit all workers" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "6. Exit" -ForegroundColor Yellow
|
2025-11-03 12:45:09 -07:00
|
|
|
|
|
|
|
|
$choice = Read-Host "Select option (1-3)"
|
|
|
|
|
|
|
|
|
|
switch ($choice) {
|
|
|
|
|
'1' { Select-SheepItWorker }
|
2025-11-25 14:51:22 -07:00
|
|
|
'2' { Start-AllSheepIt }
|
|
|
|
|
'3' { Send-SheepItCommandAll -CommandText 'pause' }
|
|
|
|
|
'4' { Send-SheepItCommandAll -CommandText 'resume' }
|
|
|
|
|
'5' { Send-SheepItCommandAll -CommandText 'quit' }
|
|
|
|
|
'6' { break }
|
2025-11-03 12:45:09 -07:00
|
|
|
default {
|
|
|
|
|
Write-Host "Invalid selection." -ForegroundColor Red
|
|
|
|
|
Start-Sleep -Seconds 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "`nExiting SheepIt launcher." -ForegroundColor Cyan
|
|
|
|
|
|