From e7d77e863cdc0c03c97b2d589da3ddd7135f089f Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:54:26 -0700 Subject: [PATCH] refactor(orcad): name the remote install helpers for what they install (#31647) The SFTP/exec-stdin transfer helpers, their options type and the install timeout still carried relay names from before orcad; they install the orcad bundle and the Node runtime. The timeout's derivation from the relay's native-deps budget is replaced by its value and the reason for it. --- .../orcad-remote-install-termination.test.ts | 10 ++--- .../install/orcad-remote-install.ts | 16 +++++--- .../orcad-remote-lifecycle-windows.test.ts | 4 +- ...-remote-node-runtime-host-fallback.test.ts | 14 ++++--- .../orcad-remote-node-runtime-noexec.test.ts | 12 +++--- ...remote-node-runtime-promotion-lock.test.ts | 4 +- ...rcad-remote-node-runtime-slow-hash.test.ts | 6 +-- .../orcad-remote-node-runtime-windows.test.ts | 12 +++--- .../install/orcad-remote-node-runtime.ts | 4 +- .../install/orcad-remote-rollback.test.ts | 4 +- .../install/remote-deploy-timing.ts | 16 ++------ .../install/remote-install-lock.ts | 17 ++++---- .../remote-install-noisy-sftp-wire.test.ts | 6 +-- .../remote-install-out-of-space.test.ts | 18 ++++----- ...te-install-transfers-exec-fallback.test.ts | 24 +++++------ .../install/remote-install-transfers.ts | 24 +++++------ .../install/remote-versioned-install.ts | 12 +++--- src/main/ssh/orcad-remote-deploy.test.ts | 40 ++++++++++--------- src/main/ssh/sftp-stream-late-error.test.ts | 8 ++-- src/main/ssh/ssh-exec-stdin-file-transfer.ts | 4 +- src/main/wsl/wsl-directory-upload.ts | 4 +- 21 files changed, 130 insertions(+), 129 deletions(-) diff --git a/src/main/managed-server/install/orcad-remote-install-termination.test.ts b/src/main/managed-server/install/orcad-remote-install-termination.test.ts index 99ad8ccd9ff..1a883b44795 100644 --- a/src/main/managed-server/install/orcad-remote-install-termination.test.ts +++ b/src/main/managed-server/install/orcad-remote-install-termination.test.ts @@ -10,8 +10,8 @@ vi.mock('./remote-install-lock', () => ({ RELAY_INSTALL_LOCK_NAME: '.install-lock' })) vi.mock('./remote-install-transfers', () => ({ - uploadRelayDirectory: vi.fn(), - writeRelayFile: vi.fn() + uploadRemoteInstallDirectory: vi.fn(), + writeRemoteInstallFile: vi.fn() })) vi.mock('./orcad-remote-node-runtime', () => ({ ensureRemoteOrcadNodeRuntime: vi.fn().mockResolvedValue(undefined) @@ -19,7 +19,7 @@ vi.mock('./orcad-remote-node-runtime', () => ({ import type { OrcadHostTransport } from '../../ssh/orcad-host-transport' import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory, writeRelayFile } from './remote-install-transfers' +import { uploadRemoteInstallDirectory, writeRemoteInstallFile } from './remote-install-transfers' import { installOrcadBundle } from './orcad-remote-install' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' import { decodeRemotePowerShellScript } from '../../ssh/ssh-remote-powershell' @@ -35,8 +35,8 @@ import { join } from 'node:path' // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: All connection operations are mocked. const conn = {} as OrcadHostTransport const mockExec = vi.mocked(execCommand) -const mockUpload = vi.mocked(uploadRelayDirectory) -const mockWrite = vi.mocked(writeRelayFile) +const mockUpload = vi.mocked(uploadRemoteInstallDirectory) +const mockWrite = vi.mocked(writeRemoteInstallFile) const fullVersion = '0.2.0+abcdef123456' function issuedCommands(): string[] { diff --git a/src/main/managed-server/install/orcad-remote-install.ts b/src/main/managed-server/install/orcad-remote-install.ts index c3ab9a4b16e..af85d6bcc13 100644 --- a/src/main/managed-server/install/orcad-remote-install.ts +++ b/src/main/managed-server/install/orcad-remote-install.ts @@ -17,7 +17,7 @@ import { ORCAD_FENCE_OWNER_FILENAME } from '../activation/orcad-activation-fence import { forgetHeldOrcadFence, rememberHeldOrcadFence } from '../../ssh/orcad-held-fence-tokens' import { exitedOwnLockProof } from '../../ssh/orcad-exited-own-lock' import { orcadWindowsBaseDir } from './orcad-remote-windows-node' -import { uploadRelayDirectory, writeRelayFile } from './remote-install-transfers' +import { uploadRemoteInstallDirectory, writeRemoteInstallFile } from './remote-install-transfers' import { abandonInstall, finalizeInstall, @@ -91,9 +91,15 @@ export async function installOrcadBundle( ) { return } - await uploadRelayDirectory(options.conn, options.localOrcadDir, remoteDir, options.host, { - signal: options.signal - }) + await uploadRemoteInstallDirectory( + options.conn, + options.localOrcadDir, + remoteDir, + options.host, + { + signal: options.signal + } + ) if (options.host.os !== 'win32') { await execCommand( options.conn, @@ -104,7 +110,7 @@ export async function installOrcadBundle( } ) } - await writeRelayFile( + await writeRemoteInstallFile( options.conn, options.host, joinRemotePath(options.host, remoteDir, ORCAD_INSTALL_MODEL.versionFilename), diff --git a/src/main/managed-server/install/orcad-remote-lifecycle-windows.test.ts b/src/main/managed-server/install/orcad-remote-lifecycle-windows.test.ts index 8a39d37336a..697c2e488d4 100644 --- a/src/main/managed-server/install/orcad-remote-lifecycle-windows.test.ts +++ b/src/main/managed-server/install/orcad-remote-lifecycle-windows.test.ts @@ -16,8 +16,8 @@ vi.mock('./remote-install-lock', () => ({ RELAY_INSTALL_LOCK_NAME: '.install-lock' })) vi.mock('./remote-install-transfers', () => ({ - uploadRelayDirectory: vi.fn().mockResolvedValue(undefined), - writeRelayFile: vi.fn().mockResolvedValue(undefined) + uploadRemoteInstallDirectory: vi.fn().mockResolvedValue(undefined), + writeRemoteInstallFile: vi.fn().mockResolvedValue(undefined) })) vi.mock('./orcad-remote-record-file', async (importOriginal) => ({ ...(await importOriginal()), diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts index 9161dace8ed..e9ce28e2112 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-host-fallback.test.ts @@ -10,7 +10,7 @@ import { REMOTE_NODE_RUNTIME_READY } from '../../ssh/orcad-remote-node-runtime-report' import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory } from './remote-install-transfers' +import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' import { pinnedNodeRuntimeAsset } from '../../../shared/node-runtime-pin' import { DesktopDownloadUnreachableError } from '../../node-runtime/pinned-runtime-materializer' @@ -25,7 +25,9 @@ vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ( ...(await importOriginal()), execCommand: vi.fn() })) -vi.mock('./remote-install-transfers', () => ({ uploadRelayDirectory: vi.fn(async () => {}) })) +vi.mock('./remote-install-transfers', () => ({ + uploadRemoteInstallDirectory: vi.fn(async () => {}) +})) // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand. const conn = {} as OrcadHostTransport @@ -36,7 +38,7 @@ const refused = (): Promise => Promise.reject(new Error('net::ERR_CONNEC afterEach(() => { vi.mocked(execCommand).mockReset() - vi.mocked(uploadRelayDirectory).mockClear() + vi.mocked(uploadRemoteInstallDirectory).mockClear() }) /** A fresh POSIX host; `download` is what its curl/wget step prints. */ @@ -77,7 +79,7 @@ describe('pinned Node when this computer cannot download it', () => { const result = await ensure(refused) expect(result.transfer).toBe('host-downloaded') - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() const promote = commands.find((command) => command.includes('tar -xzf')) expect(promote).toContain(pinnedNodeRuntimeAsset(TARGET).archive) expect(promote).toContain(pinnedNodeRuntimeAsset(TARGET).executableSha256) @@ -105,7 +107,7 @@ describe('pinned Node when this computer cannot download it', () => { expect(message).toContain('Settings > Advanced > Network > Node.js download mirror') expect(message).toContain("NODEJS_ORG_MIRROR in this computer's environment") expect(failure).toMatchObject({ mirror: 'unset' }) - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() }) it('records that both attempts went through the configured mirror', async () => { @@ -171,7 +173,7 @@ describe('pinned Node when this computer cannot download it', () => { writeFileSync(archive, 'archive') try { await expect(ensure(async () => archive)).resolves.toMatchObject({ transfer: 'uploaded' }) - expect(uploadRelayDirectory).toHaveBeenCalledTimes(1) + expect(uploadRemoteInstallDirectory).toHaveBeenCalledTimes(1) expect(commands.some((command) => command.includes('command -v curl'))).toBe(false) } finally { rmSync(cache, { recursive: true, force: true }) diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-noexec.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-noexec.test.ts index 1302b5767bf..7e874bc49f0 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-noexec.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-noexec.test.ts @@ -25,14 +25,16 @@ import { } from '../../ssh/orcad-remote-node-runtime-report' import { classifyOrcadHostUnavailable } from '../../orcad/orcad-host-unavailable' import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory } from './remote-install-transfers' +import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ({ ...(await importOriginal()), execCommand: vi.fn() })) -vi.mock('./remote-install-transfers', () => ({ uploadRelayDirectory: vi.fn(async () => {}) })) +vi.mock('./remote-install-transfers', () => ({ + uploadRemoteInstallDirectory: vi.fn(async () => {}) +})) // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand. const conn = {} as OrcadHostTransport @@ -50,7 +52,7 @@ afterEach(() => { rmSync(directory, { recursive: true, force: true }) } vi.mocked(execCommand).mockReset() - vi.mocked(uploadRelayDirectory).mockClear() + vi.mocked(uploadRemoteInstallDirectory).mockClear() }) describe('pinned-Node runtime store on a host that refuses to run programs', () => { @@ -112,7 +114,7 @@ describe('pinned-Node runtime store on a host that refuses to run programs', () expect(classifyOrcadHostUnavailable(failure)).toBe('runtime_noexec') expect(String(failure)).toContain('/root/.orca-remote/runtimes') expect(archivePath).not.toHaveBeenCalled() - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() const commands = vi.mocked(execCommand).mock.calls.map(([, command]) => command) expect(commands.at(-1)).toMatch(/^rm -rf '\/root\/\.orca-remote\/runtimes\/\.stage-/) }) @@ -144,7 +146,7 @@ describe('pinned-Node runtime store on a host that refuses to run programs', () target: 'linux-x64-glibc', archivePath: async () => archive() }) - expect(uploadRelayDirectory).toHaveBeenCalledTimes(1) + expect(uploadRemoteInstallDirectory).toHaveBeenCalledTimes(1) }) }) diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-promotion-lock.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-promotion-lock.test.ts index dccfcdffdc5..2b9424885dd 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-promotion-lock.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-promotion-lock.test.ts @@ -20,7 +20,9 @@ vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ( ...(await importOriginal()), execCommand: vi.fn() })) -vi.mock('./remote-install-transfers', () => ({ uploadRelayDirectory: vi.fn(async () => {}) })) +vi.mock('./remote-install-transfers', () => ({ + uploadRemoteInstallDirectory: vi.fn(async () => {}) +})) // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand. const conn = {} as OrcadHostTransport diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-slow-hash.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-slow-hash.test.ts index 132de1faee2..69fc444f8ca 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-slow-hash.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-slow-hash.test.ts @@ -17,14 +17,14 @@ import { } from '../../ssh/orcad-remote-node-runtime-report' import { RUNTIME_STORE_LOCK_NAME, withRuntimeStoreLock } from './remote-node-runtime-store-lock' import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory } from './remote-install-transfers' +import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ({ ...(await importOriginal()), execCommand: vi.fn() })) -vi.mock('./remote-install-transfers', () => ({ uploadRelayDirectory: vi.fn() })) +vi.mock('./remote-install-transfers', () => ({ uploadRemoteInstallDirectory: vi.fn() })) // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand, which runs it in a local shell. const conn = {} as OrcadHostTransport @@ -68,7 +68,7 @@ describe.skipIf(process.platform === 'win32')('runtime install on slow storage', const archive = join(root, 'node.tar.gz') writeFileSync(archive, 'archive') // A sibling installer publishes this pin while this client uploads. - vi.mocked(uploadRelayDirectory).mockImplementation(async () => { + vi.mocked(uploadRemoteInstallDirectory).mockImplementation(async () => { mkdirSync(join(runtimeDir, 'bin'), { recursive: true }) writeFileSync(posixNodeRuntimeExecutable(host, runtimeDir), 'node', { mode: 0o755 }) writeFileSync(join(runtimeDir, REMOTE_NODE_RUNTIME_VERIFIED_MARKER), '') diff --git a/src/main/managed-server/install/orcad-remote-node-runtime-windows.test.ts b/src/main/managed-server/install/orcad-remote-node-runtime-windows.test.ts index afa80f46cfb..897551fb99e 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime-windows.test.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime-windows.test.ts @@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { NODE_RUNTIME_ASSETS, NODE_RUNTIME_PIN } from '../../../shared/node-runtime-pin' import type { OrcadHostTransport } from '../../ssh/orcad-host-transport' import { execCommand } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory } from './remote-install-transfers' +import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { ensureRemoteOrcadNodeRuntime, NODE_RUNTIME_PROMOTE_TIMEOUT_MS, @@ -29,7 +29,7 @@ vi.mock('../../ssh/remote-exec/remote-exec-command', async (importOriginal) => ( ...(await importOriginal()), execCommand: vi.fn() })) -vi.mock('./remote-install-transfers', () => ({ uploadRelayDirectory: vi.fn() })) +vi.mock('./remote-install-transfers', () => ({ uploadRemoteInstallDirectory: vi.fn() })) const host = getRemoteHostPlatform('win32-x64') const target = 'win32-x64' @@ -47,7 +47,7 @@ function promoteScript(): string { beforeEach(() => { vi.mocked(execCommand).mockReset() - vi.mocked(uploadRelayDirectory).mockReset().mockResolvedValue(undefined) + vi.mocked(uploadRemoteInstallDirectory).mockReset().mockResolvedValue(undefined) }) describe('Windows runtime store layout', () => { @@ -190,7 +190,7 @@ describe('ensureRemoteOrcadNodeRuntime on Windows', () => { await ensureRemoteOrcadNodeRuntime({ conn, host, slotDir: relayDir, target, archivePath }) expect(execCommand).toHaveBeenCalledTimes(1) expect(vi.mocked(execCommand).mock.calls[0][2]).toMatchObject({ wrapCommand: false }) - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() }) /** Answers the probe, the store lock and the promote script by what each script does. */ @@ -226,7 +226,7 @@ describe('ensureRemoteOrcadNodeRuntime on Windows', () => { expect(stage).toMatch( /^C:\/Users\/u\/\.orca-remote\/runtimes\/\.stage-node-[0-9a-f]{64}-[0-9a-f]{16}$/ ) - expect(vi.mocked(uploadRelayDirectory).mock.calls[0][2]).toBe(stage) + expect(vi.mocked(uploadRemoteInstallDirectory).mock.calls[0][2]).toBe(stage) const locked = scripts.findIndex((s) => s.includes('CreateNew')) const promoted = scripts.findIndex((s) => s.includes('Invoke-OrcaPromote')) const released = scripts.findIndex( @@ -252,7 +252,7 @@ describe('ensureRemoteOrcadNodeRuntime on Windows', () => { it('still removes the stage when the upload fails before promote runs', async () => { vi.mocked(execCommand).mockResolvedValueOnce(REMOTE_NODE_RUNTIME_MISSING).mockResolvedValue('') - vi.mocked(uploadRelayDirectory).mockRejectedValueOnce(new Error('sftp write failed')) + vi.mocked(uploadRemoteInstallDirectory).mockRejectedValueOnce(new Error('sftp write failed')) await expect( ensureRemoteOrcadNodeRuntime({ conn, host, slotDir: relayDir, target, archivePath }) ).rejects.toThrow('sftp write failed') diff --git a/src/main/managed-server/install/orcad-remote-node-runtime.ts b/src/main/managed-server/install/orcad-remote-node-runtime.ts index 209238dbe7e..2a31d1f4e76 100644 --- a/src/main/managed-server/install/orcad-remote-node-runtime.ts +++ b/src/main/managed-server/install/orcad-remote-node-runtime.ts @@ -28,7 +28,7 @@ import { execCommand, isUnconfirmedSshCommandTermination } from '../../ssh/remote-exec/remote-exec-command' -import { uploadRelayDirectory } from './remote-install-transfers' +import { uploadRemoteInstallDirectory } from './remote-install-transfers' import { remoteSha256Command } from './remote-sha256-command' import { stageNodeRuntimeArchive } from './remote-node-runtime-host-download' import { @@ -320,7 +320,7 @@ export async function ensureRemoteOrcadNodeRuntime(options: { exec, stageDir, upload: (dir) => - remoteStep(() => uploadRelayDirectory(conn, dir, stageDir, host, { signal })), + remoteStep(() => uploadRemoteInstallDirectory(conn, dir, stageDir, host, { signal })), onLocalStage: (dir) => (localStage = dir) }) let promoteRan = false diff --git a/src/main/managed-server/install/orcad-remote-rollback.test.ts b/src/main/managed-server/install/orcad-remote-rollback.test.ts index badae6bc5d7..c5ca4bd6100 100644 --- a/src/main/managed-server/install/orcad-remote-rollback.test.ts +++ b/src/main/managed-server/install/orcad-remote-rollback.test.ts @@ -13,8 +13,8 @@ vi.mock('../../../shared/shell-argument-quoting', () => ({ quotePosixShell: (s: string) => `'${s}'` })) vi.mock('./remote-install-transfers', () => ({ - writeRelayFile: vi.fn().mockResolvedValue(undefined), - uploadRelayDirectory: vi.fn().mockResolvedValue(undefined) + writeRemoteInstallFile: vi.fn().mockResolvedValue(undefined), + uploadRemoteInstallDirectory: vi.fn().mockResolvedValue(undefined) })) vi.mock('./remote-install-lock', async (importOriginal) => ({ ...(await importOriginal()), diff --git a/src/main/managed-server/install/remote-deploy-timing.ts b/src/main/managed-server/install/remote-deploy-timing.ts index 5dc24f937f8..bcb5755a015 100644 --- a/src/main/managed-server/install/remote-deploy-timing.ts +++ b/src/main/managed-server/install/remote-deploy-timing.ts @@ -1,13 +1,3 @@ -// npm install on a cold Windows cache plus antivirus scanning can exceed the -// default 30s exec timeout. -export const NATIVE_DEPS_COMMAND_TIMEOUT_MS = 240_000 - -// Why: a missing binding can require both install and rebuild while the same -// install lock is held. Concurrent first installs must wait for that valid -// holder instead of failing halfway through its bounded work. -const NATIVE_DEPS_REPAIR_BUDGET_MS = 2 * NATIVE_DEPS_COMMAND_TIMEOUT_MS - -// Why: native repair also runs separately bounded chmod/probe/diagnostic/ -// finalize commands around install + rebuild. Keep the outer bound above that -// valid worst path while remaining below the 20-minute stale-lock threshold. -export const RELAY_DEPLOY_TIMEOUT_MS = NATIVE_DEPS_REPAIR_BUDGET_MS + 7 * 60_000 +// Why 15 minutes: it bounds one remote install step and a concurrent install's wait for the +// install lock, and must stay below the 20-minute stale-lock threshold (remote-install-lock.ts). +export const REMOTE_INSTALL_TIMEOUT_MS = 15 * 60_000 diff --git a/src/main/managed-server/install/remote-install-lock.ts b/src/main/managed-server/install/remote-install-lock.ts index b15f9ccde72..364c1657360 100644 --- a/src/main/managed-server/install/remote-install-lock.ts +++ b/src/main/managed-server/install/remote-install-lock.ts @@ -1,6 +1,6 @@ import type { RemoteInstallTransport } from './remote-install-transport' import { execHostCommand } from '../../ssh/remote-exec/remote-host-exec' -import { RELAY_DEPLOY_TIMEOUT_MS } from './remote-deploy-timing' +import { REMOTE_INSTALL_TIMEOUT_MS } from './remote-deploy-timing' import { isUnconfirmedSshCommandTermination } from '../../ssh/remote-exec/remote-exec-command' import { isRelayGcClaimed, waitForRelayGcClaimRelease } from './remote-install-gc-claim' import { @@ -23,12 +23,9 @@ const INSTALL_LOCK_POLL_MS = 1_000 // Why: a fresh lock can cross the stale threshold during our bounded wait. // Recheck infrequently so it becomes recoverable without adding an exec per poll. const INSTALL_LOCK_STALE_RECHECK_MS = 60_000 -// Why: the lock holder can legitimately use the full deploy bound for upload, -// native install/rebuild, probes, and finalization. A concurrent first install -// must not fail earlier; completed-relay repair uses a separate one-shot path. -const INSTALL_LOCK_TIMEOUT_MS = RELAY_DEPLOY_TIMEOUT_MS -// Why: native-deps repair can keep running after the deploy backstop wins its -// Promise.race, so stale takeover must leave room for that bounded work. +// Why: a waiter gives the holder at least one full install step's bound before giving up. +const INSTALL_LOCK_TIMEOUT_MS = REMOTE_INSTALL_TIMEOUT_MS +// Why longer: a holder's step can outlive the bound that raced it, so takeover leaves it room. export const INSTALL_LOCK_STALE_MS = 20 * 60_000 export const INSTALL_LOCK_STALE_SECONDS = INSTALL_LOCK_STALE_MS / 1000 const DEFAULT_REMOTE_HOST = getRemoteHostPlatform('linux-x64') @@ -36,7 +33,7 @@ const DEFAULT_REMOTE_HOST = getRemoteHostPlatform('linux-x64') export class RemoteInstallLockBusyError extends Error { constructor(lockDir: string, timeoutMs: number) { super( - `Could not acquire relay install lock at ${lockDir} after ${timeoutMs / 1000}s; ` + + `Could not acquire the install lock at ${lockDir} after ${timeoutMs / 1000}s; ` + 'another install is still in progress.' ) this.name = 'RemoteInstallLockBusyError' @@ -180,7 +177,7 @@ export async function acquireInstallLock( if (exitedOwner && reason === 'exited-owner') { options?.exitedOwner?.reclaimed(exitedOwner.token) } - console.warn(`[ssh-relay] Stealing ${reason} install lock at ${lockDir}`) + console.warn(`[ssh] Stealing ${reason} install lock at ${lockDir}`) if (await keptAfterGcRecheck()) { return } @@ -188,7 +185,7 @@ export async function acquireInstallLock( } if (Date.now() - lastWaitLogAt >= INSTALL_LOCK_STALE_RECHECK_MS) { lastWaitLogAt = Date.now() - console.info(`[ssh-relay] Waiting for install lock at ${lockDir}`) + console.info(`[ssh] Waiting for install lock at ${lockDir}`) } if (Date.now() - start >= waitTimeoutMs) { if (!sawHolder && lastCommandError !== undefined) { diff --git a/src/main/managed-server/install/remote-install-noisy-sftp-wire.test.ts b/src/main/managed-server/install/remote-install-noisy-sftp-wire.test.ts index b2d24d16304..edc6c520240 100644 --- a/src/main/managed-server/install/remote-install-noisy-sftp-wire.test.ts +++ b/src/main/managed-server/install/remote-install-noisy-sftp-wire.test.ts @@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { SshTarget } from '../../../shared/ssh-types' import { spawnProcess } from '@orca/process-host' import { SshConnection } from '../../ssh/connection/ssh-connection' -import { uploadRelayDirectory, writeRelayFile } from './remote-install-transfers' +import { uploadRemoteInstallDirectory, writeRemoteInstallFile } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' type NoisyHost = { port: number; subsystems: number; execs: number; close: () => Promise } @@ -121,8 +121,8 @@ describe.skipIf(process.platform === 'win32')( const conn = connectionWithClient(client) const linux = getRemoteHostPlatform('linux-x64') const remoteDir = join(remoteRoot, 'relay') - await uploadRelayDirectory(conn, localDir, remoteDir, linux) - await writeRelayFile(conn, linux, join(remoteDir, '.version'), '0.1.0+noisy') + await uploadRemoteInstallDirectory(conn, localDir, remoteDir, linux) + await writeRemoteInstallFile(conn, linux, join(remoteDir, '.version'), '0.1.0+noisy') expect(await readFile(join(remoteDir, 'nested', 'payload.bin'))).toEqual( Buffer.from([0, 1, 2, 255]) diff --git a/src/main/managed-server/install/remote-install-out-of-space.test.ts b/src/main/managed-server/install/remote-install-out-of-space.test.ts index d9dbe7e03e9..022c5ee8535 100644 --- a/src/main/managed-server/install/remote-install-out-of-space.test.ts +++ b/src/main/managed-server/install/remote-install-out-of-space.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { RemoteInstallTransport } from './remote-install-transport' -import { uploadRelayDirectory, writeRelayFile } from './remote-install-transfers' +import { uploadRemoteInstallDirectory, writeRemoteInstallFile } from './remote-install-transfers' import { RemoteInstallOutOfSpaceError, remoteFreeSpaceProbeCommand @@ -49,7 +49,7 @@ afterEach(() => rmSync(localDir, { recursive: true, force: true })) describe('a failed install write on a full host', () => { it('says the disk is full instead of the bare SFTP "Failure"', async () => { vi.mocked(execHostCommand).mockResolvedValue(dfLine(12)) - const upload = uploadRelayDirectory( + const upload = uploadRemoteInstallDirectory( connection(sftpFailure), localDir, '/root/.orca-remote/orcad-0.1.0+abc', @@ -67,7 +67,7 @@ describe('a failed install write on a full host', () => { it('sizes a single file write by its contents', async () => { vi.mocked(execHostCommand).mockResolvedValue(dfLine(0)) await expect( - writeRelayFile(connection(sftpFailure), posix, '/root/.orca-remote/x/.version', 'abc') + writeRemoteInstallFile(connection(sftpFailure), posix, '/root/.orca-remote/x/.version', 'abc') ).rejects.toThrow(/has 0 KB free, and Orca needs about 1 KB/) }) @@ -76,7 +76,7 @@ describe('a failed install write on a full host', () => { vi.mocked(execHostCommand).mockResolvedValue(dfLine(1_500)) const failure = sftpFailure() await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(() => failure), localDir, '/root/r', @@ -89,7 +89,7 @@ describe('a failed install write on a full host', () => { vi.mocked(execHostCommand).mockRejectedValue(new Error('df: not found')) const failure = sftpFailure() await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(() => failure), localDir, '/root/r', @@ -101,7 +101,7 @@ describe('a failed install write on a full host', () => { it('keeps explicit refusals such as permission denied without probing', async () => { const denied = Object.assign(new Error('Permission denied'), { code: 3 }) await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(() => denied), localDir, '/root/r', @@ -117,7 +117,7 @@ describe('a failed install write on a full host', () => { sshChannelCloseConfirmed: false }) await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(() => lost), localDir, '/root/r', @@ -127,13 +127,13 @@ describe('a failed install write on a full host', () => { const controller = new AbortController() controller.abort() await expect( - uploadRelayDirectory(connection(sftpFailure), localDir, '/root/r', posix, { + uploadRemoteInstallDirectory(connection(sftpFailure), localDir, '/root/r', posix, { signal: controller.signal }) ).rejects.toThrow() const failure = sftpFailure() await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(() => failure), localDir, 'C:/Users/u/r', diff --git a/src/main/managed-server/install/remote-install-transfers-exec-fallback.test.ts b/src/main/managed-server/install/remote-install-transfers-exec-fallback.test.ts index e4e743ae85d..a92b3c9d423 100644 --- a/src/main/managed-server/install/remote-install-transfers-exec-fallback.test.ts +++ b/src/main/managed-server/install/remote-install-transfers-exec-fallback.test.ts @@ -7,8 +7,8 @@ import { } from '../../ssh/ssh-exec-stdin-file-transfer' import { classifySftpFailureForExecFallback, - uploadRelayDirectory, - writeRelayFile + uploadRemoteInstallDirectory, + writeRemoteInstallFile } from './remote-install-transfers' import { getRemoteHostPlatform } from '../../ssh/ssh-remote-platform' import { tagSftpHandshakeCorruption } from '../../ssh/sftp-handshake-corruption' @@ -101,8 +101,8 @@ describe('classifySftpFailureForExecFallback', () => { describe('relay install transfer fallback selection', () => { it('streams over exec stdin when the subsystem is refused, and skips SFTP for the rest of the connect', async () => { const conn = connection(subsystemRefused) - await uploadRelayDirectory(conn, '/local/relay', '/h/.orca-remote/stage/payload', posix) - await writeRelayFile(conn, posix, '/h/.orca-remote/stage/payload/.version', '0.1.0+abc') + await uploadRemoteInstallDirectory(conn, '/local/relay', '/h/.orca-remote/stage/payload', posix) + await writeRemoteInstallFile(conn, posix, '/h/.orca-remote/stage/payload/.version', '0.1.0+abc') expect(uploadDirectoryViaExecStdin).toHaveBeenCalledWith( conn, @@ -123,16 +123,16 @@ describe('relay install transfer fallback selection', () => { it('asks SFTP again after a reconnect', async () => { let generation = 1 const conn = connection(subsystemRefused, { getConnectGeneration: () => generation }) - await uploadRelayDirectory(conn, '/l', '/h/r', posix) + await uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix) generation = 2 - await uploadRelayDirectory(conn, '/l', '/h/r', posix) + await uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix) expect(conn.uploadDirectory).toHaveBeenCalledTimes(2) }) it('falls back for a chrooted subsystem without remembering it as a host verdict', async () => { const conn = connection(sandboxed) - await uploadRelayDirectory(conn, '/l', '/h/r', posix) - await uploadRelayDirectory(conn, '/l', '/h/r', posix) + await uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix) + await uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix) expect(conn.uploadDirectory).toHaveBeenCalledTimes(2) expect(uploadDirectoryViaExecStdin).toHaveBeenCalledTimes(2) }) @@ -140,7 +140,7 @@ describe('relay install transfer fallback selection', () => { it('surfaces unverifiable transport loss instead of retrying down another path', async () => { const lost = unconfirmed() const conn = connection(() => lost) - await expect(uploadRelayDirectory(conn, '/l', '/h/r', posix)).rejects.toBe(lost) + await expect(uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix)).rejects.toBe(lost) expect(uploadDirectoryViaExecStdin).not.toHaveBeenCalled() }) @@ -151,17 +151,17 @@ describe('relay install transfer fallback selection', () => { return subsystemRefused() }) await expect( - uploadRelayDirectory(conn, '/l', '/h/r', posix, { signal: controller.signal }) + uploadRemoteInstallDirectory(conn, '/l', '/h/r', posix, { signal: controller.signal }) ).rejects.toThrow() expect(uploadDirectoryViaExecStdin).not.toHaveBeenCalled() }) it('keeps the sandbox diagnosis where exec stdin cannot apply', async () => { await expect( - uploadRelayDirectory(connection(sandboxed), '/l', 'C:/Users/u/r', windows) + uploadRemoteInstallDirectory(connection(sandboxed), '/l', 'C:/Users/u/r', windows) ).rejects.toThrow(/SFTP subsystem sees a different filesystem/) await expect( - uploadRelayDirectory( + uploadRemoteInstallDirectory( connection(sandboxed, { usesSystemSshTransport: () => true }), '/l', '/h/r', diff --git a/src/main/managed-server/install/remote-install-transfers.ts b/src/main/managed-server/install/remote-install-transfers.ts index 3b56b3899e7..1e60188e2db 100644 --- a/src/main/managed-server/install/remote-install-transfers.ts +++ b/src/main/managed-server/install/remote-install-transfers.ts @@ -1,5 +1,5 @@ -// Relay-install file writes over the host transport's own transfer methods (SFTP on SSH). When -// the host definitively refuses SFTP, POSIX hosts fall back to exec-channel stdin. +// Install file writes over the host transport's own transfer methods (SFTP on SSH). When the +// host definitively refuses SFTP, POSIX hosts fall back to exec-channel stdin. import type { RemoteInstallTransport } from './remote-install-transport' import type { SftpNamespacePathMapping } from '../../ssh/sftp-namespace-resolution' @@ -16,43 +16,43 @@ import { import { isSftpHandshakeCorruption } from '../../ssh/sftp-handshake-corruption' import { explainRemoteInstallWriteFailure, localTreeBytes } from './remote-install-out-of-space' -export type RelayTransferOptions = { +export type RemoteInstallTransferOptions = { signal?: AbortSignal sftpNamespace?: SftpNamespacePathMapping } -export async function uploadRelayDirectory( +export async function uploadRemoteInstallDirectory( conn: RemoteInstallTransport, - localRelayDir: string, + localDir: string, shellRemoteDir: string, hostPlatform: RemoteHostPlatform, - options?: RelayTransferOptions + options?: RemoteInstallTransferOptions ): Promise { await transferWithExecStdinFallback( conn, hostPlatform, shellRemoteDir, options?.signal, - () => localTreeBytes(localRelayDir), + () => localTreeBytes(localDir), () => - conn.uploadDirectory(localRelayDir, shellRemoteDir, { + conn.uploadDirectory(localDir, shellRemoteDir, { hostPlatform, signal: options?.signal, sftpNamespace: options?.sftpNamespace }), () => - uploadDirectoryViaExecStdin(conn, localRelayDir, shellRemoteDir, hostPlatform, { + uploadDirectoryViaExecStdin(conn, localDir, shellRemoteDir, hostPlatform, { signal: options?.signal }) ) } -export async function writeRelayFile( +export async function writeRemoteInstallFile( conn: RemoteInstallTransport, hostPlatform: RemoteHostPlatform, shellRemotePath: string, contents: string, - options?: RelayTransferOptions + options?: RemoteInstallTransferOptions ): Promise { await transferWithExecStdinFallback( conn, @@ -167,7 +167,7 @@ async function transferOnce( sftpUnavailableGeneration.set(conn, connectGeneration(conn)) } console.warn( - `[ssh-relay] SFTP ${reason === 'sftp-unavailable' ? 'is unavailable' : 'cannot see the install path'} (${error instanceof Error ? error.message : String(error)}); streaming ${remotePath} over exec stdin` + `[ssh] SFTP ${reason === 'sftp-unavailable' ? 'is unavailable' : 'cannot see the install path'} (${error instanceof Error ? error.message : String(error)}); streaming ${remotePath} over exec stdin` ) } await execStdinTransfer() diff --git a/src/main/managed-server/install/remote-versioned-install.ts b/src/main/managed-server/install/remote-versioned-install.ts index 06f16f0aa77..141f8365299 100644 --- a/src/main/managed-server/install/remote-versioned-install.ts +++ b/src/main/managed-server/install/remote-versioned-install.ts @@ -33,22 +33,22 @@ type RemoteInstallProbeOptions = { } /** - * Read the local relay's content-hashed version (e.g. "0.1.0+0a5fe134d020") from - * `${localRelayDir}/.version`. Throws on missing/empty so the caller can't + * Read the local build's content-hashed version (e.g. "0.1.0+0a5fe134d020") from + * `${localDir}/.version`. Throws on missing/empty so the caller can't * silently fall back to a path where a stale-generation daemon may be running. */ -export function readLocalFullVersion(localRelayDir: string): string { - const versionFile = join(localRelayDir, '.version') +export function readLocalFullVersion(localDir: string): string { + const versionFile = join(localDir, '.version') if (!existsSync(versionFile)) { throw new Error( - `Orca's local relay build is missing its version marker at ${versionFile}. ` + + `Orca's local server build is missing its version marker at ${versionFile}. ` + `This usually indicates a packaging or build problem; reinstall Orca.` ) } const v = readFileSync(versionFile, 'utf-8').trim() if (!v) { throw new Error( - `Orca's local relay version marker at ${versionFile} is empty. ` + + `Orca's local server version marker at ${versionFile} is empty. ` + `This usually indicates a packaging or build problem; reinstall Orca.` ) } diff --git a/src/main/ssh/orcad-remote-deploy.test.ts b/src/main/ssh/orcad-remote-deploy.test.ts index 1fafc0bcc64..111341f5a8a 100644 --- a/src/main/ssh/orcad-remote-deploy.test.ts +++ b/src/main/ssh/orcad-remote-deploy.test.ts @@ -18,8 +18,8 @@ vi.mock('../managed-server/install/remote-install-lock', () => ({ RELAY_INSTALL_LOCK_NAME: '.install-lock' })) vi.mock('../managed-server/install/remote-install-transfers', () => ({ - uploadRelayDirectory: vi.fn().mockResolvedValue(undefined), - writeRelayFile: vi.fn().mockResolvedValue(undefined) + uploadRemoteInstallDirectory: vi.fn().mockResolvedValue(undefined), + writeRemoteInstallFile: vi.fn().mockResolvedValue(undefined) })) vi.mock('../managed-server/install/orcad-remote-record-file', async (importOriginal) => ({ ...(await importOriginal()), @@ -37,7 +37,7 @@ import { acquireInstallLock, isRelayInstallLockStale } from '../managed-server/install/remote-install-lock' -import { uploadRelayDirectory } from '../managed-server/install/remote-install-transfers' +import { uploadRemoteInstallDirectory } from '../managed-server/install/remote-install-transfers' import { writeAtomicOrcadRemoteRecord } from '../managed-server/install/orcad-remote-record-file' import { deployOrcad, type OrcadDeployOptions } from './orcad-remote-deploy' import { installOrcadBundle } from '../managed-server/install/orcad-remote-install' @@ -243,7 +243,7 @@ describe('orcad install lock ownership', () => { await install() expect(acquireInstallLock).not.toHaveBeenCalled() expect(abandonInstall).not.toHaveBeenCalled() - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() }) it('releases its lock when another installer completed while acquisition waited', async () => { @@ -252,13 +252,13 @@ describe('orcad install lock ownership', () => { expect(acquireInstallLock).toHaveBeenCalledOnce() expect(abandonInstall).toHaveBeenCalledOnce() expect(abandonInstall).toHaveBeenCalledWith(expect.anything(), remoteDir, options().host) - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() expect(finalizeInstall).not.toHaveBeenCalled() }) it('publishes a complete install before releasing its lock once', async () => { await install() - expect(uploadRelayDirectory).toHaveBeenCalledOnce() + expect(uploadRemoteInstallDirectory).toHaveBeenCalledOnce() expect(finalizeInstall).toHaveBeenCalledWith(expect.anything(), remoteDir, options().host, { signal: undefined, releaseLock: false @@ -271,7 +271,7 @@ describe('orcad install lock ownership', () => { it('releases a failed upload without publishing it or replacing its error', async () => { const error = new Error('upload interrupted') - vi.mocked(uploadRelayDirectory).mockRejectedValueOnce(error) + vi.mocked(uploadRemoteInstallDirectory).mockRejectedValueOnce(error) await expect(install()).rejects.toBe(error) expect(abandonInstall).toHaveBeenCalledOnce() expect(finalizeInstall).not.toHaveBeenCalled() @@ -280,7 +280,7 @@ describe('orcad install lock ownership', () => { it('releases its lock without reusing an aborted operation signal', async () => { const controller = new AbortController() const error = new Error('deployment canceled') - vi.mocked(uploadRelayDirectory).mockImplementationOnce(async () => { + vi.mocked(uploadRemoteInstallDirectory).mockImplementationOnce(async () => { controller.abort(error) controller.signal.throwIfAborted() }) @@ -295,7 +295,7 @@ describe('orcad install lock ownership', () => { vi.mocked(acquireInstallLock).mockRejectedValueOnce(error) await expect(install()).rejects.toBe(error) expect(abandonInstall).not.toHaveBeenCalled() - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() }) }) @@ -338,7 +338,9 @@ describe('deployOrcad', () => { expect(vi.mocked(acquireInstallLock).mock.calls[0][1]).toBe( `/home/u/.orca-remote/orcad-${NEW_VERSION}` ) - expect(vi.mocked(uploadRelayDirectory).mock.calls[0][2]).toContain(`orcad-${NEW_VERSION}`) + expect(vi.mocked(uploadRemoteInstallDirectory).mock.calls[0][2]).toContain( + `orcad-${NEW_VERSION}` + ) }) it.each([ @@ -367,7 +369,7 @@ describe('deployOrcad', () => { expect(ensureRemoteOrcadNodeRuntime).toHaveBeenCalledWith( expect.objectContaining({ target, slotDir: `/home/u/.orca-remote/orcad-${NEW_VERSION}` }) ) - expect(vi.mocked(uploadRelayDirectory).mock.invocationCallOrder[0]).toBeLessThan( + expect(vi.mocked(uploadRemoteInstallDirectory).mock.invocationCallOrder[0]).toBeLessThan( mockExec.mock.invocationCallOrder[chmod] ) expect(mockExec.mock.invocationCallOrder[chmod]).toBeLessThan( @@ -389,7 +391,7 @@ describe('deployOrcad', () => { NEW_VERSION, `C:/Users/u/.orca-remote/orcad-${NEW_VERSION}` ) - expect(uploadRelayDirectory).toHaveBeenCalledOnce() + expect(uploadRemoteInstallDirectory).toHaveBeenCalledOnce() expect(finalizeInstall).toHaveBeenCalledOnce() expect(mockExec.mock.calls.some(([, command]) => String(command).startsWith('chmod '))).toBe( false @@ -425,7 +427,7 @@ describe('deployOrcad', () => { outcome: 'installed-not-activated', code }) - expect(uploadRelayDirectory).not.toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).not.toHaveBeenCalled() }) // BUG-21: a bare stale fence (a wake cut short) failed every update with "Recover it first". @@ -446,7 +448,7 @@ describe('deployOrcad', () => { expect(vi.mocked(acquireInstallLock).mock.calls[0]?.[3]).toMatchObject({ allowStaleTakeover: true }) - expect(uploadRelayDirectory).toHaveBeenCalled() + expect(uploadRemoteInstallDirectory).toHaveBeenCalled() }) it('leaves an upload incomplete when the remote cannot make search executable', async () => { @@ -568,7 +570,7 @@ describe('deployOrcad', () => { log: [] } scriptHost(script) - vi.mocked(uploadRelayDirectory).mockImplementationOnce(async () => { + vi.mocked(uploadRemoteInstallDirectory).mockImplementationOnce(async () => { script.activationRecord = JSON.stringify(settle(newer)) }) const admitRecord = vi.fn((record: OrcadActivationRecord) => @@ -602,7 +604,7 @@ describe('deployOrcad', () => { const counted = JSON.parse(ACTIVE_OLD) // An admitted peer activation: same app line, so only the census binding can catch it. const peer = withActivatedVersion(counted, '0.1.5+dd01', null, new Date(5), '1.4.0') - vi.mocked(uploadRelayDirectory).mockImplementationOnce(async () => { + vi.mocked(uploadRemoteInstallDirectory).mockImplementationOnce(async () => { script.activationRecord = JSON.stringify(peer) }) expect( @@ -649,7 +651,7 @@ describe('deployOrcad', () => { scriptHost(script) const counted = JSON.parse(ACTIVE_OLD) const peer = withActivatedVersion(counted, '0.1.5+dd01', null, new Date(5), '1.4.0') - vi.mocked(uploadRelayDirectory).mockImplementationOnce(async () => { + vi.mocked(uploadRemoteInstallDirectory).mockImplementationOnce(async () => { script.activationRecord = JSON.stringify(peer) }) const recountCensus = vi.fn() @@ -672,7 +674,7 @@ describe('deployOrcad', () => { code: 'orcad_update_terminals_running' }) // The bytes landed; nothing was stopped, launched or snapshotted. - expect(vi.mocked(uploadRelayDirectory)).toHaveBeenCalled() + expect(vi.mocked(uploadRemoteInstallDirectory)).toHaveBeenCalled() expect(script.log).toEqual([]) }) @@ -822,6 +824,6 @@ describe('deployOrcad', () => { } scriptHost(script) await expect(deployOrcad(options())).rejects.toThrow('activation record') - expect(vi.mocked(uploadRelayDirectory)).not.toHaveBeenCalled() + expect(vi.mocked(uploadRemoteInstallDirectory)).not.toHaveBeenCalled() }) }) diff --git a/src/main/ssh/sftp-stream-late-error.test.ts b/src/main/ssh/sftp-stream-late-error.test.ts index 267b933ccfb..7f46cc14804 100644 --- a/src/main/ssh/sftp-stream-late-error.test.ts +++ b/src/main/ssh/sftp-stream-late-error.test.ts @@ -6,7 +6,7 @@ import { PassThrough } from 'node:stream' import type { SFTPWrapper } from 'ssh2' import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { uploadBuffer, uploadFile, writeStringViaSftp } from './sftp-upload' -import { writeRelayFile } from '../managed-server/install/remote-install-transfers' +import { writeRemoteInstallFile } from '../managed-server/install/remote-install-transfers' import type { SshConnection } from './connection/ssh-connection' import { getRemoteHostPlatform } from './ssh-remote-platform' @@ -91,7 +91,7 @@ describe('sandboxed SFTP subsystem diagnosis', () => { writeFile: () => Promise.reject(Object.assign(new Error('permission denied'), { code: 3 })) } as unknown as SshConnection - const failure: unknown = await writeRelayFile( + const failure: unknown = await writeRemoteInstallFile( conn, getRemoteHostPlatform('linux-x64'), '/home/user/.orca-remote/relay-1/.version', @@ -112,7 +112,7 @@ describe('sandboxed SFTP subsystem diagnosis', () => { // Windows hosts have no exec-stdin fallback; POSIX hosts stream instead (exec-fallback tests). await expect( - writeRelayFile( + writeRemoteInstallFile( conn, getRemoteHostPlatform('win32-x64'), 'C:/Users/user/.orca-remote/relay-1/.version', @@ -127,7 +127,7 @@ describe('sandboxed SFTP subsystem diagnosis', () => { } as unknown as SshConnection await expect( - writeRelayFile(conn, getRemoteHostPlatform('linux-x64'), '/home/user/x', 'v1') + writeRemoteInstallFile(conn, getRemoteHostPlatform('linux-x64'), '/home/user/x', 'v1') ).rejects.toThrow('Connection lost') }) }) diff --git a/src/main/ssh/ssh-exec-stdin-file-transfer.ts b/src/main/ssh/ssh-exec-stdin-file-transfer.ts index 91be5113e74..0c9c0b3a6f0 100644 --- a/src/main/ssh/ssh-exec-stdin-file-transfer.ts +++ b/src/main/ssh/ssh-exec-stdin-file-transfer.ts @@ -14,7 +14,7 @@ import { Readable } from 'node:stream' import type { RemoteInstallTransport } from '../managed-server/install/remote-install-transport' import { quotePosixShell } from '../../shared/shell-argument-quoting' import { execCommand } from './remote-exec/remote-exec-command' -import { RELAY_DEPLOY_TIMEOUT_MS } from '../managed-server/install/remote-deploy-timing' +import { REMOTE_INSTALL_TIMEOUT_MS } from '../managed-server/install/remote-deploy-timing' import type { RemoteHostPlatform } from './ssh-remote-platform' import { makePosixWriteFileCommand } from './system-transport/system-ssh-file-binary-transfer' import { collectLocalUploadPlan } from './system-transport/system-ssh-file-transfer' @@ -59,7 +59,7 @@ async function writeViaExecStdin( conn, makeExecStdinWriteFileCommand(remotePath, byteLength, { token, executable }), // Why the deploy bound: a runtime archive over a slow link outlasts the 30 s probe default. - { signal: options?.signal, stdin, timeoutMs: RELAY_DEPLOY_TIMEOUT_MS } + { signal: options?.signal, stdin, timeoutMs: REMOTE_INSTALL_TIMEOUT_MS } ) if (output.trim().split('\n').at(-1) !== EXEC_STDIN_WRITE_DONE) { throw new Error(`The host did not confirm the upload of ${remotePath}`) diff --git a/src/main/wsl/wsl-directory-upload.ts b/src/main/wsl/wsl-directory-upload.ts index fc141a17395..0b349e89a1c 100644 --- a/src/main/wsl/wsl-directory-upload.ts +++ b/src/main/wsl/wsl-directory-upload.ts @@ -12,7 +12,7 @@ import { parseWslUncPath, toLinuxPath } from '../../shared/wsl-paths' import type { RemoteInstallTransport } from '../managed-server/install/remote-install-transport' import { quotePosixShell } from '../../shared/shell-argument-quoting' import { execCommand } from '../ssh/remote-exec/remote-exec-command' -import { RELAY_DEPLOY_TIMEOUT_MS } from '../managed-server/install/remote-deploy-timing' +import { REMOTE_INSTALL_TIMEOUT_MS } from '../managed-server/install/remote-deploy-timing' import { getRemoteHostPlatform } from '../ssh/ssh-remote-platform' import { collectLocalUploadPlan, @@ -182,7 +182,7 @@ export async function uploadDirectoryIntoWsl( const manifest = await digestManifest(plan, signal) const stage = `${remoteDir.replace(/\/+$/, '')}/.orca-upload-${randomBytes(8).toString('hex')}` // Why the deploy bound: a runtime tree over a cold distro outlasts the 30 s probe default. - const execOptions = { signal, timeoutMs: RELAY_DEPLOY_TIMEOUT_MS } + const execOptions = { signal, timeoutMs: REMOTE_INSTALL_TIMEOUT_MS } const stagedFromGuest = guestSource !== null && (await execCommand(conn, stageFromGuestPathCommand(guestSource, stage), execOptions)).trim() !==