@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/fc-check.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecracker host-agnostic check. Exercises the pieces of the `firecracker`
|
|
3
|
+
* driver that need no `/dev/kvm` — the API-call builders, the vsock CONNECT
|
|
4
|
+
* handshake parser, and the driver's fail-fast `ping`/`hostInfo` — so the
|
|
5
|
+
* Linux-specific code is validated on any host (incl. this Mac / CI). The live
|
|
6
|
+
* boot is gated behind `smoke:fc` on a KVM box. Run: `npm run check:fc`.
|
|
7
|
+
*/
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { buildFcApiCalls, buildSnapshotCreateCall, buildSnapshotLoadCall, FC_DEFAULT_BOOT_ARGS, FC_PAUSE_VM, FC_START_ACTION, parseVsockHandshake, } from "./driver/fc-api.js";
|
|
10
|
+
import { FirecrackerDriver } from "./driver/firecracker.js";
|
|
11
|
+
let passed = 0;
|
|
12
|
+
const ok = (l) => {
|
|
13
|
+
passed++;
|
|
14
|
+
console.log(` ✓ ${l}`);
|
|
15
|
+
};
|
|
16
|
+
async function main() {
|
|
17
|
+
// 1. API calls fully configure a VM in the right order with the right shapes.
|
|
18
|
+
const calls = buildFcApiCalls({
|
|
19
|
+
kernelPath: "/k/vmlinux",
|
|
20
|
+
rootfsPath: "/k/rootfs.img",
|
|
21
|
+
workspacePath: "/k/ws.img",
|
|
22
|
+
vcpus: 2,
|
|
23
|
+
memMib: 512,
|
|
24
|
+
vsockUds: "/k/v.sock",
|
|
25
|
+
});
|
|
26
|
+
assert.deepEqual(calls.map((c) => c.path), ["/boot-source", "/machine-config", "/drives/rootfs", "/drives/workspace", "/vsock"], "API calls cover boot/machine/drives/vsock in order");
|
|
27
|
+
const boot = calls[0].body;
|
|
28
|
+
assert.equal(boot.kernel_image_path, "/k/vmlinux");
|
|
29
|
+
assert.equal(boot.boot_args, FC_DEFAULT_BOOT_ARGS);
|
|
30
|
+
const mc = calls[1].body;
|
|
31
|
+
assert.equal(mc.vcpu_count, 2);
|
|
32
|
+
assert.equal(mc.mem_size_mib, 512);
|
|
33
|
+
// The root device is /dev/vda (ro), the workspace /dev/vdb (rw) — matches the
|
|
34
|
+
// shared guest init that mounts /dev/vdb at /workspace.
|
|
35
|
+
const rootDrive = calls[2].body;
|
|
36
|
+
assert.equal(rootDrive.is_root_device, true);
|
|
37
|
+
assert.equal(rootDrive.is_read_only, true);
|
|
38
|
+
const wsDrive = calls[3].body;
|
|
39
|
+
assert.equal(wsDrive.is_root_device, false);
|
|
40
|
+
assert.equal(wsDrive.is_read_only, false);
|
|
41
|
+
const vsock = calls[4].body;
|
|
42
|
+
assert.equal(vsock.uds_path, "/k/v.sock");
|
|
43
|
+
assert.equal(vsock.guest_cid, 3);
|
|
44
|
+
ok("buildFcApiCalls: correct order, shapes, and vda(ro)/vdb(rw) drive layout");
|
|
45
|
+
assert.equal(FC_START_ACTION.body.action_type, "InstanceStart");
|
|
46
|
+
ok("FC_START_ACTION boots the VM");
|
|
47
|
+
// 1b. Snapshot API calls: pause → create(Full) → load(File mem backend, resume).
|
|
48
|
+
assert.equal(FC_PAUSE_VM.method, "PATCH");
|
|
49
|
+
assert.equal(FC_PAUSE_VM.path, "/vm");
|
|
50
|
+
assert.equal(FC_PAUSE_VM.body.state, "Paused");
|
|
51
|
+
const snapCreate = buildSnapshotCreateCall("/s/x.vmstate", "/s/x.mem");
|
|
52
|
+
assert.equal(snapCreate.path, "/snapshot/create");
|
|
53
|
+
const scBody = snapCreate.body;
|
|
54
|
+
assert.equal(scBody.snapshot_type, "Full");
|
|
55
|
+
assert.equal(scBody.snapshot_path, "/s/x.vmstate");
|
|
56
|
+
assert.equal(scBody.mem_file_path, "/s/x.mem");
|
|
57
|
+
const snapLoad = buildSnapshotLoadCall("/s/x.vmstate", "/s/x.mem");
|
|
58
|
+
assert.equal(snapLoad.path, "/snapshot/load");
|
|
59
|
+
const slBody = snapLoad.body;
|
|
60
|
+
assert.equal(slBody.snapshot_path, "/s/x.vmstate");
|
|
61
|
+
assert.equal(slBody.mem_backend.backend_type, "File");
|
|
62
|
+
assert.equal(slBody.mem_backend.backend_path, "/s/x.mem");
|
|
63
|
+
assert.equal(slBody.resume_vm, true);
|
|
64
|
+
ok("snapshot API calls: PATCH /vm Paused → /snapshot/create(Full) → /snapshot/load(resume)");
|
|
65
|
+
// 2. Custom boot args (e.g. the pidsLimit cmdline) pass through.
|
|
66
|
+
const withPids = buildFcApiCalls({
|
|
67
|
+
kernelPath: "/k",
|
|
68
|
+
rootfsPath: "/r",
|
|
69
|
+
workspacePath: "/w",
|
|
70
|
+
vcpus: 1,
|
|
71
|
+
memMib: 256,
|
|
72
|
+
vsockUds: "/v",
|
|
73
|
+
bootArgs: `${FC_DEFAULT_BOOT_ARGS} hotcell.pids=64 sbx.pids=64`,
|
|
74
|
+
});
|
|
75
|
+
assert.match(withPids[0].body.boot_args, /hotcell\.pids=64/);
|
|
76
|
+
ok("custom boot args (pidsLimit cmdline) flow through");
|
|
77
|
+
// 3. vsock handshake parser: incomplete → null; OK line → split leftover; bad → not ok.
|
|
78
|
+
assert.equal(parseVsockHandshake(Buffer.from("OK 1024")), null, "no newline yet → null");
|
|
79
|
+
const okParse = parseVsockHandshake(Buffer.from("OK 1024\nHELLOBYTES"));
|
|
80
|
+
assert.ok(okParse && okParse.ok, "OK line parses as ok");
|
|
81
|
+
assert.equal(okParse.leftover.toString(), "HELLOBYTES", "post-handshake bytes preserved");
|
|
82
|
+
const badParse = parseVsockHandshake(Buffer.from("CONNECTION RESET\n"));
|
|
83
|
+
assert.ok(badParse && !badParse.ok, "non-OK line parses as not ok");
|
|
84
|
+
ok("vsock CONNECT handshake parser: incomplete / ok+leftover / failure");
|
|
85
|
+
// 4. Driver fail-fast on a non-KVM host (this Mac): ping names /dev/kvm.
|
|
86
|
+
const driver = new FirecrackerDriver({
|
|
87
|
+
fcBin: "firecracker",
|
|
88
|
+
kernel: "/nonexistent/vmlinux",
|
|
89
|
+
rootfs: "helpers/hotcell-vz/guest/rootfs.img",
|
|
90
|
+
stateDir: "/tmp/hotcell-fc-check",
|
|
91
|
+
diskGb: 8,
|
|
92
|
+
imageCacheDir: "/tmp/hotcell-fc-check/images",
|
|
93
|
+
});
|
|
94
|
+
assert.equal(driver.name, "firecracker");
|
|
95
|
+
if (process.platform !== "linux") {
|
|
96
|
+
await assert.rejects(() => driver.ping(), /\/dev\/kvm/, "ping fails fast with a /dev/kvm message off-KVM");
|
|
97
|
+
ok("ping fails fast naming /dev/kvm (host without KVM)");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
ok("ping skipped (on Linux — may have /dev/kvm; live boot is smoke:fc)");
|
|
101
|
+
}
|
|
102
|
+
// 5. hostInfo works anywhere (os fallback when /proc is absent).
|
|
103
|
+
const host = await driver.hostInfo();
|
|
104
|
+
assert.ok(host.memoryMb > 0, "hostInfo reports memory");
|
|
105
|
+
assert.ok(host.cpus > 0, "hostInfo reports cpus");
|
|
106
|
+
ok(`hostInfo: ${host.memoryMb}MB / ${host.cpus} cpus`);
|
|
107
|
+
console.log(`\nfc-check: ${passed} checks passed (host-agnostic; live boot gated on KVM)`);
|
|
108
|
+
}
|
|
109
|
+
main().then(() => process.exit(0), (err) => {
|
|
110
|
+
console.error("fc-check FAILED:", err);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=fc-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fc-check.js","sourceRoot":"","sources":["../src/fc-check.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BackupRegistry } from "./backups.js";
|
|
3
|
+
import { Capacity } from "./capacity.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { DriverRouter } from "./driver/router.js";
|
|
6
|
+
import { startReaper } from "./lifecycle.js";
|
|
7
|
+
import { configureLogger, log } from "./logger.js";
|
|
8
|
+
import { MetricsHistory, startSampler } from "./metrics.js";
|
|
9
|
+
import { configureTracing, stopTracing } from "./tracing.js";
|
|
10
|
+
import { SandboxStore } from "./store.js";
|
|
11
|
+
import { createApiServer } from "./api/server.js";
|
|
12
|
+
import { createProxyServer } from "./proxy/server.js";
|
|
13
|
+
import { buildProviders, createEgressProxy } from "./proxy/egress.js";
|
|
14
|
+
import { loadModelPrices } from "./pricing.js";
|
|
15
|
+
import { ensureEgressFirewall } from "./net/firewall.js";
|
|
16
|
+
async function main() {
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
configureLogger({ level: config.logLevel, format: config.logFormat });
|
|
19
|
+
configureTracing({
|
|
20
|
+
serviceName: "sbd",
|
|
21
|
+
otlpEndpoint: config.otlpEndpoint,
|
|
22
|
+
ringSize: config.traceRing,
|
|
23
|
+
});
|
|
24
|
+
const store = new SandboxStore(config.dbPath);
|
|
25
|
+
// Per-sandbox driver router: dispatches each sandbox's ops to the runtime
|
|
26
|
+
// driver it was created with (container ↔ applevz), defaulting to SBX_DRIVER.
|
|
27
|
+
const driver = new DriverRouter(config, store, config.driver);
|
|
28
|
+
const backups = new BackupRegistry(config.backupDir);
|
|
29
|
+
const history = new MetricsHistory(config.metricsHistory);
|
|
30
|
+
// Fail fast with a friendly message if the selected driver isn't available
|
|
31
|
+
// (Docker not running, or a microVM driver requested without host support).
|
|
32
|
+
try {
|
|
33
|
+
await driver.ping();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
log.error(`driver "${driver.name}" is not available`, { error: String(err) });
|
|
37
|
+
if (driver.name === "container") {
|
|
38
|
+
log.error("start Docker (or colima / Apple 'container') and retry");
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
// Host capacity for the meter + admission control (best-effort detection).
|
|
43
|
+
const host = await driver.hostInfo().catch(() => null);
|
|
44
|
+
const capacity = new Capacity(store, config, host, history);
|
|
45
|
+
if (host) {
|
|
46
|
+
log.info("host capacity", {
|
|
47
|
+
memoryMb: host.memoryMb,
|
|
48
|
+
cpus: host.cpus,
|
|
49
|
+
admission: capacity.enforced ? "enforce" : "off",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const server = createApiServer({ config, driver, store, backups, history, capacity });
|
|
53
|
+
server.listen(config.port, config.host, () => {
|
|
54
|
+
log.info("daemon listening", {
|
|
55
|
+
url: `http://${config.host}:${config.port}`,
|
|
56
|
+
driver: driver.name,
|
|
57
|
+
image: config.defaultImage,
|
|
58
|
+
auth: config.apiKey ? "on" : "off",
|
|
59
|
+
otlp: config.otlpEndpoint || "off",
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
const proxy = createProxyServer({ config, driver, store });
|
|
63
|
+
proxy.listen(config.proxyPort, config.proxyHost, () => {
|
|
64
|
+
log.info("preview proxy listening", {
|
|
65
|
+
url: `http://${config.proxyHost}:${config.proxyPort}`,
|
|
66
|
+
previews: `http://<id>-<port>.localhost:${config.proxyPort}/`,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// Egress credential proxy (LLM gateway): injects provider keys into outbound
|
|
70
|
+
// calls and meters them. Runs whenever a port is configured; routes 404 until
|
|
71
|
+
// provider keys (SBX_PROVIDER_KEY_*) are set.
|
|
72
|
+
// Install the host default-deny egress firewall (Linux + SBX_EGRESS_ENFORCE only;
|
|
73
|
+
// advisory no-op otherwise). Best-effort: logs and continues if it can't.
|
|
74
|
+
await ensureEgressFirewall(config);
|
|
75
|
+
const providers = buildProviders(config);
|
|
76
|
+
const prices = loadModelPrices(config.modelPricesPath);
|
|
77
|
+
const egress = config.egressPort > 0 ? createEgressProxy({ config, store, providers, prices }) : undefined;
|
|
78
|
+
egress?.listen(config.egressPort, config.egressHost, () => {
|
|
79
|
+
log.info("egress proxy listening", {
|
|
80
|
+
url: `http://${config.egressHost}:${config.egressPort}`,
|
|
81
|
+
providers: Object.keys(providers).length ? Object.keys(providers).join(",") : "none configured",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// Idle reaper: auto-pause sandboxes left idle past their sleepAfterMs.
|
|
85
|
+
const reaper = config.reapIntervalMs > 0
|
|
86
|
+
? startReaper({ driver, store, intervalMs: config.reapIntervalMs })
|
|
87
|
+
: undefined;
|
|
88
|
+
// Metrics sampler: integrate per-sandbox CPU/mem usage for the cost meter.
|
|
89
|
+
const sampler = config.metricsIntervalMs > 0
|
|
90
|
+
? startSampler({ driver, store, history, intervalMs: config.metricsIntervalMs })
|
|
91
|
+
: undefined;
|
|
92
|
+
const shutdown = async () => {
|
|
93
|
+
log.info("shutting down");
|
|
94
|
+
if (reaper)
|
|
95
|
+
clearInterval(reaper);
|
|
96
|
+
if (sampler)
|
|
97
|
+
clearInterval(sampler);
|
|
98
|
+
await driver.shutdown().catch(() => { }); // tear down the VZ warm pool, if any
|
|
99
|
+
stopTracing();
|
|
100
|
+
egress?.close();
|
|
101
|
+
proxy.close();
|
|
102
|
+
server.close(() => {
|
|
103
|
+
store.close();
|
|
104
|
+
process.exit(0);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
process.on("SIGINT", () => void shutdown());
|
|
108
|
+
process.on("SIGTERM", () => void shutdown());
|
|
109
|
+
}
|
|
110
|
+
main().catch((err) => {
|
|
111
|
+
log.error("fatal", { error: String(err?.stack ?? err) });
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-sandbox code-interpreter kernels.
|
|
3
|
+
*
|
|
4
|
+
* Each code context runs one of these as a long-lived background process. A
|
|
5
|
+
* kernel keeps a persistent namespace (so variables/imports survive across
|
|
6
|
+
* `runCode` calls, Jupyter-style) and serves one cell at a time over a pair of
|
|
7
|
+
* named pipes in its context directory:
|
|
8
|
+
*
|
|
9
|
+
* - reads a sequence number from `in.fifo`
|
|
10
|
+
* - executes `cell-<seq>.code`
|
|
11
|
+
* - writes a JSON `cell-<seq>.result.json` ({ stdout, stderr, results, error })
|
|
12
|
+
* - signals completion by writing a line to `out.fifo`
|
|
13
|
+
*
|
|
14
|
+
* The daemon coordinates a single round-trip per call (see runCode in the API),
|
|
15
|
+
* so cells on one context are serialized.
|
|
16
|
+
*/
|
|
17
|
+
export type KernelLanguage = "python" | "javascript";
|
|
18
|
+
export declare function kernelFor(language: KernelLanguage): {
|
|
19
|
+
filename: string;
|
|
20
|
+
source: string;
|
|
21
|
+
command: (dir: string) => string;
|
|
22
|
+
};
|
package/dist/kernels.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-sandbox code-interpreter kernels.
|
|
3
|
+
*
|
|
4
|
+
* Each code context runs one of these as a long-lived background process. A
|
|
5
|
+
* kernel keeps a persistent namespace (so variables/imports survive across
|
|
6
|
+
* `runCode` calls, Jupyter-style) and serves one cell at a time over a pair of
|
|
7
|
+
* named pipes in its context directory:
|
|
8
|
+
*
|
|
9
|
+
* - reads a sequence number from `in.fifo`
|
|
10
|
+
* - executes `cell-<seq>.code`
|
|
11
|
+
* - writes a JSON `cell-<seq>.result.json` ({ stdout, stderr, results, error })
|
|
12
|
+
* - signals completion by writing a line to `out.fifo`
|
|
13
|
+
*
|
|
14
|
+
* The daemon coordinates a single round-trip per call (see runCode in the API),
|
|
15
|
+
* so cells on one context are serialized.
|
|
16
|
+
*/
|
|
17
|
+
export function kernelFor(language) {
|
|
18
|
+
if (language === "python") {
|
|
19
|
+
return {
|
|
20
|
+
filename: "kernel.py",
|
|
21
|
+
source: PYTHON_KERNEL,
|
|
22
|
+
command: (dir) => `python3 -u ${dir}/kernel.py`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
filename: "kernel.js",
|
|
27
|
+
source: JS_KERNEL,
|
|
28
|
+
command: (dir) => `node ${dir}/kernel.js`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const PYTHON_KERNEL = String.raw `
|
|
32
|
+
import sys, os, json, ast, io, traceback, contextlib
|
|
33
|
+
|
|
34
|
+
CTX_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
IN = os.path.join(CTX_DIR, "in.fifo")
|
|
36
|
+
OUT = os.path.join(CTX_DIR, "out.fifo")
|
|
37
|
+
|
|
38
|
+
# Persistent namespace shared across cells.
|
|
39
|
+
ns = {"__name__": "__main__"}
|
|
40
|
+
|
|
41
|
+
def run_cell(code):
|
|
42
|
+
stdout, stderr = io.StringIO(), io.StringIO()
|
|
43
|
+
results = []
|
|
44
|
+
error = None
|
|
45
|
+
try:
|
|
46
|
+
tree = ast.parse(code)
|
|
47
|
+
last = tree.body[-1] if tree.body else None
|
|
48
|
+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
49
|
+
if isinstance(last, ast.Expr):
|
|
50
|
+
# Run everything but the trailing expression, then eval it so its
|
|
51
|
+
# value becomes a result (mirrors a notebook cell).
|
|
52
|
+
head = ast.Module(body=tree.body[:-1], type_ignores=[])
|
|
53
|
+
exec(compile(head, "<cell>", "exec"), ns)
|
|
54
|
+
value = eval(compile(ast.Expression(last.value), "<cell>", "eval"), ns)
|
|
55
|
+
if value is not None:
|
|
56
|
+
results.append({"type": "text", "text": repr(value)})
|
|
57
|
+
else:
|
|
58
|
+
exec(compile(tree, "<cell>", "exec"), ns)
|
|
59
|
+
except Exception:
|
|
60
|
+
error = traceback.format_exc()
|
|
61
|
+
return {"stdout": stdout.getvalue(), "stderr": stderr.getvalue(),
|
|
62
|
+
"results": results, "error": error}
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
with open(IN) as f:
|
|
66
|
+
seq = f.read().strip()
|
|
67
|
+
if not seq:
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
with open(os.path.join(CTX_DIR, "cell-%s.code" % seq)) as cf:
|
|
71
|
+
code = cf.read()
|
|
72
|
+
result = run_cell(code)
|
|
73
|
+
except Exception:
|
|
74
|
+
result = {"stdout": "", "stderr": "", "results": [],
|
|
75
|
+
"error": traceback.format_exc()}
|
|
76
|
+
with open(os.path.join(CTX_DIR, "cell-%s.result.json" % seq), "w") as rf:
|
|
77
|
+
json.dump(result, rf)
|
|
78
|
+
with open(OUT, "w") as of:
|
|
79
|
+
of.write("done\n")
|
|
80
|
+
`;
|
|
81
|
+
const JS_KERNEL = String.raw `
|
|
82
|
+
const fs = require("fs");
|
|
83
|
+
const path = require("path");
|
|
84
|
+
const vm = require("vm");
|
|
85
|
+
const util = require("util");
|
|
86
|
+
|
|
87
|
+
const CTX_DIR = __dirname;
|
|
88
|
+
const IN = path.join(CTX_DIR, "in.fifo");
|
|
89
|
+
const OUT = path.join(CTX_DIR, "out.fifo");
|
|
90
|
+
|
|
91
|
+
// Persistent context shared across cells (var/global assignments survive).
|
|
92
|
+
const sandbox = { require, process, Buffer, setTimeout, clearTimeout };
|
|
93
|
+
sandbox.global = sandbox;
|
|
94
|
+
const context = vm.createContext(sandbox);
|
|
95
|
+
|
|
96
|
+
function fmt(v) {
|
|
97
|
+
return typeof v === "string" ? v : util.inspect(v, { depth: 4 });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runCell(code) {
|
|
101
|
+
let stdout = "", stderr = "";
|
|
102
|
+
const results = [];
|
|
103
|
+
let error = null;
|
|
104
|
+
sandbox.console = {
|
|
105
|
+
log: (...a) => { stdout += a.map(fmt).join(" ") + "\n"; },
|
|
106
|
+
info: (...a) => { stdout += a.map(fmt).join(" ") + "\n"; },
|
|
107
|
+
error: (...a) => { stderr += a.map(fmt).join(" ") + "\n"; },
|
|
108
|
+
warn: (...a) => { stderr += a.map(fmt).join(" ") + "\n"; },
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const value = vm.runInContext(code, context, { filename: "<cell>" });
|
|
112
|
+
if (value !== undefined) results.push({ type: "text", text: fmt(value) });
|
|
113
|
+
} catch (e) {
|
|
114
|
+
error = (e && e.stack) ? String(e.stack) : String(e);
|
|
115
|
+
}
|
|
116
|
+
return { stdout, stderr, results, error };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (;;) {
|
|
120
|
+
const seq = fs.readFileSync(IN, "utf8").trim();
|
|
121
|
+
if (!seq) continue;
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
const code = fs.readFileSync(path.join(CTX_DIR, "cell-" + seq + ".code"), "utf8");
|
|
125
|
+
result = runCell(code);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
result = { stdout: "", stderr: "", results: [],
|
|
128
|
+
error: (e && e.stack) ? String(e.stack) : String(e) };
|
|
129
|
+
}
|
|
130
|
+
fs.writeFileSync(path.join(CTX_DIR, "cell-" + seq + ".result.json"), JSON.stringify(result));
|
|
131
|
+
fs.writeFileSync(OUT, "done\n");
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
//# sourceMappingURL=kernels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernels.js","sourceRoot":"","sources":["../src/kernels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,UAAU,SAAS,CAAC,QAAwB;IAKhD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO;YACL,QAAQ,EAAE,WAAW;YACrB,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,GAAG,YAAY;SAChD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,GAAG,YAAY;KAC1C,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD3B,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Driver } from "./driver/types.js";
|
|
2
|
+
import type { SandboxStore } from "./store.js";
|
|
3
|
+
import type { SandboxRecord } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lifecycle FSM transitions and the idle reaper.
|
|
6
|
+
*
|
|
7
|
+
* created ─▶ running ──(idle > sleepAfterMs, or POST /pause)──▶ paused ──(any op)──▶ running
|
|
8
|
+
* │ │
|
|
9
|
+
* └────────────── stop (manual) ────────────────────┴──▶ stopped ──(start)──▶ running
|
|
10
|
+
*
|
|
11
|
+
* A `paused` sandbox has had its compute freed but keeps its workspace, so the
|
|
12
|
+
* next operation transparently resumes it. Pause comes in two strengths, picked
|
|
13
|
+
* automatically per sandbox: on microVM drivers that support it, pause is a
|
|
14
|
+
* **memory snapshot** (guest RAM + devices saved; background processes come back
|
|
15
|
+
* alive on resume — nothing is lost by pausing); otherwise it's the cold
|
|
16
|
+
* stop-with-volume (workspace survives, processes don't). A `stopped` sandbox is
|
|
17
|
+
* user-intent and is never auto-resumed nor auto-paused.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Pause a running sandbox, preferring a memory snapshot (fast resume, live
|
|
21
|
+
* state preserved) and falling back to a cold stop. Runtime state (tracked
|
|
22
|
+
* processes, exposed ports) is only dropped on a cold pause — after a snapshot
|
|
23
|
+
* pause it stays valid, because resume restores those processes live.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pauseSandbox(driver: Driver, store: SandboxStore, record: SandboxRecord): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Resume a paused (or stopped) sandbox: recreate its container, reattaching the
|
|
28
|
+
* persistent volume, and mark it running + active.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resumeSandbox(driver: Driver, store: SandboxStore, record: SandboxRecord): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Auto-pause sandboxes that have been idle past their `sleepAfterMs`. A sandbox
|
|
33
|
+
* advertising a service (an exposed port) or running a tracked background
|
|
34
|
+
* process is paused only when the driver can **memory-snapshot** it — the
|
|
35
|
+
* processes come back alive, and the preview proxy wakes a paused sandbox on
|
|
36
|
+
* inbound traffic, so nothing is lost by hibernating. On cold-pause drivers
|
|
37
|
+
* such sandboxes are skipped, since pausing would kill that work.
|
|
38
|
+
* Returns the ids that were paused.
|
|
39
|
+
*/
|
|
40
|
+
export declare function reapIdle(driver: Driver, store: SandboxStore, now?: number): Promise<string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Start the periodic idle reaper. Returns the interval handle (unref'd so it
|
|
43
|
+
* won't keep the process alive); pass it to `clearInterval` on shutdown.
|
|
44
|
+
*/
|
|
45
|
+
export declare function startReaper(opts: {
|
|
46
|
+
driver: Driver;
|
|
47
|
+
store: SandboxStore;
|
|
48
|
+
intervalMs: number;
|
|
49
|
+
}): NodeJS.Timeout;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { log } from "./logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lifecycle FSM transitions and the idle reaper.
|
|
4
|
+
*
|
|
5
|
+
* created ─▶ running ──(idle > sleepAfterMs, or POST /pause)──▶ paused ──(any op)──▶ running
|
|
6
|
+
* │ │
|
|
7
|
+
* └────────────── stop (manual) ────────────────────┴──▶ stopped ──(start)──▶ running
|
|
8
|
+
*
|
|
9
|
+
* A `paused` sandbox has had its compute freed but keeps its workspace, so the
|
|
10
|
+
* next operation transparently resumes it. Pause comes in two strengths, picked
|
|
11
|
+
* automatically per sandbox: on microVM drivers that support it, pause is a
|
|
12
|
+
* **memory snapshot** (guest RAM + devices saved; background processes come back
|
|
13
|
+
* alive on resume — nothing is lost by pausing); otherwise it's the cold
|
|
14
|
+
* stop-with-volume (workspace survives, processes don't). A `stopped` sandbox is
|
|
15
|
+
* user-intent and is never auto-resumed nor auto-paused.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Pause a running sandbox, preferring a memory snapshot (fast resume, live
|
|
19
|
+
* state preserved) and falling back to a cold stop. Runtime state (tracked
|
|
20
|
+
* processes, exposed ports) is only dropped on a cold pause — after a snapshot
|
|
21
|
+
* pause it stays valid, because resume restores those processes live.
|
|
22
|
+
*/
|
|
23
|
+
export async function pauseSandbox(driver, store, record) {
|
|
24
|
+
let hibernated = false;
|
|
25
|
+
if (typeof driver.snapshot === "function" && (driver.canSnapshot?.(record.id) ?? true)) {
|
|
26
|
+
try {
|
|
27
|
+
await driver.snapshot(record.id);
|
|
28
|
+
hibernated = true;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
log.warn("snapshot pause failed; falling back to a cold stop", {
|
|
32
|
+
sandbox: record.id,
|
|
33
|
+
error: String(err),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!hibernated) {
|
|
38
|
+
await driver.stop(record.id);
|
|
39
|
+
store.clearRuntimeState(record.id); // processes/ports died with the compute
|
|
40
|
+
}
|
|
41
|
+
record.status = "paused";
|
|
42
|
+
store.add(record);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resume a paused (or stopped) sandbox: recreate its container, reattaching the
|
|
46
|
+
* persistent volume, and mark it running + active.
|
|
47
|
+
*/
|
|
48
|
+
export async function resumeSandbox(driver, store, record) {
|
|
49
|
+
await driver.start({
|
|
50
|
+
id: record.id,
|
|
51
|
+
image: record.image,
|
|
52
|
+
driver: record.driver,
|
|
53
|
+
env: record.env,
|
|
54
|
+
labels: record.labels,
|
|
55
|
+
persist: record.persist,
|
|
56
|
+
limits: record.limits,
|
|
57
|
+
});
|
|
58
|
+
record.status = "running";
|
|
59
|
+
record.lastActivityAt = new Date().toISOString();
|
|
60
|
+
store.add(record);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Auto-pause sandboxes that have been idle past their `sleepAfterMs`. A sandbox
|
|
64
|
+
* advertising a service (an exposed port) or running a tracked background
|
|
65
|
+
* process is paused only when the driver can **memory-snapshot** it — the
|
|
66
|
+
* processes come back alive, and the preview proxy wakes a paused sandbox on
|
|
67
|
+
* inbound traffic, so nothing is lost by hibernating. On cold-pause drivers
|
|
68
|
+
* such sandboxes are skipped, since pausing would kill that work.
|
|
69
|
+
* Returns the ids that were paused.
|
|
70
|
+
*/
|
|
71
|
+
export async function reapIdle(driver, store, now = Date.now()) {
|
|
72
|
+
const paused = [];
|
|
73
|
+
for (const record of store.list()) {
|
|
74
|
+
if (record.status !== "running")
|
|
75
|
+
continue;
|
|
76
|
+
if (!record.sleepAfterMs || record.sleepAfterMs <= 0)
|
|
77
|
+
continue;
|
|
78
|
+
const hasService = store.listExposed(record.id).length > 0;
|
|
79
|
+
const hasLiveProcs = store.listProcesses(record.id).some((p) => p.status === "running");
|
|
80
|
+
if ((hasService || hasLiveProcs) && !driver.canSnapshot?.(record.id))
|
|
81
|
+
continue;
|
|
82
|
+
if (now - Date.parse(record.lastActivityAt) < record.sleepAfterMs)
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
await pauseSandbox(driver, store, record);
|
|
86
|
+
paused.push(record.id);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
log.warn("reaper failed to pause sandbox", { sandbox: record.id, error: String(err) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return paused;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Start the periodic idle reaper. Returns the interval handle (unref'd so it
|
|
96
|
+
* won't keep the process alive); pass it to `clearInterval` on shutdown.
|
|
97
|
+
*/
|
|
98
|
+
export function startReaper(opts) {
|
|
99
|
+
const { driver, store, intervalMs } = opts;
|
|
100
|
+
const timer = setInterval(() => {
|
|
101
|
+
reapIdle(driver, store)
|
|
102
|
+
.then((ids) => {
|
|
103
|
+
if (ids.length)
|
|
104
|
+
log.info("auto-paused idle sandboxes", { ids });
|
|
105
|
+
})
|
|
106
|
+
.catch((err) => log.error("reaper error", { error: String(err) }));
|
|
107
|
+
}, intervalMs);
|
|
108
|
+
timer.unref?.();
|
|
109
|
+
return timer;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LogFormat, LogLevel } from "./config.js";
|
|
2
|
+
export type LogFields = Record<string, unknown>;
|
|
3
|
+
export interface Logger {
|
|
4
|
+
debug(msg: string, fields?: LogFields): void;
|
|
5
|
+
info(msg: string, fields?: LogFields): void;
|
|
6
|
+
warn(msg: string, fields?: LogFields): void;
|
|
7
|
+
error(msg: string, fields?: LogFields): void;
|
|
8
|
+
/** Derive a logger that stamps `fields` onto every line (e.g. a request id). */
|
|
9
|
+
child(fields: LogFields): Logger;
|
|
10
|
+
}
|
|
11
|
+
/** Process-wide logger. Replaced by `configureLogger`; defaults are quiet-ish. */
|
|
12
|
+
export declare let log: Logger;
|
|
13
|
+
export declare function configureLogger(opts: {
|
|
14
|
+
level: LogLevel;
|
|
15
|
+
format: LogFormat;
|
|
16
|
+
}): Logger;
|