@machinen/runtime 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/API.md +7052 -0
- package/LICENSE +110 -0
- package/README.md +119 -0
- package/dist/chunk-6SP6T537.js +163 -0
- package/dist/chunk-6SP6T537.js.map +1 -0
- package/dist/index.d.ts +2227 -0
- package/dist/index.js +7205 -0
- package/dist/index.js.map +1 -0
- package/dist/mount-server-bin.d.ts +2 -0
- package/dist/mount-server-bin.js +1341 -0
- package/dist/mount-server-bin.js.map +1 -0
- package/package.json +43 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2227 @@
|
|
|
1
|
+
import { Readable, Writable } from 'node:stream';
|
|
2
|
+
|
|
3
|
+
interface VsockExecOptions {
|
|
4
|
+
/** How long to keep retrying the UDS connect. Default 30s. */
|
|
5
|
+
connectTimeoutMs?: number;
|
|
6
|
+
/** Poll interval in ms while retrying. Default 250. */
|
|
7
|
+
retryMs?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Wall-clock ceiling for the spawned command. Default 5 minutes.
|
|
10
|
+
* Pass `null` (or `Infinity`) to disable — appropriate for
|
|
11
|
+
* long-running siblings (dev servers, file watchers, log tailers)
|
|
12
|
+
* that should live for the VM's lifetime. Mirrors `boot({ timeoutMs: null })`.
|
|
13
|
+
*/
|
|
14
|
+
execTimeoutMs?: number | null;
|
|
15
|
+
/** Called with each stdout chunk as it arrives (pass-through tee). */
|
|
16
|
+
onStdout?: (chunk: Buffer) => void;
|
|
17
|
+
/** Called with each stderr chunk as it arrives (pass-through tee). */
|
|
18
|
+
onStderr?: (chunk: Buffer) => void;
|
|
19
|
+
}
|
|
20
|
+
interface VsockExecResult {
|
|
21
|
+
exitCode: number;
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
}
|
|
25
|
+
declare const VsockExec: {
|
|
26
|
+
/**
|
|
27
|
+
* Run `cmd` inside the guest via the exec-agent. The command string
|
|
28
|
+
* is passed verbatim to `sh -c` inside the guest, so shell syntax
|
|
29
|
+
* (pipes, redirection, env) works.
|
|
30
|
+
*
|
|
31
|
+
* Resolves once the agent returns an exit code. Rejects only on I/O
|
|
32
|
+
* failure or timeout — a non-zero exit is a normal result; the
|
|
33
|
+
* caller decides what to do.
|
|
34
|
+
*
|
|
35
|
+
* The TCP UDS → vsock bridge accepts host-side connections even
|
|
36
|
+
* before the guest has bound its side of port 1978 (the agent is
|
|
37
|
+
* started by the workload, which has to wait for the kernel to
|
|
38
|
+
* reach userspace). Such early connections get immediately reset
|
|
39
|
+
* when the bridge tries to forward them. We retry on those resets
|
|
40
|
+
* until the agent is actually listening.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @throws {ExecError} EXEC_AGENT_UNAVAILABLE (retryable) |
|
|
44
|
+
* EXEC_AGENT_TIMEOUT (retryable) | EXEC_PROTOCOL
|
|
45
|
+
*/
|
|
46
|
+
readonly run: (udsPath: string, cmd: string, opts?: VsockExecOptions) => Promise<VsockExecResult>;
|
|
47
|
+
/**
|
|
48
|
+
* PTY-mode session against the exec-agent (#133). Bytes flow
|
|
49
|
+
* bidirectionally between `opts.stdin` (host keystrokes) and
|
|
50
|
+
* `opts.stdout` (workload's pty output); the returned handle's
|
|
51
|
+
* `.resize(cols, rows)` propagates window-size changes to the
|
|
52
|
+
* guest's `ioctl(TIOCSWINSZ)`, and `.cancel()` disconnects (the
|
|
53
|
+
* agent then closes its master fd, which sends SIGHUP to the
|
|
54
|
+
* workload's session and reaps the child).
|
|
55
|
+
*
|
|
56
|
+
* Resolves with `{ exitCode }` once the workload exits and the
|
|
57
|
+
* agent emits the X frame. The stdin listener attaches eagerly —
|
|
58
|
+
* the caller is responsible for putting the host terminal in raw
|
|
59
|
+
* mode beforehand (so Ctrl-C, arrows, etc. reach the guest as
|
|
60
|
+
* untranslated bytes) and restoring it after `result` settles.
|
|
61
|
+
*
|
|
62
|
+
* Connect retries are intentionally absent here: PTY sessions are
|
|
63
|
+
* always against an already-running VM whose agent is up. If the
|
|
64
|
+
* UDS isn't reachable on the first try, that's a real error worth
|
|
65
|
+
* surfacing — not a transient bring-up race like the `run()` path.
|
|
66
|
+
*/
|
|
67
|
+
readonly startPty: (udsPath: string, cmd: string, opts: VsockExecPtyOptions) => VsockExecPtyHandle;
|
|
68
|
+
};
|
|
69
|
+
interface VsockExecPtyOptions {
|
|
70
|
+
/** Initial window size; the guest passes this to forkpty()'s winp. */
|
|
71
|
+
cols: number;
|
|
72
|
+
rows: number;
|
|
73
|
+
/**
|
|
74
|
+
* Host-side input source. Each `data` chunk is forwarded as an
|
|
75
|
+
* `I <n>\n<bytes>` frame. Caller wires `process.stdin` (in raw
|
|
76
|
+
* mode) here for an interactive shell.
|
|
77
|
+
*/
|
|
78
|
+
stdin: Readable;
|
|
79
|
+
/**
|
|
80
|
+
* Host-side sink for PTY master output (`O <n>\n<bytes>` frames).
|
|
81
|
+
* Caller wires `process.stdout`.
|
|
82
|
+
*/
|
|
83
|
+
stdout: Writable;
|
|
84
|
+
/** Connect timeout (ms). Default 5000 — agent should already be up. */
|
|
85
|
+
connectTimeoutMs?: number;
|
|
86
|
+
}
|
|
87
|
+
interface VsockExecPtyResult {
|
|
88
|
+
exitCode: number;
|
|
89
|
+
}
|
|
90
|
+
interface VsockExecPtyHandle {
|
|
91
|
+
/** Resolves with the workload's exit code once X arrives. */
|
|
92
|
+
readonly result: Promise<VsockExecPtyResult>;
|
|
93
|
+
/** Send a TIOCSWINSZ update. Hook from host's SIGWINCH. */
|
|
94
|
+
resize(cols: number, rows: number): void;
|
|
95
|
+
/** Disconnect; agent will SIGHUP the workload. */
|
|
96
|
+
cancel(): void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface ChunkLogEvent {
|
|
100
|
+
/**
|
|
101
|
+
* Where the chunk came from:
|
|
102
|
+
* - `guest-console` — kernel / PL011 console bytes (VMM stderr)
|
|
103
|
+
* - `exec-stdout` — stdout of an exec invocation
|
|
104
|
+
* - `exec-stderr` — stderr of an exec invocation
|
|
105
|
+
*/
|
|
106
|
+
source: "guest-console" | "exec-stdout" | "exec-stderr";
|
|
107
|
+
/** Command string; set when `source` is `exec-stdout` or `exec-stderr`. */
|
|
108
|
+
cmd?: string;
|
|
109
|
+
/** Raw bytes as they arrive — not line-split, not decoded. */
|
|
110
|
+
chunk: Buffer;
|
|
111
|
+
}
|
|
112
|
+
interface PhaseLogEvent {
|
|
113
|
+
source: "phase";
|
|
114
|
+
/** Which runtime entry point produced these phases. */
|
|
115
|
+
kind: "boot" | "provision" | "snapshot" | "restore";
|
|
116
|
+
/** Phase name → wall-clock ms. Insertion order = timeline order. */
|
|
117
|
+
phases: ReadonlyMap<string, number>;
|
|
118
|
+
/** Wall-clock between PhaseTimer construction and flush. */
|
|
119
|
+
totalMs: number;
|
|
120
|
+
}
|
|
121
|
+
type LogEvent = ChunkLogEvent | PhaseLogEvent;
|
|
122
|
+
type OnLog = (evt: LogEvent) => void;
|
|
123
|
+
|
|
124
|
+
interface AttachOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Look up a VM by the host pid of its VMM process. Kernel-unique
|
|
127
|
+
* while alive; mutually exclusive with `name`. Exactly one of
|
|
128
|
+
* `pid` / `name` is required.
|
|
129
|
+
*/
|
|
130
|
+
pid?: number;
|
|
131
|
+
/** Look up a VM by the name passed to `boot({ name })`. */
|
|
132
|
+
name?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Streaming log callback — fires for every byte of output from execs
|
|
135
|
+
* made through the returned handle. See #83. Guest kernel console is
|
|
136
|
+
* not available on attach handles (it belongs to the process that
|
|
137
|
+
* called `boot()`), so only `exec-stdout` / `exec-stderr` sources fire.
|
|
138
|
+
*/
|
|
139
|
+
onLog?: OnLog;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reconnect to a running VM registered by an earlier `boot()` call
|
|
143
|
+
* (possibly from a different process). Returns a `VmHandle` that can
|
|
144
|
+
* `exec()`, `snapshot()`, and `kill()` the remote VM via the vsock
|
|
145
|
+
* bridge the booter left behind.
|
|
146
|
+
*
|
|
147
|
+
* Attached handles have inert stream properties (`stdin`/`stdout`/
|
|
148
|
+
* `stderr` are empty `PassThrough`s) — those belong to the original
|
|
149
|
+
* booter. `output()`/`errorOutput()` resolve with the empty string.
|
|
150
|
+
* `wait()` polls the pid rather than listening for `exit`.
|
|
151
|
+
*
|
|
152
|
+
* @throws {RegistryError} REGISTRY_VM_NOT_FOUND
|
|
153
|
+
*/
|
|
154
|
+
declare function attach(opts: AttachOptions): Promise<VmHandle>;
|
|
155
|
+
|
|
156
|
+
interface BootOptions {
|
|
157
|
+
/**
|
|
158
|
+
* Path to a rootfs tarball to boot from (e.g. the output of
|
|
159
|
+
* `provision()`, or `rootfs-debian-arm64.tar.gz` shipped in releases).
|
|
160
|
+
* Paired with `cmd` — both required, or neither (test-mode binary
|
|
161
|
+
* boots and snapshot-only restores both skip initramfs packing).
|
|
162
|
+
*/
|
|
163
|
+
image?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Command to run inside the guest. Packed into the synthesized
|
|
166
|
+
* `/machinen-config.json`. Paired with `image` — both required, or
|
|
167
|
+
* neither.
|
|
168
|
+
*/
|
|
169
|
+
cmd?: string[];
|
|
170
|
+
/**
|
|
171
|
+
* Env vars exposed to the guest workload. Packed into the synthesized
|
|
172
|
+
* `/machinen-config.json`. Distinct from `vmmEnv`, which only affects
|
|
173
|
+
* the host-side VMM process.
|
|
174
|
+
*/
|
|
175
|
+
env?: Record<string, string>;
|
|
176
|
+
/**
|
|
177
|
+
* Working directory for the guest cmd. Lands as `cwd` in the
|
|
178
|
+
* synthesized `/machinen-config.json`; `/init` calls `chdir()` to
|
|
179
|
+
* this path before exec'ing the cmd. Useful with `mount` /
|
|
180
|
+
* `liveMounts` to land directly inside the share (e.g.
|
|
181
|
+
* `guestCwd: "/mnt/workspace"`).
|
|
182
|
+
*
|
|
183
|
+
* Must be absolute. Throws `BOOT_CWD_INVALID` for relative paths or
|
|
184
|
+
* paths containing NULs. Same precedence as `cmd`/`env`: an
|
|
185
|
+
* image-baked `cwd` is overridden by this field when both are set.
|
|
186
|
+
*/
|
|
187
|
+
guestCwd?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Attach a scratch virtio-blk device (`/dev/vdb`, or `/dev/vda` on
|
|
190
|
+
* pre-#114 layouts) so this VM can be CRIU-snapshotted later via
|
|
191
|
+
* `vm.snapshot()`. Three forms:
|
|
192
|
+
*
|
|
193
|
+
* - `undefined` (default) — the runtime auto-allocates a per-boot
|
|
194
|
+
* ~8 GiB sparse scratch in `tmpdir()` and unlinks it on VM exit.
|
|
195
|
+
* Disk usage stays at zero until the guest writes; the upside is
|
|
196
|
+
* every booted VM is snapshotable without re-booting. See #50.
|
|
197
|
+
*
|
|
198
|
+
* - `'<path>'` — caller-managed file. Used as-is (must exist).
|
|
199
|
+
* Used by `restore()` to attach a tar archive of the bundle's
|
|
200
|
+
* CRIU images on `/dev/vdb`; the guest's
|
|
201
|
+
* `/sbin/machinen-restore` untars it and runs `criu restore`.
|
|
202
|
+
* The runtime synthesizes `cmd: ['/sbin/machinen-restore']` if
|
|
203
|
+
* no other cmd is given.
|
|
204
|
+
*
|
|
205
|
+
* - `false` — opt out entirely. No `/dev/vdb` attached. Use when
|
|
206
|
+
* you don't need snapshot capability and want to skip the
|
|
207
|
+
* (sparse, but still nonzero) inode allocation — typical for
|
|
208
|
+
* fast-cycling test boots.
|
|
209
|
+
*/
|
|
210
|
+
snapshot?: string | false;
|
|
211
|
+
/**
|
|
212
|
+
* Boot the guest with the rootfs on a virtio-blk device (`/dev/vda`)
|
|
213
|
+
* instead of inflating the whole rootfs into a RAM-backed tmpfs via
|
|
214
|
+
* the initramfs. See #114.
|
|
215
|
+
*
|
|
216
|
+
* Default: `true` whenever `image` is set. The runtime materializes
|
|
217
|
+
* an ext4 image from `image` (cached at
|
|
218
|
+
* `~/.cache/machinen/rootfs/<sha256>.img`) and attaches it as the
|
|
219
|
+
* rootdisk; the guest's `/init` mounts + chroots into it before
|
|
220
|
+
* running the user cmd. Materialization needs `mke2fs` (or
|
|
221
|
+
* `mkfs.ext4`) on PATH — `brew install e2fsprogs` on macOS, the
|
|
222
|
+
* `e2fsprogs` package on Linux.
|
|
223
|
+
*
|
|
224
|
+
* - `string` — path to a pre-built ext4 `.img` file to attach
|
|
225
|
+
* directly. Skips the materialize step + cache.
|
|
226
|
+
* - `false` — opt out: keep the cpio-as-rootfs path. The whole
|
|
227
|
+
* rootfs lands in a tmpfs at boot (RAM scales ~8×
|
|
228
|
+
* with rootfs size). Mostly an escape hatch for
|
|
229
|
+
* tooling that doesn't need disk-backed semantics
|
|
230
|
+
* (e.g. `provision()` itself).
|
|
231
|
+
*/
|
|
232
|
+
rootDisk?: boolean | string;
|
|
233
|
+
/**
|
|
234
|
+
* Absolute target size (bytes) for the materialized rootdisk image.
|
|
235
|
+
* Defaults to `max(2 GiB, treeBytes * 2.5)` — generous enough that
|
|
236
|
+
* boot-time `npm install -g <large package>` / `apt install ...`
|
|
237
|
+
* land without ENOSPC. Bump this for workloads that write more
|
|
238
|
+
* (e.g. 8 GiB for a build tree, 16 GiB for a model cache).
|
|
239
|
+
*
|
|
240
|
+
* The host file is sparse — unused capacity costs nothing on disk
|
|
241
|
+
* until the guest writes. The guest's online ext4 grow (in /init)
|
|
242
|
+
* resizes the on-disk filesystem to fill the file on every boot,
|
|
243
|
+
* so bumping this against an existing cached image works without
|
|
244
|
+
* a rematerialize.
|
|
245
|
+
*
|
|
246
|
+
* Ignored when `rootDisk` is a string path (the caller-provided
|
|
247
|
+
* image is taken as-is) or `rootDisk: false`. See #131.
|
|
248
|
+
*/
|
|
249
|
+
rootDiskSizeBytes?: number;
|
|
250
|
+
/**
|
|
251
|
+
* Optional name to register this VM under (`attach({ name })`
|
|
252
|
+
* lookup key). Path-shaped strings ("worker/9012") are allowed.
|
|
253
|
+
* Names are unique while live — `boot()` throws
|
|
254
|
+
* `REGISTRY_NAME_IN_USE` if another VM already holds the name.
|
|
255
|
+
*/
|
|
256
|
+
name?: string;
|
|
257
|
+
/**
|
|
258
|
+
* Bookkeeping: absolute path to the snapshot bundle this VM was
|
|
259
|
+
* forked from. Set by `restore({ snapDir })`; visible in
|
|
260
|
+
* `machinen ls`. Plain `boot()` leaves it undefined.
|
|
261
|
+
*/
|
|
262
|
+
forkedFrom?: string;
|
|
263
|
+
/**
|
|
264
|
+
* A single host directory exposed to the guest as a writable
|
|
265
|
+
* filesystem rooted under `/mnt/<guest>/`. Guest writes survive
|
|
266
|
+
* snapshot/restore but never leak to the host source dir.
|
|
267
|
+
*
|
|
268
|
+
* Implementation (#272): the runtime builds a content-addressed
|
|
269
|
+
* read-only squashfs lower from `host` (cached in
|
|
270
|
+
* `~/.cache/machinen/mountdisk/`) and a per-VM ext4 sparse upper
|
|
271
|
+
* (4 GiB by default; bump via `mountDiskUpperSizeBytes`). Both
|
|
272
|
+
* files are fd-passed to the VMM, surfacing inside the guest as
|
|
273
|
+
* `/dev/vdc` (RO) and `/dev/vdd` (RW); /init layers them as a
|
|
274
|
+
* single overlayfs at `<guest>/`. The squashfs lower stays
|
|
275
|
+
* sealed for the VM's lifetime; writes go to the upper, which
|
|
276
|
+
* is reflinked into snapshot bundles so forks see prior writes
|
|
277
|
+
* without touching the source dir.
|
|
278
|
+
*
|
|
279
|
+
* Trade-off vs. `liveMount`: `mount` is copy-into-disk-image (no
|
|
280
|
+
* runtime channel back to the host source dir, snapshots cleanly,
|
|
281
|
+
* but writes don't propagate to the host); `liveMount` is a live
|
|
282
|
+
* vsock-FUSE pass-through (writes land on the host, doesn't survive
|
|
283
|
+
* snapshot/restore). Pick `mount` for inputs the guest may modify
|
|
284
|
+
* but the host shouldn't see; `liveMount` for shared scratch.
|
|
285
|
+
*
|
|
286
|
+
* See #64 (original `mount`), #78 (`liveMount`), #114 (rootdisk
|
|
287
|
+
* relocation; same shape), #272 (this overlay relocation).
|
|
288
|
+
*/
|
|
289
|
+
mount?: {
|
|
290
|
+
host: string;
|
|
291
|
+
guest: string;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Absolute target size (bytes) for the per-VM ext4 RW upper of
|
|
295
|
+
* the `--mount` overlay (#272). Sparse, so unused capacity costs
|
|
296
|
+
* nothing on the host disk. Mirrors `rootDiskSizeBytes` (#131) —
|
|
297
|
+
* over-provision so the guest has plenty of room to write into
|
|
298
|
+
* the mount before hitting ENOSPC.
|
|
299
|
+
*
|
|
300
|
+
* Must be a positive multiple of 4096. Default 4 GiB.
|
|
301
|
+
*/
|
|
302
|
+
mountDiskUpperSizeBytes?: number;
|
|
303
|
+
/**
|
|
304
|
+
* Internal: when set, skips the squashfs+ext4 materialization
|
|
305
|
+
* pipeline and uses pre-existing lower/upper files (typically the
|
|
306
|
+
* ones a snapshot bundle carries). Used by `restore()` to
|
|
307
|
+
* reconstruct the overlay without re-running `mksquashfs` on the
|
|
308
|
+
* host source dir (which may not exist on the restoring host).
|
|
309
|
+
*
|
|
310
|
+
* The runtime reflinks `upperPath` into a per-VM path so guest
|
|
311
|
+
* writes don't mutate the bundle in-place.
|
|
312
|
+
*
|
|
313
|
+
* @internal
|
|
314
|
+
*/
|
|
315
|
+
_restoreMountDisk?: {
|
|
316
|
+
guest: string;
|
|
317
|
+
lowerPath: string;
|
|
318
|
+
upperPath: string;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Host directories exposed to the guest as live-share FUSE mounts
|
|
322
|
+
* (#78). Unlike `mount` (copy-once into the boot rootfs), these stay
|
|
323
|
+
* connected to the host: the guest reads on demand via a vsock FUSE
|
|
324
|
+
* relay, and nothing is copied at boot. `mode` defaults to `"rw"` —
|
|
325
|
+
* guest writes land on the host (#151, #156). Set `"ro"` for a
|
|
326
|
+
* one-way share (host caches, untrusted guests).
|
|
327
|
+
*
|
|
328
|
+
* Each guest path must live under `/mnt/` (same rule as `mount`).
|
|
329
|
+
* Repeatable; each entry gets its own vsock port.
|
|
330
|
+
*
|
|
331
|
+
* Snapshot / restore / fork (#273): liveMount has no guest-side
|
|
332
|
+
* state worth checkpointing — reads come from the host on demand,
|
|
333
|
+
* writes (in `"rw"`) land on the host immediately. The runtime
|
|
334
|
+
* unmounts each mount before CRIU dumps, then re-establishes a
|
|
335
|
+
* fresh window on the other side: for `vm.snapshot({ leaveRunning:
|
|
336
|
+
* true })` and `vm.fork()` the source's workload sees `/mnt/<guest>/`
|
|
337
|
+
* disappear for the dump duration (typically seconds, scales with
|
|
338
|
+
* memory size) before reappearing under fresh server state. Open
|
|
339
|
+
* fds across that window see EBADF on next syscall — same shape
|
|
340
|
+
* as "don't snapshot during a database write." Workloads that
|
|
341
|
+
* quiesce before snapshot are unaffected.
|
|
342
|
+
*
|
|
343
|
+
* Concurrent writes from multiple forks against the same host
|
|
344
|
+
* directory are no different from any other shared filesystem —
|
|
345
|
+
* the runtime re-establishes the window per-VM but doesn't
|
|
346
|
+
* coordinate writes between siblings. If two forks need
|
|
347
|
+
* non-overlapping write surfaces, point each at a distinct
|
|
348
|
+
* `host` path or use `mount` (copy-once, per-VM upper).
|
|
349
|
+
*
|
|
350
|
+
* Restore on a host where the recorded `host` path doesn't exist:
|
|
351
|
+
* fails loudly via `BOOT_MOUNT_HOST_NOT_FOUND`. Pass
|
|
352
|
+
* `restore({ liveMounts: [...] })` to override per-`guest` —
|
|
353
|
+
* each override entry's `guest` must match a recorded entry.
|
|
354
|
+
*
|
|
355
|
+
* Security note: a live-share mount gives a compromised guest a
|
|
356
|
+
* persistent channel back to the host filesystem. Containment keeps
|
|
357
|
+
* that bounded to the configured host root. `mount` (copy-once) has
|
|
358
|
+
* no such runtime channel and is strictly safer — prefer it for
|
|
359
|
+
* inputs you don't need write-through on.
|
|
360
|
+
*/
|
|
361
|
+
liveMounts?: Array<{
|
|
362
|
+
host: string;
|
|
363
|
+
guest: string;
|
|
364
|
+
mode?: "ro" | "rw";
|
|
365
|
+
}>;
|
|
366
|
+
/**
|
|
367
|
+
* Host -> guest TCP port forwards installed via gvproxy's control
|
|
368
|
+
* API. Each entry maps `hostPort` on the host (bound to `hostAddr`,
|
|
369
|
+
* default `127.0.0.1`) to `guestPort` inside the guest.
|
|
370
|
+
*/
|
|
371
|
+
portForward?: Array<{
|
|
372
|
+
hostPort: number;
|
|
373
|
+
guestPort: number;
|
|
374
|
+
hostAddr?: string;
|
|
375
|
+
}>;
|
|
376
|
+
/**
|
|
377
|
+
* Absolute or cwd-relative path to the VMM binary. Optional —
|
|
378
|
+
* if omitted, `boot()` resolves it via `resolveVmmBinary()`.
|
|
379
|
+
*/
|
|
380
|
+
binary?: string;
|
|
381
|
+
/** Working directory for the VMM (for finding fixture files). */
|
|
382
|
+
cwd?: string;
|
|
383
|
+
/** Extra argv for the VMM. */
|
|
384
|
+
args?: string[];
|
|
385
|
+
/** Path to the guest kernel Image. Forwarded as `MACHINEN_KERNEL`. */
|
|
386
|
+
kernel?: string;
|
|
387
|
+
/** Path to the guest device-tree blob. Forwarded as `MACHINEN_DTB`. */
|
|
388
|
+
dtb?: string;
|
|
389
|
+
/**
|
|
390
|
+
* Guest RAM ceiling, in MiB (decimal integer; no unit suffixes). The
|
|
391
|
+
* VMM reads this as `MACHINEN_MEMORY` (#263 phase A). Defaults to
|
|
392
|
+
* `min(host_ram_mib / 2, 16384)` with a floor of 512 — sized for
|
|
393
|
+
* typical dev workloads while leaving the host responsive. The
|
|
394
|
+
* ceiling is approximately free until the guest touches a page (see
|
|
395
|
+
* `packages/microvm/docs/memory.md`), so over-provisioning costs
|
|
396
|
+
* little until phase B's balloon lands and lets it actually shrink.
|
|
397
|
+
*
|
|
398
|
+
* This is documented as a debug knob — most workloads should never
|
|
399
|
+
* need to set it.
|
|
400
|
+
*/
|
|
401
|
+
memory?: number;
|
|
402
|
+
/**
|
|
403
|
+
* Wrap the VMM through the parent-death shim so it dies with this
|
|
404
|
+
* runtime process. Default true — the right answer for the common
|
|
405
|
+
* "boot, do work, exit" CLI flow.
|
|
406
|
+
*
|
|
407
|
+
* Set to false when the VMM is supposed to outlive the spawning
|
|
408
|
+
* process. `vm.fork()` (#216) sets this so the forked sibling
|
|
409
|
+
* survives `cli fork` returning. Without it, the kqueue-watching
|
|
410
|
+
* shim catches the CLI exit and SIGTERMs the fork mid-startup.
|
|
411
|
+
*/
|
|
412
|
+
pdeathsig?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Milliseconds to wait in `wait()` before giving up and rejecting.
|
|
415
|
+
* Defaults to 60s. Pass `null` to wait forever.
|
|
416
|
+
*/
|
|
417
|
+
timeoutMs?: number | null;
|
|
418
|
+
/**
|
|
419
|
+
* Env passed to the VMM process on the host side (not exposed to the
|
|
420
|
+
* guest workload). Mostly for dev/test flags like `MACHINEN_BOOT_TEST`.
|
|
421
|
+
*/
|
|
422
|
+
vmmEnv?: Record<string, string>;
|
|
423
|
+
/**
|
|
424
|
+
* Streaming log callback — fires for every byte of guest output:
|
|
425
|
+
* kernel console (VMM stderr) and every exec invocation made through
|
|
426
|
+
* the returned handle. See `LogEvent.source` to tell them apart. See
|
|
427
|
+
* #83. For per-call output-only tees on a single exec, use
|
|
428
|
+
* `vm.exec({ onStdout, onStderr })` instead.
|
|
429
|
+
*/
|
|
430
|
+
onLog?: OnLog;
|
|
431
|
+
/**
|
|
432
|
+
* Detach the VMM from the runtime parent so the parent can exit
|
|
433
|
+
* while the VM keeps running (issue #150 phase 2). When set, `boot()`
|
|
434
|
+
* blocks only until the guest produces its first console byte
|
|
435
|
+
* (readiness signal) and then resolves a handle whose `.wait()` /
|
|
436
|
+
* `.output()` no longer reflect the live VM — the parent has unrefed
|
|
437
|
+
* the child and is free to exit.
|
|
438
|
+
*
|
|
439
|
+
* Forces `pdeathsig: false` (otherwise the parent's exit kills the
|
|
440
|
+
* VMM, defeating the purpose). Compatible with every other boot
|
|
441
|
+
* option: gvproxy + live-mount FUSE servers spawn as detached
|
|
442
|
+
* daemons wrapped through `pdeathsig --watch-pid <vmm>`, and `mount`
|
|
443
|
+
* (squashfs+ext4 overlay) is fd-passed to the VMM at spawn so the
|
|
444
|
+
* supervisor holds no live state afterwards.
|
|
445
|
+
*
|
|
446
|
+
* Cleanup of per-boot reflink disks, bundle dirs, and vsock UDS
|
|
447
|
+
* directories normally happens in the parent's `child.once("exit")`
|
|
448
|
+
* hook. After detach the parent is gone, so those leak until the
|
|
449
|
+
* follow-up `machinen gc` / `machinen stop` commands (PR2 of #150)
|
|
450
|
+
* land. Use `--detached` only when you understand that trade-off.
|
|
451
|
+
*
|
|
452
|
+
* Reattach with `attach({ name | pid })` from another process —
|
|
453
|
+
* the registry entry stays live, the vsock UDS is still listening.
|
|
454
|
+
*/
|
|
455
|
+
detached?: boolean;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Boot a microVM and return a handle to interact with it.
|
|
459
|
+
*
|
|
460
|
+
* @throws {BootError} BOOT_VMM_MISSING | BOOT_VMM_PACKAGE_BROKEN |
|
|
461
|
+
* BOOT_IMAGE_NOT_FOUND | BOOT_SNAPSHOT_NOT_FOUND |
|
|
462
|
+
* BOOT_KERNEL_NOT_FOUND | BOOT_DTB_NOT_FOUND |
|
|
463
|
+
* BOOT_CMD_WITHOUT_IMAGE | BOOT_CMD_MISSING |
|
|
464
|
+
* BOOT_MOUNT_INVALID | BOOT_MOUNT_HOST_NOT_FOUND |
|
|
465
|
+
* BOOT_PORT_FORWARD_INVALID | BOOT_PORT_FORWARD_CONFLICT |
|
|
466
|
+
* BOOT_PORT_FORWARD_NO_GVPROXY | BOOT_PORT_FORWARD_IN_USE |
|
|
467
|
+
* BOOT_PACK_FAILED
|
|
468
|
+
*/
|
|
469
|
+
declare function boot(opts?: BootOptions): Promise<VmHandle>;
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* A caller-provided `liveMounts` entry after validation, with the
|
|
473
|
+
* vsock port + host UDS path allocated. Threaded from `boot()` into
|
|
474
|
+
* the initramfs packer so the config and the host servers agree on
|
|
475
|
+
* ports and guest paths.
|
|
476
|
+
*/
|
|
477
|
+
interface ResolvedLiveMount {
|
|
478
|
+
host: string;
|
|
479
|
+
guest: string;
|
|
480
|
+
port: number;
|
|
481
|
+
udsPath: string;
|
|
482
|
+
/**
|
|
483
|
+
* Per-mount stats file the detached helper writes its
|
|
484
|
+
* bytesServedOnPagesImg counter to. Lives next to `udsPath` under
|
|
485
|
+
* `vsockTempDir` so the supervisor's cleanupPaths sweep covers it.
|
|
486
|
+
*/
|
|
487
|
+
statsPath: string;
|
|
488
|
+
mode: "ro" | "rw";
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Build the synthesized `machinen-config.json` payload that /init
|
|
492
|
+
* reads at boot. Pure: takes the already-merged effective cmd/env
|
|
493
|
+
* plus the cwd inputs (user's guestCwd overrides image-baked cwd) and
|
|
494
|
+
* the live-mount ports.
|
|
495
|
+
*
|
|
496
|
+
* Exposed for tests; `synthesizeAndPackBundle` is the only production
|
|
497
|
+
* caller.
|
|
498
|
+
*
|
|
499
|
+
* @internal
|
|
500
|
+
*/
|
|
501
|
+
declare function buildMachinenConfig(input: {
|
|
502
|
+
cmd: string[];
|
|
503
|
+
env: Record<string, string>;
|
|
504
|
+
guestCwd?: string;
|
|
505
|
+
imageCwd?: string;
|
|
506
|
+
liveMounts: ResolvedLiveMount[];
|
|
507
|
+
}): Record<string, unknown>;
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Time-to-first-output-byte for a boot. Useful for measuring how
|
|
511
|
+
* much the snapshot path is (or isn't) buying us.
|
|
512
|
+
*/
|
|
513
|
+
declare function measureFirstByte(vm: VmHandle): Promise<number>;
|
|
514
|
+
|
|
515
|
+
declare function autoSizeMemoryMib(hostBytes?: number): number;
|
|
516
|
+
declare function validateMemoryMib(mib: number): number;
|
|
517
|
+
/**
|
|
518
|
+
* Locate the VMM binary using the same lookup order as `@machinen/cli`:
|
|
519
|
+
* 1. `MACHINEN_VMM` env var (dev-mode override)
|
|
520
|
+
* 2. `require.resolve("@machinen/vmm-<arch>-<os>")` → `binary` export
|
|
521
|
+
*
|
|
522
|
+
* Callers can pass an explicit `binary` to `boot()` to bypass this.
|
|
523
|
+
*
|
|
524
|
+
* @throws {BootError} BOOT_VMM_MISSING | BOOT_VMM_PACKAGE_BROKEN
|
|
525
|
+
*/
|
|
526
|
+
declare function resolveVmmBinary(): string;
|
|
527
|
+
/**
|
|
528
|
+
* Build the shell pipeline that `vm.writeFile()` ships through the
|
|
529
|
+
* exec-agent. Stays single-line so it works against the legacy EXEC
|
|
530
|
+
* opcode too (no need for the EXEC2 multi-line frame, which only newer
|
|
531
|
+
* agents understand).
|
|
532
|
+
*
|
|
533
|
+
* Encoding: contents go over the wire as base64 inside an `echo … |
|
|
534
|
+
* base64 -d` pipe, so any byte sequence (binary, newlines, quotes) is
|
|
535
|
+
* safe. `mkdir -p` runs first when `recursive` (the default).
|
|
536
|
+
*
|
|
537
|
+
* Returns a single cmd string. For payloads that would exceed Linux's
|
|
538
|
+
* `MAX_ARG_STRLEN` (128 KB per argv element) once shell-wrapped, use
|
|
539
|
+
* `buildWriteFileCmds` instead — `vm.writeFile()` does.
|
|
540
|
+
*/
|
|
541
|
+
declare function buildWriteFileCmd(guestPath: string, contents: Buffer | string, opts?: WriteFileOptions): string;
|
|
542
|
+
/**
|
|
543
|
+
* Plan the cmd sequence `vm.writeFile()` issues for `contents`.
|
|
544
|
+
* Small payloads (base64 ≤ `WRITE_FILE_B64_CHUNK_BYTES`) collapse to a
|
|
545
|
+
* single cmd identical to `buildWriteFileCmd`'s output. Larger payloads
|
|
546
|
+
* stage the base64 to /tmp in append-chunks and then decode once at the
|
|
547
|
+
* end, so no individual cmd line approaches `MAX_ARG_STRLEN`.
|
|
548
|
+
*/
|
|
549
|
+
declare function buildWriteFileCmds(guestPath: string, contents: Buffer | string, opts?: WriteFileOptions): string[];
|
|
550
|
+
declare function collect(stream: Readable, capBytes?: number): Promise<string>;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Shape of the optional `./machinen-config.json` baked into a rootfs
|
|
554
|
+
* tarball by `provision({ cmd, env })`. `boot()` reads it via
|
|
555
|
+
* `readImageConfig()` so callers don't need to re-pass `cmd`/`env` on
|
|
556
|
+
* every boot. `warmImageConfigCache()` accepts the same shape so a
|
|
557
|
+
* tarball-producing tool can pre-populate the lookup cache.
|
|
558
|
+
*/
|
|
559
|
+
type ImageConfig = {
|
|
560
|
+
cmd?: string[];
|
|
561
|
+
env?: Record<string, string>;
|
|
562
|
+
cwd?: string;
|
|
563
|
+
};
|
|
564
|
+
/**
|
|
565
|
+
* Pre-populate the image-config cache for a freshly-written tarball.
|
|
566
|
+
* Lets `provision()` (and other tarball producers) skip the slow
|
|
567
|
+
* `tar -xzOf` lookup that the next `boot()` would otherwise pay —
|
|
568
|
+
* see #233. Best-effort: a missing/unwritable cache dir just falls
|
|
569
|
+
* back to the slow path on the next boot.
|
|
570
|
+
*
|
|
571
|
+
* Call AFTER the tarball is on disk (so size+mtime match what the
|
|
572
|
+
* cache key will be on read), passing exactly the config that was
|
|
573
|
+
* baked into the tarball's `./machinen-config.json` (or `null` when
|
|
574
|
+
* none was baked).
|
|
575
|
+
*/
|
|
576
|
+
declare function warmImageConfigCache(imagePath: string, config: ImageConfig | null): void;
|
|
577
|
+
|
|
578
|
+
interface RestoreOptions extends Omit<BootOptions, "snapshot" | "image" | "cmd" | "name"> {
|
|
579
|
+
/**
|
|
580
|
+
* Snapshot bundle directory produced by `vm.snapshot()`.
|
|
581
|
+
* Must contain `img/<crius>` and `meta.json`.
|
|
582
|
+
*/
|
|
583
|
+
snapDir: string;
|
|
584
|
+
/**
|
|
585
|
+
* Override the rootfs image used for the restore boot. Defaults
|
|
586
|
+
* to whatever caller passes through `image`-equivalent — but
|
|
587
|
+
* `restore()` always needs a base rootfs in the initramfs to
|
|
588
|
+
* carry /sbin/machinen-restore + criu. Most callers pass the
|
|
589
|
+
* release rootfs path here.
|
|
590
|
+
*/
|
|
591
|
+
image?: string;
|
|
592
|
+
/**
|
|
593
|
+
* Optional explicit name for the restored VM. When omitted, the
|
|
594
|
+
* fork is auto-named `<sourceName>/<pid>` after spawn so it stays
|
|
595
|
+
* unique under the source's namespace.
|
|
596
|
+
*/
|
|
597
|
+
name?: string;
|
|
598
|
+
/**
|
|
599
|
+
* Opt into lazy-pages restore — bundle is vsock-FUSE-mounted into
|
|
600
|
+
* the guest read-only and `criu restore --lazy-pages` faults pages
|
|
601
|
+
* on demand (#266). Default false: the runtime packs the CRIU
|
|
602
|
+
* image into a tar on `/dev/vdb`, the guest's
|
|
603
|
+
* `/sbin/machinen-restore` untars it into tmpfs, and CRIU does an
|
|
604
|
+
* eager load.
|
|
605
|
+
*
|
|
606
|
+
* Eager is still the default because lazy bundles a host-side FUSE
|
|
607
|
+
* server that doesn't compose with `--detach` (#150 phase 3). The
|
|
608
|
+
* historical second blocker — runaway free-page-reporting under
|
|
609
|
+
* lazy — is fixed in #290 by the in-tree kernel patch that stops
|
|
610
|
+
* the buddy allocator from clearing the Reported flag during a
|
|
611
|
+
* merge.
|
|
612
|
+
*/
|
|
613
|
+
lazy?: boolean;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Restore a microVM from a snapshot bundle produced by
|
|
617
|
+
* `vm.snapshot({ outDir })`. Reads the bundle's `meta.json` to
|
|
618
|
+
* recover the source name, tars the CRIU image directory into a
|
|
619
|
+
* temporary archive, then `boot()`s with that archive attached as
|
|
620
|
+
* the scratch block device — the guest's `/sbin/machinen-restore`
|
|
621
|
+
* untars `/dev/vdb` into tmpfs and runs `criu restore` against the
|
|
622
|
+
* extracted images.
|
|
623
|
+
*
|
|
624
|
+
* The boot knobs:
|
|
625
|
+
*
|
|
626
|
+
* - `snapshot: <tar>` attaches the bundle archive as /dev/vdb
|
|
627
|
+
* - `name: <sourceName>/<pid>` auto-named fork (unless caller
|
|
628
|
+
* passed `name`)
|
|
629
|
+
* - `forkedFrom: <snapDir>` lineage for `machinen ls`
|
|
630
|
+
*
|
|
631
|
+
* Live-share mounts (#273): bundles created with active `liveMounts`
|
|
632
|
+
* carry only the `{guest, host, mode}` triples in `meta.liveMounts`
|
|
633
|
+
* — no bytes. By default `restore()` re-establishes each recorded
|
|
634
|
+
* mount as-is; the boot-time `existsSync(host)` check fails loudly
|
|
635
|
+
* (BOOT_MOUNT_HOST_NOT_FOUND) if the recorded host path is gone on
|
|
636
|
+
* the restoring host. Pass `liveMounts: [...]` to override per-
|
|
637
|
+
* `guest` (e.g. cross-host restore with remapped paths). Each
|
|
638
|
+
* override entry's `guest` MUST match a recorded one — the field is
|
|
639
|
+
* an override map, not an additive list. Bundles predating this
|
|
640
|
+
* field have `meta.liveMounts === undefined`; in that case
|
|
641
|
+
* `opts.liveMounts` is forwarded as-is for backward compatibility.
|
|
642
|
+
*
|
|
643
|
+
* @throws {BootError} BOOT_SNAPSHOT_NOT_FOUND if `<snapDir>/img/`
|
|
644
|
+
* is missing or empty.
|
|
645
|
+
* @throws {BootError} BOOT_LIVE_MOUNT_OVERRIDE_UNKNOWN if an entry in
|
|
646
|
+
* `opts.liveMounts` has a `guest` that doesn't appear in the
|
|
647
|
+
* bundle's `meta.liveMounts`.
|
|
648
|
+
*/
|
|
649
|
+
declare function restore(opts: RestoreOptions): Promise<VmHandle>;
|
|
650
|
+
|
|
651
|
+
declare const _internal: {
|
|
652
|
+
collect: typeof collect;
|
|
653
|
+
CONSOLE_TAIL_BYTES: number;
|
|
654
|
+
validateMemoryMib: typeof validateMemoryMib;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
interface VmHandle {
|
|
658
|
+
/**
|
|
659
|
+
* PID of the host-side VMM process — primary identifier across
|
|
660
|
+
* boot/attach. Kernel-unique while alive; reused after exit, so
|
|
661
|
+
* pass it to `attach({ pid })` while the VM is live (or use
|
|
662
|
+
* `--name` for a stable handle).
|
|
663
|
+
*/
|
|
664
|
+
readonly pid: number;
|
|
665
|
+
/** Optional human-friendly name passed to `boot({ name })`. */
|
|
666
|
+
readonly name?: string;
|
|
667
|
+
readonly stdin: Writable;
|
|
668
|
+
readonly stdout: Readable;
|
|
669
|
+
readonly stderr: Readable;
|
|
670
|
+
/** Resolves when the VM process exits. Rejects on timeout. */
|
|
671
|
+
wait(): Promise<{
|
|
672
|
+
code: number | null;
|
|
673
|
+
signal: NodeJS.Signals | null;
|
|
674
|
+
}>;
|
|
675
|
+
/** Send SIGKILL to the VM. Resolves once it's really gone. */
|
|
676
|
+
kill(): Promise<void>;
|
|
677
|
+
/**
|
|
678
|
+
* Drop this host-side handle without killing the VMM. The VM keeps
|
|
679
|
+
* running and can be re-attached from another process. For locally-
|
|
680
|
+
* booted handles this closes captured streams; `wait()` and
|
|
681
|
+
* `exec()` become unreliable afterwards.
|
|
682
|
+
*/
|
|
683
|
+
detach(): Promise<void>;
|
|
684
|
+
/**
|
|
685
|
+
* Buffer stdout until the process exits; return it as a UTF-8 string.
|
|
686
|
+
* Capped at ~1 MiB tail — long-running VMs keep only the most recent
|
|
687
|
+
* bytes (issue #150). Sufficient for kernel boot console + test
|
|
688
|
+
* assertions; not a full transcript.
|
|
689
|
+
*/
|
|
690
|
+
output(): Promise<string>;
|
|
691
|
+
/** Same as `output()` but for stderr (where guest console lands). */
|
|
692
|
+
errorOutput(): Promise<string>;
|
|
693
|
+
/**
|
|
694
|
+
* Run a shell command inside the guest via the vsock exec-agent. Throws
|
|
695
|
+
* BootError on non-zero exit; callers who want to inspect failure
|
|
696
|
+
* should use `execRaw`.
|
|
697
|
+
*
|
|
698
|
+
* Requires the rootfs to have the exec-agent running on vsock port 1978
|
|
699
|
+
* (the standard debian base ships it). The vsock bridge is set up
|
|
700
|
+
* automatically by `boot()` unless the caller pre-set MACHINEN_VSOCK.
|
|
701
|
+
*/
|
|
702
|
+
exec(cmd: string, opts?: VsockExecOptions): Promise<VsockExecResult>;
|
|
703
|
+
/** Like `exec()` but returns non-zero exit codes instead of throwing. */
|
|
704
|
+
execRaw(cmd: string, opts?: VsockExecOptions): Promise<VsockExecResult>;
|
|
705
|
+
/**
|
|
706
|
+
* Run a shell command inside a pseudoterminal. Bidirectional bytes
|
|
707
|
+
* flow between `opts.stdin` and `opts.stdout`; the returned handle's
|
|
708
|
+
* `.resize(cols, rows)` propagates window-size changes (hook your
|
|
709
|
+
* host's SIGWINCH).
|
|
710
|
+
*
|
|
711
|
+
* Caller is responsible for putting the host terminal in raw mode
|
|
712
|
+
* before calling and restoring it after `.result` settles — without
|
|
713
|
+
* raw mode, Ctrl-C / arrow keys / etc. won't reach the guest as
|
|
714
|
+
* untranslated bytes. See #133.
|
|
715
|
+
*/
|
|
716
|
+
execPty(cmd: string, opts: VsockExecPtyOptions): VsockExecPtyHandle;
|
|
717
|
+
/**
|
|
718
|
+
* Write `contents` to `guestPath` inside the VM. Convenience over
|
|
719
|
+
* `vm.exec(...)` for the common "drop a config file from the host"
|
|
720
|
+
* case — no quoting/heredoc gymnastics, binary-safe via base64.
|
|
721
|
+
*
|
|
722
|
+
* Parent directories are created by default (`recursive: true`).
|
|
723
|
+
* Pass `mode` to set the file mode (octal, e.g. `0o755`).
|
|
724
|
+
* Pass `append: true` to append instead of overwrite.
|
|
725
|
+
*
|
|
726
|
+
* Best for small-to-medium files (configs, scripts) — the contents
|
|
727
|
+
* ride through a single vsock exec frame, so very large blobs are
|
|
728
|
+
* better handled with `--mount` / `VsockFiles.push`.
|
|
729
|
+
*
|
|
730
|
+
* Throws `ExecError` (`EXEC_NONZERO_EXIT`) if the underlying shell
|
|
731
|
+
* write fails (e.g. permissions, full disk, missing `base64`).
|
|
732
|
+
*
|
|
733
|
+
* @throws {ExecError} EXEC_VSOCK_UNAVAILABLE | EXEC_NONZERO_EXIT |
|
|
734
|
+
* EXEC_AGENT_UNAVAILABLE (retryable) | EXEC_AGENT_TIMEOUT (retryable)
|
|
735
|
+
*/
|
|
736
|
+
writeFile(guestPath: string, contents: Buffer | string, opts?: WriteFileOptions): Promise<void>;
|
|
737
|
+
/**
|
|
738
|
+
* Freeze this VM with CRIU and write a snapshot bundle into
|
|
739
|
+
* `opts.outDir`. The bundle is a directory containing:
|
|
740
|
+
*
|
|
741
|
+
* <outDir>/img/ ← CRIU image files (pages-*.img,
|
|
742
|
+
* pagemap-*.img, core-*.img,
|
|
743
|
+
* dump.log, ...)
|
|
744
|
+
* <outDir>/meta.json ← source name + timestamp +
|
|
745
|
+
* optional mountDisk pointers
|
|
746
|
+
* <outDir>/mount-lower.sqfs ← squashfs RO lower (only when
|
|
747
|
+
* the source VM had `mount` set)
|
|
748
|
+
* <outDir>/mount-upper.img ← ext4 RW upper (only when
|
|
749
|
+
* the source VM had `mount` set)
|
|
750
|
+
*
|
|
751
|
+
* `mount-lower.sqfs` and `mount-upper.img` are reflinked from the
|
|
752
|
+
* runtime's per-VM materialization (#272), so on APFS / btrfs / xfs
|
|
753
|
+
* the snapshot is essentially free space-wise even for a large
|
|
754
|
+
* mount payload — blocks stay shared until either side writes.
|
|
755
|
+
*
|
|
756
|
+
* The caller must have booted the VM with a scratch disk (`snapshot:
|
|
757
|
+
* '<path>'` or default auto-allocation) so the guest had `/dev/vdb`
|
|
758
|
+
* to dump into; otherwise this throws `SNAPSHOT_NO_DISK`.
|
|
759
|
+
*
|
|
760
|
+
* Guest contract: the rootfs ships a dump helper callable via
|
|
761
|
+
* vsock exec — default `/sbin/machinen-dump`, override via
|
|
762
|
+
* `opts.dumpCmd`. The helper runs `criu dump --leave-running` and
|
|
763
|
+
* tars the resulting image set out on stdout, which the host
|
|
764
|
+
* extracts into `<outDir>/img/`. For destructive snapshots (default)
|
|
765
|
+
* the runtime then issues `/sbin/machinen-poweroff` over vsock to
|
|
766
|
+
* bring the VMM down; `opts.leaveRunning: true` skips that step
|
|
767
|
+
* and the source VM keeps running.
|
|
768
|
+
*
|
|
769
|
+
* `SNAPSHOT_TIMEOUT` if the dump exec doesn't return within
|
|
770
|
+
* `opts.timeoutMs`; `SNAPSHOT_DUMP_FAILED` if it returns non-zero
|
|
771
|
+
* or the streamed bundle is empty.
|
|
772
|
+
*
|
|
773
|
+
* Supported on both boot-owned and attach handles — attach uses
|
|
774
|
+
* the `diskPath` stored in the VM registry entry at boot time.
|
|
775
|
+
*
|
|
776
|
+
* By default the VM exits as part of the dump (CRIU kills the
|
|
777
|
+
* dumped tree on success). Pass `opts.leaveRunning: true` to keep
|
|
778
|
+
* the source VM alive — the workload resumes from the dump point
|
|
779
|
+
* and the bundle can be restored into a sibling VM (`vm.fork()`).
|
|
780
|
+
*/
|
|
781
|
+
snapshot(opts: SnapshotOptions): Promise<SnapshotResult>;
|
|
782
|
+
/**
|
|
783
|
+
* Read the host's view of this VM's memory: the ceiling the VMM was
|
|
784
|
+
* sized at, the host RSS the VMM is currently holding, the bytes
|
|
785
|
+
* the virtio-balloon device has reported back to the host, and the
|
|
786
|
+
* count of lazy-restore pages the guest hasn't faulted in yet (#274).
|
|
787
|
+
*
|
|
788
|
+
* Pure read, no side effects. The numbers come from:
|
|
789
|
+
* - `ceiling` — captured at boot from the resolved
|
|
790
|
+
* `MACHINEN_MEMORY` env (fork: from the
|
|
791
|
+
* registry entry).
|
|
792
|
+
* - `hostRss` — `/proc/<vmm>/status:VmRSS` on Linux,
|
|
793
|
+
* `ps -o rss=` on Darwin. May be `null`
|
|
794
|
+
* if the VMM exited between calls.
|
|
795
|
+
* - `balloonInflated` — running total of bytes the balloon
|
|
796
|
+
* device has reclaimed via free-page
|
|
797
|
+
* reporting (`mmap MAP_FIXED` on the
|
|
798
|
+
* reported runs). Read out of the shared
|
|
799
|
+
* stats file the VMM mmaps at startup.
|
|
800
|
+
* `0` when the VMM was launched without
|
|
801
|
+
* `MACHINEN_STATS_FILE`.
|
|
802
|
+
* - `lazyPagesPending` — for forks restored lazily (#266), the
|
|
803
|
+
* count of pages the rewriter marked
|
|
804
|
+
* PE_LAZY at restore time minus pages
|
|
805
|
+
* served from `pages-*.img` over the
|
|
806
|
+
* FUSE mount since. `0` for eager
|
|
807
|
+
* restores and plain boots.
|
|
808
|
+
*/
|
|
809
|
+
memoryStats(): Promise<MemoryStats>;
|
|
810
|
+
/**
|
|
811
|
+
* Snapshot this VM without killing it and immediately restore the
|
|
812
|
+
* bundle into a new sibling VM. Both source and fork keep running,
|
|
813
|
+
* independently addressable. See #216.
|
|
814
|
+
*
|
|
815
|
+
* Wraps `vm.snapshot({ leaveRunning: true })` + `restore()` with
|
|
816
|
+
* the safety defaults a fork wants:
|
|
817
|
+
* - `tcpKeep: false` (default) → the fork sees ECONNRESET on
|
|
818
|
+
* inherited TCP sockets, source keeps them. Set `tcpKeep: true`
|
|
819
|
+
* if you want both copies to share state (rarely correct).
|
|
820
|
+
* - `portForward: []` (default) → host ports are NOT inherited
|
|
821
|
+
* (they're global; source + fork would race). Pass new
|
|
822
|
+
* forwards explicitly.
|
|
823
|
+
*
|
|
824
|
+
* Returns a handle to the forked VM. The source VM is unaffected
|
|
825
|
+
* apart from being briefly frozen during `criu dump`.
|
|
826
|
+
*
|
|
827
|
+
* Bundle lifecycle: when `opts.outDir` is set, the bundle is kept
|
|
828
|
+
* and you can re-restore from it. When omitted, the bundle is
|
|
829
|
+
* written to a temp dir and removed when the fork exits.
|
|
830
|
+
*/
|
|
831
|
+
fork(opts?: ForkOptions): Promise<VmHandle>;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Host-observable memory state for one VM (#274). All four fields are
|
|
835
|
+
* snapshots of "now" — call `memoryStats()` again to refresh.
|
|
836
|
+
*/
|
|
837
|
+
interface MemoryStats {
|
|
838
|
+
/**
|
|
839
|
+
* Ceiling the VMM was sized at (MiB). The actual RSS climbs into
|
|
840
|
+
* this on demand and is reclaimed by the balloon (#263 phase B);
|
|
841
|
+
* the ceiling itself is fixed for the lifetime of the VM. `null`
|
|
842
|
+
* when the runtime didn't pick the value (caller pre-set
|
|
843
|
+
* `MACHINEN_MEMORY` via `vmmEnv`) — we won't honestly report a
|
|
844
|
+
* number we don't own.
|
|
845
|
+
*/
|
|
846
|
+
ceilingMib: number | null;
|
|
847
|
+
/**
|
|
848
|
+
* Resident bytes the host kernel sees the VMM holding. `null`
|
|
849
|
+
* when the VMM has exited or `/proc/<pid>/status` / `ps` couldn't
|
|
850
|
+
* be read.
|
|
851
|
+
*/
|
|
852
|
+
hostRssBytes: number | null;
|
|
853
|
+
/**
|
|
854
|
+
* Bytes the virtio-balloon device has reclaimed via free-page
|
|
855
|
+
* reporting since the VMM started. Strictly increases over the
|
|
856
|
+
* VMM's lifetime; if `hostRssBytes` is well below ceiling, balloon
|
|
857
|
+
* reclaim is the reason. Read out of the shared stats file the VMM
|
|
858
|
+
* writes via `MACHINEN_STATS_FILE`. `0` when the VMM was launched
|
|
859
|
+
* without that env var.
|
|
860
|
+
*/
|
|
861
|
+
balloonInflatedBytes: number;
|
|
862
|
+
/**
|
|
863
|
+
* Pages the lazy-restore path (#266) has registered as PE_LAZY but
|
|
864
|
+
* the guest hasn't faulted in yet. Approximated as
|
|
865
|
+
* `entriesFlagged - bytesServedFromPagesImg / 4096`, clamped to
|
|
866
|
+
* `>= 0`. `0` for eager restores and plain (non-restored) boots.
|
|
867
|
+
*/
|
|
868
|
+
lazyPagesPending: number;
|
|
869
|
+
}
|
|
870
|
+
interface WriteFileOptions {
|
|
871
|
+
/** Octal mode for the destination file (e.g. `0o755`). Default: leave as-is. */
|
|
872
|
+
mode?: number;
|
|
873
|
+
/** `mkdir -p` the parent directory before writing. Default: true. */
|
|
874
|
+
recursive?: boolean;
|
|
875
|
+
/** Append to the file instead of overwriting. Default: false. */
|
|
876
|
+
append?: boolean;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Options for `vm.snapshot(opts)`.
|
|
880
|
+
*
|
|
881
|
+
* Live-share mount note (#273): VMs booted with `liveMounts: [...]`
|
|
882
|
+
* are snapshottable. The runtime unmounts each FUSE mount before
|
|
883
|
+
* CRIU dumps and (for `leaveRunning: true`) re-establishes them
|
|
884
|
+
* after. Bytes are NOT captured into the bundle — only the host
|
|
885
|
+
* path / guest path / mode get recorded in `meta.liveMounts` so
|
|
886
|
+
* `restore()` can reconnect a live window on the other side. See
|
|
887
|
+
* the `liveMounts` doc on `BootOptions` for the full contract.
|
|
888
|
+
*/
|
|
889
|
+
interface SnapshotOptions {
|
|
890
|
+
/**
|
|
891
|
+
* Directory the snapshot bundle is written to. Created if missing
|
|
892
|
+
* and required to be empty (or absent) so a previous snapshot
|
|
893
|
+
* can't be silently overwritten.
|
|
894
|
+
*/
|
|
895
|
+
outDir: string;
|
|
896
|
+
/**
|
|
897
|
+
* Command to run in the guest to trigger the CRIU dump. Defaults to
|
|
898
|
+
* `/sbin/machinen-dump`.
|
|
899
|
+
*/
|
|
900
|
+
dumpCmd?: string;
|
|
901
|
+
/**
|
|
902
|
+
* Wall-clock ceiling for the dump + shutdown. If the VMM hasn't exited
|
|
903
|
+
* in this window we SIGKILL it and fail. Default 90s.
|
|
904
|
+
*/
|
|
905
|
+
timeoutMs?: number;
|
|
906
|
+
/**
|
|
907
|
+
* Streaming log callback — fires for every byte the dump emits
|
|
908
|
+
* (guest console + the dump exec). See #83. When both the snapshot
|
|
909
|
+
* call and `boot({ onLog })` have a callback set, both fire.
|
|
910
|
+
*/
|
|
911
|
+
onLog?: OnLog;
|
|
912
|
+
/**
|
|
913
|
+
* Pass `--leave-running` to `criu dump` so the source workload
|
|
914
|
+
* survives the snapshot. The VMM stays up after the dump; success
|
|
915
|
+
* is signalled by the dump exec returning 0 instead of by VMM exit.
|
|
916
|
+
* Used by `vm.fork()` (#216).
|
|
917
|
+
*
|
|
918
|
+
* Default: false (current destructive snapshot behavior).
|
|
919
|
+
*/
|
|
920
|
+
leaveRunning?: boolean;
|
|
921
|
+
/**
|
|
922
|
+
* Omit `--tcp-established` from `criu dump`. Restored sockets come
|
|
923
|
+
* back in CLOSED state — the workload sees ECONNRESET on first
|
|
924
|
+
* I/O, which is the right semantic when the dump is the source for
|
|
925
|
+
* a fork (otherwise both copies would race on the same connection
|
|
926
|
+
* state). See #216.
|
|
927
|
+
*
|
|
928
|
+
* Default: false (preserve TCP — current snapshot/restore behavior).
|
|
929
|
+
*/
|
|
930
|
+
tcpClose?: boolean;
|
|
931
|
+
}
|
|
932
|
+
interface SnapshotResult {
|
|
933
|
+
/** Absolute path to the snapshot bundle directory. */
|
|
934
|
+
snapDir: string;
|
|
935
|
+
/** Absolute path to the CRIU image directory inside the bundle. */
|
|
936
|
+
imgDir: string;
|
|
937
|
+
/** Time from `snapshot()` entry to VMM exit, in milliseconds. */
|
|
938
|
+
elapsedMs: number;
|
|
939
|
+
/** Guest console output captured during the dump. */
|
|
940
|
+
consoleLog: string;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* On-disk shape of the bundle's `meta.json`. Read by `restore()`
|
|
944
|
+
* to reconstruct the source VM's name when registering the fork.
|
|
945
|
+
*/
|
|
946
|
+
interface SnapshotMeta {
|
|
947
|
+
/** Name passed to `boot({ name })` when the source VM was started. */
|
|
948
|
+
sourceName?: string;
|
|
949
|
+
/**
|
|
950
|
+
* Absolute path of the rootfs tarball the source VM was booted with
|
|
951
|
+
* (`boot({ image })` or its restored equivalent). `restore()` uses
|
|
952
|
+
* this as the default rootfs, so the same-host quickstart works
|
|
953
|
+
* without callers having to repeat the image path. Cross-host
|
|
954
|
+
* restores need either the path to resolve on the new host, or an
|
|
955
|
+
* explicit `image` override.
|
|
956
|
+
*/
|
|
957
|
+
sourceImage?: string;
|
|
958
|
+
/** ms epoch when `vm.snapshot()` returned. */
|
|
959
|
+
snappedAt: number;
|
|
960
|
+
/**
|
|
961
|
+
* #272: when the source VM was booted with `mount: { host, guest }`,
|
|
962
|
+
* the snapshot bundle includes both halves of the overlay so a
|
|
963
|
+
* restore (same- or cross-host) can mount the same overlay without
|
|
964
|
+
* consulting the host source dir.
|
|
965
|
+
* - `guest`: absolute guest path the overlay mounts at.
|
|
966
|
+
* - `lower`: basename of the squashfs RO lower in the bundle dir.
|
|
967
|
+
* - `upper`: basename of the ext4 RW upper in the bundle dir.
|
|
968
|
+
*/
|
|
969
|
+
mountDisk?: {
|
|
970
|
+
guest: string;
|
|
971
|
+
lower: string;
|
|
972
|
+
upper: string;
|
|
973
|
+
};
|
|
974
|
+
/**
|
|
975
|
+
* #273: live-share FUSE mounts (`liveMounts: [...]` at boot) the
|
|
976
|
+
* source VM had at snapshot time. Unlike `mountDisk`, no bytes are
|
|
977
|
+
* captured — `host` is the path on the host that was being live-
|
|
978
|
+
* shared, recorded so `restore()` can re-establish the same window
|
|
979
|
+
* on the restoring host. Each entry is the resolved config from the
|
|
980
|
+
* source's `resolveLiveMounts()`:
|
|
981
|
+
* - `guest`: absolute guest path the FUSE mount lands at.
|
|
982
|
+
* - `host`: absolute host path that was being shared.
|
|
983
|
+
* - `mode`: `"ro"` or `"rw"`, the share's write semantics.
|
|
984
|
+
*
|
|
985
|
+
* Restore policy: the bundle's recorded mounts are re-established
|
|
986
|
+
* verbatim by default. Pass `restore({ liveMounts })` to override
|
|
987
|
+
* per-guest `host`/`mode` — each override entry's `guest` must
|
|
988
|
+
* match a recorded entry, else BOOT_LIVE_MOUNT_OVERRIDE_UNKNOWN.
|
|
989
|
+
* Cross-host bundles where a recorded `host` doesn't exist on the
|
|
990
|
+
* restoring host fail loudly via the boot-time existence check —
|
|
991
|
+
* users remap with the override knob.
|
|
992
|
+
*/
|
|
993
|
+
liveMounts?: Array<{
|
|
994
|
+
guest: string;
|
|
995
|
+
host: string;
|
|
996
|
+
mode: "ro" | "rw";
|
|
997
|
+
}>;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Fork = `vm.snapshot({ leaveRunning: true })` + `restore(...)` rolled
|
|
1001
|
+
* into one call. The shape mirrors `RestoreOptions` (so anything you
|
|
1002
|
+
* could pass to `restore()` works on a fork) plus two fork-only knobs:
|
|
1003
|
+
* `outDir` (where to write the bundle) and `tcpKeep` (snapshot half).
|
|
1004
|
+
*
|
|
1005
|
+
* Notably this means `mount`, `liveMounts`, `env`, `guestCwd`, `memory`,
|
|
1006
|
+
* etc. are all settable on the fork — they take effect on the restored
|
|
1007
|
+
* sibling, not the source.
|
|
1008
|
+
*
|
|
1009
|
+
* `snapDir` is omitted because `vm.fork()` produces the bundle itself.
|
|
1010
|
+
* Re-included here are the fork-shaped docs for `name`, `portForward`,
|
|
1011
|
+
* `timeoutMs`, and `onLog` so call sites see the fork-specific defaults
|
|
1012
|
+
* instead of the boot/restore ones.
|
|
1013
|
+
*/
|
|
1014
|
+
interface ForkOptions extends Omit<RestoreOptions, "snapDir"> {
|
|
1015
|
+
/**
|
|
1016
|
+
* If set, the snapshot bundle is written here and kept after the
|
|
1017
|
+
* fork exits — re-restore from this path to spawn another sibling.
|
|
1018
|
+
* If omitted, the bundle is written to a temp dir and removed
|
|
1019
|
+
* when the fork's VMM exits.
|
|
1020
|
+
*/
|
|
1021
|
+
outDir?: string;
|
|
1022
|
+
/**
|
|
1023
|
+
* Default false: omit `--tcp-established` from the dump so the
|
|
1024
|
+
* fork sees ECONNRESET on sockets the source had open. Set true
|
|
1025
|
+
* to clone live TCP state into the fork (both VMs then race on
|
|
1026
|
+
* the same connection — only correct in narrow scenarios).
|
|
1027
|
+
*/
|
|
1028
|
+
tcpKeep?: boolean;
|
|
1029
|
+
/**
|
|
1030
|
+
* Name for the forked VM. When omitted, restore()'s auto-naming
|
|
1031
|
+
* kicks in: `<sourceName>/<fork.pid>`.
|
|
1032
|
+
*/
|
|
1033
|
+
name?: string;
|
|
1034
|
+
/**
|
|
1035
|
+
* Host→guest port forwards for the fork. NOT inherited from the
|
|
1036
|
+
* source — host ports are global and source + fork would race on
|
|
1037
|
+
* the same bind. Pass explicitly when the fork needs forwards.
|
|
1038
|
+
*/
|
|
1039
|
+
portForward?: Array<{
|
|
1040
|
+
hostPort: number;
|
|
1041
|
+
guestPort: number;
|
|
1042
|
+
hostAddr?: string;
|
|
1043
|
+
}>;
|
|
1044
|
+
/**
|
|
1045
|
+
* Wall-clock ceiling for the restored fork's `wait()`. Defaults to
|
|
1046
|
+
* `null` (forever) — forks are typically long-lived sibling VMs and
|
|
1047
|
+
* interactive sessions can sit idle. Set a finite deadline if you
|
|
1048
|
+
* want the fork to be reaped after N ms of unresponsiveness. The
|
|
1049
|
+
* dump half uses `performSnapshot`'s own 90s default and isn't
|
|
1050
|
+
* configurable here.
|
|
1051
|
+
*/
|
|
1052
|
+
timeoutMs?: number | null;
|
|
1053
|
+
/**
|
|
1054
|
+
* Streaming log callback for the snapshot half. Same shape as
|
|
1055
|
+
* `vm.snapshot({ onLog })`. Also used by the restore boot.
|
|
1056
|
+
*/
|
|
1057
|
+
onLog?: OnLog;
|
|
1058
|
+
/**
|
|
1059
|
+
* Opt into lazy-pages restore for the fork — vsock-FUSE-mounted
|
|
1060
|
+
* bundle + `criu restore --lazy-pages`. Default false: the runtime
|
|
1061
|
+
* packs the CRIU image into a tar on `/dev/vdb` and the guest does
|
|
1062
|
+
* an eager load.
|
|
1063
|
+
*
|
|
1064
|
+
* Lazy keeps fork RSS proportional to the pages the sibling
|
|
1065
|
+
* actually touches, not the full snapshot size. Worth setting when
|
|
1066
|
+
* the source dumped a large heap that the fork will only sample.
|
|
1067
|
+
* Cannot combine with `--detach` (the lazy path needs the host's
|
|
1068
|
+
* FUSE server alive as long as the guest may fault, see #150
|
|
1069
|
+
* phase 3); the runtime falls back to eager in that case.
|
|
1070
|
+
*/
|
|
1071
|
+
lazy?: boolean;
|
|
1072
|
+
/**
|
|
1073
|
+
* Backpressure gate (#274). Fraction of host total memory that must
|
|
1074
|
+
* be free before `vm.fork()` is allowed to proceed; if `MemAvailable`
|
|
1075
|
+
* (Linux) / `vm_stat free+speculative+purgeable` (Darwin) drops below
|
|
1076
|
+
* `totalmem() * threshold`, the fork is refused with
|
|
1077
|
+
* `FORK_MEMORY_BACKPRESSURE`. Mirrors the throw-immediately shape of
|
|
1078
|
+
* #267's port-conflict gate — caller decides whether to retry.
|
|
1079
|
+
*
|
|
1080
|
+
* Default 1% (`0.01`) — about 250 MiB on a 24 GiB host. The gate
|
|
1081
|
+
* exists to head off OOM kills, not to enforce a working-set
|
|
1082
|
+
* policy; bigger thresholds trip on real dev loops that boot
|
|
1083
|
+
* several VMs in sequence. Pass `0` to disable the gate entirely
|
|
1084
|
+
* (useful in tests or when you're knowingly running close to the
|
|
1085
|
+
* edge).
|
|
1086
|
+
*/
|
|
1087
|
+
freeMemoryThreshold?: number;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
interface SandboxEntry {
|
|
1091
|
+
id: string;
|
|
1092
|
+
vm: VmHandle;
|
|
1093
|
+
scrollback: Buffer;
|
|
1094
|
+
readonly addedAt: number;
|
|
1095
|
+
}
|
|
1096
|
+
interface OnOutputListener {
|
|
1097
|
+
(chunk: Buffer, source: "stdout" | "stderr"): void;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Registry of live sandboxes. Thread-safe in the sense that there's
|
|
1101
|
+
* only one runtime thread anyway; the class just bookkeeps handles +
|
|
1102
|
+
* their scrollback rings so the supervisor doesn't need to.
|
|
1103
|
+
*/
|
|
1104
|
+
declare class Sandboxes {
|
|
1105
|
+
private readonly items;
|
|
1106
|
+
private readonly subs;
|
|
1107
|
+
/**
|
|
1108
|
+
* Maximum bytes retained per sandbox for replay on attach. The ring
|
|
1109
|
+
* keeps only the most recent chunk up to this limit — a reasonable
|
|
1110
|
+
* trade between "see enough context to know what's going on" and
|
|
1111
|
+
* "don't leak memory if the sandbox runs for hours."
|
|
1112
|
+
*/
|
|
1113
|
+
readonly scrollbackBytes: number;
|
|
1114
|
+
constructor(opts?: {
|
|
1115
|
+
scrollbackBytes?: number;
|
|
1116
|
+
});
|
|
1117
|
+
add(id: string, vm: VmHandle): void;
|
|
1118
|
+
/** Remove a sandbox. Does not kill the VM — call `vm.kill()` first. */
|
|
1119
|
+
remove(id: string): void;
|
|
1120
|
+
list(): Array<{
|
|
1121
|
+
id: string;
|
|
1122
|
+
addedAt: number;
|
|
1123
|
+
}>;
|
|
1124
|
+
get(id: string): SandboxEntry | undefined;
|
|
1125
|
+
/** Write `data` to the sandbox's stdin. No-op if the id is unknown. */
|
|
1126
|
+
send(id: string, data: string | Buffer): boolean;
|
|
1127
|
+
/**
|
|
1128
|
+
* Subscribe to `id`'s output. Returns an unsubscribe function. The
|
|
1129
|
+
* listener fires only for NEW bytes produced after the subscription
|
|
1130
|
+
* — use `get(id).scrollback` to replay history if you want it.
|
|
1131
|
+
*/
|
|
1132
|
+
onOutput(id: string, fn: OnOutputListener): () => void;
|
|
1133
|
+
private record;
|
|
1134
|
+
}
|
|
1135
|
+
interface SupervisorOptions {
|
|
1136
|
+
/** Registry to draw sandboxes from. */
|
|
1137
|
+
sandboxes: Sandboxes;
|
|
1138
|
+
/** Input byte stream. Defaults to `process.stdin`. */
|
|
1139
|
+
input?: NodeJS.ReadableStream;
|
|
1140
|
+
/** Output byte stream. Defaults to `process.stdout`. */
|
|
1141
|
+
output?: Writable;
|
|
1142
|
+
/** Prefix for slash-commands. Default `/`. */
|
|
1143
|
+
commandPrefix?: string;
|
|
1144
|
+
/**
|
|
1145
|
+
* Flip the terminal into raw mode while a sandbox is attached, and
|
|
1146
|
+
* restore it on detach. Enabled by default when `input` is a TTY.
|
|
1147
|
+
* Set to `false` in tests where `input` is a plain PassThrough.
|
|
1148
|
+
*/
|
|
1149
|
+
rawTtyOnAttach?: boolean;
|
|
1150
|
+
/**
|
|
1151
|
+
* Forward SIGWINCH on the parent process (terminal resize) to any
|
|
1152
|
+
* attached sandbox that implements `.resize(cols, rows)`. Enabled
|
|
1153
|
+
* by default when `output` is a TTY.
|
|
1154
|
+
*/
|
|
1155
|
+
forwardResize?: boolean;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* A minimal text-driven multiplexer. Runs until `.stop()` is called
|
|
1159
|
+
* or the input stream ends.
|
|
1160
|
+
*
|
|
1161
|
+
* Command surface when detached (lines prefixed with `/`):
|
|
1162
|
+
* /ls — list sandboxes and their state
|
|
1163
|
+
* /attach <id> — forward to/from the given sandbox
|
|
1164
|
+
* /help — show commands
|
|
1165
|
+
* /quit — stop the supervisor (does not kill sandboxes)
|
|
1166
|
+
*
|
|
1167
|
+
* When attached, bytes are piped verbatim to the sandbox's stdin.
|
|
1168
|
+
* Hit `Ctrl-] Ctrl-]` (two 0x1D bytes in a row) to detach.
|
|
1169
|
+
*/
|
|
1170
|
+
declare class Supervisor {
|
|
1171
|
+
readonly sandboxes: Sandboxes;
|
|
1172
|
+
private readonly input;
|
|
1173
|
+
private readonly output;
|
|
1174
|
+
private readonly prefix;
|
|
1175
|
+
private readonly rawTtyOnAttach;
|
|
1176
|
+
private readonly forwardResize;
|
|
1177
|
+
private attachedId;
|
|
1178
|
+
private attachedUnsub;
|
|
1179
|
+
private lastGs;
|
|
1180
|
+
private stopped;
|
|
1181
|
+
private onEnd;
|
|
1182
|
+
private priorRawState;
|
|
1183
|
+
private winchHandler;
|
|
1184
|
+
constructor(opts: SupervisorOptions);
|
|
1185
|
+
/** Run until stopped. Resolves when input ends or stop() is called. */
|
|
1186
|
+
run(): Promise<void>;
|
|
1187
|
+
/** Programmatic stop (e.g. from a test). */
|
|
1188
|
+
stop(): void;
|
|
1189
|
+
/** Attach to `id`. Throws if id doesn't exist. */
|
|
1190
|
+
attach(id: string): void;
|
|
1191
|
+
detach(): void;
|
|
1192
|
+
private enterRawTty;
|
|
1193
|
+
private leaveRawTty;
|
|
1194
|
+
private installWinchHandler;
|
|
1195
|
+
private removeWinchHandler;
|
|
1196
|
+
private ingest;
|
|
1197
|
+
private handleCommand;
|
|
1198
|
+
private doLs;
|
|
1199
|
+
private bannerText;
|
|
1200
|
+
private print;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
interface PtyBootOptions {
|
|
1204
|
+
/** Absolute or cwd-relative path to the binary to fork. */
|
|
1205
|
+
binary: string;
|
|
1206
|
+
/** Extra env. Merged over process.env. */
|
|
1207
|
+
env?: Record<string, string>;
|
|
1208
|
+
cwd?: string;
|
|
1209
|
+
args?: string[];
|
|
1210
|
+
/** Initial terminal size. Defaults to 80x24. */
|
|
1211
|
+
cols?: number;
|
|
1212
|
+
rows?: number;
|
|
1213
|
+
/** TERM value. Default `xterm-256color` — the CC banner wants colors. */
|
|
1214
|
+
name?: string;
|
|
1215
|
+
}
|
|
1216
|
+
interface PtyVmHandle {
|
|
1217
|
+
readonly pid: number;
|
|
1218
|
+
readonly stdin: Writable;
|
|
1219
|
+
readonly stdout: Readable;
|
|
1220
|
+
/** Same stream as `stdout`. A pty merges stdout + stderr in the kernel. */
|
|
1221
|
+
readonly stderr: Readable;
|
|
1222
|
+
/** Tell the kernel the terminal is now `cols`x`rows`. Triggers SIGWINCH in the child. */
|
|
1223
|
+
resize(cols: number, rows: number): void;
|
|
1224
|
+
wait(): Promise<{
|
|
1225
|
+
code: number | null;
|
|
1226
|
+
signal: NodeJS.Signals | null;
|
|
1227
|
+
}>;
|
|
1228
|
+
kill(): Promise<void>;
|
|
1229
|
+
output(): Promise<string>;
|
|
1230
|
+
/** Alias of output() — a pty gives us one merged stream. */
|
|
1231
|
+
errorOutput(): Promise<string>;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Fork `binary` under a new pty pair. The returned handle is wire-
|
|
1235
|
+
* compatible with `VmHandle` from index.ts so the existing Sandboxes
|
|
1236
|
+
* registry can hold it.
|
|
1237
|
+
*/
|
|
1238
|
+
declare function bootPty(opts: PtyBootOptions): PtyVmHandle;
|
|
1239
|
+
|
|
1240
|
+
interface VsockWinsizeOptions {
|
|
1241
|
+
/** How long to keep retrying the UDS connect. Default 10s. */
|
|
1242
|
+
timeoutMs?: number;
|
|
1243
|
+
/** Poll interval in ms while retrying. Default 250. */
|
|
1244
|
+
retryMs?: number;
|
|
1245
|
+
}
|
|
1246
|
+
declare class VsockWinsize {
|
|
1247
|
+
private socket;
|
|
1248
|
+
private closed;
|
|
1249
|
+
private lastSent;
|
|
1250
|
+
private constructor();
|
|
1251
|
+
/**
|
|
1252
|
+
* Open a host Unix socket and keep retrying until the vsock bridge
|
|
1253
|
+
* + guest agent wire themselves up. Resolves once the TCP-like
|
|
1254
|
+
* connect completes — the agent may still be registering the
|
|
1255
|
+
* vsock listener on its side, but any bytes we send will be
|
|
1256
|
+
* buffered by the bridge's connection table.
|
|
1257
|
+
*/
|
|
1258
|
+
static connect(udsPath: string, opts?: VsockWinsizeOptions): Promise<VsockWinsize>;
|
|
1259
|
+
/**
|
|
1260
|
+
* Send a new size. Idempotent against the most recent send — repeats
|
|
1261
|
+
* are dropped so a chatty SIGWINCH doesn't spam the bridge.
|
|
1262
|
+
*/
|
|
1263
|
+
send(cols: number, rows: number): void;
|
|
1264
|
+
close(): void;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
interface VsockSecretsOptions {
|
|
1268
|
+
/** How long to keep retrying the UDS connect. Default 10s. */
|
|
1269
|
+
timeoutMs?: number;
|
|
1270
|
+
/** Poll interval in ms while retrying. Default 250. */
|
|
1271
|
+
retryMs?: number;
|
|
1272
|
+
}
|
|
1273
|
+
declare const VsockSecrets: {
|
|
1274
|
+
/**
|
|
1275
|
+
* Open the UDS the vsock bridge is listening on, push every
|
|
1276
|
+
* KEY=VALUE entry, close. Resolves once the write + close drain.
|
|
1277
|
+
*
|
|
1278
|
+
* Values must be single-line (no newlines). Keys must be valid
|
|
1279
|
+
* shell identifiers (letters/digits/underscore, no leading digit);
|
|
1280
|
+
* the guest agent skips entries that don't match.
|
|
1281
|
+
*/
|
|
1282
|
+
readonly send: (udsPath: string, secrets: Record<string, string>, opts?: VsockSecretsOptions) => Promise<void>;
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
interface VsockFilesOptions {
|
|
1286
|
+
/** How long to retry the UDS connect. Default 5s. */
|
|
1287
|
+
timeoutMs?: number;
|
|
1288
|
+
retryMs?: number;
|
|
1289
|
+
/** Forwarded to `tar --exclude=PATTERN`. Repeat per pattern. */
|
|
1290
|
+
excludes?: string[];
|
|
1291
|
+
}
|
|
1292
|
+
declare const VsockFiles: {
|
|
1293
|
+
/**
|
|
1294
|
+
* Stream `hostDir`'s contents into the guest at `guestPath`. Any
|
|
1295
|
+
* existing files at that path are overwritten (standard `tar -x`
|
|
1296
|
+
* semantics). If `guestPath` doesn't exist, the agent creates it.
|
|
1297
|
+
*/
|
|
1298
|
+
readonly push: (udsPath: string, hostDir: string, guestPath: string, opts?: VsockFilesOptions) => Promise<void>;
|
|
1299
|
+
/**
|
|
1300
|
+
* Stream a tar of `guestPath` from the guest and untar into
|
|
1301
|
+
* `hostDir`. `hostDir` is created if missing.
|
|
1302
|
+
*/
|
|
1303
|
+
readonly pull: (udsPath: string, guestPath: string, hostDir: string, opts?: VsockFilesOptions) => Promise<void>;
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
interface ProvisionOptions {
|
|
1307
|
+
/**
|
|
1308
|
+
* Path to the base rootfs tarball to start from. Typically the
|
|
1309
|
+
* `rootfs-debian-arm64.tar.gz` produced by
|
|
1310
|
+
* `scripts/build-base-assets.sh` or shipped in a machinen release.
|
|
1311
|
+
*
|
|
1312
|
+
* Optional — when omitted, `provision()` resolves it via `resolveBaseRootfs()`
|
|
1313
|
+
* (MACHINEN_ASSETS_DIR env override, falling back to the `@machinen/cli`
|
|
1314
|
+
* cache at `~/.machinen/@machinen/runtime@<version>/bases/debian-arm64/`).
|
|
1315
|
+
*/
|
|
1316
|
+
base?: string;
|
|
1317
|
+
/**
|
|
1318
|
+
* User-supplied provisioning steps. Runs inside the guest via vsock.
|
|
1319
|
+
*/
|
|
1320
|
+
install: (vm: VmHandle) => Promise<void>;
|
|
1321
|
+
/**
|
|
1322
|
+
* Output path for the resulting rootfs tarball. Will be overwritten.
|
|
1323
|
+
* Consumed via `boot({ image: out })`.
|
|
1324
|
+
*/
|
|
1325
|
+
out: string;
|
|
1326
|
+
/**
|
|
1327
|
+
* Default cmd baked into the image as `/machinen-config.json`.
|
|
1328
|
+
* When the image is later booted via `boot({ image })` without a
|
|
1329
|
+
* user-supplied `cmd`, the guest runs this. User-supplied `cmd` on
|
|
1330
|
+
* `boot()` still wins if provided.
|
|
1331
|
+
*/
|
|
1332
|
+
cmd?: string[];
|
|
1333
|
+
/**
|
|
1334
|
+
* Default guest env baked into the image alongside `cmd`. Merged
|
|
1335
|
+
* with `boot({ env })` at boot time, with the caller's `env`
|
|
1336
|
+
* overriding on key collision.
|
|
1337
|
+
*/
|
|
1338
|
+
env?: Record<string, string>;
|
|
1339
|
+
/**
|
|
1340
|
+
* Optional VMM binary path. Same lookup rules as `boot()` — if
|
|
1341
|
+
* omitted, resolves `@machinen/vmm-<arch>-<os>`.
|
|
1342
|
+
*/
|
|
1343
|
+
binary?: string;
|
|
1344
|
+
/** Working directory. Defaults to process.cwd(). */
|
|
1345
|
+
cwd?: string;
|
|
1346
|
+
/**
|
|
1347
|
+
* Size of the scratch disk used to ferry the tarball from guest to
|
|
1348
|
+
* host. Must be larger than the expected post-install rootfs size.
|
|
1349
|
+
* Default: 1 GiB (sparse, so it doesn't actually take that space).
|
|
1350
|
+
*/
|
|
1351
|
+
scratchDiskSizeBytes?: number;
|
|
1352
|
+
/**
|
|
1353
|
+
* Wall-clock ceiling for the whole build. If the install hook plus
|
|
1354
|
+
* the final archive + shutdown doesn't finish in this window, we
|
|
1355
|
+
* SIGKILL the VMM and fail. Default: 10 minutes.
|
|
1356
|
+
*/
|
|
1357
|
+
timeoutMs?: number;
|
|
1358
|
+
/**
|
|
1359
|
+
* Extra env passed to the VMM process on the host side. Useful for
|
|
1360
|
+
* dev overrides like `MACHINEN_BOOT_TEST`. Distinct from `env`,
|
|
1361
|
+
* which bakes guest-workload env into the produced image.
|
|
1362
|
+
*/
|
|
1363
|
+
vmmEnv?: Record<string, string>;
|
|
1364
|
+
/**
|
|
1365
|
+
* Path to the guest kernel. Optional — when omitted, `provision()`
|
|
1366
|
+
* resolves it via `resolveBaseKernel()` (MACHINEN_ASSETS_DIR override,
|
|
1367
|
+
* falling back to the `@machinen/cli` cache). Same semantics as
|
|
1368
|
+
* `boot({ kernel })` once resolved.
|
|
1369
|
+
*/
|
|
1370
|
+
kernel?: string;
|
|
1371
|
+
/**
|
|
1372
|
+
* Path to the guest DTB. Optional — when omitted, resolved via
|
|
1373
|
+
* `resolveBaseDtb()` from the same fallback chain as `kernel`.
|
|
1374
|
+
*/
|
|
1375
|
+
dtb?: string;
|
|
1376
|
+
/**
|
|
1377
|
+
* Streaming log callback — fires for every byte of guest output
|
|
1378
|
+
* during the build: guest kernel console, every `vm.exec()` call
|
|
1379
|
+
* the install hook makes, and the internal tar / poweroff execs.
|
|
1380
|
+
* See `LogEvent.source` to tell them apart. See #83.
|
|
1381
|
+
*/
|
|
1382
|
+
onLog?: OnLog;
|
|
1383
|
+
}
|
|
1384
|
+
interface ProvisionResult {
|
|
1385
|
+
/** Absolute path to the output tarball. */
|
|
1386
|
+
imagePath: string;
|
|
1387
|
+
/** Size of the output tarball in bytes. */
|
|
1388
|
+
sizeBytes: number;
|
|
1389
|
+
/** Wall-clock time from build() entry to return. */
|
|
1390
|
+
elapsedMs: number;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Resolve the path to the base rootfs tarball, in the same order
|
|
1394
|
+
* `provision()` itself does:
|
|
1395
|
+
*
|
|
1396
|
+
* 1. `explicit` — the caller-supplied path (resolved against `cwd`).
|
|
1397
|
+
* 2. `MACHINEN_ASSETS_DIR` env var — points at a directory laid out like
|
|
1398
|
+
* `scripts/build-base-assets.sh`'s output (contains
|
|
1399
|
+
* `rootfs-debian-arm64.tar.gz`). Same convention `@machinen/cli`
|
|
1400
|
+
* honors for local/dev builds.
|
|
1401
|
+
* 3. `@machinen/cli`'s on-disk cache at
|
|
1402
|
+
* `~/.machinen/@machinen/runtime@<version>/bases/debian-arm64/rootfs.tar.gz`.
|
|
1403
|
+
* Populated by running `machinen` once against the installed runtime.
|
|
1404
|
+
*
|
|
1405
|
+
* Throws `ProvisionError` with guidance if none of those turn up a file.
|
|
1406
|
+
* Exported so callers can pre-check or build their own tooling on it.
|
|
1407
|
+
*
|
|
1408
|
+
* @throws {ProvisionError} PROVISION_BASE_NOT_FOUND | PROVISION_ASSETS_DIR_INVALID
|
|
1409
|
+
*/
|
|
1410
|
+
declare function resolveBaseRootfs(explicit?: string, cwd?: string): string;
|
|
1411
|
+
/**
|
|
1412
|
+
* Resolve the path to the guest kernel Image. Same fallback chain as
|
|
1413
|
+
* `resolveBaseRootfs`: explicit → `MACHINEN_ASSETS_DIR/Image-arm64` →
|
|
1414
|
+
* `@machinen/cli` cache at `<base>/Image`. Exported for callers that
|
|
1415
|
+
* want to pre-check or wire the path into `boot()`.
|
|
1416
|
+
*
|
|
1417
|
+
* @throws {ProvisionError} PROVISION_KERNEL_NOT_FOUND |
|
|
1418
|
+
* PROVISION_ASSETS_DIR_INVALID
|
|
1419
|
+
*/
|
|
1420
|
+
declare function resolveBaseKernel(explicit?: string, cwd?: string): string;
|
|
1421
|
+
/**
|
|
1422
|
+
* Resolve the path to the guest DTB. Same fallback chain as
|
|
1423
|
+
* `resolveBaseRootfs`: explicit → `MACHINEN_ASSETS_DIR/virt-arm64.dtb` →
|
|
1424
|
+
* `@machinen/cli` cache at `<base>/virt.dtb`.
|
|
1425
|
+
*
|
|
1426
|
+
* @throws {ProvisionError} PROVISION_DTB_NOT_FOUND |
|
|
1427
|
+
* PROVISION_ASSETS_DIR_INVALID
|
|
1428
|
+
*/
|
|
1429
|
+
declare function resolveBaseDtb(explicit?: string, cwd?: string): string;
|
|
1430
|
+
/**
|
|
1431
|
+
* Boot the base rootfs, run the user install hook, and freeze the
|
|
1432
|
+
* resulting filesystem state to a new tarball at `opts.out`.
|
|
1433
|
+
*
|
|
1434
|
+
* @throws {ProvisionError} PROVISION_BASE_NOT_FOUND |
|
|
1435
|
+
* PROVISION_KERNEL_NOT_FOUND | PROVISION_DTB_NOT_FOUND |
|
|
1436
|
+
* PROVISION_ASSETS_DIR_INVALID | PROVISION_INSTALL_HOOK_FAILED |
|
|
1437
|
+
* PROVISION_DISK_TOO_SMALL
|
|
1438
|
+
* @throws {BootError} see `boot()` — propagated from the inner boot
|
|
1439
|
+
*/
|
|
1440
|
+
declare function provision(opts: ProvisionOptions): Promise<ProvisionResult>;
|
|
1441
|
+
|
|
1442
|
+
/** Default cache root: `~/.cache/machinen/rootfs`. */
|
|
1443
|
+
declare function rootfsImgCacheDir(): string;
|
|
1444
|
+
/**
|
|
1445
|
+
* Mark a cached rootfs image as "cleanly released" by writing the
|
|
1446
|
+
* sentinel that `ensureRootfsImage()` looks for on the next boot.
|
|
1447
|
+
* Called by the runtime after a VMM child exits without a signal —
|
|
1448
|
+
* an exit-code-only termination means the kernel had time to flush
|
|
1449
|
+
* and dismount the ext4 fs, so reusing the file is safe.
|
|
1450
|
+
*
|
|
1451
|
+
* No-op if the image doesn't exist (e.g. the runtime never
|
|
1452
|
+
* materialized one). Failures are swallowed: a missing marker just
|
|
1453
|
+
* means the next boot rebuilds from the tarball, which is wasteful
|
|
1454
|
+
* but never wrong.
|
|
1455
|
+
*/
|
|
1456
|
+
declare function markRootfsImageClean(imgPath: string): void;
|
|
1457
|
+
interface EnsureRootfsImageOptions {
|
|
1458
|
+
/**
|
|
1459
|
+
* Override the cache directory. Default: `~/.cache/machinen/rootfs`.
|
|
1460
|
+
* Useful for tests.
|
|
1461
|
+
*/
|
|
1462
|
+
cacheDir?: string;
|
|
1463
|
+
/**
|
|
1464
|
+
* Force re-materialization even if a cached image is already present.
|
|
1465
|
+
* Mostly for debugging the materializer.
|
|
1466
|
+
*/
|
|
1467
|
+
force?: boolean;
|
|
1468
|
+
/**
|
|
1469
|
+
* Slack multiplier above the unpacked tarball size when sizing the
|
|
1470
|
+
* ext4 filesystem. Default: 2.5 — leaves enough room for the guest
|
|
1471
|
+
* to install a few hundred MB of packages on top of the base rootfs
|
|
1472
|
+
* before hitting ENOSPC. Sparse files cost nothing on disk until
|
|
1473
|
+
* written, so over-provisioning is essentially free; the trade-off
|
|
1474
|
+
* is a higher upper bound on physical disk use if the guest decides
|
|
1475
|
+
* to fill the filesystem.
|
|
1476
|
+
*/
|
|
1477
|
+
sizeMultiplier?: number;
|
|
1478
|
+
/**
|
|
1479
|
+
* Minimum image size in bytes. The materializer enforces at least
|
|
1480
|
+
* this for small rootfs where the multiplier alone would leave
|
|
1481
|
+
* insufficient room for a real workload. Default: 2 GiB — boot-time
|
|
1482
|
+
* `npm install -g <large package>`, `apt install`, etc. land here
|
|
1483
|
+
* (#131). Sparse, so unused capacity is free.
|
|
1484
|
+
*/
|
|
1485
|
+
minSizeBytes?: number;
|
|
1486
|
+
/**
|
|
1487
|
+
* Absolute target size in bytes. When set, overrides `sizeMultiplier`
|
|
1488
|
+
* and `minSizeBytes` entirely — fresh materializations get exactly
|
|
1489
|
+
* this size, cached `.img`s smaller than this are sparse-extended
|
|
1490
|
+
* (truncate(2)) so the next boot's online ext4 grow can fill them.
|
|
1491
|
+
* For the user-facing `boot({ rootDiskSizeBytes })` knob (#131).
|
|
1492
|
+
*/
|
|
1493
|
+
sizeBytes?: number;
|
|
1494
|
+
/**
|
|
1495
|
+
* Sub-phase callback for the caller's PhaseTimer (#233 follow-up).
|
|
1496
|
+
* Fires for each measurable internal step: `sha256`, `e2fsck`,
|
|
1497
|
+
* `sparse-extend`, `tar-extract`, `mke2fs`, `gunzip-prebake`. The
|
|
1498
|
+
* caller typically does `phases.mark("<parent>.${name}", ms)` so
|
|
1499
|
+
* the breakdown shows up alongside the parent phase.
|
|
1500
|
+
*/
|
|
1501
|
+
onPhase?: (name: string, ms: number) => void;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Resolve `tarPath` to a cached ext4 `.img`, materializing it on first
|
|
1505
|
+
* call. Returns the absolute path to the cached image.
|
|
1506
|
+
*
|
|
1507
|
+
* Cache key: sha256 of the tarball. Same tarball → same image, even
|
|
1508
|
+
* across runs and processes. Concurrent callers do not race because
|
|
1509
|
+
* we materialize into a uniquely-named staging directory and atomically
|
|
1510
|
+
* rename into place — at worst two callers do redundant work; the
|
|
1511
|
+
* loser of the rename race re-checks and uses the winner's image.
|
|
1512
|
+
*
|
|
1513
|
+
* Lifecycle (#170): the returned path is handed back in the "in-use"
|
|
1514
|
+
* state (no `.ok` marker on disk). The caller is expected to invoke
|
|
1515
|
+
* `markRootfsImageClean(path)` once they're done — `boot()` does this
|
|
1516
|
+
* from its child-exit handler when the VMM exits without a signal,
|
|
1517
|
+
* `provision()` does it after cloning the image read-only. If the
|
|
1518
|
+
* marker is never recreated (caller crashed mid-write or simply
|
|
1519
|
+
* forgot), the next `ensureRootfsImage()` for the same tarball
|
|
1520
|
+
* treats the image as poisoned and rebuilds it.
|
|
1521
|
+
*
|
|
1522
|
+
* @throws {ProvisionError} ROOTFS_IMG_TOOL_MISSING (no e2fsprogs found)
|
|
1523
|
+
* | PROVISION_BASE_NOT_FOUND (tarball missing) |
|
|
1524
|
+
* PROVISION_INSTALL_HOOK_FAILED (tar / mke2fs failed)
|
|
1525
|
+
*/
|
|
1526
|
+
declare function ensureRootfsImage(tarPath: string, opts?: EnsureRootfsImageOptions): string;
|
|
1527
|
+
/**
|
|
1528
|
+
* Resolve the mke2fs binary path using the same lookup order as
|
|
1529
|
+
* `ensureRootfsImage` itself: env override → bundled package → PATH →
|
|
1530
|
+
* Homebrew keg-only. Returns `undefined` when no binary is available
|
|
1531
|
+
* (callers should treat this as "skip the optimization", not an error).
|
|
1532
|
+
*
|
|
1533
|
+
* Exported so other tools that need to run mke2fs (e.g. `mountdisk-img`)
|
|
1534
|
+
* resolve the binary through the same lookup chain.
|
|
1535
|
+
*/
|
|
1536
|
+
declare function resolveMke2fs(): string | undefined;
|
|
1537
|
+
|
|
1538
|
+
/** Default cache root: `~/.cache/machinen/mountdisk`. */
|
|
1539
|
+
declare function mountdiskImgCacheDir(): string;
|
|
1540
|
+
/**
|
|
1541
|
+
* Mark a cached squashfs lower as "cleanly released," same idiom as
|
|
1542
|
+
* `markRootfsImageClean()`. The lower is read-only inside the guest
|
|
1543
|
+
* so corruption is unlikely, but a host crash mid-write during the
|
|
1544
|
+
* initial mksquashfs would leave a truncated file in the cache.
|
|
1545
|
+
*
|
|
1546
|
+
* No-op when the image doesn't exist. Failures are swallowed.
|
|
1547
|
+
*/
|
|
1548
|
+
declare function markMountDiskImageClean(imgPath: string): void;
|
|
1549
|
+
interface EnsureMountDiskImageOptions {
|
|
1550
|
+
/** Override the cache directory. Default: `~/.cache/machinen/mountdisk`. */
|
|
1551
|
+
cacheDir?: string;
|
|
1552
|
+
/** Force re-materialization. Mostly for debugging the materializer. */
|
|
1553
|
+
force?: boolean;
|
|
1554
|
+
/**
|
|
1555
|
+
* Sub-phase callback for the caller's PhaseTimer. Fires for each
|
|
1556
|
+
* measurable internal step: `manifest-hash`, `mksquashfs`,
|
|
1557
|
+
* `staging-rename`. The caller usually does
|
|
1558
|
+
* `phases.mark("<parent>.${name}", ms)`.
|
|
1559
|
+
*/
|
|
1560
|
+
onPhase?: (name: string, ms: number) => void;
|
|
1561
|
+
}
|
|
1562
|
+
interface EnsureMountDiskImageResult {
|
|
1563
|
+
/** Absolute path to the cached squashfs lower. */
|
|
1564
|
+
lowerPath: string;
|
|
1565
|
+
/** Tree-manifest sha256 — also the cache key. Useful for tests. */
|
|
1566
|
+
key: string;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Resolve `hostAbs` to a content-addressed squashfs lower image,
|
|
1570
|
+
* materializing it on first call. Returns the absolute path to the
|
|
1571
|
+
* cached `.sqfs`.
|
|
1572
|
+
*
|
|
1573
|
+
* Cache key: sha256 of a sorted manifest covering relpath, mode,
|
|
1574
|
+
* size, mtime_ns, and either the symlink target or the per-file
|
|
1575
|
+
* sha256. Same input tree → same image, even across runs and
|
|
1576
|
+
* processes. Concurrent callers don't race because we materialize
|
|
1577
|
+
* into a uniquely-named staging directory and atomically rename.
|
|
1578
|
+
*
|
|
1579
|
+
* Lifecycle (mirrors rootfs-img.ts): the returned path is in the
|
|
1580
|
+
* "in-use" state (no `.ok` marker on disk). The caller invokes
|
|
1581
|
+
* `markMountDiskImageClean(path)` once they're done.
|
|
1582
|
+
*
|
|
1583
|
+
* @throws {BootError} BOOT_MOUNTDISK_TOOL_MISSING when no mksquashfs
|
|
1584
|
+
* binary is found |
|
|
1585
|
+
* {ProvisionError} PROVISION_INSTALL_HOOK_FAILED when mksquashfs
|
|
1586
|
+
* exits non-zero |
|
|
1587
|
+
* {BootError} BOOT_MOUNT_HOST_NOT_FOUND when the source dir is
|
|
1588
|
+
* missing |
|
|
1589
|
+
* {BootError} BOOT_MOUNT_INVALID when the source dir isn't a
|
|
1590
|
+
* directory.
|
|
1591
|
+
*/
|
|
1592
|
+
declare function ensureMountDiskImage(hostAbs: string, opts?: EnsureMountDiskImageOptions): EnsureMountDiskImageResult;
|
|
1593
|
+
interface EnsureMountDiskUpperOptions {
|
|
1594
|
+
/**
|
|
1595
|
+
* Target size in bytes. Default 4 GiB. Sparse, so unused capacity
|
|
1596
|
+
* costs nothing on the host disk. Mirrors `rootDiskSizeBytes` —
|
|
1597
|
+
* over-provision to give the guest room to write without
|
|
1598
|
+
* having to grow the file mid-VM.
|
|
1599
|
+
*/
|
|
1600
|
+
sizeBytes?: number;
|
|
1601
|
+
}
|
|
1602
|
+
interface EnsureMountDiskUpperResult {
|
|
1603
|
+
/** Absolute path to the per-VM ext4 upper image. */
|
|
1604
|
+
upperPath: string;
|
|
1605
|
+
/** Size in bytes the file was allocated at. */
|
|
1606
|
+
sizeBytes: number;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Materialize a per-VM ext4 RW upper image for the mount overlay.
|
|
1610
|
+
* Each call returns a fresh sparse file in `tmpdir()` — the upper is
|
|
1611
|
+
* specific to one VM and gets cleaned up alongside the per-boot
|
|
1612
|
+
* rootdisk reflink. Snapshots reflink the upper into the bundle so
|
|
1613
|
+
* writes survive snapshot/restore.
|
|
1614
|
+
*
|
|
1615
|
+
* Mirrors rootfs-img.ts's mke2fs lookup for the file-format step;
|
|
1616
|
+
* shares the same `BOOT_MOUNTDISK_TOOL_MISSING` failure mode if
|
|
1617
|
+
* mke2fs is unavailable (the runtime needs e2fsprogs anyway for the
|
|
1618
|
+
* rootdisk path, so this is rarely the failure that fires first).
|
|
1619
|
+
*
|
|
1620
|
+
* @throws {BootError} BOOT_MOUNTDISK_TOOL_MISSING when no mke2fs is
|
|
1621
|
+
* available |
|
|
1622
|
+
* {ProvisionError} PROVISION_INSTALL_HOOK_FAILED when mke2fs fails.
|
|
1623
|
+
*/
|
|
1624
|
+
declare function ensureMountDiskUpper(opts?: EnsureMountDiskUpperOptions): EnsureMountDiskUpperResult;
|
|
1625
|
+
/**
|
|
1626
|
+
* Resolve the mksquashfs binary path using the same lookup order as
|
|
1627
|
+
* `ensureMountDiskImage` itself: env override → bundled package →
|
|
1628
|
+
* PATH → Homebrew opt prefix. Returns `undefined` when no binary is
|
|
1629
|
+
* available.
|
|
1630
|
+
*/
|
|
1631
|
+
declare function resolveMksquashfs(): string | undefined;
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Default directory for `<pid>.boot.log` snapshots. Honors
|
|
1635
|
+
* `MACHINEN_DETACHED_LOG_DIR` so tests can scope writes to a tmpdir
|
|
1636
|
+
* without scribbling under `$HOME`.
|
|
1637
|
+
*/
|
|
1638
|
+
declare function detachedLogRoot(): string;
|
|
1639
|
+
/** Path the next snapshot for `pid` will be written to. */
|
|
1640
|
+
declare function bootSnapshotPath(pid: number): string;
|
|
1641
|
+
/**
|
|
1642
|
+
* Atomically write the captured boot console to `path`. Best-effort:
|
|
1643
|
+
* a failure here must not block the detach — the VMM is already
|
|
1644
|
+
* running and the boot succeeded, so a missing snapshot is a
|
|
1645
|
+
* diagnostic loss, not a correctness issue. Returns `true` on
|
|
1646
|
+
* success, `false` if the write was skipped or failed.
|
|
1647
|
+
*/
|
|
1648
|
+
declare function writeBootSnapshot(path: string, contents: string): boolean;
|
|
1649
|
+
|
|
1650
|
+
/** Result of `validatePid` — easy to switch on at the call site. */
|
|
1651
|
+
type PidStatus = "alive" | "dead" | "recycled";
|
|
1652
|
+
/**
|
|
1653
|
+
* Return whether the running process at `pid` is still our VMM.
|
|
1654
|
+
*
|
|
1655
|
+
* - `alive` — pid is alive AND the exe + start-time match.
|
|
1656
|
+
* - `dead` — kill(pid, 0) failed (gone or permission-denied,
|
|
1657
|
+
* either way unreachable).
|
|
1658
|
+
* - `recycled` — pid is alive but the process isn't ours (different
|
|
1659
|
+
* exe, or start time outside skew).
|
|
1660
|
+
*
|
|
1661
|
+
* Falls back to `alive` when the recorded entry lacks `vmmExe` /
|
|
1662
|
+
* `startedAt` (older entries from before PR2). Conservative on
|
|
1663
|
+
* purpose: the gc decision then leans on `kill(pid, 0)` alone, same
|
|
1664
|
+
* behaviour we had before.
|
|
1665
|
+
*/
|
|
1666
|
+
declare function validatePid(pid: number, expected: {
|
|
1667
|
+
vmmExe?: string;
|
|
1668
|
+
startedAt?: number;
|
|
1669
|
+
}): PidStatus;
|
|
1670
|
+
|
|
1671
|
+
/** Per-entry record of what `runGc` did (or would do, with dryRun). */
|
|
1672
|
+
interface GcResult {
|
|
1673
|
+
pid: number;
|
|
1674
|
+
name?: string;
|
|
1675
|
+
status: PidStatus;
|
|
1676
|
+
/** Paths removed (or that would be removed under `dryRun`). */
|
|
1677
|
+
removedPaths: string[];
|
|
1678
|
+
/** Paths the gc tried to rm but couldn't (already gone, EPERM, …). */
|
|
1679
|
+
failedPaths: string[];
|
|
1680
|
+
/** True if the registry entry was (or would be) dropped. */
|
|
1681
|
+
registryRemoved: boolean;
|
|
1682
|
+
}
|
|
1683
|
+
interface RunGcOptions {
|
|
1684
|
+
/**
|
|
1685
|
+
* When true, list what would be cleaned without touching the disk
|
|
1686
|
+
* or registry. Used by `machinen gc --dry-run` and tests.
|
|
1687
|
+
*/
|
|
1688
|
+
dryRun?: boolean;
|
|
1689
|
+
/**
|
|
1690
|
+
* Only act on this single entry (skip everything else in the
|
|
1691
|
+
* registry). Used by `machinen stop` after killing a specific VM.
|
|
1692
|
+
*/
|
|
1693
|
+
pid?: number;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Walk the registry; for each entry that's dead or pid-recycled,
|
|
1697
|
+
* remove its cleanupPaths + bootLog + registry entry. Returns one
|
|
1698
|
+
* result per entry processed (live entries are skipped silently).
|
|
1699
|
+
*/
|
|
1700
|
+
declare function runGc(opts?: RunGcOptions): GcResult[];
|
|
1701
|
+
|
|
1702
|
+
interface RegistryEntry {
|
|
1703
|
+
/** PID of the VMM process on this host — primary key. */
|
|
1704
|
+
pid: number;
|
|
1705
|
+
/** Optional human-friendly name (from `boot({ name })`). Path-shaped allowed. */
|
|
1706
|
+
name?: string;
|
|
1707
|
+
/** Host-side vsock UDS the exec-agent is reachable on. */
|
|
1708
|
+
socketPath: string;
|
|
1709
|
+
/** Path to the image the VM was booted from (diagnostic only). */
|
|
1710
|
+
imagePath?: string;
|
|
1711
|
+
/**
|
|
1712
|
+
* Host-side path of the scratch disk attached to the guest. Used by
|
|
1713
|
+
* `attach().snapshot()` so an attach-owned handle can find the
|
|
1714
|
+
* guest-side scratch disk that backs the in-VM dump.
|
|
1715
|
+
*/
|
|
1716
|
+
diskPath?: string;
|
|
1717
|
+
/**
|
|
1718
|
+
* Absolute path to the snapshot directory this VM was forked from
|
|
1719
|
+
* (set by `restore({ snapDir })`). Visible in `ls`; informational.
|
|
1720
|
+
*/
|
|
1721
|
+
forkedFrom?: string;
|
|
1722
|
+
/**
|
|
1723
|
+
* Path to the one-shot boot-console snapshot written at detach time
|
|
1724
|
+
* (issue #150 phase 2). Only set on entries booted with
|
|
1725
|
+
* `--detached`; live post-detach console bytes are dropped on the
|
|
1726
|
+
* floor (the VMM ignores SIGPIPE), so this file is the only record
|
|
1727
|
+
* of the boot sequence on a detached VM.
|
|
1728
|
+
*/
|
|
1729
|
+
bootLogPath?: string;
|
|
1730
|
+
/**
|
|
1731
|
+
* Per-boot artifacts that need to be removed when the VMM exits.
|
|
1732
|
+
* Today the in-process exit hook handles this for non-detached
|
|
1733
|
+
* boots. After detach (#150 phase 2) the parent is gone before the
|
|
1734
|
+
* VMM exits — `machinen gc` / `machinen stop` use this list to
|
|
1735
|
+
* clean up afterward. Each entry is an absolute path to either a
|
|
1736
|
+
* file (per-boot disk image) or a directory (bundle / vsock UDS).
|
|
1737
|
+
*/
|
|
1738
|
+
cleanupPaths?: string[];
|
|
1739
|
+
/**
|
|
1740
|
+
* Absolute path to the VMM binary that was spawned. `machinen gc`
|
|
1741
|
+
* compares this against `/proc/<pid>/exe` (Linux) or `ps -o comm=`
|
|
1742
|
+
* (macOS) before treating an entry as live — without it, a recycled
|
|
1743
|
+
* pid that happens to belong to some other process would look alive
|
|
1744
|
+
* to `kill(pid, 0)` and the entry would be kept around forever.
|
|
1745
|
+
*/
|
|
1746
|
+
vmmExe?: string;
|
|
1747
|
+
/**
|
|
1748
|
+
* PID of the gvproxy process spawned alongside this VMM (issue #150
|
|
1749
|
+
* phase 2 PR3). Recorded so `machinen stop` can SIGTERM gvproxy at
|
|
1750
|
+
* the same time as the VMM, and so `machinen gc` can validate /
|
|
1751
|
+
* reap it independently. Undefined when the VM was booted without
|
|
1752
|
+
* networking (no gvproxy binary, or `MACHINEN_NET_SOCKET` was
|
|
1753
|
+
* pre-set by the caller).
|
|
1754
|
+
*/
|
|
1755
|
+
gvproxyPid?: number;
|
|
1756
|
+
/**
|
|
1757
|
+
* Absolute path to the gvproxy binary spawned for this VM. Used by
|
|
1758
|
+
* `machinen stop` for the same anti-recycling check the VMM gets
|
|
1759
|
+
* via `vmmExe` — we don't want to SIGTERM whatever process inherits
|
|
1760
|
+
* gvproxy's pid weeks later.
|
|
1761
|
+
*/
|
|
1762
|
+
gvproxyExe?: string;
|
|
1763
|
+
/**
|
|
1764
|
+
* Host→guest port forwards configured at boot/fork time. Surfaced
|
|
1765
|
+
* in `machinen ls` so users can see which host port maps to which
|
|
1766
|
+
* VM without re-reading the launch command. Undefined when the VM
|
|
1767
|
+
* was booted without `-p` / `portForward: []`.
|
|
1768
|
+
*/
|
|
1769
|
+
portForward?: Array<{
|
|
1770
|
+
hostPort: number;
|
|
1771
|
+
guestPort: number;
|
|
1772
|
+
hostAddr?: string;
|
|
1773
|
+
}>;
|
|
1774
|
+
/**
|
|
1775
|
+
* Guest RAM ceiling in MiB, as resolved by `boot()` (either the
|
|
1776
|
+
* caller's `memory:` option or `autoSizeMemoryMib()` for this host
|
|
1777
|
+
* — see #263 phase A). Surfaced in `machinen ls` (MEM column) and
|
|
1778
|
+
* read by `vm.memoryStats()` so callers can compare host RSS
|
|
1779
|
+
* against the ceiling without re-deriving it. Undefined when the
|
|
1780
|
+
* caller pre-set `MACHINEN_MEMORY` via `vmmEnv` and we never
|
|
1781
|
+
* computed our own.
|
|
1782
|
+
*/
|
|
1783
|
+
memoryCeilingMib?: number;
|
|
1784
|
+
/**
|
|
1785
|
+
* Absolute path to the shared stats file the VMM writes balloon
|
|
1786
|
+
* counters to (#274). 16 bytes, mmaped MAP_SHARED on the VMM side
|
|
1787
|
+
* via `MACHINEN_STATS_FILE`. Persisted so an attach-owned handle
|
|
1788
|
+
* can read the same counters its boot-owned sibling sees. Undefined
|
|
1789
|
+
* for VMMs launched outside the runtime (which never received the
|
|
1790
|
+
* env var).
|
|
1791
|
+
*/
|
|
1792
|
+
statsPath?: string;
|
|
1793
|
+
/**
|
|
1794
|
+
* Total pages the lazy-pages rewriter (#266) marked PE_LAZY when
|
|
1795
|
+
* the VM was restored. Set on restore-derived entries, undefined
|
|
1796
|
+
* for plain boots and eager restores. Surfaced via
|
|
1797
|
+
* `vm.memoryStats().lazyPagesPending`.
|
|
1798
|
+
*/
|
|
1799
|
+
lazyPagesTotal?: number;
|
|
1800
|
+
/**
|
|
1801
|
+
* Absolute path under which the lazy-restore FUSE mount serves
|
|
1802
|
+
* `pages-*.img` reads. The mount-server tracks bytes served below
|
|
1803
|
+
* this prefix; `vm.memoryStats()` divides that by 4096 and
|
|
1804
|
+
* subtracts from `lazyPagesTotal` to derive `lazyPagesPending`.
|
|
1805
|
+
* Undefined when the VM wasn't lazy-restored.
|
|
1806
|
+
*/
|
|
1807
|
+
lazyPagesMountRoot?: string;
|
|
1808
|
+
/**
|
|
1809
|
+
* #272: when the VM was booted with `mount: { host, guest }`, the
|
|
1810
|
+
* runtime materialized a squashfs RO lower + ext4 RW upper. Persist
|
|
1811
|
+
* those host paths so an attach-owned `vm.snapshot()` /
|
|
1812
|
+
* `vm.fork()` can reflink them into the snapshot bundle exactly
|
|
1813
|
+
* like the boot-owned handle does — without this, a CLI-side
|
|
1814
|
+
* `machinen snapshot <vm>` produces a bundle missing
|
|
1815
|
+
* `mount-lower.sqfs` / `mount-upper.img` and a later `restore`
|
|
1816
|
+
* silently boots without the overlay.
|
|
1817
|
+
*/
|
|
1818
|
+
mountDisk?: {
|
|
1819
|
+
guest: string;
|
|
1820
|
+
lowerPath: string;
|
|
1821
|
+
upperPath: string;
|
|
1822
|
+
};
|
|
1823
|
+
/**
|
|
1824
|
+
* #273: live-share FUSE mounts (`liveMounts: [...]` at boot) the
|
|
1825
|
+
* VM was started with. Persisted so an attach-owned `vm.snapshot()`
|
|
1826
|
+
* / `vm.fork()` can record the same `meta.liveMounts` block in the
|
|
1827
|
+
* bundle and trigger /sbin/machinen-remount post-dump on
|
|
1828
|
+
* leaveRunning paths. Host UDS paths and vsock ports are NOT
|
|
1829
|
+
* recorded — those are the boot process's private state and aren't
|
|
1830
|
+
* useful to other processes (the owning process keeps the servers
|
|
1831
|
+
* listening through the dump, so attach reconnects without having
|
|
1832
|
+
* to bind anything).
|
|
1833
|
+
*/
|
|
1834
|
+
liveMounts?: Array<{
|
|
1835
|
+
guest: string;
|
|
1836
|
+
host: string;
|
|
1837
|
+
mode: "ro" | "rw";
|
|
1838
|
+
}>;
|
|
1839
|
+
/**
|
|
1840
|
+
* #150 phase 3: pids + exes of the detached mount-server helpers
|
|
1841
|
+
* spawned alongside this VMM, one per live-mount. The helpers die
|
|
1842
|
+
* with the VMM via `pdeathsig --watch-pid` already, but `machinen
|
|
1843
|
+
* stop` SIGTERMs them up-front so the VMM exit hook doesn't race
|
|
1844
|
+
* with the helper's own pdeathsig-driven shutdown, and `machinen
|
|
1845
|
+
* gc` validates pid+exe to detect recycled pids the same way the
|
|
1846
|
+
* VMM and gvproxy entries do. Empty / undefined for VMs booted
|
|
1847
|
+
* without `liveMounts`.
|
|
1848
|
+
*/
|
|
1849
|
+
liveMountServers?: Array<{
|
|
1850
|
+
pid: number;
|
|
1851
|
+
exe: string;
|
|
1852
|
+
}>;
|
|
1853
|
+
/** ms epoch when the entry was created. */
|
|
1854
|
+
startedAt: number;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Absolute path to the registry root. Honors `MACHINEN_REGISTRY_DIR`
|
|
1858
|
+
* so tests can point at a scratch dir without stomping on real entries.
|
|
1859
|
+
*/
|
|
1860
|
+
declare function registryRoot(): string;
|
|
1861
|
+
/**
|
|
1862
|
+
* List all registry entries whose pid is still alive. Prunes stale
|
|
1863
|
+
* entries (pid no longer alive) and orphaned name pins as a side
|
|
1864
|
+
* effect, so a crashed VMM doesn't leave a stuck record behind.
|
|
1865
|
+
*/
|
|
1866
|
+
declare function list(): RegistryEntry[];
|
|
1867
|
+
|
|
1868
|
+
interface PackBundleOptions {
|
|
1869
|
+
/** Bundle directory with rootfs/ + machinen-config.json. */
|
|
1870
|
+
bundle: string;
|
|
1871
|
+
/** Path to the initramfs cpio to write. */
|
|
1872
|
+
out: string;
|
|
1873
|
+
/** Optional base rootfs tarball (rootfs-debian-arm64.tar.gz). */
|
|
1874
|
+
base?: string;
|
|
1875
|
+
/**
|
|
1876
|
+
* A single host directory copied into the guest between the base
|
|
1877
|
+
* tarball and the bundle's rootfs. Bundle files win on path
|
|
1878
|
+
* collisions. The caller is responsible for validating host exists
|
|
1879
|
+
* and is a directory, and that guest lives under `/mnt/`. See #64.
|
|
1880
|
+
*/
|
|
1881
|
+
mount?: {
|
|
1882
|
+
host: string;
|
|
1883
|
+
guest: string;
|
|
1884
|
+
};
|
|
1885
|
+
/**
|
|
1886
|
+
* Extra env vars to merge into the bundle's machinen-config.json `env`
|
|
1887
|
+
* field before packing. The bundle's on-disk env wins on key collision
|
|
1888
|
+
* (same precedence as the mount overlay — bundle always gets the last
|
|
1889
|
+
* word). See #89.
|
|
1890
|
+
*/
|
|
1891
|
+
env?: Record<string, string>;
|
|
1892
|
+
/** fnmatch patterns matched against each rootfs-relative path. */
|
|
1893
|
+
excludes?: string[];
|
|
1894
|
+
/** Optional path to the compiled /init. Default: ../microvm/test-fixtures/init relative to this file. */
|
|
1895
|
+
initPath?: string;
|
|
1896
|
+
/**
|
|
1897
|
+
* Optional host path to the compiled fuse-agent binary. When set,
|
|
1898
|
+
* the binary is injected at `/fuse-agent` (mode 0755) inside the
|
|
1899
|
+
* initramfs so /init can fork it per live-share mount. See #78.
|
|
1900
|
+
*/
|
|
1901
|
+
fuseAgentPath?: string;
|
|
1902
|
+
/**
|
|
1903
|
+
* Optional path to the compiled /exec-agent. Default: same dir as
|
|
1904
|
+
* /init under packages/microvm/test-fixtures/. Used to override the
|
|
1905
|
+
* stale /exec-agent that may live in a re-provisioned base tarball.
|
|
1906
|
+
*/
|
|
1907
|
+
execAgentPath?: string;
|
|
1908
|
+
}
|
|
1909
|
+
declare function packBundle(opts: PackBundleOptions): void;
|
|
1910
|
+
interface PackTinyBundleOptions {
|
|
1911
|
+
/** Bundle directory with machinen-config.json. The bundle's rootfs/ is ignored — the on-disk rootfs is on /dev/vda. */
|
|
1912
|
+
bundle: string;
|
|
1913
|
+
/** Path to the initramfs cpio to write. */
|
|
1914
|
+
out: string;
|
|
1915
|
+
/** Extra env merged into the bundle's machinen-config.json. Bundle keys win on collision. */
|
|
1916
|
+
env?: Record<string, string>;
|
|
1917
|
+
/**
|
|
1918
|
+
* Guest mountpoint for the `--mount` overlay (#272). When set, the
|
|
1919
|
+
* cpio carries `/etc/machinen-mountdisk-guest` with this path so
|
|
1920
|
+
* /init knows where to layer the squashfs+ext4 overlay after the
|
|
1921
|
+
* rootdisk pivot. The actual payload rides on virtio-blk slots 5+6,
|
|
1922
|
+
* not in the cpio. Must be an absolute path under `/mnt/`.
|
|
1923
|
+
*/
|
|
1924
|
+
mountGuest?: string;
|
|
1925
|
+
/** Optional override for the compiled /init. Default: ../microvm/test-fixtures/init relative to this file. */
|
|
1926
|
+
initPath?: string;
|
|
1927
|
+
/** Optional path to the compiled fuse-agent; staged at /fuse-agent when set. */
|
|
1928
|
+
fuseAgentPath?: string;
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Build the tiny initramfs used by every user-facing boot() (#119).
|
|
1932
|
+
*
|
|
1933
|
+
* Layout:
|
|
1934
|
+
* /init compiled Zig init
|
|
1935
|
+
* /machinen-config.json cmd/env/cwd/liveMounts for /init
|
|
1936
|
+
* /etc/machinen-boot-epoch wall clock seed for the guest
|
|
1937
|
+
* /etc/machinen-mountdisk-guest optional, target dir for the
|
|
1938
|
+
* `--mount` overlay (#272). The
|
|
1939
|
+
* actual payload rides on virtio-
|
|
1940
|
+
* blk slots 5+6, not in the cpio.
|
|
1941
|
+
* /dev/console char node 5,1 — kernel needs it
|
|
1942
|
+
* before /init re-opens the console
|
|
1943
|
+
* /fuse-agent optional, only when liveMounts
|
|
1944
|
+
* /tmp sticky 1777
|
|
1945
|
+
*
|
|
1946
|
+
* No /lib/modules tree, no kmod, no /modules/*.ko, no Debian userland.
|
|
1947
|
+
* The custom kernel ships with virtio_*, ext4, vsock, squashfs, and
|
|
1948
|
+
* overlayfs built in (scripts/build-kernel-arm64.sh), so /init pivots
|
|
1949
|
+
* straight into /dev/vda without a finit_module pass.
|
|
1950
|
+
*/
|
|
1951
|
+
declare function packTinyBundle(opts: PackTinyBundleOptions): void;
|
|
1952
|
+
interface PackRootfsOptions {
|
|
1953
|
+
rootfs: string;
|
|
1954
|
+
out: string;
|
|
1955
|
+
config?: string;
|
|
1956
|
+
excludes?: string[];
|
|
1957
|
+
initPath?: string;
|
|
1958
|
+
}
|
|
1959
|
+
declare function packRootfs(opts: PackRootfsOptions): void;
|
|
1960
|
+
interface PackMinimalOptions {
|
|
1961
|
+
out: string;
|
|
1962
|
+
initPath?: string;
|
|
1963
|
+
config?: string;
|
|
1964
|
+
}
|
|
1965
|
+
declare function packMinimal(opts: PackMinimalOptions): void;
|
|
1966
|
+
interface PackWorkspaceOptions {
|
|
1967
|
+
workspace: string;
|
|
1968
|
+
out: string;
|
|
1969
|
+
/** Directory name inside the cpio (default `workspace`). */
|
|
1970
|
+
mountpoint?: string;
|
|
1971
|
+
/** Basename-matched excludes. Default: DEFAULT_WORKSPACE_EXCLUDES. */
|
|
1972
|
+
excludes?: Iterable<string>;
|
|
1973
|
+
/** Max final size in MiB (default 500). Throws if exceeded. */
|
|
1974
|
+
maxMb?: number;
|
|
1975
|
+
}
|
|
1976
|
+
declare function packWorkspace(opts: PackWorkspaceOptions): void;
|
|
1977
|
+
/**
|
|
1978
|
+
* Invoked by the CLI shim at packages/microvm/test-fixtures/assets/mkinitramfs.ts.
|
|
1979
|
+
* Kept argv-compatible with the old Python script so shell fixtures
|
|
1980
|
+
* (smoke.sh, try.sh, handoff.sh) don't need deeper changes.
|
|
1981
|
+
*/
|
|
1982
|
+
declare function cli(argv: string[]): void;
|
|
1983
|
+
|
|
1984
|
+
declare const ErrorCode: {
|
|
1985
|
+
readonly BOOT_VMM_MISSING: "BOOT_VMM_MISSING";
|
|
1986
|
+
readonly BOOT_VMM_PACKAGE_BROKEN: "BOOT_VMM_PACKAGE_BROKEN";
|
|
1987
|
+
readonly BOOT_IMAGE_NOT_FOUND: "BOOT_IMAGE_NOT_FOUND";
|
|
1988
|
+
readonly BOOT_SNAPSHOT_NOT_FOUND: "BOOT_SNAPSHOT_NOT_FOUND";
|
|
1989
|
+
readonly BOOT_KERNEL_NOT_FOUND: "BOOT_KERNEL_NOT_FOUND";
|
|
1990
|
+
readonly BOOT_DTB_NOT_FOUND: "BOOT_DTB_NOT_FOUND";
|
|
1991
|
+
readonly BOOT_CMD_WITHOUT_IMAGE: "BOOT_CMD_WITHOUT_IMAGE";
|
|
1992
|
+
readonly BOOT_CMD_MISSING: "BOOT_CMD_MISSING";
|
|
1993
|
+
readonly BOOT_CWD_INVALID: "BOOT_CWD_INVALID";
|
|
1994
|
+
readonly BOOT_MOUNT_INVALID: "BOOT_MOUNT_INVALID";
|
|
1995
|
+
readonly BOOT_MOUNT_HOST_NOT_FOUND: "BOOT_MOUNT_HOST_NOT_FOUND";
|
|
1996
|
+
readonly BOOT_LIVE_MOUNT_OVERRIDE_UNKNOWN: "BOOT_LIVE_MOUNT_OVERRIDE_UNKNOWN";
|
|
1997
|
+
readonly BOOT_PORT_FORWARD_INVALID: "BOOT_PORT_FORWARD_INVALID";
|
|
1998
|
+
readonly BOOT_PORT_FORWARD_CONFLICT: "BOOT_PORT_FORWARD_CONFLICT";
|
|
1999
|
+
readonly BOOT_PORT_FORWARD_NO_GVPROXY: "BOOT_PORT_FORWARD_NO_GVPROXY";
|
|
2000
|
+
readonly BOOT_PORT_FORWARD_IN_USE: "BOOT_PORT_FORWARD_IN_USE";
|
|
2001
|
+
readonly BOOT_PACK_FAILED: "BOOT_PACK_FAILED";
|
|
2002
|
+
readonly BOOT_TIMEOUT: "BOOT_TIMEOUT";
|
|
2003
|
+
readonly BOOT_DETACHED_READINESS_FAILED: "BOOT_DETACHED_READINESS_FAILED";
|
|
2004
|
+
readonly BOOT_MEMORY_INVALID: "BOOT_MEMORY_INVALID";
|
|
2005
|
+
readonly FORK_MEMORY_BACKPRESSURE: "FORK_MEMORY_BACKPRESSURE";
|
|
2006
|
+
readonly BOOT_MOUNTDISK_TOOL_MISSING: "BOOT_MOUNTDISK_TOOL_MISSING";
|
|
2007
|
+
readonly EXEC_VSOCK_UNAVAILABLE: "EXEC_VSOCK_UNAVAILABLE";
|
|
2008
|
+
readonly EXEC_AGENT_UNAVAILABLE: "EXEC_AGENT_UNAVAILABLE";
|
|
2009
|
+
readonly EXEC_AGENT_TIMEOUT: "EXEC_AGENT_TIMEOUT";
|
|
2010
|
+
readonly EXEC_NONZERO_EXIT: "EXEC_NONZERO_EXIT";
|
|
2011
|
+
readonly EXEC_PROTOCOL: "EXEC_PROTOCOL";
|
|
2012
|
+
readonly SNAPSHOT_NO_DISK: "SNAPSHOT_NO_DISK";
|
|
2013
|
+
readonly SNAPSHOT_DUMP_FAILED: "SNAPSHOT_DUMP_FAILED";
|
|
2014
|
+
readonly SNAPSHOT_TIMEOUT: "SNAPSHOT_TIMEOUT";
|
|
2015
|
+
readonly PROVISION_BASE_NOT_FOUND: "PROVISION_BASE_NOT_FOUND";
|
|
2016
|
+
readonly PROVISION_KERNEL_NOT_FOUND: "PROVISION_KERNEL_NOT_FOUND";
|
|
2017
|
+
readonly PROVISION_DTB_NOT_FOUND: "PROVISION_DTB_NOT_FOUND";
|
|
2018
|
+
readonly PROVISION_ASSETS_DIR_INVALID: "PROVISION_ASSETS_DIR_INVALID";
|
|
2019
|
+
readonly PROVISION_INSTALL_HOOK_FAILED: "PROVISION_INSTALL_HOOK_FAILED";
|
|
2020
|
+
readonly PROVISION_DISK_TOO_SMALL: "PROVISION_DISK_TOO_SMALL";
|
|
2021
|
+
readonly ROOTFS_IMG_TOOL_MISSING: "ROOTFS_IMG_TOOL_MISSING";
|
|
2022
|
+
readonly REGISTRY_VM_NOT_FOUND: "REGISTRY_VM_NOT_FOUND";
|
|
2023
|
+
readonly REGISTRY_NAME_IN_USE: "REGISTRY_NAME_IN_USE";
|
|
2024
|
+
readonly FILES_HOST_DIR_NOT_FOUND: "FILES_HOST_DIR_NOT_FOUND";
|
|
2025
|
+
readonly FILES_AGENT_UNAVAILABLE: "FILES_AGENT_UNAVAILABLE";
|
|
2026
|
+
readonly MOUNT_PATH_INVALID: "MOUNT_PATH_INVALID";
|
|
2027
|
+
readonly MOUNT_PATH_ESCAPE: "MOUNT_PATH_ESCAPE";
|
|
2028
|
+
readonly MOUNT_SERVER_BIN_MISSING: "MOUNT_SERVER_BIN_MISSING";
|
|
2029
|
+
readonly MOUNT_SERVER_SPAWN_FAILED: "MOUNT_SERVER_SPAWN_FAILED";
|
|
2030
|
+
readonly SECRETS_VALUE_INVALID: "SECRETS_VALUE_INVALID";
|
|
2031
|
+
readonly SECRETS_AGENT_UNAVAILABLE: "SECRETS_AGENT_UNAVAILABLE";
|
|
2032
|
+
readonly WINSIZE_AGENT_UNAVAILABLE: "WINSIZE_AGENT_UNAVAILABLE";
|
|
2033
|
+
readonly SANDBOX_ID_DUPLICATE: "SANDBOX_ID_DUPLICATE";
|
|
2034
|
+
readonly SANDBOX_ID_UNKNOWN: "SANDBOX_ID_UNKNOWN";
|
|
2035
|
+
readonly CACHE_BIND_FAILED: "CACHE_BIND_FAILED";
|
|
2036
|
+
readonly GVPROXY_NOT_FOUND: "GVPROXY_NOT_FOUND";
|
|
2037
|
+
readonly GVPROXY_EXPOSE_FAILED: "GVPROXY_EXPOSE_FAILED";
|
|
2038
|
+
readonly GVPROXY_PORT_IN_USE: "GVPROXY_PORT_IN_USE";
|
|
2039
|
+
readonly GVPROXY_INSTALL_FAILED: "GVPROXY_INSTALL_FAILED";
|
|
2040
|
+
readonly GVPROXY_SPAWN_FAILED: "GVPROXY_SPAWN_FAILED";
|
|
2041
|
+
readonly MKINITRAMFS_BUNDLE_INVALID: "MKINITRAMFS_BUNDLE_INVALID";
|
|
2042
|
+
readonly MKINITRAMFS_WORKSPACE_INVALID: "MKINITRAMFS_WORKSPACE_INVALID";
|
|
2043
|
+
readonly MKINITRAMFS_WORKSPACE_TOO_LARGE: "MKINITRAMFS_WORKSPACE_TOO_LARGE";
|
|
2044
|
+
readonly MKINITRAMFS_BASE_EXTRACT_FAILED: "MKINITRAMFS_BASE_EXTRACT_FAILED";
|
|
2045
|
+
readonly MKINITRAMFS_INIT_MISSING: "MKINITRAMFS_INIT_MISSING";
|
|
2046
|
+
readonly PARSE_FLAG_UNKNOWN: "PARSE_FLAG_UNKNOWN";
|
|
2047
|
+
readonly PARSE_FLAG_MISSING_VALUE: "PARSE_FLAG_MISSING_VALUE";
|
|
2048
|
+
readonly PARSE_FLAG_DUPLICATE: "PARSE_FLAG_DUPLICATE";
|
|
2049
|
+
readonly PARSE_FLAG_MALFORMED: "PARSE_FLAG_MALFORMED";
|
|
2050
|
+
readonly PARSE_PORT_INVALID: "PARSE_PORT_INVALID";
|
|
2051
|
+
};
|
|
2052
|
+
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
2053
|
+
interface MachinenErrorOptions {
|
|
2054
|
+
/**
|
|
2055
|
+
* True if retrying the same call could plausibly succeed (transient
|
|
2056
|
+
* network blip, upstream fetch, vsock agent not listening yet). False
|
|
2057
|
+
* for misconfiguration (missing binary, bad mount path, invalid
|
|
2058
|
+
* port).
|
|
2059
|
+
*/
|
|
2060
|
+
retryable?: boolean;
|
|
2061
|
+
/** Underlying error preserved via the standard `Error.cause` chain. */
|
|
2062
|
+
cause?: unknown;
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Base class for every error raised by @machinen/runtime and
|
|
2066
|
+
* @machinen/cli. Carries a flat `code`, a `retryable` hint, and the
|
|
2067
|
+
* underlying cause via the standard `Error.cause` mechanism.
|
|
2068
|
+
*/
|
|
2069
|
+
declare class MachinenError extends Error {
|
|
2070
|
+
readonly code: ErrorCode;
|
|
2071
|
+
readonly retryable: boolean;
|
|
2072
|
+
constructor(code: ErrorCode, message: string, opts?: MachinenErrorOptions);
|
|
2073
|
+
}
|
|
2074
|
+
declare class BootError extends MachinenError {
|
|
2075
|
+
}
|
|
2076
|
+
declare class ExecError extends MachinenError {
|
|
2077
|
+
}
|
|
2078
|
+
declare class SnapshotError extends MachinenError {
|
|
2079
|
+
}
|
|
2080
|
+
declare class ProvisionError extends MachinenError {
|
|
2081
|
+
}
|
|
2082
|
+
declare class RegistryError extends MachinenError {
|
|
2083
|
+
}
|
|
2084
|
+
declare class FilesError extends MachinenError {
|
|
2085
|
+
}
|
|
2086
|
+
declare class MountError extends MachinenError {
|
|
2087
|
+
}
|
|
2088
|
+
declare class SecretsError extends MachinenError {
|
|
2089
|
+
}
|
|
2090
|
+
declare class WinsizeError extends MachinenError {
|
|
2091
|
+
}
|
|
2092
|
+
declare class SandboxError extends MachinenError {
|
|
2093
|
+
}
|
|
2094
|
+
declare class CacheError extends MachinenError {
|
|
2095
|
+
}
|
|
2096
|
+
declare class GvproxyError extends MachinenError {
|
|
2097
|
+
}
|
|
2098
|
+
declare class MkinitramfsError extends MachinenError {
|
|
2099
|
+
}
|
|
2100
|
+
declare class ParseError extends MachinenError {
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Narrowing type guard. Pass a specific `code` to check both identity
|
|
2104
|
+
* and discriminant in one call.
|
|
2105
|
+
*/
|
|
2106
|
+
declare function isMachinenError(err: unknown, code?: ErrorCode): err is MachinenError;
|
|
2107
|
+
/**
|
|
2108
|
+
* Format a MachinenError for CLI stderr. Shows the code inline and walks
|
|
2109
|
+
* the `cause` chain. Used by the CLI's unified `handleError`; exported so
|
|
2110
|
+
* library callers can adopt the same format if they want to.
|
|
2111
|
+
*/
|
|
2112
|
+
declare function formatMachinenError(err: MachinenError): string;
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Bytes of memory the OS reports as available right now. "Available"
|
|
2116
|
+
* is the loose union the kernel exposes:
|
|
2117
|
+
* - Linux → /proc/meminfo MemAvailable (post-3.14 kernels — every
|
|
2118
|
+
* distro machinen runs on). MemAvailable already accounts
|
|
2119
|
+
* for reclaimable slab + page-cache, so it's the right
|
|
2120
|
+
* answer for "could a new process allocate X bytes
|
|
2121
|
+
* without paging or OOM?".
|
|
2122
|
+
* - Darwin → vm_stat free + speculative + purgeable. Inactive is
|
|
2123
|
+
* excluded because it's dirty and needs a pageout, which
|
|
2124
|
+
* wouldn't help a fork that needs RAM right now.
|
|
2125
|
+
* - other → totalmem(). Soft-fail rather than block fork on a
|
|
2126
|
+
* platform we can't measure.
|
|
2127
|
+
*/
|
|
2128
|
+
declare function readHostFreeBytes(): Promise<number>;
|
|
2129
|
+
/**
|
|
2130
|
+
* Total physical memory in bytes. Thin wrapper over `os.totalmem()`
|
|
2131
|
+
* exported alongside the free reader so tests and the backpressure
|
|
2132
|
+
* check pull both numbers from the same module.
|
|
2133
|
+
*/
|
|
2134
|
+
declare function readHostTotalBytes(): number;
|
|
2135
|
+
/**
|
|
2136
|
+
* Default fraction of host memory we require to be free before
|
|
2137
|
+
* `vm.fork()` is allowed to proceed. The gate exists to keep a
|
|
2138
|
+
* runaway script from OOM-killing arbitrary host processes — not
|
|
2139
|
+
* to enforce a particular working-set policy. 1% on a 24 GiB host
|
|
2140
|
+
* = ~250 MiB, enough headroom for the lazy-restore criu spawn
|
|
2141
|
+
* (#266) plus a typical workload's UFFD page-in burst, while still
|
|
2142
|
+
* tripping early enough that a host with only a few hundred MiB
|
|
2143
|
+
* free fails fast instead of triggering the kernel OOM killer.
|
|
2144
|
+
*
|
|
2145
|
+
* Smoke-test rationale: a host running `pnpm smoke-tests` sees
|
|
2146
|
+
* five sequential VMs leave it with ~1 GiB free in steady state.
|
|
2147
|
+
* Anything stricter than this default trips on real-world dev
|
|
2148
|
+
* loops; anything looser stops being a meaningful gate.
|
|
2149
|
+
*/
|
|
2150
|
+
declare const DEFAULT_FREE_MEMORY_THRESHOLD = 0.01;
|
|
2151
|
+
interface CheckForkBackpressureOptions {
|
|
2152
|
+
/**
|
|
2153
|
+
* Fraction of host total memory that must remain free for a fork
|
|
2154
|
+
* to proceed. Pass `0` (or any non-positive number) to disable the
|
|
2155
|
+
* gate entirely. Capped at `1` — `0.5` already means "refuse
|
|
2156
|
+
* unless half the host is free."
|
|
2157
|
+
*/
|
|
2158
|
+
threshold: number;
|
|
2159
|
+
/** Pluggable for tests; defaults to {@link readHostFreeBytes}. */
|
|
2160
|
+
readFree?: () => Promise<number>;
|
|
2161
|
+
/** Pluggable for tests; defaults to {@link readHostTotalBytes}. */
|
|
2162
|
+
totalBytes?: number;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Refuse a fork when the host is under memory pressure. Throws
|
|
2166
|
+
* `BootError("FORK_MEMORY_BACKPRESSURE")` when free < total *
|
|
2167
|
+
* threshold, modeled on the throw-immediately shape of #267's
|
|
2168
|
+
* `BOOT_PORT_FORWARD_IN_USE` gate. Caller is responsible for any
|
|
2169
|
+
* retry policy.
|
|
2170
|
+
*/
|
|
2171
|
+
declare function checkForkBackpressure(opts: CheckForkBackpressureOptions): Promise<void>;
|
|
2172
|
+
|
|
2173
|
+
/** A pid plus the absolute path to its stats file (when available). */
|
|
2174
|
+
interface RssTarget {
|
|
2175
|
+
pid: number;
|
|
2176
|
+
/**
|
|
2177
|
+
* MACHINEN_STATS_FILE path for this VMM (registry entry's
|
|
2178
|
+
* `statsPath`). On Darwin we read `phys_footprint` from this file
|
|
2179
|
+
* in preference to `ps -o rss=`. Optional / undefined for arbitrary
|
|
2180
|
+
* pids that aren't machinen-managed; those fall back to ps.
|
|
2181
|
+
*/
|
|
2182
|
+
statsPath?: string;
|
|
2183
|
+
}
|
|
2184
|
+
/** RSS bytes for one pid, or null if not readable. */
|
|
2185
|
+
declare function readHostRssBytes(pid: number, statsPath?: string): number | null;
|
|
2186
|
+
/**
|
|
2187
|
+
* Bulk variant for `machinen ls`: one syscall (Linux) or one
|
|
2188
|
+
* subprocess (Darwin) for every live VM, instead of N. Pids that
|
|
2189
|
+
* can't be read are simply absent from the result map — caller
|
|
2190
|
+
* decides whether to render "?" or skip the row.
|
|
2191
|
+
*/
|
|
2192
|
+
declare function readHostRssBytesMulti(targets: ReadonlyArray<RssTarget | number>): Map<number, number>;
|
|
2193
|
+
|
|
2194
|
+
declare const STATS_FILE_SIZE = 24;
|
|
2195
|
+
interface BalloonCounters {
|
|
2196
|
+
/** Total bytes the balloon device has reclaimed via reporting. */
|
|
2197
|
+
bytesReported: number;
|
|
2198
|
+
/**
|
|
2199
|
+
* Total bytes the inflate queue has seen. We don't drive inflate
|
|
2200
|
+
* (`num_pages` stays 0), so this stays at 0 in well-behaved
|
|
2201
|
+
* deployments — non-zero means a buggy/hostile guest is pushing
|
|
2202
|
+
* pages into the balloon on its own.
|
|
2203
|
+
*/
|
|
2204
|
+
bytesInflated: number;
|
|
2205
|
+
/**
|
|
2206
|
+
* Latest sample of this VMM's Darwin `phys_footprint` (the metric
|
|
2207
|
+
* that backs Activity Monitor's "Memory" column and excludes
|
|
2208
|
+
* `MADV_FREE_REUSABLE` pages). Refreshed every ~500 ms by a
|
|
2209
|
+
* sampler thread inside the VMM. Always 0 on Linux — there's no
|
|
2210
|
+
* Darwin-equivalent metric and the runtime reads
|
|
2211
|
+
* `/proc/<pid>/status:VmRSS` instead, which already reflects
|
|
2212
|
+
* `MADV_DONTNEED` reclaim.
|
|
2213
|
+
*/
|
|
2214
|
+
hostPhysFootprintBytes: number;
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Read the balloon-stats file at `path`. Returns `null` when:
|
|
2218
|
+
* - the file is missing (VMM was launched without
|
|
2219
|
+
* `MACHINEN_STATS_FILE`, or the path is stale),
|
|
2220
|
+
* - it's shorter than `STATS_FILE_SIZE` (truncated mid-write — not
|
|
2221
|
+
* possible with the mmap'd writer, but defensive against an
|
|
2222
|
+
* out-of-band actor),
|
|
2223
|
+
* - it's unreadable (permissions, gone between stat and read).
|
|
2224
|
+
*/
|
|
2225
|
+
declare function readBalloonStats(path: string): BalloonCounters | null;
|
|
2226
|
+
|
|
2227
|
+
export { type AttachOptions, type BalloonCounters, BootError, type BootOptions, CacheError, type CheckForkBackpressureOptions, type ChunkLogEvent, DEFAULT_FREE_MEMORY_THRESHOLD, type EnsureMountDiskImageOptions, type EnsureMountDiskImageResult, type EnsureMountDiskUpperOptions, type EnsureMountDiskUpperResult, type EnsureRootfsImageOptions, ErrorCode, ExecError, FilesError, type ForkOptions, type GcResult, GvproxyError, type ImageConfig, type LogEvent, MachinenError, type MachinenErrorOptions, type MemoryStats, MkinitramfsError, MountError, type OnLog, type OnOutputListener, type PackBundleOptions, type PackMinimalOptions, type PackRootfsOptions, type PackTinyBundleOptions, type PackWorkspaceOptions, ParseError, type PhaseLogEvent, type PidStatus, ProvisionError, type ProvisionOptions, type ProvisionResult, type PtyBootOptions, type PtyVmHandle, type RegistryEntry, RegistryError, type RestoreOptions, type RssTarget, type RunGcOptions, STATS_FILE_SIZE, type SandboxEntry, SandboxError, Sandboxes, SecretsError, SnapshotError, type SnapshotMeta, type SnapshotOptions, type SnapshotResult, Supervisor, type SupervisorOptions, type VmHandle, VsockExec, type VsockExecOptions, type VsockExecPtyHandle, type VsockExecPtyOptions, type VsockExecPtyResult, type VsockExecResult, VsockFiles, type VsockFilesOptions, VsockSecrets, type VsockSecretsOptions, VsockWinsize, type VsockWinsizeOptions, WinsizeError, type WriteFileOptions, _internal, attach, autoSizeMemoryMib, boot, bootPty, bootSnapshotPath, buildMachinenConfig, buildWriteFileCmd, buildWriteFileCmds, checkForkBackpressure, detachedLogRoot, ensureMountDiskImage, ensureMountDiskUpper, ensureRootfsImage, formatMachinenError, isMachinenError, list, markMountDiskImageClean, markRootfsImageClean, measureFirstByte, packBundle as mkinitramfsBundle, cli as mkinitramfsCli, packMinimal as mkinitramfsMinimal, packRootfs as mkinitramfsRootfs, packTinyBundle as mkinitramfsTinyBundle, packWorkspace as mkinitramfsWorkspace, mountdiskImgCacheDir, provision, readBalloonStats, readHostFreeBytes, readHostRssBytes, readHostRssBytesMulti, readHostTotalBytes, registryRoot, resolveBaseDtb, resolveBaseKernel, resolveBaseRootfs, resolveMke2fs, resolveMksquashfs, resolveVmmBinary, restore, rootfsImgCacheDir, runGc, validatePid, warmImageConfigCache, writeBootSnapshot };
|