GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - Gitguy268/tabshell: A real Linux terminal in a browser tab, powered by v86 or Docker. Install packages, play terminal games, then press Reset and start clean.

BranchesTags
Open more actions menu

Latest commit

 

History

14 Commits
14 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
docker
docker
 
 
docs
docs
 
 
image
image
 
 
public/bios
public/bios
 
 
scripts
scripts
 
 
src
src
 
 
test
test
 
 
tools
tools
 
 
.dockerignore
.dockerignore
 
 
.gitignore
.gitignore
 
 
CODE_OF_CONDUCT.md
CODE_OF_CONDUCT.md
 
 
CONTRIBUTING.md
CONTRIBUTING.md
 
 
LICENSE
LICENSE
 
 
NOTICE
NOTICE
 
 
README.md
README.md
 
 
SECURITY.md
SECURITY.md
 
 
index.html
index.html
 
 
package-lock.json
package-lock.json
 
 
package.json
package.json
 
 
server.mjs
server.mjs
 
 
start.command
start.command
 
 
vite.config.js
vite.config.js
 
 

Repository files navigation

TabShell

A real Linux terminal in a browser tab. Install packages, play terminal games, then press Reset and it's as if nothing happened.

Features · Run it · Using it · How it works · Limits · Contributing

License: MIT Node 20+ Renderer: WebGL Linux: Alpine · Ubuntu

TabShell running snek in the Browser VM

Live: gitguy268.github.io/tabshell · tabshell.vercel.app

Install with Homebrew

brew trust gitguy268/tap
brew install gitguy268/tap/tabshell
tabshell

Then open http://localhost:8686/. The browser VM works right away; for Docker mode, start Docker and run tabshell docker-image once.

Run it locally, step by step

  1. Install Node.js 20 or newer and Docker.

  2. Get the code:

    git clone https://github.com/Gitguy268/tabshell.git
    cd tabshell
  3. Install and build:

    npm ci
    npm run image
    docker build -t tabshell-ubuntu -f docker/Dockerfile .
    npm run build

    On a Mac you can double-click start.command instead. It does all of this for you.

  4. Start it:

    npm start
  5. Open http://localhost:8686/ in your browser. Press Ctrl+C in the terminal to stop.

Features

Browser VM Docker
Linux Alpine 3.24, 32-bit (i686) Ubuntu 26.04, 64-bit
Runs in your browser, with v86 (an x86 emulator compiled to WebAssembly) a Docker container on your computer
Needs any modern browser Docker Desktop or container, and start.command
Install packages apk add python3 sudo apt install python3, Homebrew
Preinstalled Node.js, npm, snek, podman, spf*, container* git, curl, build tools, podman, spf*, opencode*, container*
Kept until Reset yes, saved in your browser (IndexedDB) yes, the container stays until Reset

* Built from the superfile, opencode and container forks when they're checked out next to TabShell (../superfile, ../opencode, ../container); scripts/build-tools.sh skips any that are missing. container on Linux is the fork's podman front-end (docs/linux.md in the container fork): container run --rm alpine echo hi, container ls -a, container here alpine sh.

Both modes share one terminal: xterm.js drawing on the GPU through its WebGL renderer, with resize, colours, mouse and full-screen programs. The header's theme menu switches the terminal and page between Default, Light, Nujabes and Ye: Graduation, or follows your system's light or dark setting, and remembers your choice. Every theme has a full 16-colour palette that the tests check for readable contrast. The two named themes also show a line of text in the header; with Ye: Graduation, click it for another. They are unofficial tributes; see NOTICE.

  • Reset throws the machine away and starts a fresh one: files, packages, history and the screen.
  • Clear only clears the screen.
  • Network sets the relay the Browser VM uses to reach the internet.

Run it

On a Mac: download or clone this repository and double-click start.command. It:

  • starts Docker Desktop;
  • builds what's missing (the first run takes a few minutes);
  • serves TabShell on http://localhost:8686;
  • opens it in your browser.

Close the Terminal window to stop it. If macOS says it can't open start.command because it was downloaded from the internet, right-click it and choose Open once.

Opening index.html directly doesn't work. Browsers won't run the app from a file:// address.

Anywhere else, with Node.js 20+ and Docker:

npm ci
./scripts/build-tools.sh                # optional: spf, opencode, container from ../ checkouts
npm run image                           # Alpine VM image → public/alpine/
docker build -t tabshell-ubuntu -f docker/Dockerfile .  # Ubuntu image for Docker mode
npm run build
npm start                               # http://localhost:8686

Without Docker running, only Browser VM mode works. The Alpine image is built with Docker, so that first step needs it once, or a copy of public/alpine/ from a build.

Using it

Browser VM. It boots in about 30 seconds the first time. After that it restores where you left off.

apk add python3        # about 30 s through the default relay
python3
snek                   # arrow keys to steer, Esc or q to quit

Docker mode is Ubuntu, logged in as dev with passwordless sudo:

sudo apt update && sudo apt install -y python3
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew install node
git clone https://github.com/taniarascia/snek && cd snek && npm i && npm run play

Switching modes keeps both machines. Each one is only wiped by its own Reset.

How it works

