Download src/gateway/worker-environments/bootstrap.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 39.1 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/worker-environments/bootstrap.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/gateway/worker-environments/bootstrap.test.ts
-
curl -L -o bootstrap.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/worker-environments/bootstrap.test.ts
39.1 kB
| import { createHash } from "node:crypto"; | |
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import { runInNewContext } from "node:vm"; | |
| import { describe, expect, it } from "vitest"; | |
| import { | |
| isSupportedOpenClawNodeVersion, | |
| PROCESS_NODE_VERSION_CHECK, | |
| } from "../../../node-version.mjs"; | |
| import { NODE_RELEASE_VERSION_CASES } from "../../../test/helpers/node-version-cases.js"; | |
| import type { WorkerSshEndpoint } from "../../plugins/types.js"; | |
| import { runCommandWithTimeout, type SpawnResult } from "../../process/exec.js"; | |
| import { withTestDir } from "../../test-helpers/temp-dir.js"; | |
| import { bootstrapWorker as bootstrapWorkerCore } from "./bootstrap.js"; | |
| import { createWorkerBundleProducer, type WorkerInstallationArtifact } from "./bundle.js"; | |
| type WorkerBootstrapRequest = Parameters<typeof bootstrapWorkerCore>[0]; | |
| type WorkerBootstrapDependencies = Parameters<typeof bootstrapWorkerCore>[1]; | |
| type WorkerBootstrapCommandRunner = NonNullable<WorkerBootstrapDependencies["runCommand"]>; | |
| const BUNDLE_HASH = "a".repeat(64); | |
| const TARBALL_SHA256 = "b".repeat(64); | |
| const VERSION = "2026.7.11"; | |
| const NPM_INTEGRITY = `sha512-${Buffer.alloc(64).toString("base64")}`; | |
| const OUTPUT_TAG = "OPENCLAW_WORKER_BOOTSTRAP_V1"; | |
| const OPERATION_ID = "provision-operation-1"; | |
| const OPERATION_TOKEN = createHash("sha256").update(OPERATION_ID).digest("hex"); | |
| const UPLOAD_FILENAME = `openclaw-upload-${BUNDLE_HASH}.tgz.${OPERATION_TOKEN}`; | |
| const REMOTE_TARBALL = `/home/worker/.openclaw-worker/.incoming/${UPLOAD_FILENAME}`; | |
| const HOST_KEY = ["ssh-ed25519", "AAAA"].join(" "); | |
| const RECEIPT_JSON = JSON.stringify({ | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| }); | |
| const SSH: WorkerSshEndpoint = { | |
| host: "worker.example.com", | |
| port: 2222, | |
| user: "worker", | |
| hostKey: HOST_KEY, | |
| keyRef: { source: "file", provider: "worker-keys", id: "/development-key" }, | |
| }; | |
| const BUNDLE: WorkerInstallationArtifact = { | |
| install: "bundle", | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| tarballBytes: 1, | |
| tarballSha256: TARBALL_SHA256, | |
| tarballPath: "/gateway/cache/worker.tgz", | |
| }; | |
| function tagged(action: "current" | "install" | "receipt", payload: string): string { | |
| return `${OUTPUT_TAG}\t${action}\t${payload}\n`; | |
| } | |
| function result(overrides: Partial<SpawnResult> = {}): SpawnResult { | |
| return { | |
| stdout: "", | |
| stderr: "", | |
| code: 0, | |
| signal: null, | |
| killed: false, | |
| termination: "exit", | |
| ...overrides, | |
| }; | |
| } | |
| function fakeRunner( | |
| responses: SpawnResult[], | |
| inspectCall?: ( | |
| argv: string[], | |
| options: Parameters<WorkerBootstrapCommandRunner>[1], | |
| ) => void | Promise<void>, | |
| ) { | |
| const calls: Array<{ | |
| argv: string[]; | |
| options: Parameters<WorkerBootstrapCommandRunner>[1]; | |
| }> = []; | |
| const runCommand: WorkerBootstrapCommandRunner = async (argv, options) => { | |
| calls.push({ argv, options }); | |
| await inspectCall?.(argv, options); | |
| const response = responses.shift(); | |
| if (!response) { | |
| throw new Error("unexpected bootstrap command"); | |
| } | |
| return response; | |
| }; | |
| return { calls, runCommand }; | |
| } | |
| function commandPort(argv: string[]): number { | |
| const portFlag = argv[0] === "scp" ? "-P" : "-p"; | |
| return Number(argv[argv.indexOf(portFlag) + 1]); | |
| } | |
| const resolveIdentity = async () => ({ kind: "path", path: "/keys/worker" }) as const; | |
| const bootstrapWorker = ( | |
| request: Omit<WorkerBootstrapRequest, "operationId"> & { operationId?: string }, | |
| dependencies: WorkerBootstrapDependencies, | |
| ) => | |
| bootstrapWorkerCore( | |
| { operationId: OPERATION_ID, pinnedHostKey: request.ssh.hostKey, ...request }, | |
| dependencies, | |
| ); | |
| describe("bootstrapWorker", () => { | |
| it("skips a matching installed bundle and uses the pinned host key", async () => { | |
| let knownHosts = ""; | |
| const runner = fakeRunner( | |
| [result({ stdout: `shell banner\n${tagged("current", RECEIPT_JSON)}login footer\n` })], | |
| async (argv) => { | |
| const option = argv.find((value) => value.startsWith("UserKnownHostsFile=")); | |
| if (!option) { | |
| throw new Error("missing known-hosts option"); | |
| } | |
| knownHosts = await fs.readFile(option.slice("UserKnownHostsFile=".length), "utf8"); | |
| }, | |
| ); | |
| await expect( | |
| bootstrapWorker( | |
| { | |
| ssh: SSH, | |
| artifact: BUNDLE, | |
| }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).resolves.toEqual({ | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| }); | |
| expect(runner.calls).toHaveLength(1); | |
| expect(runner.calls[0]?.argv[0]).toBe("ssh"); | |
| expect(runner.calls[0]?.argv).toContain("StrictHostKeyChecking=yes"); | |
| expect(runner.calls[0]?.options.input).toContain("actual.openclawVersion"); | |
| expect(runner.calls[0]?.options.input).toContain("openclaw-worker-bundle-v1"); | |
| expect(runner.calls[0]?.options.input).not.toContain("$root/current"); | |
| expect(knownHosts).toBe(`[worker.example.com]:2222 ${HOST_KEY}\n`); | |
| }); | |
| it("fails before resolving identity or opening SSH when the host-key pin is missing", async () => { | |
| const runner = fakeRunner([]); | |
| let identityResolutionCount = 0; | |
| await expect( | |
| bootstrapWorkerCore( | |
| { ssh: SSH, artifact: BUNDLE, operationId: OPERATION_ID }, | |
| { | |
| resolveIdentity: async () => { | |
| identityResolutionCount += 1; | |
| return { kind: "path", path: "/keys/worker" }; | |
| }, | |
| runCommand: runner.runCommand, | |
| }, | |
| ), | |
| ).rejects.toThrow("WorkerProvider.provision() must return ssh.hostKey"); | |
| expect(identityResolutionCount).toBe(0); | |
| expect(runner.calls).toHaveLength(0); | |
| }); | |
| it.each(["identity", "preflight", "transfer"] as const)( | |
| "stops bootstrap after ownership changes during %s and cleans only its own upload", | |
| async (boundary) => { | |
| let current = true; | |
| let commands = 0; | |
| const runner = fakeRunner( | |
| boundary === "identity" | |
| ? [] | |
| : [result({ stdout: tagged("install", REMOTE_TARBALL) }), result(), result()], | |
| () => { | |
| commands += 1; | |
| if ( | |
| (boundary === "preflight" && commands === 1) || | |
| (boundary === "transfer" && commands === 2) | |
| ) { | |
| current = false; | |
| } | |
| }, | |
| ); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { | |
| resolveIdentity: async () => { | |
| if (boundary === "identity") { | |
| current = false; | |
| } | |
| return resolveIdentity(); | |
| }, | |
| assertCurrent: () => { | |
| if (!current) { | |
| throw new Error("worker owner changed"); | |
| } | |
| }, | |
| runCommand: runner.runCommand, | |
| }, | |
| ), | |
| ).rejects.toThrow("worker owner changed"); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual( | |
| boundary === "identity" | |
| ? [] | |
| : boundary === "preflight" | |
| ? ["ssh", "ssh"] | |
| : ["ssh", "scp", "ssh"], | |
| ); | |
| if (boundary !== "identity") { | |
| expect(runner.calls.at(-1)?.argv.at(-1)).toContain(OPERATION_TOKEN); | |
| expect(runner.calls.at(-1)?.argv.at(-1)).toContain(BUNDLE_HASH); | |
| } | |
| }, | |
| ); | |
| it("transfers and installs a fresh bundle despite terminal cleanup failure", async () => { | |
| const runner = fakeRunner([ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result(), | |
| result({ stdout: tagged("receipt", RECEIPT_JSON) }), | |
| result({ code: 1, stderr: "synthetic cleanup failure" }), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).resolves.toEqual({ | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| }); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual(["ssh", "scp", "ssh", "ssh"]); | |
| expect(runner.calls[0]?.argv).toContain("StrictHostKeyChecking=yes"); | |
| expect(runner.calls.flatMap((call) => call.argv)).not.toContain("StrictHostKeyChecking=no"); | |
| expect(runner.calls[1]?.argv).toContain(BUNDLE.tarballPath); | |
| expect(runner.calls[1]?.argv).toContain(`worker@worker.example.com:${REMOTE_TARBALL}`); | |
| expect(runner.calls.flatMap((call) => call.argv).join(" ")).not.toContain(OPERATION_ID); | |
| expect(runner.calls[0]?.argv.at(-1)).toContain(OPERATION_TOKEN); | |
| expect(runner.calls[2]?.options.input).toContain("bootstrap-receipt.json"); | |
| expect(runner.calls[2]?.options.input).toContain("lock=$lock_root/$hash"); | |
| expect(runner.calls[2]?.options.input).toContain('ln -s "$lock_identity" "$lock"'); | |
| expect(runner.calls[2]?.options.input).toContain("worker bundle archive digest mismatch"); | |
| expect(runner.calls[2]?.options.input).toContain( | |
| 'const artifactPaths = ["github-exec-launcher.mjs","worker.mjs","workspace-rsync-receiver.mjs"]', | |
| ); | |
| expect(runner.calls[2]?.options.input).not.toContain('npm install --prefix "$staging"'); | |
| expect(runner.calls[2]?.options.input).toContain("worker install content does not match"); | |
| expect(runner.calls[2]?.options.input).toContain( | |
| 'mv "$staging" "$install_dir"\nfinish_with_receipt', | |
| ); | |
| expect(runner.calls[2]?.options.input).toMatch( | |
| /finish_with_receipt\(\) \{[\s\S]*?rm -f -- "\$upload"\s+printf [^\n]+ receipt/u, | |
| ); | |
| expect(runner.calls[2]?.options.input).toMatch( | |
| /if receipt_matches; then\s+finish_with_receipt/u, | |
| ); | |
| expect(runner.calls[2]?.argv.at(-1)).toContain(BUNDLE_HASH); | |
| expect(runner.calls[2]?.argv.at(-1)).toContain(TARBALL_SHA256); | |
| expect(runner.calls[2]?.argv.at(-1)).toContain(VERSION); | |
| }); | |
| it.each([ | |
| { name: "keeps the floor for a small bundle", tarballBytes: 1, transferTimeoutMs: 600_000 }, | |
| { | |
| name: "scales the transfer timeout for a large bundle", | |
| tarballBytes: 243_000_000, | |
| transferTimeoutMs: 1_944_000, | |
| }, | |
| { | |
| name: "caps the transfer timeout for an absurdly large bundle", | |
| tarballBytes: Number.MAX_SAFE_INTEGER, | |
| transferTimeoutMs: 3_600_000, | |
| }, | |
| ])( | |
| "$name while other phases keep the base timeout", | |
| async ({ tarballBytes, transferTimeoutMs }) => { | |
| const baseTimeoutMs = 600_000; | |
| const runner = fakeRunner([ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result(), | |
| result({ stdout: tagged("receipt", RECEIPT_JSON) }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: { ...BUNDLE, tarballBytes } }, | |
| { resolveIdentity, runCommand: runner.runCommand, timeoutMs: baseTimeoutMs }, | |
| ), | |
| ).resolves.toEqual(JSON.parse(RECEIPT_JSON)); | |
| const [preflight, transfer, install] = runner.calls; | |
| expect(preflight?.options.timeoutMs).toBeLessThanOrEqual(baseTimeoutMs); | |
| expect(preflight?.options.timeoutMs).toBeGreaterThanOrEqual(baseTimeoutMs - 100); | |
| expect(transfer?.argv[0]).toBe("scp"); | |
| expect(transfer?.options.timeoutMs).toBeLessThanOrEqual(transferTimeoutMs); | |
| expect(transfer?.options.timeoutMs).toBeGreaterThanOrEqual(transferTimeoutMs - 100); | |
| expect(install?.options.timeoutMs).toBeLessThanOrEqual(baseTimeoutMs); | |
| expect(install?.options.timeoutMs).toBeGreaterThanOrEqual(baseTimeoutMs - 100); | |
| }, | |
| ); | |
| it.each([Number.NaN, -1, 1.5, Number.POSITIVE_INFINITY])( | |
| "rejects invalid bundle tarball size %s before any remote work", | |
| async (tarballBytes) => { | |
| const runner = fakeRunner([]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: { ...BUNDLE, tarballBytes } }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("Worker bundle artifact has an invalid tarball size"); | |
| expect(runner.calls).toHaveLength(0); | |
| }, | |
| ); | |
| it.each([ | |
| `/home/worker/other/.incoming/${UPLOAD_FILENAME}`, | |
| `/home/worker/.openclaw-worker/other/${UPLOAD_FILENAME}`, | |
| `/home/worker/.openclaw-worker/.incoming/../.incoming/${UPLOAD_FILENAME}`, | |
| `/home/worker/./.openclaw-worker/.incoming/${UPLOAD_FILENAME}`, | |
| `/home//worker/.openclaw-worker/.incoming/${UPLOAD_FILENAME}`, | |
| `/home/worker/.openclaw-worker/.incoming/${UPLOAD_FILENAME}.other`, | |
| ])("rejects a noncanonical or non-owned upload path: %s", async (remotePath) => { | |
| const runner = fakeRunner([result({ stdout: tagged("install", remotePath) }), result()]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("preflight returned an invalid upload path"); | |
| expect(runner.calls).toHaveLength(2); | |
| }); | |
| it("selects an authenticated fallback before transfer and install", async () => { | |
| const runner = fakeRunner([ | |
| result({ code: 255, stderr: "primary transport unavailable" }), | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result(), | |
| result({ stdout: tagged("receipt", RECEIPT_JSON) }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: { ...SSH, fallbackPorts: [22] }, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).resolves.toEqual(JSON.parse(RECEIPT_JSON)); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual(["ssh", "ssh", "scp", "ssh", "ssh"]); | |
| expect(runner.calls.map((call) => commandPort(call.argv))).toEqual([2222, 22, 22, 22, 22]); | |
| expect(new Set(runner.calls.map((call) => call.argv[call.argv.indexOf("-i") + 1]))).toEqual( | |
| new Set(["/keys/worker"]), | |
| ); | |
| expect( | |
| new Set( | |
| runner.calls.map((call) => | |
| call.argv.find((value) => value.startsWith("UserKnownHostsFile=")), | |
| ), | |
| ).size, | |
| ).toBe(1); | |
| }); | |
| it("retries bundle transfer when the selected port changes after preflight", async () => { | |
| const runner = fakeRunner([ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result({ code: 255, stderr: "primary transport unavailable" }), | |
| result(), | |
| result({ stdout: tagged("receipt", RECEIPT_JSON) }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: { ...SSH, fallbackPorts: [22] }, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).resolves.toEqual(JSON.parse(RECEIPT_JSON)); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual(["ssh", "scp", "scp", "ssh", "ssh"]); | |
| expect(runner.calls.map((call) => commandPort(call.argv))).toEqual([2222, 2222, 22, 22, 22]); | |
| }); | |
| it("retries install when the selected port changes after bundle transfer", async () => { | |
| const runner = fakeRunner([ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result(), | |
| result({ code: 255, stderr: "primary transport unavailable" }), | |
| result({ stdout: tagged("receipt", RECEIPT_JSON) }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: { ...SSH, fallbackPorts: [22] }, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).resolves.toEqual(JSON.parse(RECEIPT_JSON)); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual(["ssh", "scp", "ssh", "ssh", "ssh"]); | |
| expect(runner.calls.map((call) => commandPort(call.argv))).toEqual([2222, 2222, 2222, 22, 22]); | |
| }); | |
| it("fails with provider setup guidance when Node.js is missing", async () => { | |
| const runner = fakeRunner([ | |
| result({ | |
| code: 42, | |
| stderr: "OPENCLAW_WORKER_NODE_MISSING\n", | |
| }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("install Node in the provider setup phase"); | |
| expect(runner.calls).toHaveLength(2); | |
| }); | |
| it("fails with provider setup guidance when Node.js is unsupported", async () => { | |
| const runner = fakeRunner([ | |
| result({ | |
| code: 45, | |
| stderr: "OPENCLAW_WORKER_NODE_UNSUPPORTED: v24.14.1\n", | |
| }), | |
| result(), | |
| ]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("Node 24.16.0+ or 26.1.0+ with WAL-reset-safe SQLite"); | |
| expect(runner.calls).toHaveLength(2); | |
| expect(runner.calls[0]?.options.input).toContain( | |
| `const nodeSafe = ${PROCESS_NODE_VERSION_CHECK};`, | |
| ); | |
| expect(runner.calls[0]?.options.input).toContain("SELECT sqlite_version() AS version"); | |
| }); | |
| it("embeds a shell-safe Node release check matching the canonical contract", () => { | |
| expect(PROCESS_NODE_VERSION_CHECK).not.toContain("'"); | |
| for (const version of NODE_RELEASE_VERSION_CASES) { | |
| const actual = runInNewContext(PROCESS_NODE_VERSION_CHECK, { | |
| process: { versions: { node: version } }, | |
| }); | |
| expect(actual, version).toBe(isSupportedOpenClawNodeVersion(version)); | |
| } | |
| }); | |
| it("installs only the exact npm package without transferring a tarball", async () => { | |
| const artifact: WorkerInstallationArtifact = { | |
| install: "npm", | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: [], | |
| packageIntegrity: NPM_INTEGRITY, | |
| packageSpec: `openclaw@${VERSION}`, | |
| }; | |
| const npmReceipt = JSON.stringify({ | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: [], | |
| }); | |
| const npmRunner = fakeRunner([ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result({ stdout: tagged("receipt", npmReceipt) }), | |
| result(), | |
| ]); | |
| await bootstrapWorker( | |
| { ssh: SSH, artifact }, | |
| { resolveIdentity, runCommand: npmRunner.runCommand }, | |
| ); | |
| expect(npmRunner.calls.map((call) => call.argv[0])).toEqual(["ssh", "ssh", "ssh"]); | |
| expect(npmRunner.calls[1]?.options.input).toContain("npm pack"); | |
| expect(npmRunner.calls[1]?.options.input).not.toContain("npm install"); | |
| expect(npmRunner.calls[1]?.options.input).toContain("--registry=https://registry.npmjs.org/"); | |
| expect(npmRunner.calls[1]?.options.input).toContain("package/dist/worker/worker.mjs"); | |
| expect(npmRunner.calls[1]?.options.input).toContain( | |
| "package/dist/worker/github-exec-launcher.mjs", | |
| ); | |
| expect(npmRunner.calls[1]?.options.input).toContain( | |
| "package/dist/worker/workspace-rsync-receiver.mjs", | |
| ); | |
| expect(npmRunner.calls[1]?.options.input).not.toContain("node_modules"); | |
| expect(npmRunner.calls[1]?.argv.at(-1)).toContain(`openclaw@${VERSION}`); | |
| }); | |
| it("rejects a non-exact npm package before opening SSH", async () => { | |
| const runner = fakeRunner([]); | |
| const artifact: WorkerInstallationArtifact = { | |
| install: "npm", | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: VERSION, | |
| protocolFeatures: [], | |
| packageIntegrity: NPM_INTEGRITY, | |
| packageSpec: "openclaw@latest", | |
| }; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact }, { resolveIdentity, runCommand: runner.runCommand }), | |
| ).rejects.toThrow(`exact package openclaw@${VERSION}`); | |
| expect(runner.calls).toHaveLength(0); | |
| }); | |
| it("rejects latest even when the npm package and version strings match", async () => { | |
| const runner = fakeRunner([]); | |
| const artifact: WorkerInstallationArtifact = { | |
| install: "npm", | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: "latest", | |
| protocolFeatures: [], | |
| packageIntegrity: NPM_INTEGRITY, | |
| packageSpec: "openclaw@latest", | |
| }; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact }, { resolveIdentity, runCommand: runner.runCommand }), | |
| ).rejects.toThrow("must use exact package"); | |
| expect(runner.calls).toHaveLength(0); | |
| }); | |
| it("rejects an explicitly supplied empty host key instead of falling back", async () => { | |
| const runner = fakeRunner([]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE, pinnedHostKey: "" }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("OpenSSH public-key format"); | |
| expect(runner.calls).toHaveLength(0); | |
| }); | |
| it("materializes inline identity data privately and removes it after use", async () => { | |
| let identityPath = ""; | |
| let identityContents = ""; | |
| let identityMode = 0; | |
| const runCommand: WorkerBootstrapCommandRunner = async (argv) => { | |
| const identityIndex = argv.indexOf("-i"); | |
| identityPath = argv[identityIndex + 1] ?? ""; | |
| identityContents = await fs.readFile(identityPath, "utf8"); | |
| identityMode = (await fs.stat(identityPath)).mode & 0o777; | |
| return result({ stdout: tagged("current", RECEIPT_JSON) }); | |
| }; | |
| await bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { | |
| resolveIdentity: async () => ({ | |
| kind: "material", | |
| contents: "fake-key-start\\nkey-data\\r\\nfake-key-end", | |
| }), | |
| runCommand, | |
| }, | |
| ); | |
| expect(identityContents).toBe("fake-key-start\nkey-data\nfake-key-end\n"); | |
| if (process.platform !== "win32") { | |
| expect(identityMode).toBe(0o600); | |
| } | |
| await expect(fs.stat(identityPath)).rejects.toMatchObject({ code: "ENOENT" }); | |
| }); | |
| it("rejects a stale remote receipt instead of synthesizing the expected fields", async () => { | |
| const staleReceipt = JSON.stringify({ | |
| bundleHash: BUNDLE_HASH, | |
| openclawVersion: "2026.7.10", | |
| protocolFeatures: ["admission-v1"], | |
| }); | |
| const runner = fakeRunner([result({ stdout: tagged("current", staleReceipt) }), result()]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("receipt does not match"); | |
| expect(runner.calls).toHaveLength(2); | |
| }); | |
| it.each([ | |
| { | |
| phase: "bundle transfer", | |
| phaseCommand: "scp", | |
| responses: [ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result({ code: 1, stderr: "transfer rejected" }), | |
| result({ code: 255, stderr: "selected port changed" }), | |
| result(), | |
| ], | |
| commands: ["ssh", "scp", "ssh", "ssh"], | |
| cleanupPorts: [2222, 22], | |
| }, | |
| { | |
| phase: "install", | |
| phaseCommand: "install", | |
| responses: [ | |
| result({ stdout: tagged("install", REMOTE_TARBALL) }), | |
| result(), | |
| result({ code: 1, stderr: "install rejected" }), | |
| result(), | |
| ], | |
| commands: ["ssh", "scp", "ssh", "ssh"], | |
| cleanupPorts: [2222], | |
| }, | |
| ])( | |
| "does not retry fallback after a non-255 $phase failure and cleans up the upload", | |
| async ({ phase, phaseCommand, responses, commands, cleanupPorts }) => { | |
| const runner = fakeRunner(responses); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: { ...SSH, fallbackPorts: [22] }, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow(`Worker bootstrap ${phase} failed`); | |
| expect(runner.calls.map((call) => call.argv[0])).toEqual(commands); | |
| const phaseCalls = runner.calls.filter((call) => | |
| phaseCommand === "scp" | |
| ? call.argv[0] === "scp" | |
| : typeof call.options.input === "string" && | |
| call.options.input.includes("receipt_json=$5"), | |
| ); | |
| expect(phaseCalls).toHaveLength(1); | |
| expect(commandPort(phaseCalls[0]!.argv)).toBe(2222); | |
| const cleanupCalls = runner.calls.filter( | |
| (call) => | |
| typeof call.options.input === "string" && | |
| call.options.input.includes("operation_token=$2"), | |
| ); | |
| expect(cleanupCalls.map((call) => commandPort(call.argv))).toEqual(cleanupPorts); | |
| expect(cleanupCalls.every((call) => call.argv.at(-1)?.includes(BUNDLE_HASH))).toBe(true); | |
| expect(cleanupCalls.every((call) => call.argv.at(-1)?.includes(OPERATION_TOKEN))).toBe(true); | |
| expect(cleanupCalls.every((call) => call.options.signal === undefined)).toBe(true); | |
| }, | |
| ); | |
| it("keeps bootstrap failure details on a valid UTF-16 boundary", async () => { | |
| const prefix = "e".repeat(511); | |
| const runner = fakeRunner([result({ code: 1, stderr: `${prefix}😀 tail` }), result()]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: BUNDLE }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow(`Worker bootstrap preflight failed (exit 1): ${prefix}`); | |
| }); | |
| it("rejects unpinned artifact digests before opening SSH", async () => { | |
| const runner = fakeRunner([]); | |
| await expect( | |
| bootstrapWorker( | |
| { ssh: SSH, artifact: { ...BUNDLE, tarballSha256: "invalid" } }, | |
| { resolveIdentity, runCommand: runner.runCommand }, | |
| ), | |
| ).rejects.toThrow("archive digest"); | |
| expect(runner.calls).toHaveLength(0); | |
| }); | |
| it.skipIf(process.platform === "win32")( | |
| "reuses and finally cleans the operation upload across ambiguous candidate attempts", | |
| async () => { | |
| await withTestDir({ prefix: "openclaw-worker-bootstrap-script-" }, async (root) => { | |
| const packageRoot = path.join(root, "package"); | |
| const remoteHome = path.join(root, "remote-home"); | |
| await fs.mkdir(path.join(packageRoot, "dist", "worker"), { recursive: true }); | |
| await fs.writeFile( | |
| path.join(packageRoot, "package.json"), | |
| `${JSON.stringify({ name: "openclaw", version: VERSION, files: ["dist/"] })}\n`, | |
| ); | |
| for (const artifact of [ | |
| "github-exec-launcher.mjs", | |
| "worker.mjs", | |
| "workspace-rsync-receiver.mjs", | |
| ]) { | |
| await fs.writeFile(path.join(packageRoot, "dist/worker", artifact), "export {};\n", { | |
| mode: 0o755, | |
| }); | |
| } | |
| const artifact = await createWorkerBundleProducer({ | |
| packageRoot, | |
| cacheDir: path.join(root, "cache"), | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| }).prepare(); | |
| const fakeBin = path.join(root, "fake-bin"); | |
| await fs.mkdir(fakeBin); | |
| await fs.writeFile(path.join(fakeBin, "tar"), "#!/bin/sh\nexit 255\n", { mode: 0o755 }); | |
| const receiptJson = JSON.stringify({ | |
| bundleHash: artifact.bundleHash, | |
| openclawVersion: VERSION, | |
| protocolFeatures: ["admission-v1"], | |
| }); | |
| const staleStaging = path.join( | |
| remoteHome, | |
| ".openclaw-worker", | |
| `.staging-${artifact.bundleHash}-99999`, | |
| ); | |
| await fs.mkdir(staleStaging, { recursive: true }); | |
| await fs.writeFile(path.join(staleStaging, "partial"), "abandoned install"); | |
| const staleLock = path.join(remoteHome, ".openclaw-worker", ".locks", artifact.bundleHash); | |
| await fs.mkdir(path.dirname(staleLock), { recursive: true }); | |
| // A reused live PID must not keep a crashed install locked forever. | |
| await fs.symlink(`${process.pid}:1`, staleLock); | |
| let remoteTarball = ""; | |
| let transfers = 0; | |
| let syntheticPreflightFailures = 2; | |
| let installAttempts = 0; | |
| let uploadSurvivedAmbiguousInstall = false; | |
| let cleanupAttempts = 0; | |
| const preflightPaths: string[] = []; | |
| const runCommand: WorkerBootstrapCommandRunner = async (argv, options) => { | |
| if (argv[0] === "scp") { | |
| transfers += 1; | |
| const destination = argv.at(-1) ?? ""; | |
| remoteTarball = destination.slice(destination.indexOf(":") + 1); | |
| await fs.copyFile(artifact.tarballPath, remoteTarball); | |
| return result(); | |
| } | |
| const isPreflight = | |
| typeof options.input === "string" && options.input.includes("expected_receipt=$2"); | |
| const isInstall = | |
| typeof options.input === "string" && options.input.includes("receipt_json=$5"); | |
| const isCleanup = | |
| typeof options.input === "string" && options.input.includes("operation_token=$2"); | |
| if (isCleanup) { | |
| cleanupAttempts += 1; | |
| } | |
| const scriptArgs = isPreflight | |
| ? [artifact.bundleHash, receiptJson, "bundle", OPERATION_TOKEN] | |
| : isInstall | |
| ? [ | |
| "bundle", | |
| artifact.bundleHash, | |
| "", | |
| "", | |
| receiptJson, | |
| remoteTarball, | |
| artifact.tarballSha256, | |
| ] | |
| : isCleanup | |
| ? [artifact.bundleHash, OPERATION_TOKEN] | |
| : []; | |
| if (isInstall && installAttempts === 1) { | |
| uploadSurvivedAmbiguousInstall = await fs | |
| .stat(remoteTarball) | |
| .then((stats) => stats.isFile()) | |
| .catch(() => false); | |
| } | |
| const useFailingTar = isInstall && installAttempts === 0; | |
| if (isInstall) { | |
| installAttempts += 1; | |
| } | |
| const shellResult = await runCommandWithTimeout(["sh", "-s", "--", ...scriptArgs], { | |
| ...options, | |
| baseEnv: { | |
| ...options.baseEnv, | |
| HOME: remoteHome, | |
| PATH: useFailingTar | |
| ? `${fakeBin}:${options.baseEnv?.PATH ?? ""}` | |
| : options.baseEnv?.PATH, | |
| }, | |
| }); | |
| if (isPreflight) { | |
| const taggedRecord = shellResult.stdout | |
| .split(/\r?\n/u) | |
| .find((line) => line.startsWith(`${OUTPUT_TAG}\tinstall\t`)); | |
| if (taggedRecord) { | |
| preflightPaths.push(taggedRecord.slice(taggedRecord.lastIndexOf("\t") + 1)); | |
| } | |
| if (syntheticPreflightFailures > 0) { | |
| syntheticPreflightFailures -= 1; | |
| return result({ code: 255, stderr: "synthetic ambiguous preflight" }); | |
| } | |
| } | |
| return shellResult; | |
| }; | |
| const bootstrapRequest = { ssh: { ...SSH, fallbackPorts: [22] }, artifact }; | |
| await expect( | |
| bootstrapWorker(bootstrapRequest, { resolveIdentity, runCommand }), | |
| ).rejects.toThrow("Worker bootstrap preflight failed (exit 255)"); | |
| expect(preflightPaths).toHaveLength(2); | |
| await expect(fs.stat(preflightPaths[0]!)).rejects.toMatchObject({ code: "ENOENT" }); | |
| syntheticPreflightFailures = 1; | |
| await expect( | |
| bootstrapWorker(bootstrapRequest, { resolveIdentity, runCommand }), | |
| ).resolves.toEqual(JSON.parse(receiptJson)); | |
| const tamperedDependency = path.join( | |
| remoteHome, | |
| ".openclaw-worker", | |
| artifact.bundleHash, | |
| "node_modules", | |
| "tampered.js", | |
| ); | |
| await fs.mkdir(path.dirname(tamperedDependency), { recursive: true }); | |
| await fs.writeFile(tamperedDependency, "export const trusted = false;\n"); | |
| await expect( | |
| bootstrapWorker(bootstrapRequest, { resolveIdentity, runCommand }), | |
| ).resolves.toEqual(JSON.parse(receiptJson)); | |
| await expect(fs.stat(tamperedDependency)).rejects.toMatchObject({ code: "ENOENT" }); | |
| const operationUpload = preflightPaths[0]!; | |
| const staleUpload = path.join( | |
| path.dirname(operationUpload), | |
| `openclaw-upload-${"c".repeat(64)}.tgz.${"d".repeat(64)}`, | |
| ); | |
| await fs.writeFile(operationUpload, "ambiguous prior upload"); | |
| await fs.writeFile(staleUpload, "stale upload"); | |
| const staleTime = new Date(Date.now() - 61 * 60_000); | |
| await fs.utimes(staleUpload, staleTime, staleTime); | |
| const cleanupAttemptsBeforeCurrent = cleanupAttempts; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact }, { resolveIdentity, runCommand }), | |
| ).resolves.toEqual(JSON.parse(receiptJson)); | |
| expect(cleanupAttempts).toBe(cleanupAttemptsBeforeCurrent); | |
| expect(transfers).toBe(2); | |
| expect(preflightPaths).toHaveLength(5); | |
| expect(new Set(preflightPaths).size).toBe(1); | |
| expect(path.basename(preflightPaths[0]!)).toBe( | |
| `openclaw-upload-${artifact.bundleHash}.tgz.${OPERATION_TOKEN}`, | |
| ); | |
| expect(installAttempts).toBe(3); | |
| expect(uploadSurvivedAmbiguousInstall).toBe(true); | |
| await expect(fs.stat(remoteTarball)).rejects.toMatchObject({ code: "ENOENT" }); | |
| await expect(fs.stat(operationUpload)).rejects.toMatchObject({ code: "ENOENT" }); | |
| await expect(fs.stat(staleUpload)).rejects.toMatchObject({ code: "ENOENT" }); | |
| await expect(fs.stat(staleStaging)).rejects.toMatchObject({ code: "ENOENT" }); | |
| await expect(fs.lstat(staleLock)).rejects.toMatchObject({ code: "ENOENT" }); | |
| await expect( | |
| fs.readFile( | |
| path.join( | |
| remoteHome, | |
| ".openclaw-worker", | |
| artifact.bundleHash, | |
| "bootstrap-receipt.json", | |
| ), | |
| "utf8", | |
| ), | |
| ).resolves.toBe(`${receiptJson}\n`); | |
| }); | |
| }, | |
| ); | |
| it.skipIf(process.platform === "win32")( | |
| "fails closed instead of following a poisoned incoming directory", | |
| async () => { | |
| await withTestDir({ prefix: "openclaw-worker-bootstrap-path-" }, async (root) => { | |
| const remoteHome = path.join(root, "remote-home"); | |
| const unrelated = path.join(root, "unrelated"); | |
| const bootstrapRoot = path.join(remoteHome, ".openclaw-worker"); | |
| await fs.mkdir(bootstrapRoot, { recursive: true }); | |
| await fs.mkdir(unrelated); | |
| await fs.writeFile(path.join(unrelated, "sentinel"), "keep"); | |
| await fs.symlink(unrelated, path.join(bootstrapRoot, ".incoming")); | |
| const runCommand: WorkerBootstrapCommandRunner = async (_argv, options) => { | |
| const isPreflight = | |
| typeof options.input === "string" && options.input.includes("expected_receipt=$2"); | |
| const scriptArgs = isPreflight | |
| ? [BUNDLE_HASH, RECEIPT_JSON, "bundle", OPERATION_TOKEN] | |
| : [BUNDLE_HASH, OPERATION_TOKEN]; | |
| return await runCommandWithTimeout(["sh", "-s", "--", ...scriptArgs], { | |
| ...options, | |
| baseEnv: { ...options.baseEnv, HOME: remoteHome }, | |
| }); | |
| }; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact: BUNDLE }, { resolveIdentity, runCommand }), | |
| ).rejects.toThrow("unsafe worker bootstrap directory"); | |
| await expect(fs.readFile(path.join(unrelated, "sentinel"), "utf8")).resolves.toBe("keep"); | |
| }); | |
| }, | |
| ); | |
| it.skipIf(process.platform === "win32")( | |
| "does not follow a poisoned bootstrap root during terminal cleanup", | |
| async () => { | |
| await withTestDir({ prefix: "openclaw-worker-bootstrap-cleanup-root-" }, async (root) => { | |
| const remoteHome = path.join(root, "remote-home"); | |
| const unrelated = path.join(root, "unrelated"); | |
| const incoming = path.join(unrelated, ".incoming"); | |
| const upload = path.join(incoming, UPLOAD_FILENAME); | |
| await fs.mkdir(remoteHome); | |
| await fs.mkdir(incoming, { recursive: true }); | |
| await fs.writeFile(upload, "keep"); | |
| await fs.symlink(unrelated, path.join(remoteHome, ".openclaw-worker")); | |
| const runCommand: WorkerBootstrapCommandRunner = async (_argv, options) => { | |
| const isPreflight = | |
| typeof options.input === "string" && options.input.includes("expected_receipt=$2"); | |
| const scriptArgs = isPreflight | |
| ? [BUNDLE_HASH, RECEIPT_JSON, "bundle", OPERATION_TOKEN] | |
| : [BUNDLE_HASH, OPERATION_TOKEN]; | |
| return await runCommandWithTimeout(["sh", "-s", "--", ...scriptArgs], { | |
| ...options, | |
| baseEnv: { ...options.baseEnv, HOME: remoteHome }, | |
| }); | |
| }; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact: BUNDLE }, { resolveIdentity, runCommand }), | |
| ).rejects.toThrow("unsafe worker bootstrap directory"); | |
| await expect(fs.readFile(upload, "utf8")).resolves.toBe("keep"); | |
| }); | |
| }, | |
| ); | |
| it.skipIf(process.platform === "win32")( | |
| "verifies npm installs from the dedicated worker artifact", | |
| async () => { | |
| await withTestDir({ prefix: "openclaw-worker-bootstrap-npm-artifact-" }, async (root) => { | |
| const packageRoot = path.join(root, "package"); | |
| const remoteHome = path.join(root, "remote-home"); | |
| await fs.mkdir(path.join(packageRoot, "dist", "worker"), { recursive: true }); | |
| await fs.writeFile( | |
| path.join(packageRoot, "package.json"), | |
| `${JSON.stringify({ name: "openclaw", version: VERSION, files: ["dist/"] })}\n`, | |
| ); | |
| const artifacts = [ | |
| "github-exec-launcher.mjs", | |
| "worker.mjs", | |
| "workspace-rsync-receiver.mjs", | |
| ]; | |
| for (const artifact of artifacts) { | |
| await fs.writeFile(path.join(packageRoot, "dist/worker", artifact), "export {};\n", { | |
| mode: 0o755, | |
| }); | |
| } | |
| const bundle = await createWorkerBundleProducer({ | |
| packageRoot, | |
| cacheDir: path.join(root, "cache"), | |
| openclawVersion: VERSION, | |
| }).prepare(); | |
| const artifact: WorkerInstallationArtifact = { | |
| install: "npm", | |
| bundleHash: bundle.bundleHash, | |
| openclawVersion: VERSION, | |
| protocolFeatures: [], | |
| packageIntegrity: NPM_INTEGRITY, | |
| packageSpec: `openclaw@${VERSION}`, | |
| }; | |
| const receiptJson = JSON.stringify({ | |
| bundleHash: bundle.bundleHash, | |
| openclawVersion: VERSION, | |
| protocolFeatures: [], | |
| }); | |
| const installRoot = path.join(remoteHome, ".openclaw-worker", bundle.bundleHash); | |
| await fs.mkdir(installRoot, { recursive: true }); | |
| for (const artifactName of artifacts) { | |
| await fs.copyFile( | |
| path.join(packageRoot, "dist", "worker", artifactName), | |
| path.join(installRoot, artifactName), | |
| ); | |
| await fs.chmod(path.join(installRoot, artifactName), 0o700); | |
| } | |
| await fs.writeFile(path.join(installRoot, "bootstrap-receipt.json"), `${receiptJson}\n`); | |
| const runCommand: WorkerBootstrapCommandRunner = async (_argv, options) => { | |
| const isPreflight = | |
| typeof options.input === "string" && options.input.includes("expected_receipt=$2"); | |
| const scriptArgs = isPreflight | |
| ? [bundle.bundleHash, receiptJson, "npm", OPERATION_TOKEN] | |
| : [bundle.bundleHash, OPERATION_TOKEN]; | |
| return await runCommandWithTimeout(["sh", "-s", "--", ...scriptArgs], { | |
| ...options, | |
| baseEnv: { ...options.baseEnv, HOME: remoteHome }, | |
| }); | |
| }; | |
| await expect( | |
| bootstrapWorker({ ssh: SSH, artifact }, { resolveIdentity, runCommand }), | |
| ).resolves.toEqual(JSON.parse(receiptJson)); | |
| }); | |
| }, | |
| ); | |
| }); | |