@multiplayer-app/sandbox 7.0.4 → 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 +2 -1
- package/dist/cli.js +45 -23
- package/dist/cli.js.map +3 -3
- package/dist/executor/qemu-executor.d.ts +2 -2
- package/dist/executor/qemu-executor.d.ts.map +1 -1
- package/dist/index.js +45 -23
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
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.
|
|
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.
|
|
659
|
-
this.httpPortEnd = config.
|
|
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:
|
|
962
|
+
const { default: http9 } = await import("http");
|
|
961
963
|
return new Promise((resolve, reject) => {
|
|
962
|
-
const req =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 :
|
|
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
|
|
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 =
|
|
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
|
|
4210
|
+
var import_http8 = __toESM(require("http"));
|
|
4189
4211
|
async function startSandboxHealthServer(options) {
|
|
4190
|
-
const server =
|
|
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" });
|