mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(ssh): name a Windows host that's too old or can't unpack Orca's runtime (#33682)
Setup on Windows Server 2012 R2 and on Windows hosts with neither System32\tar.exe nor Expand-Archive ended in the generic "Orca couldn't start its server on this host. Try connecting again.", which no retry fixes. The runtime probe that already runs before staging now also reads the Windows build and checks for an extractor, read-only, and refuses before anything is staged or uploaded. The refusal travels the existing orcad_unavailable reason plumbing to the host card and the ssh:connect error, and is rechecked on every connect like a noexec home. Fixes #33574 Reported-by: HKLHaoBin
This commit is contained in:
@@ -6,7 +6,11 @@ import type { SshTarget } from '../../../shared/ssh-types'
|
||||
import { closeTestStores, createSqliteTestStore } from '../../persistence/persistence-test-harness'
|
||||
import { Store } from '../../persistence/loading-store/store'
|
||||
import { OrcadHostUnsupportedError } from '../../orcad/orcad-host-unavailable'
|
||||
import { RemoteNodeRuntimeNoexecError } from '../../ssh/orcad-remote-node-runtime-report'
|
||||
import {
|
||||
RemoteNodeRuntimeNoExtractorError,
|
||||
RemoteNodeRuntimeNoexecError,
|
||||
RemoteWindowsBuildUnsupportedError
|
||||
} from '../../ssh/orcad-remote-node-runtime-report'
|
||||
import { allowsDirectSshRelay, SshConnectionStore } from '../../ssh/connection/ssh-connection-store'
|
||||
import { resolveHostServerOnConnect } from '../../ssh/ssh-host-server-on-connect'
|
||||
import { unservedHostServerMessage } from '../../ssh/ssh-host-unserved-status'
|
||||
@@ -126,6 +130,35 @@ describe('connect-time server decision against the real profile', () => {
|
||||
expect(mocks.deploy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it.each([
|
||||
{
|
||||
failure: () => new RemoteWindowsBuildUnsupportedError('9600'),
|
||||
reason: 'windows_build_unsupported',
|
||||
message: /older than Windows 10 and Windows Server 2016/
|
||||
},
|
||||
{
|
||||
failure: () => new RemoteNodeRuntimeNoExtractorError(),
|
||||
reason: 'windows_no_extractor',
|
||||
message: /neither tar\.exe nor PowerShell’s Expand-Archive.*connect again/
|
||||
}
|
||||
])(
|
||||
'names a Windows host’s $reason in the connect error and rechecks it next connect',
|
||||
async ({ failure, reason, message }) => {
|
||||
mocks.deploy.mockRejectedValue(failure())
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
const decision = await resolveHostServerOnConnect(
|
||||
target(),
|
||||
hostServerOnConnectDeps(userDataPath)
|
||||
)
|
||||
warn.mockRestore()
|
||||
expect(decision).toEqual({ route: 'unserved', reason: 'orcad_unavailable', detail: reason })
|
||||
const text = decision.route === 'unserved' ? unservedHostServerMessage(decision) : ''
|
||||
expect(text).toMatch(message)
|
||||
expect(text).not.toContain('Try connecting again')
|
||||
expect(target().managedServerUnavailable).toBeUndefined()
|
||||
}
|
||||
)
|
||||
|
||||
it('retries a host an older build kept on the relay', async () => {
|
||||
const deps = hostServerOnConnectDeps(userDataPath)
|
||||
store.updateSshTarget(TARGET.id, {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type * as ExecCommandModule from '../../ssh/remote-exec/remote-exec-command'
|
||||
import path from 'node:path'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { NODE_RUNTIME_ASSETS, NODE_RUNTIME_PIN } from '../../../shared/node-runtime-pin'
|
||||
import {
|
||||
NODE_RUNTIME_ASSETS,
|
||||
NODE_RUNTIME_PIN,
|
||||
NODE_RUNTIME_WINDOWS_MINIMUM_BUILD
|
||||
} from '../../../shared/node-runtime-pin'
|
||||
import { classifyOrcadHostUnavailable } from '../../orcad/orcad-host-unavailable'
|
||||
import type { OrcadHostTransport } from '../../ssh/orcad-host-transport'
|
||||
import { execCommand } from '../../ssh/remote-exec/remote-exec-command'
|
||||
import { uploadRemoteInstallDirectory } from './remote-install-transfers'
|
||||
@@ -276,6 +281,74 @@ describe('ensureRemoteOrcadNodeRuntime on Windows', () => {
|
||||
expect(failure).toMatchObject({ detail: 'node.exe changed after it ran' })
|
||||
})
|
||||
|
||||
// A host answers each check the probe asks about; tar and Expand-Archive are absent on Server 2012 R2.
|
||||
function oldHost(facts: { build: number; extractor: boolean }): string[] {
|
||||
const scripts = answer(
|
||||
"ORCA_NODE_RUNTIME_EXTRACT_FAILED The term 'Expand-Archive' is not recognized\r\n"
|
||||
)
|
||||
const respond = vi.mocked(execCommand).getMockImplementation()
|
||||
vi.mocked(execCommand).mockImplementation(async (conn, command, options) => {
|
||||
const script = decodeRemotePowerShellScript(command)
|
||||
if (script.includes('New-Item') && script.includes(REMOTE_NODE_RUNTIME_MISSING)) {
|
||||
scripts.push(script)
|
||||
const floor = /\(\$build -lt (\d+)\)/.exec(script)?.[1]
|
||||
if (floor && facts.build < Number(floor)) {
|
||||
return `ORCA_WINDOWS_BUILD_UNSUPPORTED ${facts.build}\r\n`
|
||||
}
|
||||
if (script.includes('Get-Command Expand-Archive') && !facts.extractor) {
|
||||
return 'ORCA_NODE_RUNTIME_NO_EXTRACTOR\r\n'
|
||||
}
|
||||
return REMOTE_NODE_RUNTIME_MISSING
|
||||
}
|
||||
return respond!(conn, command, options)
|
||||
})
|
||||
return scripts
|
||||
}
|
||||
|
||||
it('refuses Windows below Node’s floor before staging or uploading the runtime', async () => {
|
||||
oldHost({ build: 9600, extractor: false })
|
||||
const failure = await ensureRemoteOrcadNodeRuntime({
|
||||
conn,
|
||||
host,
|
||||
slotDir: relayDir,
|
||||
target,
|
||||
archivePath
|
||||
}).catch((error: unknown) => error)
|
||||
expect(failure).toMatchObject({ name: 'RemoteWindowsBuildUnsupportedError', build: '9600' })
|
||||
expect(classifyOrcadHostUnavailable(failure)).toBe('windows_build_unsupported')
|
||||
expect(execCommand).toHaveBeenCalledTimes(1)
|
||||
expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('names a supported Windows host with no way to unpack the runtime, before uploading it', async () => {
|
||||
oldHost({ build: 17763, extractor: false })
|
||||
const failure = await ensureRemoteOrcadNodeRuntime({
|
||||
conn,
|
||||
host,
|
||||
slotDir: relayDir,
|
||||
target,
|
||||
archivePath
|
||||
}).catch((error: unknown) => error)
|
||||
expect(failure).toMatchObject({ name: 'RemoteNodeRuntimeNoExtractorError' })
|
||||
expect(classifyOrcadHostUnavailable(failure)).toBe('windows_no_extractor')
|
||||
expect(execCommand).toHaveBeenCalledTimes(1)
|
||||
expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('checks the host before creating the stage, so a refused host keeps nothing', () => {
|
||||
const script = decodeRemotePowerShellScript(
|
||||
windowsNodeRuntimeProbeCommand(runtimeDir, target, stageDir)
|
||||
)
|
||||
const stage = script.indexOf('New-Item')
|
||||
expect(script.indexOf('CurrentBuildNumber')).toBeGreaterThan(0)
|
||||
expect(script.indexOf('CurrentBuildNumber')).toBeLessThan(stage)
|
||||
expect(script.indexOf('Get-Command Expand-Archive')).toBeLessThan(stage)
|
||||
expect(script).toContain(`($build -lt ${NODE_RUNTIME_WINDOWS_MINIMUM_BUILD})`)
|
||||
expect(
|
||||
decodeRemotePowerShellScript(probeRemoteNodeRuntimeCommand(host, runtimeDir, target))
|
||||
).not.toContain('CurrentBuildNumber')
|
||||
})
|
||||
|
||||
it('carries what node.exe said when it would not run', async () => {
|
||||
answer(
|
||||
"ORCA_NODE_RUNTIME_SELFTEST_FAILED\r\nORCA_RUNTIME_EXIT=-1\r\nProgram 'node.exe' failed to run: This program is blocked by group policy.\r\n"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
pinnedNodeRuntimeAsset,
|
||||
NODE_RUNTIME_PIN,
|
||||
NODE_RUNTIME_WINDOWS_MINIMUM_BUILD,
|
||||
nodeRuntimeExecutablePath
|
||||
} from '../../../shared/node-runtime-pin'
|
||||
import type { NodeRuntimeTarget } from '../../../shared/node-runtime-pin'
|
||||
@@ -9,10 +10,12 @@ import { ORCAD_NODE_RUNTIME_WINDOWS_EXECUTABLE } from '../../../shared/orcad-art
|
||||
import {
|
||||
REMOTE_NODE_RUNTIME_EXIT_PREFIX,
|
||||
REMOTE_NODE_RUNTIME_MISSING,
|
||||
REMOTE_NODE_RUNTIME_NO_EXTRACTOR,
|
||||
REMOTE_NODE_RUNTIME_READY,
|
||||
REMOTE_NODE_RUNTIME_SECURITY_MODIFIED,
|
||||
REMOTE_NODE_RUNTIME_SELFTEST_FAILED,
|
||||
REMOTE_NODE_RUNTIME_VERIFIED_MARKER
|
||||
REMOTE_NODE_RUNTIME_VERIFIED_MARKER,
|
||||
REMOTE_WINDOWS_BUILD_UNSUPPORTED
|
||||
} from '../../ssh/orcad-remote-node-runtime-report'
|
||||
import { powerShellCommand } from '../../ssh/ssh-remote-powershell'
|
||||
import { quotePowerShellLiteral as powerShellLiteral } from '../../../shared/powershell-native-argument'
|
||||
@@ -40,7 +43,20 @@ function runtimeVariables(runtimeDir: string): string[] {
|
||||
]
|
||||
}
|
||||
|
||||
/** Create an upload stage during a failed probe to avoid another PowerShell spawn. */
|
||||
// Git/MSYS tar.exe may precede System32 on PATH and cannot read ZIPs.
|
||||
const SYSTEM_TAR = "$tar = Join-Path $env:SystemRoot 'System32\\tar.exe'"
|
||||
|
||||
// Read-only; an unreadable build proves nothing, so only a known old one refuses.
|
||||
function hostPreflight(): string[] {
|
||||
return [
|
||||
"$build = 0; try { $build = [int](Get-ItemProperty -LiteralPath 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' -ErrorAction Stop).CurrentBuildNumber } catch { }",
|
||||
`if (($build -gt 0) -and ($build -lt ${NODE_RUNTIME_WINDOWS_MINIMUM_BUILD})) { Write-Output (${powerShellLiteral(`${REMOTE_WINDOWS_BUILD_UNSUPPORTED} `)} + $build); exit 0 }`,
|
||||
SYSTEM_TAR,
|
||||
`if (-not (Test-Path -LiteralPath $tar -PathType Leaf) -and -not (Get-Command Expand-Archive -ErrorAction SilentlyContinue)) { Write-Output ${powerShellLiteral(REMOTE_NODE_RUNTIME_NO_EXTRACTOR)}; exit 0 }`
|
||||
]
|
||||
}
|
||||
|
||||
/** Create an upload stage during a failed probe to avoid another PowerShell spawn; first refuse hosts that can't unpack or run it. */
|
||||
export function windowsNodeRuntimeProbeCommand(
|
||||
runtimeDir: string,
|
||||
target: NodeRuntimeTarget,
|
||||
@@ -57,6 +73,7 @@ export function windowsNodeRuntimeProbeCommand(
|
||||
'}',
|
||||
...(stageDir
|
||||
? [
|
||||
...hostPreflight(),
|
||||
`$null = New-Item -ItemType Directory -Force -Path ${powerShellLiteral(stageDir)} -ErrorAction Stop`
|
||||
]
|
||||
: []),
|
||||
@@ -114,8 +131,7 @@ export function windowsNodeRuntimePromoteCommand(args: {
|
||||
`$archiveHash = Get-OrcaSha256 $archive`,
|
||||
`if ($archiveHash -eq '') { Write-Output 'ORCA_NODE_RUNTIME_ARCHIVE_MISSING'; $script:code = 1; return }`,
|
||||
`if ($archiveHash -ne ${powerShellLiteral(asset.archiveSha256)}) { ${modified('the uploaded archive changed after it was written')}; return }`,
|
||||
// Git/MSYS tar.exe may precede System32 on PATH and cannot read ZIPs.
|
||||
"$tar = Join-Path $env:SystemRoot 'System32\\tar.exe'",
|
||||
SYSTEM_TAR,
|
||||
'$extractedBy = $null',
|
||||
'if (Test-Path -LiteralPath $tar -PathType Leaf) {',
|
||||
`$null = & $tar -xf $archive -C $stage ${powerShellLiteral(member)} 2>&1`,
|
||||
|
||||
@@ -34,6 +34,7 @@ import { withRuntimeStoreLock, type HeldRuntimeStoreLock } from './remote-node-r
|
||||
import {
|
||||
assertRemoteNodeRuntimePromoted,
|
||||
assertRemoteNodeRuntimeStoreExecutable,
|
||||
assertWindowsHostCanInstallNodeRuntime,
|
||||
REMOTE_NODE_RUNTIME_EXIT_PREFIX,
|
||||
REMOTE_NODE_RUNTIME_NOEXEC,
|
||||
REMOTE_NODE_RUNTIME_MISSING,
|
||||
@@ -255,6 +256,9 @@ export async function ensureRemoteOrcadNodeRuntime(options: {
|
||||
if (probe.trim() === REMOTE_NODE_RUNTIME_READY) {
|
||||
return { executable, transfer: 'cached' }
|
||||
}
|
||||
if (windows) {
|
||||
assertWindowsHostCanInstallNodeRuntime(probe)
|
||||
}
|
||||
const cleanupStage = windows
|
||||
? windowsNodeRuntimeStageCleanupCommand(stageDir)
|
||||
: `rm -rf ${quotePosixShell(stageDir)}`
|
||||
|
||||
@@ -31,6 +31,8 @@ export type OrcadHostUnavailableReason =
|
||||
| 'libc_unidentified'
|
||||
| 'runtime_self_test'
|
||||
| 'runtime_noexec'
|
||||
| 'windows_build_unsupported'
|
||||
| 'windows_no_extractor'
|
||||
| 'security_software'
|
||||
| 'native_preflight'
|
||||
| typeof ORCAD_TUNNEL_UNAVAILABLE_REASON
|
||||
@@ -42,6 +44,8 @@ const UNAVAILABLE_BY_ERROR_NAME: Record<string, OrcadHostUnavailableReason> = {
|
||||
UnidentifiedHostLibcError: 'libc_unidentified',
|
||||
RemoteNodeRuntimeSelfTestError: 'runtime_self_test',
|
||||
RemoteNodeRuntimeNoexecError: 'runtime_noexec',
|
||||
RemoteWindowsBuildUnsupportedError: 'windows_build_unsupported',
|
||||
RemoteNodeRuntimeNoExtractorError: 'windows_no_extractor',
|
||||
RemoteNodeRuntimeSecurityModifiedError: 'security_software',
|
||||
OrcadStdioBridgeUnavailableError: ORCAD_TUNNEL_UNAVAILABLE_REASON,
|
||||
OrcadWindowsLaunchRefusedError: 'unsupported_host',
|
||||
@@ -56,8 +60,15 @@ const UNAVAILABLE_BY_DEFERRAL_CODE: Record<string, OrcadHostUnavailableReason> =
|
||||
[ORCAD_WINDOWS_COMMAND_LINE_UNSAFE_CODE]: 'unsupported_host'
|
||||
}
|
||||
|
||||
// Reprobed on every connect, so fixing or upgrading the host needs no new Orca version.
|
||||
const FIXABLE_ON_HOST: ReadonlySet<string> = new Set<OrcadHostUnavailableReason>([
|
||||
'runtime_noexec',
|
||||
'windows_build_unsupported',
|
||||
'windows_no_extractor'
|
||||
])
|
||||
|
||||
export function isOrcadHostUnavailableFixableOnHost(reason: string): boolean {
|
||||
return reason === 'runtime_noexec'
|
||||
return FIXABLE_ON_HOST.has(reason)
|
||||
}
|
||||
|
||||
export function orcadCandidateLaunchFailureCode(error: unknown): string {
|
||||
|
||||
@@ -8,6 +8,8 @@ export const REMOTE_NODE_RUNTIME_NOEXEC = 'ORCA_NODE_RUNTIME_NOEXEC'
|
||||
export const REMOTE_NODE_RUNTIME_SECURITY_MODIFIED = 'ORCA_NODE_RUNTIME_SECURITY_MODIFIED'
|
||||
export const REMOTE_NODE_RUNTIME_EXIT_PREFIX = 'ORCA_RUNTIME_EXIT='
|
||||
export const REMOTE_NODE_RUNTIME_VERIFIED_MARKER = '.verified'
|
||||
export const REMOTE_WINDOWS_BUILD_UNSUPPORTED = 'ORCA_WINDOWS_BUILD_UNSUPPORTED'
|
||||
export const REMOTE_NODE_RUNTIME_NO_EXTRACTOR = 'ORCA_NODE_RUNTIME_NO_EXTRACTOR'
|
||||
|
||||
/** A failed version self-test is positive host evidence, not an unanswered transport probe. */
|
||||
export class RemoteNodeRuntimeSelfTestError extends RelayHostAnsweredError {
|
||||
@@ -50,6 +52,39 @@ export function assertRemoteNodeRuntimeStoreExecutable(probe: string, storeDir:
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteWindowsBuildUnsupportedError extends RelayHostAnsweredError {
|
||||
constructor(readonly build: string) {
|
||||
super(
|
||||
`The host runs Windows build ${build}, older than the Windows 10 / Server 2016 Orca's Node runtime needs.`
|
||||
)
|
||||
this.name = 'RemoteWindowsBuildUnsupportedError'
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteNodeRuntimeNoExtractorError extends RelayHostAnsweredError {
|
||||
constructor() {
|
||||
super(
|
||||
"The host has neither System32\\tar.exe nor PowerShell's Expand-Archive to unpack Orca's Node runtime."
|
||||
)
|
||||
this.name = 'RemoteNodeRuntimeNoExtractorError'
|
||||
}
|
||||
}
|
||||
|
||||
/** Windows hosts that can't run or unpack the runtime fail before anything is staged or uploaded. */
|
||||
export function assertWindowsHostCanInstallNodeRuntime(probe: string): void {
|
||||
for (const line of probe.split(/\r?\n/)) {
|
||||
const trimmed = line.trim()
|
||||
if (trimmed.startsWith(`${REMOTE_WINDOWS_BUILD_UNSUPPORTED} `)) {
|
||||
throw new RemoteWindowsBuildUnsupportedError(
|
||||
trimmed.slice(REMOTE_WINDOWS_BUILD_UNSUPPORTED.length + 1).slice(0, 32)
|
||||
)
|
||||
}
|
||||
if (trimmed === REMOTE_NODE_RUNTIME_NO_EXTRACTOR) {
|
||||
throw new RemoteNodeRuntimeNoExtractorError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alpine is the one supported family that ships without libstdc++ by default.
|
||||
function installHint(library: string): string {
|
||||
return library.startsWith('libstdc++')
|
||||
|
||||
@@ -25,8 +25,15 @@ export function unservedServerStatus(
|
||||
const HELD_MOVE_MESSAGE =
|
||||
'Orca paused moving this host’s projects to its Orca server because it couldn’t confirm the server’s copy. Nothing is lost: the host keeps its projects and stays locked until the move finishes. Connect again to resume. If the server can’t come back, stop or forget it in Settings > Orca on your SSH hosts.'
|
||||
|
||||
const NOEXEC_MESSAGE =
|
||||
'This host doesn’t allow running programs from ~/.orca-remote, so Orca can’t start its server there. This usually means the home folder is mounted with noexec. Ask the host’s administrator to allow running programs there, then connect again.'
|
||||
// Host-fixable reasons carry their own remedy; others fall back to the unsupported-version line.
|
||||
const UNAVAILABLE_MESSAGES: Record<string, string> = {
|
||||
runtime_noexec:
|
||||
'This host doesn’t allow running programs from ~/.orca-remote, so Orca can’t start its server there. This usually means the home folder is mounted with noexec. Ask the host’s administrator to allow running programs there, then connect again.',
|
||||
windows_build_unsupported:
|
||||
'This host runs a version of Windows older than Windows 10 and Windows Server 2016, so it can’t run Orca’s server. Use a host with a newer version of Windows, or a Linux or macOS host.',
|
||||
windows_no_extractor:
|
||||
'This Windows host has neither tar.exe nor PowerShell’s Expand-Archive, so Orca can’t unpack its server there. Install Windows PowerShell 5.1 on the host, then connect again.'
|
||||
}
|
||||
|
||||
const CONTACT_LOST_MESSAGE =
|
||||
'Orca lost contact with this host while setting up its server. Nothing on the host was stopped. Connect again to continue.'
|
||||
@@ -47,9 +54,10 @@ export function unservedHostServerMessage(decision: UnservedDecision): string {
|
||||
}
|
||||
switch (decision.reason) {
|
||||
case 'orcad_unavailable':
|
||||
return decision.detail === 'runtime_noexec'
|
||||
? NOEXEC_MESSAGE
|
||||
: 'This SSH host isn’t supported by this version of Orca. To keep using it, install an older version of Orca.'
|
||||
return (
|
||||
(decision.detail && UNAVAILABLE_MESSAGES[decision.detail]) ||
|
||||
'This SSH host isn’t supported by this version of Orca. To keep using it, install an older version of Orca.'
|
||||
)
|
||||
case 'relay_terminals_live':
|
||||
case 'relay_terminals_unverifiable':
|
||||
return 'Terminals started by an older version of Orca are still running on this host, and they don’t stop on their own. Close them in that version of Orca or end them on the host, then reconnect.'
|
||||
|
||||
@@ -13125,7 +13125,9 @@
|
||||
"nodeDownloadBlockedNoMirror": "Neither this computer nor this host could download Node.js, which Orca’s server needs. Check the network or proxy on both machines, then connect again.",
|
||||
"nodeDownloadBlockedMirrorTried": "Neither this computer nor this host could download Node.js, including from the Node.js download mirror. Check the mirror setting, then connect again.",
|
||||
"reassurance": "Runs on {{host}}, reached over your own SSH connection. Your code and files stay where they are. Only Orca’s list of projects, tabs and terminals moves to {{host}}.",
|
||||
"refusedBlockedReason": "Orca isn’t set up on this host yet. {{reason}}"
|
||||
"refusedBlockedReason": "Orca isn’t set up on this host yet. {{reason}}",
|
||||
"windowsBuildUnsupported": "This host runs a version of Windows older than Windows 10 and Windows Server 2016, so it can’t run Orca’s server. Use a host with a newer version of Windows, or a Linux or macOS host.",
|
||||
"windowsNoExtractor": "This Windows host has neither tar.exe nor PowerShell’s Expand-Archive, so Orca can’t unpack its server there. Install Windows PowerShell 5.1 on the host, then connect again."
|
||||
},
|
||||
"ZcodeCliSetupSection": {
|
||||
"title": "Use GLM with ZCode",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Mirror main's host-fixable OrcadHostUnavailableReason values; each names what to change on the host.
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
export function hostFixableUnavailableText(reason: string): string | null {
|
||||
switch (reason) {
|
||||
case 'runtime_noexec':
|
||||
return translate(
|
||||
'auto.components.settings.sshHostServer.runtimeNoexec',
|
||||
'This host doesn’t allow running programs from ~/.orca-remote, so Orca can’t start its server there. This usually means the home folder is mounted with noexec. Ask the host’s administrator to allow running programs there, then connect again.'
|
||||
)
|
||||
case 'windows_build_unsupported':
|
||||
return translate(
|
||||
'auto.components.settings.sshHostServer.windowsBuildUnsupported',
|
||||
'This host runs a version of Windows older than Windows 10 and Windows Server 2016, so it can’t run Orca’s server. Use a host with a newer version of Windows, or a Linux or macOS host.'
|
||||
)
|
||||
case 'windows_no_extractor':
|
||||
return translate(
|
||||
'auto.components.settings.sshHostServer.windowsNoExtractor',
|
||||
'This Windows host has neither tar.exe nor PowerShell’s Expand-Archive, so Orca can’t unpack its server there. Install Windows PowerShell 5.1 on the host, then connect again.'
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,24 @@ describe('SSH host server status line', () => {
|
||||
expect(line?.text).not.toContain('older version')
|
||||
})
|
||||
|
||||
it('names the Windows host’s blocker instead of advising a retry or an older Orca', () => {
|
||||
const line = (detail: string) =>
|
||||
sshHostServerStatusLine(plain, {
|
||||
managedServer: { kind: 'relay', reason: 'orcad_unavailable', detail }
|
||||
})
|
||||
expect(line('windows_build_unsupported')).toMatchObject({
|
||||
tone: 'destructive',
|
||||
text: expect.stringContaining('older than Windows 10 and Windows Server 2016')
|
||||
})
|
||||
expect(line('windows_no_extractor')).toMatchObject({
|
||||
tone: 'destructive',
|
||||
text: expect.stringContaining('Install Windows PowerShell 5.1 on the host')
|
||||
})
|
||||
for (const detail of ['windows_build_unsupported', 'windows_no_extractor']) {
|
||||
expect(line(detail)?.text).not.toMatch(/older version of Orca|Try connecting again/)
|
||||
}
|
||||
})
|
||||
|
||||
it('says plainly when neither port forwarding nor the SSH session reaches the server', () => {
|
||||
const live = sshHostServerStatusLine(plain, {
|
||||
managedServer: {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { bridgedTerminalsLine } from './ssh-host-bridged-terminals-copy'
|
||||
import { managedUpdateLine, withDetail } from './ssh-host-server-update-copy'
|
||||
import { blockedNodeDownload, nodeDownloadBlockedText } from './ssh-host-node-download-copy'
|
||||
import { settingUpLabel, sshHostServerReassurance } from './ssh-host-server-setup-copy'
|
||||
import { hostFixableUnavailableText } from './ssh-host-fixable-unavailable-copy'
|
||||
|
||||
export type SshHostServerStatusLine = {
|
||||
text: string
|
||||
@@ -113,14 +114,9 @@ function unavailableLine(reason: string | undefined): SshHostServerStatusLine {
|
||||
if (reason === TUNNEL_UNAVAILABLE) {
|
||||
return tunnelUnavailableLine()
|
||||
}
|
||||
if (reason === 'runtime_noexec') {
|
||||
return {
|
||||
tone: 'destructive',
|
||||
text: translate(
|
||||
'auto.components.settings.sshHostServer.runtimeNoexec',
|
||||
'This host doesn’t allow running programs from ~/.orca-remote, so Orca can’t start its server there. This usually means the home folder is mounted with noexec. Ask the host’s administrator to allow running programs there, then connect again.'
|
||||
)
|
||||
}
|
||||
const fixable = reason ? hostFixableUnavailableText(reason) : null
|
||||
if (fixable) {
|
||||
return { tone: 'destructive', text: fixable }
|
||||
}
|
||||
const cause = reason ? unavailableCause(reason) : null
|
||||
if (cause) {
|
||||
|
||||
@@ -147,6 +147,9 @@ export const NODE_RUNTIME_COMPAT_ASSETS: Record<CompatServerTarget, NodeRuntimeA
|
||||
/** Pinned darwin Node LC_BUILD_VERSION minos sets terminal-host floor. Packaging catches pin drift; older macOS uses desktop-forked daemons. */
|
||||
export const NODE_RUNTIME_DARWIN_MINIMUM_OS = '13.5'
|
||||
|
||||
/** Pinned Node supports Windows 10 / Server 2016 and later; 10240 is the first Windows 10 build. */
|
||||
export const NODE_RUNTIME_WINDOWS_MINIMUM_BUILD = 10240
|
||||
|
||||
export function isCompatServerTarget(target: string): target is CompatServerTarget {
|
||||
return COMPAT_SERVER_TARGETS.some((known) => known === target)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ const UNAVAILABLE_REASONS = [
|
||||
'libc_unidentified',
|
||||
'runtime_self_test',
|
||||
'runtime_noexec',
|
||||
'windows_build_unsupported',
|
||||
'windows_no_extractor',
|
||||
'security_software',
|
||||
'native_preflight'
|
||||
] as const
|
||||
|
||||
Reference in New Issue
Block a user