mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
253 lines
8.7 KiB
JavaScript
253 lines
8.7 KiB
JavaScript
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
|
|
}
|