├── .npmrc ├── dockerfiles ├── .dockerignore ├── debian_mini └── debian_large ├── src ├── routes │ ├── +page.js │ ├── alpine │ │ ├── +page.js │ │ └── +page.svelte │ ├── +page.svelte │ └── +layout.server.js ├── lib │ ├── activities.js │ ├── BlogPost.svelte │ ├── PostsTab.svelte │ ├── global.css │ ├── DiscordTab.svelte │ ├── Icon.svelte │ ├── CpuTab.svelte │ ├── GitHubTab.svelte │ ├── InformationTab.svelte │ ├── PanelButton.svelte │ ├── network.js │ ├── DiskTab.svelte │ ├── messages.js │ ├── NetworkingTab.svelte │ ├── AnthropicTab.svelte │ ├── anthropic.js │ ├── SideBar.svelte │ └── WebVM.svelte └── app.html ├── tower.ico ├── favicon.ico ├── assets ├── result.png ├── alpine_bg.png ├── leaningtech.png ├── social_2024.png ├── webvm_hero.png ├── welcome_to_WebVM_2024.png ├── fork_deploy_instructions.gif ├── welcome_to_WebVM_alpine_2024.png ├── github-mark-white.svg ├── discord-mark-blue.svg ├── tailscale.svg └── cheerpx.svg ├── documents ├── index.list ├── WebAssemblyTools.pdf ├── ArchitectureOverview.png └── Welcome.txt ├── examples ├── c │ ├── helloworld.c │ ├── link.c │ ├── Makefile │ ├── openat.c │ ├── env.c │ └── waitpid.c ├── ruby │ ├── powOf2.rb │ ├── love.rb │ └── helloWorld.rb ├── lua │ ├── sorting.lua │ ├── fizzbuzz.lua │ └── symmetric_difference.lua ├── python3 │ ├── pi.py │ ├── factorial.py │ └── fibonacci.py └── nodejs │ ├── environment.js │ ├── wasm.js │ ├── primes.js │ └── nbody.js ├── tailwind.config.js ├── svelte.config.js ├── login.html ├── scrollbar.css ├── config_public_alpine.js ├── vite.config.js ├── config_public_terminal.js ├── config_github_terminal.js ├── postcss.config.js ├── package.json ├── docs └── Tailscale.md ├── .circleci └── config.yml ├── xterm ├── xterm-addon-fit.js ├── xterm-addon-web-links.js └── xterm.css ├── nginx.conf ├── serviceWorker.js ├── README.md ├── LICENSE.txt └── .github └── workflows └── deploy.yml /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /dockerfiles/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | -------------------------------------------------------------------------------- /src/routes/+page.js: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /src/routes/alpine/+page.js: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /tower.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/tower.ico -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/favicon.ico -------------------------------------------------------------------------------- /assets/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/result.png -------------------------------------------------------------------------------- /assets/alpine_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/alpine_bg.png -------------------------------------------------------------------------------- /documents/index.list: -------------------------------------------------------------------------------- 1 | ArchitectureOverview.png 2 | WebAssemblyTools.pdf 3 | Welcome.txt 4 | -------------------------------------------------------------------------------- /assets/leaningtech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/leaningtech.png -------------------------------------------------------------------------------- /assets/social_2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/social_2024.png -------------------------------------------------------------------------------- /assets/webvm_hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/webvm_hero.png -------------------------------------------------------------------------------- /documents/WebAssemblyTools.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/documents/WebAssemblyTools.pdf -------------------------------------------------------------------------------- /examples/c/helloworld.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | printf("Hello, World!\n"); 6 | } 7 | -------------------------------------------------------------------------------- /assets/welcome_to_WebVM_2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/welcome_to_WebVM_2024.png -------------------------------------------------------------------------------- /examples/c/link.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | link("env", "env3"); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /assets/fork_deploy_instructions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/fork_deploy_instructions.gif -------------------------------------------------------------------------------- /documents/ArchitectureOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/documents/ArchitectureOverview.png -------------------------------------------------------------------------------- /assets/welcome_to_WebVM_alpine_2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aneesha/weblinux/main/assets/welcome_to_WebVM_alpine_2024.png -------------------------------------------------------------------------------- /examples/ruby/powOf2.rb: -------------------------------------------------------------------------------- 1 | puts(2 ** 1) 2 | puts(2 ** 2) 3 | puts(2 ** 3) 4 | puts(2 ** 10) 5 | puts(2 ** 100) 6 | puts(2 ** 1000) 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | 10 | -------------------------------------------------------------------------------- /examples/c/Makefile: -------------------------------------------------------------------------------- 1 | SRCS = $(wildcard *.c) 2 | 3 | PROGS = $(patsubst %.c,%,$(SRCS)) 4 | 5 | all: $(PROGS) 6 | 7 | %: %.c 8 | $(CC) $(CFLAGS) -o $@ $< 9 | 10 | clean: 11 | rm -f $(PROGS) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /examples/ruby/love.rb: -------------------------------------------------------------------------------- 1 | # Output "I love Ruby" 2 | say = "I love Ruby"; puts say 3 | 4 | # Output "I *LOVE* RUBY" 5 | say['love'] = "*love*"; puts say.upcase 6 | 7 | # Output "I *love* Ruby", 5 times 8 | 5.times { puts say } 9 | 10 | -------------------------------------------------------------------------------- /documents/Welcome.txt: -------------------------------------------------------------------------------- 1 | Welcome to WebVM: A complete desktop environment running in the browser 2 | 3 | WebVM is powered by CheerpX: a x86-to-WebAssembly virtualization engine and Just-in-Time compiler 4 | 5 | For more info: https://cheerpx.io 6 | -------------------------------------------------------------------------------- /examples/c/openat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | int ret = openat(AT_FDCWD, "/dev/tty", 0x88102, 0); 8 | printf("return value is %d and errno is %d\n", ret, errno); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/ruby/helloWorld.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | # The famous Hello World 3 | # Program is trivial in 4 | # Ruby. Superfluous: 5 | # 6 | # * A "main" method 7 | # * Newline 8 | # * Semicolons 9 | # 10 | # Here is the Code: 11 | =end 12 | 13 | puts "Hello World!" 14 | 15 | -------------------------------------------------------------------------------- /examples/lua/sorting.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | fruits = {"banana","orange","apple","grapes"} 3 | 4 | for k,v in ipairs(fruits) do 5 | print(k,v) 6 | end 7 | 8 | table.sort(fruits) 9 | print("sorted table") 10 | 11 | for k,v in ipairs(fruits) do 12 | print(k,v) 13 | end 14 | -------------------------------------------------------------------------------- /src/lib/activities.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const cpuActivity = writable(false); 4 | export const diskActivity = writable(false); 5 | export const aiActivity = writable(false); 6 | export const cpuPercentage = writable(0); 7 | export const diskLatency = writable(0); 8 | -------------------------------------------------------------------------------- /examples/python3/pi.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, getcontext 2 | getcontext().prec=60 3 | summation = 0 4 | for k in range(50): 5 | summation = summation + 1/Decimal(16)**k * ( 6 | Decimal(4)/(8*k+1) 7 | - Decimal(2)/(8*k+4) 8 | - Decimal(1)/(8*k+5) 9 | - Decimal(1)/(8*k+6) 10 | ) 11 | print(summation) 12 | 13 | -------------------------------------------------------------------------------- /src/lib/BlogPost.svelte: -------------------------------------------------------------------------------- 1 | 6 |
7 | 8 |

