← winix
API reference
winix runs a Linux kernel and its userland in the browser. This page describes the JavaScript API
(winix.js), the curl command inside the guest, and the .winix session format.
The reference matches the source as of this release.
Boot
import { Winix, proxyFetch } from './winix.js';
const w = await Winix.boot({ fetch: proxyFetch() });
Winix.boot(options) starts a machine and resolves to a Winix instance. In our tests, boot took
under a second. If the kernel panics during boot, it is retried a few times.
| Option | Default | Meaning |
baseUrl | the module's folder | Where the kernel, initramfs and linux.js are loaded from. |
variant | 'wasm32_nommu' | Build variant. Only this one is shipped. |
onLog | none | Receives host diagnostics (boot messages and runtime events). |
onConsole | none | Receives the guest console text, as it is printed. |
cmdline | built-in | Kernel command line. |
fetch | the page's fetch | Function used for every HTTP request the guest makes. See Network. |
exec
const r = await w.exec('echo hi; ls /bin | wc -l', { cwd: '/work', env: { FOO: '1' }, timeoutMs: 60000 });
// r = { stdout, stderr, exitCode, timedOut, truncated, durationMs }
exec(command, options) runs command with sh -c in the guest and resolves to:
| Field | Meaning |
stdout, stderr | Output as UTF-8 text. |
exitCode | Exit status. -1 if the machine stopped answering. |
timedOut | true if the command ran past timeoutMs. Its process group is killed. |
truncated | true if output was longer than maxOutputBytes. The output is cut, not the command. |
durationMs | Wall-clock time for the call, in milliseconds. |
| Option | Default | Meaning |
cwd | '/work' | Working directory. |
env | {} | Environment variables. Names must match [A-Za-z_][A-Za-z0-9_]*. |
timeoutMs | 60000 | Time limit for the command. |
maxOutputBytes | 1048576 | Output limit for each of stdout and stderr. |
Calls run one at a time, in the order they were made. The filesystem persists between calls. Shell
variables and working directory changes do not: each call is a new sh -c.
Files
await w.writeFile('/work/a.js', 'console.log(6 * 7)'); // string is written as UTF-8
await w.writeFile('/work/data.bin', bytes, { mode: 0o600 });
const text = await w.readFile('/work/a.js', { encoding: 'utf8' });
const bytes = await w.readFile('/work/data.bin'); // Uint8Array
| Method | Meaning |
writeFile(path, data, { mode }) | Writes a string (as UTF-8) or bytes. Creates parent directories. Default mode 0o644. |
readFile(path, { encoding }) | Returns a Uint8Array, or a string with encoding: 'utf8'. |
keyInput
w.keyInput(text) types text into the guest console, as a keyboard would. It is not queued and does not
wait for the result. Use exec for anything that needs output. It is for interactive programs
(vi, top) and for the terminal page.
Sessions
const blob = await w.exportSession({ hostState: { notes: 'anything JSON' }, onProgress: (p) => {} });
const w2 = await Winix.restoreSession(blob, { fetch: proxyFetch() });
w2.hostState; // the object saved above
| Member | Meaning |
exportSession({ hostState, onProgress }) | Saves the root filesystem as a .winix Blob. Queued like exec. hostState is plain JSON that is stored with the session and returned as hostState when it is restored. |
Winix.restoreSession(input, { onProgress, ...bootOptions }) | Validates the archive, boots a new machine, and restores the filesystem. Returns the new instance. Your existing instances are not changed. |
hostState (getter) | A copy of the host state restored with this session, or null. |
A session saves files only. Running programs, shell variables, terminal state, and editor buffers are
not saved. Stop background writers and save editors before exporting. The filesystem is not frozen during the save:
the save checks it before and after reading and fails if anything changed. Session files can contain secrets such as
.env files and keys.
A session can only be restored into the same build of winix. Restoring checks the kernel and initramfs
checksums and refuses a different build.
Lifecycle
| Method | Meaning |
reset() | Replaces the machine with a freshly booted one. The filesystem is lost. |
dispose() | Stops the machine. Later calls fail with Winix disposed. |
Network
The guest has no sockets. Every HTTP request it makes is passed to the fetch function you give to
boot(). That function decides what happens to the request: it can forward it, log it, or refuse it.
Requests are made with the page's origin and browser credentials, so treat that function as a security boundary.
import { Winix } from './winix.js';
const allowed = ['example.com', 'api.example.org'];
const w = await Winix.boot({
fetch: (url, init) => {
console.log('sandbox request:', init?.method, url); // log
if (!allowed.includes(new URL(url).hostname)) {
return new Response('blocked by page policy', { status: 403 }); // block
}
return fetch(url, init); // allow
},
});
proxyFetch(prefix) is a ready-made fetch for cross-origin requests. It sends them to
prefix + encodeURIComponent(url), which by default is /__proxy?url=. That route exists only on the
development server. The GitHub Pages site has no proxy, so cross-origin requests fail there. To run your own,
point proxyFetch at it, for example proxyFetch('https://proxy.example/?url='). A proxy that
forwards any URL is an open relay, so restrict it to the hosts you need.
curl
Inside the guest, curl performs HTTP and HTTPS requests through the fetch function above.
The flags it accepts:
| Flags | Meaning |
-s, --silent / -S, --show-error | Hide progress; show errors when silent. |
-f, --fail / --fail-with-body | Exit 22 on HTTP status 400 and above (the second also prints the body). |
-i, --include / -I, --head | Print response headers, or send only a HEAD request. |
-N, --no-buffer | Accepted for compatibility. Response bodies are already written as they arrive, so streaming (for example server-sent events) works without it. |
-o FILE, --output FILE / -O, --remote-name | Write the body to a file, or to a file named after the URL. |
-X METHOD, --request METHOD | HTTP method. |
-H HEADER, --header HEADER | Add a request header. |
-d DATA, --data, --data-raw, --data-binary | Request body. @file reads a file, @- reads stdin (not with --data-raw). |
-A UA, --user-agent UA | Set the User-Agent header. |
-w FORMAT, --write-out FORMAT | Print after the transfer. Supports %{http_code} and \n. |
-L, -k, --location, --compressed | Accepted. Redirects are always followed. |
Anything else is an error. Sockets, ssh, nc, and ping are not
available in this release.
A .winix file is gzip-compressed. Once decompressed, it holds the magic string WINIXS01,
a 32-bit little-endian manifest length, the SHA-256 of the manifest, a JSON manifest, and then the bytes of every
regular file in manifest order. Checksums detect corruption; they do not prove where a file came from, so restore
only archives you trust. Nothing in the archive runs as code: it is validated before any machine starts.
Not yet
These are planned but not part of this release: network sockets inside the guest, w.connect(), and
w.ping(). Raw networking needs a kernel build with networking enabled and a process outside the browser
to carry the traffic.