@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/config.d.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
/**
|
|
5
|
+
* Default OCI image for new sandboxes. Defaults to a small Debian-based
|
|
6
|
+
* Python image (ships /bin/bash + python3) so the vertical slice works with
|
|
7
|
+
* zero local image builds. Build images/base and set SBX_IMAGE=sbx/base:latest
|
|
8
|
+
* for the richer toolset (Node 20, git, ripgrep, …).
|
|
9
|
+
*/
|
|
10
|
+
defaultImage: string;
|
|
11
|
+
/**
|
|
12
|
+
* Runtime driver to use: `container` (Docker, the only built impl),
|
|
13
|
+
* `firecracker` (Linux microVM, Phase 3), or `applevz` (macOS microVM, Phase 3).
|
|
14
|
+
* Selects the isolation tier; the daemon/SDK/CLI are unchanged across drivers.
|
|
15
|
+
*/
|
|
16
|
+
driver: string;
|
|
17
|
+
/** Default per-sandbox memory cap in MiB for new sandboxes (`0` = unlimited). */
|
|
18
|
+
defaultMemoryMb: number;
|
|
19
|
+
/** Default per-sandbox CPU cap in fractional cores (`0` = unlimited). */
|
|
20
|
+
defaultCpus: number;
|
|
21
|
+
/** Default per-sandbox process/thread cap for new sandboxes (`0` = unlimited). */
|
|
22
|
+
defaultPidsLimit: number;
|
|
23
|
+
/**
|
|
24
|
+
* Admission control: `enforce` rejects `create` when the host's memory budget
|
|
25
|
+
* is exhausted (prevents over-subscription / OOM when launching many
|
|
26
|
+
* sandboxes); `off` only reports capacity. Default `enforce`.
|
|
27
|
+
*/
|
|
28
|
+
admission: "enforce" | "off";
|
|
29
|
+
/** Host memory budget in MiB for admission (`0` = auto-detect from the runtime). */
|
|
30
|
+
hostMemoryMb: number;
|
|
31
|
+
/** Host CPU budget for the capacity view (`0` = auto-detect from the runtime). */
|
|
32
|
+
hostCpus: number;
|
|
33
|
+
/** Memory over-commit factor applied to the host budget (e.g. `1.5` to oversubscribe). */
|
|
34
|
+
overcommit: number;
|
|
35
|
+
/**
|
|
36
|
+
* Admission **floor** (MiB) charged for an uncapped sandbox that's just started
|
|
37
|
+
* or not yet sampled. Once the metrics sampler sees it, admission uses its
|
|
38
|
+
* measured RSS instead (usage-based). Capped sandboxes use their cap.
|
|
39
|
+
*/
|
|
40
|
+
defaultReservationMb: number;
|
|
41
|
+
/** Bind host for the preview-URL reverse proxy. */
|
|
42
|
+
proxyHost: string;
|
|
43
|
+
/** Port for the preview-URL reverse proxy (separate from the REST API). */
|
|
44
|
+
proxyPort: number;
|
|
45
|
+
/**
|
|
46
|
+
* Bind host for the egress credential proxy (LLM gateway). Defaults to
|
|
47
|
+
* loopback (single-tenant safe). On **native Linux** dockerd, sandboxes reach
|
|
48
|
+
* the daemon via the bridge gateway (`host.docker.internal` → ~172.17.0.1), so
|
|
49
|
+
* a loopback-only bind is unreachable from sandboxes — set `HOTCELL_EGRESS_HOST`
|
|
50
|
+
* to `0.0.0.0` (or the bridge IP) there, relying on the per-sandbox egress
|
|
51
|
+
* token + host firewall. Docker Desktop (macOS) reaches loopback as-is.
|
|
52
|
+
*/
|
|
53
|
+
egressHost: string;
|
|
54
|
+
/** Port for the egress credential proxy. `0` disables it. */
|
|
55
|
+
egressPort: number;
|
|
56
|
+
/**
|
|
57
|
+
* Enforce default-deny egress: put sandboxes on a dedicated bridge network and
|
|
58
|
+
* (Linux only) install host `DOCKER-USER` iptables rules so the ONLY reachable
|
|
59
|
+
* destinations are the egress gateway + the pinned DNS resolver — everything else
|
|
60
|
+
* is dropped. Off by default (the gateway is then the "safe path available", not
|
|
61
|
+
* the "only path"). `HOTCELL_EGRESS_ENFORCE`. Advisory-only on macOS Docker Desktop.
|
|
62
|
+
*/
|
|
63
|
+
egressEnforce: boolean;
|
|
64
|
+
/** Docker network name sandboxes join when egress is enforced. `HOTCELL_EGRESS_NETWORK`. */
|
|
65
|
+
egressNetwork: string;
|
|
66
|
+
/**
|
|
67
|
+
* IPv4 subnet (CIDR) for the egress-enforced bridge network. The host firewall
|
|
68
|
+
* scopes its allow/deny rules to this subnet, and the bridge gateway (`.1`) is the
|
|
69
|
+
* sandbox's route to the egress gateway. `/24` assumed. `HOTCELL_EGRESS_SUBNET`.
|
|
70
|
+
*/
|
|
71
|
+
egressSubnet: string;
|
|
72
|
+
/**
|
|
73
|
+
* DNS resolver IP pinned into sandboxes under egress enforcement (so DNS can't be
|
|
74
|
+
* used as an exfil channel and DoH is the only blocked path). Empty = Docker's
|
|
75
|
+
* embedded resolver. `HOTCELL_EGRESS_DNS`.
|
|
76
|
+
*/
|
|
77
|
+
egressDnsResolver: string;
|
|
78
|
+
/**
|
|
79
|
+
* Path to a JSON `{ allow, deny }` allowlist that fully REPLACES the built-in
|
|
80
|
+
* default (forward-proxy host allowlist). `HOTCELL_ALLOWLIST_FILE`. Empty = defaults.
|
|
81
|
+
*/
|
|
82
|
+
allowlistFile: string;
|
|
83
|
+
/** Extra hosts appended to the allowlist (`HOTCELL_ALLOWLIST_EXTRA`, comma-separated). */
|
|
84
|
+
allowlistExtra: string[];
|
|
85
|
+
/**
|
|
86
|
+
* Include the `source_control` allowlist tier (github/gitlab/bitbucket). On by
|
|
87
|
+
* default so `git clone` works; set `HOTCELL_ALLOW_SOURCE_CONTROL=false` for
|
|
88
|
+
* high-security workloads (a writable git host is also an exfil channel).
|
|
89
|
+
*/
|
|
90
|
+
allowSourceControl: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Default per-sandbox LLM spend ceiling in USD — the gateway returns 402 once a
|
|
93
|
+
* sandbox's cumulative provider cost reaches it, across all its tokens. Per-create
|
|
94
|
+
* `egressSpendCapUsd` overrides. `0` = unlimited. `HOTCELL_EGRESS_SPEND_CAP`.
|
|
95
|
+
*/
|
|
96
|
+
egressSpendCapUsd: number;
|
|
97
|
+
/**
|
|
98
|
+
* Hostname advertised in egress base URLs returned to clients. Sandboxes reach
|
|
99
|
+
* the daemon host through this name, so on Docker Desktop it defaults to
|
|
100
|
+
* `host.docker.internal`. Set to a LAN IP/DNS name for remote sandboxes.
|
|
101
|
+
*/
|
|
102
|
+
egressAdvertiseHost: string;
|
|
103
|
+
/**
|
|
104
|
+
* Provider API keys, held on the daemon host and injected into outbound calls
|
|
105
|
+
* so they never live inside a sandbox. Keyed by lower-case provider name
|
|
106
|
+
* (`openai`, `anthropic`, `openrouter`). A provider's gateway route exists only
|
|
107
|
+
* when its key is present here. Sourced from `HOTCELL_PROVIDER_KEY_<NAME>` env vars.
|
|
108
|
+
*/
|
|
109
|
+
providerKeys: Record<string, string>;
|
|
110
|
+
/**
|
|
111
|
+
* Operator-defined / override provider shapes, keyed by lower-case provider
|
|
112
|
+
* name. Lets an operator add a provider the daemon doesn't ship (or repoint a
|
|
113
|
+
* built-in — e.g. route `openai` through a Cloudflare AI Gateway prefix) without
|
|
114
|
+
* a code change. Sourced from `HOTCELL_PROVIDER_<NAME>_BASEURL` / `_AUTHHEADER` /
|
|
115
|
+
* `_FORMAT`. A route still requires a key (`HOTCELL_PROVIDER_KEY_<NAME>`).
|
|
116
|
+
*/
|
|
117
|
+
providerConfigs: Record<string, ProviderConfig>;
|
|
118
|
+
/** Host directory where sandbox backup tarballs + metadata are stored. */
|
|
119
|
+
backupDir: string;
|
|
120
|
+
/**
|
|
121
|
+
* Path to the embedded SQLite database holding durable control-plane state
|
|
122
|
+
* (sandbox/process/session/context/exposed-port records). Survives daemon
|
|
123
|
+
* restarts. Set to `:memory:` for an ephemeral, in-process store.
|
|
124
|
+
*/
|
|
125
|
+
dbPath: string;
|
|
126
|
+
/**
|
|
127
|
+
* Default idle timeout (ms) applied to new sandboxes that don't specify their
|
|
128
|
+
* own `sleepAfter`. The reaper auto-pauses a sandbox after this long without
|
|
129
|
+
* activity. `0` (the default) disables auto-pause.
|
|
130
|
+
*/
|
|
131
|
+
defaultSleepAfterMs: number;
|
|
132
|
+
/** How often (ms) the idle reaper scans for sandboxes to auto-pause. */
|
|
133
|
+
reapIntervalMs: number;
|
|
134
|
+
/** How often (ms) the metrics sampler integrates per-sandbox usage. `0` disables it. */
|
|
135
|
+
metricsIntervalMs: number;
|
|
136
|
+
/** Cost-meter rate: currency units per vCPU-hour. */
|
|
137
|
+
costCpuPerHour: number;
|
|
138
|
+
/** Cost-meter rate: currency units per GB-hour of memory. */
|
|
139
|
+
costMemGbPerHour: number;
|
|
140
|
+
/** Cost-meter rate: currency units per GB of preview-proxy egress. */
|
|
141
|
+
costEgressPerGb: number;
|
|
142
|
+
/**
|
|
143
|
+
* Path to a JSON model→price override file (`HOTCELL_MODEL_PRICES`), overlaid on the
|
|
144
|
+
* built-in table. Used to compute an LLM call's USD cost when the provider does
|
|
145
|
+
* not report one. Empty = built-in defaults only.
|
|
146
|
+
*/
|
|
147
|
+
modelPricesPath: string;
|
|
148
|
+
/**
|
|
149
|
+
* Path to the signed `sbx-vz` Swift helper for the Apple VZ driver (`HOTCELL_DRIVER=applevz`).
|
|
150
|
+
* Built by `npm run build:vz`. Defaults to its in-repo build output; set
|
|
151
|
+
* `HOTCELL_VZ_HELPER_PATH` to a bundled/installed location. `HOTCELL_DRIVER=container` ignores it.
|
|
152
|
+
*/
|
|
153
|
+
vzHelperPath: string;
|
|
154
|
+
/** Guest kernel image for the Apple VZ driver (`HOTCELL_VZ_KERNEL`). Uncompressed arm64 Image. */
|
|
155
|
+
vzKernel: string;
|
|
156
|
+
/** Base rootfs image for the Apple VZ driver (`HOTCELL_VZ_ROOTFS`). */
|
|
157
|
+
vzRootfs: string;
|
|
158
|
+
/** Per-sandbox VM state dir (disks, sockets) for the Apple VZ driver (`HOTCELL_VZ_STATE_DIR`). */
|
|
159
|
+
vzStateDir: string;
|
|
160
|
+
/** Default workspace disk size in GiB for a new VZ sandbox (`HOTCELL_VZ_DISK_GB`). */
|
|
161
|
+
vzDiskGb: number;
|
|
162
|
+
/**
|
|
163
|
+
* Cache dir for OCI→ext4 converted rootfs images + the blank workspace template
|
|
164
|
+
* (`HOTCELL_VZ_IMAGE_CACHE`). Keyed by image name; populated on demand from
|
|
165
|
+
* `HOTCELL_IMAGE`. Kept separate from the per-sandbox state dir so it survives.
|
|
166
|
+
*/
|
|
167
|
+
vzImageCacheDir: string;
|
|
168
|
+
/**
|
|
169
|
+
* Warm-pool size for the Apple VZ driver (`HOTCELL_VZ_WARM_POOL`, default 0 = off):
|
|
170
|
+
* keep this many base-image microVMs pre-booted so a `create` of the base image
|
|
171
|
+
* is an instant adopt instead of a ~2s cold boot. The pool refills in the
|
|
172
|
+
* background as guests are claimed.
|
|
173
|
+
*/
|
|
174
|
+
vzWarmPool: number;
|
|
175
|
+
/** `firecracker` binary path for the Firecracker driver (`HOTCELL_FC_BIN`). */
|
|
176
|
+
fcBin: string;
|
|
177
|
+
/** Guest kernel (uncompressed vmlinux) for the Firecracker driver (`HOTCELL_FC_KERNEL`). */
|
|
178
|
+
fcKernel: string;
|
|
179
|
+
/** Prebuilt base rootfs for the Firecracker driver (`HOTCELL_FC_ROOTFS`). */
|
|
180
|
+
fcRootfs: string;
|
|
181
|
+
/** Per-sandbox VM state dir for the Firecracker driver (`HOTCELL_FC_STATE_DIR`). */
|
|
182
|
+
fcStateDir: string;
|
|
183
|
+
/** Default workspace disk size in GiB for a new Firecracker sandbox (`HOTCELL_FC_DISK_GB`). */
|
|
184
|
+
fcDiskGb: number;
|
|
185
|
+
/**
|
|
186
|
+
* Warm-pool size for the Firecracker driver (`HOTCELL_FC_WARM_POOL`, default 0 = off):
|
|
187
|
+
* keep N pre-booted spare microVMs of the default image so a plain create
|
|
188
|
+
* adopts one instantly instead of cold-booting.
|
|
189
|
+
*/
|
|
190
|
+
fcWarmPool: number;
|
|
191
|
+
/** Converted OCI→ext4 rootfs cache for the Firecracker driver (`HOTCELL_FC_IMAGE_CACHE`). */
|
|
192
|
+
fcImageCacheDir: string;
|
|
193
|
+
/** Minimum level emitted by the structured logger. */
|
|
194
|
+
logLevel: LogLevel;
|
|
195
|
+
/** Log encoding: `json` (one JSON object per line) or `pretty` (human). */
|
|
196
|
+
logFormat: LogFormat;
|
|
197
|
+
/**
|
|
198
|
+
* API key required on every REST call (except `/healthz` and the dashboard
|
|
199
|
+
* HTML). Empty string (the default) disables auth — loopback, single-tenant.
|
|
200
|
+
*/
|
|
201
|
+
apiKey: string;
|
|
202
|
+
/**
|
|
203
|
+
* OTLP/HTTP traces endpoint (e.g. `http://localhost:4318`). When set, spans are
|
|
204
|
+
* batch-exported to `<endpoint>/v1/traces`. Empty disables export; recent spans
|
|
205
|
+
* are always kept in-memory for `GET /traces` regardless.
|
|
206
|
+
*/
|
|
207
|
+
otlpEndpoint: string;
|
|
208
|
+
/** How many of the most recent metric samples per sandbox to retain in memory. */
|
|
209
|
+
metricsHistory: number;
|
|
210
|
+
/** How many of the most recent finished spans to retain in memory for `/traces`. */
|
|
211
|
+
traceRing: number;
|
|
212
|
+
/**
|
|
213
|
+
* Maximum accepted request body size in bytes (REST + egress gateway). Caps
|
|
214
|
+
* memory use from a single request. Must stay large enough for base64-encoded
|
|
215
|
+
* file writes. `HOTCELL_MAX_BODY_BYTES`, default 32 MiB.
|
|
216
|
+
*/
|
|
217
|
+
maxBodyBytes: number;
|
|
218
|
+
/**
|
|
219
|
+
* Extra Host header values accepted by the API server, beyond the loopback
|
|
220
|
+
* names and `host`. The Host allowlist is a DNS-rebinding / localhost-CSRF
|
|
221
|
+
* guard. `HOTCELL_ALLOWED_HOSTS` (comma-separated). Empty by default.
|
|
222
|
+
*/
|
|
223
|
+
allowedHosts: string[];
|
|
224
|
+
}
|
|
225
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
226
|
+
export type LogFormat = "json" | "pretty";
|
|
227
|
+
/** Operator-supplied provider shape (base URL + auth header + key-format template). */
|
|
228
|
+
export interface ProviderConfig {
|
|
229
|
+
baseUrl?: string;
|
|
230
|
+
/** Header the provider authenticates with (e.g. `authorization`, `x-api-key`). */
|
|
231
|
+
authHeader?: string;
|
|
232
|
+
/** Auth-header value template; `{key}` is replaced with the real key (e.g. `Bearer {key}`). */
|
|
233
|
+
formatTemplate?: string;
|
|
234
|
+
}
|
|
235
|
+
export declare function loadConfig(): Config;
|
|
236
|
+
/**
|
|
237
|
+
* Build the preview URL for an exposed port. Uses a `*.localhost` subdomain,
|
|
238
|
+
* which browsers resolve to 127.0.0.1 with zero DNS/hosts config. Sandbox ids
|
|
239
|
+
* are dash-free hex, so `<id>-<port>` is an unambiguous label.
|
|
240
|
+
*/
|
|
241
|
+
export declare function previewUrl(config: Config, sandboxId: string, port: number): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Read a daemon env var with legacy fallback: `HOTCELL_<name>` wins, then the
|
|
6
|
+
* pre-rename `HOTCELL_<name>` — so deployments configured before the hotcell
|
|
7
|
+
* rename keep working unchanged.
|
|
8
|
+
*/
|
|
9
|
+
function env(name) {
|
|
10
|
+
return process.env[`HOTCELL_${name}`] ?? process.env[`HOTCELL_${name}`];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Root for daemon state (db, backups, VM state, image caches): `~/.hotcell`,
|
|
14
|
+
* except when a pre-rename `~/.sbx` already exists and `~/.hotcell` doesn't —
|
|
15
|
+
* then the legacy root keeps being used so existing installs keep their data.
|
|
16
|
+
*/
|
|
17
|
+
function stateRoot() {
|
|
18
|
+
const modern = join(homedir(), ".hotcell");
|
|
19
|
+
const legacy = join(homedir(), ".sbx");
|
|
20
|
+
if (!existsSync(modern) && existsSync(legacy))
|
|
21
|
+
return legacy;
|
|
22
|
+
return modern;
|
|
23
|
+
}
|
|
24
|
+
export function loadConfig() {
|
|
25
|
+
return {
|
|
26
|
+
host: env("HOST") ?? "127.0.0.1",
|
|
27
|
+
port: Number(env("PORT") ?? 4750),
|
|
28
|
+
defaultImage: env("IMAGE") ?? "python:3.11-slim-bookworm",
|
|
29
|
+
driver: env("DRIVER") ?? "container",
|
|
30
|
+
defaultMemoryMb: Number(env("DEFAULT_MEMORY_MB") ?? 0),
|
|
31
|
+
defaultCpus: Number(env("DEFAULT_CPUS") ?? 0),
|
|
32
|
+
defaultPidsLimit: Number(env("DEFAULT_PIDS") ?? 0),
|
|
33
|
+
admission: env("ADMISSION") === "off" ? "off" : "enforce",
|
|
34
|
+
hostMemoryMb: Number(env("HOST_MEMORY_MB") ?? 0),
|
|
35
|
+
hostCpus: Number(env("HOST_CPUS") ?? 0),
|
|
36
|
+
overcommit: Number(env("OVERCOMMIT") ?? 1),
|
|
37
|
+
defaultReservationMb: Number(env("DEFAULT_RESERVATION_MB") ?? 256),
|
|
38
|
+
proxyHost: env("PROXY_HOST") ?? "127.0.0.1",
|
|
39
|
+
proxyPort: Number(env("PROXY_PORT") ?? 4751),
|
|
40
|
+
egressHost: env("EGRESS_HOST") ?? "127.0.0.1",
|
|
41
|
+
egressPort: Number(env("EGRESS_PORT") ?? 4752),
|
|
42
|
+
egressAdvertiseHost: env("EGRESS_ADVERTISE_HOST") ?? "host.docker.internal",
|
|
43
|
+
egressEnforce: env("EGRESS_ENFORCE") === "true",
|
|
44
|
+
egressNetwork: env("EGRESS_NETWORK") ?? "hotcell-egress",
|
|
45
|
+
egressSubnet: env("EGRESS_SUBNET") ?? "10.200.0.0/24",
|
|
46
|
+
egressDnsResolver: env("EGRESS_DNS") ?? "",
|
|
47
|
+
allowlistFile: env("ALLOWLIST_FILE") ?? "",
|
|
48
|
+
allowlistExtra: (env("ALLOWLIST_EXTRA") ?? "")
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((h) => h.trim())
|
|
51
|
+
.filter(Boolean),
|
|
52
|
+
allowSourceControl: env("ALLOW_SOURCE_CONTROL") !== "false",
|
|
53
|
+
egressSpendCapUsd: Number(env("EGRESS_SPEND_CAP") ?? 0),
|
|
54
|
+
providerKeys: loadProviderKeys(),
|
|
55
|
+
providerConfigs: loadProviderConfigs(),
|
|
56
|
+
backupDir: env("BACKUP_DIR") ?? join(stateRoot(), "backups"),
|
|
57
|
+
dbPath: env("DB") ?? join(stateRoot(), "state.db"),
|
|
58
|
+
defaultSleepAfterMs: Number(env("SLEEP_AFTER_MS") ?? 0),
|
|
59
|
+
reapIntervalMs: Number(env("REAP_INTERVAL_MS") ?? 15000),
|
|
60
|
+
metricsIntervalMs: Number(env("METRICS_INTERVAL_MS") ?? 10000),
|
|
61
|
+
costCpuPerHour: Number(env("COST_CPU_PER_HOUR") ?? 0.05),
|
|
62
|
+
costMemGbPerHour: Number(env("COST_MEM_GB_PER_HOUR") ?? 0.005),
|
|
63
|
+
costEgressPerGb: Number(env("COST_EGRESS_PER_GB") ?? 0.01),
|
|
64
|
+
modelPricesPath: env("MODEL_PRICES") ?? "",
|
|
65
|
+
vzHelperPath: env("VZ_HELPER_PATH") ?? "helpers/hotcell-vz/dist/hotcell-vz",
|
|
66
|
+
vzKernel: env("VZ_KERNEL") ?? "helpers/hotcell-vz/guest/vmlinux-vz",
|
|
67
|
+
vzRootfs: env("VZ_ROOTFS") ?? "helpers/hotcell-vz/guest/rootfs.img",
|
|
68
|
+
vzStateDir: env("VZ_STATE_DIR") ?? join(stateRoot(), "vz"),
|
|
69
|
+
vzDiskGb: Number(env("VZ_DISK_GB") ?? 4),
|
|
70
|
+
vzImageCacheDir: env("VZ_IMAGE_CACHE") ?? join(stateRoot(), "vz", "images"),
|
|
71
|
+
vzWarmPool: Number(env("VZ_WARM_POOL") ?? 0),
|
|
72
|
+
fcBin: env("FC_BIN") ?? "firecracker",
|
|
73
|
+
fcKernel: env("FC_KERNEL") ?? "helpers/hotcell-vz/guest/vmlinux-fc",
|
|
74
|
+
fcRootfs: env("FC_ROOTFS") ?? "helpers/hotcell-vz/guest/rootfs.img",
|
|
75
|
+
fcStateDir: env("FC_STATE_DIR") ?? join(stateRoot(), "fc"),
|
|
76
|
+
fcDiskGb: Number(env("FC_DISK_GB") ?? 8),
|
|
77
|
+
fcWarmPool: Number(env("FC_WARM_POOL") ?? 0),
|
|
78
|
+
fcImageCacheDir: env("FC_IMAGE_CACHE") ?? join(stateRoot(), "fc", "images"),
|
|
79
|
+
logLevel: parseLogLevel(env("LOG_LEVEL")),
|
|
80
|
+
logFormat: env("LOG_FORMAT") === "json" ? "json" : "pretty",
|
|
81
|
+
apiKey: env("API_KEY") ?? "",
|
|
82
|
+
otlpEndpoint: (env("OTLP_ENDPOINT") ?? "").replace(/\/$/, ""),
|
|
83
|
+
metricsHistory: Number(env("METRICS_HISTORY") ?? 60),
|
|
84
|
+
traceRing: Number(env("TRACE_RING") ?? 200),
|
|
85
|
+
maxBodyBytes: Number(env("MAX_BODY_BYTES") ?? 32 * 1024 * 1024),
|
|
86
|
+
allowedHosts: (env("ALLOWED_HOSTS") ?? "")
|
|
87
|
+
.split(",")
|
|
88
|
+
.map((h) => h.trim())
|
|
89
|
+
.filter(Boolean),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Collect provider keys from `HOTCELL_PROVIDER_KEY_<NAME>` env vars (name
|
|
93
|
+
* lowercased); legacy `SBX_PROVIDER_KEY_<NAME>` still read, HOTCELL_ wins. */
|
|
94
|
+
function loadProviderKeys() {
|
|
95
|
+
const keys = {};
|
|
96
|
+
for (const prefix of ["SBX_PROVIDER_KEY_", "HOTCELL_PROVIDER_KEY_"]) {
|
|
97
|
+
// legacy first so HOTCELL_ overwrites on conflict
|
|
98
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
99
|
+
if (name.startsWith(prefix) && value) {
|
|
100
|
+
keys[name.slice(prefix.length).toLowerCase()] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return keys;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Collect operator-defined provider shapes from `HOTCELL_PROVIDER_<NAME>_<FIELD>` env
|
|
108
|
+
* vars (FIELD ∈ BASEURL/AUTHHEADER/FORMAT), skipping the `HOTCELL_PROVIDER_KEY_*`
|
|
109
|
+
* namespace. The provider name is lower-cased; e.g.
|
|
110
|
+
* `HOTCELL_PROVIDER_CFOPENAI_BASEURL=https://gateway.ai.cloudflare.com/v1/<acct>/<gw>/openai`
|
|
111
|
+
* defines a `cfopenai` route (pair with `HOTCELL_PROVIDER_KEY_CFOPENAI`).
|
|
112
|
+
*/
|
|
113
|
+
function loadProviderConfigs() {
|
|
114
|
+
const out = {};
|
|
115
|
+
// Legacy prefix scanned first so HOTCELL_ overwrites on conflict.
|
|
116
|
+
for (const prefix of ["SBX_PROVIDER_", "HOTCELL_PROVIDER_"]) {
|
|
117
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
118
|
+
if (!name.startsWith(prefix) || !value)
|
|
119
|
+
continue;
|
|
120
|
+
if (name.startsWith("SBX_PROVIDER_KEY_") || name.startsWith("HOTCELL_PROVIDER_KEY_"))
|
|
121
|
+
continue;
|
|
122
|
+
const rest = name.slice(prefix.length); // <NAME>_<FIELD>
|
|
123
|
+
const us = rest.lastIndexOf("_");
|
|
124
|
+
if (us <= 0)
|
|
125
|
+
continue;
|
|
126
|
+
const provName = rest.slice(0, us).toLowerCase();
|
|
127
|
+
const field = rest.slice(us + 1).toUpperCase();
|
|
128
|
+
const cfg = (out[provName] ??= {});
|
|
129
|
+
if (field === "BASEURL")
|
|
130
|
+
cfg.baseUrl = value;
|
|
131
|
+
else if (field === "AUTHHEADER")
|
|
132
|
+
cfg.authHeader = value.toLowerCase();
|
|
133
|
+
else if (field === "FORMAT")
|
|
134
|
+
cfg.formatTemplate = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function parseLogLevel(value) {
|
|
140
|
+
return value === "debug" || value === "info" || value === "warn" || value === "error"
|
|
141
|
+
? value
|
|
142
|
+
: "info";
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Build the preview URL for an exposed port. Uses a `*.localhost` subdomain,
|
|
146
|
+
* which browsers resolve to 127.0.0.1 with zero DNS/hosts config. Sandbox ids
|
|
147
|
+
* are dash-free hex, so `<id>-<port>` is an unambiguous label.
|
|
148
|
+
*/
|
|
149
|
+
export function previewUrl(config, sandboxId, port) {
|
|
150
|
+
return `http://${sandboxId}-${port}.localhost:${config.proxyPort}/`;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/cost.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
import type { CostBreakdown, SandboxUsage } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Translate cumulative usage into a cost breakdown using the configured rates.
|
|
5
|
+
* CPU is exact (integrated vCPU-seconds), memory is integrated GB-seconds, and
|
|
6
|
+
* egress is the preview-proxy bytes the sandbox has sent out. (Full internet
|
|
7
|
+
* egress metering arrives with the Phase 3 egress credential proxy.)
|
|
8
|
+
*/
|
|
9
|
+
export declare function computeCost(usage: SandboxUsage, config: Config): CostBreakdown;
|
package/dist/cost.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const BYTES_PER_GB = 1e9;
|
|
2
|
+
const SECONDS_PER_HOUR = 3600;
|
|
3
|
+
/**
|
|
4
|
+
* Translate cumulative usage into a cost breakdown using the configured rates.
|
|
5
|
+
* CPU is exact (integrated vCPU-seconds), memory is integrated GB-seconds, and
|
|
6
|
+
* egress is the preview-proxy bytes the sandbox has sent out. (Full internet
|
|
7
|
+
* egress metering arrives with the Phase 3 egress credential proxy.)
|
|
8
|
+
*/
|
|
9
|
+
export function computeCost(usage, config) {
|
|
10
|
+
const cpu = (usage.cpuSeconds / SECONDS_PER_HOUR) * config.costCpuPerHour;
|
|
11
|
+
const memGbSeconds = usage.memByteSeconds / BYTES_PER_GB;
|
|
12
|
+
const mem = (memGbSeconds / SECONDS_PER_HOUR) * config.costMemGbPerHour;
|
|
13
|
+
const egress = (usage.egressBytes / BYTES_PER_GB) * config.costEgressPerGb;
|
|
14
|
+
// LLM cost is the provider's own reported figure (e.g. OpenRouter usage.cost) —
|
|
15
|
+
// the source of truth, not a rate we apply.
|
|
16
|
+
const provider = usage.providerCost ?? 0;
|
|
17
|
+
return { cpu, mem, egress, provider, total: cpu + mem + egress + provider };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=cost.js.map
|
package/dist/cost.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.js","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAGA,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAmB,EAAE,MAAc;IAC7D,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC;IAC1E,MAAM,YAAY,GAAG,KAAK,CAAC,cAAc,GAAG,YAAY,CAAC;IACzD,MAAM,GAAG,GAAG,CAAC,YAAY,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACxE,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,GAAG,YAAY,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;IAC3E,gFAAgF;IAChF,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC;IACzC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,QAAQ,EAAE,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecracker density benchmark (plan §Verification 4): launch N small microVMs
|
|
3
|
+
* on one host, prove they're all live and functional, and measure what density
|
|
4
|
+
* actually costs — boot latency distribution, host memory per VM, teardown.
|
|
5
|
+
*
|
|
6
|
+
* Run on a KVM host: SBX_DENSITY_N=40 npm run bench:density
|
|
7
|
+
* (image conversion must be cached — run smoke:fc once first).
|
|
8
|
+
*/
|
|
9
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { FirecrackerDriver } from "./driver/firecracker.js";
|
|
13
|
+
const N = Number(process.env.SBX_DENSITY_N ?? 40);
|
|
14
|
+
const CONCURRENCY = Number(process.env.SBX_DENSITY_CONCURRENCY ?? 4);
|
|
15
|
+
const MEM_MB = Number(process.env.SBX_DENSITY_MEM_MB ?? 256);
|
|
16
|
+
function hostAvailableMb() {
|
|
17
|
+
const m = /MemAvailable:\s+(\d+)\s+kB/.exec(readFileSync("/proc/meminfo", "utf8"));
|
|
18
|
+
return m ? Math.round(Number(m[1]) / 1024) : 0;
|
|
19
|
+
}
|
|
20
|
+
function pct(sorted, p) {
|
|
21
|
+
return sorted[Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))] ?? 0;
|
|
22
|
+
}
|
|
23
|
+
async function main() {
|
|
24
|
+
const stateDir = mkdtempSync(join(tmpdir(), "hotcell-density-"));
|
|
25
|
+
const image = process.env.SBX_FC_SMOKE_IMAGE ?? "alpine:3.20";
|
|
26
|
+
const driver = new FirecrackerDriver({
|
|
27
|
+
fcBin: process.env.SBX_FC_BIN ?? "firecracker",
|
|
28
|
+
kernel: process.env.SBX_FC_KERNEL ?? "helpers/hotcell-vz/guest/vmlinux-fc",
|
|
29
|
+
rootfs: "helpers/hotcell-vz/guest/rootfs.img",
|
|
30
|
+
stateDir,
|
|
31
|
+
diskGb: 1,
|
|
32
|
+
imageCacheDir: process.env.SBX_FC_IMAGE_CACHE ?? join(homedir(), ".sbx", "fc", "images"),
|
|
33
|
+
});
|
|
34
|
+
const ids = Array.from({ length: N }, (_, i) => `dens${String(i).padStart(3, "0")}`);
|
|
35
|
+
const bootMs = [];
|
|
36
|
+
const failures = [];
|
|
37
|
+
const memBefore = hostAvailableMb();
|
|
38
|
+
const t0 = Date.now();
|
|
39
|
+
console.error(`[density] booting ${N} microVMs (${MEM_MB}MB cap each, concurrency ${CONCURRENCY})…`);
|
|
40
|
+
let next = 0;
|
|
41
|
+
const worker = async () => {
|
|
42
|
+
while (next < ids.length) {
|
|
43
|
+
const id = ids[next++];
|
|
44
|
+
const t = Date.now();
|
|
45
|
+
try {
|
|
46
|
+
await driver.create({ id, image, env: {}, persist: true, limits: { memoryMb: MEM_MB, cpus: 1 } });
|
|
47
|
+
bootMs.push(Date.now() - t);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
failures.push(`${id}: ${err.message.slice(0, 120)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
|
|
55
|
+
const bootWallS = ((Date.now() - t0) / 1000).toFixed(1);
|
|
56
|
+
const memAfter = hostAvailableMb();
|
|
57
|
+
// Every VM must be live and functional, not just booted.
|
|
58
|
+
console.error("[density] exec in every VM…");
|
|
59
|
+
let functional = 0;
|
|
60
|
+
for (const id of ids) {
|
|
61
|
+
if (failures.some((f) => f.startsWith(id)))
|
|
62
|
+
continue;
|
|
63
|
+
try {
|
|
64
|
+
let out = "";
|
|
65
|
+
const code = await driver.exec(id, "echo ok-$HOSTNAME", {}, (e) => {
|
|
66
|
+
if (e.type === "stdout")
|
|
67
|
+
out += e.data;
|
|
68
|
+
});
|
|
69
|
+
if (code === 0 && /ok-/.test(out))
|
|
70
|
+
functional++;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
failures.push(`${id}: exec failed post-boot`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const booted = bootMs.length;
|
|
77
|
+
const perVmMb = booted > 0 ? Math.round((memBefore - memAfter) / booted) : 0;
|
|
78
|
+
bootMs.sort((a, b) => a - b);
|
|
79
|
+
console.log(`\ndensity-bench: ${booted}/${N} booted, ${functional} functional, ${failures.length} failures`);
|
|
80
|
+
console.log(` boot latency: p50 ${pct(bootMs, 50)}ms / p95 ${pct(bootMs, 95)}ms / max ${pct(bootMs, 100)}ms`);
|
|
81
|
+
console.log(` wall time to boot all: ${bootWallS}s`);
|
|
82
|
+
console.log(` host memory: ${memBefore}MB → ${memAfter}MB available (~${perVmMb}MB per live VM)`);
|
|
83
|
+
for (const f of failures.slice(0, 5))
|
|
84
|
+
console.log(` ✗ ${f}`);
|
|
85
|
+
console.error("[density] tearing down…");
|
|
86
|
+
const tDown = Date.now();
|
|
87
|
+
for (const id of ids)
|
|
88
|
+
await driver.destroy(id).catch(() => { });
|
|
89
|
+
console.log(` teardown: ${((Date.now() - tDown) / 1000).toFixed(1)}s; host available now ${hostAvailableMb()}MB`);
|
|
90
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
91
|
+
return failures.length === 0 && functional === booted && booted === N ? 0 : 1;
|
|
92
|
+
}
|
|
93
|
+
main().then((code) => process.exit(code), (err) => {
|
|
94
|
+
console.error("density-bench FAILED:", err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=density-bench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"density-bench.js","sourceRoot":"","sources":["../src/density-bench.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AgentConn } from "./agent.js";
|
|
2
|
+
import { UnsupportedDriver } from "./unsupported.js";
|
|
3
|
+
import type { ProcessLiveness, SandboxStats, StartProcessResult, TcpBridge, TerminalOptions, TerminalSession } from "./types.js";
|
|
4
|
+
import type { ExecEvent, ExecOptions, FileChangeEvent, FileInfo, ListFilesOptions, MkdirOptions, ReadFileOptions, StartProcessOptions, WaitForPortOptions, WatchOptions, WriteFileOptions } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Shared base for microVM drivers whose in-guest work is served by the `sbx-agent`
|
|
7
|
+
* over the A.4 wire protocol (`AgentConn`). Every agent-served `Driver` method —
|
|
8
|
+
* exec, files, watch, processes, preview bridge, terminal, stats, backup/restore —
|
|
9
|
+
* is implemented here **once**, in terms of a single abstract `openConn(id)` that
|
|
10
|
+
* each concrete driver provides for its transport (Apple VZ relays a unix socket
|
|
11
|
+
* to the guest vsock; Firecracker connects to a vsock UDS with a CONNECT
|
|
12
|
+
* handshake). Concrete drivers only implement lifecycle (`create`/`stop`/`start`/
|
|
13
|
+
* `destroy`), `openConn`, and the host probes (`ping`/`hostInfo`) — the whole
|
|
14
|
+
* point of the abstraction is that the surface above the driver is identical.
|
|
15
|
+
*/
|
|
16
|
+
export declare abstract class AgentDriver extends UnsupportedDriver {
|
|
17
|
+
/**
|
|
18
|
+
* Open a fresh agent connection to a running sandbox, resolved once the agent's
|
|
19
|
+
* Hello arrives. Throws `<name>: sandbox <id> is not running` if it isn't live.
|
|
20
|
+
* The connection is single-use unless the caller keeps it (long-lived streams).
|
|
21
|
+
*/
|
|
22
|
+
protected abstract openConn(id: string): Promise<AgentConn>;
|
|
23
|
+
/** Open a connection, run `fn`, always close it. For one-shot request/response ops. */
|
|
24
|
+
protected withConn<T>(id: string, fn: (c: AgentConn) => Promise<T>): Promise<T>;
|
|
25
|
+
exec(id: string, command: string, opts: ExecOptions, onEvent: (e: ExecEvent) => void): Promise<number>;
|
|
26
|
+
writeFile(id: string, opts: WriteFileOptions): Promise<void>;
|
|
27
|
+
readFile(id: string, opts: ReadFileOptions): Promise<string>;
|
|
28
|
+
mkdir(id: string, opts: MkdirOptions): Promise<void>;
|
|
29
|
+
listFiles(id: string, opts: ListFilesOptions): Promise<FileInfo[]>;
|
|
30
|
+
waitForPort(id: string, port: number, opts: WaitForPortOptions): Promise<boolean>;
|
|
31
|
+
/** Watch a path recursively for changes over the agent (poll-based mtime diff),
|
|
32
|
+
* streaming events until `opts.signal` aborts. Holds a connection for its life. */
|
|
33
|
+
watchFiles(id: string, path: string, opts: WatchOptions & {
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}, onEvent: (e: FileChangeEvent) => void): Promise<void>;
|
|
36
|
+
startProcess(id: string, procId: string, command: string, opts: StartProcessOptions): Promise<StartProcessResult>;
|
|
37
|
+
listProcesses(id: string, procs: Array<{
|
|
38
|
+
procId: string;
|
|
39
|
+
pid: number;
|
|
40
|
+
}>): Promise<ProcessLiveness[]>;
|
|
41
|
+
killProcess(id: string, pid: number, signal?: string): Promise<void>;
|
|
42
|
+
streamProcessLogs(id: string, logPath: string, opts: {
|
|
43
|
+
follow: boolean;
|
|
44
|
+
signal: AbortSignal;
|
|
45
|
+
}, onData: (chunk: string) => void): Promise<void>;
|
|
46
|
+
/** Raw byte bridge to `host:port` inside the guest — backs the preview-URL proxy.
|
|
47
|
+
* Each bridge holds its own connection (the duplex is long-lived). */
|
|
48
|
+
openTcpBridge(id: string, port: number, host: string): Promise<TcpBridge>;
|
|
49
|
+
/** Interactive PTY shell — backs the dashboard/CLI terminal. */
|
|
50
|
+
openTerminal(id: string, opts: TerminalOptions): Promise<TerminalSession>;
|
|
51
|
+
/** Live resource snapshot from the guest's /proc + cgroup files (via the agent).
|
|
52
|
+
* cpuPercent is left 0 by the guest — the metrics sampler derives it from the
|
|
53
|
+
* delta of successive cpuTotalUsageNs samples, exactly as for the container driver. */
|
|
54
|
+
stats(id: string): Promise<SandboxStats>;
|
|
55
|
+
/** Tar the guest's /workspace to `tarPath` on the host (over the agent). */
|
|
56
|
+
createBackup(id: string, tarPath: string): Promise<{
|
|
57
|
+
bytes: number;
|
|
58
|
+
}>;
|
|
59
|
+
/** Replace the guest's /workspace with the tar at `tarPath` (cleared first). */
|
|
60
|
+
restoreBackup(id: string, tarPath: string): Promise<void>;
|
|
61
|
+
/** Run a command and collect its full stdout/stderr/exit (for the process shims). */
|
|
62
|
+
protected execCapture(id: string, command: string, opts?: ExecOptions): Promise<{
|
|
63
|
+
stdout: string;
|
|
64
|
+
stderr: string;
|
|
65
|
+
exitCode: number;
|
|
66
|
+
}>;
|
|
67
|
+
}
|