{title}

9 |
10 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | kit: { 7 | adapter: adapter() 8 | }, 9 | preprocess: vitePreprocess() 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /examples/c/env.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Most of the C compilers support a third parameter to main which 4 | // store all envorinment variables 5 | int main(int argc, char *argv[], char * envp[]) 6 | { 7 | int i; 8 | for (i = 0; envp[i] != NULL; i++) 9 | printf("\n%s", envp[i]); 10 | getchar(); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tailscale login 8 | 9 | 10 | Loading network code... 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/python3/factorial.py: -------------------------------------------------------------------------------- 1 | def factorial(): 2 | f, n = 1, 1 3 | while True: # First iteration: 4 | yield f # yield 1 to start with and then 5 | f, n = f * n, n+1 # f will now be 1, and n will be 2, ... 6 | 7 | for index, factorial_number in zip(range(51), factorial()): 8 | print('{i:3}!= {f:65}'.format(i=index, f=factorial_number)) 9 | 10 | -------------------------------------------------------------------------------- /examples/python3/fibonacci.py: -------------------------------------------------------------------------------- 1 | def fib(): 2 | a, b = 0, 1 3 | while True: # First iteration: 4 | yield a # yield 0 to start with and then 5 | a, b = b, a + b # a will now be 1, and b will also be 1, (0 + 1) 6 | 7 | for index, fibonacci_number in zip(range(100), fib()): 8 | print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number)) 9 | 10 | -------------------------------------------------------------------------------- /examples/nodejs/environment.js: -------------------------------------------------------------------------------- 1 | console.log("process.uptime = ", global.process.uptime()); 2 | console.log("process.title = ", global.process.title); 3 | console.log("process version = ", global.process.version); 4 | console.log("process.platform = ", global.process.platform); 5 | console.log("process.cwd = ", global.process.cwd()); 6 | console.log("process.uptime = ", global.process.uptime()); 7 | -------------------------------------------------------------------------------- /examples/lua/fizzbuzz.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | cfizz,cbuzz=0,0 3 | for i=1,20 do 4 | cfizz=cfizz+1 5 | cbuzz=cbuzz+1 6 | io.write(i .. ": ") 7 | if cfizz~=3 and cbuzz~=5 then 8 | io.write(i) 9 | else 10 | if cfizz==3 then 11 | io.write("Fizz") 12 | cfizz=0 13 | end 14 | if cbuzz==5 then 15 | io.write("Buzz") 16 | cbuzz=0 17 | end 18 | end 19 | io.write("\n") 20 | end 21 | -------------------------------------------------------------------------------- /src/lib/PostsTab.svelte: -------------------------------------------------------------------------------- 1 | 5 |

