mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
* feat: add windows ssh relay base support * feat: support windows ssh relay runtime services * fix: default windows ssh pty cwd to user profile * fix: support windows hosts over system ssh * fix: preserve degraded windows relay native deps * fix: gate windows shell args by relay platform * fix: preserve windows relay fallback pipes * test: align windows native deps relay fixture * fix: build valid windows install lock command * fix: address windows SSH relay review findings Resolve correctness, efficiency, and reuse issues found reviewing the Windows SSH native-host support: - GC liveness on Windows now probes the actual named pipe (via node net.connect against markers + deterministic candidates) instead of substring-matching Win32_Process command lines, which could remove a live relay dir. Reports ALIVE conservatively only when there is no liveness signal at all (no markers and no seed pipes). - Resolve the remote node path once per deploy and thread it through install/repair/launch instead of re-resolving 3-7x. - Replace the 200ms node -e poll loop with a single long-lived remote wait process during Windows relay startup. - Skip the no-op executable command on Windows in uploadRelay. - Make the Windows fallback pipe name deterministic and recoverable (drop the global counter), with an extra reconnect attempt. - Normalize the prepended node bin dir to backslashes on Windows PATH. - Batch the system-SSH Windows directory upload into a single streamed JSON package instead of one ssh process per file. - Extract relay endpoint/marker helpers into ssh-relay-endpoints.ts and consolidate the PowerShell EncodedCommand encoding into the shared powershell-command-encoding module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Support cancellation and timeouts in Windows port scanning - Propagate the request AbortSignal and a 5-second timeout to both PowerShell and netstat child processes during Windows port scanning. - Avoid spawning the netstat fallback process if the port scan has already been aborted. - Wrap the .NET OSArchitecture check in a try/catch block during SSH Windows platform detection to robustly fall back to environment variables if needed. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
import type { SshConnection } from './ssh-connection'
|
|
import { execCommand } from './ssh-relay-deploy-helpers'
|
|
import type { RemoteHostPlatform } from './ssh-remote-platform'
|
|
import { isWindowsRemoteHost, normalizeWindowsRemotePath } from './ssh-remote-platform'
|
|
import { powerShellCommand } from './ssh-remote-powershell'
|
|
|
|
// Why: non-login SSH shells (the default for `exec`) don't source
|
|
// .bashrc/.zshrc, so node installed via nvm/fnm/Homebrew isn't in PATH.
|
|
// We try common locations and fall back to a login-shell `which`.
|
|
export async function resolveRemoteNodePath(
|
|
conn: SshConnection,
|
|
host?: RemoteHostPlatform
|
|
): Promise<string> {
|
|
if (host && isWindowsRemoteHost(host)) {
|
|
return resolveRemoteWindowsNodePath(conn)
|
|
}
|
|
|
|
const script = [
|
|
'command -v node 2>/dev/null',
|
|
'command -v /usr/local/bin/node 2>/dev/null',
|
|
'command -v /opt/homebrew/bin/node 2>/dev/null',
|
|
// Why: nvm installs into a versioned directory. `ls -1` sorts
|
|
// alphabetically, which misorders versions (e.g. v9 > v18). Pipe
|
|
// through `sort -V` (version sort) so we pick the highest version.
|
|
'ls -1 $HOME/.nvm/versions/node/*/bin/node 2>/dev/null | sort -V | tail -1',
|
|
'command -v $HOME/.local/bin/node 2>/dev/null',
|
|
'command -v $HOME/.fnm/aliases/default/bin/node 2>/dev/null'
|
|
].join(' || ')
|
|
|
|
try {
|
|
const result = await execCommand(conn, script)
|
|
const nodePath = result.trim().split('\n')[0]
|
|
if (nodePath) {
|
|
console.log(`[ssh-relay] Found node at: ${nodePath}`)
|
|
return nodePath
|
|
}
|
|
} catch {
|
|
// Fall through to login shell attempt
|
|
}
|
|
|
|
// Why: last resort — source the full login profile. This is separated into
|
|
// its own exec because `bash -lc` can hang on remotes with interactive
|
|
// shell configs (conda prompts, etc.). If this times out, the error message
|
|
// from execCommand will tell us it was the login shell attempt.
|
|
try {
|
|
console.log('[ssh-relay] Trying login shell to find node...')
|
|
const result = await execCommand(conn, "bash -lc 'command -v node' 2>/dev/null")
|
|
const nodePath = result.trim().split('\n')[0]
|
|
if (nodePath) {
|
|
console.log(`[ssh-relay] Found node via login shell: ${nodePath}`)
|
|
return nodePath
|
|
}
|
|
} catch {
|
|
// Fall through
|
|
}
|
|
|
|
throwNodeNotFound()
|
|
}
|
|
|
|
async function resolveRemoteWindowsNodePath(conn: SshConnection): Promise<string> {
|
|
const script = [
|
|
'$paths = @()',
|
|
'$cmd = Get-Command node.exe -ErrorAction SilentlyContinue',
|
|
'if ($cmd -and $cmd.Source) { $paths += $cmd.Source }',
|
|
'if ($env:ProgramFiles) { $paths += (Join-Path $env:ProgramFiles "nodejs/node.exe") }',
|
|
'if (${env:ProgramFiles(x86)}) { $paths += (Join-Path ${env:ProgramFiles(x86)} "nodejs/node.exe") }',
|
|
'if ($env:LOCALAPPDATA) { $paths += (Join-Path $env:LOCALAPPDATA "Programs/nodejs/node.exe") }',
|
|
'foreach ($path in $paths) {',
|
|
' if ($path -and (Test-Path -LiteralPath $path -PathType Leaf)) {',
|
|
' Write-Output $path',
|
|
' exit 0',
|
|
' }',
|
|
'}',
|
|
"Write-Error 'Node.js not found'",
|
|
'exit 1'
|
|
].join('\n')
|
|
|
|
try {
|
|
const result = await execCommand(conn, powerShellCommand(script), { wrapCommand: false })
|
|
const nodePath = result.trim().split('\n')[0]
|
|
if (nodePath) {
|
|
const normalized = normalizeWindowsRemotePath(nodePath)
|
|
console.log(`[ssh-relay] Found Windows node at: ${normalized}`)
|
|
return normalized
|
|
}
|
|
} catch {
|
|
// Fall through to the shared error below.
|
|
}
|
|
|
|
throwNodeNotFound()
|
|
}
|
|
|
|
function throwNodeNotFound(): never {
|
|
throw new Error(
|
|
'Node.js not found on remote host. Orca relay requires Node.js 18+. ' +
|
|
'Install Node.js on the remote and try again.'
|
|
)
|
|
}
|