Fold a computer into a URL. Kandelo is a POSIX-compatible multi-process kernel for WebAssembly that lets you compile C programs against a real musl libc and run them in the browser or Node.js with syscall-level compatibility.
Live demo: Open Kandelo in the browser
ATTENTION: This repo may contain .wasm binary builds in its history. In the future, history will likely be rewritten to remove these as they are offloaded to a better data store.
Real, unmodified software compiled to WebAssembly:
| Software | Version | Notes |
|---|---|---|
| nginx | 1.27 | Static serving, reverse proxy, FastCGI, multi-worker fork |
| PHP | 8.4 | CLI + PHP-FPM, FastCGI protocol |
| MariaDB | 10.5 | SQL database, Aria storage engine, 5 threads |
| Redis | 7.2 | In-memory store, 3 background threads |
| WordPress | 6.7 | Full CMS: nginx + PHP-FPM + SQLite or MariaDB |
| CPython | 3.13 | REPL, script execution, stdlib |
| Git | 2.47 | Core version control operations |
| Vim | 9.1 | Full editor with ncurses terminal UI |
| NetHack | 3.6.7 | Classic roguelike with curses UI |
| fbDOOM | (maximevince) | id Software's DOOM via the kernel's /dev/fb0 Linux fbdev surface |
| Perl | 5.40 | Interpreter with core modules |
| Ruby | 3.3 | Interpreter with core stdlib |
| QuickJS-NG | 0.12 | ES2023 JavaScript engine + Node.js compat layer (node:crypto hash + HMAC via OpenSSL, node:zlib deflate/inflate/gzip/gunzip via libz, native JSON.parse via yyjson). npm install works for lodash, express, and vite. |
| GNU nano | 8.3 | Terminal text editor |
| dash | 0.5.12 | POSIX shell with pipes, redirects, job control |
| GNU coreutils | 9.6 | 50+ utilities (ls, cat, sort, wc, etc.) |
| GNU grep | 3.11 | Regular expression search |
| GNU sed | 4.9 | Stream editor |
| GNU make | 4.4 | Build automation |
| curl | 8.11 | HTTP client with TLS |
| wget | 1.25 | HTTP file retrieval |
| gawk | 5.3 | Pattern scanning and processing |
| GNU findutils | 4.10 | find, xargs |
| GNU diffutils | 3.10 | diff, cmp |
| tar | 1.35 | Archive utility |
| gzip, bzip2, xz, zstd | — | Compression utilities |
| less | 668 | Terminal pager |
| bc | 1.07 | Calculator |
| m4 | 1.4.19 | Macro processor |
| file | 5.46 | File type identification |
All run in both Node.js and the browser with no source modifications.
A centralized kernel serves all processes via channel IPC (SharedArrayBuffer + Atomics):
┌─────────────────────────────────────────┐
│ User Programs (C → Wasm) │
│ Each in its own Web Worker │
│ Linked against musl libc + glue │
├─────────────────────────────────────────┤
│ Kernel (Rust → Wasm) │
│ One instance, all processes │
│ Syscalls, fd table, pipes, signals, │
│ sockets, PTY, memory management │
├─────────────────────────────────────────┤
│ Host Runtime (TypeScript) │
│ Node.js: fs, net, crypto │
│ Browser: SharedArrayBuffer FS, fetch │
└─────────────────────────────────────────┘
- Kernel (Rust → Wasm) — 170+ syscall implementations. One instance manages all processes via a
ProcessTable. - Host (TypeScript) — Loads kernel and user Wasm binaries, provides host I/O, bridges blocking syscalls to async APIs via
Atomics.waitAsync. - Glue (C) — Syscall dispatcher compiled into every user program. Translates musl's
__syscallABI into channel writes that the kernel reads.
See docs/architecture.md for the full architecture reference.
170+ POSIX syscalls across these subsystems:
| Subsystem | Highlights |
|---|---|
| File I/O | open, close, read, write, seek, dup/dup2/dup3, pipe, readv/writev, pread/pwrite, sendfile, ftruncate, fsync, copy_file_range, splice, statx |
| fcntl | Advisory locking (F_GETLK/F_SETLK/F_SETLKW), file flags, FD_CLOEXEC, cross-process locks |
| Process | fork (Asyncify), exec, posix_spawn, exit, getpid/getppid, process groups, sessions, waitpid |
| Threads | clone with CLONE_VM|CLONE_THREAD, per-thread TLS and channels |
| Signals | kill, sigaction (SA_SIGINFO), sigprocmask, sigsuspend, sigaltstack, alarm, setitimer/getitimer, RT signals, sigqueue, sigtimedwait, signalfd |
| Memory | mmap (MAP_ANONYMOUS + MAP_PRIVATE file + MAP_SHARED file), munmap, mremap, brk/sbrk, memfd_create |
| Networking | AF_INET sockets, AF_UNIX sockets, connect, send/recv, sendmsg/recvmsg, SCM_RIGHTS |
| Directories | opendir/readdir, mkdir, rmdir, rename, symlink, readlink, chmod, chown, statvfs, all *at() variants |
| Time | clock_gettime, gettimeofday, nanosleep, utimensat, timer_create/settime/gettime/delete |
| Terminal | Full PTY support (/dev/ptmx + /dev/pts/N), line discipline, canonical/raw mode, 16 terminal ioctls |
| Virtual devices | /dev/null, /dev/zero, /dev/urandom, /dev/full, /dev/fd/N, /dev/tty, /dev/ptmx, /dev/pts/* |
| Procfs | /proc/self, /proc/<pid>/stat, status, cmdline, environ, maps, fd/*, /proc/net/tcp, unix |
| IPC | SysV msg queues, semaphores, shared memory; POSIX mqueues |
| Event/Notification | eventfd, timerfd, signalfd |
| Poll/Select | poll, ppoll, pselect6, epoll (host-intercepted in browser) |
See docs/posix-status.md for the full syscall-by-syscall status.
- Rust nightly (for
build-stdand atomics) — pinned viarust-toolchain.toml - LLVM 21+ with
clangandwasm-ld(macOS:brew install llvm) - Node.js 22+
Or use the Nix flake (see Using Nix below) and skip per-tool installs.
A flake.nix provides a reproducible dev shell with the pinned Rust nightly,
LLVM 21, Node 22, Erlang 28, and the autotools/cmake/binaryen/wabt stack the
build scripts need. With Nix installed
(flakes enabled — Determinate Systems Nix has them on by default):
nix develop # interactive shell
# or
nix develop -c bash build.sh # one-shotThe shellHook exports LLVM_BIN / LLVM_PREFIX so the build scripts
pick up the Nix-provided LLVM 21 instead of looking for a Homebrew install.
The first nix develop downloads the toolchain (~10–15 min); subsequent
entries are near-instant.
git submodule update --init musl
# Build musl sysroot (first time only)
bash scripts/build-musl.sh
# Build kernel Wasm + TypeScript host
bash build.shThis builds the kernel from source. Library dependencies (zlib, openssl,
sqlite, libcxx, etc.) and ported programs (vim, git, php, etc.) are resolved
on demand by cargo xtask build-deps resolve <name>, which prefers
the per-user cache, then falls back to the published binary release at
binaries-abi-v<ABI_VERSION>,
then to a source build via the per-library build-<name>.sh. See
docs/package-management.md for the
full schema, resolution order, and release-archive contract.
If you prefer to skip cargo-driven dep resolution and pull every
pre-built artifact at once, run bash scripts/fetch-binaries.sh after
bash build.sh. It walks every examples/libs/<pkg>/package.toml
with a [binary.<arch>] block and resolves the archives into the
content-addressed cache plus binaries/programs/<arch>/ symlinks.
If you are editing a package's package.toml to iterate locally, the
resolver detects the cache-key mismatch, logs a warning, and falls
through to a source build via the package's build-<name>.sh —
no flag needed. See Iterating on a package
locally.
cd sdk && npm linkThis installs 8 CLI tools that wrap LLVM for the wasm32-posix target:
| Tool | Purpose |
|---|---|
wasm32posix-cc |
C compiler |
wasm32posix-c++ |
C++ compiler |
wasm32posix-ar |
Static archive tool |
wasm32posix-ranlib |
Archive index generator |
wasm32posix-nm |
Symbol lister |
wasm32posix-strip |
Symbol stripper (no-op) |
wasm32posix-pkg-config |
pkg-config with sysroot awareness |
wasm32posix-configure |
Autoconf configure wrapper |
See docs/sdk-guide.md for detailed SDK usage.
wasm32posix-cc examples/hello.c -o hello.wasm
npx tsx examples/run-example.ts hello# Build VFS images + start dev server (run.sh handles dependencies)
./run.sh browser
# Or manually:
cd examples/browser
npm install
npx vite --port 5198Open http://localhost:5198 to try 16 interactive demos — C programs, interactive shell, Python/Perl/Ruby/Node REPLs (including in-browser npm install), nginx, MariaDB, Redis, full WordPress, a LAMP stack, TeX Live, and DOOM — all running in the browser.
Browser demos use pre-built VFS images — binary filesystem snapshots that load instantly at runtime. See docs/browser-support.md for details.
Build scripts for all ported software are in examples/libs/:
bash examples/libs/dash/build-dash.sh # dash shell
bash examples/libs/coreutils/build-coreutils.sh # GNU coreutils
bash examples/libs/php/build-php.sh # PHP 8.4
bash examples/libs/redis/build-redis.sh # Redis 7.2
bash examples/libs/mariadb/build-mariadb.sh # MariaDB 10.5
bash examples/libs/cpython/build-cpython.sh # CPython 3.13
bash examples/libs/git/build-git.sh # Git 2.47
bash examples/libs/vim/build-vim.sh # Vim 9.1
bash examples/libs/perl/build-perl.sh # Perl 5.40
bash examples/libs/ruby/build-ruby.sh # Ruby 3.3
bash examples/libs/quickjs/build-quickjs.sh # QuickJS-NG + Node.js compat
bash examples/libs/nano/build-nano.sh # GNU nano 8.3
bash examples/libs/curl/build-curl.sh # curl
bash examples/libs/make/build-make.sh # GNU makeSee docs/porting-guide.md for how to port your own software.
# Kernel unit tests (700 tests)
cargo test -p wasm-posix-kernel --target aarch64-apple-darwin --lib
# Host integration tests (276 tests)
cd host && npx vitest run
# musl libc-test suite (0 unexpected failures)
scripts/run-libc-tests.sh
# Open POSIX test suite (0 failures)
scripts/run-posix-tests.sh
# Sortix test suite (4817+ pass, 0 failures)
scripts/run-sortix-tests.sh --allcrates/
shared/ Shared types (Errno, syscall numbers, flags, channel layout)
kernel/ Kernel implementation (syscalls, fd table, signals, pipes, sockets, PTY)
userspace/ User-space stub library
host/
src/ TypeScript host runtime (kernel loader, VFS, networking, workers)
test/ Vitest integration tests
wasm/ Compiled Wasm binaries
sdk/
src/bin/ CLI tool wrappers for LLVM cross-compilation
src/lib/ Toolchain discovery, compiler flags, arg parsing
glue/
channel_syscall.c Channel-based syscall dispatcher (compiled into every user program)
compiler_rt.c Soft-float and 64-bit compiler runtime builtins
musl/ musl libc (git submodule)
musl-overlay/ Wasm32-specific architecture patches for musl
scripts/ Build scripts, test runners (libc-test, POSIX, Sortix)
examples/
*.c / *.wasm Simple C example programs
browser/ Browser demo app (Vite + 15 demo pages)
libs/ Build scripts for ported software (36 packages)
docs/
architecture.md Architecture reference
sdk-guide.md SDK usage guide
porting-guide.md Guide to porting software and creating demos
browser-support.md Browser capabilities and limitations
posix-status.md Full syscall-by-syscall status tracker
wasm-limitations.md Fundamental WebAssembly platform limitations
| Document | Description |
|---|---|
| Architecture | Kernel design, syscall flow, multi-process model, memory layout |
| SDK Guide | Compiling programs, toolchain setup, autoconf/CMake integration |
| Porting Guide | How to port software, create Node.js and browser demos |
| Browser Support | Browser architecture, capabilities, demo list, limitations |
| Shareable Computer URLs | Boot descriptor design for sharing computer topology, signed bases/packages, mounts, and overlays |
| Package Management | examples/libs/<name>/package.toml schema, resolver, release archives |
| Package Management — Future Work | Deferred items: WASI caching, semver, multi-arch [binary], etc. |
| Binary Releases | manifest.json schema, package-system .tar.zst archive layout, fetch + verify flow |
| Profiling & Benchmarking | Syscall profiler, benchmark suite, cross-host comparison |
| POSIX Status | Syscall-by-syscall implementation status |
| Wasm Limitations | Fundamental platform constraints |
-
Compilation: C source → clang (wasm32-unknown-unknown) → linked against musl
libc.a+ glue layer. The glue translates musl's__syscall(number, args...)into typed writes to a SharedArrayBuffer channel. -
Loading: The TypeScript host instantiates the kernel Wasm module with host I/O imports, then creates process workers that each get their own Wasm memory with a channel region.
-
Syscall execution: When user code calls e.g.
open("/etc/hosts", O_RDONLY):- musl
open()→__syscall(SYS_openat, AT_FDCWD, path, flags, mode) - Glue writes syscall number + args to the channel, then
Atomics.store(status, SYSCALL_READY)+Atomics.notify() - Kernel worker wakes, reads channel, dispatches to
sys_open(), writes result back - Glue resumes with the return value (fd or negative errno)
- musl
-
Multi-process:
fork()uses Binaryen Asyncify to snapshot the Wasm call stack. The host copies process memory to a new Web Worker, and the child resumes from the fork point.exec()replaces the process image by terminating the old worker and starting a new one with fresh memory. Cross-process pipes, signals, and locks are coordinated through the shared kernel instance.
This project uses a split license model:
- GPL-2.0-or-later — The platform (kernel, host runtime, SDK, build scripts, examples)
- MIT — Runtime library components linked into user programs (musl-overlay/ and glue/)
You can compile and run your own programs — including proprietary ones — without the GPL applying to your code. The runtime code linked into your program is MIT-licensed, and the kernel communicates via IPC, not linking.
See LICENSE for full details.