Display native OpenCode and Continue checklists alongside scheduled tasks, preserving request ownership, explicit clears and saved progress. Bundle both remote runtimes and support managed Continue installation, model routing, cancellation and restart continuation. Validation: real local and Linux remote model checks, full Electron remote Continue flows, typecheck and lint passed. Full suite: 4707 passed, 67 skipped, one OpenCode compaction failure passed targeted rerun. Release note: Display native execution checklists above conversations and support managed remote Continue projects with saved progress and cancellation recovery.
1107 lines
35 KiB
JavaScript
1107 lines
35 KiB
JavaScript
const { createHash, createPublicKey, sign, verify } = require("node:crypto");
|
|
const {
|
|
chmodSync,
|
|
closeSync,
|
|
cpSync,
|
|
copyFileSync,
|
|
existsSync,
|
|
lstatSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
openSync,
|
|
readFileSync,
|
|
readSync,
|
|
readdirSync,
|
|
renameSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} = require("node:fs");
|
|
const { tmpdir } = require("node:os");
|
|
const {
|
|
basename,
|
|
dirname,
|
|
join,
|
|
posix,
|
|
relative,
|
|
resolve,
|
|
sep,
|
|
} = require("node:path");
|
|
const tar = require("tar");
|
|
const { targetName, assertTargetBinary } = require("./agent-build-target.cjs");
|
|
const {
|
|
preflightRegisteredProductionKey,
|
|
} = require("./signing-key-preflight.cjs");
|
|
const { prepareBundledOpenCodeConfig } = require("./opencode-config.cjs");
|
|
|
|
const root = join(__dirname, "..");
|
|
const supportedArchitectures = Object.freeze(["x64", "arm64"]);
|
|
const manifestFileName = "manifest.json";
|
|
const signatureFileName = "manifest.sig";
|
|
const signingPrivateKeyEnvironment = "GOODBUDDY_SIGNING_PRIVATE_KEY";
|
|
const signingKeyIdEnvironment = "GOODBUDDY_SIGNING_KEY_ID";
|
|
const signatureDomain = Buffer.from(
|
|
"GoodBuddy Remote Runtime Bundle Manifest Signature v1\0",
|
|
"utf8",
|
|
);
|
|
const profileDigests = Object.freeze({
|
|
adapter:
|
|
"sha256:ac7696abe504bbea444d8ad80dd8faa96437e6dbb27ed987ce46e1045f3dd365",
|
|
acp: "sha256:0e3764ab897258bc0234162357c2eefc3faeb9918080ae81b60e2084ed7996a6",
|
|
});
|
|
function runtimeProfileDigests(runtimeId) {
|
|
if (runtimeId === 'opencode') return profileDigests;
|
|
if (runtimeId !== 'continue') throw new Error('Unsupported Runtime');
|
|
return {
|
|
adapter: `sha256:${sha256('GoodBuddy Continue HTTP ACP facade v1')}`,
|
|
acp: `sha256:${sha256(canonicalJson({ protocolVersion: 1, capabilities: {
|
|
loadSession: true, mcpCapabilities: { http: true }, promptCapabilities: { image: true },
|
|
sessionCapabilities: { resume: {}, close: {} }
|
|
} }))}`
|
|
};
|
|
}
|
|
const fixedLimits = Object.freeze({
|
|
// Zero leaves prompt duration to its requested deadline and cancellation.
|
|
maximumPromptRuntimeMilliseconds: 0,
|
|
maximumPromptInputBytes: 16 * 1024 * 1024,
|
|
maximumPromptOutputBytes: 8 * 1024 * 1024,
|
|
});
|
|
|
|
function readJson(filePath, description) {
|
|
try {
|
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
} catch (error) {
|
|
throw new Error(
|
|
`${description} is missing or invalid: ${
|
|
error instanceof Error ? error.message : String(error)
|
|
}`,
|
|
{ cause: error },
|
|
);
|
|
}
|
|
}
|
|
|
|
function readRemoteRuntimeLock(projectRoot = root) {
|
|
const lock = readJson(
|
|
join(projectRoot, "remote-runtime-lock.json"),
|
|
"Remote Runtime lock",
|
|
);
|
|
const runtime = lock?.runtimes?.opencode;
|
|
if (
|
|
lock?.formatVersion !== 1 ||
|
|
runtime?.version !== "1.18.29" ||
|
|
runtime.provider !== "opencode" ||
|
|
runtime.entrypoint !== "bin/opencode" ||
|
|
runtime.entrypointIdentity !== "opencode-acp" ||
|
|
JSON.stringify(runtime.argvPrefix) !== JSON.stringify(["acp"]) ||
|
|
!Number.isSafeInteger(runtime.protocol?.major) ||
|
|
!Number.isSafeInteger(runtime.protocol?.minor)
|
|
) {
|
|
throw new Error("Remote Runtime lock contract is invalid");
|
|
}
|
|
for (const architecture of supportedArchitectures) {
|
|
const target = runtime.targets?.[architecture];
|
|
if (
|
|
typeof target?.package !== "string" ||
|
|
typeof target.integrity !== "string" ||
|
|
!/^sha512-[A-Za-z0-9+/]{86}==$/u.test(target.integrity)
|
|
) {
|
|
throw new Error(`Remote Runtime lock target is invalid: ${architecture}`);
|
|
}
|
|
}
|
|
return lock;
|
|
}
|
|
|
|
function readTrustedKeyRegistry(projectRoot = root) {
|
|
const registry = readJson(
|
|
join(projectRoot, "resources", "agent-release-keys.json"),
|
|
"Runtime trusted key registry",
|
|
);
|
|
if (
|
|
registry?.formatVersion !== 1 ||
|
|
!Array.isArray(registry.keys) ||
|
|
!Array.isArray(registry.revocations)
|
|
) {
|
|
throw new Error("Runtime trusted key registry contract is invalid");
|
|
}
|
|
const keyIds = new Set();
|
|
for (const key of registry.keys) {
|
|
if (
|
|
typeof key?.keyId !== "string" ||
|
|
!/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(key.keyId) ||
|
|
keyIds.has(key.keyId) ||
|
|
typeof key.publicKeySpkiBase64 !== "string" ||
|
|
!["production", "test"].includes(key.environment)
|
|
) {
|
|
throw new Error("Runtime trusted key registry entry is invalid");
|
|
}
|
|
const publicKey = createPublicKey({
|
|
key: Buffer.from(key.publicKeySpkiBase64, "base64"),
|
|
format: "der",
|
|
type: "spki",
|
|
});
|
|
if (publicKey.asymmetricKeyType !== "ed25519") {
|
|
throw new Error(`Runtime trusted key is not Ed25519: ${key.keyId}`);
|
|
}
|
|
keyIds.add(key.keyId);
|
|
}
|
|
return registry;
|
|
}
|
|
|
|
function sha256(contents) {
|
|
return createHash("sha256").update(contents).digest("hex");
|
|
}
|
|
|
|
function sha512FileIntegrity(filePath) {
|
|
const hash = createHash("sha512");
|
|
const descriptor = openSync(filePath, "r");
|
|
const chunk = Buffer.allocUnsafe(1024 * 1024);
|
|
try {
|
|
while (true) {
|
|
const bytesRead = readSync(descriptor, chunk, 0, chunk.byteLength, null);
|
|
if (bytesRead === 0) {
|
|
break;
|
|
}
|
|
hash.update(chunk.subarray(0, bytesRead));
|
|
}
|
|
} finally {
|
|
closeSync(descriptor);
|
|
}
|
|
return `sha512-${hash.digest("base64")}`;
|
|
}
|
|
|
|
function sha256FileSync(filePath) {
|
|
return sha256(readFileSync(filePath));
|
|
}
|
|
|
|
function canonicalJson(value) {
|
|
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 Runtime value is not finite");
|
|
}
|
|
return JSON.stringify(Object.is(value, -0) ? 0 : value);
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
}
|
|
if (
|
|
typeof value !== "object" ||
|
|
Object.getPrototypeOf(value) !== Object.prototype
|
|
) {
|
|
throw new Error("Canonical Runtime value is not plain JSON");
|
|
}
|
|
return `{${Object.keys(value)
|
|
.sort()
|
|
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
.join(",")}}`;
|
|
}
|
|
|
|
function digestCanonicalOperation({ method, scope, payload }) {
|
|
return `sha256:${sha256(
|
|
Buffer.from(canonicalJson({ method, payload, scope }), "utf8"),
|
|
)}`;
|
|
}
|
|
|
|
function digestRuntimeBundleIdentity(manifest) {
|
|
const { bundleDigest: _bundleDigest, ...identity } = manifest;
|
|
void _bundleDigest;
|
|
return digestCanonicalOperation({
|
|
method: "runtime/bundleIdentity",
|
|
scope: {
|
|
kind: "installation",
|
|
installationId: manifest.runtimeId,
|
|
},
|
|
payload: identity,
|
|
});
|
|
}
|
|
|
|
function digestRuntimeBundleManifest(manifest) {
|
|
return digestCanonicalOperation({
|
|
method: "runtime/bundleManifest",
|
|
scope: {
|
|
kind: "installation",
|
|
installationId: manifest.runtimeId,
|
|
},
|
|
payload: manifest,
|
|
});
|
|
}
|
|
|
|
function canonicalManifestBytes(manifest) {
|
|
return Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
function signaturePayload(manifestBytes) {
|
|
return Buffer.concat([signatureDomain, manifestBytes]);
|
|
}
|
|
|
|
function publicKeySpkiBase64(key) {
|
|
return createPublicKey(key)
|
|
.export({ format: "der", type: "spki" })
|
|
.toString("base64");
|
|
}
|
|
|
|
function productionSigningIdentity(environment = process.env) {
|
|
const keyId = environment[signingKeyIdEnvironment];
|
|
const privateKey = environment[signingPrivateKeyEnvironment];
|
|
if (!keyId || !privateKey) {
|
|
throw new Error(
|
|
`${signingKeyIdEnvironment} and ${signingPrivateKeyEnvironment} are required to sign the bundled Runtime`,
|
|
);
|
|
}
|
|
return { keyId, privateKey };
|
|
}
|
|
|
|
function trustedKeyForSigning(
|
|
signingIdentity,
|
|
registry,
|
|
verificationEnvironment,
|
|
) {
|
|
const record = registry.keys.find(
|
|
(key) => key.keyId === signingIdentity.keyId,
|
|
);
|
|
if (!record) {
|
|
throw new Error(
|
|
`Runtime signing key is not registered: ${signingIdentity.keyId}`,
|
|
);
|
|
}
|
|
if (
|
|
verificationEnvironment === "production" &&
|
|
record.environment !== "production"
|
|
) {
|
|
throw new Error(
|
|
`Runtime signing key is not registered for production: ${record.keyId}`,
|
|
);
|
|
}
|
|
if (
|
|
registry.revocations.some((revocation) => revocation.keyId === record.keyId)
|
|
) {
|
|
throw new Error("Runtime signing key is outside its trusted interval");
|
|
}
|
|
if (
|
|
publicKeySpkiBase64(signingIdentity.privateKey) !==
|
|
record.publicKeySpkiBase64
|
|
) {
|
|
throw new Error(
|
|
"Runtime signing private key does not match the trusted registry",
|
|
);
|
|
}
|
|
return record;
|
|
}
|
|
|
|
function preflightProductionSigningKey(options = {}) {
|
|
const projectRoot = options.projectRoot ?? root;
|
|
const environment = options.environment ?? process.env;
|
|
const keyId = options.keyId ?? environment[signingKeyIdEnvironment];
|
|
const registry = options.registry ?? readTrustedKeyRegistry(projectRoot);
|
|
return preflightRegisteredProductionKey({
|
|
component: "GoodBuddy",
|
|
keyId,
|
|
registry,
|
|
missingKeyIdMessage: `${signingKeyIdEnvironment} is not configured; provision the production GoodBuddy signing key ID before building a release`,
|
|
});
|
|
}
|
|
|
|
function assertSafeManifestPath(filePath) {
|
|
if (
|
|
typeof filePath !== "string" ||
|
|
filePath.length === 0 ||
|
|
filePath.length > 240 ||
|
|
filePath.includes("\\") ||
|
|
filePath.includes("\0") ||
|
|
posix.isAbsolute(filePath) ||
|
|
filePath
|
|
.split("/")
|
|
.some((part) => part.length === 0 || part === "." || part === "..")
|
|
) {
|
|
throw new Error(`Unsafe Runtime manifest path: ${String(filePath)}`);
|
|
}
|
|
}
|
|
|
|
function compareUtf8(left, right) {
|
|
return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
|
|
}
|
|
|
|
function listFiles(directory) {
|
|
const files = [];
|
|
const pending = [directory];
|
|
while (pending.length > 0) {
|
|
const current = pending.pop();
|
|
const entries = readdirSync(current, {
|
|
withFileTypes: true,
|
|
}).sort((left, right) => compareUtf8(right.name, left.name));
|
|
for (const entry of entries) {
|
|
const absolutePath = join(current, entry.name);
|
|
if (entry.isSymbolicLink()) {
|
|
throw new Error(
|
|
`Runtime bundle cannot contain a symlink: ${absolutePath}`,
|
|
);
|
|
}
|
|
if (entry.isDirectory()) {
|
|
pending.push(absolutePath);
|
|
} else if (entry.isFile()) {
|
|
files.push(absolutePath);
|
|
} else {
|
|
throw new Error(
|
|
`Runtime bundle contains an unsupported file: ${absolutePath}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return files.sort(compareUtf8);
|
|
}
|
|
|
|
function posixRelative(directory, filePath) {
|
|
return relative(directory, filePath).split(sep).join("/");
|
|
}
|
|
|
|
function modeString(filePath) {
|
|
return (statSync(filePath).mode & 0o777).toString(8).padStart(4, "0");
|
|
}
|
|
|
|
function assertElfArchitecture(
|
|
filePath,
|
|
expectedArchitecture,
|
|
platform = "linux",
|
|
) {
|
|
assertTargetBinary(filePath, expectedArchitecture, platform, "Runtime");
|
|
}
|
|
|
|
function validateManifestShape(manifest) {
|
|
if (
|
|
manifest?.formatVersion !== 2 ||
|
|
manifest.product !== "GoodBuddy" ||
|
|
!['opencode', 'continue'].includes(manifest.runtimeId) ||
|
|
manifest.runtimeVersion !== (manifest.runtimeId === 'continue' ? '1.5.47' : '1.18.29') ||
|
|
manifest.provider !== manifest.runtimeId ||
|
|
!(
|
|
manifest.platform === "linux" ||
|
|
(manifest.platform === "darwin" && manifest.architecture === "arm64")
|
|
) ||
|
|
!supportedArchitectures.includes(manifest.architecture) ||
|
|
typeof manifest.signingKeyId !== "string" ||
|
|
!/^sha256:[a-f0-9]{64}$/u.test(manifest.bundleDigest) ||
|
|
!/^sha256:[a-f0-9]{64}$/u.test(manifest.adapterDigest) ||
|
|
!/^sha256:[a-f0-9]{64}$/u.test(manifest.acpCapabilitiesDigest) ||
|
|
!Array.isArray(manifest.files) ||
|
|
!Array.isArray(manifest.licenses) ||
|
|
!Array.isArray(manifest.allowedEnvironmentNames)
|
|
) {
|
|
throw new Error("Runtime manifest contract is invalid");
|
|
}
|
|
if (
|
|
!Number.isSafeInteger(manifest.limits?.maximumPromptRuntimeMilliseconds) ||
|
|
manifest.limits.maximumPromptRuntimeMilliseconds < 0 ||
|
|
!Number.isSafeInteger(manifest.limits.maximumPromptInputBytes) ||
|
|
manifest.limits.maximumPromptInputBytes < 1 ||
|
|
!Number.isSafeInteger(manifest.limits.maximumPromptOutputBytes) ||
|
|
manifest.limits.maximumPromptOutputBytes < 0 ||
|
|
Object.keys(manifest.limits).length !== 3 ||
|
|
"quotas" in manifest
|
|
) {
|
|
throw new Error("Runtime manifest limits contract is invalid");
|
|
}
|
|
if (
|
|
manifest.entrypoint?.identity !== `${manifest.runtimeId}-acp` ||
|
|
manifest.entrypoint.path !== (manifest.runtimeId === 'continue' ? 'lib/continue/dist/cn.js' : 'bin/opencode') ||
|
|
!/^[a-f0-9]{64}$/u.test(manifest.entrypoint.sha256) ||
|
|
JSON.stringify(manifest.entrypoint.argvPrefix) !== JSON.stringify(manifest.runtimeId === 'continue' ? [] : ['acp'])
|
|
) {
|
|
throw new Error("Runtime manifest entrypoint is invalid");
|
|
}
|
|
}
|
|
|
|
function trustedKeyForManifest(manifest, registry, verificationEnvironment) {
|
|
const key = registry.keys.find(
|
|
(candidate) => candidate.keyId === manifest.signingKeyId,
|
|
);
|
|
if (!key) {
|
|
throw new Error(
|
|
`Runtime manifest uses an unknown signing key: ${manifest.signingKeyId}`,
|
|
);
|
|
}
|
|
if (
|
|
verificationEnvironment === "production" &&
|
|
key.environment !== "production"
|
|
) {
|
|
throw new Error(
|
|
`Production Runtime verification rejects non-production key: ${key.keyId}`,
|
|
);
|
|
}
|
|
if (
|
|
registry.revocations.some((revocation) => revocation.keyId === key.keyId)
|
|
) {
|
|
throw new Error("Runtime signing key is outside its trusted interval");
|
|
}
|
|
return key;
|
|
}
|
|
|
|
function assertManifestMatchesLock(
|
|
manifest,
|
|
lock,
|
|
architecture,
|
|
platform = "linux",
|
|
) {
|
|
const expected = lock.runtimes[manifest.runtimeId];
|
|
if (!expected) throw new Error('Runtime is not locked');
|
|
const target =
|
|
expected.targets[
|
|
platform === "linux" ? architecture : targetName(architecture, platform)
|
|
];
|
|
if (
|
|
manifest.runtimeId !== expected.provider ||
|
|
manifest.runtimeVersion !== expected.version ||
|
|
manifest.provider !== expected.provider ||
|
|
manifest.platform !== platform ||
|
|
manifest.architecture !== architecture ||
|
|
manifest.sourcePackage?.name !== target.package ||
|
|
manifest.sourcePackage.integrity !== target.integrity ||
|
|
manifest.entrypoint.path !== expected.entrypoint ||
|
|
manifest.entrypoint.identity !== expected.entrypointIdentity ||
|
|
JSON.stringify(manifest.entrypoint.argvPrefix) !==
|
|
JSON.stringify(expected.argvPrefix) ||
|
|
JSON.stringify(manifest.allowedEnvironmentNames) !==
|
|
JSON.stringify(expected.allowedEnvironmentNames) ||
|
|
manifest.protocol?.major !== expected.protocol.major ||
|
|
manifest.protocol?.minor !== expected.protocol.minor ||
|
|
manifest.adapterDigest !== runtimeProfileDigests(manifest.runtimeId).adapter ||
|
|
manifest.acpCapabilitiesDigest !== runtimeProfileDigests(manifest.runtimeId).acp
|
|
) {
|
|
throw new Error(
|
|
"Runtime manifest does not match the locked OpenCode profile",
|
|
);
|
|
}
|
|
}
|
|
|
|
function verifyBundleDirectory(bundleDirectoryInput, options = {}) {
|
|
const bundleDirectory = resolve(bundleDirectoryInput);
|
|
const projectRoot = options.projectRoot ?? root;
|
|
const lock = options.lock ?? readRemoteRuntimeLock(projectRoot);
|
|
const registry = options.registry ?? readTrustedKeyRegistry(projectRoot);
|
|
const architecture = options.architecture;
|
|
const platform = options.platform ?? "linux";
|
|
targetName(architecture, platform);
|
|
const manifestBytes = readFileSync(join(bundleDirectory, manifestFileName));
|
|
const signatureText = readFileSync(
|
|
join(bundleDirectory, signatureFileName),
|
|
"utf8",
|
|
);
|
|
if (!/^[A-Za-z0-9+/]{86}==\n$/u.test(signatureText)) {
|
|
throw new Error("Runtime detached signature encoding is invalid");
|
|
}
|
|
const manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
validateManifestShape(manifest);
|
|
if (!canonicalManifestBytes(manifest).equals(manifestBytes)) {
|
|
throw new Error("Runtime manifest is not in canonical deterministic form");
|
|
}
|
|
assertManifestMatchesLock(manifest, lock, architecture, platform);
|
|
const environment = options.verificationEnvironment ?? "production";
|
|
const key = trustedKeyForManifest(manifest, registry, environment);
|
|
const publicKey = createPublicKey({
|
|
key: Buffer.from(key.publicKeySpkiBase64, "base64"),
|
|
format: "der",
|
|
type: "spki",
|
|
});
|
|
const signatureBytes = Buffer.from(signatureText.trim(), "base64");
|
|
if (
|
|
signatureBytes.length !== 64 ||
|
|
!verify(null, signaturePayload(manifestBytes), publicKey, signatureBytes)
|
|
) {
|
|
throw new Error("Runtime manifest signature verification failed");
|
|
}
|
|
if (digestRuntimeBundleIdentity(manifest) !== manifest.bundleDigest) {
|
|
throw new Error("Runtime bundle identity digest does not match");
|
|
}
|
|
if (
|
|
options.requireDigestDirectory !== false &&
|
|
basename(bundleDirectory) !== manifest.bundleDigest.slice("sha256:".length)
|
|
) {
|
|
throw new Error(
|
|
"Runtime bundle digest does not match its managed directory",
|
|
);
|
|
}
|
|
|
|
const declared = new Set();
|
|
for (const file of manifest.files) {
|
|
assertSafeManifestPath(file.path);
|
|
if (
|
|
declared.has(file.path) ||
|
|
!Number.isSafeInteger(file.size) ||
|
|
file.size < 0 ||
|
|
!/^[a-f0-9]{64}$/u.test(file.sha256) ||
|
|
!["0644", "0755"].includes(file.mode)
|
|
) {
|
|
throw new Error(`Runtime manifest file entry is invalid: ${file.path}`);
|
|
}
|
|
declared.add(file.path);
|
|
const filePath = join(bundleDirectory, ...file.path.split("/"));
|
|
const stat = lstatSync(filePath);
|
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
throw new Error(`Runtime payload is not a regular file: ${file.path}`);
|
|
}
|
|
if (stat.size !== file.size) {
|
|
throw new Error(`Runtime payload size mismatch: ${file.path}`);
|
|
}
|
|
if (sha256FileSync(filePath) !== file.sha256) {
|
|
throw new Error(`Runtime payload hash mismatch: ${file.path}`);
|
|
}
|
|
if (
|
|
options.enforceFilesystemMode !== false &&
|
|
process.platform !== "win32" &&
|
|
modeString(filePath) !== file.mode
|
|
) {
|
|
throw new Error(`Runtime payload mode mismatch: ${file.path}`);
|
|
}
|
|
}
|
|
const actual = new Set(
|
|
listFiles(bundleDirectory).map((filePath) =>
|
|
posixRelative(bundleDirectory, filePath),
|
|
),
|
|
);
|
|
const expected = new Set([...declared, manifestFileName, signatureFileName]);
|
|
if (
|
|
actual.size !== expected.size ||
|
|
[...actual].some((filePath) => !expected.has(filePath))
|
|
) {
|
|
throw new Error("Runtime bundle contains undeclared or missing files");
|
|
}
|
|
for (const license of manifest.licenses) {
|
|
assertSafeManifestPath(license.path);
|
|
if (!declared.has(license.path)) {
|
|
throw new Error("Runtime manifest license is not declared");
|
|
}
|
|
}
|
|
if (
|
|
!declared.has(manifest.entrypoint.path) ||
|
|
manifest.entrypoint.sha256 !==
|
|
manifest.files.find((file) => file.path === manifest.entrypoint.path)
|
|
?.sha256
|
|
) {
|
|
throw new Error("Runtime entrypoint payload is missing");
|
|
}
|
|
if (manifest.runtimeId === 'opencode') assertElfArchitecture(
|
|
join(bundleDirectory, ...manifest.entrypoint.path.split("/")),
|
|
architecture,
|
|
platform,
|
|
);
|
|
return {
|
|
bundleDirectory,
|
|
manifest,
|
|
manifestDigest: digestRuntimeBundleManifest(manifest),
|
|
};
|
|
}
|
|
|
|
function lockedArchive(lock, architecture, archivePath, platform = "linux", runtimeId = 'opencode') {
|
|
const input = resolveLockedRuntimeInput(lock, architecture, platform, runtimeId);
|
|
if (!archivePath || !existsSync(archivePath)) {
|
|
throw new Error(`Locked OpenCode archive is required: ${input.archive}`);
|
|
}
|
|
if (!statSync(archivePath).isFile()) {
|
|
throw new Error(`Runtime archive is not a file: ${archivePath}`);
|
|
}
|
|
if (basename(archivePath) !== input.archive) {
|
|
throw new Error(
|
|
`Runtime archive filename mismatch: expected ${input.archive}`,
|
|
);
|
|
}
|
|
const actualIntegrity = sha512FileIntegrity(archivePath);
|
|
if (actualIntegrity !== input.integrity) {
|
|
throw new Error(
|
|
`Runtime archive integrity mismatch for ${targetName(architecture)}`,
|
|
);
|
|
}
|
|
return lock.runtimes[runtimeId].targets[
|
|
platform === "linux" ? architecture : targetName(architecture, platform)
|
|
];
|
|
}
|
|
|
|
function resolveLockedRuntimeInput(lock, architecture, platform = "linux", runtimeId = 'opencode') {
|
|
targetName(architecture, platform);
|
|
const runtime = lock.runtimes[runtimeId];
|
|
if (!runtime) throw new Error('Runtime is not locked');
|
|
const target =
|
|
runtime.targets[
|
|
platform === "linux" ? architecture : targetName(architecture, platform)
|
|
];
|
|
return {
|
|
packageName: target.package,
|
|
version: runtime.version,
|
|
integrity: target.integrity,
|
|
archive: `${target.package.replace('@', '').replace('/', '-')}-${runtime.version}.tgz`,
|
|
};
|
|
}
|
|
|
|
function createManifest(bundleDirectory, metadata) {
|
|
const files = listFiles(bundleDirectory)
|
|
.filter((filePath) => {
|
|
const name = posixRelative(bundleDirectory, filePath);
|
|
return name !== manifestFileName && name !== signatureFileName;
|
|
})
|
|
.map((filePath) => {
|
|
const path = posixRelative(bundleDirectory, filePath);
|
|
assertSafeManifestPath(path);
|
|
return {
|
|
path,
|
|
size: statSync(filePath).size,
|
|
sha256: sha256FileSync(filePath),
|
|
mode: path === metadata.runtime.entrypoint ? "0755" : "0644",
|
|
};
|
|
});
|
|
const entrypoint = files.find((file) => file.path === metadata.runtime.entrypoint);
|
|
if (!entrypoint) {
|
|
throw new Error("Runtime entrypoint is missing");
|
|
}
|
|
const initial = {
|
|
formatVersion: 2,
|
|
product: "GoodBuddy",
|
|
runtimeId: metadata.runtime.provider,
|
|
runtimeVersion: metadata.runtime.version,
|
|
provider: metadata.runtime.provider,
|
|
platform: metadata.platform ?? "linux",
|
|
architecture: metadata.architecture,
|
|
signingKeyId: metadata.signingKeyId,
|
|
bundleDigest: `sha256:${"0".repeat(64)}`,
|
|
adapterDigest: runtimeProfileDigests(metadata.runtime.provider).adapter,
|
|
sourcePackage: metadata.sourcePackage,
|
|
entrypoint: {
|
|
identity: metadata.runtime.entrypointIdentity,
|
|
path: metadata.runtime.entrypoint,
|
|
sha256: entrypoint.sha256,
|
|
argvPrefix: metadata.runtime.argvPrefix,
|
|
},
|
|
files,
|
|
licenses: [
|
|
{
|
|
package: metadata.runtime.provider === 'continue' ? '@continuedev/cli' : 'opencode-ai',
|
|
version: metadata.runtime.version,
|
|
spdx: metadata.runtime.provider === 'continue' ? 'Apache-2.0' : 'MIT',
|
|
path: metadata.runtime.provider === 'continue' ? 'licenses/continue-Apache-2.0.txt' : 'licenses/opencode-MIT.txt',
|
|
},
|
|
],
|
|
allowedEnvironmentNames: metadata.runtime.allowedEnvironmentNames,
|
|
protocol: metadata.runtime.protocol,
|
|
acpCapabilitiesDigest: runtimeProfileDigests(metadata.runtime.provider).acp,
|
|
limits: fixedLimits,
|
|
};
|
|
return {
|
|
...initial,
|
|
bundleDigest: digestRuntimeBundleIdentity(initial),
|
|
};
|
|
}
|
|
|
|
function defaultOutputRoot(projectRoot = root) {
|
|
return join(projectRoot, ".remote-runtime-resources");
|
|
}
|
|
|
|
function bundleDirectory(outputRoot, architecture, digest, platform = "linux", runtimeId = 'opencode') {
|
|
return join(
|
|
outputRoot,
|
|
targetName(architecture, platform),
|
|
runtimeId,
|
|
digest.slice("sha256:".length),
|
|
);
|
|
}
|
|
|
|
function buildRuntimeBundle(options) {
|
|
const projectRoot = options.projectRoot ?? root;
|
|
const architecture = options.architecture;
|
|
const platform = options.platform ?? "linux";
|
|
const lock = options.lock ?? readRemoteRuntimeLock(projectRoot);
|
|
const runtimeId = options.runtimeId ?? 'opencode';
|
|
const runtime = lock.runtimes[runtimeId];
|
|
const sourcePackage = lockedArchive(
|
|
lock,
|
|
architecture,
|
|
options.runtimeArchive,
|
|
platform,
|
|
runtimeId,
|
|
);
|
|
const registry = options.registry ?? readTrustedKeyRegistry(projectRoot);
|
|
const signingIdentity =
|
|
options.testSigningIdentity ??
|
|
options.signingIdentity ??
|
|
productionSigningIdentity(options.environment ?? process.env);
|
|
const verificationEnvironment = options.testSigningIdentity
|
|
? "test"
|
|
: "production";
|
|
trustedKeyForSigning(signingIdentity, registry, verificationEnvironment);
|
|
const outputRoot = options.outputRoot ?? defaultOutputRoot(projectRoot);
|
|
mkdirSync(outputRoot, { recursive: true });
|
|
const staging = mkdtempSync(
|
|
join(outputRoot, `.stage-${targetName(architecture)}-`),
|
|
);
|
|
const extracted = mkdtempSync(
|
|
join(tmpdir(), `goodbuddy-opencode-${targetName(architecture)}-`),
|
|
);
|
|
try {
|
|
tar.x({
|
|
file: options.runtimeArchive,
|
|
cwd: extracted,
|
|
sync: true,
|
|
strict: true,
|
|
filter(path) {
|
|
if (runtimeId === 'continue') return path === 'package/package.json' || path.startsWith('package/dist/') || path === 'package/LICENSE';
|
|
return (
|
|
path === "package/package.json" || path === "package/bin/opencode"
|
|
);
|
|
},
|
|
});
|
|
const sourceMetadata = readJson(
|
|
join(extracted, "package", "package.json"),
|
|
"OpenCode source package metadata",
|
|
);
|
|
if (
|
|
sourceMetadata.name !== sourcePackage.package &&
|
|
sourceMetadata.name !== sourcePackage.name
|
|
) {
|
|
// The lock target itself carries the canonical package name.
|
|
if (sourceMetadata.name !== sourcePackage.package) {
|
|
throw new Error("OpenCode source package name does not match the lock");
|
|
}
|
|
}
|
|
if (sourceMetadata.version !== runtime.version) {
|
|
throw new Error(
|
|
"OpenCode source package version does not match the lock",
|
|
);
|
|
}
|
|
if (runtimeId === 'continue') {
|
|
mkdirSync(join(staging, 'lib'), { mode: 0o700 });
|
|
cpSync(join(extracted, 'package'), join(staging, 'lib', 'continue'), { recursive: true });
|
|
for (const file of listFiles(staging)) chmodSync(file, 0o644);
|
|
chmodSync(join(staging, runtime.entrypoint), 0o755);
|
|
mkdirSync(join(staging, 'licenses'), { mode: 0o700 });
|
|
// The pinned CN package declares Apache-2.0 but omits the license text.
|
|
copyFileSync(join(projectRoot, 'node_modules', '@agentclientprotocol', 'sdk', 'LICENSE'), join(staging, 'licenses', 'continue-Apache-2.0.txt'));
|
|
} else {
|
|
mkdirSync(join(staging, "bin"), { mode: 0o700 });
|
|
copyFileSync(
|
|
join(extracted, "package", "bin", "opencode"),
|
|
join(staging, "bin", "opencode"),
|
|
);
|
|
chmodSync(join(staging, "bin", "opencode"), 0o755);
|
|
assertElfArchitecture(
|
|
join(staging, "bin", "opencode"),
|
|
architecture,
|
|
platform,
|
|
);
|
|
const configTemplate = prepareBundledOpenCodeConfig(projectRoot);
|
|
const configDestination = join(staging, "config", "opencode");
|
|
cpSync(configTemplate, configDestination, {
|
|
recursive: true,
|
|
// npm CLI shims are not used by the imported plugin and may be symlinks.
|
|
filter: (source) => basename(source) !== ".bin",
|
|
});
|
|
for (const file of listFiles(configDestination)) {
|
|
chmodSync(file, 0o644);
|
|
}
|
|
mkdirSync(join(staging, "licenses"), { mode: 0o700 });
|
|
copyFileSync(
|
|
join(projectRoot, "node_modules", "opencode-ai", "LICENSE"),
|
|
join(staging, "licenses", "opencode-MIT.txt"),
|
|
);
|
|
chmodSync(join(staging, "licenses", "opencode-MIT.txt"), 0o644);
|
|
}
|
|
const manifest = createManifest(staging, {
|
|
platform,
|
|
architecture,
|
|
runtime,
|
|
sourcePackage: {
|
|
name: sourcePackage.package,
|
|
integrity: sourcePackage.integrity,
|
|
},
|
|
signingKeyId: signingIdentity.keyId,
|
|
});
|
|
const manifestBytes = canonicalManifestBytes(manifest);
|
|
const signatureBytes = sign(
|
|
null,
|
|
signaturePayload(manifestBytes),
|
|
signingIdentity.privateKey,
|
|
);
|
|
writeFileSync(join(staging, manifestFileName), manifestBytes, {
|
|
mode: 0o644,
|
|
});
|
|
writeFileSync(
|
|
join(staging, signatureFileName),
|
|
`${signatureBytes.toString("base64")}\n`,
|
|
{ mode: 0o644 },
|
|
);
|
|
chmodSync(join(staging, manifestFileName), 0o644);
|
|
chmodSync(join(staging, signatureFileName), 0o644);
|
|
const destination = bundleDirectory(
|
|
outputRoot,
|
|
architecture,
|
|
manifest.bundleDigest,
|
|
platform,
|
|
runtimeId,
|
|
);
|
|
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
|
if (existsSync(destination)) {
|
|
const existing = verifyBundleDirectory(destination, {
|
|
platform,
|
|
projectRoot,
|
|
architecture,
|
|
lock,
|
|
registry,
|
|
verificationEnvironment,
|
|
enforceFilesystemMode: options.enforceFilesystemMode,
|
|
});
|
|
if (canonicalJson(existing.manifest) !== canonicalJson(manifest)) {
|
|
throw new Error(
|
|
"Existing Runtime digest directory has different content",
|
|
);
|
|
}
|
|
return existing;
|
|
}
|
|
chmodSync(staging, 0o700);
|
|
renameSync(staging, destination);
|
|
return verifyBundleDirectory(destination, {
|
|
platform,
|
|
projectRoot,
|
|
architecture,
|
|
lock,
|
|
registry,
|
|
verificationEnvironment,
|
|
enforceFilesystemMode: options.enforceFilesystemMode,
|
|
});
|
|
} finally {
|
|
rmSync(staging, { recursive: true, force: true });
|
|
rmSync(extracted, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function createRuntimeArchive(bundleDirectoryInput, archivePath) {
|
|
const directory = resolve(bundleDirectoryInput);
|
|
const manifest = readJson(join(directory, manifestFileName), 'Runtime manifest');
|
|
const modes = new Map(manifest.files.map(file => [file.path, parseInt(file.mode, 8)]));
|
|
const entries = listFiles(directory)
|
|
.map((filePath) => posixRelative(directory, filePath))
|
|
.sort(compareUtf8);
|
|
mkdirSync(dirname(archivePath), { recursive: true });
|
|
rmSync(archivePath, { force: true });
|
|
tar.c(
|
|
{
|
|
cwd: directory,
|
|
file: archivePath,
|
|
sync: true,
|
|
portable: true,
|
|
noPax: true,
|
|
mtime: new Date(0),
|
|
onWriteEntry(entry) {
|
|
if (entry.stat) entry.stat.mode = modes.get(entry.path) ?? 0o644;
|
|
},
|
|
},
|
|
entries,
|
|
);
|
|
}
|
|
|
|
function importRuntimeArchive(archivePath, options) {
|
|
if (!existsSync(archivePath)) {
|
|
throw new Error(`Runtime artifact archive is missing: ${archivePath}`);
|
|
}
|
|
const projectRoot = options.projectRoot ?? root;
|
|
const architecture = options.architecture;
|
|
const outputRoot = options.outputRoot ?? defaultOutputRoot(projectRoot);
|
|
mkdirSync(outputRoot, { recursive: true });
|
|
const staging = mkdtempSync(
|
|
join(outputRoot, `.import-${targetName(architecture)}-`),
|
|
);
|
|
try {
|
|
tar.x({
|
|
cwd: staging,
|
|
file: archivePath,
|
|
sync: true,
|
|
strict: true,
|
|
preservePaths: false,
|
|
});
|
|
const verified = verifyBundleDirectory(staging, {
|
|
...options,
|
|
projectRoot,
|
|
architecture,
|
|
requireDigestDirectory: false,
|
|
});
|
|
const destination = bundleDirectory(
|
|
outputRoot,
|
|
architecture,
|
|
verified.manifest.bundleDigest,
|
|
verified.manifest.platform,
|
|
verified.manifest.runtimeId,
|
|
);
|
|
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
|
if (existsSync(destination)) {
|
|
return verifyBundleDirectory(destination, {
|
|
...options,
|
|
projectRoot,
|
|
architecture,
|
|
});
|
|
}
|
|
chmodSync(staging, 0o700);
|
|
renameSync(staging, destination);
|
|
return verifyBundleDirectory(destination, {
|
|
...options,
|
|
projectRoot,
|
|
architecture,
|
|
});
|
|
} finally {
|
|
rmSync(staging, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function findOnlyBundle(outputRoot, architecture, runtimeId = 'opencode') {
|
|
const runtimeRoot = join(outputRoot, targetName(architecture), runtimeId);
|
|
const entries = existsSync(runtimeRoot)
|
|
? readdirSync(runtimeRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => join(runtimeRoot, entry.name))
|
|
: [];
|
|
if (entries.length !== 1) {
|
|
throw new Error(
|
|
`Expected exactly one built Runtime bundle for ${targetName(architecture)}`,
|
|
);
|
|
}
|
|
return entries[0];
|
|
}
|
|
|
|
function redactSecrets(value, environment = process.env) {
|
|
let output = String(value);
|
|
for (const secret of [
|
|
environment[signingPrivateKeyEnvironment],
|
|
environment[signingKeyIdEnvironment],
|
|
]) {
|
|
if (secret && secret.length >= 4) {
|
|
output = output.split(secret).join("[redacted]");
|
|
}
|
|
}
|
|
return output.replace(
|
|
/-----BEGIN PRIVATE KEY-----[\s\S]*?-----END PRIVATE KEY-----/gu,
|
|
"[redacted private key]",
|
|
);
|
|
}
|
|
|
|
function parseArguments(argv) {
|
|
const command = argv[0];
|
|
const options = {};
|
|
const allowed = new Set([
|
|
"--arch",
|
|
"--runtime-archive",
|
|
"--runtime-id",
|
|
"--archive",
|
|
"--output-root",
|
|
"--bundle",
|
|
]);
|
|
for (let index = 1; index < argv.length; index += 2) {
|
|
const flag = argv[index];
|
|
const value = argv[index + 1];
|
|
if (!allowed.has(flag) || !value) {
|
|
throw new Error("Invalid Remote Runtime bundle argument");
|
|
}
|
|
options[
|
|
flag
|
|
.slice(2)
|
|
.replace(/-([a-z])/gu, (_match, value) => value.toUpperCase())
|
|
] = value;
|
|
}
|
|
if (!["build", "import", "verify", "preflight"].includes(command)) {
|
|
throw new Error(
|
|
"Usage: remote-runtime-bundle.cjs <build|import|verify|preflight> --arch <x64|arm64>",
|
|
);
|
|
}
|
|
if (command === "preflight") {
|
|
return { command };
|
|
}
|
|
targetName(options.arch);
|
|
return { command, ...options };
|
|
}
|
|
|
|
function main(argv = process.argv.slice(2)) {
|
|
const options = parseArguments(argv);
|
|
if (options.command === "preflight") {
|
|
const record = preflightProductionSigningKey();
|
|
console.log(
|
|
`Production GoodBuddy signing preflight passed for bundled Runtime: ${record.keyId}`,
|
|
);
|
|
return;
|
|
}
|
|
const outputRoot = options.outputRoot
|
|
? resolve(options.outputRoot)
|
|
: defaultOutputRoot();
|
|
if (options.command === "build") {
|
|
const verified = buildRuntimeBundle({
|
|
architecture: options.arch,
|
|
runtimeId: options.runtimeId,
|
|
runtimeArchive: resolve(options.runtimeArchive ?? ""),
|
|
outputRoot,
|
|
});
|
|
const archivePath = options.archive
|
|
? resolve(options.archive)
|
|
: join(
|
|
root,
|
|
"dist",
|
|
"remote-runtime-bundles",
|
|
`goodbuddy-${options.runtimeId ?? 'opencode'}-${targetName(options.arch)}.tar`,
|
|
);
|
|
createRuntimeArchive(verified.bundleDirectory, archivePath);
|
|
console.log(
|
|
`Remote Runtime bundle built and verified: ${targetName(options.arch)}`,
|
|
);
|
|
return;
|
|
}
|
|
if (options.command === "import") {
|
|
importRuntimeArchive(resolve(options.archive ?? ""), {
|
|
architecture: options.arch,
|
|
outputRoot,
|
|
});
|
|
console.log(
|
|
`Remote Runtime bundle imported and verified: ${targetName(options.arch)}`,
|
|
);
|
|
return;
|
|
}
|
|
verifyBundleDirectory(
|
|
options.bundle
|
|
? resolve(options.bundle)
|
|
: findOnlyBundle(outputRoot, options.arch, options.runtimeId),
|
|
{ architecture: options.arch },
|
|
);
|
|
console.log(`Remote Runtime bundle verified: ${targetName(options.arch)}`);
|
|
}
|
|
|
|
module.exports = {
|
|
buildRuntimeBundle,
|
|
bundleDirectory,
|
|
canonicalJson,
|
|
canonicalManifestBytes,
|
|
createManifest,
|
|
createRuntimeArchive,
|
|
defaultOutputRoot,
|
|
digestRuntimeBundleIdentity,
|
|
digestRuntimeBundleManifest,
|
|
fixedLimits,
|
|
importRuntimeArchive,
|
|
preflightProductionSigningKey,
|
|
profileDigests,
|
|
publicKeySpkiBase64,
|
|
readRemoteRuntimeLock,
|
|
readTrustedKeyRegistry,
|
|
redactSecrets,
|
|
resolveLockedRuntimeInput,
|
|
signaturePayload,
|
|
supportedArchitectures,
|
|
targetName,
|
|
verifyBundleDirectory,
|
|
};
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(
|
|
redactSecrets(error instanceof Error ? error.message : String(error)),
|
|
);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|