Blog posts

6 |
7 | {#each $page.data.posts as post} 8 | 13 | {/each} 14 |
15 | -------------------------------------------------------------------------------- /scrollbar.css: -------------------------------------------------------------------------------- 1 | .scrollbar { 2 | scrollbar-color: #777 #0000; 3 | } 4 | 5 | .scrollbar *::-webkit-scrollbar { 6 | height: 6px; 7 | width: 6px; 8 | background-color: #0000; 9 | } 10 | 11 | /* Add a thumb */ 12 | .scrollbar *::-webkit-scrollbar-thumb { 13 | border-radius: 3px; 14 | height: 6px; 15 | width: 6px; 16 | background: #777; 17 | } 18 | 19 | .scrollbar *::-webkit-scrollbar-thumb:hover { 20 | background: #555; 21 | } 22 | -------------------------------------------------------------------------------- /examples/c/waitpid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int status; 9 | 10 | pid_t p = getpid(); 11 | // waitpid takes a children's pid, not the current process one 12 | // if the pid is not a children of the current process, it returns -ECHILD 13 | pid_t res = waitpid(1001, &status, WNOHANG); 14 | 15 | printf("res is %d, p is %d and errno is %d\n", res, p, errno); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/global.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind utilities; 5 | 6 | body 7 | { 8 | font-family: Archivo, sans-serif; 9 | margin: 0; 10 | height: 100%; 11 | overflow: hidden; 12 | background: black; 13 | } 14 | 15 | html 16 | { 17 | height: 100%; 18 | } 19 | 20 | @media (width <= 850px) 21 | { 22 | html 23 | { 24 | font-size: calc(100vw / 55); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/lua/symmetric_difference.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env luajit 2 | A = { ["John"] = true, ["Bob"] = true, ["Mary"] = true, ["Elena"] = true } 3 | B = { ["Jim"] = true, ["Mary"] = true, ["John"] = true, ["Bob"] = true } 4 | 5 | A_B = {} 6 | for a in pairs(A) do 7 | if not B[a] then A_B[a] = true end 8 | end 9 | 10 | B_A = {} 11 | for b in pairs(B) do 12 | if not A[b] then B_A[b] = true end 13 | end 14 | 15 | for a_b in pairs(A_B) do 16 | print( a_b ) 17 | end 18 | for b_a in pairs(B_A) do 19 | print( b_a ) 20 | end 21 | -------------------------------------------------------------------------------- /examples/nodejs/wasm.js: -------------------------------------------------------------------------------- 1 | (function (){ 2 | let bytes = new Uint8Array([ 3 | 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 4 | 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 5 | 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01, 6 | 0x03, 0x73, 0x75, 0x6d, 0x00, 0x00, 0x0a, 0x0a, 7 | 0x01, 0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 8 | 0x0f, 0x0b 9 | ]); 10 | 11 | console.log(bytes); 12 | let mod = new WebAssembly.Module(bytes); 13 | let instance = new WebAssembly.Instance(mod, {}); 14 | console.log(instance.exports); 15 | return instance.exports.sum(2020, 1); 16 | }()); 17 | -------------------------------------------------------------------------------- /src/routes/alpine/+page.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 |

Looking for something different? Try the classic Debian Linux terminal-based WebVM

16 |
17 | -------------------------------------------------------------------------------- /src/lib/DiscordTab.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

Discord

7 | 8 | 9 | 10 | 11 |

Do you have any question about WebVM or CheerpX?

12 |

Join our community, we are happy to help!

13 | -------------------------------------------------------------------------------- /config_public_alpine.js: -------------------------------------------------------------------------------- 1 | // The root filesystem location 2 | export const diskImageUrl = "wss://disks.webvm.io/alpine_20241109.ext2"; 3 | // The root filesystem backend type 4 | export const diskImageType = "cloud"; 5 | // Print an introduction message about the technology 6 | export const printIntro = false; 7 | // Is a graphical display needed 8 | export const needsDisplay = true; 9 | // Executable full path (Required) 10 | export const cmd = "/sbin/init"; 11 | // Arguments, as an array (Required) 12 | export const args = []; 13 | // Optional extra parameters 14 | export const opts = { 15 | // User id 16 | uid: 0, 17 | // Group id 18 | gid: 0 19 | }; 20 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 |

Looking for a complete desktop experience? Try the new Alpine Linux graphical WebVM

16 |
17 | -------------------------------------------------------------------------------- /src/lib/Icon.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /examples/nodejs/primes.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | function isPrime(p) { 4 | const upper = Math.sqrt(p); 5 | for(let i = 2; i <= upper; i++) { 6 | if (p % i === 0 ) { 7 | return false; 8 | } 9 | } 10 | return true; 11 | } 12 | 13 | // Return n-th prime 14 | function prime(n) { 15 | if (n < 1) { 16 | throw Error("n too small: " + n); 17 | } 18 | let count = 0; 19 | let result = 1; 20 | while(count < n) { 21 | result++; 22 | if (isPrime(result)) { 23 | count++; 24 | } 25 | } 26 | return result; 27 | } 28 | 29 | console.log("your prime is ", prime(100000)); 30 | 31 | }()); 32 | -------------------------------------------------------------------------------- /src/lib/CpuTab.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

Engine

7 | 8 | 9 |

Virtual CPU: {$cpuPercentage}%

10 |

CheerpX is a x86 virtualization engine in WebAssembly

11 |

It can securely run unmodified x86 binaries and libraries in the browser

12 |

Excited about our technology? Start building your projects using CheerpX today!

13 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | import { viteStaticCopy } from 'vite-plugin-static-copy'; 4 | 5 | export default defineConfig({ 6 | resolve: { 7 | alias: { 8 | '/config_terminal': process.env.WEBVM_MODE == "github" ? 'config_github_terminal.js' : 'config_public_terminal.js', 9 | "@leaningtech/cheerpx": process.env.CX_URL ? process.env.CX_URL : "@leaningtech/cheerpx" 10 | } 11 | }, 12 | build: { 13 | target: "es2022" 14 | }, 15 | plugins: [ 16 | sveltekit(), 17 | viteStaticCopy({ 18 | targets: [ 19 | { src: 'tower.ico', dest: '' }, 20 | { src: 'scrollbar.css', dest: '' }, 21 | { src: 'serviceWorker.js', dest: '' }, 22 | { src: 'login.html', dest: '' }, 23 | { src: 'assets/', dest: '' }, 24 | { src: 'documents/', dest: '' } 25 | ] 26 | }) 27 | ] 28 | }); 29 | -------------------------------------------------------------------------------- /config_public_terminal.js: -------------------------------------------------------------------------------- 1 | // The root filesystem location 2 | export const diskImageUrl = "wss://disks.webvm.io/debian_large_20230522_5044875331.ext2"; 3 | // The root filesystem backend type 4 | export const diskImageType = "cloud"; 5 | // Print an introduction message about the technology 6 | export const printIntro = true; 7 | // Is a graphical display needed 8 | export const needsDisplay = false; 9 | // Executable full path (Required) 10 | export const cmd = "/bin/bash"; 11 | // Arguments, as an array (Required) 12 | export const args = ["--login"]; 13 | // Optional extra parameters 14 | export const opts = { 15 | // Environment variables 16 | env: ["HOME=/home/user", "TERM=xterm", "USER=user", "SHELL=/bin/bash", "EDITOR=vim", "LANG=en_US.UTF-8", "LC_ALL=C"], 17 | // Current working directory 18 | cwd: "/home/user", 19 | // User id 20 | uid: 1000, 21 | // Group id 22 | gid: 1000 23 | }; 24 | -------------------------------------------------------------------------------- /src/lib/GitHubTab.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

GitHub

7 | 8 | 9 | 10 | 11 |

Like WebVM? Give us a star!

12 |

WebVM is FOSS, you can fork it to build your own version and begin working on your CheerpX-based project

13 |

Found a bug? Please open a GitHub issue

14 | -------------------------------------------------------------------------------- /src/lib/InformationTab.svelte: -------------------------------------------------------------------------------- 1 |

Information

2 | WebVM Logo 3 |

WebVM is a virtual Linux environment running in the browser via WebAssembly

4 |

It is based on:

5 | 11 | 12 | -------------------------------------------------------------------------------- /config_github_terminal.js: -------------------------------------------------------------------------------- 1 | // The root filesystem location 2 | export const diskImageUrl = IMAGE_URL; 3 | // The root filesystem backend type 4 | export const diskImageType = "github"; 5 | // Print an introduction message about the technology 6 | export const printIntro = true; 7 | // Is a graphical display needed 8 | export const needsDisplay = false; 9 | // Executable full path (Required) 10 | export const cmd = CMD; // Default: "/bin/bash"; 11 | // Arguments, as an array (Required) 12 | export const args = ARGS; // Default: ["--login"]; 13 | // Optional extra parameters 14 | export const opts = { 15 | // Environment variables 16 | env: ENV, // Default: ["HOME=/home/user", "TERM=xterm", "USER=user", "SHELL=/bin/bash", "EDITOR=vim", "LANG=en_US.UTF-8", "LC_ALL=C"], 17 | // Current working directory 18 | cwd: CWD, // Default: "/home/user", 19 | // User id 20 | uid: 1000, 21 | // Group id 22 | gid: 1000 23 | }; 24 | -------------------------------------------------------------------------------- /dockerfiles/debian_mini: -------------------------------------------------------------------------------- 1 | FROM --platform=i386 i386/debian:buster 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | RUN apt-get clean && apt-get update && apt-get -y upgrade 4 | RUN apt-get -y install apt-utils gcc \ 5 | python3 vim unzip ruby nodejs \ 6 | fakeroot dbus base whiptail hexedit \ 7 | patch wamerican ucf manpages \ 8 | file luajit make lua50 dialog curl \ 9 | less cowsay netcat-openbsd 10 | RUN useradd -m user && echo "user:password" | chpasswd 11 | COPY --chown=user:user ./examples /home/user/examples 12 | RUN chmod -R +x /home/user/examples/lua 13 | # We set WORKDIR, as this gets extracted by Webvm to be used as the cwd. This is optional. 14 | WORKDIR /home/user/ 15 | # We set env, as this gets extracted by Webvm. This is optional. 16 | ENV HOME="/home/user" TERM="xterm" USER="user" SHELL="/bin/bash" EDITOR="vim" LANG="en_US.UTF-8" LC_ALL="C" 17 | RUN echo 'root:password' | chpasswd 18 | CMD [ "/bin/bash" ] 19 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | 'postcss-discard': {rule: function(node, value) 6 | { 7 | if(!value.startsWith('.fa-') || !value.endsWith(":before")) 8 | return false; 9 | switch(value) 10 | { 11 | case '.fa-info-circle:before': 12 | case '.fa-wifi:before': 13 | case '.fa-microchip:before': 14 | case '.fa-compact-disc:before': 15 | case '.fa-discord:before': 16 | case '.fa-github:before': 17 | case '.fa-star:before': 18 | case '.fa-circle:before': 19 | case '.fa-trash-can:before': 20 | case '.fa-book-open:before': 21 | case '.fa-user:before': 22 | case '.fa-screwdriver-wrench:before': 23 | case '.fa-desktop:before': 24 | case '.fa-brands:before': 25 | case '.fa-solid:before': 26 | case '.fa-regular:before': 27 | return false; 28 | } 29 | return true; 30 | }} 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/PanelButton.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |

14 | {#if buttonImage} 15 | 16 | {:else if buttonIcon} 17 | 18 | {/if} 19 | {buttonText}

20 | -------------------------------------------------------------------------------- /assets/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/discord-mark-blue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webvm", 3 | "version": "2.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build" 8 | }, 9 | "devDependencies": { 10 | "@fortawesome/fontawesome-free": "^6.6.0", 11 | "@leaningtech/cheerpx": "latest", 12 | "@oddbird/popover-polyfill": "^0.4.4", 13 | "@sveltejs/adapter-auto": "^3.0.0", 14 | "@sveltejs/adapter-static": "^3.0.5", 15 | "@sveltejs/kit": "^2.0.0", 16 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 17 | "@xterm/addon-fit": "^0.10.0", 18 | "@xterm/addon-web-links": "^0.11.0", 19 | "@xterm/xterm": "^5.5.0", 20 | "autoprefixer": "^10.4.20", 21 | "labs": "git@github.com:leaningtech/labs.git", 22 | "node-html-parser": "^6.1.13", 23 | "postcss": "^8.4.47", 24 | "postcss-discard": "^2.0.0", 25 | "svelte": "^4.2.7", 26 | "tailwindcss": "^3.4.9", 27 | "vite": "^5.0.3", 28 | "vite-plugin-static-copy": "^1.0.6" 29 | }, 30 | "type": "module", 31 | "dependencies": { 32 | "@anthropic-ai/sdk": "^0.33.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dockerfiles/debian_large: -------------------------------------------------------------------------------- 1 | FROM --platform=i386 i386/debian:buster 2 | ARG DEBIAN_FRONTEND=noninteractive 3 | 4 | RUN apt-get update && apt-get -y upgrade && \ 5 | apt-get install -y apt-utils beef bsdgames bsdmainutils ca-certificates clang \ 6 | cowsay cpio cron curl dmidecode dmsetup g++ gcc gdbm-l10n git \ 7 | hexedit ifupdown init logrotate lsb-base lshw lua50 luajit lynx make \ 8 | nano netbase nodejs openssl procps python3 python3-cryptography \ 9 | python3-jinja2 python3-numpy python3-pandas python3-pip python3-scipy \ 10 | python3-six python3-yaml readline-common rsyslog ruby sensible-utils \ 11 | ssh systemd systemd-sysv tasksel tasksel-data udev vim wget whiptail \ 12 | xxd iptables isc-dhcp-client isc-dhcp-common kmod less netcat-openbsd 13 | 14 | # Make a user, then copy over the /example directory 15 | RUN useradd -m user && echo "user:password" | chpasswd 16 | COPY --chown=user:user ./examples /home/user/examples 17 | RUN chmod -R +x /home/user/examples/lua 18 | RUN echo 'root:password' | chpasswd 19 | CMD [ "/bin/bash" ] 20 | -------------------------------------------------------------------------------- /assets/tailscale.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/Tailscale.md: -------------------------------------------------------------------------------- 1 | # Enable networking 2 | 3 | - In order to access the public internet, you will need an Exit Node. See [Tailscale Exit Nodes](https://tailscale.com/kb/1103/exit-nodes/) for detailed instructions. 4 | - ***Note:*** This is not required to access machines in your own Tailscale Network. 5 | - Depending on your network speed, you may need to wait a few moments for the Tailscale Wasm module to be downloaded. 6 | 7 | **When all set:** 8 | - Log in with your Tailscale credentials. 9 | - Go back to the WebVM tab. 10 | - The `Connect to Tailscale` button in the Networking side-panel should be replaced by your IP address. 11 | 12 | # Log in to Tailscale with an Auth key 13 | 14 | - Add `#authKey=` at the end of the URL. 15 | - Done, you don't need to manually log in anymore. 16 | 17 | It is recommended to use an ephemeral key. 18 | 19 | # Log in to a self-hosted Tailscale network (Headscale) 20 | 21 | - Add `#controlUrl=` at the end of the URL. 22 | - You can combine this option with `authKey` with a `&`: `#controlUrl=&authKey=`. 23 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | deploy: 5 | docker: 6 | - image: cimg/node:22.9 7 | resource_class: medium 8 | steps: 9 | - add_ssh_keys: 10 | fingerprints: 11 | - "86:3b:c9:a6:d1:b9:a8:dc:0e:00:db:99:8d:19:c4:3e" 12 | - run: 13 | name: Add known hosts 14 | command: | 15 | mkdir -p ~/.ssh 16 | echo $GH_HOST >> ~/.ssh/known_hosts 17 | echo $RPM_HOST >> ~/.ssh/known_hosts 18 | - run: 19 | name: Install NPM 20 | command: | 21 | sudo apt-get update && sudo apt-get install -y rsync npm 22 | - run: 23 | name: Clone WebVM 24 | command: | 25 | git clone --branch $CIRCLE_BRANCH --single-branch git@github.com:leaningtech/webvm.git 26 | - run: 27 | name: Build WebVM 28 | command: | 29 | cd webvm/ 30 | npm install 31 | npm run build 32 | - run: 33 | name: Deploy webvm 34 | command: | 35 | rsync -avz -e "ssh -p ${SSH_PORT}" webvm/build/ leaningtech@${SSH_HOST}:/srv/web/webvm/ 36 | 37 | workflows: 38 | deploy: 39 | when: 40 | equal: [ << pipeline.trigger_source >>, "api" ] 41 | jobs: 42 | - deploy 43 | -------------------------------------------------------------------------------- /src/routes/+layout.server.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'node-html-parser'; 2 | import { read } from '$app/server'; 3 | 4 | var posts = [ 5 | "https://labs.leaningtech.com/blog/cx-10", 6 | "https://labs.leaningtech.com/blog/webvm-20", 7 | "https://labs.leaningtech.com/blog/join-the-webvm-hackathon", 8 | "https://labs.leaningtech.com/blog/mini-webvm-your-linux-box-from-dockerfile-via-wasm", 9 | "https://labs.leaningtech.com/blog/webvm-virtual-machine-with-networking-via-tailscale", 10 | "https://labs.leaningtech.com/blog/webvm-server-less-x86-virtual-machines-in-the-browser", 11 | ]; 12 | 13 | async function getPostData(u) 14 | { 15 | var ret = { title: null, image: null, url: u }; 16 | var response = await fetch(u); 17 | var str = await response.text(); 18 | var root = parse(str); 19 | var tags = root.getElementsByTagName("meta"); 20 | for(var i=0;i(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})())); 2 | //# sourceMappingURL=addon-fit.js.map 3 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | error_log nginx_main_error.log info; 8 | pid nginx_user.pid; 9 | daemon off; 10 | 11 | http { 12 | access_log nginx_access.log; 13 | error_log nginx_error.log info; 14 | 15 | types { 16 | text/html html htm shtml; 17 | text/css css; 18 | application/javascript js; 19 | application/wasm wasm; 20 | image/png png; 21 | image/jpeg jpg jpeg; 22 | image/svg+xml svg; 23 | } 24 | 25 | default_type application/octet-stream; 26 | 27 | sendfile on; 28 | 29 | server { 30 | # listen 8080 ssl; 31 | listen 8081; 32 | server_name localhost; 33 | 34 | gzip on; 35 | # Enable compression for .wasm, .js and .txt files (used for the runtime chunks) 36 | gzip_types application/javascript application/wasm text/plain application/octet-stream; 37 | 38 | charset utf-8; 39 | 40 | # ssl_certificate nginx.crt; 41 | # ssl_certificate_key nginx.key; 42 | 43 | location / { 44 | root build; 45 | autoindex on; 46 | index index.html index.htm; 47 | add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; 48 | add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; 49 | add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/network.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { browser } from '$app/environment' 3 | 4 | let authKey = undefined; 5 | let controlUrl = undefined; 6 | if(browser) 7 | { 8 | let params = new URLSearchParams("?"+window.location.hash.substr(1)); 9 | authKey = params.get("authKey") || undefined; 10 | controlUrl = params.get("controlUrl") || undefined; 11 | } 12 | let dashboardUrl = controlUrl ? null : "https://login.tailscale.com/admin/machines"; 13 | let resolveLogin = null; 14 | let loginPromise = new Promise((f,r) => { 15 | resolveLogin = f; 16 | }); 17 | let connectionState = writable("DISCONNECTED"); 18 | let exitNode = writable(false); 19 | 20 | function loginUrlCb(url) 21 | { 22 | connectionState.set("LOGINREADY"); 23 | resolveLogin(url); 24 | } 25 | 26 | function stateUpdateCb(state) 27 | { 28 | switch(state) 29 | { 30 | case 6 /*Running*/: 31 | { 32 | connectionState.set("CONNECTED"); 33 | break; 34 | } 35 | } 36 | } 37 | 38 | function netmapUpdateCb(map) 39 | { 40 | networkData.currentIp = map.self.addresses[0]; 41 | var exitNodeFound = false; 42 | for(var i=0; i < map.peers.length;i++) 43 | { 44 | if(map.peers[i].exitNode) 45 | { 46 | exitNodeFound = true; 47 | break; 48 | } 49 | } 50 | if(exitNodeFound) 51 | { 52 | exitNode.set(true); 53 | } 54 | } 55 | 56 | export async function startLogin() 57 | { 58 | connectionState.set("LOGINSTARTING"); 59 | const url = await loginPromise; 60 | networkData.loginUrl = url; 61 | return url; 62 | } 63 | 64 | export const networkInterface = { authKey: authKey, controlUrl: controlUrl, loginUrlCb: loginUrlCb, stateUpdateCb: stateUpdateCb, netmapUpdateCb: netmapUpdateCb }; 65 | 66 | export const networkData = { currentIp: null, connectionState: connectionState, exitNode: exitNode, loginUrl: null, dashboardUrl: dashboardUrl } 67 | -------------------------------------------------------------------------------- /src/lib/DiskTab.svelte: -------------------------------------------------------------------------------- 1 | 50 |

Disk

51 | 52 | 53 | {#if state == "CONFIRM"} 54 |

Warning: WebVM will reload

55 | {:else if state == "RESETTING"} 56 |

Reset in progress: Please wait...

57 | {:else} 58 |

Backend latency: {$diskLatency}ms

59 | {/if} 60 |

WebVM runs on top of a complete Linux distribution

61 |

Filesystems up to 2GB are supported and data is downloaded completely on-demand

62 |

The WebVM cloud backend uses WebSockets and a it's distributed via a global CDN to minimize download latency

63 | -------------------------------------------------------------------------------- /assets/cheerpx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebVM - Linux virtualization in WebAssembly 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | %sveltekit.head% 34 | 35 | 36 |
%sveltekit.body%
37 | 38 | 39 | -------------------------------------------------------------------------------- /xterm/xterm-addon-web-links.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(self,(()=>(()=>{"use strict";var e={6:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(6),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})())); 2 | //# sourceMappingURL=addon-web-links.js.map 3 | -------------------------------------------------------------------------------- /src/lib/messages.js: -------------------------------------------------------------------------------- 1 | const color= "\x1b[1;35m"; 2 | const underline= "\x1b[94;4m"; 3 | const normal= "\x1b[0m"; 4 | /* 5 | export const introMessage = [ 6 | "+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+", 7 | "| |", 8 | "| WebVM is a virtual Linux environment running in the browser via WebAssembly |", 9 | "| |", 10 | "| WebVM is powered by the CheerpX virtualization engine, which enables safe, |", 11 | "| sandboxed client-side execution of x86 binaries, fully client-side |", 12 | "| |", 13 | "| CheerpX includes an x86-to-WebAssembly JIT compiler, a virtual block-based |", 14 | "| file system, and a Linux syscall emulator |", 15 | "| |", 16 | "| [News] CheerpX 1.0 officially released! |", 17 | "| |", 18 | "| " + underline + "https://cheerpx.io/blog/cx-10" + normal + " |", 19 | "| |", 20 | "| Try out the new Alpine / Xorg / i3 WebVM: " + underline + "https://webvm.io/alpine.html" + normal + " |", 21 | "| |", 22 | "+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+", 23 | "", 24 | " Welcome to WebVM. If unsure, try these examples:", 25 | "", 26 | " python3 examples/python3/fibonacci.py ", 27 | " gcc -o helloworld examples/c/helloworld.c && ./helloworld", 28 | " objdump -d ./helloworld | less -M", 29 | " vim examples/c/helloworld.c", 30 | " curl --max-time 15 parrot.live # requires networking", 31 | "" 32 | ]; 33 | */ 34 | export const introMessage = [ 35 | "+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+", 36 | "| |", 37 | "| WebVM is a virtual Linux environment running in the browser via WebAssembly |", 38 | "| |", 39 | "+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+" 40 | ]; 41 | export const errorMessage = [ 42 | color + "CheerpX could not start" + normal, 43 | "", 44 | "CheerpX is expected to work with recent desktop versions of Chrome, Edge, Firefox and Safari", 45 | "", 46 | "Give it a try from a desktop version / another browser!", 47 | "", 48 | "CheerpX internal error message is:", 49 | "" 50 | ]; 51 | export const unexpectedErrorMessage = [ 52 | color + "WebVM encountered an unexpected error" + normal, 53 | "", 54 | "Check the DevTools console for further information", 55 | "", 56 | "Please consider reporting a bug!", 57 | "", 58 | "CheerpX internal error message is:", 59 | "" 60 | ]; 61 | -------------------------------------------------------------------------------- /src/lib/NetworkingTab.svelte: -------------------------------------------------------------------------------- 1 | 101 |

Networking

102 | 103 | {#if $connectionState == "CONNECTED"} 104 | 105 | {/if} 106 | 107 |

WebVM can connect to the Internet via Tailscale

108 |

Using Tailscale is required since browser do not support TCP/UDP sockets (yet!)

109 | -------------------------------------------------------------------------------- /src/lib/AnthropicTab.svelte: -------------------------------------------------------------------------------- 1 | 84 |

Claude AI Integration

85 |

WebVM is integrated with Claude by Anthropic AI. You can prompt the AI to control the system.

86 |

You need to provide your API key. The key is only saved locally to your browser.

87 |
88 |
89 |
90 | {#each $messageList as msg} 91 | {#if isToolUse(msg)} 92 |

{getMessageForTool(msg)}

93 | {:else if !isToolResult(msg)} 94 |

{msg.content}

95 | {/if} 96 | {/each} 97 |
98 |
99 |
100 | {#if $apiState == "KEY_REQUIRED"} 101 |