import { execFile } from 'node:child_process' import { createHash } from 'node:crypto' import { createReadStream } from 'node:fs' import { readdir, realpath, stat } from 'node:fs/promises' import { createRequire } from 'node:module' import { dirname, isAbsolute, join, relative } from 'node:path' import { isDeepStrictEqual, promisify } from 'node:util' const require = createRequire(import.meta.url) const execFileAsync = promisify(execFile) const MAX_TOOL_BYTES = 300 * 1024 * 1024 const TOOL_TIMEOUT_MS = 60_000 const TOOL_OUTPUT_BYTES = 1024 * 1024 const MAX_PACKAGE_FILES = 500 const MAX_PACKAGE_BYTES = 32 * 1024 * 1024 async function sha256File(path) { const metadata = await stat(path) if (!metadata.isFile() || metadata.size > MAX_TOOL_BYTES) { throw new Error(`Runtime build tool is not a bounded regular file: ${path}`) } const hash = createHash('sha256') for await (const chunk of createReadStream(path)) { hash.update(chunk) } return `sha256:${hash.digest('hex')}` } async function capture(command, args) { try { return await execFileAsync(command, args, { encoding: 'utf8', maxBuffer: TOOL_OUTPUT_BYTES, timeout: TOOL_TIMEOUT_MS, windowsHide: true }) } catch (error) { if (typeof error?.stdout === 'string' || typeof error?.stderr === 'string') { return { stdout: error.stdout ?? '', stderr: error.stderr ?? '' } } throw error } } export function selectSshRelayRuntimeToolVersion({ stdout = '', stderr = '' }, pattern) { const lines = `${stderr}\n${stdout}` .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean) const version = pattern ? lines.find((line) => pattern.test(line)) : lines[0] if (!version || Buffer.byteLength(version) > 512) { throw new Error('Runtime build tool did not report a bounded version line') } return version } async function locateExecutable(command, { xcrun = false } = {}) { if (isAbsolute(command)) { return realpath(command) } const locator = xcrun ? ['xcrun', ['--find', command]] : process.platform === 'win32' ? ['where.exe', [command]] : ['which', [command]] const result = await capture(...locator) const path = result.stdout .split(/\r?\n/) .map((line) => line.trim()) .find(Boolean) if (!path || !isAbsolute(path)) { throw new Error(`Runtime build tool executable could not be resolved: ${command}`) } return realpath(path) } async function executableRecord({ command, args, versionCommand, versionArgs, versionPattern, xcrun = false }) { const path = await locateExecutable(command, { xcrun }) const result = await capture(versionCommand ?? path, versionArgs ?? args) return { version: selectSshRelayRuntimeToolVersion(result, versionPattern), sha256: await sha256File(path) } } async function packageTreeRecord(name) { const parsed = require(`${name}/package.json`) const root = dirname(require.resolve(`${name}/package.json`)) const files = [] let totalBytes = 0 async function visit(directory) { const entries = await readdir(directory, { withFileTypes: true }) entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) for (const entry of entries) { const path = join(directory, entry.name) if (entry.isDirectory()) { await visit(path) } else if (entry.isFile()) { files.push(path) totalBytes += (await stat(path)).size } else { throw new Error(`Runtime build package contains a special entry: ${name}`) } if (files.length > MAX_PACKAGE_FILES) { throw new Error(`Runtime build package exceeds the file-count bound: ${name}`) } if (totalBytes > MAX_PACKAGE_BYTES) { throw new Error(`Runtime build package exceeds the byte bound: ${name}`) } } } await visit(root) const hash = createHash('sha256') for (const path of files) { hash.update(relative(root, path).replaceAll('\\', '/')) hash.update('\0') hash.update(await sha256File(path)) hash.update('\n') } return { version: parsed.version, sha256: `sha256:${hash.digest('hex')}`, files: files.length } } export function sshRelayRuntimeBuilderIdentity({ gitCommit, env = process.env }) { if (env.GITHUB_ACTIONS !== 'true') { return `local://${process.platform}/${process.arch}` } if ( !/^[0-9a-f]{40}$/.test(gitCommit) || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(env.GITHUB_REPOSITORY ?? '') ) { throw new Error('GitHub runtime builder identity is incomplete') } const workflow = (env.GITHUB_WORKFLOW_REF ?? '').split('@')[0] const prefix = `${env.GITHUB_REPOSITORY}/` if ( !workflow.startsWith(prefix) || workflow.slice(prefix.length) !== '.github/workflows/ssh-relay-runtime-artifacts.yml' ) { throw new Error('GitHub runtime builder workflow identity is malformed') } return `https://github.com/${env.GITHUB_REPOSITORY}/blob/${gitCommit}/${workflow.slice(prefix.length)}` } export function sshRelayRuntimeRunnerIdentity({ env = process.env } = {}) { const github = env.GITHUB_ACTIONS === 'true' const identity = { os: env.RUNNER_OS ?? process.platform, architecture: env.RUNNER_ARCH ?? process.arch, environment: env.RUNNER_ENVIRONMENT ?? (github ? undefined : 'local'), requestedLabel: env.ORCA_RUNTIME_REQUESTED_RUNNER ?? (github ? undefined : 'local'), image: { os: env.ImageOS ?? (github ? undefined : process.platform), version: env.ImageVersion ?? (github ? undefined : 'local') } } if ( !identity.os || !identity.architecture || !identity.environment || !identity.requestedLabel || !identity.image.os || !identity.image.version ) { throw new Error('Runtime runner identity is incomplete') } return identity } export function assertSshRelayRuntimeToolchain(toolchain, tuple) { const common = ['buildNode', 'bundledNode', 'compiler', 'buildSystem', 'python', 'archive'] const expected = tuple.startsWith('win32-') ? [...common, 'linker', 'nodeAddonApi', 'nodeGyp'] : [...common, 'nodeAddonApi', 'nodeGyp', 'strip'] if (!isDeepStrictEqual(Object.keys(toolchain).sort(), expected.sort())) { throw new Error(`Runtime toolchain record is incomplete for ${tuple}`) } for (const [name, record] of Object.entries(toolchain)) { const keys = Object.keys(record).sort() const expectedKeys = record.files === undefined ? ['sha256', 'version'] : ['files', 'sha256', 'version'] if ( !isDeepStrictEqual(keys, expectedKeys) || typeof record.version !== 'string' || record.version.length === 0 || Buffer.byteLength(record.version) > 512 || record.version.includes('\n') || !/^sha256:[0-9a-f]{64}$/.test(record.sha256) || (record.files !== undefined && (!Number.isSafeInteger(record.files) || record.files <= 0)) ) { throw new Error(`Runtime toolchain record is malformed: ${name}`) } } } export async function collectSshRelayRuntimeToolchain(nodePath) { const common = { bundledNode: await executableRecord({ command: nodePath, args: ['--version'] }), buildNode: await executableRecord({ command: process.execPath, args: ['--version'] }), nodeGyp: await packageTreeRecord('node-gyp'), nodeAddonApi: await packageTreeRecord('node-addon-api') } if (process.platform === 'win32') { const toolchain = { ...common, compiler: await executableRecord({ command: 'cl.exe', args: [], versionPattern: /Compiler Version/i }), linker: await executableRecord({ command: 'link.exe', args: [], versionPattern: /Linker Version/i }), buildSystem: await executableRecord({ command: 'msbuild.exe', args: ['-version', '-nologo'] }), python: await executableRecord({ command: 'python.exe', args: ['--version'] }), archive: await packageTreeRecord('yazl') } assertSshRelayRuntimeToolchain(toolchain, 'win32-x64') return toolchain } const toolchain = { ...common, compiler: await executableRecord({ command: process.platform === 'darwin' ? 'clang++' : 'c++', args: ['--version'], xcrun: process.platform === 'darwin' }), buildSystem: await executableRecord({ command: 'make', args: ['--version'] }), python: await executableRecord({ command: 'python3', args: ['--version'] }), archive: await executableRecord({ command: 'xz', args: ['--version'] }), strip: await executableRecord({ command: 'strip', args: [], versionCommand: process.platform === 'darwin' ? 'xcodebuild' : undefined, versionArgs: process.platform === 'darwin' ? ['-version'] : undefined, xcrun: process.platform === 'darwin' }) } assertSshRelayRuntimeToolchain(toolchain, `${process.platform}-x64`) return toolchain }