Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

## Unreleased

### A bad `COMPUTER_MEMORY_BYTES` refuses to start the supervisor, instead of capping a computer at 512 bytes

`COMPUTER_MEMORY_BYTES=512m` used to parse as `512` via `parseInt`, which Docker accepts as a memory
cap Chromium cannot live in. Empty `COMPUTER_MEMORY_BYTES=` is still unset (no cap). A value that is
not a whole number of bytes now exits before any computer is created.

### The engine socket the supervisor is given can be pointed somewhere else

Compose mounted `/var/run/docker.sock` into the supervisor as a fixed path. That is correct for
Expand Down
27 changes: 27 additions & 0 deletions supervisor/src/computer-memory-bytes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Optional Docker memory cap for each computer, from `COMPUTER_MEMORY_BYTES`.
*
* Unset or empty (compose blank, leftover `.env` line) means no cap. The same empty-string and
* prefix traps as `listenPort`: `Number.parseInt("", 10)` is `NaN` (silently dropped) and
* `Number.parseInt("512m", 10)` is `512` bytes, which Docker accepts and Chromium cannot live in.
*/
export function computerMemoryBytes(
raw: string | undefined,
): { ok: true; bytes: number | undefined } | { ok: false; reason: string } {
const trimmed = raw?.trim();
if (!trimmed) return { ok: true, bytes: undefined };
if (!/^\d+$/.test(trimmed)) {
return {
ok: false,
reason: `COMPUTER_MEMORY_BYTES must be a whole number of bytes (got ${JSON.stringify(raw)}).`,
};
}
const value = Number.parseInt(trimmed, 10);
if (value < 1) {
return {
ok: false,
reason: `COMPUTER_MEMORY_BYTES must be a whole number of bytes (got ${JSON.stringify(raw)}).`,
};
}
return { ok: true, bytes: value };
}
10 changes: 7 additions & 3 deletions supervisor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "./docker";
import { registerEntry } from "./identity";
import { namesFor } from "./names";
import { computerMemoryBytes } from "./computer-memory-bytes";
import { listenPort } from "./listen-port";

/**
Expand Down Expand Up @@ -60,9 +61,12 @@ if (!token) {
const image = process.env.COMPUTER_IMAGE ?? "openbot-agent-computer:latest";
const network = process.env.COMPUTER_NETWORK;
const runtime = process.env.COMPUTER_RUNTIME;
const memoryBytes = process.env.COMPUTER_MEMORY_BYTES
? Number.parseInt(process.env.COMPUTER_MEMORY_BYTES, 10)
: undefined;
const resolvedMemory = computerMemoryBytes(process.env.COMPUTER_MEMORY_BYTES);
if (!resolvedMemory.ok) {
console.error(resolvedMemory.reason);
process.exit(1);
}
const memoryBytes = resolvedMemory.bytes;
const spireSocketVolume = process.env.SPIRE_AGENT_SOCKET_VOLUME;

/**
Expand Down
23 changes: 23 additions & 0 deletions supervisor/tests/computer-memory-bytes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, test } from "bun:test";
import { computerMemoryBytes } from "../src/computer-memory-bytes";

describe("supervisor computer memory bytes", () => {
test("unset and empty string mean no cap", () => {
expect(computerMemoryBytes(undefined)).toEqual({ ok: true, bytes: undefined });
expect(computerMemoryBytes("")).toEqual({ ok: true, bytes: undefined });
expect(computerMemoryBytes(" ")).toEqual({ ok: true, bytes: undefined });
});

test("a whole number of bytes is accepted", () => {
expect(computerMemoryBytes("1073741824")).toEqual({ ok: true, bytes: 1_073_741_824 });
expect(computerMemoryBytes("1")).toEqual({ ok: true, bytes: 1 });
});

test("suffix units and non-digits are refused instead of parseInt prefixes", () => {
expect(computerMemoryBytes("512m").ok).toBe(false);
expect(computerMemoryBytes("1e9").ok).toBe(false);
expect(computerMemoryBytes("0").ok).toBe(false);
expect(computerMemoryBytes("-1").ok).toBe(false);
expect(computerMemoryBytes("1.5").ok).toBe(false);
});
});