1
0
forked from wrenn/wrenn

chore: update proto, scripts, and docs for CH migration

- Update hostagent proto: firecracker_version → vmm_version in metadata
- Regenerate hostagent.pb.go
- Update .env.example: WRENN_FIRECRACKER_BIN → WRENN_CH_BIN
- Update Makefile: remove --isnotfc from dev-envd target
- Update prepare-wrenn-user.sh: firecracker → cloud-hypervisor paths
  and capability assignments
- Update wrenn-init.sh: disable write_zeroes on rootfs for dm-snapshot
  compatibility with CH
- Update README.md and CLAUDE.md: Firecracker → Cloud Hypervisor
  throughout
This commit is contained in:
2026-05-17 01:33:35 +06:00
parent dd8a940431
commit fb16bc9ed1
8 changed files with 36 additions and 31 deletions

View File

@ -16,7 +16,7 @@ WRENN_HOST_LISTEN_ADDR=:50051
WRENN_HOST_INTERFACE=eth0 WRENN_HOST_INTERFACE=eth0
WRENN_CP_URL=http://localhost:9725 WRENN_CP_URL=http://localhost:9725
WRENN_DEFAULT_ROOTFS_SIZE=5Gi WRENN_DEFAULT_ROOTFS_SIZE=5Gi
WRENN_FIRECRACKER_BIN=/usr/local/bin/firecracker WRENN_CH_BIN=/usr/local/bin/cloud-hypervisor
# Auth # Auth
JWT_SECRET= JWT_SECRET=

View File

