mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 21:03:19 +00:00
v86.md describing how we could possibly ship a minimal alpine container with mom by default
This commit is contained in:
parent
c352857a43
commit
93d000b19d
1 changed files with 319 additions and 0 deletions
319
packages/mom/docs/v86.md
Normal file
319
packages/mom/docs/v86.md
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# v86 Sandbox Evaluation
|
||||
|
||||
v86 is an x86 emulator written in JavaScript/WebAssembly that can run Linux in the browser or Node.js. This document details our evaluation for using it as a sandboxed execution environment.
|
||||
|
||||
## Overview
|
||||
|
||||
- **What it is**: x86 PC emulator (32-bit, Pentium 4 level)
|
||||
- **How it works**: Translates machine code to WebAssembly at runtime
|
||||
- **Guest OS**: Alpine Linux 3.21 (32-bit x86)
|
||||
- **Available packages**: Node.js 22, Python 3.12, git, curl, etc. (full Alpine repos)
|
||||
|
||||
## Key Findings
|
||||
|
||||
### What Works
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Outbound TCP | ✅ | HTTP, HTTPS, TLS all work |
|
||||
| Outbound UDP | ✅ | DNS queries work |
|
||||
| WebSocket client | ✅ | Can connect to external WebSocket servers |
|
||||
| File I/O | ✅ | 9p filesystem for host<->guest file exchange |
|
||||
| State save/restore | ✅ | ~80-100MB state files, instant resume |
|
||||
| Package persistence | ✅ | Installed packages persist in saved state |
|
||||
| npm install | ✅ | Works (outbound HTTPS) |
|
||||
| git clone | ✅ | Works (outbound HTTPS) |
|
||||
|
||||
### What Doesn't Work
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Inbound connections | ❌ | VM is behind NAT (10.0.2.x), needs port forwarding |
|
||||
| ICMP ping | ❌ | Userspace network stack limitation |
|
||||
| 64-bit | ❌ | v86 only emulates 32-bit x86 |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Host (Node.js) │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌─────────────────────────────┐ │
|
||||
│ │ rootlessRelay│◄───►│ v86 │ │
|
||||
│ │ (WebSocket) │ │ ┌─────────────────────┐ │ │
|
||||
│ │ │ │ │ Alpine Linux │ │ │
|
||||
│ │ - DHCP │ │ │ - Node.js 22 │ │ │
|
||||
│ │ - DNS proxy │ │ │ - Python 3.12 │ │ │
|
||||
│ │ - NAT │ │ │ - etc. │ │ │
|
||||
│ └──────────────┘ │ └─────────────────────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 9p filesystem │ │
|
||||
│ ▼ │ │ │ │
|
||||
│ Internet │ ▼ │ │
|
||||
│ │ Host filesystem │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Components & Sizes
|
||||
|
||||
| Component | Size | Purpose |
|
||||
|-----------|------|---------|
|
||||
| v86.wasm | ~2 MB | x86 emulator |
|
||||
| libv86.mjs | ~330 KB | JavaScript runtime |
|
||||
| seabios.bin | ~128 KB | BIOS |
|
||||
| vgabios.bin | ~36 KB | VGA BIOS |
|
||||
| Alpine rootfs | ~57 MB | Compressed filesystem (loaded on-demand) |
|
||||
| alpine-fs.json | ~160 KB | Filesystem index |
|
||||
| rootlessRelay | ~75 KB | Network relay |
|
||||
| **Total** | **~60 MB** | Without saved state |
|
||||
| Saved state | ~80-100 MB | Optional, for instant resume |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install v86 ws
|
||||
```
|
||||
|
||||
## Building the Alpine Image
|
||||
|
||||
v86 provides Docker tooling to build the Alpine image:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/copy/v86.git
|
||||
cd v86/tools/docker/alpine
|
||||
|
||||
# Edit Dockerfile to add packages:
|
||||
# ENV ADDPKGS=nodejs,npm,python3,git,curl
|
||||
|
||||
./build.sh
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `images/alpine-fs.json` - Filesystem index
|
||||
- `images/alpine-rootfs-flat/` - Compressed file chunks
|
||||
|
||||
## Network Relay Setup
|
||||
|
||||
v86 needs a network relay for TCP/UDP connectivity. We use rootlessRelay:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/obegron/rootlessRelay.git
|
||||
cd rootlessRelay
|
||||
npm install
|
||||
```
|
||||
|
||||
### Required Patches for Host Access
|
||||
|
||||
To allow the VM to connect to host services via the gateway IP (10.0.2.2), apply these patches to `relay.js`:
|
||||
|
||||
**Patch 1: Disable reverse TCP handling for gateway (line ~684)**
|
||||
```javascript
|
||||
// Change:
|
||||
if (protocol === 6 && dstIP === GATEWAY_IP) {
|
||||
this.handleReverseTCP(ipPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
// To:
|
||||
if (false && protocol === 6 && dstIP === GATEWAY_IP) { // PATCHED
|
||||
this.handleReverseTCP(ipPacket);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
**Patch 2: Redirect gateway TCP to localhost (line ~792)**
|
||||
```javascript
|
||||
// Change:
|
||||
const socket = net.connect(dstPort, dstIP, () => {
|
||||
|
||||
// To:
|
||||
const actualDstIP = dstIP === GATEWAY_IP ? "127.0.0.1" : dstIP;
|
||||
const socket = net.connect(dstPort, actualDstIP, () => {
|
||||
```
|
||||
|
||||
**Patch 3: Redirect gateway UDP to localhost (lines ~1431 and ~1449)**
|
||||
```javascript
|
||||
// Change:
|
||||
this.udpSocket.send(payload, dstPort, dstIP, (err) => {
|
||||
|
||||
// To:
|
||||
const actualUdpDstIP = dstIP === GATEWAY_IP ? "127.0.0.1" : dstIP;
|
||||
this.udpSocket.send(payload, dstPort, actualUdpDstIP, (err) => {
|
||||
```
|
||||
|
||||
### Starting the Relay
|
||||
|
||||
```bash
|
||||
ENABLE_WSS=false LOG_LEVEL=1 node relay.js
|
||||
# Listens on ws://127.0.0.1:8086/
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```javascript
|
||||
import { V86 } from "v86";
|
||||
import path from "node:path";
|
||||
|
||||
const emulator = new V86({
|
||||
wasm_path: path.join(__dirname, "node_modules/v86/build/v86.wasm"),
|
||||
bios: { url: path.join(__dirname, "bios/seabios.bin") },
|
||||
vga_bios: { url: path.join(__dirname, "bios/vgabios.bin") },
|
||||
filesystem: {
|
||||
basefs: path.join(__dirname, "images/alpine-fs.json"),
|
||||
baseurl: path.join(__dirname, "images/alpine-rootfs-flat/"),
|
||||
},
|
||||
autostart: true,
|
||||
memory_size: 512 * 1024 * 1024,
|
||||
bzimage_initrd_from_filesystem: true,
|
||||
cmdline: "rw root=host9p rootfstype=9p rootflags=trans=virtio,cache=loose modules=virtio_pci tsc=reliable console=ttyS0",
|
||||
net_device: {
|
||||
type: "virtio",
|
||||
relay_url: "ws://127.0.0.1:8086/",
|
||||
},
|
||||
});
|
||||
|
||||
// Capture output
|
||||
emulator.add_listener("serial0-output-byte", (byte) => {
|
||||
process.stdout.write(String.fromCharCode(byte));
|
||||
});
|
||||
|
||||
// Send commands
|
||||
emulator.serial0_send("echo hello\n");
|
||||
```
|
||||
|
||||
## Communication Methods
|
||||
|
||||
### 1. Serial Console (stdin/stdout)
|
||||
|
||||
```javascript
|
||||
// Send command
|
||||
emulator.serial0_send("ls -la\n");
|
||||
|
||||
// Receive output
|
||||
let output = "";
|
||||
emulator.add_listener("serial0-output-byte", (byte) => {
|
||||
output += String.fromCharCode(byte);
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 9p Filesystem (file I/O)
|
||||
|
||||
```javascript
|
||||
// Write file to VM
|
||||
const data = new TextEncoder().encode("#!/bin/sh\necho hello\n");
|
||||
await emulator.create_file("/tmp/script.sh", data);
|
||||
|
||||
// Read file from VM
|
||||
const result = await emulator.read_file("/tmp/output.txt");
|
||||
console.log(new TextDecoder().decode(result));
|
||||
```
|
||||
|
||||
### 3. Network (TCP to host services)
|
||||
|
||||
From inside the VM, connect to `10.0.2.2:PORT` to reach `localhost:PORT` on the host (requires patched relay).
|
||||
|
||||
```bash
|
||||
# Inside VM
|
||||
wget http://10.0.2.2:8080/ # Connects to host's localhost:8080
|
||||
```
|
||||
|
||||
## State Save/Restore
|
||||
|
||||
```javascript
|
||||
// Save state (includes all installed packages, files, etc.)
|
||||
const state = await emulator.save_state();
|
||||
fs.writeFileSync("vm-state.bin", Buffer.from(state));
|
||||
|
||||
// Restore state (instant resume, ~2 seconds)
|
||||
const stateBuffer = fs.readFileSync("vm-state.bin");
|
||||
await emulator.restore_state(stateBuffer.buffer);
|
||||
```
|
||||
|
||||
## Network Setup Inside VM
|
||||
|
||||
After boot, run these commands to enable networking:
|
||||
|
||||
```bash
|
||||
modprobe virtio-net
|
||||
ip link set eth0 up
|
||||
udhcpc -i eth0
|
||||
```
|
||||
|
||||
Or as a one-liner:
|
||||
```bash
|
||||
modprobe virtio-net && ip link set eth0 up && udhcpc -i eth0
|
||||
```
|
||||
|
||||
The VM will get IP `10.0.2.15` (or similar) via DHCP from the relay.
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Cold boot | ~20-25 seconds |
|
||||
| State restore | ~2-3 seconds |
|
||||
| Memory usage | ~512 MB (configurable) |
|
||||
|
||||
## Typical Workflow for Mom
|
||||
|
||||
1. **First run**:
|
||||
- Start rootlessRelay
|
||||
- Boot v86 with Alpine (~25s)
|
||||
- Setup network
|
||||
- Install needed packages (`apk add nodejs npm python3 git`)
|
||||
- Save state
|
||||
|
||||
2. **Subsequent runs**:
|
||||
- Start rootlessRelay
|
||||
- Restore saved state (~2s)
|
||||
- Ready to execute commands
|
||||
|
||||
3. **Command execution**:
|
||||
- Send commands via `serial0_send()`
|
||||
- Capture output via `serial0-output-byte` listener
|
||||
- Exchange files via 9p filesystem
|
||||
|
||||
## Alternative: fetch Backend (No Relay Needed)
|
||||
|
||||
For HTTP-only networking, v86 has a built-in `fetch` backend:
|
||||
|
||||
```javascript
|
||||
net_device: {
|
||||
type: "virtio",
|
||||
relay_url: "fetch",
|
||||
}
|
||||
```
|
||||
|
||||
This uses the browser/Node.js `fetch()` API for HTTP requests. Limitations:
|
||||
- Only HTTP/HTTPS (no raw TCP/UDP)
|
||||
- No WebSocket
|
||||
- Host access via `http://<port>.external` (e.g., `http://8080.external`)
|
||||
|
||||
## Files Reference
|
||||
|
||||
After building, you need these files:
|
||||
|
||||
```
|
||||
project/
|
||||
├── node_modules/v86/build/
|
||||
│ ├── v86.wasm
|
||||
│ └── libv86.mjs
|
||||
├── bios/
|
||||
│ ├── seabios.bin
|
||||
│ └── vgabios.bin
|
||||
├── images/
|
||||
│ ├── alpine-fs.json
|
||||
│ └── alpine-rootfs-flat/
|
||||
│ └── *.bin.zst (many files)
|
||||
└── rootlessRelay/
|
||||
└── relay.js (patched)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [v86 GitHub](https://github.com/copy/v86)
|
||||
- [v86 Networking Docs](https://github.com/copy/v86/blob/master/docs/networking.md)
|
||||
- [v86 Alpine Setup](https://github.com/copy/v86/tree/master/tools/docker/alpine)
|
||||
- [rootlessRelay](https://github.com/obegron/rootlessRelay)
|
||||
- [v86 npm package](https://www.npmjs.com/package/v86)
|
||||
Loading…
Add table
Add a link
Reference in a new issue