There are webshell files bundled with this blog post. Check ~/page_files in the webshell.
Hi, welcome to my first blog entry! Here’s a list of stuff that changed when I fully revamped and rewrote my entire site, which used to consist of only one page.
Blogs
I have blogs now. It’s more like a scratchpad, if anything. Markdown is supported. It’s pretty cool.
Codeblocks
I can embed blocks of code into the page, like this:
int main() {
return 1;
}I originally wanted to use Monaco, but fitting in a fully fledged editor blew up the size of the webpage, so I settled with Shiki instead. Some codeblocks can be ran. This one’s backed by Pyodide, so I can run Python here:
print(332)
"return this value back"Exception handling and JavaScript exists, too:
console.log("hi")
await new Promise(res => setTimeout(res, 100))
throw 100;Terminal Output
Terminal outputs are embeddable. They are backed by xterm.js.
Webshell
In case you haven’t realized already, to the right of your screen (or, if you’re on mobile, bottom left) there is a webshell window peeking out. Click it to bring it out! The webshell is fully windowed (within the confines of the website page): it can be dragged around, resized, minified, and closed.
From a technical perspective, though, it’s a bit more interesting. The webshell runs a minimal install of Alpine Linux off v86 on a shared worker thread. The entire installation is <30MB in size, and has a persistent filesystem as well as an Internet connection.
Operating System
Naturally, I decided to go with Alpine Linux, given that the distro itself is minimal, lightweight, and supports 32-bit x86. Choosing the right distro, however, is only one piece of the puzzle.
The kernel used by the Linux install is custom compiled and stripped down to be light and minimal. On my M2 MacBook, the kernel itself takes only ~1.2s to fully initialize and run the init entrypoint script. I hand rolled a shell script (cat /sbin/init | less) to act as the init process. At idle, there are no more than 3-4 processes running on the system at any given time. I also hand-rolled and optimized the shell profile being used by ash for visual appeal.
Guest-Host Interop
For the webshell to be practically usable (though v86 itself is extremely slow), we need to be able to interact with the VM itself beyond having access to an interactive terminal.
I, too, hand-rolled a small C program called vmutil running in the background of the VM started by the init script to handle communication between the VM and browser worlds though an unused teletype exposed to the VM.
As a matter of fact, if you run ps aux, you will see vmutil running in the background right now!
Using a tty exposed to the Linux system is enough to pass along control signals between the VM and the browser, but for file transport the teletype will be far too slow. To work around this, file transfers take place through the virtio 9p file transport that v86 supports.
The root filesystem that is mounted to the VM is mounted to the VM as a block device containing a read-only squashfs blob. Persistent storage uses a 9p-backed filesystem overlay to 1) store the files in IndexedDB, and 2) avoid the overhead and hassle of allocating a large persistent block device on the browser.
Having 9p-backed filesystems also allowed me to add “attachments” to certain select pages, like this one. If you cd into ~/page_files, you’ll be able to find two scripts that I deliberately left there as VM file attachments, all of which can be ran inside the webshell.
Conclusion?
There’s not much for me to add here. I actually began writing out this personal website a little over a year and a half ago, but did not get to actually publishing it until now. I hope you have just as much fun reading and playing around on my website as I did writing it.

