Developer guide

Build locally, run on brintOS

The end-to-end loop: build the toolchain once, compile your program, mount a machine's drive over the network with the FUSE client, drop the binary onto it, then launch the machine in your browser and run it on brintos.io.

A brintOS package is not a generic .wasm file. It must be produced by the brintOS toolchain (hwjs-cc), which emits a small versioned header in a custom wasm section so the kernel can execve it. A binary built with Emscripten or wasi-libc will not run on a brintOS machine.

Prerequisites

You need the brintOS toolchain installed: the pinned LLVM, the HwjsCoroutinize pass and the wasmcc/wasmld drivers on your PATH, and our musl built into a sysroot. If you haven't done that yet, work through Install the toolchain first (all three steps — packages need the musl sysroot), then come back here.

1. Compile your package

wasmcc is a drop-in replacement for cc: it targets wasm32, links the musl from your sysroot, runs the HwjsCoroutinize pass, and attaches the binfmt_wasm header. Start with a hello-world:

cat > hello.c <<'EOF'
#include <stdio.h>
int main(void) {
    printf("hello from a brintOS package\n");
    return 0;
}
EOF

# Compile to a wasm-native Linux executable.
# WASMLD_HWJS=1 selects the coroutinizing arm — required for real builds
# (your toolchain env from the setup guide should already export it).
export WASMLD_HWJS=1
wasmcc hello.c -o hello

The result is a standard wasm module with one extra custom section. You can inspect it with stock wasm tooling — the custom section is ignored by tools that don't understand it:

wasm-objdump -h hello     # the custom binfmt_wasm section is present
file hello                # WebAssembly (wasm) binary module

Make sure the libc actually linked. A correctly linked package imports the kernel's shared memory and the syscall entry point our musl wrappers call. Confirm both are present:

wasm-objdump -x hello | grep -E 'kernel_memory|wasm_syscall'
# (env) kernel_memory   memory   ...
# (env) wasm_syscall    func     ...

If those imports are missing, your program didn't link against the brintOS musl — re-check the driver environment from the toolchain setup (WASMLD_HWJS=1, HWJS_TOOLS_DIR / WASMLD_HWJS_LIBC_BC) and rebuild. Builds are static by default, so hello bundles musl and is self-contained. Any build system that respects CC/LD works — point it at wasmcc/wasmld (e.g. make CC=wasmcc, with the sysroot env set) to build larger packages.

2. Get a machine and mount its drive

You need a machine you can write to. Fork a userland image that ships a shell — for example a bash + coreutils machine from Explore — into your own account. Forking gives you a writable copy at <you>/<repo>.

Then install the FUSE client, brintos-fs, which mounts a machine's filesystem as a local folder. Download a build from the Downloads page (or build it from source at brintos/brintos-fs). It needs FUSE 3 on Linux/WSL2:

sudo apt install fuse3 libfuse3-3            # Debian / Ubuntu / WSL2

brintos login                               # opens a browser to authorize
mkdir -p ~/mnt/brintos
brintos mount <you>/<repo> ~/mnt/brintos   # read-write because you own the repo
The mount is read-write only when you have write access to the repo — so mount a machine you own (your fork), not someone else's public one. Anonymous/public mounts are always read-only. See the Command line guide and the brintos-fs reference for flags like --snapshot and --prefetch.

3. Copy the package onto the filesystem

The mount looks like an ordinary folder. Drop your binary somewhere on the machine's PATH and make sure it's executable:

cp hello ~/mnt/brintos/usr/local/bin/hello
chmod +x ~/mnt/brintos/usr/local/bin/hello

# Flush to the server before you unmount. brintos-fs is write-back, so a
# plain copy is only locally durable until it drains; fsync/sync/unmount
# are the server-durable barriers.
sync

Writes use an asynchronous write-back model (like the Linux page cache): copies finish at local disk speed and drain to the server in the background. fsync, sync, and unmount block until your changes are uploaded and attached, so run one of those before you launch the machine in the browser.

4. Run it in the browser

Open your machine on brintos.io and press Power on. The machine boots its configured kernel and init (a shell, for a bash/coreutils image) entirely in the tab — its root filesystem is served from the same cloud storage you just wrote to. When the serial console shows a prompt, run your program:

$ hello
hello from a brintOS package

The kernel's binfmt_wasm loader recognizes the header you compiled in, swaps the image in on execve, and your process runs as a real Linux task — it can fork, take signals, and block on syscalls like any other binary.

Want your package to be the machine — run automatically instead of from a shell? Set the machine's init to your binary's path (e.g. /usr/local/bin/hello) in its machine spec, and the kernel will exec it as PID 1 on boot. See Machines. Building your own kernel image instead of using a stock one? See Build the Linux kernel.