@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
Wrenn Sandbox is a microVM-based code execution platform. Users create isolated sandboxes (Firecracker microVMs), run code inside them, and get output back via SDKs. Think E2B but with persistent sandboxes, pool-based pricing, and a single-binary deployment story. Wrenn Sandbox is a microVM-based code execution platform. Users create isolated sandboxes (Cloud Hypervisor microVMs), run code inside them, and get output back via SDKs. Think E2B but with persistent sandboxes, pool-based pricing, and a single-binary deployment story.
## Build & Development Commands ## Build & Development Commands
@ -23,11 +23,11 @@ make dev-down # Stop dev infra
make dev-cp # Control plane with hot reload (if air installed) make dev-cp # Control plane with hot reload (if air installed)
make dev-frontend # Vite dev server with HMR (port 5173) make dev-frontend # Vite dev server with HMR (port 5173)
make dev-agent # Host agent (sudo required) make dev-agent # Host agent (sudo required)
make dev-envd # envd in debug mode (--isnotfc, port 49983) make dev-envd # envd in debug mode (port 49983)
make check # fmt + vet + lint + test (CI order) make check # fmt + vet + lint + test (CI order)
make test # Unit tests: go test -race -v ./internal/... make test # Unit tests: go test -race -v ./internal/...
make test-integration # Integration tests (require host agent + Firecracker) make test-integration # Integration tests (require host agent + Cloud Hypervisor)
make fmt # gofmt make fmt # gofmt
make vet # go vet make vet # go vet
make lint # golangci-lint make lint # golangci-lint
@ -92,7 +92,7 @@ Startup (`cmd/host-agent/main.go`) wires: root/capabilities check → enable IP
- **RPC Server** (`internal/hostagent/server.go`): implements `hostagentv1connect.HostAgentServiceHandler`. Thin wrapper — every method delegates to `sandbox.Manager`. Maps Connect error codes on return. - **RPC Server** (`internal/hostagent/server.go`): implements `hostagentv1connect.HostAgentServiceHandler`. Thin wrapper — every method delegates to `sandbox.Manager`. Maps Connect error codes on return.
- **Sandbox Manager** (`internal/sandbox/manager.go`): the core orchestration layer. Maintains in-memory state in `boxes map[string]*sandboxState` (protected by `sync.RWMutex`). Each `sandboxState` holds a `models.Sandbox`, a `*network.Slot`, and an `*envdclient.Client`. Runs a TTL reaper (every 10s) that auto-destroys timed-out sandboxes. - **Sandbox Manager** (`internal/sandbox/manager.go`): the core orchestration layer. Maintains in-memory state in `boxes map[string]*sandboxState` (protected by `sync.RWMutex`). Each `sandboxState` holds a `models.Sandbox`, a `*network.Slot`, and an `*envdclient.Client`. Runs a TTL reaper (every 10s) that auto-destroys timed-out sandboxes.
- **VM Manager** (`internal/vm/manager.go`, `fc.go`, `config.go`): manages Firecracker processes. Uses raw HTTP API over Unix socket (`/tmp/fc-{sandboxID}.sock`), not the firecracker-go-sdk Machine type. Launches Firecracker via `unshare -m` + `ip netns exec`. Configures VM via PUT to `/boot-source`, `/drives/rootfs`, `/network-interfaces/eth0`, `/machine-config`, then starts with PUT `/actions`. - **VM Manager** (`internal/vm/manager.go`, `ch.go`, `config.go`): manages Cloud Hypervisor processes. Uses raw HTTP API over Unix socket (`/tmp/ch-{sandboxID}.sock`). Launches Cloud Hypervisor via `unshare -m` + `ip netns exec` with `--api-socket path=...`. Configures and boots VM via `PUT /vm.create` + `PUT /vm.boot`. Snapshot restore uses `--restore source_url=file://...`.
- **Network** (`internal/network/setup.go`, `allocator.go`): per-sandbox network namespace with veth pair + TAP device. See Networking section below. - **Network** (`internal/network/setup.go`, `allocator.go`): per-sandbox network namespace with veth pair + TAP device. See Networking section below.
- **Device Mapper** (`internal/devicemapper/devicemapper.go`): CoW rootfs via device-mapper snapshots. Shared read-only loop devices per base template (refcounted `LoopRegistry`), per-sandbox sparse CoW files, dm-snapshot create/restore/remove/flatten operations. - **Device Mapper** (`internal/devicemapper/devicemapper.go`): CoW rootfs via device-mapper snapshots. Shared read-only loop devices per base template (refcounted `LoopRegistry`), per-sandbox sparse CoW files, dm-snapshot create/restore/remove/flatten operations.
- **envd Client** (`internal/envdclient/client.go`, `health.go`): dual interface to the guest agent. Connect RPC for streaming process exec (`process.Start()` bidirectional stream). Plain HTTP for file operations (POST/GET `/files?path=...&username=root`). Health check polls `GET /health` every 100ms until ready (30s timeout). - **envd Client** (`internal/envdclient/client.go`, `health.go`): dual interface to the guest agent. Connect RPC for streaming process exec (`process.Start()` bidirectional stream). Plain HTTP for file operations (POST/GET `/files?path=...&username=root`). Health check polls `GET /health` every 100ms until ready (30s timeout).
@ -109,7 +109,7 @@ Runs as PID 1 inside the microVM via `wrenn-init.sh` (mounts procfs/sysfs/dev, s
- **HTTP endpoints**: GET `/health`, GET `/metrics`, POST `/init`, POST `/snapshot/prepare`, GET/POST `/files` - **HTTP endpoints**: GET `/health`, GET `/metrics`, POST `/init`, POST `/snapshot/prepare`, GET/POST `/files`
- **Proto codegen**: `connectrpc-build` compiles `proto/envd/*.proto` at `cargo build` time via `build.rs` — no committed stubs - **Proto codegen**: `connectrpc-build` compiles `proto/envd/*.proto` at `cargo build` time via `build.rs` — no committed stubs
- **Build**: `make build-envd` → static musl binary in `builds/envd` - **Build**: `make build-envd` → static musl binary in `builds/envd`
- **Dev**: `make dev-envd``cargo run -- --isnotfc --port 49983` - **Dev**: `make dev-envd``cargo run -- --port 49983`
### Dashboard (Frontend) ### Dashboard (Frontend)
@ -164,7 +164,7 @@ HIBERNATED → RUNNING (cold snapshot resume, slower)
**Sandbox creation** (`POST /v1/capsules`): **Sandbox creation** (`POST /v1/capsules`):
1. API handler generates sandbox ID, inserts into DB as "pending" 1. API handler generates sandbox ID, inserts into DB as "pending"
2. RPC `CreateSandbox` → host agent → `sandbox.Manager.Create()` 2. RPC `CreateSandbox` → host agent → `sandbox.Manager.Create()`
3. Manager: resolve base rootfs → acquire shared loop device → create dm-snapshot (sparse CoW file) → allocate network slot → `CreateNetwork()` (netns + veth + tap + NAT) → `vm.Create()` (start Firecracker with `/dev/mapper/wrenn-{id}`, configure via HTTP API, boot) → `envdclient.WaitUntilReady()` (poll /health) → store in-memory state 3. Manager: resolve base rootfs → acquire shared loop device → create dm-snapshot (sparse CoW file) → allocate network slot → `CreateNetwork()` (netns + veth + tap + NAT) → `vm.Create()` (start Cloud Hypervisor with `/dev/mapper/wrenn-{id}`, configure via `PUT /vm.create` + `PUT /vm.boot`) → `envdclient.WaitUntilReady()` (poll /health) → store in-memory state
4. API handler updates DB to "running" with host_ip 4. API handler updates DB to "running" with host_ip
**Command execution** (`POST /v1/capsules/{id}/exec`): **Command execution** (`POST /v1/capsules/{id}/exec`):
@ -210,9 +210,9 @@ To add a new query: add it to the appropriate `.sql` file in `db/queries/` → `
- **Connect RPC** (not gRPC) for all RPC communication between components - **Connect RPC** (not gRPC) for all RPC communication between components
- **Buf + protoc-gen-connect-go** for Go code generation; **connectrpc-build** for Rust code generation in envd - **Buf + protoc-gen-connect-go** for Go code generation; **connectrpc-build** for Rust code generation in envd
- **Raw Firecracker HTTP API** via Unix socket (not firecracker-go-sdk Machine type) - **Raw Cloud Hypervisor HTTP API** via Unix socket (`PUT /vm.create` + `PUT /vm.boot`)
- **TAP networking** (not vsock) for host-to-envd communication - **TAP networking** (not vsock) for host-to-envd communication
- **Device-mapper snapshots** for rootfs CoW — shared read-only loop device per base template, per-sandbox sparse CoW file, Firecracker gets `/dev/mapper/wrenn-{id}` - **Device-mapper snapshots** for rootfs CoW — shared read-only loop device per base template, per-sandbox sparse CoW file, Cloud Hypervisor gets `/dev/mapper/wrenn-{id}`
- **PostgreSQL** via pgx/v5 + sqlc (type-safe query generation). Goose for migrations (plain SQL, up/down) - **PostgreSQL** via pgx/v5 + sqlc (type-safe query generation). Goose for migrations (plain SQL, up/down)
- **Dashboard**: SvelteKit (Svelte 5, adapter-static) + Tailwind CSS v4 + Bits UI. Built to static files in `frontend/build/`, served by Caddy (not embedded in the Go binary) - **Dashboard**: SvelteKit (Svelte 5, adapter-static) + Tailwind CSS v4 + Bits UI. Built to static files in `frontend/build/`, served by Caddy (not embedded in the Go binary)
- **Lago** for billing (external service, not in this codebase) - **Lago** for billing (external service, not in this codebase)
@ -237,19 +237,19 @@ To add a new query: add it to the appropriate `.sql` file in `db/queries/` → `
- Kernel: `/var/lib/wrenn/kernels/vmlinux` - Kernel: `/var/lib/wrenn/kernels/vmlinux`
- Base rootfs images: `/var/lib/wrenn/images/{template}.ext4` - Base rootfs images: `/var/lib/wrenn/images/{template}.ext4`
- Sandbox clones: `/var/lib/wrenn/sandboxes/` - Sandbox clones: `/var/lib/wrenn/sandboxes/`
- Firecracker: `/usr/local/bin/firecracker` (e2b's fork of firecracker) - Cloud Hypervisor: `/usr/local/bin/cloud-hypervisor`
## Design Context ## Design Context
### Users ### Users
Developers across the full spectrum — solo engineers building side projects, startup teams integrating sandboxed execution into products, and platform/infra engineers at larger organizations running production workloads on Firecracker microVMs. They arrive with context: they know what a process is, what a rootfs is, what a TTY means. The interface must feel at home for all three: approachable enough not to intimidate a hacker, precise enough to earn the trust of a production ops team. Never condescend, never oversimplify. Trust the user to understand what they're looking at. Developers across the full spectrum — solo engineers building side projects, startup teams integrating sandboxed execution into products, and platform/infra engineers at larger organizations running production workloads on Cloud Hypervisor microVMs. They arrive with context: they know what a process is, what a rootfs is, what a TTY means. The interface must feel at home for all three: approachable enough not to intimidate a hacker, precise enough to earn the trust of a production ops team. Never condescend, never oversimplify. Trust the user to understand what they're looking at.
**Primary job to be done:** Understand what's running, act on it confidently, and get back to code. **Primary job to be done:** Understand what's running, act on it confidently, and get back to code.
### Brand Personality ### Brand Personality
**Precise. Warm. Uncompromising.** **Precise. Warm. Uncompromising.**
Wrenn is an engineer's favorite tool — built with visible care, not assembled from defaults. It runs real infrastructure (Firecracker microVMs), so the UI should reflect that seriousness without becoming cold or corporate. The warmth comes from the typography and color palette; the precision comes from hierarchy, density, and data fidelity. Wrenn is an engineer's favorite tool — built with visible care, not assembled from defaults. It runs real infrastructure (Cloud Hypervisor microVMs), so the UI should reflect that seriousness without becoming cold or corporate. The warmth comes from the typography and color palette; the precision comes from hierarchy, density, and data fidelity.
Emotional goal: **in control.** Users leave a session with full confidence in what's running, what happened, and what comes next. Nothing is hidden, nothing is ambiguous. Emotional goal: **in control.** Users leave a session with full confidence in what's running, what happened, and what comes next. Nothing is hidden, nothing is ambiguous.

View File

@ -62,7 +62,7 @@ dev-frontend:
cd frontend && bun run dev --port 5173 --host 0.0.0.0 cd frontend && bun run dev --port 5173 --host 0.0.0.0
dev-envd: dev-envd:
cd envd-rs && cargo run -- --isnotfc --port 49983 cd envd-rs && cargo run -- --port 49983
# ═══════════════════════════════════════════════════ # ═══════════════════════════════════════════════════
# Database (goose) # Database (goose)
@ -181,7 +181,7 @@ help:
@echo " make dev-cp Control plane (hot reload if air installed)" @echo " make dev-cp Control plane (hot reload if air installed)"
@echo " make dev-frontend Vite dev server with HMR (port 5173)" @echo " make dev-frontend Vite dev server with HMR (port 5173)"
@echo " make dev-agent Host agent (sudo required)" @echo " make dev-agent Host agent (sudo required)"
@echo " make dev-envd envd in debug mode (--isnotfc, port 49983)" @echo " make dev-envd envd in debug mode (port 49983)"
@echo "" @echo ""
@echo " make build Build all binaries → builds/" @echo " make build Build all binaries → builds/"
@echo " make build-frontend Build SvelteKit dashboard → frontend/build/" @echo " make build-frontend Build SvelteKit dashboard → frontend/build/"

View File

@ -5,7 +5,7 @@ Secure infrastructure for AI
## Prerequisites ## Prerequisites
- Linux host with `/dev/kvm` access (bare metal or nested virt) - Linux host with `/dev/kvm` access (bare metal or nested virt)
- Firecracker binary at `/usr/local/bin/firecracker` - Cloud Hypervisor binary at `/usr/local/bin/cloud-hypervisor`
- PostgreSQL - PostgreSQL
- Go 1.25+ - Go 1.25+
- Rust 1.88+ with `x86_64-unknown-linux-musl` target (`rustup target add x86_64-unknown-linux-musl`) - Rust 1.88+ with `x86_64-unknown-linux-musl` target (`rustup target add x86_64-unknown-linux-musl`)

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
# wrenn-init: minimal PID 1 init for Firecracker microVMs. # wrenn-init: minimal PID 1 init for Cloud Hypervisor microVMs.
# Mounts virtual filesystems, starts chronyd for time sync, then execs tini + envd. # Mounts virtual filesystems, starts chronyd for time sync, then execs tini + envd.
set -e set -e
@ -17,6 +17,11 @@ mkdir -p /sys/fs/cgroup
mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null || true mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null || true
echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true
# Disable write_zeroes on rootfs — dm-snapshot doesn't support BLKZEROOUT,
# and CH advertises the feature anyway. Without this, every zeroing IO
# hits EOPNOTSUPP and CH spams warnings. Only writable on kernel 6.6+.
echo 0 > /sys/block/vda/queue/write_zeroes_max_bytes 2>/dev/null || true
# Set hostname and make it resolvable (sudo requires this). # Set hostname and make it resolvable (sudo requires this).
hostname capsule hostname capsule
echo "127.0.0.1 capsule" >> /etc/hosts echo "127.0.0.1 capsule" >> /etc/hosts

View File

@ -155,7 +155,7 @@ type CreateSandboxResponse struct {
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
HostIp string `protobuf:"bytes,3,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"` HostIp string `protobuf:"bytes,3,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"`
// Runtime metadata collected during sandbox creation (e.g. envd_version, // Runtime metadata collected during sandbox creation (e.g. envd_version,
// kernel_version, firecracker_version, agent_version). // kernel_version, vmm_version, agent_version).
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache

View File

@ -146,7 +146,7 @@ message CreateSandboxResponse {
string host_ip = 3; string host_ip = 3;
// Runtime metadata collected during sandbox creation (e.g. envd_version, // Runtime metadata collected during sandbox creation (e.g. envd_version,
// kernel_version, firecracker_version, agent_version). // kernel_version, vmm_version, agent_version).
map<string, string> metadata = 4; map<string, string> metadata = 4;
} }

View File

@ -22,7 +22,7 @@
# Prerequisites: # Prerequisites:
# - wrenn-agent binary at /usr/local/bin/wrenn-agent # - wrenn-agent binary at /usr/local/bin/wrenn-agent
# - wrenn-cp binary at /usr/local/bin/wrenn-cp # - wrenn-cp binary at /usr/local/bin/wrenn-cp
# - firecracker binary at /usr/local/bin/firecracker # - cloud-hypervisor binary at /usr/local/bin/cloud-hypervisor
# - libcap2-bin installed (for setcap) # - libcap2-bin installed (for setcap)
set -euo pipefail set -euo pipefail
@ -41,7 +41,7 @@ WRENN_GROUP="wrenn"
WRENN_DIR="/var/lib/wrenn" WRENN_DIR="/var/lib/wrenn"
AGENT_BIN="/usr/local/bin/wrenn-agent" AGENT_BIN="/usr/local/bin/wrenn-agent"
CP_BIN="/usr/local/bin/wrenn-cp" CP_BIN="/usr/local/bin/wrenn-cp"
FC_BIN="/usr/local/bin/firecracker" CH_BIN="/usr/local/bin/cloud-hypervisor"
RESTORE_CAPS_SCRIPT="/etc/wrenn/restore-caps.sh" RESTORE_CAPS_SCRIPT="/etc/wrenn/restore-caps.sh"
# ── 1. Create system user ─────────────────────────────────────────────────── # ── 1. Create system user ───────────────────────────────────────────────────
@ -100,7 +100,7 @@ done
# routing table manipulation # routing table manipulation
# CAP_NET_RAW — raw socket access (needed by iptables internally) # CAP_NET_RAW — raw socket access (needed by iptables internally)
# CAP_SYS_PTRACE — reading /proc/self/ns/net (netns.Get) # CAP_SYS_PTRACE — reading /proc/self/ns/net (netns.Get)
# CAP_KILL — sending SIGTERM/SIGKILL to Firecracker processes # CAP_KILL — sending SIGTERM/SIGKILL to Cloud Hypervisor processes
# CAP_DAC_OVERRIDE — accessing /dev/loop*, /dev/mapper/*, /dev/net/tun, # CAP_DAC_OVERRIDE — accessing /dev/loop*, /dev/mapper/*, /dev/net/tun,
# /proc/sys/net/ipv4/ip_forward # /proc/sys/net/ipv4/ip_forward
# CAP_MKNOD — creating device nodes (dm-snapshot) # CAP_MKNOD — creating device nodes (dm-snapshot)
@ -120,12 +120,12 @@ else
getcap "${AGENT_BIN}" getcap "${AGENT_BIN}"
fi fi
# Firecracker also needs capabilities when spawned by a non-root parent. # Cloud Hypervisor also needs capabilities when spawned by a non-root parent.
# CAP_NET_ADMIN is required for network device access inside the netns. # CAP_NET_ADMIN is required for network device access inside the netns.
if [[ -f "${FC_BIN}" ]]; then if [[ -f "${CH_BIN}" ]]; then
setcap cap_net_admin,cap_sys_admin,cap_dac_override+ep "${FC_BIN}" setcap cap_net_admin,cap_sys_admin,cap_dac_override+ep "${CH_BIN}"
echo " Capabilities set on ${FC_BIN}:" echo " Capabilities set on ${CH_BIN}:"
getcap "${FC_BIN}" getcap "${CH_BIN}"
fi fi
# ── Helper: resolve binary path and apply setcap ──────────────────────────── # ── Helper: resolve binary path and apply setcap ────────────────────────────
@ -191,13 +191,13 @@ setcap_binary() {
setcap "$caps" "$bin" 2>/dev/null || true setcap "$caps" "$bin" 2>/dev/null || true
} }
# wrenn-agent and firecracker (only if present — they aren't package-managed). # wrenn-agent and cloud-hypervisor (only if present — they aren't package-managed).
[[ -f /usr/local/bin/wrenn-agent ]] && \ [[ -f /usr/local/bin/wrenn-agent ]] && \
setcap cap_sys_admin,cap_net_admin,cap_net_raw,cap_sys_ptrace,cap_kill,cap_dac_override,cap_mknod+ep \ setcap cap_sys_admin,cap_net_admin,cap_net_raw,cap_sys_ptrace,cap_kill,cap_dac_override,cap_mknod+ep \
/usr/local/bin/wrenn-agent 2>/dev/null || true /usr/local/bin/wrenn-agent 2>/dev/null || true
[[ -f /usr/local/bin/firecracker ]] && \ [[ -f /usr/local/bin/cloud-hypervisor ]] && \
setcap cap_net_admin,cap_sys_admin,cap_dac_override+ep \ setcap cap_net_admin,cap_sys_admin,cap_dac_override+ep \
/usr/local/bin/firecracker 2>/dev/null || true /usr/local/bin/cloud-hypervisor 2>/dev/null || true
# Child binaries (these are the ones wiped by apt). # Child binaries (these are the ones wiped by apt).
setcap_binary iptables "cap_net_admin,cap_net_raw+ep" setcap_binary iptables "cap_net_admin,cap_net_raw+ep"
@ -315,14 +315,14 @@ ExecStart=/usr/local/bin/wrenn-agent --address ${WRENN_ADVERTISE_ADDR}
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
# File descriptor limits (Firecracker + loop devices + sockets). # File descriptor limits (Cloud Hypervisor + loop devices + sockets).
LimitNOFILE=65536 LimitNOFILE=65536
LimitNPROC=4096 LimitNPROC=4096
# Protect host filesystem — only allow access to what's needed. # Protect host filesystem — only allow access to what's needed.
ProtectHome=true ProtectHome=true
ReadWritePaths=/var/lib/wrenn /tmp /run/netns /dev/mapper ReadWritePaths=/var/lib/wrenn /tmp /run/netns /dev/mapper
ReadOnlyPaths=/usr/local/bin/firecracker ReadOnlyPaths=/usr/local/bin/cloud-hypervisor
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target