mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
127 lines
3.7 KiB
JavaScript
127 lines
3.7 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import {
|
|
access,
|
|
appendFile,
|
|
chmod,
|
|
copyFile,
|
|
mkdtemp,
|
|
rm,
|
|
} from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { promisify } from "node:util";
|
|
|
|
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const verifierRun = createVerifierRun();
|
|
const command = process.env.REA_VERIFY_SERVER_COMMAND;
|
|
if (process.platform !== "linux" || process.getuid?.() !== 0)
|
|
throw new Error("Linux setup verification requires a root Linux runner");
|
|
if (!command) throw new Error("REA_VERIFY_SERVER_COMMAND is required");
|
|
try {
|
|
await access("/opt/hopper/bin/Hopper");
|
|
throw new Error("Linux setup verification requires a clean runner");
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message.includes("clean runner"))
|
|
throw error;
|
|
}
|
|
|
|
const runJson = async (args) => {
|
|
const { stdout } = await execFileAsync(command, args, {
|
|
env: process.env,
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
});
|
|
return JSON.parse(stdout);
|
|
};
|
|
|
|
const plan = await runJson(["setup", "--json"]);
|
|
if (plan.status !== "needs_confirmation")
|
|
throw new Error("setup did not produce a read-only confirmation plan");
|
|
await access("/opt/hopper/bin/Hopper").then(
|
|
() => {
|
|
throw new Error("setup plan mutated the clean runner");
|
|
},
|
|
() => undefined,
|
|
);
|
|
|
|
const installed = await runJson([
|
|
"setup",
|
|
"--yes",
|
|
"--install-hopper",
|
|
"--json",
|
|
]);
|
|
if (
|
|
installed.status !== "ready" ||
|
|
!installed.appliedActions.includes("installed_hopper")
|
|
)
|
|
throw new Error(
|
|
`approved setup did not become ready: ${JSON.stringify(installed)}`,
|
|
);
|
|
const doctor = await runJson(["doctor", "--json"]);
|
|
if (doctor.healthy !== true)
|
|
throw new Error(`doctor is unhealthy: ${JSON.stringify(doctor)}`);
|
|
|
|
await execFileAsync(command, ["analyze", "/usr/bin/true"], {
|
|
env: process.env,
|
|
timeout: 180_000,
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
});
|
|
process.env.HOPPER_TARGET_PATH = "/usr/bin/true";
|
|
process.env.HOPPER_SECOND_TARGET_PATH = "/usr/bin/false";
|
|
process.env.REA_VERIFY_SERVER_ARGS_JSON = '["mcp"]';
|
|
await import("./verify-real-hopper-linux.mjs");
|
|
|
|
const modifiedDirectory = await mkdtemp(join(tmpdir(), "rea-modified-hopper-"));
|
|
try {
|
|
const modifiedHopper = join(modifiedDirectory, "Hopper");
|
|
await copyFile("/opt/hopper/bin/Hopper", modifiedHopper);
|
|
await appendFile(modifiedHopper, new Uint8Array([0]));
|
|
await chmod(modifiedHopper, 0o700);
|
|
try {
|
|
await execFileAsync(command, ["analyze", "/usr/bin/true"], {
|
|
env: { ...process.env, HOPPER_LAUNCHER_PATH: modifiedHopper },
|
|
timeout: 30_000,
|
|
});
|
|
throw new Error("modified Hopper build was not rejected");
|
|
} catch (error) {
|
|
const output = `${error?.stdout ?? ""}\n${error?.stderr ?? ""}`;
|
|
if (!output.includes("unsupported_hopper_build"))
|
|
throw new Error(
|
|
"modified Hopper build did not return its stable failure code",
|
|
);
|
|
}
|
|
} finally {
|
|
await rm(modifiedDirectory, { recursive: true, force: true });
|
|
}
|
|
|
|
const repeated = await runJson([
|
|
"setup",
|
|
"--yes",
|
|
"--install-hopper",
|
|
"--json",
|
|
]);
|
|
if (
|
|
repeated.status !== "ready" ||
|
|
repeated.appliedActions.includes("installed_hopper")
|
|
)
|
|
throw new Error("a second setup was not idempotent");
|
|
const { stdout: processes } = await execFileAsync("ps", [
|
|
"-ax",
|
|
"-o",
|
|
"command=",
|
|
]);
|
|
if (/\/opt\/hopper\/bin\/Hopper|\bXvfb\b|hopper-demo-x11\.py/u.test(processes))
|
|
throw new Error("Linux setup verification leaked a Hopper demo session");
|
|
|
|
console.log(
|
|
JSON.stringify({
|
|
verifier_run: await completeVerifierRun(verifierRun),
|
|
status: "verified",
|
|
setup: true,
|
|
cli: true,
|
|
mcp: true,
|
|
idempotent: true,
|
|
}),
|
|
);
|