@local-sandbox/lsb-nodejs
v0.3.12
Published
Native Node.js bindings for lsb microVM sandboxes
Maintainers
Readme
@local-sandbox/lsb-nodejs
Native Node.js bindings for lsb, built with
napi-rs.
This package is the canonical JavaScript and TypeScript entrypoint for lsb. It wraps the
Rust lsb-sdk directly and exposes a Node-facing Sandbox API.
Install
npm install @local-sandbox/lsb-nodejsThe published npm package is split into a root package plus a platform package. On supported hosts,
npm resolves and installs either @local-sandbox/lsb-nodejs-darwin-arm64 or
@local-sandbox/lsb-nodejs-darwin-x64 automatically.
For local development, use Corepack to run the Yarn version pinned in
package.json:
corepack yarn installRequirements
- Node.js 18+
- macOS 14+ on Apple Silicon or Intel x86_64
- Runtime assets initialized with
initSandbox()orlsb init.Sandbox.start()still expects the lsb runtime data directory to already containImage,rootfs.ext4, andinitramfs.cpio.gz; it does not download assets implicitly. - On macOS, the
nodeexecutable loading this SDK must be code signed with thecom.apple.security.virtualizationentitlement. For a project-local workflow, sign a copied Node binary with../../lsb.entitlements, or usetest:signed-nodeas a reference.
Usage
Start a sandbox and run commands
import { Sandbox, initSandbox } from '@local-sandbox/lsb-nodejs'
const dataDir = `${process.env.HOME}/.local/share/lsb`
await initSandbox({ dataDir })
const sandbox = await Sandbox.start({
dataDir,
cpus: 2,
memoryMb: 2048,
mounts: [{ type: 'overlay', hostPath: './src', guestPath: '/workspace' }],
network: { allow: ['registry.npmjs.org'] },
})
const result = await sandbox.exec('echo hello from lsb')
console.log(result.stdout)
await sandbox.writeFile('/tmp/demo.txt', 'hello')
const content = await sandbox.readFile('/tmp/demo.txt')
console.log(content.toString())
await sandbox.stop()Initialize runtime assets
import { initSandbox } from '@local-sandbox/lsb-nodejs'
const init = await initSandbox()
console.log(init.dataDir, init.version, init.downloaded)initSandbox() defaults to this package version and pins that base rootfs.
Sandbox.start() defaults to the initialized VERSION in the runtime data directory. You only need
to pass a version when preparing or booting from an older pinned base.
await initSandbox({ version: '0.3.8' })
const sandbox = await Sandbox.start({ baseVersion: '0.3.8' })Pass argv directly or run through a shell
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const sandbox = await Sandbox.start()
const argvResult = await sandbox.exec(['sh', '-lc', 'printf "%s" "$HOME"'])
console.log(argvResult.stdout)
const shellResult = await sandbox.execShell('uname -a')
console.log(shellResult.stdout)
await sandbox.stop()Inspect the guest filesystem
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const sandbox = await Sandbox.start()
await sandbox.writeFile('/tmp/demo.txt', 'hello from lsb')
const entries = await sandbox.readDir('/tmp')
const stat = await sandbox.stat('/tmp/demo.txt')
const exists = await sandbox.exists('/tmp/demo.txt')
console.log(entries.map((entry) => `${entry.type}: ${entry.name}`))
console.log({ size: stat.size, mode: stat.mode, exists })
await sandbox.stop()Save and resume from a checkpoint
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const base = await Sandbox.start()
await base.exec('mkdir -p /workspace && echo ready > /workspace/state.txt')
await base.checkpoint('my-env')
const resumed = await Sandbox.start({ from: 'my-env' })
const state = await resumed.readFile('/workspace/state.txt')
console.log(state.toString())
await resumed.stop()Configure mounts, ports, secrets, and network policy
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const sandbox = await Sandbox.start({
cpus: 4,
memoryMb: 4096,
diskSizeMb: 8192,
ports: [{ host: 8080, guest: 80 }],
mounts: [{ type: 'direct', hostPath: './src', guestPath: '/workspace', flags: 0 }],
network: {
allow: ['api.openai.com', 'registry.npmjs.org'],
exposeHost: [{ host: 3000, guest: 3000 }],
secrets: {
API_KEY: { value: 'sk-test', hosts: ['api.openai.com'] },
},
},
})
console.log(sandbox.instanceDir)
await sandbox.stop()Start options
| Option | Type | Description |
| ------------ | ----------------------------------- | ------------------------------ |
| instanceId | string | Stable instance directory name |
| from | string | Checkpoint name to start from |
| cpus | number | Number of vCPUs |
| memoryMb | number | Memory in MB |
| diskSizeMb | number | Disk size in MB |
| dataDir | string | lsb runtime data directory |
| ports | { host: number; guest: number }[] | Host-to-guest port forwards |
| mounts | MountConfig[] | Directory mounts |
| network | NetworkConfig | Network access policy |
mounts accepts discriminated entries:
| Type | Shape | Behavior |
| --------- | ------------------------------------------------------------------------ | --------------------------------------------- |
| overlay | { type: 'overlay'; hostPath: string; guestPath: string } | Host is read-only; guest writes go to overlay |
| direct | { type: 'direct'; hostPath: string; guestPath: string; flags: number } | Mounts VirtioFS directly with libc flags |
For direct mounts, flags: 0 is read-write and flags: 1 is MS_RDONLY.
network enables proxy networking when present. It accepts:
| Option | Type | Description |
| ------------ | ------------------------------------ | ---------------------------------- |
| allow | string[] | Allowed outbound host patterns |
| exposeHost | { host: number; guest?: number }[] | Host ports exposed to the guest |
| secrets | Record<string, SecretConfig> | Secrets injected via the lsb proxy |
Stream process output
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const sandbox = await Sandbox.start()
const proc = await sandbox.spawn('echo out; echo err >&2')
for await (const chunk of proc.stdout) {
process.stdout.write(chunk)
}
console.log(await proc.exited)
await sandbox.stop()Watch files
import { Sandbox } from '@local-sandbox/lsb-nodejs'
const sandbox = await Sandbox.start()
const events = await sandbox.watch('/tmp')
for await (const event of events) {
console.log(event.path, event.event)
}Scripts
corepack yarn build
corepack yarn test
corepack yarn test:signed-nodecorepack yarn test always builds the native binding first, then runs AVA against the
generated root entrypoint. The positive VM smoke test only runs when both of these are true:
- lsb runtime assets already exist in
~/.local/share/lsbor inLSB_NODEJS_TEST_DATA_DIR(Imageis expected there and usually needs to be provisioned manually) - the current
nodeexecutable has thecom.apple.security.virtualizationentitlement
To avoid modifying your global Node installation, use test:signed-node,
which copies the current node binary into .signed-node/node, signs that local copy with
../../lsb.entitlements, prepends it to PATH, and then runs
the local napi build --platform plus ava commands through that signed Node:
corepack yarn test:signed-nodeIf runtime assets are missing, provision them in the lsb data directory first. The generated build outputs
(index.js, index.d.ts, lsb-nodejs.*.node) are local artifacts and are ignored by git.
This explicit smoke-test entrypoint will attempt a real VM boot; if your host still refuses
virtualization after signing the local Node copy, the command will surface that underlying error.
Platform Notes
- Supported targets: macOS on Apple Silicon (
aarch64-apple-darwin) and Intel (x86_64-apple-darwin). - Installation is limited to supported macOS architectures. Unsupported platforms should fail during
npm installinstead of installing a package that only errors at runtime. - The published native binaries live in the platform packages
@local-sandbox/lsb-nodejs-darwin-arm64and@local-sandbox/lsb-nodejs-darwin-x64.
