@multiplayer-app/sandbox 7.0.5 → 7.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -173,7 +173,8 @@ pnpm --filter @multiplayer-app/sandbox run cli -- images pull --from s3://<bucke
173
173
 
174
174
  Base images can also be built from scratch with `scripts/setup-vm-images.sh`
175
175
  (QEMU/Firecracker kernel + initramfs) or `scripts/setup-docker-images.sh`
176
- (Docker engine images).
176
+ (Docker engine images). For a step-by-step walkthrough of running `qemu`
177
+ locally on macOS, see [`docs/qemu-local-setup.md`](../../docs/qemu-local-setup.md).
177
178
 
178
179
  To build a complete, portable catalog for all runners, use:
179
180
 
package/dist/cli.js CHANGED
@@ -28,7 +28,7 @@ var import_config5 = require("dotenv/config");
28
28
  var import_commander = require("commander");
29
29
 
30
30
  // package.json
31
- var version = "7.0.5";
31
+ var version = "7.0.6";
32
32
 
33
33
  // src/config.ts
34
34
  var import_os2 = __toESM(require("os"));
@@ -620,6 +620,7 @@ var MockExecutor = class {
620
620
 
621
621
  // src/executor/qemu-executor.ts
622
622
  var import_child_process2 = require("child_process");
623
+ var import_http2 = __toESM(require("http"));
623
624
  var import_net = __toESM(require("net"));
624
625
  var import_path4 = __toESM(require("path"));
625
626
  var import_promises3 = __toESM(require("fs/promises"));
@@ -655,8 +656,8 @@ var QemuExecutor = class {
655
656
  this.usedSerialPorts = /* @__PURE__ */ new Set();
656
657
  this.bin = config.bin ?? "qemu-system-aarch64";
657
658
  this.vmDataPath = config.vmDataPath ?? import_path4.default.join(import_os6.default.tmpdir(), "qemu-vms");
658
- this.httpPortStart = config.httpPortRangeStart ?? 9e3;
659
- this.httpPortEnd = config.httpPortRangeEnd ?? 9099;
659
+ this.httpPortStart = config.portRangeStart ?? 9e3;
660
+ this.httpPortEnd = config.portRangeEnd ?? 9099;
660
661
  this.vncBasePort = config.vncBasePort ?? 5900;
661
662
  this.serialBasePort = config.serialBasePort ?? 8e3;
662
663
  this.accel = config.accel ?? "hvf";
@@ -808,6 +809,7 @@ var QemuExecutor = class {
808
809
  this.vms.set(spec.vmId, vm);
809
810
  if (runMode === "job") vm.serialBuffer = connectSerialBuffer(serialPort);
810
811
  await waitForQmp(qmpSocketPath);
812
+ if (runMode !== "job") await waitForHttp("127.0.0.1", hostPort);
811
813
  src_default.info({ vmId: spec.vmId, hostPort, vncPort, serialPort, runMode }, "qemu vm ready");
812
814
  return handle;
813
815
  }
@@ -957,9 +959,9 @@ var QemuExecutor = class {
957
959
  async proxyHttp(vmId, request) {
958
960
  const vm = this.vms.get(vmId);
959
961
  if (!vm?.handle.running) throw new Error(`vm ${vmId} is not running`);
960
- const { default: http8 } = await import("http");
962
+ const { default: http9 } = await import("http");
961
963
  return new Promise((resolve, reject) => {
962
- const req = http8.request(
964
+ const req = http9.request(
963
965
  {
964
966
  host: "127.0.0.1",
965
967
  port: vm.hostPort,
@@ -1033,6 +1035,26 @@ function waitForQmp(socketPath, timeoutMs = 3e4) {
1033
1035
  attempt();
1034
1036
  });
1035
1037
  }
1038
+ function waitForHttp(host, port, timeoutMs = 6e4) {
1039
+ return new Promise((resolve, reject) => {
1040
+ const deadline = Date.now() + timeoutMs;
1041
+ const attempt = () => {
1042
+ if (Date.now() > deadline)
1043
+ return reject(new Error(`timed out waiting for HTTP at ${host}:${port}`));
1044
+ const req = import_http2.default.get(`http://${host}:${port}/health`, (res) => {
1045
+ if (res.statusCode && res.statusCode < 500) return resolve();
1046
+ res.resume();
1047
+ setTimeout(attempt, 1e3);
1048
+ });
1049
+ req.on("error", () => setTimeout(attempt, 1e3));
1050
+ req.setTimeout(2e3, () => {
1051
+ req.destroy();
1052
+ setTimeout(attempt, 1e3);
1053
+ });
1054
+ };
1055
+ attempt();
1056
+ });
1057
+ }
1036
1058
  async function sendQmpCommand(socketPath, execute) {
1037
1059
  return new Promise((resolve, reject) => {
1038
1060
  const sock = import_net.default.createConnection(socketPath);
@@ -1078,7 +1100,7 @@ async function sendQmpCommand(socketPath, execute) {
1078
1100
  var import_child_process3 = require("child_process");
1079
1101
  var import_child_process4 = require("child_process");
1080
1102
  var import_util = require("util");
1081
- var import_http2 = __toESM(require("http"));
1103
+ var import_http3 = __toESM(require("http"));
1082
1104
  var import_net2 = __toESM(require("net"));
1083
1105
  var import_path5 = __toESM(require("path"));
1084
1106
  var import_promises4 = __toESM(require("fs/promises"));
@@ -1215,7 +1237,7 @@ var FirecrackerExecutor = class {
1215
1237
  host_dev_name: tapName
1216
1238
  });
1217
1239
  await fcRequest(apiSocketPath, "PUT", "/actions", { action_type: "InstanceStart" });
1218
- if (runMode !== "job") await waitForHttp(guestIp, 8080);
1240
+ if (runMode !== "job") await waitForHttp2(guestIp, 8080);
1219
1241
  const metadata = { apiSocketPath, tapName, subnet, guestIp, pid: proc.pid };
1220
1242
  const handle = {
1221
1243
  vmId: spec.vmId,
@@ -1378,7 +1400,7 @@ var FirecrackerExecutor = class {
1378
1400
  const vm = this.vms.get(vmId);
1379
1401
  if (!vm?.handle.running) throw new Error(`vm ${vmId} is not running`);
1380
1402
  return new Promise((resolve, reject) => {
1381
- const req = import_http2.default.request(
1403
+ const req = import_http3.default.request(
1382
1404
  {
1383
1405
  host: vm.guestIp,
1384
1406
  port: 8080,
@@ -1424,7 +1446,7 @@ var FirecrackerExecutor = class {
1424
1446
  function fcRequest(socketPath, method, urlPath, body) {
1425
1447
  return new Promise((resolve, reject) => {
1426
1448
  const payload = JSON.stringify(body);
1427
- const req = import_http2.default.request(
1449
+ const req = import_http3.default.request(
1428
1450
  {
1429
1451
  socketPath,
1430
1452
  method,
@@ -1473,13 +1495,13 @@ function waitForSocket(socketPath, timeoutMs = 1e4) {
1473
1495
  attempt();
1474
1496
  });
1475
1497
  }
1476
- function waitForHttp(host, port, timeoutMs = 6e4) {
1498
+ function waitForHttp2(host, port, timeoutMs = 6e4) {
1477
1499
  return new Promise((resolve, reject) => {
1478
1500
  const deadline = Date.now() + timeoutMs;
1479
1501
  const attempt = () => {
1480
1502
  if (Date.now() > deadline)
1481
1503
  return reject(new Error(`timed out waiting for HTTP at ${host}:${port}`));
1482
- const req = import_http2.default.get(`http://${host}:${port}/health`, (res) => {
1504
+ const req = import_http3.default.get(`http://${host}:${port}/health`, (res) => {
1483
1505
  if (res.statusCode && res.statusCode < 500) return resolve();
1484
1506
  res.resume();
1485
1507
  setTimeout(attempt, 1e3);
@@ -1497,7 +1519,7 @@ function waitForHttp(host, port, timeoutMs = 6e4) {
1497
1519
  // src/executor/docker-executor.ts
1498
1520
  var import_child_process5 = require("child_process");
1499
1521
  var import_util2 = require("util");
1500
- var import_http3 = __toESM(require("http"));
1522
+ var import_http4 = __toESM(require("http"));
1501
1523
  var DEFAULT_EXEC_TIMEOUT_MS2 = 6e4;
1502
1524
  var MAX_EXEC_TIMEOUT_MS2 = 3e5;
1503
1525
  var MAX_OUTPUT_BYTES4 = 1024 * 1024;
@@ -1789,7 +1811,7 @@ var DockerExecutor = class {
1789
1811
  const vm = this.mustGet(vmId);
1790
1812
  if (!vm.handle.running) throw new Error(`vm ${vmId} is not running`);
1791
1813
  return new Promise((resolve, reject) => {
1792
- const req = import_http3.default.request(
1814
+ const req = import_http4.default.request(
1793
1815
  {
1794
1816
  host: "127.0.0.1",
1795
1817
  port: vm.hostPort,
@@ -1907,7 +1929,7 @@ var DockerExecutor = class {
1907
1929
  const deadline = Date.now() + timeoutMs;
1908
1930
  for (; ; ) {
1909
1931
  const ok = await new Promise((resolve) => {
1910
- const req = import_http3.default.get({ host: "127.0.0.1", port, path: "/", timeout: 2e3 }, (res) => {
1932
+ const req = import_http4.default.get({ host: "127.0.0.1", port, path: "/", timeout: 2e3 }, (res) => {
1911
1933
  res.resume();
1912
1934
  resolve(true);
1913
1935
  });
@@ -1955,7 +1977,7 @@ var import_util3 = require("util");
1955
1977
  var import_promises5 = __toESM(require("fs/promises"));
1956
1978
  var import_os8 = __toESM(require("os"));
1957
1979
  var import_path6 = __toESM(require("path"));
1958
- var import_http4 = __toESM(require("http"));
1980
+ var import_http5 = __toESM(require("http"));
1959
1981
  var DEFAULT_EXEC_TIMEOUT_MS3 = 6e4;
1960
1982
  var MAX_EXEC_TIMEOUT_MS3 = 3e5;
1961
1983
  var MAX_OUTPUT_BYTES5 = 1024 * 1024;
@@ -2263,7 +2285,7 @@ var DockerSandboxExecutor = class {
2263
2285
  const vm = this.mustGet(vmId);
2264
2286
  if (!vm.handle.running) throw new Error(`vm ${vmId} is not running`);
2265
2287
  return new Promise((resolve, reject) => {
2266
- const req = import_http4.default.request(
2288
+ const req = import_http5.default.request(
2267
2289
  {
2268
2290
  host: "127.0.0.1",
2269
2291
  port: vm.hostPort,
@@ -2372,7 +2394,7 @@ var DockerSandboxExecutor = class {
2372
2394
  }
2373
2395
  async probeAgent(port) {
2374
2396
  return new Promise((resolve) => {
2375
- const req = import_http4.default.get({ host: "127.0.0.1", port, path: "/", timeout: 2e3 }, (res) => {
2397
+ const req = import_http5.default.get({ host: "127.0.0.1", port, path: "/", timeout: 2e3 }, (res) => {
2376
2398
  res.resume();
2377
2399
  resolve(true);
2378
2400
  });
@@ -2395,7 +2417,7 @@ var DockerSandboxExecutor = class {
2395
2417
  };
2396
2418
 
2397
2419
  // src/executor/remote-executor.ts
2398
- var import_http5 = __toESM(require("http"));
2420
+ var import_http6 = __toESM(require("http"));
2399
2421
  var import_https = __toESM(require("https"));
2400
2422
  var JOB_POLL_INTERVAL_MS = 3e3;
2401
2423
  var RemoteExecutor = class {
@@ -2501,7 +2523,7 @@ var RemoteExecutor = class {
2501
2523
  }
2502
2524
  request(method, path8, body) {
2503
2525
  const url = new URL(this.base + path8);
2504
- const lib = url.protocol === "https:" ? import_https.default : import_http5.default;
2526
+ const lib = url.protocol === "https:" ? import_https.default : import_http6.default;
2505
2527
  const payload = body !== void 0 ? JSON.stringify(body) : void 0;
2506
2528
  return new Promise((resolve, reject) => {
2507
2529
  const req = lib.request(
@@ -2846,7 +2868,7 @@ var LambdaMicrovmExecutor = class {
2846
2868
  };
2847
2869
 
2848
2870
  // src/executor/fargate-executor.ts
2849
- var import_http6 = __toESM(require("http"));
2871
+ var import_http7 = __toESM(require("http"));
2850
2872
  var import_client_ecs = require("@aws-sdk/client-ecs");
2851
2873
  var JOB_POLL_INTERVAL_MS2 = 5e3;
2852
2874
  var FARGATE_MEM_RANGES = {
@@ -3038,7 +3060,7 @@ var FargateExecutor = class {
3038
3060
  const vm = this.mustGet(vmId);
3039
3061
  if (!vm.handle.running) throw new Error(`vm ${vmId} is not running`);
3040
3062
  return new Promise((resolve, reject) => {
3041
- const req = import_http6.default.request(
3063
+ const req = import_http7.default.request(
3042
3064
  {
3043
3065
  host: vm.ip,
3044
3066
  port: this.config.guestPort ?? 8080,
@@ -4185,9 +4207,9 @@ async function prepareWorkerImages(config, env = process.env) {
4185
4207
  }
4186
4208
 
4187
4209
  // src/health-server.ts
4188
- var import_http7 = __toESM(require("http"));
4210
+ var import_http8 = __toESM(require("http"));
4189
4211
  async function startSandboxHealthServer(options) {
4190
- const server = import_http7.default.createServer((req, res) => {
4212
+ const server = import_http8.default.createServer((req, res) => {
4191
4213
  const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
4192
4214
  if (pathname === `${options.pathPrefix}/healthz`) {
4193
4215
  res.writeHead(200, { "content-type": "application/json" });