@hotcell/daemon 0.1.0
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/dist/agent-check.d.ts +1 -0
- package/dist/agent-check.js +105 -0
- package/dist/agent-check.js.map +1 -0
- package/dist/api/server.d.ts +64 -0
- package/dist/api/server.js +1368 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api/ws.d.ts +14 -0
- package/dist/api/ws.js +214 -0
- package/dist/api/ws.js.map +1 -0
- package/dist/applevz-check.d.ts +1 -0
- package/dist/applevz-check.js +280 -0
- package/dist/applevz-check.js.map +1 -0
- package/dist/backups.d.ts +29 -0
- package/dist/backups.js +76 -0
- package/dist/backups.js.map +1 -0
- package/dist/capacity.d.ts +63 -0
- package/dist/capacity.js +95 -0
- package/dist/capacity.js.map +1 -0
- package/dist/config.d.ts +241 -0
- package/dist/config.js +152 -0
- package/dist/config.js.map +1 -0
- package/dist/cost.d.ts +9 -0
- package/dist/cost.js +19 -0
- package/dist/cost.js.map +1 -0
- package/dist/density-bench.d.ts +1 -0
- package/dist/density-bench.js +97 -0
- package/dist/density-bench.js.map +1 -0
- package/dist/driver/agent-driver.d.ts +67 -0
- package/dist/driver/agent-driver.js +197 -0
- package/dist/driver/agent-driver.js.map +1 -0
- package/dist/driver/agent.d.ts +94 -0
- package/dist/driver/agent.js +319 -0
- package/dist/driver/agent.js.map +1 -0
- package/dist/driver/applevz.d.ts +95 -0
- package/dist/driver/applevz.js +527 -0
- package/dist/driver/applevz.js.map +1 -0
- package/dist/driver/container.d.ts +99 -0
- package/dist/driver/container.js +733 -0
- package/dist/driver/container.js.map +1 -0
- package/dist/driver/fc-api.d.ts +85 -0
- package/dist/driver/fc-api.js +173 -0
- package/dist/driver/fc-api.js.map +1 -0
- package/dist/driver/firecracker.d.ts +126 -0
- package/dist/driver/firecracker.js +560 -0
- package/dist/driver/firecracker.js.map +1 -0
- package/dist/driver/index.d.ts +17 -0
- package/dist/driver/index.js +61 -0
- package/dist/driver/index.js.map +1 -0
- package/dist/driver/router.d.ts +82 -0
- package/dist/driver/router.js +159 -0
- package/dist/driver/router.js.map +1 -0
- package/dist/driver/types.d.ts +234 -0
- package/dist/driver/types.js +2 -0
- package/dist/driver/types.js.map +1 -0
- package/dist/driver/unsupported.d.ts +50 -0
- package/dist/driver/unsupported.js +81 -0
- package/dist/driver/unsupported.js.map +1 -0
- package/dist/driver/vz-image.d.ts +35 -0
- package/dist/driver/vz-image.js +75 -0
- package/dist/driver/vz-image.js.map +1 -0
- package/dist/e2e-harness.d.ts +1 -0
- package/dist/e2e-harness.js +99 -0
- package/dist/e2e-harness.js.map +1 -0
- package/dist/egress-check.d.ts +8 -0
- package/dist/egress-check.js +317 -0
- package/dist/egress-check.js.map +1 -0
- package/dist/fc-check.d.ts +1 -0
- package/dist/fc-check.js +113 -0
- package/dist/fc-check.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -0
- package/dist/kernels.d.ts +22 -0
- package/dist/kernels.js +134 -0
- package/dist/kernels.js.map +1 -0
- package/dist/lifecycle.d.ts +49 -0
- package/dist/lifecycle.js +111 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +67 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +46 -0
- package/dist/metrics.js +92 -0
- package/dist/metrics.js.map +1 -0
- package/dist/net/firewall.d.ts +4 -0
- package/dist/net/firewall.js +80 -0
- package/dist/net/firewall.js.map +1 -0
- package/dist/pricing.d.ts +36 -0
- package/dist/pricing.js +81 -0
- package/dist/pricing.js.map +1 -0
- package/dist/proxy/allowlist-default.d.ts +23 -0
- package/dist/proxy/allowlist-default.js +53 -0
- package/dist/proxy/allowlist-default.js.map +1 -0
- package/dist/proxy/allowlist.d.ts +43 -0
- package/dist/proxy/allowlist.js +87 -0
- package/dist/proxy/allowlist.js.map +1 -0
- package/dist/proxy/egress.d.ts +49 -0
- package/dist/proxy/egress.js +599 -0
- package/dist/proxy/egress.js.map +1 -0
- package/dist/proxy/ratelimit.d.ts +49 -0
- package/dist/proxy/ratelimit.js +74 -0
- package/dist/proxy/ratelimit.js.map +1 -0
- package/dist/proxy/server.d.ts +26 -0
- package/dist/proxy/server.js +202 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/router-check.d.ts +1 -0
- package/dist/router-check.js +97 -0
- package/dist/router-check.js.map +1 -0
- package/dist/smoke-fc.d.ts +1 -0
- package/dist/smoke-fc.js +183 -0
- package/dist/smoke-fc.js.map +1 -0
- package/dist/smoke-microvm.d.ts +1 -0
- package/dist/smoke-microvm.js +187 -0
- package/dist/smoke-microvm.js.map +1 -0
- package/dist/smoke-remote.d.ts +20 -0
- package/dist/smoke-remote.js +155 -0
- package/dist/smoke-remote.js.map +1 -0
- package/dist/smoke-vz.d.ts +1 -0
- package/dist/smoke-vz.js +161 -0
- package/dist/smoke-vz.js.map +1 -0
- package/dist/smoke.d.ts +7 -0
- package/dist/smoke.js +962 -0
- package/dist/smoke.js.map +1 -0
- package/dist/store.d.ts +124 -0
- package/dist/store.js +578 -0
- package/dist/store.js.map +1 -0
- package/dist/tracing.d.ts +45 -0
- package/dist/tracing.js +144 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +27 -0
- package/dist/util.js +63 -0
- package/dist/util.js.map +1 -0
- package/dist/warmpool-check.d.ts +1 -0
- package/dist/warmpool-check.js +83 -0
- package/dist/warmpool-check.js.map +1 -0
- package/dist/web/dashboard.d.ts +1 -0
- package/dist/web/dashboard.js +456 -0
- package/dist/web/dashboard.js.map +1 -0
- package/package.json +53 -0
package/dist/smoke-fc.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecracker live-boot smoke (B1–B6). Drives the real `FirecrackerDriver` —
|
|
3
|
+
* which spawns `firecracker`, configures it over its API socket, boots a microVM,
|
|
4
|
+
* and talks to the in-guest `sbx-agent` over vsock — through the core sandbox
|
|
5
|
+
* surface. **Requires a KVM host** (`/dev/kvm` + the `firecracker` binary + a
|
|
6
|
+
* vsock-capable guest kernel); run on the nested-virt box: `npm run smoke:fc`.
|
|
7
|
+
*
|
|
8
|
+
* Env: SBX_FC_KERNEL (guest vmlinux), SBX_FC_BIN (firecracker). Uses a converted
|
|
9
|
+
* OCI image (default `alpine:3.20`) so the rootfs matches the host arch.
|
|
10
|
+
*/
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
13
|
+
import { createServer as createHttpServer } from "node:http";
|
|
14
|
+
import { homedir, tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
17
|
+
import { FirecrackerDriver } from "./driver/firecracker.js";
|
|
18
|
+
async function main() {
|
|
19
|
+
const stateDir = mkdtempSync(join(tmpdir(), "hotcell-fc-smoke-"));
|
|
20
|
+
const image = process.env.SBX_FC_SMOKE_IMAGE ?? "alpine:3.20";
|
|
21
|
+
// Mock egress gateway: guests reach it ONLY via the vsock relay (no NIC).
|
|
22
|
+
const EGRESS_PORT = 47523;
|
|
23
|
+
const gateway = createHttpServer((_req, res) => res.end("fc-egress-pong"));
|
|
24
|
+
await new Promise((r) => gateway.listen(EGRESS_PORT, "127.0.0.1", r));
|
|
25
|
+
const driver = new FirecrackerDriver({
|
|
26
|
+
fcBin: process.env.SBX_FC_BIN ?? "firecracker",
|
|
27
|
+
kernel: process.env.SBX_FC_KERNEL ?? "helpers/hotcell-vz/guest/vmlinux-fc",
|
|
28
|
+
rootfs: "helpers/hotcell-vz/guest/rootfs.img", // prebuilt path (unused for a converted image)
|
|
29
|
+
stateDir,
|
|
30
|
+
diskGb: 2,
|
|
31
|
+
imageCacheDir: process.env.SBX_FC_IMAGE_CACHE ?? join(homedir(), ".sbx", "fc", "images"),
|
|
32
|
+
egressPort: EGRESS_PORT,
|
|
33
|
+
egressHost: "127.0.0.1",
|
|
34
|
+
});
|
|
35
|
+
const id = "fcsmoke01";
|
|
36
|
+
let passed = 0;
|
|
37
|
+
const ok = (l) => {
|
|
38
|
+
passed++;
|
|
39
|
+
console.log(` ✓ ${l}`);
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
await driver.ping();
|
|
43
|
+
ok("ping — /dev/kvm + firecracker present");
|
|
44
|
+
console.error(`[smoke-fc] converting ${image} + booting microVM…`);
|
|
45
|
+
await driver.create({ id, image, env: { SBX_FC_TEST: "fc-ok" }, persist: true });
|
|
46
|
+
ok("create — microVM booted and agent reachable over vsock");
|
|
47
|
+
let out = "";
|
|
48
|
+
const code = await driver.exec(id, "echo hi-from-fc && uname -sm", {}, (e) => {
|
|
49
|
+
if (e.type === "stdout")
|
|
50
|
+
out += e.data;
|
|
51
|
+
});
|
|
52
|
+
assert.equal(code, 0, "exec exit code");
|
|
53
|
+
assert.match(out, /hi-from-fc/, "exec stdout");
|
|
54
|
+
assert.match(out, /Linux x86_64/, "running inside a real x86_64 Linux microVM");
|
|
55
|
+
ok(`exec inside the microVM — ${out.trim().replace(/\n/g, " | ")}`);
|
|
56
|
+
let envOut = "";
|
|
57
|
+
await driver.exec(id, "echo $SBX_FC_TEST", {}, (e) => {
|
|
58
|
+
if (e.type === "stdout")
|
|
59
|
+
envOut += e.data;
|
|
60
|
+
});
|
|
61
|
+
assert.match(envOut, /fc-ok/, "sandbox env applied");
|
|
62
|
+
ok("sandbox env applied to exec");
|
|
63
|
+
// Egress-over-vsock: the NIC-less guest reaches the (mock) gateway through
|
|
64
|
+
// its loopback relay — its only route out of the VM.
|
|
65
|
+
let egOut = "";
|
|
66
|
+
const egCode = await driver.exec(id, `wget -qO- http://127.0.0.1:${EGRESS_PORT}/ping`, {}, (e) => {
|
|
67
|
+
if (e.type === "stdout")
|
|
68
|
+
egOut += e.data;
|
|
69
|
+
});
|
|
70
|
+
assert.equal(egCode, 0, "in-guest wget via the egress relay");
|
|
71
|
+
assert.match(egOut, /fc-egress-pong/, "egress relay reached the gateway");
|
|
72
|
+
ok("★ egress-over-vsock: guest loopback → vsock → gateway (no NIC)");
|
|
73
|
+
await driver.writeFile(id, { path: "/workspace/persist.txt", content: "fc-persist-123" });
|
|
74
|
+
assert.equal(await driver.readFile(id, { path: "/workspace/persist.txt" }), "fc-persist-123");
|
|
75
|
+
const files = await driver.listFiles(id, { path: "/workspace" });
|
|
76
|
+
assert.ok(files.some((f) => f.name === "persist.txt"), "listFiles sees the file");
|
|
77
|
+
ok("files: write / read / list in /workspace");
|
|
78
|
+
// Background process + liveness.
|
|
79
|
+
const proc = await driver.startProcess(id, "p1", "i=0; while true; do echo t-$i; i=$((i+1)); sleep 0.2; done", {});
|
|
80
|
+
await sleep(700);
|
|
81
|
+
const live = await driver.listProcesses(id, [{ procId: "p1", pid: proc.pid }]);
|
|
82
|
+
assert.ok(live[0]?.running, "process running");
|
|
83
|
+
await driver.killProcess(id, proc.pid);
|
|
84
|
+
ok("background process: start → running → kill");
|
|
85
|
+
// Stats from the guest /proc.
|
|
86
|
+
const stats = await driver.stats(id);
|
|
87
|
+
assert.ok(stats.memBytes > 0 && stats.onlineCpus >= 1, "stats reports mem + cpus");
|
|
88
|
+
ok(`stats: ${stats.onlineCpus} cpu / ${(stats.memBytes / 1e6).toFixed(0)}MB / ${stats.pids} pids`);
|
|
89
|
+
// Fast-pause → resume (B7): full snapshot to disk, restore WITHOUT a kernel
|
|
90
|
+
// boot. In-RAM state must survive: a tmpfs marker + a live background process.
|
|
91
|
+
console.error("[smoke-fc] snapshot → resume (fast-pause)…");
|
|
92
|
+
const markerCode = await driver.exec(id, "grep -qE '^tmpfs /tmp ' /proc/mounts && echo ram-1 > /tmp/ram-marker", {}, () => { });
|
|
93
|
+
assert.equal(markerCode, 0, "/tmp is tmpfs (marker is genuinely RAM-only)");
|
|
94
|
+
const survivor = await driver.startProcess(id, "p2", "while true; do sleep 1; done", {});
|
|
95
|
+
const tSave = Date.now();
|
|
96
|
+
await driver.snapshot(id);
|
|
97
|
+
const saveMs = Date.now() - tSave;
|
|
98
|
+
const tResume = Date.now();
|
|
99
|
+
await driver.start({ id, image, env: {}, persist: true });
|
|
100
|
+
const resumeMs = Date.now() - tResume;
|
|
101
|
+
assert.match(await driver.readFile(id, { path: "/tmp/ram-marker" }), /ram-1/, "tmpfs marker survived snapshot→resume (guest RAM restored)");
|
|
102
|
+
const alive = await driver.listProcesses(id, [{ procId: "p2", pid: survivor.pid }]);
|
|
103
|
+
assert.ok(alive[0]?.running, "background process survived snapshot→resume");
|
|
104
|
+
await driver.killProcess(id, survivor.pid);
|
|
105
|
+
// The egress relay survives the cycle: the in-guest listener lives in
|
|
106
|
+
// restored RAM; the host relay is re-installed by the fresh VMM spawn.
|
|
107
|
+
let egResume = "";
|
|
108
|
+
await driver.exec(id, `wget -qO- http://127.0.0.1:${EGRESS_PORT}/after-resume`, {}, (e) => {
|
|
109
|
+
if (e.type === "stdout")
|
|
110
|
+
egResume += e.data;
|
|
111
|
+
});
|
|
112
|
+
assert.match(egResume, /fc-egress-pong/, "egress relay must work after snapshot→resume");
|
|
113
|
+
ok(`★ snapshot fast-pause → resume, no kernel boot (save ${saveMs}ms, resume ${resumeMs}ms); egress relay survives`);
|
|
114
|
+
// Persistence across stop/start (cold path: workspace survives, RAM doesn't).
|
|
115
|
+
console.error("[smoke-fc] stop → start (workspace persistence)…");
|
|
116
|
+
await driver.stop(id);
|
|
117
|
+
const tCold = Date.now();
|
|
118
|
+
await driver.start({ id, image, env: {}, persist: true });
|
|
119
|
+
const coldMs = Date.now() - tCold;
|
|
120
|
+
assert.equal(await driver.readFile(id, { path: "/workspace/persist.txt" }), "fc-persist-123", "workspace persisted across stop/start");
|
|
121
|
+
const markerGone = await driver
|
|
122
|
+
.readFile(id, { path: "/tmp/ram-marker" })
|
|
123
|
+
.then(() => false)
|
|
124
|
+
.catch(() => true);
|
|
125
|
+
assert.ok(markerGone, "tmpfs marker cleared by a cold stop/start (RAM not persisted)");
|
|
126
|
+
ok(`★ workspace.img persists across stop/start (cold boot ${coldMs}ms vs resume ${resumeMs}ms)`);
|
|
127
|
+
await driver.destroy(id);
|
|
128
|
+
ok("destroy");
|
|
129
|
+
// Warm pool (B7): a pre-booted spare is adopted near-instantly, and a
|
|
130
|
+
// snapshot → resume of the ADOPTED VM still works — the no-move symlink
|
|
131
|
+
// adoption keeps Firecracker's recorded boot-time paths valid.
|
|
132
|
+
console.error("[smoke-fc] warm pool…");
|
|
133
|
+
const poolStateDir = mkdtempSync(join(tmpdir(), "hotcell-fc-pool-"));
|
|
134
|
+
const pooled = new FirecrackerDriver({
|
|
135
|
+
fcBin: process.env.SBX_FC_BIN ?? "firecracker",
|
|
136
|
+
kernel: process.env.SBX_FC_KERNEL ?? "helpers/hotcell-vz/guest/vmlinux-fc",
|
|
137
|
+
rootfs: "helpers/hotcell-vz/guest/rootfs.img",
|
|
138
|
+
stateDir: poolStateDir,
|
|
139
|
+
diskGb: 2,
|
|
140
|
+
imageCacheDir: process.env.SBX_FC_IMAGE_CACHE ?? join(homedir(), ".sbx", "fc", "images"),
|
|
141
|
+
warmPool: 1,
|
|
142
|
+
poolImage: image,
|
|
143
|
+
});
|
|
144
|
+
const wid = "fcwarm01";
|
|
145
|
+
try {
|
|
146
|
+
const deadline = Date.now() + 60000;
|
|
147
|
+
while (pooled.poolSize() < 1 && Date.now() < deadline)
|
|
148
|
+
await sleep(250);
|
|
149
|
+
assert.ok(pooled.poolSize() >= 1, "warm pool failed to pre-boot a spare");
|
|
150
|
+
const tAdopt = Date.now();
|
|
151
|
+
await pooled.create({ id: wid, image, env: {}, persist: true });
|
|
152
|
+
const adoptMs = Date.now() - tAdopt;
|
|
153
|
+
let wout = "";
|
|
154
|
+
await pooled.exec(wid, "echo WARM_OK", {}, (e) => {
|
|
155
|
+
if (e.type === "stdout")
|
|
156
|
+
wout += e.data;
|
|
157
|
+
});
|
|
158
|
+
assert.match(wout, /WARM_OK/, "adopted microVM must serve exec");
|
|
159
|
+
// Cross-feature: the adopted VM must still snapshot → resume with RAM intact.
|
|
160
|
+
await pooled.exec(wid, "echo warm-ram > /tmp/warm-marker", {}, () => { });
|
|
161
|
+
await pooled.snapshot(wid);
|
|
162
|
+
await pooled.start({ id: wid, image, env: {}, persist: true });
|
|
163
|
+
assert.match(await pooled.readFile(wid, { path: "/tmp/warm-marker" }), /warm-ram/, "adopted VM lost RAM across snapshot→resume (symlink adoption broken?)");
|
|
164
|
+
ok(`★ warm pool: adopt ${adoptMs}ms; adopted VM snapshot→resume keeps RAM`);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
await pooled.destroy(wid).catch(() => { });
|
|
168
|
+
await pooled.drainPool().catch(() => { });
|
|
169
|
+
rmSync(poolStateDir, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
console.log(`\nsmoke-fc: ${passed} checks passed (real Firecracker microVM)`);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await driver.stop(id).catch(() => { });
|
|
175
|
+
await new Promise((r) => gateway.close(() => r()));
|
|
176
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
main().then(() => process.exit(0), (err) => {
|
|
180
|
+
console.error("smoke-fc FAILED:", err);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=smoke-fc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke-fc.js","sourceRoot":"","sources":["../src/smoke-fc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MicroVM parity smoke. Boots an in-process daemon backed by a **microVM
|
|
3
|
+
* driver** — Apple VZ (default) or Firecracker via `SBX_SMOKE_DRIVER` — and
|
|
4
|
+
* drives the full sandbox surface through the daemon's HTTP/SSE stack via the
|
|
5
|
+
* public TypeScript SDK — the same flow `npm run smoke` exercises against the
|
|
6
|
+
* container driver. This is the plan's parity gate: the *same* end-to-end flow
|
|
7
|
+
* must work on every driver, proving the driver abstraction holds from
|
|
8
|
+
* REST → SSE → SDK down to a real microVM.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the driver-direct checks (`applevz-check.ts`, `smoke-fc.ts`), this goes
|
|
11
|
+
* through the daemon server, the preview proxy, and the SDK — so it covers the
|
|
12
|
+
* integration those don't.
|
|
13
|
+
* - applevz: needs a Mac with the VZ helper + guest artifacts built
|
|
14
|
+
* (`npm run build:vz`, `build:agent`, `build-guest.sh`). `npm run smoke:vz`.
|
|
15
|
+
* - firecracker: needs a Linux KVM host with `firecracker`, a guest kernel at
|
|
16
|
+
* `helpers/hotcell-vz/guest/vmlinux-fc`, and the amd64 agent staged (see plan
|
|
17
|
+
* Appendix B). `npm run smoke:fc:full`.
|
|
18
|
+
*/
|
|
19
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
20
|
+
import { tmpdir, homedir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { BackupRegistry } from "./backups.js";
|
|
23
|
+
import { Capacity } from "./capacity.js";
|
|
24
|
+
import { loadConfig } from "./config.js";
|
|
25
|
+
import { DriverRouter } from "./driver/router.js";
|
|
26
|
+
import { MetricsHistory } from "./metrics.js";
|
|
27
|
+
import { SandboxStore } from "./store.js";
|
|
28
|
+
import { createApiServer } from "./api/server.js";
|
|
29
|
+
import { createProxyServer } from "./proxy/server.js";
|
|
30
|
+
import { HotcellClient } from "@hotcell/sdk";
|
|
31
|
+
async function main() {
|
|
32
|
+
// Force the microVM driver + isolated ports/state so the run leaves nothing
|
|
33
|
+
// behind and never collides with a dev daemon on the default ports.
|
|
34
|
+
const driverName = process.env.SBX_SMOKE_DRIVER ?? "applevz";
|
|
35
|
+
process.env.SBX_DRIVER = driverName;
|
|
36
|
+
const config = loadConfig();
|
|
37
|
+
config.driver = driverName;
|
|
38
|
+
config.port = 4790;
|
|
39
|
+
config.proxyPort = 4791;
|
|
40
|
+
config.backupDir = await mkdtemp(join(tmpdir(), "hotcell-mvm-backups-"));
|
|
41
|
+
const dbDir = await mkdtemp(join(tmpdir(), "hotcell-mvm-db-"));
|
|
42
|
+
config.dbPath = join(dbDir, "state.db");
|
|
43
|
+
const stateDir = await mkdtemp(join(tmpdir(), "hotcell-mvm-state-"));
|
|
44
|
+
if (driverName === "firecracker") {
|
|
45
|
+
config.fcStateDir = stateDir; // image cache stays at the stable default
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
config.vzStateDir = stateDir;
|
|
49
|
+
config.vzImageCacheDir = join(homedir(), ".sbx", "vz", "images"); // stable cache
|
|
50
|
+
}
|
|
51
|
+
// The prebuilt "base" rootfs sentinel is an arm64 VZ artifact; Firecracker
|
|
52
|
+
// hosts convert an OCI image for the host arch instead (cached after first use).
|
|
53
|
+
const image = process.env.SBX_SMOKE_IMAGE ?? (driverName === "firecracker" ? "alpine:3.20" : "base");
|
|
54
|
+
const store = new SandboxStore(config.dbPath);
|
|
55
|
+
const driver = new DriverRouter(config, store, config.driver);
|
|
56
|
+
const backups = new BackupRegistry(config.backupDir);
|
|
57
|
+
const history = new MetricsHistory(config.metricsHistory);
|
|
58
|
+
const capacity = new Capacity(store, config, null, history);
|
|
59
|
+
const server = createApiServer({ config, driver, store, backups, history, capacity });
|
|
60
|
+
await new Promise((r) => server.listen(config.port, config.host, r));
|
|
61
|
+
const proxy = createProxyServer({ config, driver, store });
|
|
62
|
+
await new Promise((r) => proxy.listen(config.proxyPort, config.proxyHost, r));
|
|
63
|
+
const endpoint = `http://${config.host}:${config.port}`;
|
|
64
|
+
const proxyEndpoint = `http://${config.proxyHost}:${config.proxyPort}`;
|
|
65
|
+
const client = new HotcellClient({ endpoint });
|
|
66
|
+
let passed = 0;
|
|
67
|
+
const ok = (l) => {
|
|
68
|
+
passed++;
|
|
69
|
+
console.error(` ✓ ${l}`);
|
|
70
|
+
};
|
|
71
|
+
const assert = (cond, msg) => {
|
|
72
|
+
if (!cond)
|
|
73
|
+
throw new Error(msg);
|
|
74
|
+
};
|
|
75
|
+
let sandbox;
|
|
76
|
+
try {
|
|
77
|
+
// 1. Health + info: the daemon reports the microVM driver.
|
|
78
|
+
const health = await client.health();
|
|
79
|
+
assert(health.ok, "daemon health not ok");
|
|
80
|
+
const info = await client.info();
|
|
81
|
+
assert(info.driver === driverName, `expected driver=${driverName}, got ${info.driver}`);
|
|
82
|
+
ok(`daemon up on the ${driverName} driver (${endpoint})`);
|
|
83
|
+
// 2. Create a microVM sandbox.
|
|
84
|
+
sandbox = await client.getSandbox(undefined, { image });
|
|
85
|
+
ok(`created microVM sandbox ${sandbox.id} (${image})`);
|
|
86
|
+
// 3. exec streams stdout/exit over SSE and runs in a real Linux VM.
|
|
87
|
+
const uname = await sandbox.exec("echo hi-from-vm && uname -sm");
|
|
88
|
+
assert(uname.exitCode === 0, `exec exit ${uname.exitCode}`);
|
|
89
|
+
assert(/hi-from-vm/.test(uname.stdout), "exec stdout missing");
|
|
90
|
+
assert(/Linux (aarch64|x86_64)/.test(uname.stdout), "not a real Linux VM");
|
|
91
|
+
ok(`exec over SSE → ${uname.stdout.trim().replace(/\n/g, " | ")}`);
|
|
92
|
+
// 4. Files: write/read/mkdir/list through REST.
|
|
93
|
+
await sandbox.writeFile("/workspace/hello.txt", "vz-files");
|
|
94
|
+
assert((await sandbox.readFile("/workspace/hello.txt")) === "vz-files", "readFile mismatch");
|
|
95
|
+
await sandbox.mkdir("/workspace/sub");
|
|
96
|
+
const files = await sandbox.listFiles("/workspace");
|
|
97
|
+
assert(files.some((f) => f.name === "hello.txt"), "listFiles missing file");
|
|
98
|
+
assert(files.some((f) => f.name === "sub" && f.isDirectory), "listFiles missing dir");
|
|
99
|
+
ok("files: write / read / mkdir / list");
|
|
100
|
+
// 5. Sandbox env + session cwd persistence.
|
|
101
|
+
await sandbox.setEnvVars({ SBX_MVM_SMOKE: "env-ok" });
|
|
102
|
+
const envOut = await sandbox.exec("echo $SBX_MVM_SMOKE");
|
|
103
|
+
assert(/env-ok/.test(envOut.stdout), "sandbox env not applied");
|
|
104
|
+
const session = await sandbox.createSession({ cwd: "/workspace/sub" });
|
|
105
|
+
await session.exec("echo cwd-marker > here.txt");
|
|
106
|
+
const cwdRead = await sandbox.readFile("/workspace/sub/here.txt");
|
|
107
|
+
assert(/cwd-marker/.test(cwdRead), "session cwd not honored");
|
|
108
|
+
ok("env applied + session cwd persists");
|
|
109
|
+
// 6. Background process + port readiness + preview proxy fetch.
|
|
110
|
+
const proc = await sandbox.startProcess("while true; do printf 'HTTP/1.0 200 OK\\r\\n\\r\\nmvm-preview-ok' | nc -l -p 9000 2>/dev/null; done");
|
|
111
|
+
assert(Number.isFinite(proc.pid), "startProcess returned no pid");
|
|
112
|
+
const ready = await sandbox.waitForPort(9000, { timeoutMs: 8000 });
|
|
113
|
+
assert(ready, "port 9000 never became ready");
|
|
114
|
+
await sandbox.exposePort(9000);
|
|
115
|
+
const preview = await fetch(`${proxyEndpoint}/_hotcell/${sandbox.id}/9000/`);
|
|
116
|
+
const previewBody = await preview.text();
|
|
117
|
+
assert(/mvm-preview-ok/.test(previewBody), `preview proxy did not serve: ${previewBody.slice(0, 60)}`);
|
|
118
|
+
ok("background process → waitForPort → preview proxy round-trip");
|
|
119
|
+
// 7. watch: open the stream, create a file, see the event. The SDK watch is
|
|
120
|
+
// an async generator; calling return() closes the underlying SSE stream.
|
|
121
|
+
const watchGen = sandbox.watch("/workspace", { intervalMs: 200 });
|
|
122
|
+
let sawWatch = false;
|
|
123
|
+
const watching = (async () => {
|
|
124
|
+
for await (const ev of watchGen) {
|
|
125
|
+
if (/watched\.txt/.test(ev.path)) {
|
|
126
|
+
sawWatch = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
})().catch(() => { });
|
|
131
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
132
|
+
await sandbox.writeFile("/workspace/watched.txt", "x");
|
|
133
|
+
for (let i = 0; i < 30 && !sawWatch; i++)
|
|
134
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
135
|
+
await watchGen.return(undefined); // unblock + close the stream if still pending
|
|
136
|
+
await watching;
|
|
137
|
+
assert(sawWatch, "watch never reported the new file");
|
|
138
|
+
ok("watch streamed a create event");
|
|
139
|
+
// 8. Backup → mutate → restore → rollback.
|
|
140
|
+
await sandbox.writeFile("/workspace/keep.txt", "in-backup");
|
|
141
|
+
const backup = await sandbox.createBackup();
|
|
142
|
+
await sandbox.writeFile("/workspace/keep.txt", "MUTATED");
|
|
143
|
+
await sandbox.restoreBackup(backup.backupId);
|
|
144
|
+
assert((await sandbox.readFile("/workspace/keep.txt")) === "in-backup", "restore did not roll back");
|
|
145
|
+
ok("backup → mutate → restore → rollback");
|
|
146
|
+
// 9. Metrics: the sampler/stats path reports usage through the daemon.
|
|
147
|
+
const metrics = await sandbox.metrics();
|
|
148
|
+
assert(metrics.live ? metrics.live.memBytes > 0 : true, "live metrics memBytes should be > 0");
|
|
149
|
+
ok(`metrics endpoint served (live mem ${metrics.live ? (metrics.live.memBytes / 1e6).toFixed(0) + "MB" : "n/a"})`);
|
|
150
|
+
// 10. Persistence across stop/start (cold resume; workspace survives).
|
|
151
|
+
await sandbox.stop();
|
|
152
|
+
await sandbox.start();
|
|
153
|
+
assert((await sandbox.readFile("/workspace/keep.txt")) === "in-backup", "workspace lost across stop/start");
|
|
154
|
+
ok("workspace persists across stop/start");
|
|
155
|
+
// 11. Manual pause = memory snapshot: a background process AND an exposed
|
|
156
|
+
// server survive, and an inbound preview request transparently WAKES the
|
|
157
|
+
// paused sandbox (what lets the reaper hibernate port-exposing sandboxes).
|
|
158
|
+
const survivor = await sandbox.startProcess("while true; do sleep 1; done");
|
|
159
|
+
await sandbox.startProcess("while true; do printf 'HTTP/1.0 200 OK\\r\\n\\r\\nwake-ok' | nc -l -p 9100 2>/dev/null; done");
|
|
160
|
+
assert(await sandbox.waitForPort(9100, { timeoutMs: 8000 }), "port 9100 never ready");
|
|
161
|
+
await sandbox.exposePort(9100);
|
|
162
|
+
await sandbox.pause();
|
|
163
|
+
const woke = await fetch(`${proxyEndpoint}/_hotcell/${sandbox.id}/9100/`);
|
|
164
|
+
assert(/wake-ok/.test(await woke.text()), "preview request did not wake the paused sandbox");
|
|
165
|
+
const back = await sandbox.exec(`kill -0 ${survivor.pid} 2>/dev/null && echo SURVIVED`);
|
|
166
|
+
assert(/SURVIVED/.test(back.stdout), "background process did not survive pause/resume");
|
|
167
|
+
ok("pause (memory snapshot) → preview request wakes the VM; processes alive");
|
|
168
|
+
console.error(`\n[smoke-microvm] passed — ${passed} checks (full surface on the ${driverName} driver)`);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
console.error(`[smoke-microvm] FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
if (sandbox)
|
|
177
|
+
await sandbox.destroy().catch(() => { });
|
|
178
|
+
await new Promise((r) => proxy.close(() => r()));
|
|
179
|
+
await new Promise((r) => server.close(() => r()));
|
|
180
|
+
store.close();
|
|
181
|
+
await rm(config.backupDir, { recursive: true, force: true }).catch(() => { });
|
|
182
|
+
await rm(dbDir, { recursive: true, force: true }).catch(() => { });
|
|
183
|
+
await rm(stateDir, { recursive: true, force: true }).catch(() => { });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
main().then((code) => process.exit(code));
|
|
187
|
+
//# sourceMappingURL=smoke-microvm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke-microvm.js","sourceRoot":"","sources":["../src/smoke-microvm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote end-to-end smoke test.
|
|
3
|
+
*
|
|
4
|
+
* Unlike `smoke.ts` (which boots an in-process daemon and drives it over raw
|
|
5
|
+
* HTTP), this script drives a *live, already-running* daemon over the network
|
|
6
|
+
* via the public TypeScript SDK — exercising the same surface an external
|
|
7
|
+
* operator would. It proves the egress policy round-trips through the live
|
|
8
|
+
* daemon, that egress env auto-wiring landed, and (optionally) that a real
|
|
9
|
+
* provider call meters cost.
|
|
10
|
+
*
|
|
11
|
+
* Required env:
|
|
12
|
+
* SBX_ENDPOINT daemon base URL, e.g. https://sbx.example.com:4750
|
|
13
|
+
* Optional env:
|
|
14
|
+
* SBX_API_KEY bearer key (when the daemon is started with one)
|
|
15
|
+
* SBX_SMOKE_REAL_PROVIDER provider name (e.g. "openai") to enable a live LLM call
|
|
16
|
+
* SBX_SMOKE_MODEL model id for the real call (default "gpt-4o-mini")
|
|
17
|
+
*
|
|
18
|
+
* Usage: SBX_ENDPOINT=... [SBX_API_KEY=...] node dist/smoke-remote.js
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote end-to-end smoke test.
|
|
3
|
+
*
|
|
4
|
+
* Unlike `smoke.ts` (which boots an in-process daemon and drives it over raw
|
|
5
|
+
* HTTP), this script drives a *live, already-running* daemon over the network
|
|
6
|
+
* via the public TypeScript SDK — exercising the same surface an external
|
|
7
|
+
* operator would. It proves the egress policy round-trips through the live
|
|
8
|
+
* daemon, that egress env auto-wiring landed, and (optionally) that a real
|
|
9
|
+
* provider call meters cost.
|
|
10
|
+
*
|
|
11
|
+
* Required env:
|
|
12
|
+
* SBX_ENDPOINT daemon base URL, e.g. https://sbx.example.com:4750
|
|
13
|
+
* Optional env:
|
|
14
|
+
* SBX_API_KEY bearer key (when the daemon is started with one)
|
|
15
|
+
* SBX_SMOKE_REAL_PROVIDER provider name (e.g. "openai") to enable a live LLM call
|
|
16
|
+
* SBX_SMOKE_MODEL model id for the real call (default "gpt-4o-mini")
|
|
17
|
+
*
|
|
18
|
+
* Usage: SBX_ENDPOINT=... [SBX_API_KEY=...] node dist/smoke-remote.js
|
|
19
|
+
*/
|
|
20
|
+
import { HotcellClient } from "@hotcell/sdk";
|
|
21
|
+
async function main() {
|
|
22
|
+
const endpoint = process.env.SBX_ENDPOINT;
|
|
23
|
+
if (!endpoint) {
|
|
24
|
+
console.error("[smoke-remote] SBX_ENDPOINT is required (the live daemon base URL, e.g. http://127.0.0.1:4750)");
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
const apiKey = process.env.SBX_API_KEY;
|
|
28
|
+
const client = new HotcellClient({ endpoint, apiKey });
|
|
29
|
+
// 1. Health + info: confirm the daemon is reachable and learn its driver +
|
|
30
|
+
// which egress providers it has configured.
|
|
31
|
+
const health = await client.health();
|
|
32
|
+
if (!health.ok)
|
|
33
|
+
throw new Error(`daemon health not ok: ${JSON.stringify(health)}`);
|
|
34
|
+
const info = await client.info();
|
|
35
|
+
console.error(`[smoke-remote] daemon up at ${endpoint} — driver=${info.driver}, ` +
|
|
36
|
+
`egressProviders=[${info.egressProviders.join(", ") || "<none>"}]`);
|
|
37
|
+
let sandbox;
|
|
38
|
+
try {
|
|
39
|
+
// 2. Create a sandbox with a scoped egress policy (no id => provision new).
|
|
40
|
+
// The ttlMs sugar should resolve to a concrete `expiresAt` on the daemon.
|
|
41
|
+
sandbox = await client.getSandbox(undefined, {
|
|
42
|
+
egress: { spendCapUsd: 1, models: ["gpt-4o"], ttlMs: 3600_000 },
|
|
43
|
+
});
|
|
44
|
+
console.error(`[smoke-remote] created sandbox ${sandbox.id} with scoped egress policy`);
|
|
45
|
+
// 3. Assert the policy round-trips through the live daemon: exactly one token
|
|
46
|
+
// is bound to the sandbox, carrying the policy we minted it with.
|
|
47
|
+
const { tokens } = await sandbox.listEgressTokens();
|
|
48
|
+
if (tokens.length !== 1) {
|
|
49
|
+
throw new Error(`expected exactly 1 egress token, got ${tokens.length}`);
|
|
50
|
+
}
|
|
51
|
+
const policy = tokens[0].policy;
|
|
52
|
+
if (policy.spendCapUsd !== 1) {
|
|
53
|
+
throw new Error(`expected policy.spendCapUsd === 1, got ${policy.spendCapUsd}`);
|
|
54
|
+
}
|
|
55
|
+
if (!policy.models || !policy.models.includes("gpt-4o")) {
|
|
56
|
+
throw new Error(`expected policy.models to include "gpt-4o", got ${JSON.stringify(policy.models)}`);
|
|
57
|
+
}
|
|
58
|
+
if (!policy.expiresAt) {
|
|
59
|
+
throw new Error("expected ttlMs sugar to resolve to a concrete policy.expiresAt");
|
|
60
|
+
}
|
|
61
|
+
console.error(`[smoke-remote] egress policy round-trips (spendCapUsd=1, models=[gpt-4o], expiresAt=${policy.expiresAt})`);
|
|
62
|
+
// 4. Egress env auto-wiring: the provider base URL is injected into the
|
|
63
|
+
// sandbox so an LLM SDK routes through the gateway with no real key.
|
|
64
|
+
const baseUrlRes = await sandbox.exec("printenv OPENAI_BASE_URL");
|
|
65
|
+
const baseUrl = baseUrlRes.stdout.trim();
|
|
66
|
+
if (!baseUrl.endsWith("/openai")) {
|
|
67
|
+
throw new Error(`expected OPENAI_BASE_URL to end with "/openai", got "${baseUrl}"`);
|
|
68
|
+
}
|
|
69
|
+
console.error(`[smoke-remote] OPENAI_BASE_URL auto-wired to ${baseUrl}`);
|
|
70
|
+
// HTTP_PROXY is only injected once the operator enables fail-closed egress
|
|
71
|
+
// enforcement, which may not be on yet — treat a missing value as a soft skip.
|
|
72
|
+
const proxyRes = await sandbox.exec("printenv HTTP_PROXY");
|
|
73
|
+
const httpProxy = proxyRes.stdout.trim();
|
|
74
|
+
if (httpProxy) {
|
|
75
|
+
console.error(`[smoke-remote] HTTP_PROXY set to ${httpProxy} (fail-closed networking enabled)`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.error("[smoke-remote] HTTP_PROXY not set (fail-closed networking not enabled)");
|
|
79
|
+
}
|
|
80
|
+
// 5. Allowlisted-registry reachability: report only — allowlist behaviour
|
|
81
|
+
// depends on the deployment's egress config, so do not hard-assert. Uses
|
|
82
|
+
// `pip` (always present in the python image, and proxy-auth-capable so the
|
|
83
|
+
// signal is truthful even under fail-closed enforcement, unlike curl/urllib).
|
|
84
|
+
const reach = await sandbox.exec("pip download --no-deps --progress-bar off --dest /tmp/sbx-reach six >/dev/null 2>&1 && echo OK || echo BLOCKED");
|
|
85
|
+
const reachVerdict = reach.stdout.trim() || "(no output)";
|
|
86
|
+
console.error(`[smoke-remote] allowlisted registry (pypi/pythonhosted) reachability: ${reachVerdict}`);
|
|
87
|
+
// 6. Optional real-provider LLM call, gated on SBX_SMOKE_REAL_PROVIDER. When
|
|
88
|
+
// set, drive a minimal chat-completion from inside the sandbox through the
|
|
89
|
+
// egress gateway (no real key in the sandbox) and assert it meters cost.
|
|
90
|
+
const realProvider = process.env.SBX_SMOKE_REAL_PROVIDER;
|
|
91
|
+
if (realProvider) {
|
|
92
|
+
const model = process.env.SBX_SMOKE_MODEL ?? "gpt-4o-mini";
|
|
93
|
+
console.error(`[smoke-remote] real-provider call enabled (provider=${realProvider}, model=${model})`);
|
|
94
|
+
// Build the request body in JSON (single-quoted in the shell so the inner
|
|
95
|
+
// double quotes survive); read the HTTP status code separately from the body.
|
|
96
|
+
const body = JSON.stringify({
|
|
97
|
+
model,
|
|
98
|
+
messages: [{ role: "user", content: "say hi" }],
|
|
99
|
+
max_tokens: 8,
|
|
100
|
+
});
|
|
101
|
+
const curl = `curl -sS -o /dev/null -w '%{http_code}' --max-time 30 ` +
|
|
102
|
+
`-X POST "$OPENAI_BASE_URL/v1/chat/completions" ` +
|
|
103
|
+
`-H "content-type: application/json" ` +
|
|
104
|
+
`-H "authorization: Bearer $OPENAI_API_KEY" ` +
|
|
105
|
+
`-d '${body}'`;
|
|
106
|
+
const callRes = await sandbox.exec(curl);
|
|
107
|
+
const httpCode = callRes.stdout.trim();
|
|
108
|
+
if (!/^2\d\d$/.test(httpCode)) {
|
|
109
|
+
throw new Error(`real-provider call did not succeed: HTTP ${httpCode || "(none)"} stderr=${callRes.stderr.trim()}`);
|
|
110
|
+
}
|
|
111
|
+
console.error(`[smoke-remote] real-provider call returned HTTP ${httpCode}`);
|
|
112
|
+
// The gateway should have metered the call + its cost.
|
|
113
|
+
const metrics = await sandbox.metrics();
|
|
114
|
+
if (!(metrics.usage.providerCalls >= 1)) {
|
|
115
|
+
throw new Error(`expected usage.providerCalls >= 1, got ${metrics.usage.providerCalls}`);
|
|
116
|
+
}
|
|
117
|
+
if (!(metrics.cost.provider > 0)) {
|
|
118
|
+
throw new Error(`expected cost.provider > 0, got ${metrics.cost.provider}`);
|
|
119
|
+
}
|
|
120
|
+
console.error(`[smoke-remote] metering: providerCalls=${metrics.usage.providerCalls}, ` +
|
|
121
|
+
`cost.provider=${metrics.cost.provider}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.error("[smoke-remote] real-provider call skipped (set SBX_SMOKE_REAL_PROVIDER=openai to enable)");
|
|
125
|
+
}
|
|
126
|
+
// 7. Revoke the egress token, then verify it's gone.
|
|
127
|
+
const tokenToRevoke = tokens[0].token;
|
|
128
|
+
await sandbox.revokeEgressToken(tokenToRevoke);
|
|
129
|
+
const after = await sandbox.listEgressTokens();
|
|
130
|
+
if (after.tokens.some((t) => t.token === tokenToRevoke)) {
|
|
131
|
+
throw new Error("egress token still present after revoke");
|
|
132
|
+
}
|
|
133
|
+
console.error("[smoke-remote] revoked egress token");
|
|
134
|
+
console.error("[smoke-remote] passed");
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
console.error(`[smoke-remote] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
// Always tear the sandbox down, even on assertion failure.
|
|
143
|
+
if (sandbox) {
|
|
144
|
+
try {
|
|
145
|
+
await sandbox.destroy();
|
|
146
|
+
console.error(`[smoke-remote] destroyed sandbox ${sandbox.id}`);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.error(`[smoke-remote] cleanup: destroy failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
main().then((code) => process.exit(code));
|
|
155
|
+
//# sourceMappingURL=smoke-remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke-remote.js","sourceRoot":"","sources":["../src/smoke-remote.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|