@supermachine/core 0.5.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/examples/06-mount-workspace.mjs +97 -0
- package/examples/07-npm-workspace-fast.mjs +115 -0
- package/examples/README.md +3 -1
- package/index.d.ts +62 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -182,6 +182,78 @@ const image = await Image.build({
|
|
|
182
182
|
|
|
183
183
|
Host file contents are folded into the snapshot's input-hash, so editing the host file invalidates the cache and rebakes automatically.
|
|
184
184
|
|
|
185
|
+
### Pattern 5: Live host directory mount (virtio-fs DAX)
|
|
186
|
+
|
|
187
|
+
For dev loops, code-mounted-from-host workflows, and "run my project" sandboxes, you want a *live* host directory inside the guest — edits on the host show up in the guest immediately, and the guest can read/write back. This is what `mounts` does: a virtio-fs share with DAX (zero-copy reads through the host page cache, kqueue-driven cache invalidation when host files change).
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const image = await Image.build({
|
|
191
|
+
ref: "node:20-alpine",
|
|
192
|
+
mounts: [
|
|
193
|
+
{
|
|
194
|
+
hostPath: "/Users/me/my-app", // host source tree
|
|
195
|
+
guestTag: "workspace", // guest mounts via this tag
|
|
196
|
+
symlinks: "opaque", // see below
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const vm = await (await image.pool()).acquire();
|
|
202
|
+
// Inside the guest:
|
|
203
|
+
// mount -t virtiofs workspace /work
|
|
204
|
+
// cd /work && npm install
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**`symlinks` policy** picks the trust posture for the mount. Workspace tools (npm workspaces, pnpm, yarn berry) symlink prolifically — `node_modules/<member> → ../packages/<member>` — so symlink creation has to work. But symlinks are also the classic escape vector if you're running untrusted code. The policy lets integrators pick:
|
|
208
|
+
|
|
209
|
+
| Value | Guest can create symlinks? | External host symlinks visible/traversable? | When |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| `"deny"` | no (`EPERM`) | no (`EACCES` at LOOKUP) | Paranoid mounts: pure file content, no metadata surprises |
|
|
212
|
+
| `"opaque"` *(default)* | yes — targets stored verbatim, host never resolves them | no | **Safe multi-tenant default**: npm/pnpm/yarn all work; a hostile guest can plant `escape → /etc/passwd` but the host never follows it |
|
|
213
|
+
| `"follow"` | yes | yes | Trusted single-tenant: dev trees that symlink into Homebrew/`~/.cache`/etc. |
|
|
214
|
+
|
|
215
|
+
The `"opaque"` default exists because of how `symlink(2)` works: the target is opaque bytes stored on the symlink inode — POSIX never validates it. Resolution happens *in the guest's kernel* against the guest's own namespace, so a target like `/etc/passwd` resolves to the guest's `/etc/passwd`, not the host's. The host serves `readlink()` (returns those bytes) and refuses to follow them. That's the security model: guest can store arbitrary bytes, host never canonicalizes guest-supplied paths.
|
|
216
|
+
|
|
217
|
+
A runnable example is in [`examples/06-mount-workspace.mjs`](./examples/06-mount-workspace.mjs).
|
|
218
|
+
|
|
219
|
+
#### Performance: don't put dependency caches on the mount
|
|
220
|
+
|
|
221
|
+
A mount is great for source code (host-editable, DAX-fast reads). It is **the wrong place** for `node_modules`, `__pycache__`, `target/`, `.next/`, or anything else that gets thousands of small files written by a build/install step.
|
|
222
|
+
|
|
223
|
+
Why: every guest file operation on a virtio-fs mount becomes a FUSE message round-trip over vsock — open, write, close, fsync each cost ~µs of in-guest overhead plus ~µs of host-side handling. For one big file (a tarball, an image), that overhead is invisible. For an `npm install` that creates ~10–30 k tiny files, you're paying milliseconds × tens of thousands of round-trips. We've measured the same `npm install` at **3–6 minutes** on a virtio-fs mount vs **10–30 seconds** on a virtio-blk volume backing `node_modules`. Same workload, same data, same VM — the difference is purely FUSE round-trip cost × small-file count.
|
|
224
|
+
|
|
225
|
+
Block devices (`virtio-blk`, exposed via `volumes`) bypass FUSE entirely — the guest's own filesystem driver writes to a backing file on the host with no per-syscall protocol overhead. Same speed as a "normal" disk inside the guest.
|
|
226
|
+
|
|
227
|
+
### Pattern 6: npm / pnpm / cargo workflow (mount for source, volume for cache)
|
|
228
|
+
|
|
229
|
+
The shape that gets you both live host edits and fast installs:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const image = await Image.build({
|
|
233
|
+
ref: "node:20-alpine",
|
|
234
|
+
// Source tree — live host edits, DAX reads
|
|
235
|
+
mounts: [{ hostPath: "/Users/me/my-app", guestTag: "src", symlinks: "opaque" }],
|
|
236
|
+
// Dependency cache — fast writes, persists between bakes via the snapshot
|
|
237
|
+
volumes: [
|
|
238
|
+
{ name: "node_modules", sizeMib: 1024, mountPoint: "/work/node_modules" },
|
|
239
|
+
],
|
|
240
|
+
// One-time install at bake. Once snapshotted, every restore has
|
|
241
|
+
// node_modules already populated — no install on the hot path.
|
|
242
|
+
warmup: async (vm) => {
|
|
243
|
+
await vm.exec({ argv: ["mount", "-t", "virtiofs", "src", "/work"] });
|
|
244
|
+
await vm.exec({ argv: ["cp", "-r", "/work/package*.json", "/work/node_modules/.."] });
|
|
245
|
+
await vm.exec({ argv: ["sh", "-c", "cd /work && npm install"], timeoutMs: 600_000 });
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Three layers, each playing to their strengths:
|
|
251
|
+
- **`mounts`** for source: read-only-ish on the hot path, DAX zero-copy, host edits show up immediately (kqueue invalidation).
|
|
252
|
+
- **`volumes`** for write-heavy caches: native-speed writes, no FUSE overhead, persists across restores.
|
|
253
|
+
- **`warmup`** + bake to do the install once: every restore reuses the baked node_modules; cold install is amortized across all future runs of this snapshot.
|
|
254
|
+
|
|
255
|
+
A runnable example is in [`examples/07-npm-workspace-fast.mjs`](./examples/07-npm-workspace-fast.mjs).
|
|
256
|
+
|
|
185
257
|
---
|
|
186
258
|
|
|
187
259
|
## Full API
|
|
@@ -217,6 +289,7 @@ class Image {
|
|
|
217
289
|
| `guestPort` | `number` | 80 | Listener port (used with `listenerRequired`) |
|
|
218
290
|
| `listenerRequired` | `boolean` | `false` | Wait for workload's listener before snapshotting |
|
|
219
291
|
| `extraFiles` | `ExtraFile[]` | — | Stage host files into guest at fixed paths |
|
|
292
|
+
| `mounts` | `MountSpec[]` | — | Live host dir mounts via virtio-fs DAX. See Pattern 5. |
|
|
220
293
|
| `warmupTag` | `string` | `"default"` | Cache key for the warmup; bump to invalidate |
|
|
221
294
|
| `warmup` | `(vm: WarmupVm) => Promise<void>` | — | Post-bake state pre-population |
|
|
222
295
|
| `snapshotsDir` | `string` | `~/.local/supermachine-snapshots` | Where to write the snapshot |
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// 06-mount-workspace.mjs — mount a host directory into the guest via
|
|
2
|
+
// virtio-fs DAX, then run an `npm install`-style symlink workflow
|
|
3
|
+
// from inside the VM.
|
|
4
|
+
//
|
|
5
|
+
// Demonstrates:
|
|
6
|
+
// - `mounts: [{ hostPath, guestTag, symlinks }]` on Image.build
|
|
7
|
+
// - The per-mount `symlinks` policy: "deny" | "opaque" | "follow"
|
|
8
|
+
// - The npm-workspace symlink pattern (node_modules/<name> →
|
|
9
|
+
// ../packages/<name>) working end-to-end through virtio-fs
|
|
10
|
+
//
|
|
11
|
+
// The mount surfaces host files in the guest with zero-copy DAX
|
|
12
|
+
// reads and kqueue-driven cache invalidation when the host file
|
|
13
|
+
// changes — edit a file on the host, the guest sees the new bytes
|
|
14
|
+
// on the next read with no manual re-mount.
|
|
15
|
+
//
|
|
16
|
+
// `symlinks: "opaque"` is the safe default for guest-writable mounts
|
|
17
|
+
// in a multi-tenant setting: guest can create symlinks (workspace
|
|
18
|
+
// tools need this), targets are stored verbatim (POSIX symlink(2)),
|
|
19
|
+
// and the host never resolves them. A guest planting
|
|
20
|
+
// `escape -> /etc/passwd` gets a symlink whose readlink returns
|
|
21
|
+
// "/etc/passwd" bytes — but the host won't follow it. Path
|
|
22
|
+
// resolution happens in the guest's kernel against the guest's
|
|
23
|
+
// own namespace.
|
|
24
|
+
//
|
|
25
|
+
// Run:
|
|
26
|
+
// node examples/06-mount-workspace.mjs
|
|
27
|
+
|
|
28
|
+
import { Image } from "@supermachine/core";
|
|
29
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readlinkSync } from "node:fs";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
|
|
33
|
+
// 1. Build a host-side workspace tree that mimics what npm creates:
|
|
34
|
+
// project/
|
|
35
|
+
// packages/alpha/index.js "ALPHA"
|
|
36
|
+
// packages/beta/index.js "BETA"
|
|
37
|
+
// Inside the guest we'll add node_modules/<name> symlinks
|
|
38
|
+
// pointing at ../packages/<name>.
|
|
39
|
+
const project = mkdtempSync(join(tmpdir(), "sm-workspace-"));
|
|
40
|
+
mkdirSync(join(project, "packages", "alpha"), { recursive: true });
|
|
41
|
+
mkdirSync(join(project, "packages", "beta"), { recursive: true });
|
|
42
|
+
writeFileSync(join(project, "packages", "alpha", "index.js"), "ALPHA");
|
|
43
|
+
writeFileSync(join(project, "packages", "beta", "index.js"), "BETA");
|
|
44
|
+
|
|
45
|
+
// 2. Bake an image with the project mounted at guest tag "workspace".
|
|
46
|
+
// `symlinks: "opaque"` is the default but spelled out here for
|
|
47
|
+
// documentation value.
|
|
48
|
+
const img = await Image.build({
|
|
49
|
+
ref: "alpine:latest",
|
|
50
|
+
name: "mount-workspace-demo",
|
|
51
|
+
memoryMib: 256,
|
|
52
|
+
cmd: ["/bin/sh", "-c", "sleep infinity"],
|
|
53
|
+
mounts: [
|
|
54
|
+
{
|
|
55
|
+
hostPath: project,
|
|
56
|
+
guestTag: "workspace",
|
|
57
|
+
symlinks: "opaque",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const pool = await img.pool({ min: 1, max: 1, restoreOnRelease: false });
|
|
63
|
+
const vm = await pool.acquire();
|
|
64
|
+
|
|
65
|
+
// 3. Inside the guest: mount the virtio-fs share, create the
|
|
66
|
+
// workspace symlinks, resolve through them.
|
|
67
|
+
const result = await vm.exec({
|
|
68
|
+
argv: [
|
|
69
|
+
"sh",
|
|
70
|
+
"-c",
|
|
71
|
+
[
|
|
72
|
+
"mkdir -p /work && mount -t virtiofs workspace /work",
|
|
73
|
+
"mkdir -p /work/node_modules",
|
|
74
|
+
"ln -s ../packages/alpha /work/node_modules/alpha",
|
|
75
|
+
"ln -s ../packages/beta /work/node_modules/beta",
|
|
76
|
+
"echo === readlink ===",
|
|
77
|
+
"readlink /work/node_modules/alpha",
|
|
78
|
+
"readlink /work/node_modules/beta",
|
|
79
|
+
"echo === resolve through ===",
|
|
80
|
+
"cat /work/node_modules/alpha/index.js",
|
|
81
|
+
"cat /work/node_modules/beta/index.js",
|
|
82
|
+
].join(" && "),
|
|
83
|
+
],
|
|
84
|
+
timeoutMs: 10_000,
|
|
85
|
+
});
|
|
86
|
+
console.log(result.stdout.toString());
|
|
87
|
+
|
|
88
|
+
// 4. Host-side: confirm the symlinks landed on disk as real
|
|
89
|
+
// symlinks (not regular files), with the bytes the guest asked
|
|
90
|
+
// for. This is the "store-target-verbatim" guarantee in action.
|
|
91
|
+
console.log("=== host-side symlink targets ===");
|
|
92
|
+
console.log("alpha →", readlinkSync(join(project, "node_modules", "alpha")));
|
|
93
|
+
console.log("beta →", readlinkSync(join(project, "node_modules", "beta")));
|
|
94
|
+
|
|
95
|
+
await vm.release();
|
|
96
|
+
await pool.shutdown();
|
|
97
|
+
rmSync(project, { recursive: true, force: true });
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// 07-npm-workspace-fast.mjs — the integrator-recommended pattern
|
|
2
|
+
// for "edit on host, run in VM, install dependencies once."
|
|
3
|
+
//
|
|
4
|
+
// Three layers, each playing to its strength:
|
|
5
|
+
//
|
|
6
|
+
// 1. virtio-fs MOUNT for the source tree — host edits are live
|
|
7
|
+
// in the guest (DAX-fast reads, kqueue-driven invalidation),
|
|
8
|
+
// `symlinks: 'opaque'` so npm workspace symlinks work without
|
|
9
|
+
// letting the host follow guest-supplied targets.
|
|
10
|
+
//
|
|
11
|
+
// 2. virtio-blk VOLUME for `node_modules` — block I/O is
|
|
12
|
+
// ~native disk speed; bypasses the FUSE per-file round-trip
|
|
13
|
+
// cost that makes `npm install` 3-6 min on a virtio-fs mount.
|
|
14
|
+
// Same install on a volume: ~10-30 s.
|
|
15
|
+
//
|
|
16
|
+
// 3. WARMUP runs `npm install` once at bake time. The result
|
|
17
|
+
// (node_modules populated on the volume) persists across
|
|
18
|
+
// restores — every subsequent restore has dependencies ready,
|
|
19
|
+
// no install on the hot path.
|
|
20
|
+
//
|
|
21
|
+
// Run:
|
|
22
|
+
// node examples/07-npm-workspace-fast.mjs
|
|
23
|
+
|
|
24
|
+
import { Image } from "@supermachine/core";
|
|
25
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
|
|
29
|
+
// 1. Build a host-side project that mimics a tiny npm workspace.
|
|
30
|
+
const project = mkdtempSync(join(tmpdir(), "sm-fast-"));
|
|
31
|
+
mkdirSync(project, { recursive: true });
|
|
32
|
+
writeFileSync(
|
|
33
|
+
join(project, "package.json"),
|
|
34
|
+
JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
name: "fast-demo",
|
|
37
|
+
version: "0.0.0",
|
|
38
|
+
dependencies: { "lodash.chunk": "^4.2.0" },
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// 2. Volume backing file — sparse, 1 GiB cap. Sits alongside the
|
|
46
|
+
// project for the demo; in production you'd put it under
|
|
47
|
+
// ~/.local/supermachine/volumes/ or wherever your tenancy boundary
|
|
48
|
+
// lives.
|
|
49
|
+
const volumePath = join(project, "node_modules.img");
|
|
50
|
+
|
|
51
|
+
// 3. Bake with a warmup that runs `npm install` once on the volume.
|
|
52
|
+
// The snapshot captures the *post-install* state; every restore
|
|
53
|
+
// has node_modules ready instantly.
|
|
54
|
+
const t0 = Date.now();
|
|
55
|
+
const img = await Image.build({
|
|
56
|
+
ref: "node:20-alpine",
|
|
57
|
+
name: "npm-workspace-fast-demo",
|
|
58
|
+
memoryMib: 512,
|
|
59
|
+
cmd: ["/bin/sh", "-c", "sleep infinity"],
|
|
60
|
+
mounts: [
|
|
61
|
+
{ hostPath: project, guestTag: "src", symlinks: "opaque" },
|
|
62
|
+
],
|
|
63
|
+
volumes: [
|
|
64
|
+
{ hostPath: volumePath, guestPath: "/work/node_modules", sizeMib: 1024 },
|
|
65
|
+
],
|
|
66
|
+
warmup: async (vm) => {
|
|
67
|
+
// Mount source RO-ish + format and mount the node_modules volume.
|
|
68
|
+
await vm.exec({
|
|
69
|
+
argv: [
|
|
70
|
+
"sh",
|
|
71
|
+
"-c",
|
|
72
|
+
[
|
|
73
|
+
"mkdir -p /src /work/node_modules",
|
|
74
|
+
"mount -t virtiofs src /src",
|
|
75
|
+
// The volume comes up as the next /dev/vdN; on first bake
|
|
76
|
+
// it's blank, format ext4. Idempotent: existing fs is left
|
|
77
|
+
// alone by mke2fs -F? No — use `blkid` to skip if already
|
|
78
|
+
// formatted. For a demo, the bake-time guest always sees
|
|
79
|
+
// an empty volume.
|
|
80
|
+
"mkfs.ext4 -F -q /dev/vdb",
|
|
81
|
+
"mount /dev/vdb /work/node_modules",
|
|
82
|
+
// Copy package*.json out of the read-mount into the
|
|
83
|
+
// volume-backed dir so `npm install` writes locally.
|
|
84
|
+
"cp /src/package.json /work/package.json",
|
|
85
|
+
"cd /work && npm install --silent --no-fund --no-audit",
|
|
86
|
+
].join(" && "),
|
|
87
|
+
],
|
|
88
|
+
timeoutMs: 120_000,
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
warmupTag: "v1",
|
|
92
|
+
});
|
|
93
|
+
console.log(`[fast] bake total: ${Date.now() - t0} ms`);
|
|
94
|
+
|
|
95
|
+
// 4. Run a workload: every restore lands with node_modules already
|
|
96
|
+
// populated. Re-mount mounts and continue.
|
|
97
|
+
const pool = await img.pool({ min: 1, max: 1, restoreOnRelease: false });
|
|
98
|
+
const vm = await pool.acquire();
|
|
99
|
+
const r = await vm.exec({
|
|
100
|
+
argv: [
|
|
101
|
+
"sh",
|
|
102
|
+
"-c",
|
|
103
|
+
[
|
|
104
|
+
"mount -t virtiofs src /src 2>/dev/null || true",
|
|
105
|
+
"mount /dev/vdb /work/node_modules 2>/dev/null || true",
|
|
106
|
+
"cd /work && node -e \"console.log(require('lodash.chunk')([1,2,3,4,5], 2))\"",
|
|
107
|
+
].join("; "),
|
|
108
|
+
],
|
|
109
|
+
timeoutMs: 10_000,
|
|
110
|
+
});
|
|
111
|
+
console.log(r.stdout.toString());
|
|
112
|
+
|
|
113
|
+
await vm.release();
|
|
114
|
+
await pool.shutdown();
|
|
115
|
+
rmSync(project, { recursive: true, force: true });
|
package/examples/README.md
CHANGED
|
@@ -11,6 +11,8 @@ with the package installed.
|
|
|
11
11
|
| `03-snapshot-then-restore.mjs` | `vm.writeFile` / `readFile`, mid-flight `vm.snapshot()`, `Image.fromSnapshot` |
|
|
12
12
|
| `04-expose-tcp.mjs` | `listenerRequired: true` bakes, `vm.exposeTcp()`, `TcpForwarder` |
|
|
13
13
|
| `05-spawn-streaming.mjs` | `vm.spawn()` → `ExecChild` (writeStdin / readStdout / wait) |
|
|
14
|
+
| `06-mount-workspace.mjs` | `mounts` + `symlinks` policy: live host dir into guest, npm-workspace symlink pattern |
|
|
15
|
+
| `07-npm-workspace-fast.mjs` | The recommended npm/pnpm/cargo pattern: virtio-fs mount for source + virtio-blk volume for node_modules + warmup-bake for one-time install |
|
|
14
16
|
|
|
15
17
|
## Requirements
|
|
16
18
|
|
|
@@ -33,7 +35,7 @@ node --input-type=module -e \
|
|
|
33
35
|
"$(sed 's|@supermachine/core|./index.js|' examples/01-hello.mjs)"
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
For a quick smoke test that all
|
|
38
|
+
For a quick smoke test that all examples work end-to-end:
|
|
37
39
|
|
|
38
40
|
```sh
|
|
39
41
|
for f in examples/*.mjs; do
|
package/index.d.ts
CHANGED
|
@@ -39,6 +39,49 @@ export interface MountSpec {
|
|
|
39
39
|
hostPath: string
|
|
40
40
|
/** virtiofs tag the guest mounts by. Max 35 bytes. */
|
|
41
41
|
guestTag: string
|
|
42
|
+
/**
|
|
43
|
+
* Symlink policy: `"deny"` | `"opaque"` | `"follow"`. Default
|
|
44
|
+
* `"opaque"` — guest may create symlinks (npm/pnpm/yarn rely on
|
|
45
|
+
* them) but host-side symlinks pointing outside the mount root
|
|
46
|
+
* are rejected. Use `"deny"` for paranoid mounts (no symlinks or
|
|
47
|
+
* hard links at all) or `"follow"` for trusted single-tenant
|
|
48
|
+
* workloads (pre-0.5.5 `allowExternalSymlinks: true` behaviour).
|
|
49
|
+
*/
|
|
50
|
+
symlinks?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A writable virtio-blk volume backed by a host file. Use for
|
|
55
|
+
* dependency caches (`node_modules`, `target/`, `__pycache__`,
|
|
56
|
+
* `.next/`) and other write-heavy paths — block I/O is ~native
|
|
57
|
+
* disk speed, no FUSE round-trip per file. Contrast with
|
|
58
|
+
* [`MountSpec`] which exposes a host directory via virtio-fs
|
|
59
|
+
* (right for source trees you want live-editable from the host,
|
|
60
|
+
* wrong for thousands-of-small-files write workloads).
|
|
61
|
+
*
|
|
62
|
+
* The host file is created sparse at `hostPath` if missing
|
|
63
|
+
* (actual disk usage tracks what the guest writes). Snapshots
|
|
64
|
+
* capture the mapping, not the volume contents — same backing
|
|
65
|
+
* file is reused across all restores of this snapshot.
|
|
66
|
+
*/
|
|
67
|
+
export interface VolumeSpec {
|
|
68
|
+
/**
|
|
69
|
+
* Host file backing this volume. Created sparse at `sizeMib`
|
|
70
|
+
* MiB if missing. Persists across restores; delete it to
|
|
71
|
+
* reset state.
|
|
72
|
+
*/
|
|
73
|
+
hostPath: string
|
|
74
|
+
/**
|
|
75
|
+
* Mount point inside the guest, e.g. `"/var/lib/postgres"`
|
|
76
|
+
* or `"/work/node_modules"`. The guest needs to actually
|
|
77
|
+
* mount it (init can do this, or your warmup callback).
|
|
78
|
+
*/
|
|
79
|
+
guestPath: string
|
|
80
|
+
/**
|
|
81
|
+
* Size cap in MiB. Default 1024 (1 GiB). Sparse — actual
|
|
82
|
+
* disk usage matches what the guest writes.
|
|
83
|
+
*/
|
|
84
|
+
sizeMib?: number
|
|
42
85
|
}
|
|
43
86
|
|
|
44
87
|
/**
|
|
@@ -105,6 +148,25 @@ export interface BuildOptions {
|
|
|
105
148
|
* re-bakes.
|
|
106
149
|
*/
|
|
107
150
|
mounts?: Array<MountSpec>
|
|
151
|
+
/**
|
|
152
|
+
* Writable virtio-blk volumes. Each entry maps a sparse host
|
|
153
|
+
* file to a guest mount point. Use for dependency caches
|
|
154
|
+
* (`node_modules`, `target/`) — block I/O is ~native disk
|
|
155
|
+
* speed and bypasses the FUSE round-trip cost that makes
|
|
156
|
+
* `mounts` slow for thousands-of-small-files workloads.
|
|
157
|
+
* Volume bytes persist across restores; only the mapping is
|
|
158
|
+
* captured in the snapshot.
|
|
159
|
+
*/
|
|
160
|
+
volumes?: Array<VolumeSpec>
|
|
161
|
+
/**
|
|
162
|
+
* Extra kernel cmdline tokens, appended after supermachine's
|
|
163
|
+
* own defaults (earlycon, console, tsi_hijack, etc.). Power-
|
|
164
|
+
* user / testing escape hatch — e.g. `["panic=1"]` to force
|
|
165
|
+
* an immediate panic on warning, or `["init=/nope"]` to
|
|
166
|
+
* deliberately trigger an early-init kernel panic in a test.
|
|
167
|
+
* Folded into the bake's input hash so changing this re-bakes.
|
|
168
|
+
*/
|
|
169
|
+
extraKernelArgs?: Array<string>
|
|
108
170
|
/**
|
|
109
171
|
* Override where to fetch the image bytes. Default behavior
|
|
110
172
|
* (when omitted) treats `ref` as a registry reference and pulls
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supermachine/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Run any OCI/Docker image as a hardware-isolated microVM. Node/Bun/Deno binding for the supermachine Rust crate.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"arm64"
|
|
23
23
|
],
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"@supermachine/core-darwin-arm64": "0.
|
|
25
|
+
"@supermachine/core-darwin-arm64": "0.7.1"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build:rust": "TYPE_DEF_TMP_PATH=$(pwd)/scripts/.napi_type_defs.tmp cargo build --release -p supermachine-napi && cargo build --release --bin supermachine-worker -p supermachine",
|