Compare commits
9 Commits
7d253caeee
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
713243685a | ||
|
|
5c3a12ed0e | ||
|
|
428d9bac6b | ||
|
|
e568e02cdf | ||
| 18683b4ad0 | |||
|
|
b61baac09b | ||
|
|
2aa9061114 | ||
|
|
0b8f4f30fa | ||
|
|
3ef8b09737 |
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
<!-- Generated by SpecStory, Markdown v2.0.0 -->
|
||||
<!-- Generated by SpecStory, Markdown v2.1.0 -->
|
||||
|
||||
<!-- cursor Session a7f6e07f-fadd-406b-8735-387c0659fa23 (2025-11-03 19:27Z) -->
|
||||
|
||||
@@ -22,7 +22,7 @@ I’m crafting the remote PowerShell snippet to start SheepIt asynchronously so
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="list_dir">
|
||||
<details>
|
||||
<summary>Tool use: **list_directory** • Listed directory undefined • **0** results</summary>
|
||||
<summary>Tool use: **list_dir** • Listed directory, 0 results</summary>
|
||||
|
||||
No results found
|
||||
</details>
|
||||
@@ -48,66 +48,66 @@ I’m wrapping up the main loop with clear menu options and proper input handlin
|
||||
*** Add File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
+function Show-Header {
|
||||
+ Clear-Host
|
||||
+ Write-Host "====================================" -ForegroundColor Cyan
|
||||
+ Write-Host " UNIFIED SHEEPIT LAUNCHER" -ForegroundColor Cyan
|
||||
+ Write-Host "====================================" -ForegroundColor Cyan
|
||||
+ Write-Host "====================================" -ForegroundColor Cyan
|
||||
+ Write-Host " UNIFIED SHEEPIT LAUNCHER" -ForegroundColor Cyan
|
||||
+ Write-Host "====================================" -ForegroundColor Cyan
|
||||
+ Write-Host
|
||||
+}
|
||||
+
|
||||
+$defaultSheepItPath = "C:\Users\Nathan\Desktop\sheepit-autoupdate.exe"
|
||||
+$defaultSheepItPath = "C:\Users\Nathan\Desktop\sheepit-autoupdate.exe"
|
||||
+
|
||||
+$workers = @(
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; SheepItPath = $defaultSheepItPath; Enabled = $false },
|
||||
+ @{ ID = 2; Name = "blender-boss"; SSHArgs = "-t blender-boss"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 3; Name = "max"; SSHArgs = "-t max"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 4; Name = "masterbox"; SSHArgs = "-t masterbox"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 5; Name = "echo"; SSHArgs = "-t echo"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; SheepItPath = $defaultSheepItPath; Enabled = $true }
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; SheepItPath = $defaultSheepItPath; Enabled = $false },
|
||||
+ @{ ID = 2; Name = "blender-boss"; SSHArgs = "-t blender-boss"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 3; Name = "max"; SSHArgs = "-t max"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 4; Name = "masterbox"; SSHArgs = "-t masterbox"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 5; Name = "echo"; SSHArgs = "-t echo"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
+ @{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; SheepItPath = $defaultSheepItPath; Enabled = $true }
|
||||
+)
|
||||
+
|
||||
+function Get-RemoteSheepItCommand {
|
||||
+ param([string]$ExecutablePath)
|
||||
+
|
||||
+@"
|
||||
+`$exe = "$ExecutablePath"
|
||||
+@"
|
||||
+`$exe = "$ExecutablePath"
|
||||
+if (-not (Test-Path `$exe)) {
|
||||
+ Write-Host "SheepIt launcher not found at `$exe" -ForegroundColor Red
|
||||
+ Write-Host "SheepIt launcher not found at `$exe" -ForegroundColor Red
|
||||
+ exit 1
|
||||
+}
|
||||
+
|
||||
+`$dir = Split-Path `$exe
|
||||
+Write-Host "Launching SheepIt at `$exe" -ForegroundColor Cyan
|
||||
+Write-Host "Launching SheepIt at `$exe" -ForegroundColor Cyan
|
||||
+`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
+Write-Host "SheepIt started (PID: `$($proc.Id))." -ForegroundColor Green
|
||||
+Write-Host "SheepIt started (PID: `$($proc.Id))." -ForegroundColor Green
|
||||
+exit 0
|
||||
+"@
|
||||
+"@
|
||||
+}
|
||||
+
|
||||
+function Start-SheepItWorker {
|
||||
+ param([object]$Worker)
|
||||
+
|
||||
+ if (-not $Worker.Enabled) {
|
||||
+ Write-Host "$($Worker.Name) does not have SheepIt configured yet." -ForegroundColor Yellow
|
||||
+ Write-Host "$($Worker.Name) does not have SheepIt configured yet." -ForegroundColor Yellow
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
+ $remoteCommand = Get-RemoteSheepItCommand -ExecutablePath $Worker.SheepItPath
|
||||
+ $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($remoteCommand))
|
||||
+ $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) \"powershell -NoLogo -NoProfile -EncodedCommand $encoded\""
|
||||
+ $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) \"powershell -NoLogo -NoProfile -EncodedCommand $encoded\""
|
||||
+
|
||||
+ Write-Host "Connecting to $($Worker.Name)..." -ForegroundColor Cyan
|
||||
+ Write-Host "Connecting to $($Worker.Name)..." -ForegroundColor Cyan
|
||||
+ try {
|
||||
+ Invoke-Expression $ssh
|
||||
+ $exitCode = $LASTEXITCODE
|
||||
+
|
||||
+ if ($exitCode -eq 0) {
|
||||
+ Write-Host "SheepIt launched on $($Worker.Name)." -ForegroundColor Green
|
||||
+ Write-Host "SheepIt launched on $($Worker.Name)." -ForegroundColor Green
|
||||
+ } else {
|
||||
+ Write-Host "Launch on $($Worker.Name) exited with $exitCode." -ForegroundColor Red
|
||||
+ Write-Host "Launch on $($Worker.Name) exited with $exitCode." -ForegroundColor Red
|
||||
+ }
|
||||
+ }
|
||||
+ catch {
|
||||
+ Write-Host "Connection to $($Worker.Name) failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
+ Write-Host "Connection to $($Worker.Name) failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@@ -115,7 +115,7 @@ I’m wrapping up the main loop with clear menu options and proper input handlin
|
||||
+ $targets = $workers | Where-Object { $_.Enabled }
|
||||
+
|
||||
+ if (-not $targets) {
|
||||
+ Write-Host "No systems are ready for SheepIt." -ForegroundColor Yellow
|
||||
+ Write-Host "No systems are ready for SheepIt." -ForegroundColor Yellow
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
@@ -128,37 +128,37 @@ I’m wrapping up the main loop with clear menu options and proper input handlin
|
||||
+function Prompt-For-Worker {
|
||||
+ while ($true) {
|
||||
+ Show-Header
|
||||
+ Write-Host "Select a system:" -ForegroundColor Magenta
|
||||
+ Write-Host "Select a system:" -ForegroundColor Magenta
|
||||
+
|
||||
+ foreach ($worker in $workers) {
|
||||
+ $status = if ($worker.Enabled) { "ready" } else { "missing exe" }
|
||||
+ $color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
||||
+ Write-Host ("{0}. {1} ({2})" -f $worker.ID, $worker.Name, $status) -ForegroundColor $color
|
||||
+ $status = if ($worker.Enabled) { "ready" } else { "missing exe" }
|
||||
+ $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"
|
||||
+ Write-Host "B. Back" -ForegroundColor Yellow
|
||||
+ $selection = Read-Host "Choose system"
|
||||
+
|
||||
+ if ($selection -match '^[Bb]$') {
|
||||
+ if ($selection -match '^[Bb]$') {
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
+ if ($selection -match '^\d+$') {
|
||||
+ 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
|
||||
+ Read-Host "Press Enter to return to the main menu" | Out-Null
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
+ Write-Host "Unknown selection." -ForegroundColor Red
|
||||
+ Write-Host "Unknown selection." -ForegroundColor Red
|
||||
+ Start-Sleep -Seconds 1
|
||||
+ }
|
||||
+ else {
|
||||
+ Write-Host "Invalid input." -ForegroundColor Red
|
||||
+ Write-Host "Invalid input." -ForegroundColor Red
|
||||
+ Start-Sleep -Seconds 1
|
||||
+ }
|
||||
+ }
|
||||
@@ -166,29 +166,29 @@ I’m wrapping up the main loop with clear menu options and proper input handlin
|
||||
+
|
||||
+while ($true) {
|
||||
+ Show-Header
|
||||
+ Write-Host "Main Menu:" -ForegroundColor Magenta
|
||||
+ Write-Host "1. Launch SheepIt on a single system" -ForegroundColor Yellow
|
||||
+ Write-Host "2. Launch SheepIt on all ready systems" -ForegroundColor Yellow
|
||||
+ Write-Host "3. Exit" -ForegroundColor Yellow
|
||||
+ Write-Host "Main Menu:" -ForegroundColor Magenta
|
||||
+ Write-Host "1. Launch SheepIt on a single system" -ForegroundColor Yellow
|
||||
+ Write-Host "2. Launch SheepIt on all ready systems" -ForegroundColor Yellow
|
||||
+ Write-Host "3. Exit" -ForegroundColor Yellow
|
||||
+
|
||||
+ $choice = Read-Host "Select option (1-3)"
|
||||
+ $choice = Read-Host "Select option (1-3)"
|
||||
+
|
||||
+ switch ($choice) {
|
||||
+ '1' { Prompt-For-Worker }
|
||||
+ '2' {
|
||||
+ '1' { Prompt-For-Worker }
|
||||
+ '2' {
|
||||
+ Start-AllSheepIt
|
||||
+ Write-Host
|
||||
+ Read-Host "Press Enter to return to the main menu" | Out-Null
|
||||
+ Read-Host "Press Enter to return to the main menu" | Out-Null
|
||||
+ }
|
||||
+ '3' { break }
|
||||
+ '3' { break }
|
||||
+ default {
|
||||
+ Write-Host "Invalid selection." -ForegroundColor Red
|
||||
+ Write-Host "Invalid selection." -ForegroundColor Red
|
||||
+ Start-Sleep -Seconds 1
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+Write-Host "`nExiting SheepIt launcher." -ForegroundColor Cyan
|
||||
+Write-Host "`nExiting SheepIt launcher." -ForegroundColor Cyan
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
@@ -333,14 +333,14 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) \"powershell -NoLogo -NoProfile -EncodedCommand $encoded\""
|
||||
+ $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -EncodedCommand $encoded`""
|
||||
- $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) \"powershell -NoLogo -NoProfile -EncodedCommand $encoded\""
|
||||
+ $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -EncodedCommand $encoded`""
|
||||
@@
|
||||
-function Prompt-For-Worker {
|
||||
+function Select-SheepItWorker {
|
||||
@@
|
||||
- '1' { Prompt-For-Worker }
|
||||
+ '1' { Select-SheepItWorker }
|
||||
- '1' { Prompt-For-Worker }
|
||||
+ '1' { Select-SheepItWorker }
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
@@ -435,7 +435,7 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
+@echo off
|
||||
+title Unified SheepIt Launcher
|
||||
+echo Starting Unified SheepIt Launcher...
|
||||
+powershell -ExecutionPolicy Bypass -File "%~dp0unified_sheepit_launcher.ps1"
|
||||
+powershell -ExecutionPolicy Bypass -File "%~dp0unified_sheepit_launcher.ps1"
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
@@ -565,12 +565,12 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
-`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
-Write-Host "SheepIt started (PID: `$($proc.Id))." -ForegroundColor Green
|
||||
-Write-Host "SheepIt started (PID: `$($proc.Id))." -ForegroundColor Green
|
||||
+`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
+if (`$proc) {
|
||||
+ Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+ Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+} else {
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -680,21 +680,21 @@ I'm going to update the remote script to pause briefly and then check if the sta
|
||||
@@
|
||||
-`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
-if (`$proc) {
|
||||
- Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
- Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
-} else {
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
-}
|
||||
+`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
+if (`$proc) {
|
||||
+ Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+ Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+ Start-Sleep -Seconds 1
|
||||
+ if (Get-Process -Id `$proc.Id -ErrorAction SilentlyContinue) {
|
||||
+ Write-Host ("SheepIt is running on `$env:COMPUTERNAME (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+ Write-Host ("SheepIt is running on `$env:COMPUTERNAME (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
+ } else {
|
||||
+ Write-Host "SheepIt process exited immediately after launch." -ForegroundColor Yellow
|
||||
+ Write-Host "SheepIt process exited immediately after launch." -ForegroundColor Yellow
|
||||
+ }
|
||||
+} else {
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -775,45 +775,45 @@ This is a pretty detailed puzzle! I’m outlining an approach to enhance the rem
|
||||
@@
|
||||
-`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
-if (`$proc) {
|
||||
- Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
- Write-Host ("SheepIt started (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
- Start-Sleep -Seconds 1
|
||||
- if (Get-Process -Id `$proc.Id -ErrorAction SilentlyContinue) {
|
||||
- Write-Host ("SheepIt is running on `$env:COMPUTERNAME (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
- Write-Host ("SheepIt is running on `$env:COMPUTERNAME (PID: {0})." -f `$proc.Id) -ForegroundColor Green
|
||||
- } else {
|
||||
- Write-Host "SheepIt process exited immediately after launch." -ForegroundColor Yellow
|
||||
- Write-Host "SheepIt process exited immediately after launch." -ForegroundColor Yellow
|
||||
- }
|
||||
-} else {
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
-}
|
||||
+`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
+if (`$proc) {
|
||||
+ `$id = `$proc.Id
|
||||
+ Write-Host ("SheepIt launcher started (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
+ Write-Host ("SheepIt launcher started (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
+ Start-Sleep -Seconds 1
|
||||
+
|
||||
+ if (Get-Process -Id `$id -ErrorAction SilentlyContinue) {
|
||||
+ Write-Host ("SheepIt launcher is still running (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
+ Write-Host ("SheepIt launcher is still running (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
+ }
|
||||
+ else {
|
||||
+ try {
|
||||
+ `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=`$id"
|
||||
+ `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=`$id"
|
||||
+ if (`$children) {
|
||||
+ Write-Host "Launcher handed off to:" -ForegroundColor Green
|
||||
+ Write-Host "Launcher handed off to:" -ForegroundColor Green
|
||||
+ foreach (`$child in `$children) {
|
||||
+ Write-Host (" {0} (PID: {1})" -f `$child.Name, `$child.ProcessId) -ForegroundColor Green
|
||||
+ Write-Host (" {0} (PID: {1})" -f `$child.Name, `$child.ProcessId) -ForegroundColor Green
|
||||
+ }
|
||||
+ }
|
||||
+ else {
|
||||
+ Write-Host "Launcher exited immediately after starting. Check Task Manager for SheepIt/Java processes." -ForegroundColor Yellow
|
||||
+ Write-Host "Launcher exited immediately after starting. Check Task Manager for SheepIt/Java processes." -ForegroundColor Yellow
|
||||
+ }
|
||||
+ }
|
||||
+ catch {
|
||||
+ Write-Host "Unable to inspect child processes." -ForegroundColor Yellow
|
||||
+ Write-Host "Unable to inspect child processes." -ForegroundColor Yellow
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+else {
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+ Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
+}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -948,37 +948,37 @@ Parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": "# SheepIt Jar Upgrade Plan\n\n1. Gather Render Key\n - Add session-level prompt storing the render key (secure string optional) so remote commands can inject `--renderkey`.\n\n2. Update Remote SheepIt Command\n - Replace executable launch with PowerShell that ensures `%APPDATA%\\sheepit` exists, downloads `client-latest.jar` to `sheepit-client.jar`, and runs `java -jar` with `--ui text --stdout --verbose -gpu optix 0 --renderkey <key>`.\n\n3. Make Launch Interactive\n - Adjust `Start-SheepItWorker` / `Start-AllSheepIt` to open each worker in its own terminal tab (like the Flamenco launcher) so the SSH session stays open after the jar exits, allowing CLI interaction.",
|
||||
"name": "she",
|
||||
"todos": [
|
||||
"plan": "# SheepIt Jar Upgrade Plan\n\n1. Gather Render Key\n - Add session-level prompt storing the render key (secure string optional) so remote commands can inject `--renderkey`.\n\n2. Update Remote SheepIt Command\n - Replace executable launch with PowerShell that ensures `%APPDATA%\\sheepit` exists, downloads `client-latest.jar` to `sheepit-client.jar`, and runs `java -jar` with `--ui text --stdout --verbose -gpu optix 0 --renderkey <key>`.\n\n3. Make Launch Interactive\n - Adjust `Start-SheepItWorker` / `Start-AllSheepIt` to open each worker in its own terminal tab (like the Flamenco launcher) so the SSH session stays open after the jar exits, allowing CLI interaction.",
|
||||
"name": "she",
|
||||
"todos": [
|
||||
{
|
||||
"content": "Add render key prompt/state to unified_sheepit_launcher.ps1",
|
||||
"status": "pending",
|
||||
"id": "renderkey-prompt"
|
||||
"content": "Add render key prompt/state to unified_sheepit_launcher.ps1",
|
||||
"status": "pending",
|
||||
"id": "renderkey-prompt"
|
||||
},
|
||||
{
|
||||
"content": "Implement remote script to download jar to %APPDATA%\\sheepit and run java with requested flags",
|
||||
"status": "pending",
|
||||
"id": "remote-jar-command"
|
||||
"content": "Implement remote script to download jar to %APPDATA%\\sheepit and run java with requested flags",
|
||||
"status": "pending",
|
||||
"id": "remote-jar-command"
|
||||
},
|
||||
{
|
||||
"content": "Update worker launch functions to run ssh in interactive tabs retaining session control",
|
||||
"status": "pending",
|
||||
"id": "interactive-launch"
|
||||
"content": "Update worker launch functions to run ssh in interactive tabs retaining session control",
|
||||
"status": "pending",
|
||||
"id": "interactive-launch"
|
||||
}
|
||||
],
|
||||
"overview": "Switch SheepIt launcher to fetch the jar client per worker and run it interactively with render key support."
|
||||
"overview": "Switch SheepIt launcher to fetch the jar client per worker and run it interactively with render key support."
|
||||
}
|
||||
```
|
||||
Additional data:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "accepted",
|
||||
"reviewData": {
|
||||
"status": "Requested",
|
||||
"selectedOption": "none",
|
||||
"isShowingInput": false
|
||||
"status": "accepted",
|
||||
"reviewData": {
|
||||
"status": "Requested",
|
||||
"selectedOption": "none",
|
||||
"isShowingInput": false
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -986,22 +986,22 @@ Result:
|
||||
|
||||
```json
|
||||
{
|
||||
"accepted": {
|
||||
"finalTodos": [
|
||||
"accepted": {
|
||||
"finalTodos": [
|
||||
{
|
||||
"content": "Add render key prompt/state to unified_sheepit_launcher.ps1",
|
||||
"status": "pending",
|
||||
"id": "renderkey-prompt"
|
||||
"content": "Add render key prompt/state to unified_sheepit_launcher.ps1",
|
||||
"status": "pending",
|
||||
"id": "renderkey-prompt"
|
||||
},
|
||||
{
|
||||
"content": "Implement remote script to download jar to %APPDATA%\\sheepit and run java with requested flags",
|
||||
"status": "pending",
|
||||
"id": "remote-jar-command"
|
||||
"content": "Implement remote script to download jar to %APPDATA%\\sheepit and run java with requested flags",
|
||||
"status": "pending",
|
||||
"id": "remote-jar-command"
|
||||
},
|
||||
{
|
||||
"content": "Update worker launch functions to run ssh in interactive tabs retaining session control",
|
||||
"status": "pending",
|
||||
"id": "interactive-launch"
|
||||
"content": "Update worker launch functions to run ssh in interactive tabs retaining session control",
|
||||
"status": "pending",
|
||||
"id": "interactive-launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1044,7 +1044,7 @@ To-do's from the plan have already been created. Do not create them again. Mark
|
||||
|
||||
_**Agent (model gpt-5-codex, mode Agent)**_
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="todo_write">
|
||||
<tool-use data-tool-type="task" data-tool-name="todo_write">
|
||||
<details><summary>Tool use: **todo_write** • Todo List</summary>
|
||||
|
||||
- [-] Add render key prompt/state to unified_sheepit_launcher.ps1
|
||||
@@ -1089,37 +1089,37 @@ I'm planning how to generate per-worker session scripts that run SSH with the en
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
-$defaultSheepItPath = "C:\Users\Nathan\Desktop\sheepit-autoupdate.exe"
|
||||
-$defaultSheepItPath = "C:\Users\Nathan\Desktop\sheepit-autoupdate.exe"
|
||||
-
|
||||
-$workers = @(
|
||||
- @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; SheepItPath = $defaultSheepItPath; Enabled = $false },
|
||||
- @{ ID = 2; Name = "blender-boss"; SSHArgs = "-t blender-boss"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 3; Name = "max"; SSHArgs = "-t max"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 4; Name = "masterbox"; SSHArgs = "-t masterbox"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 5; Name = "echo"; SSHArgs = "-t echo"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; SheepItPath = $defaultSheepItPath; Enabled = $true }
|
||||
- @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; SheepItPath = $defaultSheepItPath; Enabled = $false },
|
||||
- @{ ID = 2; Name = "blender-boss"; SSHArgs = "-t blender-boss"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 3; Name = "max"; SSHArgs = "-t max"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 4; Name = "masterbox"; SSHArgs = "-t masterbox"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 5; Name = "echo"; SSHArgs = "-t echo"; SheepItPath = $defaultSheepItPath; Enabled = $true },
|
||||
- @{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; SheepItPath = $defaultSheepItPath; Enabled = $true }
|
||||
-)
|
||||
+$SheepItJarUrl = 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
+$SheepItJarUrl = 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
+$script:SheepItUsername = $null
|
||||
+$script:SheepItRenderKey = $null
|
||||
+
|
||||
+function Initialize-SheepItCredentials {
|
||||
+ if (-not $script:SheepItUsername) {
|
||||
+ $script:SheepItUsername = Read-Host "SheepIt username" -Default "RaincloudTheDragon"
|
||||
+ $script:SheepItUsername = Read-Host "SheepIt username" -Default "RaincloudTheDragon"
|
||||
+ }
|
||||
+
|
||||
+ if (-not $script:SheepItRenderKey) {
|
||||
+ $script:SheepItRenderKey = Read-Host "SheepIt render key" -Default "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
+ $script:SheepItRenderKey = Read-Host "SheepIt render key" -Default "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+$workers = @(
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $false },
|
||||
+ @{ 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 }
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $false },
|
||||
+ @{ 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 }
|
||||
+)
|
||||
*** End Patch
|
||||
```
|
||||
@@ -1146,47 +1146,47 @@ I'm planning how to generate per-worker session scripts that run SSH with the en
|
||||
-function Get-RemoteSheepItCommand {
|
||||
- param([string]$ExecutablePath)
|
||||
-
|
||||
-@"
|
||||
-`$exe = "$ExecutablePath"
|
||||
-@"
|
||||
-`$exe = "$ExecutablePath"
|
||||
-if (-not (Test-Path `$exe)) {
|
||||
- Write-Host "SheepIt launcher not found at `$exe" -ForegroundColor Red
|
||||
- Write-Host "SheepIt launcher not found at `$exe" -ForegroundColor Red
|
||||
- exit 1
|
||||
-}
|
||||
-
|
||||
-`$dir = Split-Path `$exe
|
||||
-Write-Host "Launching SheepIt at `$exe" -ForegroundColor Cyan
|
||||
-Write-Host "Launching SheepIt at `$exe" -ForegroundColor Cyan
|
||||
-`$proc = Start-Process -FilePath `$exe -WorkingDirectory `$dir -WindowStyle Minimized -PassThru
|
||||
-if (`$proc) {
|
||||
- `$id = `$proc.Id
|
||||
- Write-Host ("SheepIt launcher started (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
- Write-Host ("SheepIt launcher started (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
- Start-Sleep -Seconds 1
|
||||
-
|
||||
- if (Get-Process -Id `$id -ErrorAction SilentlyContinue) {
|
||||
- Write-Host ("SheepIt launcher is still running (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
- Write-Host ("SheepIt launcher is still running (PID: {0})." -f `$id) -ForegroundColor Green
|
||||
- }
|
||||
- else {
|
||||
- try {
|
||||
- `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=`$id"
|
||||
- `$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=`$id"
|
||||
- if (`$children) {
|
||||
- Write-Host "Launcher handed off to:" -ForegroundColor Green
|
||||
- Write-Host "Launcher handed off to:" -ForegroundColor Green
|
||||
- foreach (`$child in `$children) {
|
||||
- Write-Host (" {0} (PID: {1})" -f `$child.Name, `$child.ProcessId) -ForegroundColor Green
|
||||
- Write-Host (" {0} (PID: {1})" -f `$child.Name, `$child.ProcessId) -ForegroundColor Green
|
||||
- }
|
||||
- }
|
||||
- else {
|
||||
- Write-Host "Launcher exited immediately after starting. Check Task Manager for SheepIt/Java processes." -ForegroundColor Yellow
|
||||
- Write-Host "Launcher exited immediately after starting. Check Task Manager for SheepIt/Java processes." -ForegroundColor Yellow
|
||||
- }
|
||||
- }
|
||||
- catch {
|
||||
- Write-Host "Unable to inspect child processes." -ForegroundColor Yellow
|
||||
- Write-Host "Unable to inspect child processes." -ForegroundColor Yellow
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-else {
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
- Write-Host "SheepIt start confirmed." -ForegroundColor Green
|
||||
-}
|
||||
-exit 0
|
||||
-"@
|
||||
-"@
|
||||
-}
|
||||
+function Get-RemoteSheepItCommand {
|
||||
+ param(
|
||||
@@ -1194,36 +1194,36 @@ I'm planning how to generate per-worker session scripts that run SSH with the en
|
||||
+ [string]$Username
|
||||
+ )
|
||||
+
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
+
|
||||
+@"
|
||||
+`$ErrorActionPreference = 'Stop'
|
||||
+@"
|
||||
+`$ErrorActionPreference = 'Stop'
|
||||
+
|
||||
+try {
|
||||
+ `$appData = [Environment]::GetFolderPath('ApplicationData')
|
||||
+ `$sheepDir = Join-Path `$appData 'sheepit'
|
||||
+ `$appData = [Environment]::GetFolderPath('ApplicationData')
|
||||
+ `$sheepDir = Join-Path `$appData 'sheepit'
|
||||
+ if (-not (Test-Path `$sheepDir)) {
|
||||
+ New-Item -Path `$sheepDir -ItemType Directory -Force | Out-Null
|
||||
+ }
|
||||
+
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
+ Write-Host "Downloading SheepIt client..." -ForegroundColor Cyan
|
||||
+ Invoke-WebRequest -Uri '$SheepItJarUrl' -OutFile `$jarPath -UseBasicParsing
|
||||
+ Write-Host "Download complete." -ForegroundColor Green
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
+ Write-Host "Downloading SheepIt client..." -ForegroundColor Cyan
|
||||
+ Invoke-WebRequest -Uri '$SheepItJarUrl' -OutFile `$jarPath -UseBasicParsing
|
||||
+ Write-Host "Download complete." -ForegroundColor Green
|
||||
+
|
||||
+ Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
+ if ('${safeUser}') {
|
||||
+ Write-Host "Authenticating as ${safeUser} via render key." -ForegroundColor Yellow
|
||||
+ Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
+ if ('${safeUser}') {
|
||||
+ Write-Host "Authenticating as ${safeUser} via render key." -ForegroundColor Yellow
|
||||
+ }
|
||||
+
|
||||
+ Set-Location `$sheepDir
|
||||
+ & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+}
|
||||
+catch {
|
||||
+ Write-Host "Error: `$($_.Exception.Message)" -ForegroundColor Red
|
||||
+ Write-Host "Error: `$($_.Exception.Message)" -ForegroundColor Red
|
||||
+}
|
||||
+"@
|
||||
+"@
|
||||
+}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -1269,32 +1269,32 @@ The model made no changes to the file.
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- $safeKey = $RenderKey -replace "'", "''"
|
||||
- $safeUser = $Username -replace "'", "''"
|
||||
- $safeKey = $RenderKey -replace "'", "''"
|
||||
- $safeUser = $Username -replace "'", "''"
|
||||
-
|
||||
-@"
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
-@"
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
+ $userLine = if ([string]::IsNullOrWhiteSpace($Username)) {
|
||||
+ ''
|
||||
+ ''
|
||||
+ } else {
|
||||
+ " Write-Host 'Authenticating as ${safeUser} via render key.' -ForegroundColor Yellow`n"
|
||||
+ " Write-Host 'Authenticating as ${safeUser} via render key.' -ForegroundColor Yellow`n"
|
||||
+ }
|
||||
+
|
||||
+@"
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
+@"
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
|
||||
try {
|
||||
@@
|
||||
- Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
- if ('${safeUser}') {
|
||||
- Write-Host "Authenticating as ${safeUser} via render key." -ForegroundColor Yellow
|
||||
- Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
- if ('${safeUser}') {
|
||||
- Write-Host "Authenticating as ${safeUser} via render key." -ForegroundColor Yellow
|
||||
- }
|
||||
-
|
||||
- Set-Location `$sheepDir
|
||||
+ Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
+ Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
+${userLine} Set-Location `$sheepDir
|
||||
& java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
& java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
}
|
||||
catch {
|
||||
*** End Patch
|
||||
@@ -1323,27 +1323,27 @@ The model made no changes to the file.
|
||||
- param([object]$Worker)
|
||||
-
|
||||
- if (-not $Worker.Enabled) {
|
||||
- Write-Host "$($Worker.Name) does not have SheepIt configured yet." -ForegroundColor Yellow
|
||||
- Write-Host "$($Worker.Name) does not have SheepIt configured yet." -ForegroundColor Yellow
|
||||
- return
|
||||
- }
|
||||
-
|
||||
- $remoteCommand = Get-RemoteSheepItCommand -ExecutablePath $Worker.SheepItPath
|
||||
- $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($remoteCommand))
|
||||
- $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -EncodedCommand $encoded`""
|
||||
- $ssh = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -EncodedCommand $encoded`""
|
||||
-
|
||||
- Write-Host "Connecting to $($Worker.Name)..." -ForegroundColor Cyan
|
||||
- Write-Host "Connecting to $($Worker.Name)..." -ForegroundColor Cyan
|
||||
- try {
|
||||
- Invoke-Expression $ssh
|
||||
- $exitCode = $LASTEXITCODE
|
||||
-
|
||||
- if ($exitCode -eq 0) {
|
||||
- Write-Host "SheepIt launched on $($Worker.Name)." -ForegroundColor Green
|
||||
- Write-Host "SheepIt launched on $($Worker.Name)." -ForegroundColor Green
|
||||
- } else {
|
||||
- Write-Host "Launch on $($Worker.Name) exited with $exitCode." -ForegroundColor Red
|
||||
- Write-Host "Launch on $($Worker.Name) exited with $exitCode." -ForegroundColor Red
|
||||
- }
|
||||
- }
|
||||
- catch {
|
||||
- Write-Host "Connection to $($Worker.Name) failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
- Write-Host "Connection to $($Worker.Name) failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
- }
|
||||
-}
|
||||
+function New-SheepItSessionScript {
|
||||
@@ -1352,12 +1352,12 @@ The model made no changes to the file.
|
||||
+ [string]$EncodedCommand
|
||||
+ )
|
||||
+
|
||||
+@"
|
||||
+Write-Host 'Connecting to $($Worker.Name)...' -ForegroundColor Cyan
|
||||
+ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand`"
|
||||
+Write-Host "`nSSH session ended." -ForegroundColor Yellow
|
||||
+Read-Host "Press Enter to close"
|
||||
+"@
|
||||
+@"
|
||||
+Write-Host 'Connecting to $($Worker.Name)...' -ForegroundColor Cyan
|
||||
+ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand`"
|
||||
+Write-Host "`nSSH session ended." -ForegroundColor Yellow
|
||||
+Read-Host "Press Enter to close"
|
||||
+"@
|
||||
+}
|
||||
+
|
||||
+function Launch-SheepItTab {
|
||||
@@ -1366,13 +1366,13 @@ The model made no changes to the file.
|
||||
+ [string]$Content
|
||||
+ )
|
||||
+
|
||||
+ $tempScript = [System.IO.Path]::GetTempFileName() + '.ps1'
|
||||
+ $tempScript = [System.IO.Path]::GetTempFileName() + '.ps1'
|
||||
+ Set-Content -Path $tempScript -Value $Content -Encoding UTF8
|
||||
+
|
||||
+ if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
|
||||
+ Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ } else {
|
||||
+ Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@@ -1380,7 +1380,7 @@ The model made no changes to the file.
|
||||
+ param([object]$Worker)
|
||||
+
|
||||
+ if (-not $Worker.Enabled) {
|
||||
+ Write-Host "$($Worker.Name) is not enabled for SheepIt." -ForegroundColor Yellow
|
||||
+ Write-Host "$($Worker.Name) is not enabled for SheepIt." -ForegroundColor Yellow
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
@@ -1389,10 +1389,10 @@ The model made no changes to the file.
|
||||
+ $remoteCommand = Get-RemoteSheepItCommand -RenderKey $script:SheepItRenderKey -Username $script:SheepItUsername
|
||||
+ $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($remoteCommand))
|
||||
+ $sessionScript = New-SheepItSessionScript -Worker $Worker -EncodedCommand $encoded
|
||||
+ $title = "$($Worker.Name) - SheepIt"
|
||||
+ $title = "$($Worker.Name) - SheepIt"
|
||||
+
|
||||
+ Launch-SheepItTab -Title $title -Content $sessionScript
|
||||
+ Write-Host "Opened SheepIt session for $($Worker.Name) in a new terminal tab." -ForegroundColor Green
|
||||
+ Write-Host "Opened SheepIt session for $($Worker.Name) in a new terminal tab." -ForegroundColor Green
|
||||
+}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -1417,12 +1417,12 @@ The model made no changes to the file.
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- foreach ($worker in $workers) {
|
||||
- $status = if ($worker.Enabled) { "ready" } else { "missing exe" }
|
||||
- $color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
||||
- $status = if ($worker.Enabled) { "ready" } else { "missing exe" }
|
||||
- $color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
||||
+ foreach ($worker in $workers) {
|
||||
+ $status = if ($worker.Enabled) { "ready" } else { "disabled" }
|
||||
+ $color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
||||
Write-Host ("{0}. {1} ({2})" -f $worker.ID, $worker.Name, $status) -ForegroundColor $color
|
||||
+ $status = if ($worker.Enabled) { "ready" } else { "disabled" }
|
||||
+ $color = if ($worker.Enabled) { "Green" } else { "DarkYellow" }
|
||||
Write-Host ("{0}. {1} ({2})" -f $worker.ID, $worker.Name, $status) -ForegroundColor $color
|
||||
}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -1500,7 +1500,7 @@ The model made no changes to the file.
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="grep" data-tool-name="grep">
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "SheepItPath" in "unified_sheepit_launcher.ps1" • 0 matches</summary>
|
||||
|
||||
@@ -1560,14 +1560,14 @@ Lint errors for `unified_sheepit_launcher.ps1`
|
||||
+function Start-SheepItTab {
|
||||
@@
|
||||
- if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
|
||||
- Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
- Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
- } else {
|
||||
- Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
- Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
- }
|
||||
+ if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
|
||||
+ Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ } else {
|
||||
+ Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
+ }
|
||||
}
|
||||
@@
|
||||
@@ -1608,7 +1608,7 @@ Lint results
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="todo_write">
|
||||
<tool-use data-tool-type="task" data-tool-name="todo_write">
|
||||
<details><summary>Tool use: **todo_write** • Todo List</summary>
|
||||
|
||||
- [x] Add render key prompt/state to unified_sheepit_launcher.ps1
|
||||
@@ -1625,7 +1625,7 @@ Lint results
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="todo_write">
|
||||
<tool-use data-tool-type="task" data-tool-name="todo_write">
|
||||
<details><summary>Tool use: **todo_write** • Todo List</summary>
|
||||
|
||||
- [x] Add render key prompt/state to unified_sheepit_launcher.ps1
|
||||
@@ -1642,7 +1642,7 @@ Lint results
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="todo_write">
|
||||
<tool-use data-tool-type="task" data-tool-name="todo_write">
|
||||
<details><summary>Tool use: **todo_write** • Todo List</summary>
|
||||
|
||||
- [x] Add render key prompt/state to unified_sheepit_launcher.ps1
|
||||
@@ -1723,9 +1723,9 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
-$workers = @(
|
||||
- @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $false },
|
||||
- @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $false },
|
||||
+$workers = @(
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $true },
|
||||
+ @{ ID = 1; Name = "i9kf"; SSHArgs = "-t i9kf"; Enabled = $true },
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
@@ -1798,13 +1798,13 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
@@
|
||||
function Initialize-SheepItCredentials {
|
||||
if (-not $script:SheepItUsername) {
|
||||
- $script:SheepItUsername = Read-Host "SheepIt username" -Default "RaincloudTheDragon"
|
||||
+ $script:SheepItUsername = "RaincloudTheDragon"
|
||||
- $script:SheepItUsername = Read-Host "SheepIt username" -Default "RaincloudTheDragon"
|
||||
+ $script:SheepItUsername = "RaincloudTheDragon"
|
||||
}
|
||||
|
||||
if (-not $script:SheepItRenderKey) {
|
||||
- $script:SheepItRenderKey = Read-Host "SheepIt render key" -Default "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
+ $script:SheepItRenderKey = "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
- $script:SheepItRenderKey = Read-Host "SheepIt render key" -Default "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
+ $script:SheepItRenderKey = "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
}
|
||||
}
|
||||
*** End Patch
|
||||
@@ -1890,14 +1890,14 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
- & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
}
|
||||
catch {
|
||||
- Write-Host "Error: `$($_.Exception.Message)" -ForegroundColor Red
|
||||
+ Write-Host ('Error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
||||
- Write-Host "Error: `$($_.Exception.Message)" -ForegroundColor Red
|
||||
+ Write-Host ('Error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
||||
}
|
||||
"@
|
||||
"@
|
||||
}
|
||||
*** End Patch
|
||||
```
|
||||
@@ -1975,53 +1975,53 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
-$SheepItJarUrl = 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
-$SheepItJarUrl = 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
+$SheepItJarUrls = @(
|
||||
+ 'https://www.sheepit-renderfarm.com/media/applet/client-latest.php',
|
||||
+ 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
+ 'https://www.sheepit-renderfarm.com/media/applet/client-latest.php',
|
||||
+ 'https://www.sheepit-renderfarm.com/media/applet/client-latest.jar'
|
||||
+)
|
||||
@@
|
||||
- $safeKey = $RenderKey -replace "'", "''"
|
||||
- $safeUser = $Username -replace "'", "''"
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
- $safeKey = $RenderKey -replace "'", "''"
|
||||
- $safeUser = $Username -replace "'", "''"
|
||||
+ $safeKey = $RenderKey -replace "'", "''"
|
||||
+ $safeUser = $Username -replace "'", "''"
|
||||
$userLine = if ([string]::IsNullOrWhiteSpace($Username)) {
|
||||
''
|
||||
''
|
||||
} else {
|
||||
" Write-Host 'Authenticating as ${safeUser} via render key.' -ForegroundColor Yellow`n"
|
||||
" Write-Host 'Authenticating as ${safeUser} via render key.' -ForegroundColor Yellow`n"
|
||||
}
|
||||
+
|
||||
+ $urlLiteral = '@(' + (($SheepItJarUrls | ForEach-Object { "'$_'" }) -join ', ') + ')'
|
||||
+ $urlLiteral = '@(' + (($SheepItJarUrls | ForEach-Object { "'$_'" }) -join ', ') + ')'
|
||||
|
||||
@"
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
@"
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
|
||||
try {
|
||||
@@
|
||||
- `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
- Write-Host "Downloading SheepIt client..." -ForegroundColor Cyan
|
||||
- Invoke-WebRequest -Uri '$SheepItJarUrl' -OutFile `$jarPath -UseBasicParsing
|
||||
- Write-Host "Download complete." -ForegroundColor Green
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
- `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
- Write-Host "Downloading SheepIt client..." -ForegroundColor Cyan
|
||||
- Invoke-WebRequest -Uri '$SheepItJarUrl' -OutFile `$jarPath -UseBasicParsing
|
||||
- Write-Host "Download complete." -ForegroundColor Green
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
+ `$urls = $urlLiteral
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+ `$downloaded = $false
|
||||
+
|
||||
+ foreach (`$url in `$urls) {
|
||||
+ Write-Host "Downloading SheepIt client from `$url..." -ForegroundColor Cyan
|
||||
+ 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
|
||||
+ Write-Host "Download complete." -ForegroundColor Green
|
||||
+ break
|
||||
+ }
|
||||
+ catch {
|
||||
+ Write-Host ("Download failed from {0}: {1}" -f `$url, `$_.Exception.Message) -ForegroundColor Yellow
|
||||
+ 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.'
|
||||
+ throw 'Unable to download SheepIt client from any known URL.'
|
||||
+ }
|
||||
*** End Patch
|
||||
```
|
||||
@@ -2134,9 +2134,9 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
- `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
- `$downloaded = $false
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+ `$downloaded = `$false
|
||||
@@
|
||||
- `$downloaded = $true
|
||||
@@ -2458,57 +2458,57 @@ I'm updating the remote script to skip downloading the jar if it already exists,
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
- `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
- `$urls = $urlLiteral
|
||||
- `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
- `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
- `$downloaded = `$false
|
||||
-
|
||||
- foreach (`$url in `$urls) {
|
||||
- Write-Host "Downloading SheepIt client from `$url..." -ForegroundColor Cyan
|
||||
- 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
|
||||
- Write-Host "Download complete." -ForegroundColor Green
|
||||
- break
|
||||
- }
|
||||
- catch {
|
||||
- Write-Host ("Download failed from {0}: {1}" -f `$url, `$_.Exception.Message) -ForegroundColor Yellow
|
||||
- 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.'
|
||||
- throw 'Unable to download SheepIt client from any known URL.'
|
||||
- }
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
+ `$jarPath = Join-Path `$sheepDir 'sheepit-client.jar'
|
||||
+ `$urls = $urlLiteral
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+ `$headers = @{ 'User-Agent' = 'Mozilla/5.0' }
|
||||
+
|
||||
+ if (Test-Path `$jarPath) {
|
||||
+ Write-Host "SheepIt client already present at `$jarPath. Skipping download." -ForegroundColor Green
|
||||
+ 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
|
||||
+ 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
|
||||
+ Write-Host "Download complete." -ForegroundColor Green
|
||||
+ break
|
||||
+ }
|
||||
+ catch {
|
||||
+ Write-Host ("Download failed from {0}: {1}" -f `$url, `$_.Exception.Message) -ForegroundColor Yellow
|
||||
+ 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.'
|
||||
+ throw 'Unable to download SheepIt client from any known URL.'
|
||||
+ }
|
||||
+ }
|
||||
@@
|
||||
- & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 --renderkey '${safeKey}'
|
||||
- & java -jar `$jarPath --ui text --stdout --verbose -gpu optix 0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 --renderkey '${safeKey}'
|
||||
}
|
||||
catch {
|
||||
*** End Patch
|
||||
@@ -2784,8 +2784,8 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Begin Patch
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\unified_sheepit_launcher.ps1
|
||||
@@
|
||||
- & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 -login '${safeUser}' -password '${safeKey}'
|
||||
- & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 --renderkey '${safeKey}'
|
||||
+ & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 -login '${safeUser}' -password '${safeKey}'
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Generated by SpecStory, Markdown v2.0.0 -->
|
||||
<!-- Generated by SpecStory, Markdown v2.1.0 -->
|
||||
|
||||
<!-- cursor Session fe037dc9-a2ad-48b1-84ac-2bb4d322c421 (2025-11-06 17:28Z) -->
|
||||
|
||||
@@ -32,109 +32,109 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js
|
||||
@@
|
||||
{
|
||||
key: "render_output_root",
|
||||
type: "string",
|
||||
subtype: "dir_path",
|
||||
key: "render_output_root",
|
||||
type: "string",
|
||||
subtype: "dir_path",
|
||||
required: false,
|
||||
visible: "submission",
|
||||
eval: "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
|
||||
visible: "submission",
|
||||
eval: "__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
|
||||
evalInfo: {
|
||||
showLinkButton: true,
|
||||
description: "Auto-detect the project's Renders folder"
|
||||
description: "Auto-detect the project's Renders folder"
|
||||
},
|
||||
description: "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically."
|
||||
description: "Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically."
|
||||
},
|
||||
- {
|
||||
- key: "daily_path",
|
||||
- type: "string",
|
||||
- key: "daily_path",
|
||||
- type: "string",
|
||||
- required: false,
|
||||
- visible: "submission",
|
||||
- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
|
||||
- visible: "submission",
|
||||
- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
|
||||
- evalInfo: {
|
||||
- showLinkButton: true,
|
||||
- description: "Auto-fill with today's daily folder name"
|
||||
- description: "Auto-fill with today's daily folder name"
|
||||
- },
|
||||
- description: "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date."
|
||||
- description: "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date."
|
||||
- },
|
||||
{
|
||||
key: "use_submodule",
|
||||
label: "Use Submodule",
|
||||
type: "bool",
|
||||
key: "use_submodule",
|
||||
label: "Use Submodule",
|
||||
type: "bool",
|
||||
required: false,
|
||||
@@
|
||||
{
|
||||
key: "render_output_path",
|
||||
type: "string",
|
||||
subtype: "file_path",
|
||||
key: "render_output_path",
|
||||
type: "string",
|
||||
subtype: "file_path",
|
||||
editable: false,
|
||||
- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))",
|
||||
+ eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
description: "Final file path of where render output will be saved"
|
||||
- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))",
|
||||
+ eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
description: "Final file path of where render output will be saved"
|
||||
},
|
||||
@@
|
||||
- if (!settings.daily_path || String(settings.daily_path).trim() === '') {
|
||||
- if (!settings.daily_path || String(settings.daily_path).trim() === '') {
|
||||
- const createdDate = job && job.created ? new Date(job.created) : new Date();
|
||||
- settings.daily_path = formatDailyYYMMDD(createdDate);
|
||||
- }
|
||||
const recomposed = computeAutoRenderOutputPath(job);
|
||||
if (recomposed) settings.render_output_path = recomposed;
|
||||
@@
|
||||
- const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings);
|
||||
- const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings);
|
||||
-
|
||||
- // Resolve render root
|
||||
- let renderRoot = null;
|
||||
- if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
- renderRoot = ("" + settings.render_output_root).trim();
|
||||
- if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
- renderRoot = ("" + settings.render_output_root).trim();
|
||||
- } else if (projectRoot) {
|
||||
- renderRoot = path.join(projectRoot, 'Renders');
|
||||
- renderRoot = path.join(projectRoot, 'Renders');
|
||||
- } else {
|
||||
- // Fallback to the blendfile's directory Renders sibling
|
||||
- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
- // Fallback to the blendfile's directory Renders sibling
|
||||
- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
- }
|
||||
-
|
||||
- // Resolve daily path
|
||||
- let daily = null;
|
||||
- if (settings.daily_path && ("" + settings.daily_path).trim()) {
|
||||
- daily = ("" + settings.daily_path).trim();
|
||||
- if (settings.daily_path && ("" + settings.daily_path).trim()) {
|
||||
- daily = ("" + settings.daily_path).trim();
|
||||
- } else {
|
||||
- const createdDate = job && job.created ? new Date(job.created) : new Date();
|
||||
- daily = formatDailyYYMMDD(createdDate);
|
||||
- }
|
||||
- const jobname = (job && job.name) ? job.name : path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
+ const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings);
|
||||
- const jobname = (job && job.name) ? job.name : path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
+ const submodule = (settings.submodule && ("" + settings.submodule).trim()) ? ("" + settings.submodule).trim() : detectSubmodule(settings);
|
||||
+
|
||||
+ // Resolve render root
|
||||
+ let renderRoot = null;
|
||||
+ if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
+ renderRoot = ("" + settings.render_output_root).trim();
|
||||
+ if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
+ renderRoot = ("" + settings.render_output_root).trim();
|
||||
+ } else if (projectRoot) {
|
||||
+ renderRoot = path.join(projectRoot, 'Renders');
|
||||
+ renderRoot = path.join(projectRoot, 'Renders');
|
||||
+ } else {
|
||||
+ // Fallback to the blendfile's directory Renders sibling
|
||||
+ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
+ // Fallback to the blendfile's directory Renders sibling
|
||||
+ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
+ }
|
||||
+
|
||||
+ const blendname = path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
+ const blendname = path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
|
||||
print('AutoPath: blendfile=', settings.blendfile);
|
||||
print('AutoPath: projectRoot=', projectRoot);
|
||||
print('AutoPath: renderRoot=', renderRoot);
|
||||
print('AutoPath: submodule=', submodule);
|
||||
- print('AutoPath: daily=', daily);
|
||||
- print('AutoPath: jobname=', jobname);
|
||||
+ print('AutoPath: blendname=', blendname);
|
||||
print('AutoPath: blendfile=', settings.blendfile);
|
||||
print('AutoPath: projectRoot=', projectRoot);
|
||||
print('AutoPath: renderRoot=', renderRoot);
|
||||
print('AutoPath: submodule=', submodule);
|
||||
- print('AutoPath: daily=', daily);
|
||||
- print('AutoPath: jobname=', jobname);
|
||||
+ print('AutoPath: blendname=', blendname);
|
||||
|
||||
const parts = [renderRoot];
|
||||
if (submodule) parts.push(submodule);
|
||||
- parts.push(daily, jobname, `${jobname}_######`);
|
||||
+ parts.push(blendname, `${blendname}_######`);
|
||||
const finalPath = path.join.apply(path, parts);
|
||||
print('AutoPath: finalPath=', finalPath);
|
||||
print('AutoPath: finalPath=', finalPath);
|
||||
return finalPath;
|
||||
}
|
||||
@@
|
||||
-function formatDailyYYMMDD(dateObj) {
|
||||
- const pad2 = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
- const pad2 = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
- const yy = (dateObj.getFullYear() % 100);
|
||||
- const mm = dateObj.getMonth() + 1;
|
||||
- const dd = dateObj.getDate();
|
||||
@@ -164,45 +164,45 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js
|
||||
@@
|
||||
{
|
||||
key: 'render_output_root',
|
||||
type: 'string',
|
||||
subtype: 'dir_path',
|
||||
key: 'render_output_root',
|
||||
type: 'string',
|
||||
subtype: 'dir_path',
|
||||
required: false,
|
||||
visible: 'submission',
|
||||
visible: 'submission',
|
||||
eval:
|
||||
"__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
|
||||
evalInfo: { showLinkButton: true, description: "Auto-detect the project's Renders folder" },
|
||||
"__import__('os').path.normpath(__import__('os').path.join(((__import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')) and __import__('re').search(r'^(.*?)[\\/][Bb]lends[\\/]', bpy.data.filepath.replace('\\\\','/')).group(1)) or __import__('os').path.dirname(bpy.data.filepath)), 'Renders'))",
|
||||
evalInfo: { showLinkButton: true, description: "Auto-detect the project's Renders folder" },
|
||||
description:
|
||||
"Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
|
||||
"Base path where renders are stored, typically the project's Renders folder. If empty, derived automatically.",
|
||||
},
|
||||
- {
|
||||
- key: 'daily_path',
|
||||
- type: 'string',
|
||||
- key: 'daily_path',
|
||||
- type: 'string',
|
||||
- required: false,
|
||||
- visible: 'submission',
|
||||
- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
|
||||
- evalInfo: { showLinkButton: true, description: "Auto-fill with today's daily folder name" },
|
||||
- visible: 'submission',
|
||||
- eval: "__import__('datetime').datetime.now().strftime('daily_%y%m%d')",
|
||||
- evalInfo: { showLinkButton: true, description: "Auto-fill with today's daily folder name" },
|
||||
- description:
|
||||
- "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.",
|
||||
- "Daily folder name under the render root, e.g. 'daily_250813'. If empty, auto-fills to today's date.",
|
||||
- },
|
||||
{
|
||||
key: 'use_submodule',
|
||||
label: 'Use Submodule',
|
||||
type: 'bool',
|
||||
key: 'use_submodule',
|
||||
label: 'Use Submodule',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
@@
|
||||
{
|
||||
key: 'render_output_path',
|
||||
type: 'string',
|
||||
subtype: 'file_path',
|
||||
key: 'render_output_path',
|
||||
type: 'string',
|
||||
subtype: 'file_path',
|
||||
editable: false,
|
||||
- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))",
|
||||
- eval: "str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), (settings.daily_path or __import__('datetime').datetime.now().strftime('daily_%y%m%d')), jobname, jobname + '_######'))",
|
||||
+ eval:
|
||||
+ "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
description: 'Final file path of where render output will be saved',
|
||||
+ "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
description: 'Final file path of where render output will be saved',
|
||||
},
|
||||
@@
|
||||
- if (!settings.daily_path || String(settings.daily_path).trim() === '') {
|
||||
- if (!settings.daily_path || String(settings.daily_path).trim() === '') {
|
||||
- const createdDate = job && job.created ? new Date(job.created) : new Date();
|
||||
- settings.daily_path = formatDailyYYMMDD(createdDate);
|
||||
- }
|
||||
@@ -212,22 +212,22 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
- const submodule = detectSubmodule(settings);
|
||||
- // Resolve render root
|
||||
- let renderRoot = null;
|
||||
- if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
- renderRoot = ("" + settings.render_output_root).trim();
|
||||
- if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
- renderRoot = ("" + settings.render_output_root).trim();
|
||||
- } else if (projectRoot) {
|
||||
- renderRoot = path.join(projectRoot, 'Renders');
|
||||
- renderRoot = path.join(projectRoot, 'Renders');
|
||||
- } else {
|
||||
- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
- renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
- }
|
||||
- // Resolve daily path
|
||||
- let daily = null;
|
||||
- if (settings.daily_path && ("" + settings.daily_path).trim()) {
|
||||
- daily = ("" + settings.daily_path).trim();
|
||||
- if (settings.daily_path && ("" + settings.daily_path).trim()) {
|
||||
- daily = ("" + settings.daily_path).trim();
|
||||
- } else {
|
||||
- const createdDate = job && job.created ? new Date(job.created) : new Date();
|
||||
- daily = formatDailyYYMMDD(createdDate);
|
||||
- }
|
||||
- const jobname = job && job.name ? job.name : path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
- const jobname = job && job.name ? job.name : path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
-
|
||||
- const parts = [renderRoot];
|
||||
- if (submodule) parts.push(submodule);
|
||||
@@ -236,14 +236,14 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
+ const submodule = detectSubmodule(settings);
|
||||
+ // Resolve render root
|
||||
+ let renderRoot = null;
|
||||
+ if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
+ renderRoot = ("" + settings.render_output_root).trim();
|
||||
+ if (settings.render_output_root && ("" + settings.render_output_root).trim()) {
|
||||
+ renderRoot = ("" + settings.render_output_root).trim();
|
||||
+ } else if (projectRoot) {
|
||||
+ renderRoot = path.join(projectRoot, 'Renders');
|
||||
+ renderRoot = path.join(projectRoot, 'Renders');
|
||||
+ } else {
|
||||
+ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
+ renderRoot = path.join(path.dirname(settings.blendfile), 'Renders');
|
||||
+ }
|
||||
+ const blendname = path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
+ const blendname = path.stem(settings.blendfile).replace('.flamenco', '');
|
||||
+
|
||||
+ const parts = [renderRoot];
|
||||
+ if (submodule) parts.push(submodule);
|
||||
@@ -252,7 +252,7 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
}
|
||||
@@
|
||||
-function formatDailyYYMMDD(dateObj) {
|
||||
- const pad2 = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
- const pad2 = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
- const yy = dateObj.getFullYear() % 100;
|
||||
- const mm = dateObj.getMonth() + 1;
|
||||
- const dd = dateObj.getDate();
|
||||
@@ -272,7 +272,7 @@ I’m refining the plan to remove all daily path handling, carefully adjusting p
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="grep" data-tool-name="grep">
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "daily_path" in "scripts" • 0 matches</summary>
|
||||
|
||||
@@ -291,7 +291,7 @@ _No matches found_
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="grep" data-tool-name="grep">
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "formatDaily" in "scripts" • 0 matches</summary>
|
||||
|
||||
@@ -597,20 +597,20 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js
|
||||
@@
|
||||
- {
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- editable: false,
|
||||
- eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- eval: "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- },
|
||||
+ {
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ editable: false,
|
||||
+ eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
@@ -635,22 +635,22 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js
|
||||
@@
|
||||
- {
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- editable: false,
|
||||
- eval:
|
||||
- "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- "((lambda blendname: str(Path(abspath(settings.render_output_root or '//'), ((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else '')), blendname, blendname + '_######')))(__import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- },
|
||||
+ {
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ editable: false,
|
||||
+ eval:
|
||||
+ "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
@@ -743,20 +743,20 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js
|
||||
@@
|
||||
- {
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- editable: false,
|
||||
- eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- eval: "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- },
|
||||
+ {
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ editable: false,
|
||||
+ eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
@@ -776,22 +776,22 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js
|
||||
@@
|
||||
- {
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- editable: false,
|
||||
- eval:
|
||||
- "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- "((lambda Path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((__import__('os').path.basename(__import__('os').path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- },
|
||||
+ {
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ editable: false,
|
||||
+ eval:
|
||||
+ "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
@@ -888,7 +888,7 @@ _**Agent (model gpt-5-codex, mode Agent)**_
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="grep" data-tool-name="grep">
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "settings.render_output_path" • 8 matches</summary>
|
||||
|
||||
@@ -917,7 +917,7 @@ Output mode: content
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="grep" data-tool-name="grep">
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "render_output_path','\s*type" in "scripts" • 0 matches</summary>
|
||||
|
||||
@@ -948,20 +948,20 @@ I'm figuring out why the new lambda-based eval code can't see the `settings` var
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads Custom Render.js
|
||||
@@
|
||||
- {
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- key: "render_output_path",
|
||||
- type: "string",
|
||||
- subtype: "file_path",
|
||||
- editable: false,
|
||||
- eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- eval: "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: "Final file path of where render output will be saved"
|
||||
- },
|
||||
+ {
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ key: "render_output_path",
|
||||
+ type: "string",
|
||||
+ subtype: "file_path",
|
||||
+ editable: false,
|
||||
+ eval: "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ eval: "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: "Final file path of where render output will be saved"
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
@@ -981,22 +981,22 @@ I'm figuring out why the new lambda-based eval code can't see the `settings` var
|
||||
*** Update File: C:\Users\Nathan\Desktop\flamenco\scripts\TalkingHeads cycles_optix_gpu.js
|
||||
@@
|
||||
- {
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- key: 'render_output_path',
|
||||
- type: 'string',
|
||||
- subtype: 'file_path',
|
||||
- editable: false,
|
||||
- eval:
|
||||
- "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- "((lambda Path, abspath, os_path, blend: str(Path(abspath(settings.render_output_root or '//')) / (((str(settings.submodule or '').strip()) if (settings.use_submodule and str(settings.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
- description: 'Final file path of where render output will be saved',
|
||||
- },
|
||||
+ {
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ key: 'render_output_path',
|
||||
+ type: 'string',
|
||||
+ subtype: 'file_path',
|
||||
+ editable: false,
|
||||
+ eval:
|
||||
+ "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ "((lambda Path, abspath, os_path, settings_obj, blend: str(Path(abspath(settings_obj.render_output_root or '//')) / (((str(settings_obj.submodule or '').strip()) if (settings_obj.use_submodule and str(settings_obj.submodule or '').strip()) else ((os_path.basename(os_path.dirname(bpy.data.filepath))) if settings_obj.use_submodule else ''))) / blend / (blend + '_######')))(__import__('pathlib').Path, __import__('os').path.abspath, __import__('os').path, settings, __import__('os').path.splitext(__import__('os').path.basename(bpy.data.filepath))[0]))",
|
||||
+ description: 'Final file path of where render output will be saved',
|
||||
+ },
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
<!-- Generated by SpecStory, Markdown v2.0.0 -->
|
||||
|
||||
<!-- cursor Session 9522a0d8-694a-45d0-a000-ce16feaaf911 (2025-11-19 17:03Z) -->
|
||||
|
||||
# Error initializing SheepIt client (2025-11-19 17:03Z)
|
||||
|
||||
_**User (2025-11-19 17:03Z)**_
|
||||
|
||||
SheepIt client already present at C:\Users\nathan\AppData\Roaming\sheepit\sheepit-client.jar. Skipping download.
|
||||
|
||||
Starting SheepIt client...
|
||||
|
||||
Authenticating as RaincloudTheDragon via render key.
|
||||
|
||||
Error occurred during initialization of VM
|
||||
|
||||
Failed setting boot class path.
|
||||
|
||||
Connection to i9-13ks closed.
|
||||
|
||||
|
||||
|
||||
SSH session ended.
|
||||
|
||||
Press Enter to close:
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model default, mode Agent)**_
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
The "Failed setting boot class path" error usually indicates a Java version mismatch or incompatible JVM arguments. Checking the SheepIt launcher script to see how it's launching the client.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: unified_sheepit_launcher.ps1</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: unified_sheepit_launcher.bat</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
The error "Failed setting boot class path" usually indicates a Java version mismatch or incompatible JVM arguments. Updating the launch command to be more compatible and add error handling:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="write" data-tool-name="search_replace">
|
||||
|
||||
<details><summary>Tool use: **code_edit** • Edit file: unified_sheepit_launcher.ps1</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 13, lines removed: 2
|
||||
|
||||
```diff
|
||||
@@ -88,6 +88,17 @@
|
||||
|
||||
Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
- ${userLine} Set-Location `$sheepDir
|
||||
- & java -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 -login '${safeUser}' -password '${safeKey}'
|
||||
+
|
||||
+ # Check Java version
|
||||
+ try {
|
||||
+ `$javaVersion = java -version 2>&1 | 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
|
||||
+ & java -XX:+IgnoreUnrecognizedVMOptions -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 -login '${safeUser}' -password '${safeKey}'
|
||||
}
|
||||
catch {
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Improving error handling to capture and display more details about Java errors:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="write" data-tool-name="search_replace">
|
||||
|
||||
<details><summary>Tool use: **code_edit** • Edit file: unified_sheepit_launcher.ps1</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 20, lines removed: 1
|
||||
|
||||
```diff
|
||||
@@ -99,9 +99,28 @@
|
||||
|
||||
Set-Location `$sheepDir
|
||||
+
|
||||
+ # Check for problematic environment variables
|
||||
+ if (`$env:JAVA_TOOL_OPTIONS) {
|
||||
+ Write-Host "Warning: JAVA_TOOL_OPTIONS is set: `$env:JAVA_TOOL_OPTIONS" -ForegroundColor Yellow
|
||||
+ }
|
||||
+
|
||||
# Use -XX:+IgnoreUnrecognizedVMOptions to handle any incompatible JVM args
|
||||
- & java -XX:+IgnoreUnrecognizedVMOptions -jar `$jarPath -ui text --log-stdout --verbose -gpu OPTIX_0 -login '${safeUser}' -password '${safeKey}'
|
||||
+ # 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 updating Java to a compatible version (Java 8 or Java 11+ recommended)." -ForegroundColor Yellow
|
||||
+ throw
|
||||
+ }
|
||||
}
|
||||
catch {
|
||||
Write-Host ('Error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
||||
+ Write-Host ('Stack trace: {0}' -f `$_.ScriptStackTrace) -ForegroundColor DarkRed
|
||||
}
|
||||
"@
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Checking for linting errors:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="read_lints">
|
||||
<details>
|
||||
<summary>Tool use: **read_lints** • Read lints for 1 file</summary>
|
||||
|
||||
Lint paths:
|
||||
|
||||
- `unified_sheepit_launcher.ps1`
|
||||
|
||||
Lint results
|
||||
|
||||
|
||||
**No lint errors found**
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Updated the SheepIt launcher to address the "Failed setting boot class path" error. Changes:
|
||||
|
||||
1. **Added `-XX:+IgnoreUnrecognizedVMOptions` flag** — helps with Java 9+ compatibility issues that can cause boot class path errors
|
||||
2. **Added Java version check** — displays the Java version in use for debugging
|
||||
3. **Added environment variable check** — warns if `JAVA_TOOL_OPTIONS` is set (can cause conflicts)
|
||||
4. **Improved error handling** — clearer error messages and diagnostics
|
||||
|
||||
The main fix is the `-XX:+IgnoreUnrecognizedVMOptions` flag, which tells the JVM to ignore unrecognized VM options. This often resolves boot class path errors when the JAR or environment includes incompatible arguments.
|
||||
|
||||
Try running the launcher again. If the error persists, it may indicate:
|
||||
- An incompatible Java version on the remote machine (i9-13ks)
|
||||
- System-level Java configuration issues
|
||||
|
||||
The script will now show the Java version being used, which will help diagnose further if needed.
|
||||
|
||||
---
|
||||
|
||||
_**User (2025-11-19 17:03Z)**_
|
||||
|
||||
I think it's an issue with the system itself. Can you ssh into the system and see if it can reach the internet?
|
||||
|
||||
SheepIt client already present at C:\Users\nathan\AppData\Roaming\sheepit\sheepit-client.jar. Skipping download.
|
||||
|
||||
Starting SheepIt client...
|
||||
|
||||
Java version: Error occurred during initialization of VM
|
||||
|
||||
Error occurred during initialization of VM
|
||||
|
||||
Failed setting boot class path.
|
||||
|
||||
Connection to i9-13ks closed.
|
||||
|
||||
SSH session ended.
|
||||
|
||||
Press Enter to close:
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model default, mode Agent)**_
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
SSHing into i9-13ks to check internet connectivity and diagnose the Java issue. Since `java -version` also fails, this likely points to a system-level Java problem.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: ssh -t -p 22146 i9-13ks "powershell -Command \"Write-Host 'Testing internet connectivity...' -ForegroundColor Cyan; Test-NetConnection -ComputerName google.com -Port 80 -InformationLevel Quiet; if ($?) { Write-Host 'Internet connection: OK' -ForegroundColor Green } else { Write-Host 'Internet connection: FAILED' -ForegroundColor Red }; Write-Host ''; Write-Host 'Testing DNS resolution...' -ForegroundColor Cyan; try { $result = Resolve-DnsName -Name google.com -ErrorAction Stop; Write-Host 'DNS resolution: OK' -ForegroundColor Green } catch { Write-Host 'DNS resolution: FAILED' -ForegroundColor Red }; Write-Host ''; Write-Host 'Testing HTTP connectivity...' -ForegroundColor Cyan; try { $response = Invoke-WebRequest -Uri 'https://www.google.com' -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop; Write-Host 'HTTP connectivity: OK' -ForegroundColor Green } catch { Write-Host \"HTTP connectivity: FAILED - $($_.Exception.Message)\" -ForegroundColor Red }\""</summary>
|
||||
|
||||
```bash
|
||||
ssh -t -p 22146 i9-13ks "powershell -Command \"Write-Host 'Testing internet connectivity...' -ForegroundColor Cyan; Test-NetConnection -ComputerName google.com -Port 80 -InformationLevel Quiet; if ($?) { Write-Host 'Internet connection: OK' -ForegroundColor Green } else { Write-Host 'Internet connection: FAILED' -ForegroundColor Red }; Write-Host ''; Write-Host 'Testing DNS resolution...' -ForegroundColor Cyan; try { $result = Resolve-DnsName -Name google.com -ErrorAction Stop; Write-Host 'DNS resolution: OK' -ForegroundColor Green } catch { Write-Host 'DNS resolution: FAILED' -ForegroundColor Red }; Write-Host ''; Write-Host 'Testing HTTP connectivity...' -ForegroundColor Cyan; try { $response = Invoke-WebRequest -Uri 'https://www.google.com' -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop; Write-Host 'HTTP connectivity: OK' -ForegroundColor Green } catch { Write-Host \"HTTP connectivity: FAILED - $($_.Exception.Message)\" -ForegroundColor Red }\""
|
||||
```
|
||||
|
||||
```
|
||||
[?9001h[?1004h[?25l[2J[m[H]0;Administrator: C:\WINDOWS\system32\conhost.exe[?25h[38;5;9m\ : The term '\' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the [m[38;5;9m
|
||||
spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
At line:1 char:1
|
||||
+ \ Write-Host Testing internet connectivity... -ForegroundColor Cyan
|
||||
+ ~
|
||||
+ CategoryInfo : ObjectNotFound: (\:String) [], CommandNotFoundException
|
||||
+ FullyQualifiedErrorId : CommandNotFoundException
|
||||
|
||||
[?9001l[?1004l[mConnection to i9-13ks closed.
|
||||
True
|
||||
Internet connection: OK
|
||||
|
||||
Testing DNS resolution...
|
||||
DNS resolution: OK
|
||||
|
||||
Testing HTTP connectivity...
|
||||
HTTP connectivity: OK
|
||||
\: C:\Users\Nathan\AppData\Local\Temp\ps-script-f07861ff-1930-4e0c-a126-2a9512cba633.ps1:7
|
||||
Line |
|
||||
7 | . ctivity: FAILED - $($_.Exception.Message)\" -ForegroundColor Red }\""
|
||||
| ~~~
|
||||
| The term '\' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the
|
||||
| spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Internet connectivity is working. Checking the Java installation:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: ssh -t -p 22146 i9-13ks "powershell -Command \"Write-Host 'Checking Java installation...' -ForegroundColor Cyan; Write-Host ''; Write-Host 'Java executable path:' -ForegroundColor Yellow; Get-Command java -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source; Write-Host ''; Write-Host 'JAVA_HOME environment variable:' -ForegroundColor Yellow; Write-Host \$env:JAVA_HOME; Write-Host ''; Write-Host 'JAVA_TOOL_OPTIONS:' -ForegroundColor Yellow; Write-Host \$env:JAVA_TOOL_OPTIONS; Write-Host ''; Write-Host 'PATH entries containing java:' -ForegroundColor Yellow; \$env:PATH -split ';' | Where-Object { \$_ -like '*java*' }; Write-Host ''; Write-Host 'Attempting java -version (this may fail):' -ForegroundColor Yellow; java -version 2>&1\""</summary>
|
||||
|
||||
```bash
|
||||
ssh -t -p 22146 i9-13ks "powershell -Command \"Write-Host 'Checking Java installation...' -ForegroundColor Cyan; Write-Host ''; Write-Host 'Java executable path:' -ForegroundColor Yellow; Get-Command java -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source; Write-Host ''; Write-Host 'JAVA_HOME environment variable:' -ForegroundColor Yellow; Write-Host \$env:JAVA_HOME; Write-Host ''; Write-Host 'JAVA_TOOL_OPTIONS:' -ForegroundColor Yellow; Write-Host \$env:JAVA_TOOL_OPTIONS; Write-Host ''; Write-Host 'PATH entries containing java:' -ForegroundColor Yellow; \$env:PATH -split ';' | Where-Object { \$_ -like '*java*' }; Write-Host ''; Write-Host 'Attempting java -version (this may fail):' -ForegroundColor Yellow; java -version 2>&1\""
|
||||
```
|
||||
|
||||
```
|
||||
[?9001h[?1004h[?25l[2J[m[H]0;Administrator: C:\WINDOWS\system32\conhost.exe[?25h[38;5;9m\ : The term '\' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the [?9001l[?1004l[m[38;5;9m
|
||||
spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
At line:1 char:1
|
||||
+ \ Write-Host Checking Java installation... -ForegroundColor Cyan
|
||||
+ ~
|
||||
+ CategoryInfo : ObjectNotFound: (\:String) [], CommandNotFoundException
|
||||
+ FullyQualifiedErrorId : CommandNotFoundException
|
||||
|
||||
|
||||
Java executable path:
|
||||
C:\Program Files\Eclipse Adoptium\jdk-21.0.9.10-hotspot\bin\java.exe
|
||||
|
||||
JAVA_HOME environment variable:
|
||||
\
|
||||
|
||||
JAVA_TOOL_OPTIONS:
|
||||
\
|
||||
|
||||
PATH entries containing java:
|
||||
Connection to i9-13ks closed.
|
||||
|
||||
Attempting java -version (this may fail):
|
||||
\$env:PATH: C:\Users\Nathan\AppData\Local\Temp\ps-script-2b04d60b-8094-4ccf-b84e-435f31732af2.ps1:118
|
||||
Line |
|
||||
118 | . entries containing java:' -ForegroundColor Yellow; \$env:PATH -split .
|
||||
| ~~~~~~~~~~
|
||||
| The term '\$env:PATH' is not recognized as a name of a cmdlet, function, script file, or executable program.
|
||||
| Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
openjdk version "21.0.9" 2025-10-21 LTS
|
||||
OpenJDK Runtime Environment Temurin-21.0.9+10 (build 21.0.9+10-LTS)
|
||||
OpenJDK 64-Bit Server VM Temurin-21.0.9+10 (build 21.0.9+10-LTS, mixed mode, sharing)
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Java is installed (Java 21), but `JAVA_HOME` and `JAVA_TOOL_OPTIONS` are set to `\`, which is invalid and likely causing the boot class path error. Checking and fixing these environment variables:
|
||||
|
||||
|
||||
---
|
||||
|
||||
Cancelled
|
||||
|
||||
---
|
||||
|
||||
_**User (2025-11-19 17:03Z)**_
|
||||
|
||||
Connecting to i9-13ks...
|
||||
|
||||
|
||||
|
||||
SheepIt client already present at C:\Users\nathan\AppData\Roaming\sheepit\sheepit-client.jar. Skipping download.
|
||||
|
||||
Starting SheepIt client...
|
||||
|
||||
Warning: Could not determine Java version
|
||||
|
||||
|
||||
|
||||
I had to choco uninstall and reinstall Temurin21. This is the 2nd time I've had to do this on this system.
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model default, mode Agent)**_
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Invalid `JAVA_HOME` and `JAVA_TOOL_OPTIONS` (set to `\`) were causing the issue. Updating the script to detect and handle invalid Java environment variables to prevent this from recurring:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: unified_sheepit_launcher.ps1</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Updating the script to detect and handle invalid Java environment variables:
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="write" data-tool-name="search_replace">
|
||||
|
||||
<details><summary>Tool use: **code_edit** • Edit file: unified_sheepit_launcher.ps1</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 27, lines removed: 1
|
||||
|
||||
```diff
|
||||
@@ -88,8 +88,34 @@
|
||||
|
||||
Write-Host "Starting SheepIt client..." -ForegroundColor Cyan
|
||||
+
|
||||
+ # 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 {
|
||||
- `$javaVersion = java -version 2>&1 | Select-Object -First 1
|
||||
+ `$javaOutput = java -version 2>&1
|
||||
+ `$javaVersion = `$javaOutput | Select-Object -First 1
|
||||
Write-Host "Java version: `$javaVersion" -ForegroundColor Gray
|
||||
}
|
||||
```
|
||||
|
||||
**Chunk 2**
|
||||
Lines added: undefined, lines removed: 5
|
||||
|
||||
```diff
|
||||
@@ -99,9 +125,4 @@
|
||||
|
||||
Set-Location `$sheepDir
|
||||
-
|
||||
- # Check for problematic environment variables
|
||||
- if (`$env:JAVA_TOOL_OPTIONS) {
|
||||
- Write-Host "Warning: JAVA_TOOL_OPTIONS is set: `$env:JAVA_TOOL_OPTIONS" -ForegroundColor Yellow
|
||||
- }
|
||||
|
||||
# Use -XX:+IgnoreUnrecognizedVMOptions to handle any incompatible JVM args
|
||||
```
|
||||
|
||||
**Chunk 3**
|
||||
Lines added: 1, lines removed: 1
|
||||
|
||||
```diff
|
||||
@@ -116,5 +137,5 @@
|
||||
catch {
|
||||
Write-Host ('Java execution error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
||||
- Write-Host "If the error persists, try updating Java to a compatible version (Java 8 or Java 11+ recommended)." -ForegroundColor Yellow
|
||||
+ Write-Host "If the error persists, try reinstalling Java (Temurin 21 recommended)." -ForegroundColor Yellow
|
||||
throw
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="read_lints">
|
||||
<details>
|
||||
<summary>Tool use: **read_lints** • Read lints for 1 file</summary>
|
||||
|
||||
Lint paths:
|
||||
|
||||
- `unified_sheepit_launcher.ps1`
|
||||
|
||||
Lint results
|
||||
|
||||
|
||||
**No lint errors found**
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Updated the script to detect and handle invalid Java environment variables. It will:
|
||||
|
||||
1. **Check `JAVA_HOME`** — if it's set to `\`, empty, or an invalid path, it temporarily unsets it
|
||||
2. **Check `JAVA_TOOL_OPTIONS`** — if it's set to `\` or empty, it temporarily unsets it
|
||||
3. **Show warnings** when these are fixed so you know what happened
|
||||
4. **Proceed with Java launch** after cleaning up the environment
|
||||
|
||||
Since this is the second time you've had to reinstall Java on this system, something may be corrupting these environment variables. The script will now automatically fix them when detected, so you shouldn't need to reinstall Java again.
|
||||
|
||||
The script temporarily unsets these variables only for the SheepIt client process, so it won't affect other applications or require system-wide changes. This should prevent the "Failed setting boot class path" error from recurring.
|
||||
|
||||
---
|
||||
|
||||
2110
.specstory/history/2025-11-19_17-03Z-why-is-git-frozen.md
Normal file
2110
.specstory/history/2025-11-19_17-03Z-why-is-git-frozen.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,292 @@
|
||||
<!-- Generated by SpecStory, Markdown v2.1.0 -->
|
||||
|
||||
<!-- cursor Session 21c1dff0-7d20-4158-84d9-19180276948d (2025-11-21 01:14Z) -->
|
||||
|
||||
# Workers failing and SSH connection issues (2025-11-21 01:14Z)
|
||||
|
||||
_**User (2025-11-21 01:14Z)**_
|
||||
|
||||
all workers are failing?
|
||||
|
||||
|
||||
|
||||
Connecting to i9kf...
|
||||
|
||||
exec request failed on channel 0
|
||||
|
||||
|
||||
|
||||
SSH session ended.
|
||||
|
||||
Press Enter to close:
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model gpt-5.1-codex, mode Agent)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Checking workspace directory**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="list_dir">
|
||||
<details>
|
||||
<summary>Tool use: **list_dir** • Listed directory, 0 results</summary>
|
||||
|
||||
No results found
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Diagnosing worker connection failures**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: scripts/TalkingHeads cycles_optix_gpu.js</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Checking for relevant scripts**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: scripts/outline.txt</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Investigating worker failure context**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: unified_flamenco_launcher.ps1</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Analyzing SSH exec request failure**
|
||||
|
||||
I’m piecing together why the SSH “exec request failed on channel 0” error occurs when the script tries running `powershell -EncodedCommand` remotely. It looks like the remote hosts may lack PowerShell, use restrictive SSH configs, or have quoting and command invocation issues; I’m considering host OS differences, SSH subsystems, and command formatting problems to guide a helpful troubleshooting response.</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: unified_flamenco_launcher.bat</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Analyzing remote SSH exec failure**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="search" data-tool-name="grep">
|
||||
<details>
|
||||
<summary>Tool use: **grep** • Grep for "exec request failed" in "." • 0 matches</summary>
|
||||
|
||||
Output mode: content
|
||||
|
||||
|
||||
_No matches found_
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Analyzing SSH exec failure causes**
|
||||
|
||||
I'm gathering possible reasons why all workers fail with "exec request failed on channel 0," focusing on remote SSH command execution limits, shell configuration, command length limits on Windows, and potential remote server restrictions or misconfigurations. I’m considering the encoded PowerShell command size, quoting issues, remote subsystems, ForceCommand settings, and suggesting diagnostic steps to help troubleshoot the problem.
|
||||
**Proposing remote script streaming approach**
|
||||
|
||||
I'm exploring a way to fix the failure by modifying the script to stream the remote PowerShell code via SSH standard input instead of using long encoded commands, which may exceed length limits or cause quoting issues. I’m outlining implementation ideas to pipe the script through SSH to remote `powershell -Command -` to avoid exec errors and suggesting a plan to update the launcher accordingly.
|
||||
**Detailing PowerShell SSH streaming implementation**
|
||||
|
||||
I'm outlining a helper to stream the worker script to the remote PowerShell session via SSH by piping the script through standard input using `Start-Process` with redirected input. I’m carefully considering argument quoting, how to pass SSH options and commands, and PowerShell’s handling of reading scripts from stdin, while also weighing alternatives like copying a script file to the remote host for execution.</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="shell" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **run_terminal_cmd** • Run command: cd C:\Users\Nathan\Desktop\flamenco
|
||||
python - <<'PY'
|
||||
import re, base64
|
||||
from pathlib import Path
|
||||
text = Path('unified_flamenco_launcher.ps1').read_text()
|
||||
patterns = [r"function Get-RemoteStandardWorkerCommand \{\s*@'([\s\S]+?)'@", r"function Get-RemoteCmdWorkerCommand \{\s*@'([\s\S]+?)'@", r"function Get-RemoteSimplifiedCmdWorkerCommand \{\s*@'([\s\S]+?)'@"]
|
||||
for pat in patterns:
|
||||
m = re.search(pat, text)
|
||||
if m:
|
||||
body = m.group(1)
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(pat.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
PY</summary>
|
||||
|
||||
```bash
|
||||
cd C:\Users\Nathan\Desktop\flamenco
|
||||
python - <<'PY'
|
||||
import re, base64
|
||||
from pathlib import Path
|
||||
text = Path('unified_flamenco_launcher.ps1').read_text()
|
||||
patterns = [r"function Get-RemoteStandardWorkerCommand \{\s*@'([\s\S]+?)'@", r"function Get-RemoteCmdWorkerCommand \{\s*@'([\s\S]+?)'@", r"function Get-RemoteSimplifiedCmdWorkerCommand \{\s*@'([\s\S]+?)'@"]
|
||||
for pat in patterns:
|
||||
m = re.search(pat, text)
|
||||
if m:
|
||||
body = m.group(1)
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(pat.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
PY
|
||||
```
|
||||
|
||||
```
|
||||
ParserError: C:\Users\Nathan\AppData\Local\Temp\ps-script-4890d486-a555-41a3-8a38-e515f235a750.ps1:8
|
||||
Line |
|
||||
8 | python - <<'PY'
|
||||
| ~
|
||||
| Missing file specification after redirection operator.
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Exploring PowerShell heredoc limits**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="shell" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **run_terminal_cmd** • Run command: cd C:\Users\Nathan\Desktop\flamenco
|
||||
python -c "import re, base64, pathlib; text = pathlib.Path('unified_flamenco_launcher.ps1').read_text();
|
||||
import itertools
|
||||
patterns = ['function Get-RemoteStandardWorkerCommand {', 'function Get-RemoteCmdWorkerCommand {', 'function Get-RemoteSimplifiedCmdWorkerCommand {']
|
||||
import sys
|
||||
for name in patterns:
|
||||
idx = text.find(name)
|
||||
if idx == -1:
|
||||
continue
|
||||
start = text.find("@'", idx)
|
||||
end = text.find("'@", start+2)
|
||||
body = text[start+2:end]
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(name.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
"</summary>
|
||||
|
||||
```bash
|
||||
cd C:\Users\Nathan\Desktop\flamenco
|
||||
python -c "import re, base64, pathlib; text = pathlib.Path('unified_flamenco_launcher.ps1').read_text();
|
||||
import itertools
|
||||
patterns = ['function Get-RemoteStandardWorkerCommand {', 'function Get-RemoteCmdWorkerCommand {', 'function Get-RemoteSimplifiedCmdWorkerCommand {']
|
||||
import sys
|
||||
for name in patterns:
|
||||
idx = text.find(name)
|
||||
if idx == -1:
|
||||
continue
|
||||
start = text.find("@'", idx)
|
||||
end = text.find("'@", start+2)
|
||||
body = text[start+2:end]
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(name.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
"
|
||||
```
|
||||
|
||||
```
|
||||
ParserError: C:\Users\Nathan\AppData\Local\Temp\ps-script-a0e83e85-1956-43ab-ab83-9ea6c4a2f958.ps1:16
|
||||
Line |
|
||||
16 | start = text.find("@'", idx)
|
||||
| ~
|
||||
| No characters are allowed after a here-string header but before the end of the line.
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Resolving PowerShell string parsing issues**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="shell" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **run_terminal_cmd** • Run command: cd C:\Users\Nathan\Desktop\flamenco
|
||||
@"
|
||||
import re, base64, pathlib
|
||||
text = pathlib.Path('unified_flamenco_launcher.ps1').read_text()
|
||||
patterns = ['function Get-RemoteStandardWorkerCommand {', 'function Get-RemoteCmdWorkerCommand {', 'function Get-RemoteSimplifiedCmdWorkerCommand {']
|
||||
for name in patterns:
|
||||
idx = text.find(name)
|
||||
if idx == -1:
|
||||
continue
|
||||
start = text.find("@'", idx)
|
||||
end = text.find("'@", start+2)
|
||||
body = text[start+2:end]
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(name.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
"@ | Set-Content measure.py
|
||||
python measure.py
|
||||
Remove-Item measure.py
|
||||
</summary>
|
||||
|
||||
```bash
|
||||
cd C:\Users\Nathan\Desktop\flamenco
|
||||
@"
|
||||
import re, base64, pathlib
|
||||
text = pathlib.Path('unified_flamenco_launcher.ps1').read_text()
|
||||
patterns = ['function Get-RemoteStandardWorkerCommand {', 'function Get-RemoteCmdWorkerCommand {', 'function Get-RemoteSimplifiedCmdWorkerCommand {']
|
||||
for name in patterns:
|
||||
idx = text.find(name)
|
||||
if idx == -1:
|
||||
continue
|
||||
start = text.find("@'", idx)
|
||||
end = text.find("'@", start+2)
|
||||
body = text[start+2:end]
|
||||
utf16 = body.encode('utf-16le')
|
||||
b64 = base64.b64encode(utf16)
|
||||
print(name.split()[1], 'chars', len(body), 'utf16 bytes', len(utf16), 'b64 len', len(b64))
|
||||
"@ | Set-Content measure.py
|
||||
python measure.py
|
||||
Remove-Item measure.py
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Get-RemoteStandardWorkerCommand chars 2690 utf16 bytes 5380 b64 len 7176
|
||||
Get-RemoteCmdWorkerCommand chars 2439 utf16 bytes 4878 b64 len 6504
|
||||
Get-RemoteSimplifiedCmdWorkerCommand chars 2012 utf16 bytes 4024 b64 len 5368
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Evaluating remote SSH exec failure causes**</details></think>
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because one or more lines are too long
29
persistent_workers.md
Normal file
29
persistent_workers.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Persistent Remote Worker Architecture
|
||||
|
||||
This repository now includes a small controller/attach framework that keeps both Flamenco and SheepIt workers running on each remote Windows host even after the SSH session closes.
|
||||
|
||||
### Components
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `remote_worker_controller.ps1` | Runs on each remote host. Launches the real worker process (Flamenco or SheepIt), redirects its stdout/stderr to a log file, listens for commands, and restarts the worker if it crashes. |
|
||||
| `remote_worker_attach.ps1` | Also runs remotely. Streams the controller’s log file back to the local SSH session and forwards commands (`pause`, `resume`, `quit`, etc.) to the worker’s stdin. It can also be invoked in a `-CommandOnly` mode to send a single command without attaching. |
|
||||
| `unified_flamenco_launcher.ps1` / `unified_sheepit_launcher.ps1` | Local launchers that copy the helper scripts to each host, start controllers in the background, and open attach sessions as requested. |
|
||||
|
||||
All metadata, logs and helper scripts live under `C:\ProgramData\UnifiedWorkers\<worker-type>\<worker-name>\`.
|
||||
|
||||
### Typical Flow
|
||||
|
||||
1. **Start/attach**: The launcher ensures the controller + payload are present on the target host, starts the controller through `Start-Process`, then launches `remote_worker_attach.ps1` via SSH so you can see live output and type commands. Closing the SSH window only ends the attach session—the worker keeps running.
|
||||
2. **Reattach**: Run the launcher again and choose the same worker. If a controller reports that a worker is already running, the launcher simply opens another attach session.
|
||||
3. **Pause/Resume/Quit all (SheepIt)**: The SheepIt launcher now exposes menu options that iterate over every enabled worker and invoke the attach helper in `-CommandOnly` mode to send the requested command.
|
||||
|
||||
### Manual Verification Checklist
|
||||
|
||||
1. **Controller deployment**: From the launcher, start a worker once and confirm `%ProgramData%\UnifiedWorkers` appears on the remote host with `controller.ps1`, `attach-helper.ps1`, `logs\worker.log`, and `state\worker-info.json`.
|
||||
2. **Persistence**: Attach to a worker, close the SSH window, then reattach. The log stream should resume and the worker PID reported in metadata should remain unchanged.
|
||||
3. **Command channel**: While attached, type `pause`, `resume`, and `quit`. The controller should log each command and the worker should react accordingly. Repeat the same commands using the SheepIt “Pause/Resume/Quit All” menu entries to validate the automation path.
|
||||
4. **Failure recovery**: Manually terminate the worker process on a remote host. The controller should log the exit, update metadata, and restart the worker up to the configured retry limit.
|
||||
|
||||
These steps ensure both Flamenco and SheepIt workflows operate as intended with the new persistent worker model. Adjust the retry/backoff values inside `remote_worker_controller.ps1` if you need more aggressive or conservative restart behavior.
|
||||
|
||||
116
remote_worker_attach.ps1
Normal file
116
remote_worker_attach.ps1
Normal file
@@ -0,0 +1,116 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WorkerName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WorkerType,
|
||||
|
||||
[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),
|
||||
|
||||
[switch]$CommandOnly,
|
||||
|
||||
[string]$Command
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-WorkerPaths {
|
||||
param([string]$Root, [string]$Type, [string]$Name)
|
||||
|
||||
$instanceRoot = Join-Path -Path (Join-Path -Path $Root -ChildPath $Type) -ChildPath $Name
|
||||
return [pscustomobject]@{
|
||||
Metadata = Join-Path -Path $instanceRoot -ChildPath 'state\worker-info.json'
|
||||
Command = Join-Path -Path $instanceRoot -ChildPath 'state\commands.txt'
|
||||
Log = Join-Path -Path $instanceRoot -ChildPath 'logs\worker.log'
|
||||
}
|
||||
}
|
||||
|
||||
$paths = Get-WorkerPaths -Root $DataRoot -Type $WorkerType -Name $WorkerName
|
||||
|
||||
if (-not (Test-Path $paths.Metadata)) {
|
||||
Write-Host "No worker metadata found for $WorkerName ($WorkerType)." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$metadata = Get-Content -Path $paths.Metadata -Raw | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
Write-Host "Unable to read worker metadata: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (Test-Path $paths.Log) {
|
||||
# ensure log file exists but do not truncate
|
||||
$null = (Get-Item $paths.Log)
|
||||
} else {
|
||||
New-Item -Path $paths.Log -ItemType File -Force | Out-Null
|
||||
}
|
||||
|
||||
if (-not (Test-Path $paths.Command)) {
|
||||
New-Item -Path $paths.Command -ItemType File -Force | Out-Null
|
||||
}
|
||||
|
||||
function Send-WorkerCommand {
|
||||
param([string]$Value)
|
||||
|
||||
$clean = $Value.Trim()
|
||||
if (-not $clean) {
|
||||
return
|
||||
}
|
||||
|
||||
Add-Content -Path $paths.Command -Value $clean -Encoding UTF8
|
||||
Write-Host "Sent command '$clean' to $WorkerName." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
if ($CommandOnly) {
|
||||
if (-not $Command) {
|
||||
Write-Host "CommandOnly flag set but no command provided." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Send-WorkerCommand -Value $Command
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "Attaching to $WorkerName ($WorkerType) logs." -ForegroundColor Cyan
|
||||
Write-Host "Type commands and press Enter. Type 'detach' to exit session." -ForegroundColor Yellow
|
||||
|
||||
$logJob = Start-Job -ScriptBlock {
|
||||
param($LogPath)
|
||||
Get-Content -Path $LogPath -Tail 50 -Wait
|
||||
} -ArgumentList $paths.Log
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
$input = Read-Host "> "
|
||||
if ($null -eq $input) {
|
||||
continue
|
||||
}
|
||||
|
||||
$normalized = $input.Trim()
|
||||
if ($normalized.StartsWith(':')) {
|
||||
$normalized = $normalized.TrimStart(':').Trim()
|
||||
}
|
||||
|
||||
if ($normalized.Length -eq 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($normalized.ToLowerInvariant() -eq 'detach') {
|
||||
break
|
||||
}
|
||||
|
||||
Send-WorkerCommand -Value $input
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($logJob) {
|
||||
Stop-Job -Job $logJob -ErrorAction SilentlyContinue
|
||||
Receive-Job -Job $logJob -ErrorAction SilentlyContinue | Out-Null
|
||||
Remove-Job -Job $logJob -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Detached from worker $WorkerName." -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
407
remote_worker_controller.ps1
Normal file
407
remote_worker_controller.ps1
Normal file
@@ -0,0 +1,407 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WorkerName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WorkerType,
|
||||
|
||||
[string]$PayloadBase64,
|
||||
|
||||
[string]$PayloadBase64Path,
|
||||
|
||||
[string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'),
|
||||
|
||||
[int]$MaxRestarts = 5,
|
||||
|
||||
[int]$RestartDelaySeconds = 10
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
try {
|
||||
if ($Host -and $Host.Runspace) {
|
||||
[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace = $Host.Runspace
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Ignore runspace assignment errors - not critical for non-interactive execution
|
||||
}
|
||||
|
||||
# region Path setup
|
||||
$workerRoot = Join-Path -Path $DataRoot -ChildPath $WorkerType
|
||||
$instanceRoot = Join-Path -Path $workerRoot -ChildPath $WorkerName
|
||||
New-Item -ItemType Directory -Path $instanceRoot -Force | Out-Null
|
||||
|
||||
$logsRoot = Join-Path -Path $instanceRoot -ChildPath 'logs'
|
||||
$stateRoot = Join-Path -Path $instanceRoot -ChildPath 'state'
|
||||
New-Item -ItemType Directory -Path $logsRoot -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $stateRoot -Force | Out-Null
|
||||
|
||||
$logPath = Join-Path -Path $logsRoot -ChildPath 'worker.log'
|
||||
$metaPath = Join-Path -Path $stateRoot -ChildPath 'worker-info.json'
|
||||
$commandPath = Join-Path -Path $stateRoot -ChildPath 'commands.txt'
|
||||
$payloadPath = Join-Path -Path $stateRoot -ChildPath "payload.ps1"
|
||||
# endregion
|
||||
|
||||
# region Logging
|
||||
try {
|
||||
$logStream = [System.IO.FileStream]::new(
|
||||
$logPath,
|
||||
[System.IO.FileMode]::Append,
|
||||
[System.IO.FileAccess]::Write,
|
||||
[System.IO.FileShare]::ReadWrite
|
||||
)
|
||||
$logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)
|
||||
$logWriter.AutoFlush = $true
|
||||
}
|
||||
catch {
|
||||
# If we can't open the log file, write error to metadata and exit
|
||||
$errorMeta = [pscustomobject]@{
|
||||
WorkerName = $WorkerName
|
||||
WorkerType = $WorkerType
|
||||
Status = 'error'
|
||||
ControllerPid = $PID
|
||||
WorkerPid = $null
|
||||
Restarts = 0
|
||||
LastExitCode = 1
|
||||
LogPath = $logPath
|
||||
CommandPath = $commandPath
|
||||
PayloadPath = $payloadPath
|
||||
UpdatedAtUtc = (Get-Date).ToUniversalTime()
|
||||
ErrorMessage = "Failed to open log file: $($_.Exception.Message)"
|
||||
}
|
||||
$errorMeta | ConvertTo-Json -Depth 5 | Set-Content -Path $metaPath -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
Write-Error "Controller failed to initialize: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Create C# event handler class that doesn't require PowerShell runspace
|
||||
if (-not ("UnifiedWorkers.ProcessLogHandler" -as [type])) {
|
||||
$csharpCode = @'
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace UnifiedWorkers
|
||||
{
|
||||
public sealed class ProcessLogHandler
|
||||
{
|
||||
private readonly TextWriter _writer;
|
||||
private readonly string _prefix;
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public ProcessLogHandler(TextWriter writer, string prefix)
|
||||
{
|
||||
_writer = writer ?? throw new ArgumentNullException("writer");
|
||||
_prefix = prefix ?? throw new ArgumentNullException("prefix");
|
||||
}
|
||||
|
||||
public void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e == null || string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
try
|
||||
{
|
||||
var timestamp = DateTime.UtcNow.ToString("u");
|
||||
_writer.WriteLine(string.Format("[{0} {1}] {2}", _prefix, timestamp, e.Data));
|
||||
_writer.Flush();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore write errors to prevent cascading failures
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
Add-Type -TypeDefinition $csharpCode -ErrorAction Stop
|
||||
}
|
||||
|
||||
function Write-LogLine {
|
||||
param(
|
||||
[string]$Prefix,
|
||||
[string]$Message
|
||||
)
|
||||
|
||||
if (-not $logWriter) { return }
|
||||
$timestamp = (Get-Date).ToString('u')
|
||||
$logWriter.WriteLine("[$Prefix $timestamp] $Message")
|
||||
}
|
||||
|
||||
function Write-ControllerLog {
|
||||
param([string]$Message)
|
||||
Write-LogLine -Prefix 'CTRL' -Message $Message
|
||||
}
|
||||
|
||||
function Write-FatalLog {
|
||||
param([string]$Message)
|
||||
|
||||
try {
|
||||
Write-ControllerLog $Message
|
||||
}
|
||||
catch {
|
||||
$timestamp = (Get-Date).ToString('u')
|
||||
$fallback = "[CTRL $timestamp] $Message"
|
||||
try {
|
||||
[System.IO.File]::AppendAllText($logPath, $fallback + [Environment]::NewLine, [System.Text.Encoding]::UTF8)
|
||||
}
|
||||
catch {
|
||||
# last resort: write to host
|
||||
Write-Error $fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
# endregion
|
||||
|
||||
# region Helpers
|
||||
|
||||
function Resolve-PayloadBase64 {
|
||||
if ($PayloadBase64) {
|
||||
return $PayloadBase64.Trim()
|
||||
}
|
||||
|
||||
if ($PayloadBase64Path) {
|
||||
if (-not (Test-Path $PayloadBase64Path)) {
|
||||
throw "Payload file '$PayloadBase64Path' not found."
|
||||
}
|
||||
|
||||
$content = Get-Content -Path $PayloadBase64Path -Raw
|
||||
if ([string]::IsNullOrWhiteSpace($content)) {
|
||||
throw "Payload file '$PayloadBase64Path' is empty."
|
||||
}
|
||||
|
||||
return $content.Trim()
|
||||
}
|
||||
|
||||
throw "No payload data provided to controller."
|
||||
}
|
||||
|
||||
function Write-Metadata {
|
||||
param(
|
||||
[string]$Status,
|
||||
[nullable[int]]$WorkerPid = $null,
|
||||
[nullable[int]]$ControllerPid = $PID,
|
||||
[int]$Restarts = 0,
|
||||
[nullable[int]]$LastExitCode = $null
|
||||
)
|
||||
|
||||
$payload = [pscustomobject]@{
|
||||
WorkerName = $WorkerName
|
||||
WorkerType = $WorkerType
|
||||
Status = $Status
|
||||
ControllerPid = $ControllerPid
|
||||
WorkerPid = $WorkerPid
|
||||
Restarts = $Restarts
|
||||
LastExitCode = $LastExitCode
|
||||
LogPath = $logPath
|
||||
CommandPath = $commandPath
|
||||
PayloadPath = $payloadPath
|
||||
UpdatedAtUtc = (Get-Date).ToUniversalTime()
|
||||
}
|
||||
|
||||
$payload | ConvertTo-Json -Depth 5 | Set-Content -Path $metaPath -Encoding UTF8
|
||||
}
|
||||
|
||||
function Get-PendingCommands {
|
||||
if (-not (Test-Path $commandPath)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
try {
|
||||
$lines = Get-Content -Path $commandPath -ErrorAction Stop
|
||||
Remove-Item -Path $commandPath -Force -ErrorAction SilentlyContinue
|
||||
return $lines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
}
|
||||
catch {
|
||||
return @()
|
||||
}
|
||||
}
|
||||
# endregion
|
||||
|
||||
try {
|
||||
# record initial state before launching worker
|
||||
Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0
|
||||
|
||||
$resolvedPayloadBase64 = Resolve-PayloadBase64
|
||||
$PayloadBase64 = $resolvedPayloadBase64
|
||||
|
||||
try {
|
||||
# Write payload script to disk
|
||||
# The payload is base64-encoded UTF-16 (Unicode), so decode it properly
|
||||
Write-ControllerLog "Decoding payload base64 (length: $($resolvedPayloadBase64.Length))"
|
||||
$payloadBytes = [Convert]::FromBase64String($resolvedPayloadBase64)
|
||||
Write-ControllerLog "Decoded payload to $($payloadBytes.Length) bytes"
|
||||
|
||||
# Convert UTF-16 bytes back to string, then write as UTF-8 (PowerShell's preferred encoding)
|
||||
$payloadText = [Text.Encoding]::Unicode.GetString($payloadBytes)
|
||||
[IO.File]::WriteAllText($payloadPath, $payloadText, [Text.Encoding]::UTF8)
|
||||
Write-ControllerLog "Payload written to $payloadPath ($($payloadText.Length) characters)"
|
||||
}
|
||||
catch {
|
||||
Write-FatalLog "Unable to write payload: $($_.Exception.Message)"
|
||||
if ($_.Exception.InnerException) {
|
||||
Write-FatalLog "Inner exception: $($_.Exception.InnerException.Message)"
|
||||
}
|
||||
throw
|
||||
}
|
||||
|
||||
$restartCount = 0
|
||||
$controllerPid = $PID
|
||||
|
||||
while ($restartCount -le $MaxRestarts) {
|
||||
try {
|
||||
# Initialize worker process
|
||||
$psi = [System.Diagnostics.ProcessStartInfo]::new()
|
||||
$pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
|
||||
if ($pwsh) {
|
||||
$psi.FileName = $pwsh.Source
|
||||
}
|
||||
else {
|
||||
$psi.FileName = (Get-Command powershell -ErrorAction Stop).Source
|
||||
}
|
||||
|
||||
$psi.Arguments = "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$payloadPath`""
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardInput = $true
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.CreateNoWindow = $true
|
||||
|
||||
$workerProcess = New-Object System.Diagnostics.Process
|
||||
$workerProcess.StartInfo = $psi
|
||||
|
||||
if (-not $workerProcess.Start()) {
|
||||
throw "Failed to start worker process."
|
||||
}
|
||||
|
||||
Write-ControllerLog "Worker process started with PID $($workerProcess.Id)"
|
||||
Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount
|
||||
|
||||
# Check if process exited immediately
|
||||
if ($workerProcess.HasExited) {
|
||||
$exitCode = -1
|
||||
try {
|
||||
$exitCode = $workerProcess.ExitCode
|
||||
}
|
||||
catch {
|
||||
Write-ControllerLog "Unable to read immediate exit code: $($_.Exception.Message)"
|
||||
}
|
||||
Write-ControllerLog "Worker process exited immediately after start with code $exitCode"
|
||||
Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode
|
||||
if ($exitCode -eq 0) { break }
|
||||
# Continue to restart logic below
|
||||
}
|
||||
else {
|
||||
$stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')
|
||||
$stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')
|
||||
|
||||
$outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived
|
||||
$errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived
|
||||
|
||||
$workerProcess.add_OutputDataReceived($outputHandler)
|
||||
$workerProcess.add_ErrorDataReceived($errorHandler)
|
||||
$workerProcess.BeginOutputReadLine()
|
||||
$workerProcess.BeginErrorReadLine()
|
||||
Write-ControllerLog "Output handlers set up successfully"
|
||||
|
||||
# Give process a moment to start, then check again
|
||||
Start-Sleep -Milliseconds 200
|
||||
if ($workerProcess.HasExited) {
|
||||
$exitCode = -1
|
||||
try {
|
||||
$exitCode = $workerProcess.ExitCode
|
||||
}
|
||||
catch {
|
||||
Write-ControllerLog "Unable to read exit code after delay: $($_.Exception.Message)"
|
||||
}
|
||||
Write-ControllerLog "Worker process exited after 200ms delay with code $exitCode"
|
||||
Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode
|
||||
if ($exitCode -eq 0) { break }
|
||||
# Continue to restart logic below
|
||||
}
|
||||
else {
|
||||
Write-ControllerLog "Worker process is running, entering monitoring loop"
|
||||
|
||||
while (-not $workerProcess.HasExited) {
|
||||
$commands = Get-PendingCommands
|
||||
foreach ($command in $commands) {
|
||||
$trimmed = $command.Trim()
|
||||
if (-not $trimmed) { continue }
|
||||
|
||||
Write-ControllerLog "Received command '$trimmed'"
|
||||
try {
|
||||
$workerProcess.StandardInput.WriteLine($trimmed)
|
||||
$workerProcess.StandardInput.Flush()
|
||||
}
|
||||
catch {
|
||||
Write-ControllerLog "Failed to forward command '$trimmed': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
if ($trimmed -ieq 'quit') {
|
||||
Write-ControllerLog "Quit command issued. Waiting for worker to exit."
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
# End of monitoring loop - process has exited
|
||||
Write-ControllerLog "Worker process exited, exiting monitoring loop"
|
||||
}
|
||||
}
|
||||
|
||||
# Wait for process to fully exit before reading exit code
|
||||
$workerProcess.WaitForExit(1000)
|
||||
|
||||
$exitCode = -1
|
||||
try {
|
||||
$exitCode = $workerProcess.ExitCode
|
||||
}
|
||||
catch {
|
||||
Write-ControllerLog "Unable to read worker exit code: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-ControllerLog "Worker exited with code $exitCode"
|
||||
Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-ControllerLog "Controller error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$restartCount++
|
||||
if ($restartCount -gt $MaxRestarts) {
|
||||
Write-ControllerLog "Maximum restart attempts reached. Controller stopping."
|
||||
break
|
||||
}
|
||||
|
||||
Write-ControllerLog "Restarting worker in $RestartDelaySeconds seconds (attempt $restartCount of $MaxRestarts)."
|
||||
Start-Sleep -Seconds $RestartDelaySeconds
|
||||
}
|
||||
|
||||
Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount
|
||||
Write-ControllerLog "Controller exiting."
|
||||
}
|
||||
catch {
|
||||
Write-FatalLog "Fatal controller error: $($_.Exception.Message)"
|
||||
if ($_.ScriptStackTrace) {
|
||||
Write-FatalLog "Stack: $($_.ScriptStackTrace)"
|
||||
}
|
||||
throw
|
||||
}
|
||||
finally {
|
||||
if ($logWriter) {
|
||||
$logWriter.Dispose()
|
||||
}
|
||||
if ($logStream) {
|
||||
$logStream.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,372 @@ $workers = @(
|
||||
}
|
||||
)
|
||||
|
||||
$script:ControllerScriptBase64 = $null
|
||||
$script:AttachHelperScriptBase64 = $null
|
||||
$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
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-RemotePowerShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][object]$Worker,
|
||||
[Parameter(Mandatory = $true)][string]$Script,
|
||||
[switch]$Interactive
|
||||
)
|
||||
|
||||
$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
|
||||
|
||||
return $execExit
|
||||
}
|
||||
|
||||
function Ensure-ControllerDeployed {
|
||||
param([object]$Worker)
|
||||
$controllerBase64 = Get-ControllerScriptBase64
|
||||
$script = @"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
||||
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
||||
`$controllerPath = Join-Path `$dataRoot 'controller.ps1'
|
||||
[IO.File]::WriteAllBytes(`$controllerPath, [Convert]::FromBase64String('$controllerBase64'))
|
||||
"@
|
||||
Invoke-RemotePowerShell -Worker $Worker -Script $script | Out-Null
|
||||
}
|
||||
|
||||
function Ensure-AttachHelperDeployed {
|
||||
param([object]$Worker)
|
||||
$helperBase64 = Get-AttachHelperScriptBase64
|
||||
$script = @"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
||||
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
||||
`$attachPath = Join-Path `$dataRoot 'attach-helper.ps1'
|
||||
[IO.File]::WriteAllBytes(`$attachPath, [Convert]::FromBase64String('$helperBase64'))
|
||||
"@
|
||||
Invoke-RemotePowerShell -Worker $Worker -Script $script | Out-Null
|
||||
}
|
||||
|
||||
function Get-EnsureWorkerScript {
|
||||
param(
|
||||
[string]$WorkerName,
|
||||
[string]$WorkerType,
|
||||
[string]$PayloadBase64
|
||||
)
|
||||
|
||||
$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'
|
||||
|
||||
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 "Unable to parse metadata for `$workerName. Starting new controller." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
if (`$shouldStart) {
|
||||
`$pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
|
||||
if (`$pwsh) {
|
||||
`$psExe = `$pwsh.Source
|
||||
}
|
||||
else {
|
||||
`$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
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
function Ensure-PersistentWorker {
|
||||
param(
|
||||
[object]$Worker,
|
||||
[string]$WorkerType,
|
||||
[string]$PayloadScript
|
||||
)
|
||||
|
||||
Write-Host "[$($Worker.Name)] Ensuring worker is running under controller..." -ForegroundColor Cyan
|
||||
Ensure-ControllerDeployed -Worker $Worker
|
||||
$payloadBase64 = ConvertTo-Base64Unicode -Content $PayloadScript
|
||||
$ensureScript = Get-EnsureWorkerScript -WorkerName $Worker.Name -WorkerType $WorkerType -PayloadBase64 $payloadBase64
|
||||
$result = Invoke-RemotePowerShell -Worker $Worker -Script $ensureScript
|
||||
if ($result -ne 0) {
|
||||
Write-Host "[$($Worker.Name)] Remote ensure command exited with code $result." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$parts = Get-WorkerConnectionParts -RawArgs $Worker.SSHArgs -DefaultHost $Worker.Name
|
||||
$sshArgs = Build-SshArgsFromParts -Parts $parts -Interactive:(!$CommandOnly)
|
||||
$remoteBasePath = Get-WorkerBasePath -Worker $Worker -ConnectionParts $parts
|
||||
$remoteHelper = Join-Path $remoteBasePath 'attach-helper.ps1'
|
||||
$quotedArgs = ($paramsBlock | ForEach-Object { '"' + ($_ -replace '"','""') + '"' }) -join ' '
|
||||
$remoteCmd = "powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$remoteHelper`" $quotedArgs"
|
||||
|
||||
& ssh @sshArgs $remoteCmd
|
||||
}
|
||||
|
||||
# FUNCTIONS
|
||||
|
||||
# This function generates the standard PowerShell remote command
|
||||
@@ -259,56 +625,9 @@ function Start-StandardWorker {
|
||||
[object]$Worker
|
||||
)
|
||||
|
||||
$retryCount = 0
|
||||
$retryDelay = 15 # seconds between retries
|
||||
$workerRestarted = $false
|
||||
|
||||
while ($true) { # Changed to infinite loop
|
||||
if ($retryCount -gt 0) {
|
||||
Write-Host "`nRestarting worker process (Attempt $($retryCount + 1))..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds $retryDelay
|
||||
$workerRestarted = $true
|
||||
}
|
||||
|
||||
Write-Host "Connecting to $($Worker.Name)..." -ForegroundColor Cyan
|
||||
if ($workerRestarted) {
|
||||
Write-Host "Worker was restarted due to disconnection or crash." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
try {
|
||||
$remoteCommand = Get-RemoteStandardWorkerCommand
|
||||
|
||||
# Encode the command to handle special characters
|
||||
$bytes = [System.Text.Encoding]::Unicode.GetBytes($remoteCommand)
|
||||
$encodedCommand = [Convert]::ToBase64String($bytes)
|
||||
|
||||
# Execute the encoded command on the remote machine
|
||||
Write-Host "Connecting to $($Worker.Name) and executing worker script..." -ForegroundColor Yellow
|
||||
|
||||
# Add SSH keepalive settings to reduce chance of random disconnections
|
||||
$sshCommand = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) ""powershell -EncodedCommand $encodedCommand"""
|
||||
|
||||
# Execute the SSH command and capture the exit code
|
||||
Invoke-Expression $sshCommand
|
||||
$sshExitCode = $LASTEXITCODE
|
||||
|
||||
# Check if SSH command completed successfully
|
||||
if ($sshExitCode -eq 0) {
|
||||
Write-Host "`nWorker completed successfully. Restarting automatically..." -ForegroundColor Green
|
||||
Start-Sleep -Seconds 2 # Brief pause before restarting
|
||||
$retryCount = 0 # Reset counter for successful completion
|
||||
continue # Continue the loop instead of breaking
|
||||
} else {
|
||||
throw "Worker process exited with code: $sshExitCode"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$retryCount++
|
||||
Write-Host "`nAn error occurred while running worker on $($Worker.Name):" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "`nAttempting to restart worker in $retryDelay seconds..." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
$payloadScript = Get-RemoteStandardWorkerCommand
|
||||
Ensure-PersistentWorker -Worker $Worker -WorkerType 'flamenco' -PayloadScript $payloadScript
|
||||
Invoke-WorkerAttach -Worker $Worker -WorkerType 'flamenco'
|
||||
}
|
||||
|
||||
# This function launches the CMD worker
|
||||
@@ -318,56 +637,9 @@ function Start-CmdWorker {
|
||||
[object]$Worker
|
||||
)
|
||||
|
||||
$retryCount = 0
|
||||
$retryDelay = 5 # seconds between retries
|
||||
$workerRestarted = $false
|
||||
|
||||
while ($true) { # Changed to infinite loop
|
||||
if ($retryCount -gt 0) {
|
||||
Write-Host "`nRestarting worker process (Attempt $($retryCount + 1))..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds $retryDelay
|
||||
$workerRestarted = $true
|
||||
}
|
||||
|
||||
Write-Host "Connecting to $($Worker.Name) (CMD mode)..." -ForegroundColor Cyan
|
||||
if ($workerRestarted) {
|
||||
Write-Host "Worker was restarted due to disconnection or crash." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
try {
|
||||
$remoteCommand = Get-RemoteCmdWorkerCommand
|
||||
|
||||
# Encode the command to handle special characters
|
||||
$bytes = [System.Text.Encoding]::Unicode.GetBytes($remoteCommand)
|
||||
$encodedCommand = [Convert]::ToBase64String($bytes)
|
||||
|
||||
# Execute the encoded command on the remote machine
|
||||
Write-Host "Connecting to $($Worker.Name) and executing CMD worker script..." -ForegroundColor Yellow
|
||||
|
||||
# Add SSH keepalive settings to reduce chance of random disconnections
|
||||
$sshCommand = "ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) ""powershell -EncodedCommand $encodedCommand"""
|
||||
|
||||
# Execute the SSH command and capture the exit code
|
||||
Invoke-Expression $sshCommand
|
||||
$sshExitCode = $LASTEXITCODE
|
||||
|
||||
# Check if SSH command completed successfully
|
||||
if ($sshExitCode -eq 0) {
|
||||
Write-Host "`nWorker completed successfully. Restarting automatically..." -ForegroundColor Green
|
||||
Start-Sleep -Seconds 2 # Brief pause before restarting
|
||||
$retryCount = 0 # Reset counter for successful completion
|
||||
continue # Continue the loop instead of breaking
|
||||
} else {
|
||||
throw "Worker process exited with code: $sshExitCode"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$retryCount++
|
||||
Write-Host "`nAn error occurred while running worker on $($Worker.Name):" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "`nAttempting to restart worker in $retryDelay seconds..." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
$payloadScript = Get-RemoteCmdWorkerCommand
|
||||
Ensure-PersistentWorker -Worker $Worker -WorkerType 'flamenco' -PayloadScript $payloadScript
|
||||
Invoke-WorkerAttach -Worker $Worker -WorkerType 'flamenco'
|
||||
}
|
||||
|
||||
# This function launches ALL workers in Windows Terminal tabs
|
||||
@@ -377,173 +649,21 @@ function Start-AllWorkers {
|
||||
[string]$WorkerType
|
||||
)
|
||||
|
||||
Write-Host "Launching ALL $WorkerType workers in Windows Terminal tabs..." -ForegroundColor Cyan
|
||||
Write-Host "Ensuring ALL $WorkerType workers are running under controllers..." -ForegroundColor Cyan
|
||||
|
||||
try {
|
||||
# First, check if Windows Terminal is available
|
||||
if (-not (Get-Command wt.exe -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Windows Terminal (wt.exe) not found. Falling back to separate windows." -ForegroundColor Yellow
|
||||
$useTerminal = $false
|
||||
} else {
|
||||
$useTerminal = $true
|
||||
}
|
||||
|
||||
foreach ($worker in $workers) {
|
||||
# Create a new PowerShell script with a unique name for this worker
|
||||
$tempScriptPath = [System.IO.Path]::GetTempFileName() + ".ps1"
|
||||
|
||||
# Create different script content based on worker type
|
||||
if ($WorkerType -eq "CMD") {
|
||||
# CMD workers get retry logic at the local level
|
||||
$scriptContent = @"
|
||||
# Wrap everything in a try-catch to prevent script termination
|
||||
try {
|
||||
Write-Host 'Launching $WorkerType worker for $($worker.Name)' -ForegroundColor Cyan
|
||||
|
||||
`$retryCount = 0
|
||||
`$retryDelay = 5 # seconds between retries
|
||||
`$workerRestarted = `$false
|
||||
|
||||
while (`$true) { # Changed to infinite loop
|
||||
try {
|
||||
if (`$retryCount -gt 0) {
|
||||
Write-Host "`nRestarting worker process (Attempt `$(`$retryCount + 1))..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds `$retryDelay
|
||||
`$workerRestarted = `$true
|
||||
}
|
||||
|
||||
Write-Host "Connecting to $($worker.Name) ($WorkerType mode)..." -ForegroundColor Cyan
|
||||
if (`$workerRestarted) {
|
||||
Write-Host "Worker was restarted due to disconnection or crash." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Get remote command
|
||||
`$remoteCommand = @'
|
||||
$(Get-RemoteSimplifiedCmdWorkerCommand)
|
||||
'@
|
||||
|
||||
# Encode the command
|
||||
`$bytes = [System.Text.Encoding]::Unicode.GetBytes(`$remoteCommand)
|
||||
`$encodedCommand = [Convert]::ToBase64String(`$bytes)
|
||||
|
||||
# Execute SSH command with keepalive settings and capture exit code
|
||||
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($worker.SSHArgs) "powershell -EncodedCommand `$encodedCommand"
|
||||
`$sshExitCode = `$LASTEXITCODE
|
||||
|
||||
# Check if SSH command completed successfully
|
||||
if (`$sshExitCode -eq 0) {
|
||||
Write-Host "`nWorker completed successfully. Restarting automatically..." -ForegroundColor Green
|
||||
Start-Sleep -Seconds 2 # Brief pause before restarting
|
||||
`$retryCount = 0 # Reset counter for successful completion
|
||||
continue # Continue the loop instead of breaking
|
||||
} else {
|
||||
throw "SSH command failed with exit code: `$sshExitCode"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
`$retryCount++
|
||||
Write-Host "An error occurred while connecting to $($worker.Name):" -ForegroundColor Red
|
||||
Write-Host `$_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "Attempting to reconnect in `$retryDelay seconds..." -ForegroundColor Yellow
|
||||
# Don't rethrow - we want to continue the retry loop
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# This outer catch block is for any unexpected errors that might terminate the script
|
||||
Write-Host "`nCRITICAL ERROR: Script encountered an unexpected error:" -ForegroundColor Red
|
||||
Write-Host `$_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "`nRestarting the entire worker process in 5 seconds..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 5
|
||||
# Restart the script by calling itself
|
||||
& `$MyInvocation.MyCommand.Path
|
||||
}
|
||||
"@
|
||||
} else {
|
||||
# Standard workers keep the original retry logic
|
||||
$scriptContent = @"
|
||||
# Wrap everything in a try-catch to prevent script termination
|
||||
try {
|
||||
Write-Host 'Launching $WorkerType worker for $($worker.Name)' -ForegroundColor Cyan
|
||||
|
||||
`$retryCount = 0
|
||||
`$retryDelay = 5 # seconds between retries
|
||||
|
||||
while (`$true) { # Changed to infinite loop
|
||||
try {
|
||||
if (`$retryCount -gt 0) {
|
||||
Write-Host "Retry attempt `$retryCount..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds `$retryDelay
|
||||
}
|
||||
|
||||
# Get remote command
|
||||
`$remoteCommand = @'
|
||||
$(Get-RemoteStandardWorkerCommand)
|
||||
'@
|
||||
|
||||
# Encode the command
|
||||
`$bytes = [System.Text.Encoding]::Unicode.GetBytes(`$remoteCommand)
|
||||
`$encodedCommand = [Convert]::ToBase64String(`$bytes)
|
||||
|
||||
# Execute SSH command with keepalive settings
|
||||
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($worker.SSHArgs) "powershell -EncodedCommand `$encodedCommand"
|
||||
`$sshExitCode = `$LASTEXITCODE
|
||||
|
||||
# Check SSH exit code and handle accordingly
|
||||
if (`$sshExitCode -eq 0) {
|
||||
Write-Host "`nWorker completed successfully. Restarting automatically..." -ForegroundColor Green
|
||||
Start-Sleep -Seconds 2 # Brief pause before restarting
|
||||
`$retryCount = 0 # Reset counter for successful completion
|
||||
continue # Continue the loop instead of breaking
|
||||
} else {
|
||||
throw "SSH command failed with exit code: `$sshExitCode"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
`$retryCount++
|
||||
Write-Host "An error occurred while connecting to $($worker.Name):" -ForegroundColor Red
|
||||
Write-Host `$_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "Attempting to reconnect in `$retryDelay seconds..." -ForegroundColor Yellow
|
||||
# Don't rethrow - we want to continue the retry loop
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# This outer catch block is for any unexpected errors that might terminate the script
|
||||
Write-Host "`nCRITICAL ERROR: Script encountered an unexpected error:" -ForegroundColor Red
|
||||
Write-Host `$_.Exception.Message -ForegroundColor Red
|
||||
Write-Host "`nRestarting the entire worker process in 5 seconds..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 5
|
||||
# Restart the script by calling itself
|
||||
& `$MyInvocation.MyCommand.Path
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
# Write the script to file
|
||||
Set-Content -Path $tempScriptPath -Value $scriptContent
|
||||
|
||||
if ($useTerminal) {
|
||||
# Launch in a new Windows Terminal tab
|
||||
$tabTitle = "$($worker.Name) - $WorkerType Worker"
|
||||
Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$tabTitle`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScriptPath`""
|
||||
} else {
|
||||
# Fallback to separate window if Windows Terminal is not available
|
||||
Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScriptPath`""
|
||||
}
|
||||
|
||||
Write-Host "Started $($worker.Name) ($WorkerType) worker in a new tab." -ForegroundColor Green
|
||||
Start-Sleep -Milliseconds 300 # Small delay between launches
|
||||
}
|
||||
|
||||
Write-Host "`nAll $WorkerType worker scripts have been launched in Windows Terminal tabs." -ForegroundColor Cyan
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error launching workers: $($_.Exception.Message)" -ForegroundColor Red
|
||||
$payloadScript = if ($WorkerType -eq 'CMD') {
|
||||
Get-RemoteCmdWorkerCommand
|
||||
} else {
|
||||
Get-RemoteStandardWorkerCommand
|
||||
}
|
||||
|
||||
foreach ($worker in $workers) {
|
||||
Ensure-PersistentWorker -Worker $worker -WorkerType 'flamenco' -PayloadScript $payloadScript
|
||||
}
|
||||
|
||||
Write-Host "All workers processed. Attach to any worker from the menu to monitor output." -ForegroundColor Green
|
||||
Write-Host "Press Enter to return to the menu..." -ForegroundColor Green
|
||||
Read-Host
|
||||
Read-Host | Out-Null
|
||||
}
|
||||
|
||||
# Main menu loop
|
||||
|
||||
@@ -32,6 +32,531 @@ $workers = @(
|
||||
@{ ID = 6; Name = "i9-13ks"; SSHArgs = "-t -p 22146 i9-13ks"; Enabled = $true }
|
||||
)
|
||||
|
||||
$script:ControllerScriptBase64 = $null
|
||||
$script:AttachHelperScriptBase64 = $null
|
||||
$script:WorkerBasePathCache = @{}
|
||||
|
||||
function Remove-ClixmlNoise {
|
||||
param([object[]]$Lines)
|
||||
|
||||
$noisePatterns = @(
|
||||
'^#<\s*CLIXML',
|
||||
'^\s*<Objs\b', '^\s*</Objs>',
|
||||
'^\s*<Obj\b', '^\s*</Obj>',
|
||||
'^\s*<TN\b', '^\s*</TN>',
|
||||
'^\s*<MS\b', '^\s*</MS>',
|
||||
'^\s*<PR\b', '^\s*</PR>',
|
||||
'^\s*<I64\b', '^\s*</I64>',
|
||||
'^\s*<AI\b', '^\s*</AI>',
|
||||
'^\s*<Nil\b', '^\s*</Nil>',
|
||||
'^\s*<PI\b', '^\s*</PI>',
|
||||
'^\s*<PC\b', '^\s*</PC>',
|
||||
'^\s*<SR\b', '^\s*</SR>',
|
||||
'^\s*<SD\b', '^\s*</SD>',
|
||||
'^\s*<S\b', '^\s*</S>'
|
||||
)
|
||||
|
||||
$filtered = @()
|
||||
foreach ($entry in $Lines) {
|
||||
if ($null -eq $entry) { continue }
|
||||
$text = $entry.ToString()
|
||||
$isNoise = $false
|
||||
foreach ($pattern in $noisePatterns) {
|
||||
if ($text -match $pattern) {
|
||||
$isNoise = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $isNoise) {
|
||||
$filtered += $text
|
||||
}
|
||||
}
|
||||
return $filtered
|
||||
}
|
||||
|
||||
function Write-FilteredSshOutput {
|
||||
param([object[]]$Lines)
|
||||
$clean = Remove-ClixmlNoise -Lines $Lines
|
||||
foreach ($line in $clean) {
|
||||
Write-Host $line
|
||||
}
|
||||
}
|
||||
|
||||
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 -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand $encoded"
|
||||
$rawOutput = & ssh @sshArgs $remoteCmd 2>&1
|
||||
$output = Remove-ClixmlNoise -Lines $rawOutput
|
||||
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
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-RemotePowerShell {
|
||||
param(
|
||||
[object]$Worker,
|
||||
[string]$Script,
|
||||
[switch]$Interactive
|
||||
)
|
||||
|
||||
$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 -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand " + [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($ensureScript))
|
||||
$ensureOutput = & ssh @sshBaseArgs $ensureCmd 2>&1
|
||||
$ensureExit = $LASTEXITCODE
|
||||
Write-FilteredSshOutput -Lines $ensureOutput
|
||||
if ($ensureExit -ne 0) {
|
||||
Remove-Item $localTemp -ErrorAction SilentlyContinue
|
||||
return $ensureExit
|
||||
}
|
||||
|
||||
$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 -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -File `"$remoteScriptWin`""
|
||||
$execOutput = & ssh @sshBaseArgs $execCmd 2>&1
|
||||
$execExit = $LASTEXITCODE
|
||||
Write-FilteredSshOutput -Lines $execOutput
|
||||
|
||||
$cleanupScript = "Remove-Item -LiteralPath '$remoteScriptWin' -ErrorAction SilentlyContinue"
|
||||
$cleanupCmd = "powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand " + [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cleanupScript))
|
||||
$cleanupOutput = & ssh @sshBaseArgs $cleanupCmd 2>&1
|
||||
Write-FilteredSshOutput -Lines $cleanupOutput
|
||||
|
||||
return [int]$execExit
|
||||
}
|
||||
|
||||
function Resolve-ExitCode {
|
||||
param($Value)
|
||||
|
||||
if ($Value -is [System.Array]) {
|
||||
for ($i = $Value.Count - 1; $i -ge 0; $i--) {
|
||||
$candidate = Resolve-ExitCode -Value $Value[$i]
|
||||
if ($candidate -ne $null) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if ($Value -is [int]) {
|
||||
return $Value
|
||||
}
|
||||
|
||||
$text = $Value
|
||||
if ($null -eq $text) {
|
||||
return 0
|
||||
}
|
||||
|
||||
$parsed = 0
|
||||
if ([int]::TryParse($text.ToString(), [ref]$parsed)) {
|
||||
return $parsed
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function Ensure-ControllerDeployed {
|
||||
param([object]$Worker)
|
||||
$controllerBase64 = Get-ControllerScriptBase64
|
||||
$script = @"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
||||
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
||||
`$controllerPath = Join-Path `$dataRoot 'controller.ps1'
|
||||
[IO.File]::WriteAllBytes(`$controllerPath, [Convert]::FromBase64String('$controllerBase64'))
|
||||
"@
|
||||
$exit = Resolve-ExitCode (Invoke-RemotePowerShell -Worker $Worker -Script $script)
|
||||
if ($exit -ne 0) {
|
||||
throw "Controller deployment failed on $($Worker.Name) (exit $exit)."
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-AttachHelperDeployed {
|
||||
param([object]$Worker)
|
||||
$helperBase64 = Get-AttachHelperScriptBase64
|
||||
$script = @"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
`$dataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'
|
||||
New-Item -ItemType Directory -Path `$dataRoot -Force | Out-Null
|
||||
`$attachPath = Join-Path `$dataRoot 'attach-helper.ps1'
|
||||
[IO.File]::WriteAllBytes(`$attachPath, [Convert]::FromBase64String('$helperBase64'))
|
||||
"@
|
||||
$exit = Resolve-ExitCode (Invoke-RemotePowerShell -Worker $Worker -Script $script)
|
||||
if ($exit -ne 0) {
|
||||
throw "Attach helper deployment failed on $($Worker.Name) (exit $exit)."
|
||||
}
|
||||
}
|
||||
|
||||
function Get-EnsureWorkerScript {
|
||||
param(
|
||||
[string]$WorkerName,
|
||||
[string]$WorkerType,
|
||||
[string]$PayloadBase64
|
||||
)
|
||||
|
||||
$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
|
||||
`$logsRoot = Join-Path `$instanceRoot 'logs'
|
||||
`$stateRoot = Join-Path `$instanceRoot 'state'
|
||||
New-Item -ItemType Directory -Path `$logsRoot -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path `$stateRoot -Force | Out-Null
|
||||
`$logPath = Join-Path `$logsRoot 'worker.log'
|
||||
`$commandPath = Join-Path `$stateRoot 'commands.txt'
|
||||
`$payloadPath = Join-Path `$stateRoot 'payload.ps1'
|
||||
`$payloadBase64Path = Join-Path `$stateRoot 'payload.b64'
|
||||
if (-not (Test-Path `$logPath)) { New-Item -Path `$logPath -ItemType File -Force | Out-Null }
|
||||
if (-not (Test-Path `$commandPath)) { New-Item -Path `$commandPath -ItemType File -Force | Out-Null }
|
||||
[IO.File]::WriteAllText(`$payloadBase64Path, `$payloadBase64, [System.Text.Encoding]::UTF8)
|
||||
`$metaPath = Join-Path `$instanceRoot 'state\worker-info.json'
|
||||
`$controllerPath = Join-Path `$dataRoot 'controller.ps1'
|
||||
|
||||
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) {
|
||||
`$initialMeta = [pscustomobject]@{
|
||||
WorkerName = `$workerName
|
||||
WorkerType = `$workerType
|
||||
Status = 'launching'
|
||||
ControllerPid = `$null
|
||||
WorkerPid = `$null
|
||||
Restarts = 0
|
||||
LastExitCode = `$null
|
||||
LogPath = `$logPath
|
||||
CommandPath = `$commandPath
|
||||
PayloadPath = `$payloadPath
|
||||
UpdatedAtUtc = (Get-Date).ToUniversalTime()
|
||||
} | ConvertTo-Json -Depth 5
|
||||
`$initialMeta | Set-Content -Path `$metaPath -Encoding UTF8
|
||||
|
||||
`$pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
|
||||
if (`$pwsh) {
|
||||
`$psExe = `$pwsh.Source
|
||||
}
|
||||
else {
|
||||
`$psExe = (Get-Command powershell -ErrorAction Stop).Source
|
||||
}
|
||||
|
||||
`$controllerArgs = @(
|
||||
'-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',
|
||||
'-File',"`$controllerPath",
|
||||
'-WorkerName',"`$workerName",
|
||||
'-WorkerType',"`$workerType",
|
||||
'-PayloadBase64Path',"`$payloadBase64Path"
|
||||
)
|
||||
|
||||
Start-Process -FilePath `$psExe -ArgumentList `$controllerArgs -WindowStyle Hidden | Out-Null
|
||||
Write-Host "Worker `$workerName started under controller." -ForegroundColor Green
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
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
|
||||
$exit = Resolve-ExitCode (Invoke-RemotePowerShell -Worker $Worker -Script $ensureScript)
|
||||
if ($exit -ne 0) {
|
||||
throw "Worker ensure script failed on $($Worker.Name) (exit $exit)."
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$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"
|
||||
|
||||
& ssh @sshArgs $remoteCmd
|
||||
}
|
||||
|
||||
function Get-RemoteSheepItCommand {
|
||||
param(
|
||||
[string]$RenderKey,
|
||||
@@ -40,15 +565,11 @@ function Get-RemoteSheepItCommand {
|
||||
|
||||
$safeKey = $RenderKey -replace "'", "''"
|
||||
$safeUser = $Username -replace "'", "''"
|
||||
$userLine = if ([string]::IsNullOrWhiteSpace($Username)) {
|
||||
''
|
||||
} else {
|
||||
" Write-Host 'Authenticating as ${safeUser} via render key.' -ForegroundColor Yellow`n"
|
||||
}
|
||||
|
||||
$urlLiteral = '@(' + (($SheepItJarUrls | ForEach-Object { "'$_'" }) -join ', ') + ')'
|
||||
|
||||
@"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
|
||||
try {
|
||||
@@ -140,6 +661,7 @@ try {
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
catch {
|
||||
Write-Host ('Error: {0}' -f `$_.Exception.Message) -ForegroundColor Red
|
||||
Write-Host ('Stack trace: {0}' -f `$_.ScriptStackTrace) -ForegroundColor DarkRed
|
||||
@@ -147,71 +669,56 @@ catch {
|
||||
"@
|
||||
}
|
||||
|
||||
function New-SheepItSessionScript {
|
||||
param(
|
||||
[object]$Worker,
|
||||
[string]$EncodedCommand
|
||||
)
|
||||
|
||||
@"
|
||||
Write-Host 'Connecting to $($Worker.Name)...' -ForegroundColor Cyan
|
||||
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=30 $($Worker.SSHArgs) `"powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand`"
|
||||
Write-Host "`nSSH session ended." -ForegroundColor Yellow
|
||||
Read-Host "Press Enter to close"
|
||||
"@
|
||||
}
|
||||
|
||||
function Start-SheepItTab {
|
||||
param(
|
||||
[string]$Title,
|
||||
[string]$Content
|
||||
)
|
||||
|
||||
$tempScript = [System.IO.Path]::GetTempFileName() + '.ps1'
|
||||
Set-Content -Path $tempScript -Value $Content -Encoding UTF8
|
||||
|
||||
if (Get-Command wt.exe -ErrorAction SilentlyContinue) {
|
||||
Start-Process wt.exe -ArgumentList "-w 0 new-tab --title `"$Title`" powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
} else {
|
||||
Start-Process powershell -ArgumentList "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempScript`""
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
function Start-SheepItWorker {
|
||||
param([object]$Worker)
|
||||
|
||||
if (-not $Worker.Enabled) {
|
||||
Write-Host "$($Worker.Name) is not enabled for SheepIt." -ForegroundColor Yellow
|
||||
try {
|
||||
Write-Host "Ensuring SheepIt controller on $($Worker.Name)..." -ForegroundColor Cyan
|
||||
Ensure-SheepItWorkerController -Worker $Worker
|
||||
}
|
||||
catch {
|
||||
Write-Host "Failed to ensure controller on $($Worker.Name): $($_.Exception.Message)" -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
Initialize-SheepItCredentials
|
||||
if (-not (Wait-WorkerMetadata -Worker $Worker -WorkerType 'sheepit' -TimeoutSeconds 30)) {
|
||||
Write-Host "Worker metadata did not appear on $($Worker.Name). Check controller logs under %LocalAppData%\UnifiedWorkers." -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
$remoteCommand = Get-RemoteSheepItCommand -RenderKey $script:SheepItRenderKey -Username $script:SheepItUsername
|
||||
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($remoteCommand))
|
||||
$sessionScript = New-SheepItSessionScript -Worker $Worker -EncodedCommand $encoded
|
||||
$title = "$($Worker.Name) - SheepIt"
|
||||
|
||||
Start-SheepItTab -Title $title -Content $sessionScript
|
||||
Write-Host "Opened SheepIt session for $($Worker.Name) in a new terminal tab." -ForegroundColor Green
|
||||
Write-Host "Controller ready. Attaching to SheepIt worker on $($Worker.Name)..." -ForegroundColor Cyan
|
||||
Invoke-WorkerAttach -Worker $Worker -WorkerType 'sheepit'
|
||||
}
|
||||
|
||||
function Start-AllSheepIt {
|
||||
Initialize-SheepItCredentials
|
||||
|
||||
$targets = $workers | Where-Object { $_.Enabled }
|
||||
|
||||
if (-not $targets) {
|
||||
Write-Host "No systems are ready for SheepIt." -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($worker in $targets) {
|
||||
Start-SheepItWorker -Worker $worker
|
||||
Start-Sleep -Milliseconds 200
|
||||
foreach ($worker in $workers | Where-Object { $_.Enabled }) {
|
||||
Ensure-SheepItWorkerController -Worker $worker
|
||||
}
|
||||
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)
|
||||
|
||||
foreach ($worker in $workers | Where-Object { $_.Enabled }) {
|
||||
Write-Host "[$($worker.Name)] Sending '$CommandText'..." -ForegroundColor Gray
|
||||
Invoke-WorkerAttach -Worker $worker -WorkerType 'sheepit' -CommandOnly -Command $CommandText
|
||||
}
|
||||
Write-Host "Command '$CommandText' dispatched to all enabled workers." -ForegroundColor Green
|
||||
Read-Host "Press Enter to continue" | Out-Null
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Select-SheepItWorker {
|
||||
while ($true) {
|
||||
Show-Header
|
||||
@@ -256,20 +763,25 @@ Initialize-SheepItCredentials
|
||||
while ($true) {
|
||||
Show-Header
|
||||
Write-Host "Main Menu:" -ForegroundColor Magenta
|
||||
Write-Host "1. Launch SheepIt on a single system" -ForegroundColor Yellow
|
||||
Write-Host "2. Launch SheepIt on all ready systems" -ForegroundColor Yellow
|
||||
Write-Host "3. Exit" -ForegroundColor Yellow
|
||||
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
|
||||
|
||||
$choice = Read-Host "Select option (1-3)"
|
||||
$choice = Read-Host "Select option (1-6)"
|
||||
|
||||
if ($choice -eq '6') {
|
||||
break
|
||||
}
|
||||
|
||||
switch ($choice) {
|
||||
'1' { Select-SheepItWorker }
|
||||
'2' {
|
||||
Start-AllSheepIt
|
||||
Write-Host
|
||||
Read-Host "Press Enter to return to the main menu" | Out-Null
|
||||
}
|
||||
'3' { break }
|
||||
'2' { Start-AllSheepIt }
|
||||
'3' { Send-SheepItCommandAll -CommandText 'pause' }
|
||||
'4' { Send-SheepItCommandAll -CommandText 'resume' }
|
||||
'5' { Send-SheepItCommandAll -CommandText 'quit' }
|
||||
default {
|
||||
Write-Host "Invalid selection." -ForegroundColor Red
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
39
workers.json.example
Normal file
39
workers.json.example
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"workers": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "i9kf",
|
||||
"enabled": true,
|
||||
"ssh": {
|
||||
"host": "i9kf",
|
||||
"port": 22,
|
||||
"args": "-t i9kf"
|
||||
},
|
||||
"workerTypes": {
|
||||
"sheepit": {
|
||||
"gpu": "OPTIX_0",
|
||||
"username": "RaincloudTheDragon",
|
||||
"renderKey": "IfCOWBHFQpceG0601DmyrwOOJOAp2UJAQ0O0X0jF"
|
||||
},
|
||||
"flamenco": {
|
||||
"workerPath": "F:\\software\\Flamenco 3.7",
|
||||
"networkDrives": ["A:", "F:", "N:", "P:"],
|
||||
"networkPaths": [
|
||||
"\\\\NEXUS\\amazon",
|
||||
"\\\\NEXUS\\flamenco",
|
||||
"\\\\NEXUS\\proj",
|
||||
"\\\\NAS\\amazon"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalSettings": {
|
||||
"sheepitJarUrls": [
|
||||
"https://www.sheepit-renderfarm.com/media/applet/client-latest.php",
|
||||
"https://www.sheepit-renderfarm.com/media/applet/client-latest.jar"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user