
Лёгкие и безопасные Linux-песочницы для недоверенных процессов. Работают в браузере и на сервере.
<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>
[Живое демо](https://browser.vpod.sh) • [Начало работы](#getting-started) • [Документация](https://docs.vpod.sh/quickstart) • [Issues](https://github.com/capsulerun/vpod/issues/new) • [Участие в разработке](#contributing)
</div>
## Что такое `vpod`?
`vpod` — это лёгкая, переносимая песочница, которая предоставляет непроверенному процессу мгновенное Linux-окружение. Она использует архитектуру RISC‑V и полностью работает внутри WebAssembly.
- **Быстрый запуск**: загрузка менее чем за секунду.
- **Переносимость**: работает где угодно без какой-либо настройки.
- **Изоляция**: всё состояние выполнения остаётся внутри WASM-песочниц.
## Как это работает
`vpod` запускает полноценную RISC‑V систему (RV64GC, одно виртуальное ядро), скомпилированную в WebAssembly. Внутри загружается реальное ядро Linux с реальным пользовательским пространством, поэтому оболочки, инструменты и демоны ведут себя так же, как на реальном оборудовании.
**Снимки.** Вместо загрузки Linux с нуля `vpod` восстанавливает снимок: сохранённое состояние машины (регистры CPU, RAM, файловая система), захваченное сразу после загрузки. Восстановление занимает значительно меньше секунды. Приостановка работает аналогичным образом в обратном направлении: на диск записываются только изменённые страницы памяти, поэтому вы можете поставить песочницу на паузу и возобновить её позже, даже из другого процесса.
**Упреждающая трансляция.** Чистая эмуляция «инструкция за инструкцией» медленна, а WebAssembly исключает JIT во время выполнения. Поэтому на этапе сборки снимка самые горячие пути гостевого кода транслируются из RISC‑V в нативный код, который компилируется непосредственно в сам WASM-модуль. Во время выполнения эмулятор переключается на эти транслированные блоки, когда гостевой код совпадает, и возвращается к интерпретатору, когда нет. Это даёт примерно 5-кратное ускорение на задачах, интенсивно использующих CPU, без какого-либо влияния на изоляцию: транслированный код проходит через те же проверки MMU и памяти, что и интерпретируемый.
**Граница WASI.** WASM-компонент взаимодействует с хостом исключительно через WASI 0.2. Гость никогда не видит файловые дескрипторы, сокеты или память хоста: доступ к файловой системе осуществляется через явно смонтированные каталоги, а сеть — через пользовательский сетевой стек внутри компонента, который запрашивает у хоста только обычные исходящие сокеты. Всё остальное (гостевое ядро, процессы, память) находится внутри линейной памяти WASM и исчезает вместе с ней.
### Спецификация RV64GC
**G (расширения общего назначения)**
- **I**: Базовый набор 64-битных целочисленных инструкций.
- **M**: Аппаратное умножение и деление, полезно для хеширования и криптографии.
- **A**: Атомарные операции для потокобезопасных программ.
- **F/D**: Числа с плавающей запятой одинарной и двойной точности, подходят для научных вычислений и ML-инференса.
**C (сжатые инструкции)**
Уменьшает размер кода на 30%, улучшая скорость выборки инструкций и эффективность использования памяти. Это важно при запуске полного пользовательского пространства Linux в нашей ограниченной по памяти WASM-среде.
> [!NOTE]
> Векторное расширение V не реализовано. Инструкции RVV выполнялись бы как эмулируемый RISC-V; сквозной передачи SIMD на CPU хоста нет. Добавление V увеличило бы накладные расходы на эмуляцию без какого-либо выигрыша в производительности для векторизованных нагрузок.
## Начало работы
### TypeScript SDK
```bash
npm install @capsule-run/vpod
```
```ts
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// Состояние сохраняется между вызовами
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// Python REPL — переменные сохраняются
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();
```
Тот же пакет работает во вкладке браузера, где снимок кэшируется в приватном хранилище источника, а не на диске.
> [!IMPORTANT]
> Первый вызов `Sandbox.create()` загружает снимок по умолчанию (`alpine`) и кэширует его локально, если он ещё не присутствует.
### Python SDK
```bash
pip install vpod
```
```python
from vpod import Sandbox
# Выполнение команды
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Постоянная сессия — состояние сохраняется между вызовами
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# Python REPL — переменные сохраняются
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>Или установка через PowerShell (Windows)</summary>
>
> ```bash
> irm https://install.vpod.sh | iex
> ```
>
> </details>
```bash
# Загрузка снимка
vpod pull alpine:latest
# Запуск интерактивной оболочки
vpod
```
## Документация
Посетите [документацию Vpod](https://docs.vpod.sh/quickstart).
## Ограничения
- **Накладные расходы на эмуляцию**: внутри WebAssembly нет аппаратной виртуализации, поэтому весь гостевой код эмулируется. Накладные расходы полностью зависят от рабочей нагрузки: задачи, связанные с вводом-выводом и сетью, работают почти с нативной скоростью, тогда как тяжёлые задачи, интенсивно использующие CPU, работают заметно медленнее даже с AOT-трансляцией. Если ваша нагрузка в основном «запустить инструмент, прочитать файл, вызвать API», вы ничего не заметите.
- **Нет доступа к GPU**: CUDA, Metal и аппаратные ML-ускорители недоступны. Поддержка может быть добавлена в будущем с помощью wasi-nn.
## Участие в разработке
Приветствуются любые вклады — от сообщений об ошибках до поддержки новых устройств. Откройте [issue](https://github.com/capsulerun/vpod/issues/new), чтобы обсудить что-либо существенное перед реализацией.
### Предварительные требования
- **Rust** (последняя стабильная версия) с целью `wasm32-wasip2`: `rustup target add wasm32-wasip2`
- **Python 3.10+** для Python SDK
- **Node 20+** для TypeScript SDK
- **Zig** (0.16) и **bsdtar**, нужны только если вы собираете снимки самостоятельно
### Настройка среды разработки
```bash
# Однократно: генерация AOT-заглушки (в свежем клоне нет транслированных блоков)
./scripts/aot-stub.sh
# Сборка WASM-компонента (библиотека + CLI). Копирует оба уровня в sdks/python/vpod/
./scripts/build-wasm.sh
# Установка хостового CLI
cargo install --path crates/vpod
# Установка Python SDK в режиме разработки
pip install -e "sdks/python[dev]"
# Сборка TypeScript SDK. Подхватывает компонент из каталога Python SDK
cd sdks/typescript && npm install && npm run build
```
`npm run build` по умолчанию использует `--tier aot`; CI фиксирует `--tier base`. Сборка
отказывается использовать компонент старше самого нового файла в `crates/`, поэтому
перезапускайте `./scripts/build-wasm.sh` после изменений в эмуляторе. Изменение эмулятора
проявляется только через гостя, поэтому устаревший компонент компилируется и проходит
почти все тесты.
### Запуск тестов
CI запускает их на каждом PR, поэтому запускайте их перед отправкой:
```bash
cargo fmt --all -- --check # форматирование
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # тесты Rust
# Интеграционные тесты Python SDK (требуется WASM-библиотека на месте)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# TypeScript SDK (из sdks/typescript)
npm run typecheck
npm test # модульные
npm run test:all # модульные + интеграционные, требуется локальный снимок
npm run test:perf # регрессии гостевого времени, точные константы
```
Тесты TypeScript импортируют собранный `dist/`, а не `src/`, поэтому сначала выполните
сборку. Они ищут снимок в общем каталоге кэша;
`VPOD_TEST_SNAPSHOT=/path/to/x.snap` указывает на другое место.
Для сквозного тестирования браузера `npm run dev` обслуживает страницу с включёнными COOP/COEP,
а `node dev/run-network.mjs --browser chrome` управляет ею в headless-режиме.
### Использование локально собранного снимка
SDK по умолчанию загружают из `registry.vpod.sh`. Чтобы использовать снимок, собранный
самостоятельно, передайте его напрямую вместо имени реестра:
```ts
// TypeScript: файл на диске (Node) или байты (где угодно)
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
```
В любом случае указывайте размер RAM в имени файла, поскольку эмулятор считывает его
оттуда.
### Сборка снимков
Проект использует предварительно собранные снимки Alpine из `registry.vpod.sh`, поэтому
обычно это не требуется. Для локальной сборки:
```bash
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # вариант 512 МБ с numpy/pandas/scipy
```
> [!TIP]
> Чтобы использовать локально собранный снимок в CLI, раскомментируйте строки в `resolve_snapshot()` в `crates/vpod/src/main.rs`.
Сборка снимков также может выполнять AOT-проход (`scripts/aot-snapshot.sh <snapshot>`), который трассирует репрезентативную рабочую нагрузку, транслирует горячие блоки и пересобирает эмулятор с ними. Это занимает время; заглушка из `aot-stub.sh` подходит для повседневной разработки — всё работает одинаково, просто медленнее.
#### Из Dockerfile (macOS и Linux)
Пользовательские снимки также можно собирать из **Dockerfile**. Сборщик использует
[CLI `container` от Apple](https://github.com/apple/container) на macOS и
Docker Buildx на Linux. Свежая установка macOS требует однократной настройки среды выполнения,
иначе сборка будет ждать сборщик, который никогда не запустится:
```bash
container system kernel set --recommended
container builder start
```
На Linux установите Docker с плагином Buildx и один раз зарегистрируйте эмуляцию riscv64,
если хост ещё не настроен для кросс-платформенных сборок:
```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
# опционально: --aot --trace-cmd '<горячая команда образа>' для встраивания AOT-блоков
```
Dockerfile собирается для `linux/riscv64` (BuildKit выполняет шаги RUN
под эмуляцией), его уплощённая корневая файловая система заменяет Alpine minirootfs,
а остальной конвейер идентичен: vpod overlay, загрузка,
`--snapshot-save`.
При экспорте сохраняется только файловая система. `ENV`, `CMD` и `ENTRYPOINT` из
конфигурации образа отбрасываются, поэтому сохраняйте переменные окружения через `/etc/profile.d/` в
шаге `RUN`. Автоматический тёплый запуск Python применяется для образов на основе musl,
в которых `python3` находится в `/usr/bin` или `/bin`.
### Pull requests
- Держите PR сфокусированными: одно изменение на PR.
- `fmt`, `clippy` и набор тестов должны проходить (CI проверяет все три).
- Если вы затрагиваете пути выполнения или памяти эмулятора, опишите, как вы проверили корректность (минимум — набор тестов; для тонких изменений хорошей проверкой будет загрузка и реальная рабочая нагрузка в госте).
## Лицензия
Этот проект лицензирован под **Apache License 2.0**.
Подробности см. в файле [LICENSE](https://github.com/capsulerun/vpod/blob/main/LICENSE).