mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 08:02:33 +00:00
fix(ssh): Windows hosts without Add-Type staging; runtime-store GC on Windows (#24149)
* fix(ssh): collect the pinned-Node runtime store on Windows hosts
Windows SSH hosts now run runtime-store GC instead of skipping it: one
PowerShell inventory reads .runtime-ref-node-<sha> and .runtime-node refs from
every version dir, and one Get-CimInstance Win32_Process query filtered on an
image path under runtimes\ adds process holds (never by image name; a failed
query keeps everything). Stale upload stages are swept with the same rule as
POSIX. Promotion and the post-upload hold check now take the store lock on
Windows too, and the lock's own commands run unwrapped there.
Windows relay version-dir liveness now honours .relay-pid (design D5): a live
PID answers ALIVE before any pipe is touched, a dead one (ESRCH) plus refusing
pipes is exited, anything else is unverifiable. The runtime probe adopts a
pinned node.exe an earlier vault reader left without a .verified marker after
running it.
* fix(ssh): Windows stage fencing and vault runtime go through the verified node.exe
Upload-stage file identity on Windows no longer compiles an Add-Type P/Invoke
helper when the relay runs on Orca's verified pinned node.exe: the stage
commands run a fixed fs.lstatSync(..., {bigint:true}) script through it. It
prints the legacy helper's vol:high:low lowercase hex, and identity files are
compared after normalising hex spelling, so old and new clients recover each
other's stages. Host-Node relays keep the legacy helper; the choice is
documented in windows-edr-posture.md.
The Windows OpenCode vault reader now installs the pinned runtime through
ensureRemoteOrcadNodeRuntime (official zip, host-side extraction, .verified,
store lock) instead of uploading a client-extracted node.exe, and the relay dir
gains a .runtime-ref-node-<sha> so store GC keeps the runtime the vault uses.
* test(ssh): run the Windows stage-identity and store-GC tests on the Windows lane
The legacy/node.exe identity compatibility test and the Win32_Process hold path
were gated to win32 but no CI lane ran them. Add both files to the Windows
package lane and a real running-node.exe hold test.
* test(ssh): tear down Windows-lane temp trees through removeTreeSync
* test(ssh): grant the store lock to the Windows OpenCode runtime setup test
The Windows promote now runs under runtimes/.store-lock, so the mocked host
must answer the lock's CreateNew step.
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -1,16 +1,11 @@
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { copyFile, link, mkdtemp, rm } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { waitForPromiseWithSignal } from '../../shared/abort-signal-reason'
|
||||
import { ORCAD_NODE_RUNTIME_WINDOWS_EXECUTABLE } from '../../shared/orcad-artifacts'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import type { RemoteRuntimeStep } from './orcad-remote-node-runtime'
|
||||
import {
|
||||
preparePinnedNodeForVault,
|
||||
type PinnedNodeVaultUpload
|
||||
} from './ssh-relay-opencode-pinned-node'
|
||||
import { preparePinnedNodeForVault } from './ssh-relay-opencode-pinned-node'
|
||||
import { RUNTIME_REF_NODE_PREFIX } from './remote-node-runtime-store-inventory'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { uploadRelayDirectory, writeRelayFile } from './ssh-relay-install-transfers'
|
||||
import { writeRelayFile } from './ssh-relay-install-transfers'
|
||||
import {
|
||||
createRelayUploadStageNamespace,
|
||||
relayUploadStageSftpNamespaceMapping
|
||||
@@ -28,7 +23,6 @@ import {
|
||||
import {
|
||||
parseOpenCodeRuntimeResult,
|
||||
probeOpenCodeNodeSqliteCommand,
|
||||
promoteOpenCodeRuntimeCommand,
|
||||
publishOpenCodeRuntimeReferenceCommand
|
||||
} from './ssh-relay-opencode-runtime-commands'
|
||||
|
||||
@@ -46,6 +40,8 @@ const installations = new WeakMap<
|
||||
|
||||
type SetupOptions = {
|
||||
nodePath: string
|
||||
/** The relay's verified pinned node.exe, if any; stage fencing then needs no Add-Type (D5). */
|
||||
verifiedNodePath?: string
|
||||
relayDir: string
|
||||
signal?: AbortSignal
|
||||
cacheRoot?: string
|
||||
@@ -156,21 +152,28 @@ async function install(
|
||||
throw new Error('The host did not complete its SQLite read probe.')
|
||||
}
|
||||
let executable = node.executable
|
||||
let upload: PinnedNodeVaultUpload | undefined
|
||||
let runtimeRef: { path: string; sha256: string } | undefined
|
||||
let identityNode = options.verifiedNodePath
|
||||
if (node.status === 'unsupported') {
|
||||
const pinned = await preparePinnedNodeForVault({
|
||||
conn,
|
||||
host,
|
||||
nodePath: options.nodePath,
|
||||
relayDir: options.relayDir,
|
||||
cacheRoot: options.cacheRoot,
|
||||
referencePath: joinRemotePath(host, options.relayDir, RUNTIME_REFERENCE_NAME),
|
||||
signal,
|
||||
exec,
|
||||
remote
|
||||
})
|
||||
executable = pinned.executable
|
||||
upload = pinned.upload
|
||||
identityNode ??= pinned.executable
|
||||
runtimeRef = {
|
||||
path: joinRemotePath(
|
||||
host,
|
||||
options.relayDir,
|
||||
`${RUNTIME_REF_NODE_PREFIX}${pinned.runtimeSha256}`
|
||||
),
|
||||
sha256: pinned.runtimeSha256
|
||||
}
|
||||
}
|
||||
if (!executable) {
|
||||
throw new Error('The host did not identify its SQLite executable.')
|
||||
@@ -179,12 +182,13 @@ async function install(
|
||||
const relativePool = `${RELAY_REMOTE_DIR}/${RELAY_UPLOAD_STAGE_POOL_NAME}`
|
||||
const poolDir = joinRemotePath(host, remoteHome, relativePool)
|
||||
const owner = createRelayInstallMarkerFileName()
|
||||
await exec(recoverOneStaleRelayUploadStageCommand(host, poolDir))
|
||||
const identity = identityNode ? { node: identityNode } : undefined
|
||||
await exec(recoverOneStaleRelayUploadStageCommand(host, poolDir, undefined, identity))
|
||||
const stage = parseReservedRelayUploadStage(
|
||||
host,
|
||||
poolDir,
|
||||
owner,
|
||||
await exec(reserveRelayUploadStageCommand(host, poolDir, owner))
|
||||
await exec(reserveRelayUploadStageCommand(host, poolDir, owner, identity))
|
||||
)
|
||||
const stageDir = stage.slotDir
|
||||
const namespace = createRelayUploadStageNamespace(`${relativePool}/${stage.slotName}`, owner)
|
||||
@@ -194,40 +198,6 @@ async function install(
|
||||
: undefined
|
||||
let cleanupAllowed = true
|
||||
try {
|
||||
if (upload) {
|
||||
const { localRuntime } = upload
|
||||
const localStage = await mkdtemp(join(dirname(localRuntime), '.vault-upload-'))
|
||||
try {
|
||||
const binaryName = ORCAD_NODE_RUNTIME_WINDOWS_EXECUTABLE
|
||||
const localBinary = join(localStage, binaryName)
|
||||
await link(localRuntime, localBinary).catch(() => copyFile(localRuntime, localBinary))
|
||||
signal.throwIfAborted()
|
||||
await remote(() =>
|
||||
uploadRelayDirectory(conn, localStage, joinRemotePath(host, stageDir, 'payload'), host, {
|
||||
signal,
|
||||
sftpNamespace: mapping()
|
||||
})
|
||||
)
|
||||
const promoted = parseOpenCodeRuntimeResult(
|
||||
await exec(
|
||||
promoteOpenCodeRuntimeCommand({
|
||||
host,
|
||||
nodePath: options.nodePath,
|
||||
stagedBinary: joinRemotePath(host, stageDir, 'payload', binaryName),
|
||||
executable,
|
||||
expectedHash: upload.expectedHash,
|
||||
repairToken: token
|
||||
})
|
||||
)
|
||||
)
|
||||
if (promoted.status !== 'ready' || !promoted.executable) {
|
||||
throw new Error('The host did not verify the uploaded SQLite runtime.')
|
||||
}
|
||||
executable = promoted.executable
|
||||
} finally {
|
||||
await rm(localStage, { recursive: true, force: true }).catch(() => {})
|
||||
}
|
||||
}
|
||||
const referenceName = RUNTIME_REFERENCE_NAME
|
||||
const stagedReference = joinRemotePath(host, stageDir, 'payload', referenceName)
|
||||
signal.throwIfAborted()
|
||||
@@ -244,7 +214,8 @@ async function install(
|
||||
nodePath: options.nodePath,
|
||||
stagedReference,
|
||||
reference: joinRemotePath(host, options.relayDir, referenceName),
|
||||
token
|
||||
token,
|
||||
runtimeRef
|
||||
})
|
||||
)
|
||||
)
|
||||
@@ -254,11 +225,13 @@ async function install(
|
||||
throw error
|
||||
} finally {
|
||||
if (cleanupAllowed && !signal.aborted) {
|
||||
await exec(cleanupOwnedRelayUploadStageCommand(host, stage, owner)).catch((error) => {
|
||||
if (isUnconfirmedSshCommandTermination(error)) {
|
||||
throw error
|
||||
await exec(cleanupOwnedRelayUploadStageCommand(host, stage, owner, identity)).catch(
|
||||
(error) => {
|
||||
if (isUnconfirmedSshCommandTermination(error)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user