mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
* feat(ssh): wire rung B to the glibc 2.17 compat runtime; gate rung C vault on full node:sqlite - COMPAT_RELAY_RUNTIMES lists linux-x64-glibc217; rung B plans the compat slot and compat pinned Node when glibc is below 2.28 or rung A refused with libc_floor/missing_lib. - The relay version folds the compat runtime's executable hash; refusals are cached per runtime. - The orcad template stages an optional linux-x64-glibc217 target (base package + compat node-pty slot + compat runtime marker); the verifier and materializer accept it. - node-pty slot loader falls back to the compat slot when the default slot is missing or needs a newer glibc. - Runtime store GC keeps the compat pin beside the default one on every relay connect. - hasNodeSqliteReaderApi (DatabaseSync + backup) gates relay session search and the relay OpenCode reader, which now names the host Node version in its unavailable reason; the SSH vault reader installs the compat Node on old-glibc hosts and uploads nothing when no pinned Node can run. - Rung D: a remembered noexec reports home_noexec and never advises installing Node. * fix(ssh): re-prove a replayed noexec after rung D so allowing exec recovers the host * fix(ssh): keep the rung B compat runtime pinned in the relay-connect store GC * test(ssh): mock deployment-target facts in the Windows OpenCode runtime tests * ci(ssh): build the glibc 2.17 compat slot for the hostile-host matrix; CentOS 7 lands on rung B --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain>
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
import { join } from 'node:path'
|
|
import { getAppEnvironment } from '../../shared/app-environment'
|
|
import { waitForPromiseWithSignal } from '../../shared/abort-signal-reason'
|
|
import { pinnedNodeRuntimeAsset, type NodeRuntimeTarget } from '../../shared/node-runtime-pin'
|
|
import type { SshConnection } from './ssh-connection'
|
|
import { resolveOrcadDeploymentTargetFacts } from './orcad-deployment-target'
|
|
import { ensureRemoteOrcadNodeRuntime, type RemoteRuntimeStep } from './orcad-remote-node-runtime'
|
|
import { materializeNodeRuntimeArchive } from './pinned-runtime-materializer'
|
|
import type { RemoteHostPlatform } from './ssh-remote-platform'
|
|
import { pinnedRuntimeTargetForHost } from './ssh-relay-runtime-ladder'
|
|
|
|
const DOWNLOAD_TIMEOUT_MS = 180_000
|
|
const downloads = new Map<string, Promise<string>>()
|
|
|
|
/**
|
|
* The pinned Node for hosts whose own Node cannot read OpenCode's database (design D4a). Every
|
|
* host, Windows included, installs it into the shared runtimes/ store as the official archive
|
|
* with a `.verified` marker; nothing here touches vault-sqlite/, which old relays' references
|
|
* still name. `runtimeSha256` is the ref the relay dir must carry so store GC keeps it.
|
|
*/
|
|
export async function preparePinnedNodeForVault(options: {
|
|
conn: SshConnection
|
|
host: RemoteHostPlatform
|
|
relayDir: string
|
|
cacheRoot?: string
|
|
signal: AbortSignal
|
|
exec: (command: string) => Promise<string>
|
|
remote: RemoteRuntimeStep
|
|
}): Promise<{ executable: string; runtimeSha256: string }> {
|
|
const { conn, host, signal, exec } = options
|
|
const facts = await resolveOrcadDeploymentTargetFacts({ conn, host, signal, exec })
|
|
// Why before any upload: below every runtime's glibc floor the self-test could only fail.
|
|
const target = pinnedRuntimeTargetForHost(facts)
|
|
if (!target) {
|
|
const glibc = facts.glibc ? `${facts.glibc.major}.${facts.glibc.minor}` : 'unknown'
|
|
throw new Error(`No Orca-managed Node runs on this host's glibc ${glibc}`)
|
|
}
|
|
const cacheRoot =
|
|
options.cacheRoot ?? join(getAppEnvironment().getPath('userData'), 'orcad-artifacts')
|
|
const { executable } = await ensureRemoteOrcadNodeRuntime({
|
|
conn,
|
|
host,
|
|
slotDir: options.relayDir,
|
|
target,
|
|
archivePath: () => cachedArchive(target, cacheRoot, signal),
|
|
signal,
|
|
remoteStep: options.remote
|
|
})
|
|
return { executable, runtimeSha256: pinnedNodeRuntimeAsset(target).executableSha256 }
|
|
}
|
|
|
|
function cachedArchive(
|
|
target: NodeRuntimeTarget,
|
|
cacheRoot: string,
|
|
signal: AbortSignal
|
|
): Promise<string> {
|
|
const key = `${cacheRoot}\0${target}`
|
|
let pending = downloads.get(key)
|
|
if (!pending) {
|
|
pending = materializeNodeRuntimeArchive(target, cacheRoot, {
|
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
}).finally(() => downloads.delete(key))
|
|
downloads.set(key, pending)
|
|
}
|
|
return waitForPromiseWithSignal(pending, signal)
|
|
}
|