browser: xterm.js + WebGL renderer
 ├─ Browser VM: v86 (WebAssembly) ─ Alpine on a 9p filesystem ─ virtio console
 │                  └─ network: Wisp relay over a WebSocket
 └─ Docker:     server.mjs (127.0.0.1:8686) ─ WebSocket proxy ─ ttyd ─ bash in Ubuntu (Docker or apple/container)
Browser VM details
  • Image. scripts/build-image.sh builds an i386 Alpine container from image/Dockerfile and exports its filesystem. v86's fs2json.py and copy-to-sha256.py then turn it into public/alpine/: a JSON index plus one zstd-compressed file per file. The VM downloads files only when it reads them.
  • Boot. The kernel and initramfs are read from that filesystem. The kernel uses hvc0 (virtio console) as its console, and getty logs in as root there.
  • Terminal. Bytes go between xterm.js and the virtio console. Terminal size reaches the guest as virtio resize messages, so stty size and full-screen programs follow the window.
  • Saving.
    • What's saved: the whole machine (memory, devices and changed files), using save_state().
    • Where: IndexedDB.
    • When: 5 seconds after the screen goes quiet, every minute during nonstop output such as a game, and when the tab is hidden. Changes from the last few seconds before closing the tab can be lost.
    • On the next visit, the VM continues from that state. Its clock is re-synced over NTP within a minute.
  • Reset. Stops the VM, deletes the saved state and boots the original image.
  • Network. The VM's virtio network card talks to a relay over a WebSocket.
    • Default: Mercury Workshop's public Wisp server wisps://wisp.mercurywork.shop/v86/, the same one v86's own demo offers.
    • Speed: in testing, apk add python3 took about 30 s.
    • Older relay: wss://relay.widgetry.org/ also works but was about 35 KB/s.
Docker mode details
  • docker/Dockerfile is Ubuntu 26.04 with ttyd, sudo and Homebrew's prerequisites. Python and Homebrew are left for you to install.
  • server.mjs uses only Node's standard library. It:
    • creates the container tabshell-linux the first time it's needed;
    • starts it again later;
    • removes and recreates it on Reset;
    • attaches no volumes or bind mounts, so removing the container removes everything.
  • The page talks ttyd's WebSocket protocol through server.mjs.
  • Without Docker. When Docker doesn't answer but container system status does, the same image runs on apple/container instead: start.command builds it with container build, and the proxy reaches ttyd on the container VM's own address rather than a forwarded port.

Security. A local shell is a tempting target for other websites, so:

  • the server listens on 127.0.0.1 only;
  • it refuses any Host header other than localhost:8686 or 127.0.0.1:8686, which stops DNS rebinding;
  • it refuses a POST or WebSocket from any other page's origin;
  • it sends no CORS headers;
  • ttyd is published on 127.0.0.1:8687 (on apple/container: the VM's private vmnet address, reachable only from this Mac and its VMs) and needs a random per-container password, which the proxy adds;
  • the container runs without the Docker socket and without access to your files. It is --privileged (all capabilities on apple/container) so podman can run containers inside it, which gives its shell root over Docker's Linux VM.
The terminal projects this was built from
  • Used directly: xterm.js, v86 and ttyd, the last inside the container.
  • Considered but not used:
    • node-pty: ttyd already provides the PTY, so no native module is needed.
    • Hyper, Rio and Termux: desktop and Android apps.
    • terminal.js, WebTerm and tehckisnow/terminal.

Limits

  • No Homebrew in Browser VM. v86 emulates a 32-bit PC, and Homebrew only supports 64-bit Linux. Use Docker mode for Homebrew.
  • Browser VM speed. It's an emulator: plain shell work is fine, while compiling or installing large packages is slow.
  • Containers in the Browser VM. Images live in the VM's 512 MB of memory and share its network, so small images like alpine work and big ones don't fit.
  • Relay. The Browser VM's internet traffic goes through a third-party relay unless you run your own. See Network in the app.
  • Docker mode is local. It isn't available on the hosted page, only through start.command or npm start.
  • Shells after a reload. Reloading in Docker mode starts a new shell. Files and packages stay, but programs running in the old shell are stopped.
  • Image updates. When a new version of TabShell ships a changed Linux image, a VM saved on the old image can't be restored and you get a fresh one.
  • Browser storage. A saved Browser VM is about 150 MB with Python installed, and grows with what you add. Clearing the site's data in your browser has the same effect as Reset.

Contributing

See CONTRIBUTING.md. Report security problems privately as described in SECURITY.md. By taking part you agree to the Code of Conduct.

License

MIT © Lucas Van De Weerd. Third-party software and its licences are listed in NOTICE. TabShell isn't affiliated with any of the projects it uses.

About

A real Linux terminal in a browser tab, powered by v86 or Docker. Install packages, play terminal games, then press Reset and start clean.

gitguy268.github.io/tabshell/

Topics

browserdockerlinuxsandboxterminalv86webassemblyxterm

Resources

Readme
MIT license

Code of conduct

Code of conduct

Contributing

Contributing

Security policy

Security policy
Activity

Stars

0 stars

Watchers

0 watching

Forks

0 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.