mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 16:02:24 +00:00
* fix(serve): keep owned Xvfb alive through Electron teardown * test(serve): gate packaged signal shutdown * test: harden headless shutdown lifecycle gate * fix(serve): isolate Xvfb from foreground signals * docs(serve): preserve Xvfb during systemd stop * test(serve): pin shutdown policy to owned Xvfb unit * test(serve): harden shutdown gate portability * test(serve): bound systemd unit parsing
159 lines
4.5 KiB
JavaScript
Executable File
159 lines
4.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import { spawnSync } from 'node:child_process'
|
|
import { createHash } from 'node:crypto'
|
|
import { existsSync, readFileSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
|
|
const args = process.argv.slice(2)
|
|
const appImageArg = valueAfter('--appimage')
|
|
const platform = valueAfter('--platform') ?? 'linux/amd64'
|
|
const signalTarget = valueAfter('--signal-target') ?? 'app'
|
|
const entrypoint = valueAfter('--entrypoint') ?? 'app'
|
|
const intDelivery = valueAfter('--int-delivery') ?? 'foreground-process-group'
|
|
const launcherExecOverlay = args.includes('--launcher-exec-overlay')
|
|
if (!appImageArg) {
|
|
fail('Usage: run-headless-serve-shutdown-docker.mjs --appimage /path/to/orca.AppImage')
|
|
}
|
|
if (!['app', 'serving-electron'].includes(signalTarget)) {
|
|
fail(`Unsupported --signal-target: ${signalTarget}`)
|
|
}
|
|
if (!['app', 'launcher'].includes(entrypoint)) {
|
|
fail(`Unsupported --entrypoint: ${entrypoint}`)
|
|
}
|
|
if (!['pid', 'foreground-process-group'].includes(intDelivery)) {
|
|
fail(`Unsupported --int-delivery: ${intDelivery}`)
|
|
}
|
|
if (intDelivery === 'foreground-process-group' && signalTarget !== 'app') {
|
|
fail('--int-delivery foreground-process-group requires --signal-target app')
|
|
}
|
|
if (launcherExecOverlay && entrypoint !== 'launcher') {
|
|
fail('--launcher-exec-overlay requires --entrypoint launcher')
|
|
}
|
|
|
|
const appImage = resolve(appImageArg)
|
|
const shutdownDockerDirectory = resolve('config', 'docker', 'headless-serve-shutdown')
|
|
const shutdownDockerfile = resolve(shutdownDockerDirectory, 'Dockerfile')
|
|
if (!existsSync(appImage)) {
|
|
fail(`AppImage not found: ${appImage}`)
|
|
}
|
|
|
|
const suffix = `${process.pid}-${Date.now()}`
|
|
const image = `orca-headless-serve-shutdown:${suffix}`
|
|
const artifactVolume = `orca-headless-serve-shutdown-${suffix}`
|
|
const sha256 = createHash('sha256').update(readFileSync(appImage)).digest('hex')
|
|
|
|
try {
|
|
docker([
|
|
'build',
|
|
'--platform',
|
|
platform,
|
|
'-f',
|
|
shutdownDockerfile,
|
|
'-t',
|
|
image,
|
|
shutdownDockerDirectory
|
|
])
|
|
docker(['volume', 'create', artifactVolume])
|
|
docker([
|
|
'run',
|
|
'--rm',
|
|
'--platform',
|
|
platform,
|
|
'--entrypoint',
|
|
'bash',
|
|
'-v',
|
|
`${appImage}:/input/orca.AppImage:ro`,
|
|
'-v',
|
|
`${artifactVolume}:/artifacts`,
|
|
image,
|
|
'-lc',
|
|
[
|
|
'7z x /input/orca.AppImage -o/artifacts/root -y >/dev/null',
|
|
launcherExecOverlay
|
|
? "sed -i 's/^ELECTRON_RUN_AS_NODE=1 /export ELECTRON_RUN_AS_NODE=1\\nexec /' /artifacts/root/resources/bin/orca-ide"
|
|
: ':',
|
|
'chmod -R a+rX /artifacts/root'
|
|
].join(' && ')
|
|
])
|
|
|
|
console.log(
|
|
JSON.stringify({
|
|
type: 'appimage_under_test',
|
|
appImage,
|
|
sha256,
|
|
platform,
|
|
signalTarget,
|
|
entrypoint,
|
|
intDelivery,
|
|
launcherExecOverlay
|
|
})
|
|
)
|
|
const failedSignals = []
|
|
for (const signal of ['INT', 'TERM']) {
|
|
const result = docker(
|
|
[
|
|
'run',
|
|
'--rm',
|
|
'--init',
|
|
'--platform',
|
|
platform,
|
|
'--shm-size',
|
|
'256m',
|
|
'--name',
|
|
`orca-headless-serve-shutdown-${signal.toLowerCase()}-${suffix}`,
|
|
'-e',
|
|
`ORCA_SIGNAL_TARGET=${signalTarget}`,
|
|
'-e',
|
|
`ORCA_TEST_ENTRYPOINT=${entrypoint}`,
|
|
'-e',
|
|
`ORCA_INT_DELIVERY=${intDelivery}`,
|
|
'-v',
|
|
`${artifactVolume}:/artifacts:ro`,
|
|
image,
|
|
signal
|
|
],
|
|
{ allowFailure: true }
|
|
)
|
|
process.stdout.write(result.stdout)
|
|
process.stderr.write(result.stderr)
|
|
if (result.status !== 0) {
|
|
failedSignals.push(`${signal}:${result.status}`)
|
|
}
|
|
}
|
|
if (failedSignals.length > 0) {
|
|
fail(`Shutdown oracle failed: ${failedSignals.join(', ')}`)
|
|
}
|
|
console.log('Headless serve packaged shutdown Docker validation passed.')
|
|
} finally {
|
|
docker(['volume', 'rm', artifactVolume], { allowFailure: true })
|
|
docker(['image', 'rm', image], { allowFailure: true })
|
|
}
|
|
|
|
function valueAfter(flag) {
|
|
const index = args.indexOf(flag)
|
|
return index === -1 ? null : (args[index + 1] ?? null)
|
|
}
|
|
|
|
function docker(dockerArgs, options = {}) {
|
|
const result = spawnSync('docker', dockerArgs, {
|
|
cwd: process.cwd(),
|
|
encoding: 'utf8',
|
|
maxBuffer: 20 * 1024 * 1024
|
|
})
|
|
if (result.error) {
|
|
throw result.error
|
|
}
|
|
if (result.status !== 0 && !options.allowFailure) {
|
|
process.stdout.write(result.stdout)
|
|
process.stderr.write(result.stderr)
|
|
fail(`docker ${dockerArgs[0]} failed with status ${result.status}`)
|
|
}
|
|
return result
|
|
}
|
|
|
|
function fail(message) {
|
|
console.error(message)
|
|
process.exitCode = 1
|
|
throw new Error(message)
|
|
}
|