
Sandbox Linux leggere e sicure per processi non attendibili. Funzionano nel browser e sul server.
<h1 align="center"> <code>Vpod</code> </h1>
<div align="center">
<a href="https://github.com/capsulerun/vpod/actions/workflows/ci.yml" target="_blank">
<img src="https://img.shields.io/github/actions/workflow/status/capsulerun/vpod/ci.yml?branch=main&label=CI&logo=github" alt="CI">
</a>
<a href="https://riscv.org/specifications/ratified/"><img src="https://img.shields.io/badge/RISCV-RV64GC-orange?logo=RISCV" alt="Risc-V"></a>
<a href="https://wasi.dev/"><img src="https://img.shields.io/badge/Wasm/WASI-0.2.0-654FF0?logo=webassembly&logoColor=white" alt="Wasm/WASI 0.2 Sandbox"></a>
[Demo live](https://browser.vpod.sh) • [Per iniziare](#getting-started) • [Documentazione](https://docs.vpod.sh/quickstart) • [Problemi](https://github.com/capsulerun/vpod/issues/new) • [Contribuire](#contributing)
</div>
## Cos'è un `vpod` ?
Un `vpod` è una sandbox leggera e portabile che offre a un processo non fidato un ambiente Linux immediato. Utilizza un'architettura RISC‑V e gira interamente all'interno di WebAssembly.
- **Avvio rapido** : Si avvia in meno di un secondo.
- **Portabile** : Funziona ovunque senza alcuna configurazione richiesta.
- **Isolato** : Tutto lo stato di esecuzione rimane all'interno delle sandbox WASM.
## Come funziona
Un `vpod` esegue un sistema RISC‑V completo (RV64GC, singola vCPU) compilato in WebAssembly. Al suo interno avvia un vero kernel Linux con un vero userspace, quindi shell, strumenti e demoni si comportano come farebbero su hardware reale.
**Snapshot.** Invece di avviare Linux da zero, un `vpod` ripristina uno snapshot: uno stato della macchina salvato (registri CPU, RAM, filesystem) catturato subito dopo l'avvio. Ripristinarne uno richiede meno di un secondo. La sospensione funziona allo stesso modo al contrario: solo le pagine di memoria sporche vengono riscritte su disco, così puoi mettere in pausa una sandbox e riprenderla in seguito, anche da un altro processo.
**Traduzione ahead-of-time.** La pura emulazione istruzione-per-istruzione è lenta e WebAssembly esclude un JIT a runtime. Quindi, al momento della creazione dello snapshot, i percorsi di codice guest più caldi vengono tradotti da RISC‑V in codice nativo che viene compilato nel modulo WASM stesso. A runtime l'emulatore esegue il dispatch in questi blocchi tradotti quando il codice guest corrisponde e ripiega sull'interprete quando non corrisponde. Questo vale circa 5x sul lavoro CPU-bound, con zero effetti sull'isolamento: il codice tradotto passa attraverso gli stessi controlli MMU e di memoria del codice interpretato.
**Il confine WASI.** Il componente WASM comunica con l'host esclusivamente tramite WASI 0.2. Il guest non vede mai descrittori di file, socket o memoria dell'host: l'accesso al filesystem passa attraverso directory montate esplicitamente e la rete passa attraverso uno stack di rete user-mode all'interno del componente che chiede all'host solo socket outbound semplici. Tutto il resto (kernel guest, processi, memoria) vive nella memoria lineare WASM e muore con essa.
### Specifica RV64GC
**G (Estensioni per scopi generali)**
- **I** : Set di istruzioni intere a 64 bit di base.
- **M** : Moltiplicazione e divisione hardware, utili per hashing e crittografia.
- **A** : Operazioni atomiche per programmi thread-safe.
- **F/D** : Virgola mobile a precisione singola e doppia, adatta al calcolo scientifico e all'inferenza ML.
**C (Istruzioni compresse)**
Riduce la dimensione del codice del 30%, migliorando la velocità di fetch delle istruzioni e l'efficienza della memoria. Questo è importante quando si esegue un userspace Linux completo nel nostro ambiente WASM con memoria limitata.
> [!NOTE]
> L'estensione V (vettoriale) non è implementata. Le istruzioni RVV verrebbero eseguite come RISC-V emulato; non c'è pass-through SIMD alla CPU host. Aggiungere V aumenterebbe l'overhead di emulazione senza alcun beneficio prestazionale per i carichi di lavoro vettorializzati.
## Per iniziare
### SDK TypeScript
```bash
npm install @capsule-run/vpod
```
```ts
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// Lo stato è preservato tra le chiamate
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// REPL Python — le variabili persistono
await sandbox.code.run("data = [1, 2, 3]");
const total = await sandbox.code.run("print(sum(data))");
console.log(total.text); // 6
await sandbox.close();
```
Lo stesso pacchetto funziona in una scheda del browser, dove lo snapshot viene memorizzato nella cache di storage origin-private invece che su disco.
> [!IMPORTANT]
> La prima chiamata a `Sandbox.create()` scarica lo snapshot predefinito (`alpine`) e lo memorizza nella cache locale se non è già presente.
### SDK Python
```bash
pip install vpod
```
```python
from vpod import Sandbox
# Esegui un comando
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Sessione persistente — stato preservato tra le chiamate
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# REPL Python — le variabili persistono
with Sandbox.create() as sandbox:
sandbox.code.run("import requests")
sandbox.code.run("data = [1, 2, 3]")
result = sandbox.code.run("print(sum(data))")
print(result.text) # 6
```
### CLI
```bash
curl -fsSL https://install.vpod.sh | sh
```
> <details>
> <summary>Oppure installa tramite PowerShell (Windows)</summary>
>
> ```bash
> irm https://install.vpod.sh | iex
> ```
>
> </details>
```bash
# Scarica uno snapshot
vpod pull alpine:latest
# Avvia una shell interattiva
vpod
```
## Documentazione
Visita la [documentazione di Vpod](https://docs.vpod.sh/quickstart).
## Limitazioni
- **Overhead di emulazione**: Non c'è virtualizzazione hardware all'interno di WebAssembly, quindi tutto il codice guest è emulato. L'overhead dipende interamente dal carico di lavoro: il lavoro I/O-bound e network-bound gira quasi alla velocità nativa, mentre il lavoro pesante CPU-bound gira notevolmente più lento anche con la traduzione AOT. Se il tuo carico di lavoro è principalmente "esegui uno strumento, leggi un file, chiama un'API", non te ne accorgerai.
- **Nessun accesso GPU**: CUDA, Metal e acceleratori ML hardware non sono disponibili. Il supporto potrebbe essere aggiunto in futuro con wasi-nn.
## Contribuire
I contributi sono benvenuti, dai report di bug al supporto di nuovi dispositivi. Apri un [issue](https://github.com/capsulerun/vpod/issues/new) per discutere qualsiasi cosa sostanziale prima di realizzarla.
### Prerequisiti
- **Rust** (ultima versione stabile) con il target `wasm32-wasip2`: `rustup target add wasm32-wasip2`
- **Python 3.10+** per l'SDK Python
- **Node 20+** per l'SDK TypeScript
- **Zig** (0.16) e **bsdtar**, necessari solo se crei gli snapshot da solo
### Configurazione di sviluppo
```bash
# Una tantum: genera lo stub AOT (un clone fresco non ha blocchi tradotti)
./scripts/aot-stub.sh
# Compila il componente WASM (libreria + CLI). Copia entrambi i livelli in sdks/python/vpod/
./scripts/build-wasm.sh
# Installa la CLI host
cargo install --path crates/vpod
# Installa l'SDK Python in modalità dev
pip install -e "sdks/python[dev]"
# Compila l'SDK TypeScript. Preleva il componente dalla directory dell'SDK Python
cd sdks/typescript && npm install && npm run build
```
`npm run build` usa come predefinito `--tier aot`; CI fissa `--tier base`. La build
rifiuta un componente più vecchio del file più recente sotto `crates/`, quindi riesegui
`./scripts/build-wasm.sh` dopo aver toccato l'emulatore. Una modifica all'emulatore si
manifesta solo attraverso il guest, quindi un componente obsoleto compila e supera quasi
tutto.
### Esecuzione dei test
CI esegue questi test su ogni PR, quindi eseguili prima di fare push:
```bash
cargo fmt --all -- --check # formattazione
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # test Rust
# Test di integrazione SDK Python (richiede la libreria WASM in posizione)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# SDK TypeScript (da sdks/typescript)
npm run typecheck
npm test # unità
npm run test:all # unità + integrazione, richiede uno snapshot locale
npm run test:perf # regressioni del tempo guest, costanti esatte
```
I test TypeScript importano la `dist/` compilata, non `src/`, quindi compila prima di
eseguirli. Cercano uno snapshot nella directory cache condivisa;
`VPOD_TEST_SNAPSHOT=/path/to/x.snap` li indirizza altrove.
Per esercitare il browser end-to-end, `npm run dev` serve la pagina con COOP/COEP
attivi e `node dev/run-network.mjs --browser chrome` la guida in modalità headless.
### Utilizzo di uno snapshot compilato localmente
Gli SDK scaricano da `registry.vpod.sh` per impostazione predefinita. Per eseguirne uno compilato da te,
passalo direttamente invece di un nome di registry:
```ts
// TypeScript: un file su disco (Node), o byte (ovunque)
await Sandbox.create({ snapshot: { path: "./dist/alpine-3.23.0-256mb.snap" } });
await Sandbox.create({ snapshot: { bytes, name: "alpine-3.23.0-256mb.snap" } });
```
```python
# Python: VPOD_SNAPSHOT=/path/to/x.snap
```
Mantieni la dimensione della RAM nel nome del file in ogni caso, perché l'emulatore
la legge da lì.
### Creazione di snapshot
Il progetto usa snapshot Alpine precompilati da `registry.vpod.sh`, quindi normalmente non ne hai bisogno. Per crearne uno localmente:
```bash
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # variante da 512 MB con numpy/pandas/scipy
```
> [!TIP]
> Per usare uno snapshot compilato localmente nella CLI, decommenta le righe in `resolve_snapshot()` in `crates/vpod/src/main.rs`.
Le build di snapshot possono anche eseguire il passaggio AOT (`scripts/aot-snapshot.sh <snapshot>`), che traccia un carico di lavoro rappresentativo, traduce i blocchi caldi e ricompila l'emulatore con essi incorporati. Richiede tempo; lo stub di `aot-stub.sh` è sufficiente per lo sviluppo quotidiano, tutto funziona allo stesso modo, solo più lentamente.
#### Da un Dockerfile (macOS e Linux)
Gli snapshot personalizzati possono anche essere creati da un **Dockerfile**. Il builder usa
il [CLI `container` di Apple](https://github.com/apple/container) su macOS e
Docker Buildx su Linux. Una nuova installazione macOS richiede la configurazione del runtime una volta,
altrimenti la build attende un builder che non si avvia mai:
```bash
container system kernel set --recommended
container builder start
```
Su Linux, installa Docker con il plugin Buildx e registra l'emulazione riscv64
una volta se l'host non è già configurato per build cross-platform:
```bash
docker run --privileged --rm tonistiigi/binfmt --install riscv64
```
```bash
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# opzionalmente: --aot --trace-cmd '<il comando caldo dell'immagine>' per incorporare i blocchi AOT
```
Il Dockerfile viene compilato per `linux/riscv64` (BuildKit esegue i passaggi RUN
sotto emulazione), il suo rootfs appiattito sostituisce il minirootfs Alpine,
e il resto della pipeline è identico: overlay vpod, avvio,
`--snapshot-save`.
Solo il filesystem sopravvive all'esportazione. `ENV`, `CMD` e `ENTRYPOINT` dalla
configurazione dell'immagine vengono scartati, quindi persisti l'ambiente tramite `/etc/profile.d/` in
un passaggio `RUN`. Il warm-start Python viene applicato automaticamente per le immagini basate su musl
che includono `python3` in `/usr/bin` o `/bin`.
### Pull request
- Mantieni le PR focalizzate: una modifica per PR.
- `fmt`, `clippy` e la suite di test devono passare (CI impone tutti e tre).
- Se tocchi i percorsi di esecuzione o memoria dell'emulatore, spiega come hai validato la correttezza (la suite di test come minimo; per modifiche sottili un avvio più un carico di lavoro reale nel guest è un buon controllo di sanità).
## Licenza
Questo progetto è concesso in licenza sotto la **Apache License 2.0**.
Consulta il file [LICENSE](https://github.com/capsulerun/vpod/blob/main/LICENSE) per i dettagli.