Files
orca/src/main/ssh/orcad-remote-node-runtime.ts
T
OrcaWinandm4air a5601375d4 feat(ssh): opt-in SSH relay on the pinned Node with prebuilt addons (#24129)
* feat(relay): runtime self-test flag and informational runtime on handshake-ok

relay.js --orca-runtime-selftest <nonce> dlopens pty.node, opens and closes a
PTY, and prints one JSON line (nonce, node, napi, glibcVersionRuntime) for the
client to classify before it launches a daemon on a runtime (design D5).

handshake-ok gains an optional runtime {kind, version}; bridge and daemon
already match exactly on version, so it is informational only (D8.1).

* feat(ssh): opt-in pinned-Node relay with prebuilt addons (D5, D6 rung A, D8.1)

SshTarget.remoteRuntime (legacy | pinned-node, default legacy; env
ORCA_SSH_REMOTE_RUNTIME for development) selects the runtime. On POSIX hosts
the pinned path resolves the target with its glibc major.minor, ensures
~/.orca-remote/runtimes/node-<sha>/bin/node, uploads the relay bundle plus
the target's node-pty slot and @parcel/watcher from the orcad artifact
(no npm or node-gyp on the host), writes .runtime-ref-node-<sha>, and folds
the runtime and addon digests into the relay version so pinned and host-Node
builds never share a dir or socket.

A 30 s self-test (node --version, then the relay self-test) gates
.install-complete. Timeouts and lost channels are unverifiable and never step
down; noexec, missing_lib, libc_floor, illegal_instruction and wrong_libc
refusals fall back to the untouched host-Node path with a logged reason,
remembered for the session.

* fix(ssh): only an answered libc probe steps the pinned relay down

A lost channel during target detection says nothing about the host; descending
would launch a host-Node daemon beside a running pinned one and strand its sessions.

* test(ssh): mark the mocked SSH connection casts in the pinned relay tests

* test(ssh): resolve the pinned runtime mock to an executable path

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-10-01 01:41:14 -07:00

276 lines
11 KiB
TypeScript

/**
* Puts the pinned Node beside the orcad slots, at `runtimes/node-<executableSha256>/bin/node`
* (design D2/D5), where `selectOrcadSlotRuntimeCommand` resolves a slot's `.runtime-node`.
* The OpenCode vault reader uses the same store on SSH and WSL hosts (design D4a).
*
* The official archive is uploaded as published and extracted on the host; the executable's
* hash is checked there before it is published. Store-wide GC and the fallback ladder are
* Phase 2: nothing here deletes a runtime.
*/
import { randomBytes } from 'node:crypto'
import { copyFile, link, mkdtemp, rm } from 'node:fs/promises'
import { basename, dirname, join } from 'node:path'
import {
NODE_RUNTIME_ASSETS,
NODE_RUNTIME_PIN,
nodeRuntimeExecutablePath,
type ServerTarget
} from '../../shared/node-runtime-pin'
import {
ORCAD_NODE_RUNTIME_DIR_PREFIX,
ORCAD_NODE_RUNTIME_POSIX_EXECUTABLE,
ORCAD_RUNTIMES_DIRNAME
} from '../../shared/orcad-artifacts'
import type { SshConnection } from './ssh-connection'
import { shellEscape } from './ssh-connection-utils'
import { execCommand } from './ssh-relay-deploy-helpers'
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
import { joinRemotePath, remoteDirname, type RemoteHostPlatform } from './ssh-remote-platform'
import { assertPosixOrcadHost } from './orcad-remote-host-support'
export const REMOTE_NODE_RUNTIME_READY = 'ORCA_NODE_RUNTIME_READY'
export const REMOTE_NODE_RUNTIME_MISSING = 'ORCA_NODE_RUNTIME_MISSING'
export const REMOTE_NODE_RUNTIME_SELFTEST_FAILED = 'ORCA_NODE_RUNTIME_SELFTEST_FAILED'
export const REMOTE_NODE_RUNTIME_EXIT_PREFIX = 'ORCA_RUNTIME_EXIT='
const VERIFIED_MARKER = '.verified'
/** `<storeParent>/runtimes/node-<executableSha256>`, the one host layout (design D5). */
export function nodeRuntimeStoreDir(
host: RemoteHostPlatform,
storeParent: string,
target: ServerTarget
): string {
return joinRemotePath(
host,
storeParent,
ORCAD_RUNTIMES_DIRNAME,
`${ORCAD_NODE_RUNTIME_DIR_PREFIX}${NODE_RUNTIME_ASSETS[target].executableSha256}`
)
}
/** The pinned runtime ran on the host and did not report its version: a host verdict, with evidence. */
export class RemoteNodeRuntimeSelfTestError extends Error {
constructor(
readonly exitStatus: number | null,
readonly output: string
) {
super(
`The pinned Node runtime did not run on the host (exit ${exitStatus ?? 'unknown'}): ${output}`
)
this.name = 'RemoteNodeRuntimeSelfTestError'
}
}
/** Splits `ORCA_RUNTIME_EXIT=<n>` from the output that follows it. */
export function parseRemoteRuntimeExitReport(text: string): {
exitStatus: number | null
output: string
} {
const lines = text.split('\n')
const index = lines.findIndex((line) => line.startsWith(REMOTE_NODE_RUNTIME_EXIT_PREFIX))
if (index === -1) {
return { exitStatus: null, output: text.trim() }
}
const status = Number.parseInt(lines[index].slice(REMOTE_NODE_RUNTIME_EXIT_PREFIX.length), 10)
return {
exitStatus: Number.isNaN(status) ? null : status,
output: lines
.slice(index + 1)
.join('\n')
.trim()
}
}
/**
* The runtime directory beside a version dir (an orcad slot or a relay install); the slot
* selector computes the same path, and the vault reader shares the store.
*/
export function remoteNodeRuntimeDir(
host: RemoteHostPlatform,
slotDir: string,
target: ServerTarget
): string {
return nodeRuntimeStoreDir(host, remoteDirname(slotDir.replace(/\/+$/, ''), host), target)
}
/** The executable inside a POSIX runtime directory. */
export function posixNodeRuntimeExecutable(host: RemoteHostPlatform, runtimeDir: string): string {
return joinRemotePath(host, runtimeDir, ...ORCAD_NODE_RUNTIME_POSIX_EXECUTABLE.split('/'))
}
/** A per-installer stage beside the runtime; its dot prefix keeps it out of `node-*` listings. */
export function nodeRuntimeStageDir(
host: RemoteHostPlatform,
runtimeDir: string,
token: string
): string {
return joinRemotePath(
host,
remoteDirname(runtimeDir, host),
`.stage-${basename(runtimeDir)}-${token}`
)
}
// Why both tools: GNU/busybox ship sha256sum, macOS ships shasum; either prints the digest first.
function sha256Of(path: string): string {
return `{ sha256sum ${path} 2>/dev/null || shasum -a 256 ${path}; } | cut -d' ' -f1`
}
/** Ready only when the executable hashes to the pin and reports the pinned version. */
export function probeRemoteNodeRuntimeCommand(
host: RemoteHostPlatform,
runtimeDir: string,
target: ServerTarget
): string {
assertPosixOrcadHost(host)
const executable = shellEscape(posixNodeRuntimeExecutable(host, runtimeDir))
const verified = shellEscape(joinRemotePath(host, runtimeDir, VERIFIED_MARKER))
return (
`if [ -f ${verified} ] && [ -x ${executable} ] && ` +
`[ "$(${sha256Of(executable)})" = ${shellEscape(NODE_RUNTIME_ASSETS[target].executableSha256)} ]; ` +
`then echo ${REMOTE_NODE_RUNTIME_READY}; else echo ${REMOTE_NODE_RUNTIME_MISSING}; fi`
)
}
/** Cheap warm-path check: a published runtime has its marker and an executable; no re-hash. */
export function remoteNodeRuntimePresentCommand(
host: RemoteHostPlatform,
runtimeDir: string
): string {
assertPosixOrcadHost(host)
const executable = shellEscape(
joinRemotePath(host, runtimeDir, ...ORCAD_NODE_RUNTIME_POSIX_EXECUTABLE.split('/'))
)
const verified = shellEscape(joinRemotePath(host, runtimeDir, VERIFIED_MARKER))
return (
`if [ -f ${verified} ] && [ -x ${executable} ]; ` +
`then echo ${REMOTE_NODE_RUNTIME_READY}; else echo ${REMOTE_NODE_RUNTIME_MISSING}; fi`
)
}
/**
* Extract, verify, self-test and publish. The executable is renamed into place file-by-file,
* so a concurrent installer of the same pin only ever replaces identical verified bytes.
*/
export function promoteRemoteNodeRuntimeCommand(
host: RemoteHostPlatform,
args: {
stageDir: string
archive: string
runtimeDir: string
target: ServerTarget
token: string
}
): string {
assertPosixOrcadHost(host)
const asset = NODE_RUNTIME_ASSETS[args.target]
const member = nodeRuntimeExecutablePath(args.target, asset.archive)
const stage = shellEscape(args.stageDir)
const extracted = shellEscape(joinRemotePath(host, args.stageDir, ...member.split('/')))
const binDir = shellEscape(joinRemotePath(host, args.runtimeDir, 'bin'))
const temporary = shellEscape(joinRemotePath(host, args.runtimeDir, 'bin', `node.${args.token}`))
const executable = shellEscape(posixNodeRuntimeExecutable(host, args.runtimeDir))
const verified = shellEscape(joinRemotePath(host, args.runtimeDir, VERIFIED_MARKER))
return [
`cd ${stage} || exit 1`,
`tar -xzf ${shellEscape(joinRemotePath(host, args.stageDir, args.archive))} ${shellEscape(member)} || { echo ORCA_NODE_RUNTIME_EXTRACT_FAILED; exit 1; }`,
`[ "$(${sha256Of(extracted)})" = ${shellEscape(asset.executableSha256)} ] || { echo ORCA_NODE_RUNTIME_HASH_MISMATCH; exit 1; }`,
`chmod 755 ${extracted}`,
// Why run it: executing is the only reliable check for noexec mounts and a wrong libc.
// Exit 0 on refusal so the caller receives the loader's words to classify, not a bare exit 1.
`{ orca_rt_out=$(${extracted} --version 2>&1); orca_rt_status=$?; ` +
`[ "$orca_rt_out" = ${shellEscape(`v${NODE_RUNTIME_PIN.version}`)} ] || ` +
`{ echo ${REMOTE_NODE_RUNTIME_SELFTEST_FAILED}; echo "${REMOTE_NODE_RUNTIME_EXIT_PREFIX}$orca_rt_status"; ` +
`printf '%s\\n' "$orca_rt_out" | head -c 4000; exit 0; }; }`,
`mkdir -p ${binDir}`,
`mv -f ${extracted} ${temporary}`,
`mv -f ${temporary} ${executable}`,
`: > ${verified}`,
`echo ${REMOTE_NODE_RUNTIME_READY}`
].join(' && ')
}
/**
* Copies the archive at `$1` (a path the host can already read, e.g. a WSL view of the
* client's cache) into a fresh stage, promotes it, and removes the stage whatever happens.
*/
export function installNodeRuntimeFromHostArchiveCommand(
host: RemoteHostPlatform,
args: { runtimeDir: string; archive: string; target: ServerTarget; token: string }
): string {
const stageDir = nodeRuntimeStageDir(host, args.runtimeDir, args.token)
const stage = shellEscape(stageDir)
const promote = promoteRemoteNodeRuntimeCommand(host, { ...args, stageDir })
const copy = `cp -- "$1" ${shellEscape(joinRemotePath(host, stageDir, args.archive))}`
// Subshell: promote's `exit 1` must still reach the stage cleanup.
return `(umask 077 && mkdir -p ${stage} && ${copy} && ${promote}); status=$?; rm -rf ${stage}; exit $status`
}
export type RemoteRuntimeStep = <T>(operation: () => Promise<T>) => Promise<T>
const runDirectly: RemoteRuntimeStep = (operation) => operation()
/**
* Ensures the runtime beside `slotDir` exists on the host, uploading the pinned archive only
* when needed, and returns its executable.
*/
export async function ensureRemoteOrcadNodeRuntime(options: {
conn: SshConnection
host: RemoteHostPlatform
slotDir: string
target: ServerTarget
/** The locally verified pinned archive (pinned-runtime-materializer). */
archivePath: () => Promise<string>
signal?: AbortSignal
/** Wraps each host round trip, so a caller can tell an unconfirmed channel from a local failure. */
remoteStep?: RemoteRuntimeStep
}): Promise<string> {
const { conn, host, target, signal } = options
const remoteStep = options.remoteStep ?? runDirectly
const exec = (command: string, commandSignal?: AbortSignal): Promise<string> =>
remoteStep(() => execCommand(conn, command, { signal: commandSignal }))
const runtimeDir = remoteNodeRuntimeDir(host, options.slotDir, target)
const executable = posixNodeRuntimeExecutable(host, runtimeDir)
const probe = await exec(probeRemoteNodeRuntimeCommand(host, runtimeDir, target), signal)
if (probe.trim() === REMOTE_NODE_RUNTIME_READY) {
return executable
}
const archivePath = await options.archivePath()
const token = randomBytes(8).toString('hex')
const stageDir = nodeRuntimeStageDir(host, runtimeDir, token)
const localStage = await mkdtemp(join(dirname(archivePath), '.runtime-upload-'))
let stageUnconfirmed = false
try {
const archive = basename(archivePath)
await link(archivePath, join(localStage, archive)).catch(() =>
copyFile(archivePath, join(localStage, archive))
)
await exec(`mkdir -p ${shellEscape(stageDir)}`, signal)
await remoteStep(() => uploadRelayDirectory(conn, localStage, stageDir, host, { signal }))
const promoted = await exec(
promoteRemoteNodeRuntimeCommand(host, { stageDir, archive, runtimeDir, target, token }),
signal
)
const selfTestFailure = promoted.indexOf(REMOTE_NODE_RUNTIME_SELFTEST_FAILED)
if (selfTestFailure !== -1) {
const report = parseRemoteRuntimeExitReport(promoted.slice(selfTestFailure))
throw new RemoteNodeRuntimeSelfTestError(report.exitStatus, report.output)
}
if (promoted.trim().split('\n').at(-1) !== REMOTE_NODE_RUNTIME_READY) {
throw new Error(`The host did not verify the pinned Node runtime: ${promoted.trim()}`)
}
return executable
} catch (error) {
stageUnconfirmed = isUnconfirmedSshCommandTermination(error)
throw error
} finally {
await rm(localStage, { recursive: true, force: true }).catch(() => {})
// A transfer that may still be writing keeps its stage; loss of contact is not an exit.
if (!stageUnconfirmed) {
await exec(`rm -rf ${shellEscape(stageDir)}`).catch(() => {})
}
}
}