Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

tractordev/wanix

A sandbox toolkit for the web, inspired by Plan 9

tractordev/wanix.json
{
"createdAt": "2023-10-15T21:30:55Z",
"defaultBranch": "main",
"description": "A sandbox toolkit for the web, inspired by Plan 9",
"fullName": "tractordev/wanix",
"homepage": "https://wanix.sh",
"language": "Go",
"name": "wanix",
"pushedAt": "2026-07-03T23:58:11Z",
"stargazersCount": 724,
"topics": [
"browser",
"development-environment",
"golang",
"operating-system",
"sandbox",
"web-development",
"webassembly"
],
"updatedAt": "2026-07-10T06:44:24Z",
"url": "https://github.com/tractordev/wanix"
}

Discord GitHub Sponsors

Wanix is an embeddable runtime that brings a Unix-like environment to the browser. Declare a <wanix-system>, bind files and archives into a namespace, run Wasm and JavaScript tasks, boot Linux in an x86 emulator, and wire up terminals and a VS Code workbench, all from HTML.

<wanix-system>
<wanix-bind type="file" dst="helloworld.wasm" unsrc="./helloworld.wasm"></wanix-bind>
<wanix-task cmd="helloworld.wasm" term start></wanix-task>
<wanix-term path="#task/1/term"></wanix-term>
</wanix-system>
  • Everything is a file. Processes, terminals, VMs, browser APIs, and storage are exposed through a unified namespace you compose with binds. The same idea as Plan 9, with improvements, in the browser.
  • Composable environments. Layer tar archives, fetch remote files, write inline scripts, and union directories to build exactly the filesystem your app needs.
  • Pluggable compute. Run Go/TinyGo (gojs), WASI Wasm, JavaScript workers, and x86 Linux (via v86) as tasks in the same namespace.
  • Isolation by design. Each task gets its own namespace. VMs export a guest namespace. Import remote namespaces over 9P.
  • Browser-native integration. OPFS persistence, DOM control, web workers, service workers, and fetch are first-class via the #web namespace.
  • No backend required. The runtime (wanix.min.js + wanix.wasm) runs entirely client-side. Host static assets on any CDN.
  • Progressive complexity. Start with a single Wasm binary and terminal. Add Linux VMs, a full IDE workbench, or cross-origin federation when you need them.
  • In-browser dev environments: edit/run code without a remote container.
  • Interactive demos and tutorials: embed reproducible sandboxes in docs, blog posts, and courseware.
  • Local-first apps: persist user data in OPFS, run logic in workers, and build your own platform.
  • Agent sandboxes: utilize browser sandboxing to isolate an agent environment you construct.
  • Personal compute: build your own computing environment / operating system.
<!DOCTYPE html>
<html>
<head>
<script type="module"
src="https://cdn.jsdelivr.net/npm/wanix@0.4.0-alpha8/dist/wanix.min.js">
</script>
</head>
<body style="height: 100vh; margin: 0;">
<wanix-system>
<!-- bind alloc ramfs to namespace root -->
<wanix-bind dst="." unsrc="#ramfs/new"></wanix-bind>
<!-- bind inline file into namespace -->
<wanix-bind type="file" dst="hello.sh" perm="0755">
echo "Hello from Wanix!"
</wanix-bind>
<!-- bind wasm executable from url -->
<wanix-bind type="file" dst="rc.wasm"
src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/rc.wasm">
</wanix-bind>
<!-- declare a task that will autostart -->
<wanix-task id="shell" cmd="rc.wasm -c hello.sh" term start></wanix-task>
<!-- show a terminal wired up to the task -->
<wanix-term path="#task/shell/term"></wanix-term>
</wanix-system>
</body>
</html>

After <wanix-system> fires a ready event, use the filesystem handle:

<script type="module">
const sys = document.querySelector('wanix-system');
sys.addEventListener('ready', async () => {
console.log(await sys.root.readDir('.'));
await sys.root.writeFile('note.txt', 'saved from JS');
});
</script>

Full API: [api/handle.js]!(api/handle.js).

Elements let you compose a Wanix system in HTML. The only visual elements are <wanix-term> and <wanix-workbench>.

The root namespace and Wasm kernel. All other elements live inside it (or reference it with for).

AttributeDescription
wasmURL to the Wanix Wasm module. Defaults to ./wanix.wasm next to the bundle.
debugEnable DevTools helpers and verbose logging.
idSystem identifier used for cross-origin import/export.
allow-originsSpace-separated origins allowed to import this system via postMessage (use * to allow all). Requires id.

Events: ready (namespace usable), error (load failure).

Mount a source into the namespace at dst.

