openshell-driver-vm
Status: Experimental. The VM compute driver is under active development and the interface still has VM-specific plumbing that will be generalized.
Standalone libkrun-backed ComputeDriver for OpenShell. The gateway spawns this binary as a subprocess, talks to it over a Unix domain socket with the openshell.compute.v1.ComputeDriver gRPC surface, and lets it manage per-sandbox microVMs. The runtime (libkrun + libkrunfw + gvproxy) and sandbox rootfs are embedded directly in the binary — no sibling files required at runtime.
How it fits together
flowchart LR
subgraph host["Host process"]
gateway["openshell-server<br/>(compute::vm::spawn)"]
driver["openshell-driver-vm<br/>├── libkrun (VM)<br/>├── gvproxy (net)<br/>└── rootfs.tar.zst"]
gateway <-->|"gRPC over UDS<br/>compute-driver.sock"| driver
end
subgraph guest["Per-sandbox microVM"]
init["/srv/openshell-vm-<br/>sandbox-init.sh"]
supervisor["/opt/openshell/bin/<br/>openshell-sandbox<br/>(PID 1)"]
init --> supervisor
end
driver -->|"CreateSandbox<br/>boots via libkrun"| guest
supervisor -.->|"gRPC callback<br/>--grpc-endpoint"| gateway
client["openshell-cli"] -->|"SSH proxy<br/>127.0.0.1:<port>"| supervisor
client -->|"CreateSandbox / Watch"| gateway
Sandbox guests execute /opt/openshell/bin/openshell-sandbox as PID 1 inside the VM. gvproxy exposes a single inbound SSH port (host:<allocated> → guest:2222) and provides virtio-net egress.
Quick start (recommended)
mise run gateway:vm
First run takes a few minutes while mise run vm:setup stages libkrun/libkrunfw/gvproxy and mise run vm:rootfs -- --base builds the embedded rootfs. Subsequent runs are cached. To keep the Unix socket path under macOS SUN_LEN, mise run gateway:vm and start.sh default the state dir to /tmp/openshell-vm-driver-dev-$USER-port-$PORT/ (SQLite DB + per-sandbox rootfs + compute-driver.sock) unless OPENSHELL_VM_DRIVER_STATE_DIR is set.
The wrapper auto-registers the gateway with the CLI (gateway destroy + gateway add) so no manual registration step is needed. When running under sudo, it uses sudo -u $SUDO_USER for the registration so the config is written under the invoking user's home directory. Re-runs are idempotent.
It also exports OPENSHELL_DRIVER_DIR=$PWD/target/debug before starting the gateway so local dev runs use the freshly built openshell-driver-vm instead of an older installed copy from ~/.local/libexec/openshell, /usr/libexec/openshell, or /usr/local/libexec.
For GPU passthrough (VFIO), pass -- --gpu and run with root privileges:
sudo -E env "PATH=$PATH" mise run gateway:vm -- --gpu
See architecture/vm-gpu-sandbox-guide.md for full GPU prerequisites and usage.
Override via environment:
OPENSHELL_SERVER_PORT=9090 \
crates/openshell-driver-vm/start.sh
Run multiple dev gateways side by side by giving each one a unique port. The wrapper derives a distinct default state dir from that port automatically:
OPENSHELL_SERVER_PORT=8080 mise run gateway:vm
OPENSHELL_SERVER_PORT=8081 mise run gateway:vm
If you want a custom suffix instead of port-$PORT, set OPENSHELL_VM_INSTANCE:
OPENSHELL_SERVER_PORT=8082 \
OPENSHELL_VM_INSTANCE=feature-a \
mise run gateway:vm
If you want a custom CLI gateway name, set OPENSHELL_VM_GATEWAY_NAME:
OPENSHELL_SERVER_PORT=8082 \
OPENSHELL_VM_GATEWAY_NAME=vm-feature-a \
mise run gateway:vm
Teardown:
rm -rf /tmp/openshell-vm-driver-dev-$USER-port-8080
Manual equivalent
If you want to drive the launch yourself instead of using start.sh:
# 1. Stage runtime artifacts + base rootfs into target/vm-runtime-compressed/
mise run vm:setup
mise run vm:rootfs -- --base # if rootfs.tar.zst is not already present
# 2. Build both binaries with the staged artifacts embedded
OPENSHELL_VM_RUNTIME_COMPRESSED_DIR=$PWD/target/vm-runtime-compressed \
cargo build -p openshell-server -p openshell-driver-vm
# 3. macOS only: codesign the driver for Hypervisor.framework
codesign \
--entitlements crates/openshell-driver-vm/entitlements.plist \
--force -s - target/debug/openshell-driver-vm
# 4. Start the gateway with the VM driver
mkdir -p /tmp/openshell-vm-driver-dev-$USER-port-8080
target/debug/openshell-gateway \
--drivers vm \
--disable-tls \
--database-url sqlite:/tmp/openshell-vm-driver-dev-$USER-port-8080/openshell.db \
--driver-dir $PWD/target/debug \
--grpc-endpoint http://host.containers.internal:8080 \
--ssh-gateway-host 127.0.0.1 \
--ssh-gateway-port 8080 \
--vm-driver-state-dir /tmp/openshell-vm-driver-dev-$USER-port-8080
The gateway resolves openshell-driver-vm in this order: --driver-dir, conventional install locations (~/.local/libexec/openshell, /usr/libexec/openshell, /usr/local/libexec/openshell, /usr/local/libexec), then a sibling of the gateway binary.
Flags
| Flag | Env var | Default | Purpose |
|---|---|---|---|
--drivers vm |
OPENSHELL_DRIVERS |
kubernetes |
Select the VM compute driver. |
--grpc-endpoint URL |
OPENSHELL_GRPC_ENDPOINT |
— | Required. URL the sandbox guest calls back to. Use a host alias that resolves to the gateway's host from inside the VM (host.containers.internal comes from gvproxy DNS; the guest init script also seeds host.openshell.internal to 192.168.127.1). |
--vm-driver-state-dir DIR |
OPENSHELL_VM_DRIVER_STATE_DIR |
target/openshell-vm-driver |
Per-sandbox rootfs, console logs, and the compute-driver.sock UDS. |
--driver-dir DIR |
OPENSHELL_DRIVER_DIR |
unset | Override the directory searched for openshell-driver-vm. |
--vm-driver-vcpus N |
OPENSHELL_VM_DRIVER_VCPUS |
2 |
vCPUs per sandbox. |
--vm-driver-mem-mib N |
OPENSHELL_VM_DRIVER_MEM_MIB |
2048 |
Memory per sandbox, in MiB. |
--vm-krun-log-level N |
OPENSHELL_VM_KRUN_LOG_LEVEL |
1 |
libkrun verbosity (0–5). |
--vm-tls-ca PATH |
OPENSHELL_VM_TLS_CA |
— | CA cert for the guest's mTLS client bundle. Required when --grpc-endpoint uses https://. |
--vm-tls-cert PATH |
OPENSHELL_VM_TLS_CERT |
— | Guest client certificate. |
--vm-tls-key PATH |
OPENSHELL_VM_TLS_KEY |
— | Guest client private key. |
See openshell-gateway --help for the full flag surface shared with the Kubernetes driver.
Verifying the gateway
The gateway is auto-registered by start.sh. In another terminal:
scripts/bin/openshell sandbox create --name demo
scripts/bin/openshell sandbox connect demo
First sandbox takes 10–30 seconds to boot (rootfs extraction + libkrun + guest init). Subsequent creates reuse the prepared sandbox rootfs.
Logs and debugging
Raise log verbosity for both processes:
RUST_LOG=openshell_server=debug,openshell_driver_vm=debug \
crates/openshell-driver-vm/start.sh
The VM guest's serial console is appended to <state-dir>/<sandbox-id>/console.log. The compute-driver.sock lives at <state-dir>/compute-driver.sock; the gateway removes it on clean shutdown via ManagedDriverProcess::drop.
Prerequisites
- macOS on Apple Silicon, or Linux on aarch64/x86_64 with KVM
- Rust toolchain
- Guest-supervisor cross-compile toolchain (needed on macOS, and on Linux when host arch ≠ guest arch):
- Matching rustup target:
rustup target add aarch64-unknown-linux-gnu(orx86_64-unknown-linux-gnufor an amd64 guest) cargo install --locked cargo-zigbuildandbrew install zig(or distro equivalent).build-rootfs.shusescargo zigbuildto cross-compile the in-VMopenshell-sandboxsupervisor binary.
- Matching rustup target:
- mise task runner
- Docker (needed by
mise run vm:rootfsto build the base rootfs) ghCLI (used bymise run vm:setupto download pre-built runtime artifacts)
Relationship to openshell-vm
openshell-vm is a separate, legacy crate that runs the whole OpenShell gateway inside a single VM. openshell-driver-vm is the compute driver called by a host-resident gateway to spawn per-sandbox VMs. Both embed libkrun but share no Rust code — the driver vendors its own rootfs handling and runtime loader so openshell-server never has to link libkrun.
TODOs
- The gateway still configures the driver via CLI args; this will move to a gRPC bootstrap call so the driver interface is uniform across backends. See the
TODO(driver-abstraction)notes incrates/openshell-server/src/lib.rsandcrates/openshell-server/src/compute/vm.rs. - macOS codesigning is handled by
start.sh; a packaged release would need signing in CI.