Files
goodbuddy/src/main/remote-agent/control-plane-package-installer.ts
T
mesalogo 9c6d1611e2 feat: support macOS arm64 remote Agent hosts
Extend GoodBuddy Agent packaging, installation, SSH attachment, Runtime lifecycle, and Desktop package management to macOS arm64. Linux x64, Linux arm64, and Darwin arm64 share the existing signed catalog. Preserve Ask read-only behavior and Execute account permissions, and stop detached tool descendants during Mac cancellation.

Upgrade Desktop before publishing the first mixed-platform catalog because older Linux-only readers reject Darwin entries. Intel Macs are not supported; no launchd service or administrator access is required. This commit does not publish a release.

Validation: 3520 tests passed, 57 skipped; typecheck, lint, and development build passed. Real Mac installation, Ask/Execute, cancellation and reconnect passed; final Linux installation, Ask and reconnect passed. Nine real model requests were recorded.

Release note: 支持将 Apple Silicon Mac 用作远程项目 Host,可安装 macOS arm64 Agent 并使用 Ask、Execute 和断线重连;与 Linux 共用一个包目录,发布混合目录前需升级 Desktop。
2026-09-06 16:14:02 +08:00

2649 lines
75 KiB
TypeScript

import {
createHash,
createPublicKey,
verify
} from 'node:crypto'
import {
chmodSync,
closeSync,
constants,
existsSync,
fstatSync,
fsyncSync,
lstatSync,
mkdirSync,
mkdtempSync,
openSync,
readSync,
renameSync,
rmSync,
statSync,
unlinkSync,
writeSync
} from 'node:fs'
import { homedir } from 'node:os'
import {
basename,
dirname,
join,
resolve
} from 'node:path'
import { crc32 } from 'node:zlib'
const PACKAGE_DESCRIPTOR = 'agent-package.json'
const PACKAGE_SIGNATURE = 'agent-package.sig'
const NODE_PATH = 'agent/node'
const MAXIMUM_ARCHIVE_BYTES = 512 * 1024 * 1024
const MAXIMUM_EXPANDED_BYTES = 1024 * 1024 * 1024
const MAXIMUM_ENTRY_BYTES = 384 * 1024 * 1024
const MAXIMUM_METADATA_BYTES = 1024 * 1024
const MAXIMUM_ENTRIES = 50_002
const MAXIMUM_CENTRAL_BYTES = 32 * 1024 * 1024
const ZIP_LOCAL = 0x04034b50
const ZIP_CENTRAL = 0x02014b50
const ZIP_END = 0x06054b50
const PACKAGE_SIGNATURE_DOMAIN = Buffer.from(
'GoodBuddy Agent Package Descriptor Signature v1\0',
'utf8'
)
const AGENT_SIGNATURE_DOMAIN = Buffer.from(
'GoodBuddy Agent Bundle Manifest Signature v1\0',
'utf8'
)
const RUNTIME_SIGNATURE_DOMAIN = Buffer.from(
'GoodBuddy Remote Runtime Bundle Manifest Signature v1\0',
'utf8'
)
const sha256Pattern = /^[a-f0-9]{64}$/u
const digestPattern = /^sha256:[a-f0-9]{64}$/u
const versionPattern =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u
const windowsReservedNamePattern =
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu
type JsonRecord = Record<string, unknown>
type Architecture = 'x64' | 'arm64'
type Platform = 'linux' | 'darwin'
type Protocol = { major: number; minor: number }
type FileRecord = {
path: string
size: number
sha256: string
mode: '0644' | '0755'
}
type ZipEntry = {
name: string
size: number
checksum: number
mode: '0644' | '0755'
dataOffset: number
recordStart: number
recordEnd: number
}
type TrustedRegistry = {
formatVersion: 1
keys: Array<{
keyId: string
publicKeySpkiBase64: string
environment: 'production' | 'test'
}>
revocations: Array<{ keyId: string }>
}
export type PackageInstallerAgentIdentity = {
installationId: string
agentVersion: string
manifestSha256: string
binaryDigest: string
platform: Platform
architecture: Architecture
protocol: Protocol
supervisor: 'detached-on-demand'
}
export type PackageInstallerRuntimeIdentity = {
runtimeId: 'opencode'
runtimeVersion: string
bundleDigest: string
manifestDigest: string
runtimeAdapterDigest: string
acpCapabilitiesDigest: string
platform: Platform
architecture: Architecture
protocol: Protocol
}
export type PackageInstallerResult = {
type: 'result'
command: 'prepare' | 'commit'
status: 'prepared' | 'committed'
archiveSha256: string
agent: PackageInstallerAgentIdentity
runtime: PackageInstallerRuntimeIdentity
}
export type PackageInstallerEvent =
| {
type: 'progress'
command: 'prepare' | 'commit'
phase: string
}
| PackageInstallerResult
| {
type: 'error'
command: 'prepare' | 'commit'
status: 'failed' | 'rollback-incomplete'
message: string
}
class PackageInstallerRollbackIncompleteError
extends AggregateError {}
type InstallerOptions = {
operationRoot: string
archive: string
expectedSha256: string
/** The acquisition path verified the archive before installer execution. */
archiveSha256Verified?: boolean
homeDirectory?: string
emit?: (event: PackageInstallerEvent) => void
}
type VerifiedPackage = {
archiveSha256: string
entries: Map<string, ZipEntry>
descriptor: JsonRecord
descriptorBytes: Buffer
packageSignatureBytes: Buffer
registry: TrustedRegistry
releaseKeyRegistryBytes: Buffer
agentRuntimeLockBytes: Buffer
remoteRuntimeLockBytes: Buffer
agentManifest: JsonRecord
runtimeManifest: JsonRecord
agentManifestSha256: string
runtimeManifestDigest: string
agent: PackageInstallerAgentIdentity
runtime: PackageInstallerRuntimeIdentity
}
type PreparedState = {
formatVersion: 1
archiveSha256: string
releaseKeyRegistrySha256: string
agentRuntimeLockSha256: string
remoteRuntimeLockSha256: string
packageDescriptorSha256: string
packageSignatureSha256: string
agent: PackageInstallerAgentIdentity
runtime: PackageInstallerRuntimeIdentity
}
function canonicalJson(value: unknown): string {
if (
value === null ||
typeof value === 'boolean' ||
typeof value === 'string'
) {
return JSON.stringify(value)
}
if (typeof value === 'number') {
if (!Number.isFinite(value)) {
throw new Error('Canonical JSON contains a non-finite number')
}
return JSON.stringify(Object.is(value, -0) ? 0 : value)
}
if (Array.isArray(value)) {
return `[${value.map(canonicalJson).join(',')}]`
}
if (!isRecord(value)) {
throw new Error('Canonical JSON contains a non-JSON value')
}
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
.join(',')}}`
}
function prettyBytes(value: unknown): Buffer {
return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8')
}
function sha256(bytes: Buffer | string): string {
return createHash('sha256').update(bytes).digest('hex')
}
function digestCanonicalOperation(
method: string,
scope: unknown,
payload: unknown
): string {
return `sha256:${sha256(
Buffer.from(canonicalJson({ method, payload, scope }), 'utf8')
)}`
}
function isRecord(value: unknown): value is JsonRecord {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
)
}
function exactKeys(
value: JsonRecord,
keys: readonly string[],
label: string
): void {
const actual = Object.keys(value).sort()
const expected = [...keys].sort()
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`${label} has unsupported or missing fields`)
}
}
function protocol(value: unknown, label: string): Protocol {
if (!isRecord(value)) {
throw new Error(`${label} is invalid`)
}
exactKeys(value, ['major', 'minor'], label)
if (
!Number.isSafeInteger(value.major) ||
Number(value.major) < 0 ||
!Number.isSafeInteger(value.minor) ||
Number(value.minor) < 0
) {
throw new Error(`${label} is invalid`)
}
return {
major: Number(value.major),
minor: Number(value.minor)
}
}
function safePath(value: unknown): value is string {
return (
typeof value === 'string' &&
value.length > 0 &&
value.length <= 512 &&
!value.startsWith('/') &&
!value.endsWith('/') &&
!value.includes('\\') &&
value.split('/').every(
(part) =>
part.length > 0 &&
part !== '.' &&
part !== '..' &&
!part.endsWith('.') &&
!part.endsWith(' ') &&
!part.includes(':') &&
[...part].every((character) => character.charCodeAt(0) > 0x1f) &&
!windowsReservedNamePattern.test(part)
)
)
}
function safeManifestPath(value: unknown): value is string {
return (
safePath(value) &&
value.length <= 240
)
}
function readExact(
handle: number,
length: number,
position: number,
label: string
): Buffer {
const output = Buffer.allocUnsafe(length)
let offset = 0
while (offset < length) {
const count = readSync(
handle,
output,
offset,
length - offset,
position + offset
)
if (count <= 0) {
throw new Error(`${label} is truncated`)
}
offset += count
}
return output
}
function parseZip(handle: number, archiveSize: number): Map<string, ZipEntry> {
if (archiveSize < 22) {
throw new Error('Agent package ZIP end record is missing')
}
const endSize = Math.min(archiveSize, 65_557)
const endStart = archiveSize - endSize
const tail = readExact(handle, endSize, endStart, 'Agent package ZIP end record')
let endOffset = -1
for (let offset = tail.length - 22; offset >= 0; offset -= 1) {
if (
tail.readUInt32LE(offset) === ZIP_END &&
offset + 22 + tail.readUInt16LE(offset + 20) === tail.length
) {
endOffset = offset
break
}
}
if (endOffset < 0 || tail.readUInt16LE(endOffset + 20) !== 0) {
throw new Error('Agent package ZIP end record is invalid')
}
const count = tail.readUInt16LE(endOffset + 10)
const centralSize = tail.readUInt32LE(endOffset + 12)
const centralOffset = tail.readUInt32LE(endOffset + 16)
if (
tail.readUInt16LE(endOffset + 4) !== 0 ||
tail.readUInt16LE(endOffset + 6) !== 0 ||
tail.readUInt16LE(endOffset + 8) !== count ||
count < 1 ||
count > MAXIMUM_ENTRIES ||
centralSize < 46 ||
centralSize > MAXIMUM_CENTRAL_BYTES ||
centralOffset + centralSize !== endStart + endOffset
) {
throw new Error('Agent package ZIP central directory is invalid')
}
const central = readExact(
handle,
centralSize,
centralOffset,
'Agent package ZIP central directory'
)
const entries = new Map<string, ZipEntry>()
let offset = 0
let expanded = 0
let previousName: string | undefined
for (let index = 0; index < count; index += 1) {
if (
offset + 46 > central.length ||
central.readUInt32LE(offset) !== ZIP_CENTRAL
) {
throw new Error('Agent package ZIP central entry is invalid')
}
const nameLength = central.readUInt16LE(offset + 28)
const extraLength = central.readUInt16LE(offset + 30)
const commentLength = central.readUInt16LE(offset + 32)
const recordLength = 46 + nameLength + extraLength + commentLength
const flags = central.readUInt16LE(offset + 8)
const compressed = central.readUInt32LE(offset + 20)
const size = central.readUInt32LE(offset + 24)
const external = central.readUInt32LE(offset + 38)
const unixMode = external >>> 16
const permissions = unixMode & 0o777
const mode =
permissions === 0o644
? '0644'
: permissions === 0o755
? '0755'
: undefined
if (
offset + recordLength > central.length ||
central.readUInt16LE(offset + 4) !== 0x0314 ||
central.readUInt16LE(offset + 6) !== 20 ||
flags !== 0x0800 ||
central.readUInt16LE(offset + 10) !== 0 ||
central.readUInt16LE(offset + 30) !== 0 ||
central.readUInt16LE(offset + 32) !== 0 ||
central.readUInt16LE(offset + 34) !== 0 ||
compressed !== size ||
size > MAXIMUM_ENTRY_BYTES ||
(unixMode & 0o170000) !== 0o100000 ||
mode === undefined
) {
throw new Error('Agent package ZIP entry is unsupported')
}
const nameBytes = central.subarray(offset + 46, offset + 46 + nameLength)
const name = new TextDecoder('utf-8', { fatal: true }).decode(nameBytes)
if (
!safePath(name) ||
entries.has(name) ||
(
previousName !== undefined &&
Buffer.compare(
Buffer.from(previousName, 'utf8'),
Buffer.from(name, 'utf8')
) >= 0
)
) {
throw new Error('Agent package contains an unsafe, duplicate, or unordered path')
}
previousName = name
expanded += size
if (expanded > MAXIMUM_EXPANDED_BYTES) {
throw new Error('Agent package expanded payload exceeds its limit')
}
const localOffset = central.readUInt32LE(offset + 42)
const local = readExact(handle, 30, localOffset, 'Agent package ZIP local header')
const localNameLength = local.readUInt16LE(26)
const localExtraLength = local.readUInt16LE(28)
if (
local.readUInt32LE(0) !== ZIP_LOCAL ||
local.readUInt16LE(4) !== 20 ||
local.readUInt16LE(6) !== flags ||
local.readUInt16LE(8) !== 0 ||
local.readUInt32LE(14) !== central.readUInt32LE(offset + 16) ||
local.readUInt32LE(18) !== size ||
local.readUInt32LE(22) !== size ||
localNameLength !== nameLength ||
localExtraLength !== 0 ||
!readExact(
handle,
nameLength,
localOffset + 30,
'Agent package ZIP local name'
).equals(nameBytes)
) {
throw new Error('Agent package ZIP local header is invalid')
}
const dataOffset = localOffset + 30 + nameLength
entries.set(name, {
name,
size,
checksum: central.readUInt32LE(offset + 16),
mode,
dataOffset,
recordStart: localOffset,
recordEnd: dataOffset + size
})
offset += recordLength
}
if (offset !== central.length) {
throw new Error('Agent package ZIP central count is invalid')
}
const ordered = [...entries.values()].sort(
(left, right) => left.recordStart - right.recordStart
)
let end = 0
for (const entry of ordered) {
if (entry.recordStart !== end || entry.recordEnd > centralOffset) {
throw new Error('Agent package ZIP entries overlap or contain gaps')
}
end = entry.recordEnd
}
if (end !== centralOffset) {
throw new Error('Agent package ZIP data region is invalid')
}
return entries
}
function streamEntry(
handle: number,
entry: ZipEntry,
destination?: string
): { sha256: string; crc32: number; bytes?: Buffer } {
const wantedBytes =
destination === undefined && entry.size <= MAXIMUM_METADATA_BYTES
? Buffer.allocUnsafe(entry.size)
: undefined
let output: number | undefined
if (destination !== undefined) {
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 })
output = openSync(
destination,
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL,
entry.mode === '0755' ? 0o755 : 0o644
)
}
const hash = createHash('sha256')
const chunk = Buffer.allocUnsafe(1024 * 1024)
let checksum = 0
let position = 0
try {
while (position < entry.size) {
const count = readSync(
handle,
chunk,
0,
Math.min(chunk.length, entry.size - position),
entry.dataOffset + position
)
if (count <= 0) {
throw new Error(`Agent package entry is truncated: ${entry.name}`)
}
const bytes = chunk.subarray(0, count)
hash.update(bytes)
checksum = crc32(bytes, checksum)
if (wantedBytes !== undefined) {
bytes.copy(wantedBytes, position)
}
if (output !== undefined) {
let written = 0
while (written < count) {
const amount = writeFileChunk(output, bytes, written)
written += amount
}
}
position += count
}
if (output !== undefined) {
fsyncSync(output)
}
} finally {
if (output !== undefined) {
closeSync(output)
chmodSync(destination!, entry.mode === '0755' ? 0o755 : 0o644)
}
}
return {
sha256: hash.digest('hex'),
crc32: checksum >>> 0,
...(wantedBytes === undefined ? {} : { bytes: wantedBytes })
}
}
function writeFileChunk(handle: number, bytes: Buffer, offset: number): number {
const written = writeSync(handle, bytes, offset, bytes.length - offset)
if (written <= 0) {
throw new Error('Agent package extraction made no progress')
}
return written
}
function readEntry(handle: number, entries: Map<string, ZipEntry>, name: string): Buffer {
const entry = entries.get(name)
if (entry === undefined || entry.size > MAXIMUM_METADATA_BYTES) {
throw new Error(`Agent package metadata is missing or too large: ${name}`)
}
const actual = streamEntry(handle, entry)
if (actual.crc32 !== entry.checksum) {
throw new Error(`Agent package ZIP checksum mismatch: ${name}`)
}
return actual.bytes!
}
function assertDeclaredMetadata(
name: string,
bytes: Buffer,
declared: ReadonlyMap<string, FileRecord>
): void {
const expected = declared.get(name)
if (
expected === undefined ||
expected.size !== bytes.byteLength ||
expected.sha256 !== sha256(bytes)
) {
throw new Error(
`Agent package metadata digest mismatch: ${name}`
)
}
}
function parseJson(bytes: Buffer, label: string): unknown {
try {
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes))
} catch (error) {
throw new Error(`${label} is invalid JSON`, { cause: error })
}
}
function parseCanonicalJson(bytes: Buffer, label: string): JsonRecord {
const value = parseJson(bytes, label)
if (!isRecord(value) || !prettyBytes(value).equals(bytes)) {
throw new Error(`${label} is not canonical`)
}
return value
}
function parseRegistry(bytes: Buffer): TrustedRegistry {
const value = parseJson(bytes, 'Agent package key registry')
if (!isRecord(value)) {
throw new Error('Agent package key registry is invalid')
}
exactKeys(value, ['formatVersion', 'keys', 'revocations'], 'Agent package key registry')
if (
value.formatVersion !== 1 ||
!Array.isArray(value.keys) ||
!Array.isArray(value.revocations) ||
value.keys.length < 1 ||
value.keys.length > 1_000 ||
value.revocations.length > 1_000
) {
throw new Error('Agent package key registry is invalid')
}
const ids = new Set<string>()
const keys = value.keys.map((candidate) => {
if (!isRecord(candidate)) {
throw new Error('Agent package key registry is invalid')
}
exactKeys(
candidate,
['keyId', 'publicKeySpkiBase64', 'environment'],
'Agent package key'
)
if (
typeof candidate.keyId !== 'string' ||
!/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(candidate.keyId) ||
ids.has(candidate.keyId) ||
typeof candidate.publicKeySpkiBase64 !== 'string' ||
!['production', 'test'].includes(String(candidate.environment))
) {
throw new Error('Agent package key registry is invalid')
}
const publicKey = createPublicKey({
key: Buffer.from(candidate.publicKeySpkiBase64, 'base64'),
format: 'der',
type: 'spki'
})
if (publicKey.asymmetricKeyType !== 'ed25519') {
throw new Error('Agent package registry key is not Ed25519')
}
ids.add(candidate.keyId)
return {
keyId: candidate.keyId,
publicKeySpkiBase64: candidate.publicKeySpkiBase64,
environment: candidate.environment as 'production' | 'test'
}
})
const revoked = new Set<string>()
const revocations = value.revocations.map((candidate) => {
if (!isRecord(candidate) || typeof candidate.keyId !== 'string') {
throw new Error('Agent package key revocation is invalid')
}
if (revoked.has(candidate.keyId)) {
throw new Error('Agent package key revocation is duplicated')
}
revoked.add(candidate.keyId)
return { keyId: candidate.keyId }
})
if (!keys.some((key) => key.environment === 'production')) {
throw new Error('Agent package has no production trust root')
}
return { formatVersion: 1, keys, revocations }
}
function trustedProductionKey(registry: TrustedRegistry, keyId: unknown) {
const key = registry.keys.find(
(candidate) =>
candidate.keyId === keyId &&
candidate.environment === 'production'
)
if (
key === undefined ||
registry.revocations.some((item) => item.keyId === key.keyId)
) {
throw new Error(`GoodBuddy signing key is not trusted: ${String(keyId)}`)
}
return createPublicKey({
key: Buffer.from(key.publicKeySpkiBase64, 'base64'),
format: 'der',
type: 'spki'
})
}
function verifySignature(
bytes: Buffer,
signatureBytes: Buffer,
publicKey: ReturnType<typeof createPublicKey>,
domain: Buffer,
label: string
): void {
const text = signatureBytes.toString('utf8')
if (!/^[A-Za-z0-9+/]{86}==\n$/u.test(text)) {
throw new Error(`${label} detached signature encoding is invalid`)
}
const signature = Buffer.from(text.trim(), 'base64')
if (
signature.length !== 64 ||
!verify(null, Buffer.concat([domain, bytes]), publicKey, signature)
) {
throw new Error(`${label} signature verification failed`)
}
}
function parseFiles(value: unknown, label: string): FileRecord[] {
if (!Array.isArray(value) || value.length < 1 || value.length > 50_000) {
throw new Error(`${label} file inventory is invalid`)
}
const paths = new Set<string>()
return value.map((item) => {
if (!isRecord(item)) {
throw new Error(`${label} file inventory is invalid`)
}
exactKeys(item, ['path', 'size', 'sha256', 'mode'], `${label} file`)
if (
!safePath(item.path) ||
!Number.isSafeInteger(item.size) ||
Number(item.size) < 0 ||
!sha256Pattern.test(String(item.sha256)) ||
!['0644', '0755'].includes(String(item.mode)) ||
paths.has(item.path)
) {
throw new Error(`${label} file inventory is invalid`)
}
paths.add(item.path)
return {
path: item.path,
size: Number(item.size),
sha256: String(item.sha256),
mode: item.mode as '0644' | '0755'
}
})
}
function validateLicenses(
value: unknown,
declared: Set<string>,
label: string
): Array<{
package: string
version: string
spdx: string
path: string
}> {
if (!Array.isArray(value) || value.length < 1 || value.length > 1_000) {
throw new Error(`${label} license inventory is invalid`)
}
const paths = new Set<string>()
return value.map((item) => {
if (!isRecord(item)) {
throw new Error(`${label} license inventory is invalid`)
}
exactKeys(
item,
['package', 'version', 'spdx', 'path'],
`${label} license`
)
if (
typeof item.package !== 'string' ||
item.package.length < 1 ||
typeof item.version !== 'string' ||
item.version.length < 1 ||
typeof item.spdx !== 'string' ||
item.spdx.length < 1 ||
!safeManifestPath(item.path) ||
!declared.has(item.path) ||
paths.has(item.path)
) {
throw new Error(`${label} license inventory is invalid`)
}
paths.add(item.path)
return {
package: item.package,
version: item.version,
spdx: item.spdx,
path: item.path
}
})
}
function expectedAgentMode(path: string): '0644' | '0755' {
return (
path === 'node' ||
path === 'goodbuddy-agent' ||
path.startsWith('helpers/')
)
? '0755'
: '0644'
}
function assertOuterDescriptor(
descriptor: JsonRecord,
bytes: Buffer,
entries: Map<string, ZipEntry>,
registry: TrustedRegistry,
signature: Buffer
): FileRecord[] {
exactKeys(
descriptor,
[
'format',
'formatVersion',
'product',
'component',
'version',
'minimumDesktopVersion',
'platform',
'architecture',
'signingKeyId',
'agentProtocol',
'remoteRuntime',
'contentDigest',
'files'
],
'Agent package descriptor'
)
const architecture = descriptor.architecture
const remoteRuntime = descriptor.remoteRuntime
if (
descriptor.format !== 'goodbuddy-agent-package' ||
descriptor.formatVersion !== 1 ||
descriptor.product !== 'GoodBuddy' ||
descriptor.component !== 'agent' ||
!versionPattern.test(String(descriptor.version)) ||
!versionPattern.test(String(descriptor.minimumDesktopVersion)) ||
!isSupportedTarget(descriptor.platform, architecture) ||
!['x64', 'arm64'].includes(String(architecture)) ||
typeof descriptor.signingKeyId !== 'string' ||
!digestPattern.test(String(descriptor.contentDigest)) ||
!isRecord(remoteRuntime)
) {
throw new Error('Agent package descriptor is invalid')
}
exactKeys(
remoteRuntime,
['runtimeId', 'provider', 'version', 'bundleDigest', 'protocol'],
'Agent package Runtime identity'
)
if (
remoteRuntime.runtimeId !== 'opencode' ||
remoteRuntime.provider !== 'opencode' ||
!versionPattern.test(String(remoteRuntime.version)) ||
!digestPattern.test(String(remoteRuntime.bundleDigest))
) {
throw new Error('Agent package Runtime identity is invalid')
}
protocol(descriptor.agentProtocol, 'Agent package protocol')
protocol(remoteRuntime.protocol, 'Agent package Runtime protocol')
const { contentDigest: _contentDigest, ...content } = descriptor
void _contentDigest
if (
descriptor.contentDigest !==
`sha256:${sha256(Buffer.from(canonicalJson(content), 'utf8'))}`
) {
throw new Error('Agent package content identity is invalid')
}
verifySignature(
bytes,
signature,
trustedProductionKey(registry, descriptor.signingKeyId),
PACKAGE_SIGNATURE_DOMAIN,
'Agent package'
)
const files = parseFiles(descriptor.files, 'Agent package')
const expected = new Set([
...files.map((file) => file.path),
PACKAGE_DESCRIPTOR,
PACKAGE_SIGNATURE
])
if (
entries.size !== expected.size ||
[...entries.keys()].some((name) => !expected.has(name))
) {
throw new Error('Agent package contains undeclared or missing files')
}
for (const file of files) {
const entry = entries.get(file.path)
if (
entry === undefined ||
entry.size !== file.size ||
entry.mode !== file.mode
) {
throw new Error(`Agent package ZIP inventory mismatch: ${file.path}`)
}
}
for (const metadata of [PACKAGE_DESCRIPTOR, PACKAGE_SIGNATURE]) {
if (entries.get(metadata)?.mode !== '0644') {
throw new Error('Agent package metadata mode is invalid')
}
}
if (
!files.some((file) => file.path === NODE_PATH && file.mode === '0755')
) {
throw new Error('Agent package is not self-bootstrap capable')
}
return files
}
function assertAgentManifest(
bytes: Buffer,
signature: Buffer,
registry: TrustedRegistry,
lock: JsonRecord,
descriptor: JsonRecord,
packageFiles: Map<string, FileRecord>
): JsonRecord {
const manifest = parseCanonicalJson(bytes, 'Agent manifest')
exactKeys(
manifest,
[
'formatVersion',
'product',
'agentVersion',
'platform',
'arch',
'protocol',
'signingKeyId',
'entrypoint',
'files',
'licenses'
],
'Agent manifest'
)
if (
manifest.formatVersion !== 1 ||
manifest.product !== 'GoodBuddy' ||
!versionPattern.test(String(manifest.agentVersion)) ||
!isSupportedTarget(manifest.platform, manifest.arch) ||
!['x64', 'arm64'].includes(String(manifest.arch)) ||
!isRecord(manifest.entrypoint) ||
!Array.isArray(manifest.licenses)
) {
throw new Error('Agent manifest contract is invalid')
}
exactKeys(
manifest.entrypoint,
['path', 'runtimePath', 'scriptPath'],
'Agent entrypoint'
)
if (
manifest.entrypoint.path !== 'goodbuddy-agent' ||
manifest.entrypoint.runtimePath !== 'node' ||
manifest.entrypoint.scriptPath !== 'lib/agent.cjs'
) {
throw new Error('Agent entrypoint is invalid')
}
const agentProtocol = protocol(manifest.protocol, 'Agent manifest protocol')
if (
lock.formatVersion !== 1 ||
manifest.agentVersion !== lock.agentVersion ||
manifest.arch !== descriptor.architecture ||
manifest.platform !== descriptor.platform ||
manifest.agentVersion !== descriptor.version ||
canonicalJson(agentProtocol) !== canonicalJson(descriptor.agentProtocol) ||
canonicalJson(agentProtocol) !== canonicalJson(lock.protocol)
) {
throw new Error('Agent manifest does not match package identity or lock')
}
verifySignature(
bytes,
signature,
trustedProductionKey(registry, manifest.signingKeyId),
AGENT_SIGNATURE_DOMAIN,
'Agent manifest'
)
const files = parseFiles(manifest.files, 'Agent manifest')
const declared = new Set<string>()
for (const file of files) {
if (
!safeManifestPath(file.path) ||
file.mode !== expectedAgentMode(file.path)
) {
throw new Error(`Agent manifest path or mode is invalid: ${file.path}`)
}
declared.add(file.path)
const outer = packageFiles.get(`agent/${file.path}`)
if (
outer === undefined ||
outer.size !== file.size ||
outer.sha256 !== file.sha256 ||
outer.mode !== file.mode
) {
throw new Error(`Agent inner inventory mismatch: ${file.path}`)
}
}
const expectedAgentPaths = new Set([
...files.map((file) => `agent/${file.path}`),
'agent/manifest.json',
'agent/manifest.sig'
])
const actualAgentPaths = [...packageFiles.keys()].filter((path) =>
path.startsWith('agent/')
)
if (
actualAgentPaths.length !== expectedAgentPaths.size ||
actualAgentPaths.some((path) => !expectedAgentPaths.has(path))
) {
throw new Error('Agent bundle contains undeclared or missing files')
}
for (const required of [
'node',
'goodbuddy-agent',
'lib/agent.cjs'
]) {
if (!declared.has(required)) {
throw new Error(`Agent required payload is missing: ${required}`)
}
}
const architecture = manifest.arch as Architecture
const koffiPackageRoot = 'lib/node_modules/koffi'
const koffiNativePackage = `@koromix/koffi-${manifest.platform}-${architecture}`
const koffiNativeRoot =
`lib/node_modules/${koffiNativePackage}`
for (const required of [
`${koffiPackageRoot}/package.json`,
`${koffiPackageRoot}/index.js`,
`${koffiPackageRoot}/src/koffi/index.js`,
`${koffiPackageRoot}/src/koffi/src/static.js`,
`${koffiNativeRoot}/package.json`,
`${koffiNativeRoot}/index.js`
]) {
if (!declared.has(required)) {
throw new Error(`Agent Koffi payload is missing: ${required}`)
}
}
for (const native of nativeKoffiPaths(manifest.platform as Platform, architecture)) {
if (!declared.has(native)) {
throw new Error(`Agent Koffi payload is missing: ${native}`)
}
}
if (
!isRecord(lock.koffi) ||
typeof lock.koffi.version !== 'string'
) {
throw new Error('Agent runtime lock contract is invalid')
}
const licenses = validateLicenses(
manifest.licenses,
declared,
'Agent manifest'
)
const koffiLicense = licenses.find(
(license) => license.package === 'koffi'
)
const nativeLicense = licenses.find(
(license) => license.package === koffiNativePackage
)
if (
koffiLicense?.version !== lock.koffi.version ||
koffiLicense.spdx !== 'MIT' ||
koffiLicense.path !== 'licenses/koffi-MIT.txt' ||
nativeLicense?.version !== lock.koffi.version ||
nativeLicense.spdx !== 'MIT' ||
nativeLicense.path !== 'licenses/koffi-native-MIT.txt'
) {
throw new Error('Agent Koffi license declaration is invalid')
}
return manifest
}
function assertRuntimeManifest(
bytes: Buffer,
signature: Buffer,
registry: TrustedRegistry,
lock: JsonRecord,
descriptor: JsonRecord,
packageFiles: Map<string, FileRecord>
): { manifest: JsonRecord; manifestDigest: string } {
const manifest = parseCanonicalJson(bytes, 'Runtime manifest')
exactKeys(
manifest,
[
'formatVersion',
'product',
'runtimeId',
'runtimeVersion',
'provider',
'platform',
'architecture',
'signingKeyId',
'bundleDigest',
'adapterDigest',
'sourcePackage',
'entrypoint',
'files',
'licenses',
'allowedEnvironmentNames',
'protocol',
'acpCapabilitiesDigest',
'limits'
],
'Runtime manifest'
)
const runtime = isRecord(lock.runtimes) && isRecord(lock.runtimes.opencode)
? lock.runtimes.opencode
: undefined
const targetKey = descriptor.platform === 'darwin' ? 'darwin-arm64' : String(descriptor.architecture)
const target =
runtime !== undefined &&
isRecord(runtime.targets) &&
isRecord(runtime.targets[targetKey])
? runtime.targets[targetKey]
: undefined
const remote = descriptor.remoteRuntime as JsonRecord
if (
manifest.formatVersion !== 2 ||
manifest.product !== 'GoodBuddy' ||
manifest.runtimeId !== 'opencode' ||
manifest.provider !== 'opencode' ||
manifest.platform !== descriptor.platform ||
manifest.architecture !== descriptor.architecture ||
!versionPattern.test(String(manifest.runtimeVersion)) ||
!digestPattern.test(String(manifest.bundleDigest)) ||
!digestPattern.test(String(manifest.adapterDigest)) ||
!digestPattern.test(String(manifest.acpCapabilitiesDigest)) ||
!isRecord(manifest.entrypoint) ||
!isRecord(manifest.sourcePackage) ||
!isRecord(manifest.limits) ||
!Array.isArray(manifest.files) ||
!Array.isArray(manifest.licenses) ||
!Array.isArray(manifest.allowedEnvironmentNames) ||
runtime === undefined ||
target === undefined
) {
throw new Error('Runtime manifest contract or lock is invalid')
}
const lockedTarget = target as JsonRecord
const entrypoint = manifest.entrypoint as JsonRecord
const sourcePackage = manifest.sourcePackage as JsonRecord
const limits = manifest.limits as JsonRecord
exactKeys(
entrypoint,
['identity', 'path', 'sha256', 'argvPrefix'],
'Runtime entrypoint'
)
exactKeys(
sourcePackage,
['name', 'integrity'],
'Runtime source package'
)
exactKeys(
limits,
[
'maximumPromptRuntimeMilliseconds',
'maximumPromptInputBytes',
'maximumPromptOutputBytes'
],
'Runtime limits'
)
const runtimeProtocol = protocol(manifest.protocol, 'Runtime manifest protocol')
if (
manifest.runtimeVersion !== runtime.version ||
manifest.runtimeVersion !== remote.version ||
manifest.bundleDigest !== remote.bundleDigest ||
canonicalJson(runtimeProtocol) !== canonicalJson(runtime.protocol) ||
canonicalJson(runtimeProtocol) !== canonicalJson(remote.protocol) ||
entrypoint.path !== runtime.entrypoint ||
entrypoint.identity !== runtime.entrypointIdentity ||
canonicalJson(entrypoint.argvPrefix) !==
canonicalJson(runtime.argvPrefix) ||
canonicalJson(manifest.allowedEnvironmentNames) !==
canonicalJson(runtime.allowedEnvironmentNames) ||
sourcePackage.name !== lockedTarget.package ||
sourcePackage.integrity !== lockedTarget.integrity ||
!sha256Pattern.test(String(entrypoint.sha256)) ||
!Number.isSafeInteger(limits.maximumPromptRuntimeMilliseconds) ||
Number(limits.maximumPromptRuntimeMilliseconds) < 1 ||
!Number.isSafeInteger(limits.maximumPromptInputBytes) ||
Number(limits.maximumPromptInputBytes) < 1 ||
!Number.isSafeInteger(limits.maximumPromptOutputBytes) ||
Number(limits.maximumPromptOutputBytes) < 0
) {
throw new Error('Runtime manifest does not match package identity or lock')
}
const { bundleDigest: _bundleDigest, ...identity } = manifest
void _bundleDigest
if (
manifest.bundleDigest !==
digestCanonicalOperation(
'runtime/bundleIdentity',
{ kind: 'installation', installationId: manifest.runtimeId },
identity
)
) {
throw new Error('Runtime bundle identity digest does not match')
}
verifySignature(
bytes,
signature,
trustedProductionKey(registry, manifest.signingKeyId),
RUNTIME_SIGNATURE_DOMAIN,
'Runtime manifest'
)
const files = parseFiles(manifest.files, 'Runtime manifest')
const digestDirectory = String(manifest.bundleDigest).slice('sha256:'.length)
const prefix = `runtime/opencode/${digestDirectory}/`
const expectedRuntimePaths = new Set([
...files.map((file) => `${prefix}${file.path}`),
`${prefix}manifest.json`,
`${prefix}manifest.sig`
])
const actualRuntimePaths = [...packageFiles.keys()].filter((path) =>
path.startsWith('runtime/')
)
if (
actualRuntimePaths.length !== expectedRuntimePaths.size ||
actualRuntimePaths.some((path) => !expectedRuntimePaths.has(path))
) {
throw new Error('Runtime bundle contains undeclared or missing files')
}
for (const file of files) {
if (!safeManifestPath(file.path)) {
throw new Error(`Runtime manifest path is invalid: ${file.path}`)
}
const expectedMode = file.path === 'bin/opencode' ? '0755' : '0644'
const outer = packageFiles.get(`${prefix}${file.path}`)
if (
file.mode !== expectedMode ||
outer === undefined ||
outer.size !== file.size ||
outer.sha256 !== file.sha256 ||
outer.mode !== file.mode
) {
throw new Error(`Runtime inner inventory mismatch: ${file.path}`)
}
}
const runtimeLicenses = validateLicenses(
manifest.licenses,
new Set(files.map((file) => file.path)),
'Runtime manifest'
)
const openCodeLicense = runtimeLicenses.find(
(license) => license.package === 'opencode-ai'
)
if (
openCodeLicense?.version !== manifest.runtimeVersion ||
openCodeLicense?.spdx !== 'MIT' ||
openCodeLicense?.path !== 'licenses/opencode-MIT.txt'
) {
throw new Error('Runtime license declaration is invalid')
}
if (
entrypoint.path !== 'bin/opencode' ||
!files.some(
(file) =>
file.path === 'bin/opencode' &&
file.sha256 === entrypoint.sha256
)
) {
throw new Error('Runtime entrypoint is missing')
}
return {
manifest,
manifestDigest: digestCanonicalOperation(
'runtime/bundleManifest',
{ kind: 'installation', installationId: manifest.runtimeId },
manifest
)
}
}
function isSupportedTarget(platform: unknown, architecture: unknown): boolean {
return (platform === 'linux' && (architecture === 'x64' || architecture === 'arm64')) ||
(platform === 'darwin' && architecture === 'arm64')
}
function nativeKoffiPaths(platform: Platform, architecture: Architecture): string[] {
const root = `lib/node_modules/@koromix/koffi-${platform}-${architecture}`
return platform === 'darwin'
? [`${root}/darwin_${architecture}/koffi.node`]
: [`${root}/linux_${architecture}/koffi.node`, `${root}/musl_${architecture}/koffi.node`]
}
function elfArchitecture(bytes: Buffer, platform: Platform): Architecture | undefined {
if (platform === 'darwin') {
if (bytes.length < 8) return undefined
const cpu = bytes.readUInt32LE(0) === 0xfeedfacf
? bytes.readUInt32LE(4)
: bytes.readUInt32BE(0) === 0xfeedfacf ? bytes.readUInt32BE(4) : 0
return cpu === 0x0100000c ? 'arm64' : undefined
}
if (
bytes.length < 20 ||
!bytes.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))
) {
return undefined
}
const machine =
bytes[5] === 1
? bytes.readUInt16LE(18)
: bytes[5] === 2
? bytes.readUInt16BE(18)
: 0
return machine === 62 ? 'x64' : machine === 183 ? 'arm64' : undefined
}
function assertExtractedArchitectures(
preparedRoot: string,
architecture: Architecture,
platform: Platform
): void {
for (const path of [
join(preparedRoot, 'agent', 'node'),
...nativeKoffiPaths(platform, architecture).map(path => join(preparedRoot, 'agent', path)),
join(preparedRoot, 'runtime', 'bin', 'opencode')
]) {
if (
elfArchitecture(readFileHeader(path, 64), platform) !== architecture
) {
throw new Error(`Installed executable architecture mismatch: ${basename(path)}`)
}
}
}
function readFileHeader(path: string, maximumBytes: number): Buffer {
const handle = openSync(
path,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)
)
try {
const snapshot = fstatSync(handle)
if (!snapshot.isFile()) {
throw new Error(`Installed payload is not a regular file: ${path}`)
}
return readExact(
handle,
Math.min(snapshot.size, maximumBytes),
0,
'Installed payload header'
)
} finally {
closeSync(handle)
}
}
function hashArchive(handle: number, size: number): string {
const hash = createHash('sha256')
const chunk = Buffer.allocUnsafe(1024 * 1024)
let position = 0
while (position < size) {
const count = readSync(
handle,
chunk,
0,
Math.min(chunk.length, size - position),
position
)
if (count <= 0) {
throw new Error('Agent package archive is truncated')
}
hash.update(chunk.subarray(0, count))
position += count
}
return hash.digest('hex')
}
function sameSnapshot(
before: ReturnType<typeof fstatSync>,
after: ReturnType<typeof fstatSync>
): boolean {
return (
before.dev === after.dev &&
before.ino === after.ino &&
before.size === after.size &&
before.mtimeMs === after.mtimeMs &&
before.ctimeMs === after.ctimeMs
)
}
function verifyArchive(
archivePath: string,
expectedSha256: string,
archiveSha256Verified: boolean,
emit: (phase: string) => void
): VerifiedPackage {
const handle = openSync(
archivePath,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)
)
try {
const snapshot = fstatSync(handle)
if (
!snapshot.isFile() ||
snapshot.size <= 0 ||
snapshot.size > MAXIMUM_ARCHIVE_BYTES
) {
throw new Error('Agent package archive is not a bounded regular file')
}
if (!archiveSha256Verified) {
emit('hashing-archive')
const archiveSha256 = hashArchive(handle, snapshot.size)
if (archiveSha256 !== expectedSha256) {
throw new Error('Agent package archive SHA-256 does not match the authenticated catalog')
}
}
const archiveSha256 = expectedSha256
emit('verifying-zip')
const entries = parseZip(handle, snapshot.size)
const descriptorBytes = readEntry(handle, entries, PACKAGE_DESCRIPTOR)
const signatureBytes = readEntry(handle, entries, PACKAGE_SIGNATURE)
const registryBytes = readEntry(handle, entries, 'agent-release-keys.json')
const descriptor = parseCanonicalJson(
descriptorBytes,
'Agent package descriptor'
)
const registry = parseRegistry(registryBytes)
const declared = assertOuterDescriptor(
descriptor,
descriptorBytes,
entries,
registry,
signatureBytes
)
const declaredMap = new Map(declared.map((file) => [file.path, file]))
assertDeclaredMetadata(
'agent-release-keys.json',
registryBytes,
declaredMap
)
emit('verifying-payload')
const agentManifestBytes = readEntry(handle, entries, 'agent/manifest.json')
assertDeclaredMetadata(
'agent/manifest.json',
agentManifestBytes,
declaredMap
)
const agentRuntimeLockBytes = readEntry(
handle,
entries,
'agent-runtime-lock.json'
)
assertDeclaredMetadata(
'agent-runtime-lock.json',
agentRuntimeLockBytes,
declaredMap
)
const agentSignatureBytes = readEntry(
handle,
entries,
'agent/manifest.sig'
)
assertDeclaredMetadata(
'agent/manifest.sig',
agentSignatureBytes,
declaredMap
)
const agentManifest = assertAgentManifest(
agentManifestBytes,
agentSignatureBytes,
registry,
parseCanonicalJson(
agentRuntimeLockBytes,
'Agent runtime lock'
),
descriptor,
declaredMap
)
const runtimeDigest = String(
(descriptor.remoteRuntime as JsonRecord).bundleDigest
).slice('sha256:'.length)
const runtimeManifestBytes = readEntry(
handle,
entries,
`runtime/opencode/${runtimeDigest}/manifest.json`
)
assertDeclaredMetadata(
`runtime/opencode/${runtimeDigest}/manifest.json`,
runtimeManifestBytes,
declaredMap
)
const remoteRuntimeLockBytes = readEntry(
handle,
entries,
'remote-runtime-lock.json'
)
assertDeclaredMetadata(
'remote-runtime-lock.json',
remoteRuntimeLockBytes,
declaredMap
)
const runtimeSignaturePath =
`runtime/opencode/${runtimeDigest}/manifest.sig`
const runtimeSignatureBytes = readEntry(
handle,
entries,
runtimeSignaturePath
)
assertDeclaredMetadata(
runtimeSignaturePath,
runtimeSignatureBytes,
declaredMap
)
const runtime = assertRuntimeManifest(
runtimeManifestBytes,
runtimeSignatureBytes,
registry,
parseCanonicalJson(
remoteRuntimeLockBytes,
'Remote Runtime lock'
),
descriptor,
declaredMap
)
if (!sameSnapshot(snapshot, fstatSync(handle))) {
throw new Error('Agent package archive changed during verification')
}
const agentManifestSha256 = sha256(agentManifestBytes)
const architecture = descriptor.architecture as Architecture
return {
archiveSha256,
entries,
descriptor,
descriptorBytes,
packageSignatureBytes: signatureBytes,
registry,
releaseKeyRegistryBytes: registryBytes,
agentRuntimeLockBytes,
remoteRuntimeLockBytes,
agentManifest,
runtimeManifest: runtime.manifest,
agentManifestSha256,
runtimeManifestDigest: runtime.manifestDigest,
agent: {
installationId: `agent-${agentManifestSha256}`,
agentVersion: String(agentManifest.agentVersion),
manifestSha256: agentManifestSha256,
binaryDigest: `sha256:${agentManifestSha256}`,
platform: descriptor.platform as Platform,
architecture,
protocol: protocol(agentManifest.protocol, 'Agent protocol'),
supervisor: 'detached-on-demand'
},
runtime: {
runtimeId: 'opencode',
runtimeVersion: String(runtime.manifest.runtimeVersion),
bundleDigest: String(runtime.manifest.bundleDigest),
manifestDigest: runtime.manifestDigest,
runtimeAdapterDigest: String(runtime.manifest.adapterDigest),
acpCapabilitiesDigest: String(runtime.manifest.acpCapabilitiesDigest),
platform: descriptor.platform as Platform,
architecture,
protocol: protocol(runtime.manifest.protocol, 'Runtime protocol')
}
}
} finally {
closeSync(handle)
}
}
function assertAbsoluteInputs(
options: InstallerOptions,
createOperationRoot = true
): {
operationRoot: string
archive: string
expectedSha256: string
} {
if (
!sha256Pattern.test(options.expectedSha256) ||
resolve(options.operationRoot) !== options.operationRoot ||
resolve(options.archive) !== options.archive
) {
throw new Error('Installer paths must be absolute and archive SHA-256 must be lowercase hexadecimal')
}
const operationRoot = resolve(options.operationRoot)
const archive = resolve(options.archive)
if (
operationRoot === resolve(options.homeDirectory ?? homedir()) ||
operationRoot === dirname(operationRoot)
) {
throw new Error('Installer operation root is unsafe')
}
if (createOperationRoot) {
mkdirSync(operationRoot, { recursive: true, mode: 0o700 })
}
if (
!existsSync(operationRoot) ||
!statSync(operationRoot).isDirectory() ||
lstatSync(operationRoot).isSymbolicLink()
) {
throw new Error('Installer operation root is not a real directory')
}
return {
operationRoot,
archive,
expectedSha256: options.expectedSha256
}
}
function persistPreparedState(
operationRoot: string,
verified: VerifiedPackage
): void {
const preparedRoot = join(operationRoot, 'prepared')
if (existsSync(preparedRoot)) {
const metadata = lstatSync(preparedRoot)
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
throw new Error('Prepared package state directory is invalid')
}
} else {
mkdirSync(preparedRoot, { recursive: false, mode: 0o700 })
}
writePreparedFile(
preparedRoot,
'agent-release-keys.json',
verified.releaseKeyRegistryBytes
)
writePreparedFile(
preparedRoot,
'agent-runtime-lock.json',
verified.agentRuntimeLockBytes
)
writePreparedFile(
preparedRoot,
'remote-runtime-lock.json',
verified.remoteRuntimeLockBytes
)
writePreparedFile(
preparedRoot,
PACKAGE_DESCRIPTOR,
verified.descriptorBytes
)
writePreparedFile(
preparedRoot,
PACKAGE_SIGNATURE,
verified.packageSignatureBytes
)
const destination = join(preparedRoot, 'result.json')
const temporary = join(
preparedRoot,
`.result-${process.pid}-${Date.now().toString(36)}.tmp`
)
const state: PreparedState = {
formatVersion: 1,
archiveSha256: verified.archiveSha256,
releaseKeyRegistrySha256:
sha256(verified.releaseKeyRegistryBytes),
agentRuntimeLockSha256:
sha256(verified.agentRuntimeLockBytes),
remoteRuntimeLockSha256:
sha256(verified.remoteRuntimeLockBytes),
packageDescriptorSha256:
sha256(verified.descriptorBytes),
packageSignatureSha256:
sha256(verified.packageSignatureBytes),
agent: verified.agent,
runtime: verified.runtime
}
const bytes = prettyBytes(state)
if (bytes.length > MAXIMUM_METADATA_BYTES) {
throw new Error('Prepared package state exceeds its limit')
}
writeBufferAtomically(destination, temporary, bytes)
}
function writePreparedFile(
directory: string,
name: string,
bytes: Buffer
): void {
if (
bytes.byteLength <= 0 ||
bytes.byteLength > MAXIMUM_METADATA_BYTES
) {
throw new Error(`Prepared package metadata is invalid: ${name}`)
}
const destination = join(directory, name)
const temporary = join(
directory,
`.${name}-${process.pid}-${Date.now().toString(36)}.tmp`
)
writeBufferAtomically(destination, temporary, bytes)
}
function writeBufferAtomically(
destination: string,
temporary: string,
bytes: Buffer,
replaceExistingOnWindows = false
): void {
const handle = openSync(
temporary,
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL,
0o600
)
try {
let offset = 0
while (offset < bytes.length) {
offset += writeFileChunk(handle, bytes, offset)
}
fsyncSync(handle)
} finally {
closeSync(handle)
}
try {
chmodSync(temporary, 0o600)
if (
replaceExistingOnWindows &&
process.platform === 'win32' &&
existsSync(destination)
) {
unlinkSync(destination)
}
renameSync(temporary, destination)
} finally {
if (existsSync(temporary)) {
unlinkSync(temporary)
}
}
}
function extractPayload(
archive: string,
destinationRoot: string,
verified: VerifiedPackage
): void {
if (existsSync(destinationRoot)) {
throw new Error('Prepared package payload already exists')
}
const temporary = mkdtempSync(
join(dirname(destinationRoot), '.payload-')
)
try {
mkdirSync(join(temporary, 'agent'), { recursive: true, mode: 0o700 })
mkdirSync(join(temporary, 'runtime'), { recursive: true, mode: 0o700 })
const runtimePrefix =
`runtime/opencode/${verified.runtime.bundleDigest.slice('sha256:'.length)}/`
const declared = new Map(
parseFiles(verified.descriptor.files, 'Agent package')
.map((file) => [file.path, file])
)
const handle = openSync(
archive,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)
)
try {
const snapshot = fstatSync(handle)
if (
!snapshot.isFile() ||
snapshot.size <= 0 ||
snapshot.size > MAXIMUM_ARCHIVE_BYTES
) {
throw new Error('Agent package archive is not a bounded regular file')
}
for (const [name, entry] of verified.entries) {
let target: string | undefined
if (name.startsWith('agent/')) {
target = join(temporary, 'agent', ...name.slice(6).split('/'))
} else if (name.startsWith(runtimePrefix)) {
target = join(
temporary,
'runtime',
...name.slice(runtimePrefix.length).split('/')
)
}
if (target !== undefined) {
const actual = streamEntry(handle, entry, target)
const expected = declared.get(name)
if (
expected === undefined ||
actual.crc32 !== entry.checksum ||
actual.sha256 !== expected.sha256
) {
throw new Error(
`Extracted package payload digest or checksum mismatch: ${name}`
)
}
}
}
if (!sameSnapshot(snapshot, fstatSync(handle))) {
throw new Error('Agent package archive changed during extraction')
}
} finally {
closeSync(handle)
}
assertExtractedArchitectures(temporary, verified.agent.architecture, verified.agent.platform)
chmodSync(temporary, 0o700)
renameSync(temporary, destinationRoot)
} catch (error) {
rmSync(temporary, { recursive: true, force: true })
throw error
}
}
function eventEmitter(
command: 'prepare' | 'commit',
emit?: (event: PackageInstallerEvent) => void
): (phase: string) => void {
return (phase) => emit?.({ type: 'progress', command, phase })
}
export function preparePackage(options: InstallerOptions): PackageInstallerResult {
const inputs = assertAbsoluteInputs(options)
const progress = eventEmitter('prepare', options.emit)
progress('validating')
const preparedRoot = join(inputs.operationRoot, 'prepared')
const preparedStatePath = join(preparedRoot, 'result.json')
if (existsSync(preparedStatePath)) {
const state = readPreparedState(preparedStatePath)
if (state.archiveSha256 !== inputs.expectedSha256) {
throw new Error(
'Prepared package state does not match the requested archive'
)
}
const result: PackageInstallerResult = {
type: 'result',
command: 'prepare',
status: 'prepared',
archiveSha256: state.archiveSha256,
agent: state.agent,
runtime: state.runtime
}
options.emit?.(result)
return result
}
const verified = verifyArchive(
inputs.archive,
inputs.expectedSha256,
options.archiveSha256Verified === true,
progress
)
try {
mkdirSync(preparedRoot, { recursive: false, mode: 0o700 })
progress('extracting-payload')
extractPayload(
inputs.archive,
join(preparedRoot, 'payload'),
verified
)
progress('persisting-prepared-state')
persistPreparedState(inputs.operationRoot, verified)
} catch (error) {
rmSync(preparedRoot, { recursive: true, force: true })
throw error
}
const result: PackageInstallerResult = {
type: 'result',
command: 'prepare',
status: 'prepared',
archiveSha256: verified.archiveSha256,
agent: verified.agent,
runtime: verified.runtime
}
options.emit?.(result)
return result
}
type PublishedDirectory = {
destination: string
backupRoot?: string
backup?: string
}
function publishDirectory(
source: string,
destination: string,
operationRoot: string
): PublishedDirectory {
assertOwnedManagedDirectory(dirname(destination))
if (existsSync(destination)) {
const destinationMetadata = lstatSync(destination)
if (
!destinationMetadata.isDirectory() ||
destinationMetadata.isSymbolicLink() ||
(
typeof process.getuid === 'function' &&
destinationMetadata.uid !== process.getuid()
)
) {
throw new Error(`Existing digest destination has conflicting content: ${destination}`)
}
const backupRoot = mkdtempSync(
join(operationRoot, '.replaced-destination-')
)
const backup = join(backupRoot, 'payload')
renameSync(destination, backup)
try {
renameSync(source, destination)
} catch (error) {
renameSync(backup, destination)
rmSync(backupRoot, { recursive: true, force: true })
throw new Error(
'Prepared payload could not replace the conflicting managed destination',
{ cause: error }
)
}
return { destination, backupRoot, backup }
}
try {
renameSync(source, destination)
} catch (error) {
throw new Error(
'Prepared payload could not be atomically published on the managed filesystem',
{ cause: error }
)
}
return { destination }
}
function ensureOwnedManagedHierarchy(
home: string,
runtimeId: string
): void {
assertOwnedManagedDirectory(home, false)
const managedRoot = join(home, '.goodbuddy')
for (const directory of [
managedRoot,
join(managedRoot, 'agent'),
join(managedRoot, 'agent', 'installations'),
join(managedRoot, 'runtimes'),
join(managedRoot, 'runtimes', runtimeId)
]) {
ensureOwnedManagedDirectory(directory)
}
}
type LocalMetadataSnapshot = {
path: string
contents: Buffer | undefined
}
function snapshotLocalMetadata(
paths: readonly string[]
): LocalMetadataSnapshot[] {
return paths.map((path) => ({
path,
contents: existsSync(path)
? readBoundedRegularFile(
path,
MAXIMUM_METADATA_BYTES,
'Managed environment metadata',
'0600'
)
: undefined
}))
}
function writePrivateFileAtomic(
destination: string,
contents: Buffer
): void {
if (
contents.byteLength <= 0 ||
contents.byteLength > MAXIMUM_METADATA_BYTES
) {
throw new Error('Managed environment metadata is invalid')
}
const temporary = join(
dirname(destination),
`.${basename(destination)}-${process.pid}-${Date.now().toString(36)}.tmp`
)
writeBufferAtomically(
destination,
temporary,
contents,
true
)
}
function restoreLocalMetadata(
snapshots: readonly LocalMetadataSnapshot[]
): void {
const errors: unknown[] = []
for (const snapshot of snapshots) {
try {
if (snapshot.contents === undefined) {
rmSync(snapshot.path, { force: true })
} else {
writePrivateFileAtomic(
snapshot.path,
snapshot.contents
)
}
} catch (error) {
errors.push(error)
}
}
if (errors.length > 0) {
throw new AggregateError(
errors,
'Managed environment metadata rollback was incomplete'
)
}
}
function ensureOwnedManagedDirectory(path: string): void {
try {
mkdirSync(path, { recursive: false, mode: 0o700 })
} catch (error) {
if (!isExistingPathError(error)) {
throw error
}
}
assertOwnedManagedDirectory(path)
}
function assertOwnedManagedDirectory(
path: string,
requirePrivateMode = true
): void {
const metadata = lstatSync(path)
if (
!metadata.isDirectory() ||
metadata.isSymbolicLink() ||
(
typeof process.getuid === 'function' &&
metadata.uid !== process.getuid()
) ||
(
requirePrivateMode &&
process.platform !== 'win32' &&
(metadata.mode & 0o777) !== 0o700
)
) {
throw new Error(
`Managed installation directory is unsafe: ${path}`
)
}
}
function isExistingPathError(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
(error as { code?: unknown }).code === 'EEXIST'
)
}
function restoreReplacedDirectory(published: PublishedDirectory): void {
if (!published.backup || !published.backupRoot) {
return
}
rmSync(published.destination, { recursive: true, force: true })
renameSync(published.backup, published.destination)
rmSync(published.backupRoot, { recursive: true, force: true })
}
function discardReplacedDirectory(published: PublishedDirectory): void {
if (published.backupRoot) {
try {
rmSync(published.backupRoot, { recursive: true, force: true })
} catch {
// The bootstrap removes the bounded operation root after commit.
}
}
}
function readPreparedState(path: string): PreparedState {
const bytes = readBoundedRegularFile(
path,
MAXIMUM_METADATA_BYTES,
'Prepared package state',
'0600'
)
const value = parseCanonicalJson(bytes, 'Prepared package state')
exactKeys(
value,
[
'formatVersion',
'archiveSha256',
'releaseKeyRegistrySha256',
'agentRuntimeLockSha256',
'remoteRuntimeLockSha256',
'packageDescriptorSha256',
'packageSignatureSha256',
'agent',
'runtime'
],
'Prepared package state'
)
if (
value.formatVersion !== 1 ||
!sha256Pattern.test(String(value.archiveSha256)) ||
!sha256Pattern.test(
String(value.releaseKeyRegistrySha256)
) ||
!sha256Pattern.test(
String(value.agentRuntimeLockSha256)
) ||
!sha256Pattern.test(
String(value.remoteRuntimeLockSha256)
) ||
!sha256Pattern.test(
String(value.packageDescriptorSha256)
) ||
!sha256Pattern.test(
String(value.packageSignatureSha256)
) ||
!isRecord(value.agent) ||
!isRecord(value.runtime)
) {
throw new Error('Prepared package state is invalid')
}
const agent = value.agent
const runtime = value.runtime
exactKeys(
agent,
[
'installationId',
'agentVersion',
'manifestSha256',
'binaryDigest',
'platform',
'architecture',
'protocol',
'supervisor'
],
'Prepared Agent identity'
)
exactKeys(
runtime,
[
'runtimeId',
'runtimeVersion',
'bundleDigest',
'manifestDigest',
'runtimeAdapterDigest',
'acpCapabilitiesDigest',
'platform',
'architecture',
'protocol'
],
'Prepared Runtime identity'
)
const agentProtocol = protocol(agent.protocol, 'Prepared Agent protocol')
const runtimeProtocol = protocol(
runtime.protocol,
'Prepared Runtime protocol'
)
if (
typeof agent.installationId !== 'string' ||
!versionPattern.test(String(agent.agentVersion)) ||
!sha256Pattern.test(String(agent.manifestSha256)) ||
agent.installationId !== `agent-${String(agent.manifestSha256)}` ||
agent.binaryDigest !== `sha256:${String(agent.manifestSha256)}` ||
!isSupportedTarget(agent.platform, agent.architecture) ||
!['x64', 'arm64'].includes(String(agent.architecture)) ||
agent.supervisor !== 'detached-on-demand' ||
runtime.runtimeId !== 'opencode' ||
!versionPattern.test(String(runtime.runtimeVersion)) ||
!digestPattern.test(String(runtime.bundleDigest)) ||
!digestPattern.test(String(runtime.manifestDigest)) ||
!digestPattern.test(String(runtime.runtimeAdapterDigest)) ||
!digestPattern.test(String(runtime.acpCapabilitiesDigest)) ||
runtime.platform !== agent.platform ||
runtime.architecture !== agent.architecture
) {
throw new Error('Prepared package state identity is invalid')
}
return {
formatVersion: 1,
archiveSha256: String(value.archiveSha256),
releaseKeyRegistrySha256:
String(value.releaseKeyRegistrySha256),
agentRuntimeLockSha256:
String(value.agentRuntimeLockSha256),
remoteRuntimeLockSha256:
String(value.remoteRuntimeLockSha256),
packageDescriptorSha256:
String(value.packageDescriptorSha256),
packageSignatureSha256:
String(value.packageSignatureSha256),
agent: {
installationId: agent.installationId,
agentVersion: String(agent.agentVersion),
manifestSha256: String(agent.manifestSha256),
binaryDigest: String(agent.binaryDigest),
platform: agent.platform as Platform,
architecture: agent.architecture as Architecture,
protocol: agentProtocol,
supervisor: 'detached-on-demand'
},
runtime: {
runtimeId: 'opencode',
runtimeVersion: String(runtime.runtimeVersion),
bundleDigest: String(runtime.bundleDigest),
manifestDigest: String(runtime.manifestDigest),
runtimeAdapterDigest: String(runtime.runtimeAdapterDigest),
acpCapabilitiesDigest: String(runtime.acpCapabilitiesDigest),
platform: agent.platform as Platform,
architecture: runtime.architecture as Architecture,
protocol: runtimeProtocol
}
}
}
function readBoundedRegularFile(
path: string,
maximumBytes: number,
label: string,
expectedMode: '0600' | '0644' | '0755'
): Buffer {
const handle = openSync(
path,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)
)
try {
const snapshot = fstatSync(handle)
const getuid = process.getuid
const expectedPermissions =
expectedMode === '0755'
? 0o755
: expectedMode === '0600'
? 0o600
: 0o644
if (
!snapshot.isFile() ||
snapshot.size <= 0 ||
snapshot.size > maximumBytes ||
(getuid !== undefined && snapshot.uid !== getuid.call(process)) ||
(
process.platform !== 'win32' &&
(snapshot.mode & 0o777) !== expectedPermissions
)
) {
throw new Error(`${label} is not a bounded trusted regular file`)
}
const bytes = readExact(handle, snapshot.size, 0, label)
if (!sameSnapshot(snapshot, fstatSync(handle))) {
throw new Error(`${label} changed while it was read`)
}
return bytes
} finally {
closeSync(handle)
}
}
function assertPreparedIdentity(
agentRoot: string,
runtimeRoot: string,
state: PreparedState,
releaseKeyRegistryBytes: Buffer,
agentRuntimeLockBytes: Buffer,
remoteRuntimeLockBytes: Buffer,
descriptorBytes: Buffer,
packageSignatureBytes: Buffer
): void {
const descriptor = parseCanonicalJson(
descriptorBytes,
'Prepared Agent package descriptor'
)
const declared = parseFiles(
descriptor.files,
'Prepared Agent package'
)
const entries = new Map<string, ZipEntry>()
for (const file of declared) {
entries.set(file.path, {
name: file.path,
size: file.size,
checksum: 0,
mode: file.mode,
dataOffset: 0,
recordStart: 0,
recordEnd: 0
})
}
for (const name of [PACKAGE_DESCRIPTOR, PACKAGE_SIGNATURE]) {
entries.set(name, {
name,
size: name === PACKAGE_DESCRIPTOR
? descriptorBytes.byteLength
: packageSignatureBytes.byteLength,
checksum: 0,
mode: '0644',
dataOffset: 0,
recordStart: 0,
recordEnd: 0
})
}
const registry = parseRegistry(releaseKeyRegistryBytes)
const packageFiles = new Map(
assertOuterDescriptor(
descriptor,
descriptorBytes,
entries,
registry,
packageSignatureBytes
).map((file) => [file.path, file])
)
const agentManifestBytes = readBoundedRegularFile(
join(agentRoot, 'manifest.json'),
MAXIMUM_METADATA_BYTES,
'Prepared Agent manifest',
'0644'
)
const agentManifest = assertAgentManifest(
agentManifestBytes,
readBoundedRegularFile(
join(agentRoot, 'manifest.sig'),
MAXIMUM_METADATA_BYTES,
'Prepared Agent manifest signature',
'0644'
),
registry,
parseCanonicalJson(
agentRuntimeLockBytes,
'Prepared Agent runtime lock'
),
descriptor,
packageFiles
)
const runtime = assertRuntimeManifest(
readBoundedRegularFile(
join(runtimeRoot, 'manifest.json'),
MAXIMUM_METADATA_BYTES,
'Prepared Runtime manifest',
'0644'
),
readBoundedRegularFile(
join(runtimeRoot, 'manifest.sig'),
MAXIMUM_METADATA_BYTES,
'Prepared Runtime manifest signature',
'0644'
),
registry,
parseCanonicalJson(
remoteRuntimeLockBytes,
'Prepared Remote Runtime lock'
),
descriptor,
packageFiles
)
const agentManifestSha256 = sha256(agentManifestBytes)
const architecture = descriptor.architecture as Architecture
const expectedAgent: PackageInstallerAgentIdentity = {
installationId: `agent-${agentManifestSha256}`,
agentVersion: String(agentManifest.agentVersion),
manifestSha256: agentManifestSha256,
binaryDigest: `sha256:${agentManifestSha256}`,
platform: agentManifest.platform as Platform,
architecture,
protocol: protocol(agentManifest.protocol, 'Agent protocol'),
supervisor: 'detached-on-demand'
}
const expectedRuntime: PackageInstallerRuntimeIdentity = {
runtimeId: 'opencode',
runtimeVersion: String(runtime.manifest.runtimeVersion),
bundleDigest: String(runtime.manifest.bundleDigest),
manifestDigest: runtime.manifestDigest,
runtimeAdapterDigest: String(runtime.manifest.adapterDigest),
acpCapabilitiesDigest: String(
runtime.manifest.acpCapabilitiesDigest
),
platform: agentManifest.platform as Platform,
architecture,
protocol: protocol(
runtime.manifest.protocol,
'Runtime protocol'
)
}
if (
canonicalJson(state.agent) !== canonicalJson(expectedAgent) ||
canonicalJson(state.runtime) !== canonicalJson(expectedRuntime)
) {
throw new Error(
'Prepared package identity does not match the authenticated archive'
)
}
}
export function commitPackage(options: InstallerOptions): PackageInstallerResult {
const inputs = assertAbsoluteInputs(options, false)
const progress = eventEmitter('commit', options.emit)
progress('validating-prepared-state')
const preparedRoot = join(inputs.operationRoot, 'prepared')
if (
!existsSync(preparedRoot) ||
!lstatSync(preparedRoot).isDirectory() ||
lstatSync(preparedRoot).isSymbolicLink()
) {
throw new Error('Prepared package state directory is invalid')
}
const state = readPreparedState(join(preparedRoot, 'result.json'))
if (state.archiveSha256 !== inputs.expectedSha256) {
throw new Error('Prepared package state does not match the requested archive')
}
const releaseKeyRegistryBytes = readBoundedRegularFile(
join(preparedRoot, 'agent-release-keys.json'),
MAXIMUM_METADATA_BYTES,
'Prepared Agent release-key registry',
'0600'
)
const agentRuntimeLockBytes = readBoundedRegularFile(
join(preparedRoot, 'agent-runtime-lock.json'),
MAXIMUM_METADATA_BYTES,
'Prepared Agent runtime lock',
'0600'
)
const remoteRuntimeLockBytes = readBoundedRegularFile(
join(preparedRoot, 'remote-runtime-lock.json'),
MAXIMUM_METADATA_BYTES,
'Prepared Remote Runtime lock',
'0600'
)
const descriptorBytes = readBoundedRegularFile(
join(preparedRoot, PACKAGE_DESCRIPTOR),
MAXIMUM_METADATA_BYTES,
'Prepared Agent package descriptor',
'0600'
)
const packageSignatureBytes = readBoundedRegularFile(
join(preparedRoot, PACKAGE_SIGNATURE),
MAXIMUM_METADATA_BYTES,
'Prepared Agent package signature',
'0600'
)
if (
sha256(releaseKeyRegistryBytes) !==
state.releaseKeyRegistrySha256 ||
sha256(agentRuntimeLockBytes) !==
state.agentRuntimeLockSha256 ||
sha256(remoteRuntimeLockBytes) !==
state.remoteRuntimeLockSha256 ||
sha256(descriptorBytes) !==
state.packageDescriptorSha256 ||
sha256(packageSignatureBytes) !==
state.packageSignatureSha256
) {
throw new Error(
'Prepared package metadata does not match its authenticated identity'
)
}
const payloadRoot = join(preparedRoot, 'payload')
if (
!existsSync(payloadRoot) ||
!lstatSync(payloadRoot).isDirectory() ||
lstatSync(payloadRoot).isSymbolicLink()
) {
throw new Error('Prepared package payload directory is invalid')
}
const agentSource = join(payloadRoot, 'agent')
const runtimeSource = join(payloadRoot, 'runtime')
const agentSourceExists = existsSync(agentSource)
const runtimeSourceExists = existsSync(runtimeSource)
if (agentSourceExists && runtimeSourceExists) {
assertPreparedIdentity(
agentSource,
runtimeSource,
state,
releaseKeyRegistryBytes,
agentRuntimeLockBytes,
remoteRuntimeLockBytes,
descriptorBytes,
packageSignatureBytes
)
}
const home = resolve(options.homeDirectory ?? homedir())
const managedRoot = join(home, '.goodbuddy')
ensureOwnedManagedHierarchy(
home,
state.runtime.runtimeId
)
const agentDestination = join(
managedRoot,
'agent',
'installations',
state.agent.installationId
)
const runtimeDestination = join(
managedRoot,
'runtimes',
state.runtime.runtimeId,
state.runtime.bundleDigest.slice('sha256:'.length)
)
const metadataPaths = [
join(managedRoot, 'agent', 'release-keys.json'),
join(managedRoot, 'runtimes', 'release-keys.json'),
join(managedRoot, 'runtimes', 'remote-runtime-lock.json')
] as const
if (!agentSourceExists || !runtimeSourceExists) {
assertPreparedIdentity(
agentSourceExists ? agentSource : agentDestination,
runtimeSourceExists ? runtimeSource : runtimeDestination,
state,
releaseKeyRegistryBytes,
agentRuntimeLockBytes,
remoteRuntimeLockBytes,
descriptorBytes,
packageSignatureBytes
)
}
progress('publishing-content')
const metadataSnapshots = snapshotLocalMetadata(metadataPaths)
const published: PublishedDirectory[] = []
try {
if (agentSourceExists) {
published.push(
publishDirectory(
agentSource,
agentDestination,
inputs.operationRoot
)
)
}
if (runtimeSourceExists) {
published.push(
publishDirectory(
runtimeSource,
runtimeDestination,
inputs.operationRoot
)
)
}
progress('publishing-metadata')
writePrivateFileAtomic(
metadataPaths[0],
releaseKeyRegistryBytes
)
writePrivateFileAtomic(
metadataPaths[1],
releaseKeyRegistryBytes
)
writePrivateFileAtomic(
metadataPaths[2],
remoteRuntimeLockBytes
)
for (const entry of published) {
discardReplacedDirectory(entry)
}
const result: PackageInstallerResult = {
type: 'result',
command: 'commit',
status: 'committed',
archiveSha256: state.archiveSha256,
agent: state.agent,
runtime: state.runtime
}
options.emit?.(result)
return result
} catch (error) {
const rollbackErrors: unknown[] = []
try {
restoreLocalMetadata(metadataSnapshots)
} catch (rollbackError) {
rollbackErrors.push(rollbackError)
}
for (const entry of [...published].reverse()) {
try {
restoreReplacedDirectory(entry)
} catch (rollbackError) {
rollbackErrors.push(rollbackError)
}
}
if (rollbackErrors.length > 0) {
throw new PackageInstallerRollbackIncompleteError(
[error, ...rollbackErrors],
'Package commit failed and managed state could not be fully restored',
{ cause: error }
)
}
throw error
}
}
function parseCli(argv: readonly string[]): {
command: 'prepare' | 'commit'
options: InstallerOptions
} {
const command = argv[0]
if (command !== 'prepare' && command !== 'commit') {
throw new Error(
'Usage: remote-package-installer.mjs <prepare|commit> --operation-root <absolute-path> --archive <absolute-path> --expected-sha256 <hex> [--archive-sha256-verified true]'
)
}
const values: Record<string, string> = {}
const required = [
'--operation-root',
'--archive',
'--expected-sha256'
] as const
const allowed = new Set([
...required,
'--archive-sha256-verified'
])
for (let index = 1; index < argv.length; index += 2) {
const flag = argv[index]
const value = argv[index + 1]
if (
flag === undefined ||
value === undefined ||
value.startsWith('--') ||
!allowed.has(flag) ||
values[flag] !== undefined
) {
throw new Error('Invalid package installer argument')
}
values[flag] = value
}
for (const flag of required) {
if (values[flag] === undefined) {
throw new Error(`Missing required package installer argument: ${flag}`)
}
}
if (
values['--archive-sha256-verified'] !== undefined &&
values['--archive-sha256-verified'] !== 'true'
) {
throw new Error('Invalid package installer argument')
}
return {
command,
options: {
operationRoot: values['--operation-root']!,
archive: values['--archive']!,
expectedSha256: values['--expected-sha256']!,
archiveSha256Verified:
values['--archive-sha256-verified'] === 'true'
}
}
}
function boundedMessage(error: unknown): string {
const message = error instanceof Error ? error.message : String(error)
return message.replace(/[\r\n]+/gu, ' ').slice(0, 2_000)
}
export function runPackageInstallerCli(
argv: readonly string[] = process.argv.slice(2)
): number {
let command: 'prepare' | 'commit' = argv[0] === 'commit' ? 'commit' : 'prepare'
const emit = (event: PackageInstallerEvent): void => {
process.stdout.write(`${JSON.stringify(event)}\n`)
}
try {
const parsed = parseCli(argv)
command = parsed.command
const options = { ...parsed.options, emit }
if (command === 'prepare') {
preparePackage(options)
} else {
commitPackage(options)
}
return 0
} catch (error) {
emit({
type: 'error',
command,
status:
error instanceof PackageInstallerRollbackIncompleteError
? 'rollback-incomplete'
: 'failed',
message: boundedMessage(error)
})
return 1
}
}
if (typeof require !== 'undefined' && require.main === module) {
process.exitCode = runPackageInstallerCli()
}