AttributeDescription
dstDestination path. . is the namespace root. Paths do not start with /.
srcSource path or URL. System paths use # prefix (e.g. #ramfs, #web/opfs).
typeBind type (see below). Default: ns.
permFile permission mode for file binds. Default: 0644.
unionUnion mode when binding to an existing directory. Default: after.
TypeBehavior
nsBind another namespace path (default).
fileWrite element text content (or fetched URL if src is set) to dst.
archiveFetch a .tar or .tar.gz and mount as a directory tree.
importImport a remote Wanix namespace via WebSocket (ws:// / wss://) or iframe + 9P (src URL with #system-id).

Allocate and run a task, which is shaped like a process (args, env, stdio, …) and executed by a task driver. Tasks run in their own namespace, by default inheriting the current/root namespace.

AttributeDescription
cmdCommand line to run.
typeTask driver: auto, gojs, wasi, js, etc. Default: auto.
roleSemantic role. Use shell for workbench shell templates.
id / aliasOptional name for referencing the task at #task/<id>/….
envEnvironment variables, space-separated KEY=VALUE pairs (use quotes for values with spaces).
wdWorking directory within the task namespace.
fsysBase filesystem path for the task namespace.
stdin / stdout / stderrNamespace paths for I/O redirection.
termAllocate a terminal device for this task.
startStart the task automatically when the system is ready.
forID of a <wanix-system> to attach to (instead of being a direct child).

Terminal path after allocation: #task/<id>/term (or #task/<rid>/term without alias).

TypeBehavior
autoDetermine automatically (default).
jsRun plain JavaScript as a task.
gojsRun Wasm compiled by Go using GOOS=js GOARCH=wasm.
wasiRun any Wasm compiled using wasi/wasip1.

Allocate and run a virtual machine.

AttributeDescription
typeVM backend. Default: v86.
id / aliasOptional name. Terminal at #vm/<id>/term.
fsysRoot filesystem path in the namespace.
termAllocate a terminal for serial console I/O.
startBoot automatically when ready.
appendKernel command line additions.
exportHost-export device (e.g. ttyS0, hvc1) for guest ↔ host bridging.
memRAM size (e.g. 512M, 1G).
boot, bios, netdev, …Additional QEMU-style flags mapped from attribute names.

Using <wanix-vm> requires a VM backend to be loaded using bind to #vm/<type>:

<wanix-bind dst="#vm/v86" type="archive" src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/v86.tgz"></wanix-bind>

Render an xterm.js terminal connected to a Wanix terminal device.

AttributeDescription
pathTerminal device path (e.g. #term/1, #task/shell/term, #vm/1/term).
rawRaw mode — no local line editing; bytes pass through directly. Use for VM serial consoles.
forID of a <wanix-system> to attach to.

Style the element with height: 100% (and flex layout on parents) for full-page terminals.

Embed a VS Code web workbench backed by the Wanix filesystem.

AttributeDescription
assetsURL prefix for workbench static assets (built with make -C workbench).
wdWorkspace folder path in the namespace (e.g. root, .).
openSpace-separated file paths to open on startup.
termEnable integrated terminal panel.
rawRaw terminal mode for integrated terminal.
sidebarInitial sidebar state: default, hidden, or never (hidden even if user previously opened).
welcomeShow welcome page on startup.
debugVerbose workbench logging.
task-ns, term-nsOverride task/terminal namespace paths (e.g. for VM guest shells).

Include a child <wanix-task role="shell" …> as the shell template.

These # paths are provided by the kernel and can be bound into your namespace:

PathDescription
#taskProcess control and task namespaces.
#termTerminal devices.
#vmVirtual machine control.
#ramfsIn-memory filesystem (cloned per bind).
#pipePipe pairs (cloned per bind).
#signalSignal devices (cloned per bind).
#webBrowser integration — OPFS (#web/opfs), DOM, workers, caches, etc.
#wanixInternal Wanix devices.

Run a Go/TinyGo Wasm binary with a terminal:

<wanix-system>
<wanix-bind type="file" dst="app.wasm" src="https://example.com/app.wasm"></wanix-bind>
<wanix-task id="app" cmd="app.wasm" term start></wanix-task>
<wanix-term path="#task/app/term"></wanix-term>
</wanix-system>

See [examples/basic-terminal.html]!(examples/basic-terminal.html).

Create a virtual filesystem to use via JS:

<wanix-system>
<wanix-bind dst="." unsrc="#ramfs/new"></wanix-bind>
<wanix-bind dst="greeting.txt" type="file" perm="0644">
Hello, world!
</wanix-bind>
</wanix-system>

See [examples/basic-namespace.html]!(examples/basic-namespace.html).

Run inline JS in a Wanix task:

<wanix-system>
<wanix-bind dst="." unsrc="#ramfs/new"></wanix-bind>
<wanix-bind dst="task.js" type="file" perm="0766">
console.log("JS task running!");
</wanix-bind>
<wanix-task cmd="task.js" start></wanix-task>
</wanix-system>

See [examples/task-js.html]!(examples/task-js.html).

Stack archives and overlay individual files — later binds win:

<wanix-system>
<wanix-bind type="archive" dst="root"
src="https://example.com/base-rootfs.tar.gz"></wanix-bind>
<wanix-bind type="archive" dst="root"
src="https://example.com/overlay.tar.gz"></wanix-bind>
<wanix-bind type="file" dst="root/boot/bzImage"
src="https://example.com/custom-kernel"></wanix-bind>
</wanix-system>

See [examples/bind-types.html]!(examples/bind-types.html).

<wanix-system>
<wanix-bind dst="." type="archive"
src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/wanix-linux.tgz">
</wanix-bind>
<wanix-bind dst="#vm/v86" type="archive"
src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/v86.tgz">
</wanix-bind>
<wanix-vm export="ttyS0" mem="1G" term start></wanix-vm>
<wanix-term path="#vm/1/term" raw></wanix-term>
</wanix-system>

See [examples/basic-vm.html]!(examples/basic-vm.html).

Host workbench assets locally (make -C workbench), then:

<wanix-system debug>
<wanix-bind type="archive" dst="root"
src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/wanix-linux.tgz">
</wanix-bind>
<wanix-bind type="fetch" dst="rc.wasm"
src="https://cdn.jsdelivr.net/npm/wanix-extras@0.4.0-rc1/dist/rc.wasm">
</wanix-bind>
<wanix-workbench assets="/workbench" term>
<wanix-task role="shell" cmd="rc.wasm"></wanix-task>
</wanix-workbench>
</wanix-system>

See [examples/basic-workbench.html]!(examples/basic-workbench.html).

Edit files on the host namespace while running shells inside a Linux VM:

<wanix-system debug>
<wanix-bind dst="." type="archive" unsrc="/assets/wanix-linux.tgz"></wanix-bind>
<wanix-bind dst="#vm/v86" type="archive" unsrc="/assets/v86.tgz"></wanix-bind>
<wanix-vm export="ttyS0" start></wanix-vm>
<wanix-workbench assets="/workbench"
task-ns="#vm/1/guest/#task"
term-ns="#vm/1/guest/#term"
raw term>
<wanix-task role="shell" cmd="bin/sh"></wanix-task>
</wanix-workbench>
</wanix-system>

See [examples/vm-workbench.html]!(examples/vm-workbench.html).

Persist files in the browser with Origin Private File System:

<wanix-system>
<wanix-bind dst="." unsrc="#web/opfs"></wanix-bind>
<wanix-bind dst="main.js" type="file" perm="0644">
export default function() { return 42; }
</wanix-bind>
<wanix-workbench open="main.js" assets="/workbench"></wanix-workbench>
</wanix-system>

Export a namespace from one page:

<wanix-system id="main" allow-origins="*">
<wanix-bind dst="." unsrc="#ramfs/new"></wanix-bind>
<wanix-bind dst="shared.txt" type="file">shared data</wanix-bind>
</wanix-system>

Import it from another page:

<wanix-system>
<wanix-bind type="import" dst="remote"
src="https://other.example/app.html#main"></wanix-bind>
<wanix-task id="repl" cmd="rc.wasm" term start></wanix-task>
<wanix-term path="#task/repl/term"></wanix-term>
</wanix-system>

Import over WebSocket 9P:

<wanix-bind type="import" dst="home" unsrc="wss://example.com/9p"></wanix-bind>

See [examples/example-export.html]!(examples/example-export.html) and [examples/bind-import.html]!(examples/bind-import.html).

Import a VM running on another origin and attach a workbench to its guest namespace:

<wanix-system debug>
<wanix-bind type="import" dst="remote"
src="https://vm-host.example/linux.html#linux"></wanix-bind>
<wanix-workbench assets="/workbench"
task-ns="remote/vm/1/guest/#task"
term-ns="remote/vm/1/guest/#term"
raw term>
<wanix-task role="shell" cmd="bin/sh"></wanix-task>
</wanix-workbench>
</wanix-system>

See [examples/import-workbench.html]!(examples/import-workbench.html).

See all [examples]!(examples).

Terminal window
make build # build runtime + wanix CLI
make examples # serve examples at http://localhost:7070/examples
make # show all make tasks

Using make examples will build extras the first time, and will need Docker running to succeed. Podman users will need to set DOCKER_CMD=podman.

See [CONTRIBUTING.md]!(CONTRIBUTING.md) for the full build guide.

We’d love your contributions! Take a look at our issues to see how you can help out. You can also ask questions and participate in discussions, however right now most discussion takes place in our Discord.

Be sure to read our [CONTRIBUTING.md]!(CONTRIBUTING.md) doc to get started.

There are components of Wanix that have been written with AI assistance. However, we will not accept “vibe coded” PRs, which is to say a human needs to know how the PR works and be responsible for it.

MIT