2026-02-08 21:47:33 +00:00
# @push.rocks/smartvm
2026-05-01 18:42:05 +00:00
Boot and control Firecracker microVMs from TypeScript without rebuilding the same host plumbing every time. `@push.rocks/smartvm` handles Firecracker binaries, Unix-socket API calls, per-VM runtime directories, base-image bundles, TAP/bridge networking, optional egress firewalling, host-side WireGuard routing, and cleanup so you can focus on the VM workload.
2026-02-08 21:47:33 +00:00
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/ ](https://community.foss.global/ ). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/ ](https://code.foss.global/ ) account to submit Pull Requests directly.
2026-05-01 18:42:05 +00:00
## Why It Rocks
`smartvm` is for programmers who want real microVM isolation with a TypeScript control plane and sane defaults:
- 🚀 Boot Firecracker VMs with a few typed calls.
- 🧊 Keep runtime state ephemeral by default with tmpfs-backed sockets and staged writable drives.
- 🧰 Use known-good Firecracker CI images for quick starts, or ship your own hosted image manifest with SHA256 verification.
- 🌐 Create Linux TAP devices, bridges, deterministic guest MACs, static guest IP data, and NAT without custom scripts.
- 🔒 Apply VM-subnet egress policy with ordered IPv4 firewall rules.
- 🕳️ Route all VM traffic through a host WireGuard interface without installing WireGuard in the guest.
- 🧹 Tear down processes, sockets, TAP devices, bridge/NAT/firewall/WireGuard state, and staged drive copies.
2026-05-01 17:50:21 +00:00
## What It Does
`smartvm` wraps the operational parts of Amazon Firecracker:
- Downloads and caches Firecracker binaries.
- Resolves bootable Firecracker CI base-image bundles with `latest` and `lts` presets.
- Supports project-owned hosted base-image manifests with SHA256 verification.
- Creates and controls microVMs through Firecracker's HTTP-over-Unix-socket API.
- Converts TypeScript camelCase config into Firecracker's snake_case payloads.
2026-05-01 18:42:05 +00:00
- Creates TAP devices, a Linux bridge, static guest network assignments, and NAT rules.
2026-05-01 18:32:08 +00:00
- Applies optional global VM egress firewall rules for the managed subnet.
- Routes VM egress through host-side WireGuard when configured.
2026-05-01 17:50:21 +00:00
- Defaults VM runtime artifacts to tmpfs via `/dev/shm/.smartvm/runtime` when available.
- Stages writable drives into per-VM ephemeral storage by default so guest writes do not touch cached rootfs files.
2026-05-01 18:32:08 +00:00
- Cleans up Firecracker processes, sockets, TAPs, bridges, NAT/firewall/WireGuard rules, and staged drive copies.
2026-05-01 17:50:21 +00:00
2026-05-01 18:42:05 +00:00
The default mental model is: immutable base image, explicit writable scratch, no accidental persistent state, and persistence only when you opt in.
2026-05-01 17:50:21 +00:00
2026-02-08 21:47:33 +00:00
## Install
``` bash
2026-05-01 17:50:21 +00:00
pnpm add @push.rocks/smartvm
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
## Runtime Requirements
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
Firecracker is a Linux/KVM technology. The package is TypeScript, but the runtime host must provide the VM substrate.
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
| Requirement | Why it matters |
|---|---|
| Linux with `/dev/kvm` | Firecracker needs KVM acceleration. |
| Firecracker binary | Downloaded by `ensureBinary()` or supplied through `firecrackerBinaryPath` . |
2026-05-01 18:42:05 +00:00
| Root privileges for networking | TAP devices, bridges, IP forwarding, iptables NAT, firewalling, and WireGuard policy routing require elevated privileges. |
| Host tools: `curl` , `tar` , `ip` , `sysctl` , `iptables` , `wg` when WireGuard is used | Used for binary/image downloads and network setup. `dd` and `mkfs.ext4` are only needed for `ImageManager.createBlankRootfs()` . |
2026-05-01 17:50:21 +00:00
| Enough tmpfs memory | Writable VM drives are copied into `/dev/shm` by default when available. |
2026-05-01 13:30:51 +00:00
2026-05-01 18:42:05 +00:00
If you only use `VMConfig` , `SocketClient` , or custom low-level flows without creating host networking, those pieces do not need root. Actual Firecracker boot still needs a Linux/KVM-capable host.
2026-02-08 21:47:33 +00:00
## Quick Start
2026-05-01 18:42:05 +00:00
This is the happy path: let `smartvm` download Firecracker, resolve a known-good base image, boot it, and clean everything up. Run it on a Linux host with KVM access.
2026-05-01 17:50:21 +00:00
2026-02-08 21:47:33 +00:00
``` typescript
import { SmartVM } from '@push.rocks/smartvm' ;
const smartvm = new SmartVM ( {
2026-05-01 17:50:21 +00:00
// Optional. Defaults are intentionally disk-light.
dataDir : '/tmp/.smartvm' ,
runtimeDir : '/dev/shm/.smartvm/runtime' ,
2026-02-08 21:47:33 +00:00
} ) ;
2026-05-01 17:50:21 +00:00
const baseImage = await smartvm . ensureBaseImage ( { preset : 'latest' } ) ;
2026-02-08 21:47:33 +00:00
const vm = await smartvm . createVM ( {
2026-05-01 17:50:21 +00:00
id : 'hello-firecracker' ,
2026-02-08 21:47:33 +00:00
bootSource : {
2026-05-01 17:50:21 +00:00
kernelImagePath : baseImage.kernelImagePath ,
bootArgs : baseImage.bootArgs ,
2026-02-08 21:47:33 +00:00
} ,
machineConfig : {
2026-05-01 17:50:21 +00:00
vcpuCount : 1 ,
2026-02-08 21:47:33 +00:00
memSizeMib : 256 ,
} ,
drives : [
{
driveId : 'rootfs' ,
2026-05-01 17:50:21 +00:00
pathOnHost : baseImage.rootfsPath ,
2026-02-08 21:47:33 +00:00
isRootDevice : true ,
2026-05-01 17:50:21 +00:00
isReadOnly : baseImage.rootfsIsReadOnly ,
2026-02-08 21:47:33 +00:00
} ,
] ,
} ) ;
2026-05-01 17:50:21 +00:00
try {
await vm . start ( ) ;
console . log ( vm . state ) ; // "running"
console . log ( await vm . getVersion ( ) ) ;
console . log ( await vm . getInfo ( ) ) ;
} finally {
if ( vm . state === 'running' || vm . state === 'paused' ) {
await vm . stop ( ) ;
}
await vm . cleanup ( ) ;
await smartvm . cleanup ( ) ;
}
```
2026-02-08 21:47:33 +00:00
2026-05-01 18:42:05 +00:00
What happened:
- Firecracker was downloaded or reused from `/tmp/.smartvm/bin` .
- A base image bundle was resolved and cached under `/tmp/.smartvm/base-images` .
- A per-VM socket directory was created under `/dev/shm/.smartvm/runtime/<vmId>` when `/dev/shm` exists.
- Writable drives would be staged into that runtime directory before boot; read-only drives stay in place.
- `cleanup()` removed VM runtime files and networking resources owned by this `SmartVM` instance.
2026-05-01 17:50:21 +00:00
## Disk-Light Runtime Model
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
By default, `smartvm` treats VMs as ephemeral execution units.
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
| Path | Default | Persistence model |
|---|---|---|
| Firecracker binaries | `/tmp/.smartvm/bin` | Cached for reuse. |
| Base images | `/tmp/.smartvm/base-images` | Cached, retention-limited, verified before reuse. |
| VM sockets | `/dev/shm/.smartvm/runtime/<vmId>/firecracker.sock` | Per-VM tmpfs, deleted on cleanup. |
| Writable drives | `/dev/shm/.smartvm/runtime/<vmId>/drives/*` | Per-VM tmpfs copy, deleted on cleanup. |
| Read-only drives | Original path | Not copied unless `ephemeral: true` . |
Writable drives are staged into the VM runtime directory before boot. Firecracker receives the staged path, so guest writes do not modify cached base images or source rootfs files.
``` typescript
const vm = await smartvm . createVM ( {
bootSource : { kernelImagePath : baseImage.kernelImagePath , bootArgs : baseImage.bootArgs } ,
machineConfig : { vcpuCount : 1 , memSizeMib : 256 } ,
drives : [
{
driveId : 'rootfs' ,
pathOnHost : baseImage.rootfsPath ,
isRootDevice : true ,
isReadOnly : false ,
// Default for writable drives: true
ephemeral : true ,
} ,
] ,
} ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
Opt into persistence only when that is the point:
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
``` typescript
const persistentVm = await smartvm . createVM ( {
bootSource : { kernelImagePath : '/images/vmlinux' , bootArgs : 'console=ttyS0 reboot=k panic=1 pci=off' } ,
machineConfig : { vcpuCount : 2 , memSizeMib : 512 } ,
drives : [
{
driveId : 'state' ,
pathOnHost : '/var/lib/my-vm/state.ext4' ,
isRootDevice : true ,
isReadOnly : false ,
ephemeral : false ,
} ,
] ,
} ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
You can also disable writable-drive staging globally:
``` typescript
const smartvm = new SmartVM ( {
ephemeralWritableDrives : false ,
} ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
Best practice for high-volume VM starts:
- Prefer `squashfs` or another read-only root filesystem.
- Put mutable scratch data on tmpfs-backed writable drives.
- Keep shared assets read-only by default.
- Use external services, object storage, databases, or explicit persistent drives for durable state.
- Use a dedicated `runtimeDir` on a real tmpfs if `/dev/shm` is too small or unavailable.
## Architecture
``` text
SmartVM
ImageManager downloads/caches Firecracker binaries and manual images
BaseImageManager resolves known-good base-image bundles
2026-05-01 18:32:08 +00:00
NetworkManager creates TAP devices, bridge, NAT, firewall/WireGuard egress, and static guest network data
2026-05-01 17:50:21 +00:00
MicroVM
FirecrackerProcess starts/stops the VMM process
SocketClient talks HTTP over the Firecracker Unix socket
VMConfig validates and transforms TypeScript config
```
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
Firecracker exposes a REST API over a Unix domain socket. `smartvm` starts the child process, waits for readiness, sends pre-boot config in the right order, starts the instance, and tears down host resources when you are done.
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
## SmartVM
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
`SmartVM` is the top-level orchestrator.
2026-02-08 21:47:33 +00:00
``` typescript
import { SmartVM } from '@push.rocks/smartvm' ;
import type { ISmartVMOptions } from '@push.rocks/smartvm' ;
2026-05-01 17:50:21 +00:00
const options : ISmartVMOptions = {
dataDir : '/tmp/.smartvm' ,
runtimeDir : '/dev/shm/.smartvm/runtime' ,
ephemeralWritableDrives : true ,
firecrackerVersion : 'v1.7.0' ,
arch : 'x86_64' ,
firecrackerBinaryPath : '/usr/bin/firecracker' ,
bridgeName : 'svbr0' ,
subnet : '172.30.0.0/24' ,
2026-05-01 18:32:08 +00:00
firewall : {
egress : {
defaultAction : 'allow' ,
rules : [ ] ,
} ,
} ,
wireguard : {
existingInterface : 'wg0' ,
failClosed : true ,
} ,
2026-05-01 17:50:21 +00:00
baseImageCacheDir : '/tmp/.smartvm/base-images' ,
maxStoredBaseImages : 2 ,
baseImageManifestUrl : 'https://assets.example.com/smartvm/manifest.json' ,
baseImageManifestPath : './assets/base-images/local.manifest.json' ,
} ;
const smartvm = new SmartVM ( options ) ;
```
| API | Description |
|---|---|
| `ensureBinary()` | Ensures the Firecracker binary exists and returns its path. |
| `ensureBaseImage(options)` | Resolves/downloads a base-image bundle and returns kernel/rootfs paths plus boot args. |
| `createVM(config)` | Creates a `MicroVM` instance. It does not boot until `vm.start()` . |
| `getRuntimeDir()` | Returns the active runtime directory used for per-VM tmpfs artifacts. |
| `getVM(id)` | Looks up an active VM by ID. |
| `listVMs()` | Lists active VM IDs. |
| `vmCount` | Number of tracked VMs. |
| `removeVM(id)` | Removes a VM from the internal tracking map. |
| `stopAll()` | Stops every running or paused VM. |
| `cleanup()` | Cleans up all tracked VMs and networking resources. |
## Base Images
2026-05-01 18:42:05 +00:00
`BaseImageManager` gives you fast bootable image discovery without committing giant rootfs files to git. The `latest` and `lts` presets use Firecracker CI artifacts, which are excellent for tests, demos, and bring-up. For product workloads, prefer the `hosted` manifest path so you control the kernel/rootfs pair and verify artifacts with SHA256.
2026-05-01 17:50:21 +00:00
``` typescript
const baseImage = await smartvm . ensureBaseImage ( ) ; // preset: "latest"
const ltsBaseImage = await smartvm . ensureBaseImage ( { preset : 'lts' } ) ;
const freshBaseImage = await smartvm . ensureBaseImage ( { preset : 'latest' , forceDownload : true } ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
Presets:
| Preset | Behavior |
2026-02-08 21:47:33 +00:00
|---|---|
2026-05-01 17:50:21 +00:00
| `latest` | Resolves the latest Firecracker release and matching CI demo artifacts. |
| `lts` | Uses the pinned Firecracker CI train `v1.7` / Firecracker `v1.7.0` . |
| `hosted` | Uses a project-owned manifest. Requires `manifestUrl` , `manifestPath` , or manager-level hosted manifest options. |
The resolver prefers read-only `squashfs` rootfs artifacts when Firecracker CI exposes them, falling back to `ext4` when needed.
``` typescript
import { BaseImageManager } from '@push.rocks/smartvm' ;
const baseImageManager = new BaseImageManager ( {
arch : 'x86_64' ,
cacheDir : '/tmp/.smartvm/base-images' ,
maxStoredBaseImages : 4 ,
hostedManifestPath : './assets/base-images/smartvm-minimal.manifest.json' ,
} ) ;
console . log ( baseImageManager . getCacheDir ( ) ) ;
console . log ( baseImageManager . getMaxStoredBaseImages ( ) ) ;
const hosted = await baseImageManager . ensureBaseImage ( { preset : 'hosted' } ) ;
const evictedBundleIds = await baseImageManager . pruneBaseImageCache ( hosted . bundleId ) ;
```
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
`IBaseImageBundle` contains:
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
- `kernelImagePath`
- `rootfsPath`
- `rootfsType`
- `rootfsIsReadOnly`
- `bootArgs`
- `firecrackerVersion`
- `checksums`
- `sizes`
- source metadata
Cache behavior:
- Default cache directory: `/tmp/.smartvm/base-images`
- Default retention: `2` bundles
- Older bundles are evicted with a `console.warn` when retention is exceeded
- Cached artifacts are checked for size and SHA256 before reuse
- Hosted URL artifacts require SHA256 hashes
- Hosted local-path artifacts may omit SHA256, but hashes are still recorded in the cached manifest
2026-05-01 18:42:05 +00:00
Hosted manifests are the clean way to ship a project-owned minimal image. Keep big binaries in object storage or release assets, keep only the manifest in git, and let `smartvm` verify the exact bytes before boot.
2026-05-01 17:50:21 +00:00
Hosted manifest example:
The repository ships an example at `assets/base-images/smartvm-minimal.manifest.example.json` .
``` json
{
"schemaVersion" : 1 ,
"bundleId" : "smartvm-minimal-v1-x86_64" ,
"name" : "SmartVM minimal x86_64 bundle" ,
"arch" : "x86_64" ,
"firecrackerVersion" : "v1.15.1" ,
"rootfsType" : "squashfs" ,
"rootfsIsReadOnly" : true ,
"bootArgs" : "console=ttyS0 reboot=k panic=1 pci=off ro rootfstype=squashfs" ,
"kernel" : {
"url" : "https://assets.example.com/smartvm/vmlinux" ,
"fileName" : "vmlinux" ,
"sha256" : "0000000000000000000000000000000000000000000000000000000000000000" ,
"sizeBytes" : 12345678
} ,
"rootfs" : {
"url" : "https://assets.example.com/smartvm/rootfs.squashfs" ,
"fileName" : "rootfs.squashfs" ,
"sha256" : "0000000000000000000000000000000000000000000000000000000000000000" ,
"sizeBytes" : 12345678
}
}
```
## MicroVM Lifecycle
`MicroVM` is a single Firecracker instance with a strict state machine:
``` text
created -> configuring -> running -> paused -> stopped
\-> error
```
2026-02-08 21:47:33 +00:00
``` typescript
const vm = await smartvm . createVM ( {
2026-05-01 17:50:21 +00:00
id : 'api-worker-1' ,
2026-02-08 21:47:33 +00:00
bootSource : {
2026-05-01 17:50:21 +00:00
kernelImagePath : baseImage.kernelImagePath ,
bootArgs : baseImage.bootArgs ,
2026-02-08 21:47:33 +00:00
} ,
machineConfig : {
2026-05-01 17:50:21 +00:00
vcpuCount : 2 ,
2026-02-08 21:47:33 +00:00
memSizeMib : 512 ,
smt : false ,
2026-05-01 17:50:21 +00:00
cpuTemplate : 'T2' ,
2026-02-08 21:47:33 +00:00
trackDirtyPages : true ,
} ,
drives : [
{
driveId : 'rootfs' ,
2026-05-01 17:50:21 +00:00
pathOnHost : baseImage.rootfsPath ,
2026-02-08 21:47:33 +00:00
isRootDevice : true ,
2026-05-01 17:50:21 +00:00
isReadOnly : baseImage.rootfsIsReadOnly ,
cacheType : 'Unsafe' ,
ephemeral : true ,
2026-02-08 21:47:33 +00:00
rateLimiter : {
bandwidth : { size : 100_000_000 , refillTime : 1_000_000_000 } ,
ops : { size : 1000 , refillTime : 1_000_000_000 } ,
} ,
} ,
] ,
2026-05-01 17:50:21 +00:00
networkInterfaces : [ { ifaceId : 'eth0' } ] ,
2026-02-08 21:47:33 +00:00
vsock : {
guestCid : 3 ,
2026-05-01 17:50:21 +00:00
udsPath : '/dev/shm/api-worker-1.vsock' ,
2026-02-08 21:47:33 +00:00
} ,
balloon : {
amountMib : 128 ,
deflateOnOom : true ,
statsPollingIntervalS : 5 ,
} ,
mmds : {
version : 'V2' ,
networkInterfaces : [ 'eth0' ] ,
} ,
} ) ;
2026-05-01 17:50:21 +00:00
await vm . start ( ) ;
await vm . pause ( ) ;
await vm . resume ( ) ;
await vm . stop ( ) ;
await vm . cleanup ( ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
| API | Valid state | Description |
2026-02-08 21:47:33 +00:00
|---|---|---|
2026-05-01 17:50:21 +00:00
| `start()` | `created` | Stages ephemeral drives, starts Firecracker, applies config, boots the VM. |
| `pause()` | `running` | Pauses execution. |
| `resume()` | `paused` | Resumes execution. |
| `stop()` | `running` , `paused` | Sends Ctrl+Alt+Del, waits briefly, then stops the process. |
| `cleanup()` | any | Stops process, deletes sockets/runtime dir, removes auto-created TAPs. |
| `getInfo()` | after start | Returns Firecracker instance info. |
| `getVersion()` | after start | Returns Firecracker version info. |
| `setMetadata(data)` | `running` , `paused` | Writes MMDS metadata. |
| `getMetadata()` | `running` , `paused` | Reads MMDS metadata. |
| `updateDrive(id, path)` | `running` , `paused` | Hot-updates a drive path. |
| `updateNetworkInterface(id, update)` | `running` , `paused` | Updates network interface config such as rate limiters. |
| `updateBalloon(mib)` | `running` , `paused` | Resizes the balloon device. |
| `createSnapshot(params)` | `paused` | Creates a Firecracker snapshot. |
| `loadSnapshot(params)` | `created` , `configuring` | Low-level Firecracker snapshot-load call; requires an initialized socket client. |
| `getTapDevices()` | any | Returns TAP devices created automatically by this VM. |
| `getVMConfig()` | any | Returns the internal `VMConfig` instance. |
| `getRuntimeDir()` | any | Returns the per-VM runtime directory after it has been created. |
Snapshot caveat: diff snapshots require dirty-page tracking to be enabled before boot through `machineConfig.trackDirtyPages` . Snapshot restore is currently exposed as a low-level API; full restore orchestration should be built around the Firecracker process/config lifecycle intentionally.
## Networking
`NetworkManager` creates host-side networking primitives. It does not run DHCP inside the guest. Your guest image must configure its interface itself, or you must pass static `ip=` kernel boot arguments.
Automatic mode:
2026-02-08 21:47:33 +00:00
``` typescript
2026-05-01 17:50:21 +00:00
const vm = await smartvm . createVM ( {
bootSource : { kernelImagePath : baseImage.kernelImagePath , bootArgs : baseImage.bootArgs } ,
machineConfig : { vcpuCount : 1 , memSizeMib : 256 } ,
drives : [ { driveId : 'rootfs' , pathOnHost : baseImage.rootfsPath , isRootDevice : true , isReadOnly : baseImage.rootfsIsReadOnly } ] ,
networkInterfaces : [ { ifaceId : 'eth0' } ] ,
} ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
Static-kernel-args mode:
2026-05-01 13:30:51 +00:00
``` typescript
2026-05-01 17:50:21 +00:00
const tap = await smartvm . networkManager . createTapDevice ( 'net-vm' , 'eth0' ) ;
2026-05-01 13:30:51 +00:00
const vm = await smartvm . createVM ( {
2026-05-01 17:50:21 +00:00
id : 'net-vm' ,
2026-05-01 13:30:51 +00:00
bootSource : {
kernelImagePath : baseImage.kernelImagePath ,
2026-05-01 17:50:21 +00:00
bootArgs : ` ${ baseImage . bootArgs } ${ smartvm . networkManager . getGuestNetworkBootArgs ( tap ) } ` ,
2026-05-01 13:30:51 +00:00
} ,
machineConfig : { vcpuCount : 1 , memSizeMib : 256 } ,
2026-05-01 17:50:21 +00:00
drives : [ { driveId : 'rootfs' , pathOnHost : baseImage.rootfsPath , isRootDevice : true , isReadOnly : baseImage.rootfsIsReadOnly } ] ,
networkInterfaces : [
2026-05-01 13:30:51 +00:00
{
2026-05-01 17:50:21 +00:00
ifaceId : 'eth0' ,
hostDevName : tap.tapName ,
guestMac : tap.mac ,
2026-05-01 13:30:51 +00:00
} ,
] ,
} ) ;
```
2026-05-01 17:50:21 +00:00
Networking behavior:
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
- Default bridge: `svbr0`
- Default subnet: `172.30.0.0/24`
- Subnet input is normalized to the network address
- Prefix length must be `1-30`
- Gateway uses the first usable address
- Guest IP allocation starts at the second usable address
- Allocation is sequential and not reused within the same `NetworkManager` instance
- MAC addresses are deterministic and locally administered (`02:xx:xx:xx:xx:xx` )
- TAP names are capped to Linux's 15-character IFNAMSIZ limit
2026-05-01 18:32:08 +00:00
- NAT masquerade uses the host default route interface unless WireGuard egress is configured
2026-05-01 17:50:21 +00:00
- Use a dedicated bridge name; `cleanup()` tears down the bridge configured by this manager
2026-05-01 18:32:08 +00:00
### Egress Firewall
Configure `firewall.egress` on `SmartVM` to apply one ordered policy to all VMs using that manager's subnet. The default action is `allow` , so existing behavior is preserved unless you opt into a stricter policy.
``` typescript
const smartvm = new SmartVM ( {
firewall : {
egress : {
defaultAction : 'deny' ,
rules : [
{ action : 'allow' , to : '1.1.1.1' , protocol : 'udp' , ports : 53 , comment : 'DNS' } ,
{ action : 'allow' , to : '203.0.113.0/24' , protocol : 'tcp' , ports : [ 443 ] } ,
] ,
} ,
} ,
} ) ;
```
Firewall behavior:
- Rules are evaluated in order before the final `defaultAction` .
- `to` accepts IPv4 addresses or CIDR ranges only.
- `protocol` can be `all` , `tcp` , `udp` , or `icmp` .
- `ports` are destination ports and require `protocol: 'tcp'` or `protocol: 'udp'` .
- The implementation uses an owned `iptables` chain jumped from `FORWARD` for traffic from the VM subnet.
### WireGuard Egress
WireGuard routing is host-side. The guest does not need WireGuard installed; `smartvm` policy-routes packets from the VM subnet through a WireGuard interface and leaves normal host traffic on the host default route.
Managed interface mode creates and removes the WireGuard interface:
``` typescript
const smartvm = new SmartVM ( {
wireguard : {
interfaceName : 'svwg0' ,
routeTable : 51820 ,
failClosed : true ,
config : `
[Interface]
PrivateKey = <private-key>
Address = 10.70.0.2/32
MTU = 1420
[Peer]
PublicKey = <public-key>
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25
` ,
} ,
} ) ;
```
Existing interface mode uses an interface you manage outside `smartvm` :
``` typescript
const smartvm = new SmartVM ( {
wireguard : {
existingInterface : 'wg0' ,
routeTable : 51820 ,
failClosed : true ,
} ,
} ) ;
```
WireGuard behavior:
- `routeAllVmTraffic` defaults to `true` ; set it to `false` to keep normal default-route NAT.
- `failClosed` defaults to `true` ; VM forwarding to non-WireGuard egress interfaces is dropped when WireGuard routing is active.
- Managed configs accept wg-quick-style `Address` and `MTU` , but reject `PreUp` , `PostUp` , `PreDown` , `PostDown` , and `SaveConfig` .
- `DNS` and `Table` in managed configs are ignored; use host DNS and the `routeTable` option instead.
- IPv4 addresses and IPv4 `AllowedIPs` are supported in this release.
- `cleanup()` removes owned policy routes, iptables rules, NAT rules, and managed WireGuard interfaces. Existing WireGuard interfaces are not deleted.
2026-05-01 17:50:21 +00:00
## ImageManager
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
`ImageManager` is the lower-level helper for Firecracker binaries and manually managed kernel/rootfs files.
2026-05-01 13:30:51 +00:00
``` typescript
2026-05-01 17:50:21 +00:00
const imageManager = smartvm . imageManager ;
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
await imageManager . ensureDirectories ( ) ;
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
const latest = await imageManager . getLatestVersion ( ) ;
const firecrackerPath = await imageManager . downloadFirecracker ( latest ) ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
const kernelPath = await imageManager . downloadKernel (
'https://example.com/vmlinux' ,
'vmlinux' ,
) ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
const rootfsPath = await imageManager . downloadRootfs (
'https://example.com/rootfs.ext4' ,
'rootfs.ext4' ,
) ;
const blankRootfs = await imageManager . createBlankRootfs ( 'scratch.ext4' , 1024 ) ;
const clonedRootfs = await imageManager . cloneRootfs ( rootfsPath , 'vm-rootfs.ext4' ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
Useful path helpers:
- `getBinDir()`
- `getKernelsDir()`
- `getRootfsDir()`
- `getSocketsDir()`
- `getFirecrackerPath(version)`
- `getJailerPath(version)`
- `getSocketPath(vmId)`
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
Note: `SmartVM.createVM()` uses `runtimeDir/<vmId>/firecracker.sock` for new VM sockets by default. `ImageManager.getSocketPath()` remains available for lower-level/custom flows.
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
## VMConfig
`VMConfig` validates `IMicroVMConfig` and transforms it into Firecracker API payloads.
2026-02-08 21:47:33 +00:00
``` typescript
import { VMConfig } from '@push.rocks/smartvm' ;
const vmConfig = new VMConfig ( {
2026-05-01 17:50:21 +00:00
bootSource : { kernelImagePath : '/images/vmlinux' , bootArgs : 'console=ttyS0 reboot=k panic=1 pci=off' } ,
2026-02-08 21:47:33 +00:00
machineConfig : { vcpuCount : 2 , memSizeMib : 256 } ,
2026-05-01 17:50:21 +00:00
drives : [ { driveId : 'rootfs' , pathOnHost : '/images/rootfs.ext4' , isRootDevice : true } ] ,
2026-02-08 21:47:33 +00:00
} ) ;
2026-05-01 17:50:21 +00:00
const validation = vmConfig . validate ( ) ;
if ( ! validation . valid ) {
throw new Error ( validation . errors . join ( '; ' ) ) ;
}
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
console . log ( vmConfig . toBootSourcePayload ( ) ) ;
console . log ( vmConfig . toMachineConfigPayload ( ) ) ;
console . log ( vmConfig . toDrivePayload ( vmConfig . config . drives ! [ 0 ] ) ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
The constructor clones caller-provided config, so internal normalization does not mutate your original object.
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
## SocketClient
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
`SocketClient` is the raw Firecracker API client. Most users should go through `MicroVM` , but the low-level client is exported for tooling and diagnostics.
2026-02-08 21:47:33 +00:00
``` typescript
2026-05-01 17:50:21 +00:00
import { SocketClient } from '@push.rocks/smartvm' ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
const client = new SocketClient ( { socketPath : '/dev/shm/.smartvm/runtime/vm/firecracker.sock' } ) ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
const version = await client . get ( '/version' ) ;
const machineConfig = await client . put ( '/machine-config' , {
vcpu_count : 1 ,
mem_size_mib : 256 ,
2026-02-08 21:47:33 +00:00
} ) ;
2026-05-01 17:50:21 +00:00
const paused = await client . patch ( '/vm' , { state : 'Paused' } ) ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
console . log ( version . ok , version . statusCode , version . body ) ;
console . log ( machineConfig . ok , machineConfig . statusCode , machineConfig . body ) ;
console . log ( paused . ok , paused . statusCode , paused . body ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
`SocketClient` returns `{ ok, statusCode, body }` . Non-2xx responses do not become `API_ERROR` until higher-level `MicroVM` helpers validate them.
## Metadata Service
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
Firecracker MMDS lets the host pass structured metadata to a running VM.
2026-02-08 21:47:33 +00:00
``` typescript
const vm = await smartvm . createVM ( {
2026-05-01 17:50:21 +00:00
bootSource : { kernelImagePath : baseImage.kernelImagePath , bootArgs : baseImage.bootArgs } ,
machineConfig : { vcpuCount : 1 , memSizeMib : 256 } ,
drives : [ { driveId : 'rootfs' , pathOnHost : baseImage.rootfsPath , isRootDevice : true , isReadOnly : baseImage.rootfsIsReadOnly } ] ,
2026-02-08 21:47:33 +00:00
networkInterfaces : [ { ifaceId : 'eth0' } ] ,
mmds : {
version : 'V2' ,
networkInterfaces : [ 'eth0' ] ,
} ,
} ) ;
await vm . start ( ) ;
await vm . setMetadata ( {
2026-05-01 17:50:21 +00:00
instance : { id : 'api-worker-1' , region : 'local' } ,
config : { mode : 'ephemeral' } ,
2026-02-08 21:47:33 +00:00
} ) ;
2026-05-01 17:50:21 +00:00
console . log ( await vm . getMetadata ( ) ) ;
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
## Error Handling
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
All package-level failures use `SmartVMError` with structured codes.
2026-02-08 21:47:33 +00:00
``` typescript
2026-05-01 17:50:21 +00:00
import { SmartVMError } from '@push.rocks/smartvm' ;
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
try {
await vm . start ( ) ;
} catch ( err ) {
if ( err instanceof SmartVMError ) {
console . error ( err . code ) ;
console . error ( err . statusCode ) ;
console . error ( err . details ) ;
}
throw err ;
}
2026-02-08 21:47:33 +00:00
```
2026-05-01 17:50:21 +00:00
| Code | Meaning |
|---|---|
| `INVALID_STATE` | Operation is invalid for the current VM state. |
| `INVALID_CONFIG` | VM configuration failed validation. |
| `SOCKET_TIMEOUT` | Firecracker did not create its socket in time. |
| `API_TIMEOUT` | Firecracker API readiness check timed out. |
| `SOCKET_REQUEST_FAILED` | Unix-socket HTTP request failed. |
| `API_ERROR` | Firecracker returned a non-2xx response through a high-level VM call. |
| `BINARY_NOT_FOUND` | Custom Firecracker binary path does not exist. |
| `DOWNLOAD_FAILED` | Binary, kernel, or rootfs download failed. |
| `VERSION_FETCH_FAILED` | Latest Firecracker version lookup failed. |
| `BASE_IMAGE_RESOLVE_FAILED` | Firecracker CI base-image artifact resolution failed. |
| `BASE_IMAGE_MANIFEST_FAILED` | Hosted manifest could not be loaded or used. |
| `BASE_IMAGE_PREPARE_FAILED` | Base-image download/copy/verification failed. |
| `INVALID_BASE_IMAGE_MANIFEST` | Hosted manifest schema or artifact metadata is invalid. |
| `INVALID_BASE_IMAGE_CACHE_LIMIT` | Base-image retention limit is invalid. |
| `INVALID_SUBNET` | Subnet is not a supported IPv4 CIDR. |
| `INVALID_INTERFACE_NAME` | Bridge or TAP name is invalid. |
2026-05-01 18:32:08 +00:00
| `INVALID_FIREWALL_CONFIG` | VM egress firewall config is invalid. |
| `INVALID_WIREGUARD_CONFIG` | WireGuard egress config is invalid. |
2026-05-01 17:50:21 +00:00
| `IP_EXHAUSTED` | No guest IPs remain in the configured subnet. |
| `BRIDGE_SETUP_FAILED` | Bridge/NAT setup failed. |
2026-05-01 18:32:08 +00:00
| `WIREGUARD_SETUP_FAILED` | WireGuard interface or policy-route setup failed. |
2026-05-01 17:50:21 +00:00
| `TAP_CREATE_FAILED` | TAP creation failed. |
| `ROOTFS_CREATE_FAILED` | Blank rootfs creation failed. |
| `ROOTFS_CLONE_FAILED` | Rootfs clone failed. |
| `START_FAILED` | VM start sequence failed. |
| `NO_CLIENT` | Socket client is not initialized. |
2026-05-01 13:30:51 +00:00
## Testing
2026-05-01 17:50:21 +00:00
Default tests are safe on machines without KVM or root privileges:
2026-05-01 13:30:51 +00:00
``` bash
pnpm test
pnpm run build
```
2026-05-01 18:32:08 +00:00
The default suite covers config validation, payload generation, lifecycle guards, base-image cache behavior, hosted manifest validation, VM tracking, ephemeral drive staging, subnet/IP behavior, and firewall/WireGuard option validation.
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
Opt into real Firecracker boot tests on a Linux/KVM host:
2026-05-01 13:30:51 +00:00
``` bash
SMARTVM_RUN_INTEGRATION = true pnpm test
```
2026-05-01 17:50:21 +00:00
Accepted truthy values for `SMARTVM_RUN_INTEGRATION` : `1` , `true` , `yes` .
2026-05-01 13:30:51 +00:00
Useful integration-test environment variables:
2026-05-01 17:50:21 +00:00
| Variable | Purpose |
|---|---|
| `SMARTVM_BASE_IMAGE_PRESET` | `latest` or `lts` ; default is `latest` . |
| `SMARTVM_BASE_IMAGE_MANIFEST_URL` | Hosted/project-owned base-image manifest URL. |
| `SMARTVM_BASE_IMAGE_MANIFEST_PATH` | Local hosted manifest path. |
| `SMARTVM_BASE_IMAGE_CACHE_DIR` | Override `/tmp/.smartvm/base-images` . |
| `SMARTVM_MAX_STORED_BASE_IMAGES` | Override default retention of `2` . |
| `SMARTVM_FIRECRACKER_VERSION` | Override the Firecracker binary version. |
| `SMARTVM_ARCH` | `x86_64` or `aarch64` ; defaults from host architecture. |
| `SMARTVM_INTEGRATION_DATA_DIR` | Override the Firecracker binary data directory used by integration tests. |
## TypeScript Surface
2026-05-01 13:30:51 +00:00
2026-05-01 17:50:21 +00:00
Main exports:
``` typescript
export {
SmartVM ,
MicroVM ,
NetworkManager ,
FirecrackerProcess ,
BaseImageManager ,
ImageManager ,
SocketClient ,
VMConfig ,
} ;
```
2026-02-08 21:47:33 +00:00
2026-05-01 17:50:21 +00:00
Important exported types:
2026-02-08 21:47:33 +00:00
``` typescript
import type {
ISmartVMOptions ,
2026-05-01 15:28:06 +00:00
IMicroVMRuntimeOptions ,
2026-02-08 21:47:33 +00:00
IMicroVMConfig ,
IBootSource ,
IMachineConfig ,
IDriveConfig ,
INetworkInterfaceConfig ,
IVsockConfig ,
IBalloonConfig ,
IMmdsConfig ,
ILoggerConfig ,
IMetricsConfig ,
2026-05-01 13:30:51 +00:00
IBaseImageManagerOptions ,
IEnsureBaseImageOptions ,
IBaseImageBundle ,
IBaseImageHostedManifest ,
IBaseImageArtifactManifest ,
2026-02-08 21:47:33 +00:00
ISnapshotCreateParams ,
ISnapshotLoadParams ,
IRateLimiter ,
INetworkManagerOptions ,
ITapDevice ,
2026-05-01 18:32:08 +00:00
IFirewallConfig ,
IFirewallEgressConfig ,
IFirewallRule ,
TFirewallAction ,
TFirewallProtocol ,
TWireGuardConfig ,
IWireGuardManagedConfig ,
IWireGuardExistingInterfaceConfig ,
2026-02-08 21:47:33 +00:00
ISocketClientOptions ,
IApiResponse ,
TVMState ,
TFirecrackerArch ,
TCacheType ,
TSnapshotType ,
TLogLevel ,
2026-05-01 13:30:51 +00:00
TBaseImagePreset ,
TBaseImageRootfsType ,
2026-02-08 21:47:33 +00:00
} from '@push.rocks/smartvm' ;
```
## License and Legal Information
2026-05-01 18:42:05 +00:00
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE ](./LICENSE ) file.
2026-02-08 21:47:33 +00:00
**Please note: ** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
### Company Information
2026-05-01 17:50:21 +00:00
Task Venture Capital GmbH
2026-02-08 21:47:33 +00:00
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task .vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.