From 77bd1b9e85dadfdec20d5069a45a8d6a47e2b7f1 Mon Sep 17 00:00:00 2001 From: Yanshu Wang Date: Mon, 22 Jun 2026 18:50:17 +0800 Subject: [PATCH] add debian xfce setup example --- Readme.md | 1 + eslint.config.mjs | 2 +- examples/debian-9p.html | 42 +++++++ examples/debian-raw-disk.html | 48 ++++++++ tools/docker/debian/Dockerfile | 109 +++++++++++++++++++ tools/docker/debian/Readme.md | 31 ++++++ tools/docker/debian/build-9p.sh | 33 ++++++ tools/docker/debian/build-raw-disk.sh | 48 ++++++++ tools/docker/debian/build-state-9p.js | 115 ++++++++++++++++++++ tools/docker/debian/build-state-raw-disk.js | 115 ++++++++++++++++++++ 10 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 examples/debian-9p.html create mode 100644 examples/debian-raw-disk.html create mode 100644 tools/docker/debian/Dockerfile create mode 100644 tools/docker/debian/Readme.md create mode 100755 tools/docker/debian/build-9p.sh create mode 100755 tools/docker/debian/build-raw-disk.sh create mode 100755 tools/docker/debian/build-state-9p.js create mode 100755 tools/docker/debian/build-state-raw-disk.js diff --git a/Readme.md b/Readme.md index ca3fa0f7..6d78d621 100644 --- a/Readme.md +++ b/Readme.md @@ -65,6 +65,7 @@ list of emulated hardware: [Dial-up modem networking](docs/modem.md) — [Alpine Linux guest setup](tools/docker/alpine/) — [Arch Linux guest setup](docs/archlinux.md) — +[Debian with xfce guest setup](tools/docker/debian/) — [MS-DOS/FreeDOS guest setup](docs/dos.md) — [Windows 3.1x guest setup](docs/windows-31x.md) — [Windows 9x guest setup](docs/windows-9x.md) — diff --git a/eslint.config.mjs b/eslint.config.mjs index 9a45ed83..e9edc161 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -59,7 +59,7 @@ export default [ "no-extra-semi": "error", "no-tabs": "error", "id-match": ["error", - "^([a-z][a-z0-9_]*|[A-Z][a-zA-Z0-9_]*|_|\\$|__dirname|__filename|spawnSync|execFile|createServer|workerData|parentPort|isMainThread|fileURLToPath|registerProcessor|sampleRate)$", + "^([a-z][a-z0-9_]*|[A-Z][a-zA-Z0-9_]*|_|\\$|__dirname|__filename|spawnSync|execFile|execFileSync|createServer|workerData|parentPort|isMainThread|fileURLToPath|registerProcessor|sampleRate)$", { "onlyDeclarations": true, "properties": false }], "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }], "keyword-spacing": ["error", { "overrides": { diff --git a/examples/debian-9p.html b/examples/debian-9p.html new file mode 100644 index 00000000..815c367e --- /dev/null +++ b/examples/debian-9p.html @@ -0,0 +1,42 @@ + + + + + debian example + + + + +
+
+ +
+ + + + diff --git a/examples/debian-raw-disk.html b/examples/debian-raw-disk.html new file mode 100644 index 00000000..1541c738 --- /dev/null +++ b/examples/debian-raw-disk.html @@ -0,0 +1,48 @@ + + + + + debian example + + + + +
+
+ +
+ + + + diff --git a/tools/docker/debian/Dockerfile b/tools/docker/debian/Dockerfile new file mode 100644 index 00000000..f5270798 --- /dev/null +++ b/tools/docker/debian/Dockerfile @@ -0,0 +1,109 @@ +FROM i386/debian:bookworm-slim + +WORKDIR /root/build +ENV DEBIAN_FRONTEND=noninteractive + +# Base Setup +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + linux-image-686 \ + systemd-sysv \ + locales \ + libterm-readline-perl-perl \ + && rm -rf /var/lib/apt/lists/* + +RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \ + locale-gen && \ + echo 'LANG="en_US.UTF-8"' > /etc/default/locale && \ + chsh -s /bin/bash root + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + xserver-xorg-core xserver-xorg-video-fbdev xinit \ + xfce4 xfce4-terminal \ + xserver-xorg-input-mouse xserver-xorg-input-kbd xserver-xorg-input-evdev \ + libxext6 libxrender1 libxtst6 libxi6 libx11-xcb1 libxrender-dev \ + libfontconfig1 fonts-dejavu-core adwaita-icon-theme fonts-wqy-microhei fonts-symbola \ + wget tar unzip ca-certificates xterm \ + xdg-utils shared-mime-info dbus-x11 desktop-base \ + thunar-archive-plugin xfce4-taskmanager ristretto \ + gvfs fonts-liberation xfce4-notifyd gvfs-backends \ + isc-dhcp-client iproute2 iputils-ping sudo \ + cmake git tmux mc htop vim nano p7zip-full geany mousepad atril engrampa curl build-essential \ + firefox-esr libreoffice thunar pavucontrol xfce4-screenshooter thunderbird gimp \ + && rm -rf /var/lib/apt/lists/* + +# Configure Root and Standard User ('username') +RUN passwd -d root && \ + sed -i 's/nullok_secure/nullok/' /etc/pam.d/common-auth && \ + useradd -m -s /bin/bash username && \ + passwd -d username && \ + usermod -aG sudo,video,tty,input,dialout username && \ + echo "username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Configure Serial Console Auto-login +RUN mkdir -p /etc/systemd/system/getty@tty1.service.d /etc/systemd/system/serial-getty@ttyS0.service.d && \ + printf '%s\n' \ + '[Service]' \ + 'TTYVTDisallocate=no' \ + > /etc/systemd/system/getty@tty1.service.d/getty-noclear.conf && \ + printf '%s\n' \ + '[Service]' \ + 'ExecStart=' \ + 'ExecStart=-/sbin/agetty --autologin username --noissue --noclear %I 38400 $TERM' \ + > /etc/systemd/system/getty@tty1.service.d/getty-override.conf && \ + printf '%s\n' \ + '[Service]' \ + 'ExecStart=' \ + 'ExecStart=-/sbin/agetty --autologin username --noissue --noclear -s %I 115200,38400,9600 vt102' \ + > /etc/systemd/system/serial-getty@ttyS0.service.d/getty-autologin-serial.conf + +# Disable Unnecessary Services +RUN systemctl disable systemd-timesyncd.service && \ + systemctl disable apt-daily.timer && \ + systemctl disable apt-daily-upgrade.timer + +# 9p Filesystem Boot Configuration +# the boot script switches on the kernel command line: root=host9p mounts the +# 9p filesystem, anything else falls back to the standard disk boot +RUN printf '%s\n' 9p 9pnet 9pnet_virtio virtio virtio_ring virtio_pci | tee -a /etc/initramfs-tools/modules + +RUN printf '%s\n' \ + '#!/bin/sh' \ + 'case $1 in prereqs) exit 0;; esac' \ + '. /scripts/functions' \ + 'if [ "${ROOT}" = "host9p" ]; then' \ + ' mkdir -p ${rootmnt}' \ + ' mount -n -t 9p -o trans=virtio,version=9p2000.L,cache=loose,rw host9p ${rootmnt}' \ + 'else' \ + ' . /scripts/local' \ + 'fi' \ + > /etc/initramfs-tools/scripts/boot-9p && \ + chmod +x /etc/initramfs-tools/scripts/boot-9p + +RUN echo 'BOOT=boot-9p' | tee -a /etc/initramfs-tools/initramfs.conf +RUN update-initramfs -u + +# Configure X11, Networking, and Auto-start for 'username' +RUN echo '(sleep 5 && sudo bash -c "printf \"\\n\\nGUI_READY\\n\" > /dev/ttyS0") & \n\ +exec startxfce4' > /home/username/.xinitrc + +RUN { \ + echo 'if [ -z "$DISPLAY" ]; then'; \ + echo ' sudo bash -c "echo 127.0.0.1 localhost > /etc/hosts"'; \ + echo ' sudo bash -c "echo localhost > /etc/hostname"'; \ + echo ' sudo hostname localhost'; \ + echo ' startx -- -ac'; \ + echo 'fi'; \ +} >> /home/username/.bashrc + +RUN chown username:username /home/username/.xinitrc /home/username/.bashrc + +# Force Xorg to use the FBDEV driver +RUN mkdir -p /etc/X11/xorg.conf.d && \ + echo 'Section "Device"\n\ + Identifier "Card0"\n\ + Driver "fbdev"\n\ +EndSection' > /etc/X11/xorg.conf.d/10-fbdev.conf + +WORKDIR /home/username diff --git a/tools/docker/debian/Readme.md b/tools/docker/debian/Readme.md new file mode 100644 index 00000000..315b9717 --- /dev/null +++ b/tools/docker/debian/Readme.md @@ -0,0 +1,31 @@ +Debian-xfce docker example: + +For using split raw disk: +1. run `bash build-raw-disk.sh` to build images +2. run `./build-state-raw-disk.js` to build the state +3. An example frontend is `examples/debian-raw-disk.html` + +For using 9p filesystem: + +1. run `bash build-9p.sh` to build images +2. run `./build-state-9p.js` to build the state +3. An example frontend is `examples/debian-9p.html` + +For setting up website: + +1. Either build v86 or downloading the release and place `v86.wasm` and `libv86.js` in `build` directory +2. Launch a server at the root of this repository, e.g. `python3 -m http.server 8000` +3. Open `http://localhost:8000/examples/debian-raw-disk.html` (raw disk) or `http://localhost:8000/examples/debian-9p.html` (9p filesystem) + +Network: + +Enable network by typing the following command in the xfce terminal: + +``` +sudo dhclient enp0s5 +``` + +Credit: + +v86 debian config from sandbox-bio: https://github.com/sandbox-bio/v86/tree/master/tools/docker/debian + diff --git a/tools/docker/debian/build-9p.sh b/tools/docker/debian/build-9p.sh new file mode 100755 index 00000000..d2fe78d6 --- /dev/null +++ b/tools/docker/debian/build-9p.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -veuo pipefail + +# Use 'dirname' so this script works from any folder +DIR="$(dirname "$0")" +IMAGES="$DIR"/../../../images +OUT_ROOTFS_TAR="$IMAGES"/debian-9p-rootfs.tar +OUT_ROOTFS_FLAT="$IMAGES"/debian-9p-rootfs-flat +OUT_FSJSON="$IMAGES"/debian-base-fs.json +CONTAINER_NAME=debian-xfce-build +IMAGE_NAME=local/debian-xfce-v86 + +mkdir -p "$IMAGES" + +echo "Building Docker image..." +docker build "$DIR" --platform linux/386 --rm --tag "$IMAGE_NAME" + +echo "Exporting filesystem..." +docker rm "$CONTAINER_NAME" || true +docker create --platform linux/386 --name "$CONTAINER_NAME" "$IMAGE_NAME" + +docker export "$CONTAINER_NAME" > "$OUT_ROOTFS_TAR" + +echo "Converting to JSON..." +"$DIR"/../../../tools/fs2json.py --zstd --out "$OUT_FSJSON" "$OUT_ROOTFS_TAR" + +echo "Creating flat filesystem..." +# Clear old files to save space +rm -rf "$OUT_ROOTFS_FLAT" +mkdir -p "$OUT_ROOTFS_FLAT" +"$DIR"/../../../tools/copy-to-sha256.py --zstd "$OUT_ROOTFS_TAR" "$OUT_ROOTFS_FLAT" + +echo "Done. Artifacts created at $IMAGES" diff --git a/tools/docker/debian/build-raw-disk.sh b/tools/docker/debian/build-raw-disk.sh new file mode 100755 index 00000000..34aecfd0 --- /dev/null +++ b/tools/docker/debian/build-raw-disk.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -veuo pipefail + +# Use 'dirname' so this script works from any folder +DIR="$(dirname "$0")" +IMAGES="$DIR"/../../../images +OUT_ROOTFS_TAR="$IMAGES"/debian-rootfs.tar +OUT_IMG="$IMAGES"/debian.img +OUT_CHUNKS="$IMAGES"/debian-chunks +MNT="$DIR"/mnt +CONTAINER_NAME=debian-xfce-build +IMAGE_NAME=local/debian-xfce-v86 + +mkdir -p "$IMAGES" + +echo "Building Docker image..." +docker build "$DIR" --platform linux/386 --rm --tag "$IMAGE_NAME" + +echo "Exporting filesystem..." +docker rm "$CONTAINER_NAME" || true +docker create --platform linux/386 --name "$CONTAINER_NAME" "$IMAGE_NAME" +docker export "$CONTAINER_NAME" > "$OUT_ROOTFS_TAR" + +echo "Creating ext4 disk image..." +truncate -s 5G "$OUT_IMG" +mkfs.ext4 -F "$OUT_IMG" + +echo "Mounting and transferring files..." +mkdir -p "$MNT" +sudo mount "$OUT_IMG" "$MNT" +trap 'sudo umount "$MNT"' EXIT +sudo tar -xf "$OUT_ROOTFS_TAR" -C "$MNT" + +echo "Extracting kernel and initrd for v86 boot..." +cp "$MNT"/boot/vmlinuz-* "$IMAGES"/debian-bzImage +cp "$MNT"/boot/initrd.img-* "$IMAGES"/debian-initrd + +sudo umount "$MNT" +trap - EXIT +rmdir "$MNT" + +echo "Splitting image into 128KB chunks..." +rm -rf "$OUT_CHUNKS" +mkdir -p "$OUT_CHUNKS" +python3 "$DIR"/../../split-image.py --zstd 128k "$OUT_IMG" "$OUT_CHUNKS/chunk-%d-%d" + +echo "Done. Artifacts created at $IMAGES" +echo "Chunks are ready in $OUT_CHUNKS" diff --git a/tools/docker/debian/build-state-9p.js b/tools/docker/debian/build-state-9p.js new file mode 100755 index 00000000..7b6a116a --- /dev/null +++ b/tools/docker/debian/build-state-9p.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +import path from "node:path"; +import fs from "node:fs"; +import url from "node:url"; +import { execFileSync } from "node:child_process"; +import { V86 } from "../../../build/libv86.mjs"; + +// TODO: +// - Timeout + +console.log("Don't forget to run `make all` before running this script"); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const V86_ROOT = path.join(__dirname, "../../.."); +var OUTPUT_FILE = path.join(V86_ROOT, "images/debian-state-base.bin"); + +process.stdin.setRawMode(true); +process.stdin.resume(); +process.stdin.setEncoding("utf8"); +process.stdin.on("data", handle_key); + +var emulator = new V86({ + wasm_path: path.join(V86_ROOT, "build/v86.wasm"), + bios: { url: path.join(V86_ROOT, "/bios/seabios.bin") }, + vga_bios: { url: path.join(V86_ROOT, "/bios/vgabios.bin") }, + autostart: true, + memory_size: 1636 * 1024 * 1024, + vga_memory_size: 8 * 1024 * 1024, + network_relay_url: "", + bzimage_initrd_from_filesystem: true, + // enable communication b/w JavaScript and v86 on serial port 1 (input) and 2 (output) + uart1: true, + uart2: true, + // Remove unneeded security features since running in a sandbox (spectre_v2=off, pti=off). + // Make state file half the size by setting "init_on_free=on", i.e. Linux overwrites freed memory with zeroes. + // Set "rootflags=trans=virtio,cache=none --> doesn't help with FS cache" + cmdline: "rw init=/bin/systemd root=host9p console=ttyS0 spectre_v2=off pti=off init_on_free=on", + filesystem: { + basefs: { + url: path.join(V86_ROOT, "/images/debian-base-fs.json"), + }, + baseurl: path.join(V86_ROOT, "/images/debian-9p-rootfs-flat/"), + }, + screen_dummy: true, +}); + +console.log("Now booting, please stand by ..."); + +var boot_start = Date.now(); +var serial_text = ""; +let booted = false; +let saving = false; + +emulator.add_listener("serial0-output-byte", function(byte) +{ + var c = String.fromCharCode(byte); + process.stdout.write(c); + + serial_text += c; + + if(!booted && serial_text.includes("GUI_READY")) + { + console.error("\nBooted in %d", (Date.now() - boot_start) / 1000); + booted = true; + + // Sync and drop caches: Makes it safer to change the filesystem as fewer files are rendered + // Disable kernel logging with dmesg (drop_caches outputs things to ttyS0 even if run it on another port) + emulator.serial0_send("sudo dmesg -n 1; sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\n"); + + } + + if(booted && !saving && serial_text.includes("GUI_READY")) + { + saving = true; + console.error("\n\n'GUI_READY' signal received!"); + console.error("Waiting 240 seconds for GUI to fully render before saving state..."); + + // Delay the state save by 240 seconds + setTimeout(function() { + console.error("\nTime is up! Saving state now..."); + emulator.save_state().then(function(s) + { + fs.writeFile(OUTPUT_FILE, new Uint8Array(s), function(e) + { + if(e) throw e; + console.error("Saved as " + OUTPUT_FILE); + console.error("Compressing to " + OUTPUT_FILE + ".zst"); + execFileSync("zstd", ["-19", "-f", OUTPUT_FILE, "-o", OUTPUT_FILE + ".zst"], { stdio: "inherit" }); + stop(); + }); + }); + }, 240000); + } +}); + +function handle_key(c) +{ + if(c === "\u0003") + { + // ctrl c + stop(); + } + else + { + emulator.serial0_send(c); + } +} + +function stop() +{ + emulator.stop(); + process.stdin.pause(); +} diff --git a/tools/docker/debian/build-state-raw-disk.js b/tools/docker/debian/build-state-raw-disk.js new file mode 100755 index 00000000..0c119377 --- /dev/null +++ b/tools/docker/debian/build-state-raw-disk.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +import path from "node:path"; +import fs from "node:fs"; +import url from "node:url"; +import { execFileSync } from "node:child_process"; +import { V86 } from "../../../build/libv86.mjs"; + +// TODO: +// - Timeout + +console.log("Don't forget to run `make all` before running this script"); + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +const V86_ROOT = path.join(__dirname, "../../.."); +var OUTPUT_FILE = path.join(V86_ROOT, "images/debian-state-base.bin"); + +process.stdin.setRawMode(true); +process.stdin.resume(); +process.stdin.setEncoding("utf8"); +process.stdin.on("data", handle_key); + +var emulator = new V86({ + wasm_path: path.join(V86_ROOT, "build/v86.wasm"), + bios: { url: path.join(V86_ROOT, "/bios/seabios.bin") }, + vga_bios: { url: path.join(V86_ROOT, "/bios/vgabios.bin") }, + autostart: true, + memory_size: 1636 * 1024 * 1024, + vga_memory_size: 8 * 1024 * 1024, + network_relay_url: "", + bzimage: { url: path.join(V86_ROOT, "images/debian-bzImage") }, + initrd: { url: path.join(V86_ROOT, "images/debian-initrd") }, + hda: { + url: path.join(V86_ROOT, "images/debian.img"), + async: true, + size: 5 * 1024 * 1024 * 1024, + }, + // enable communication b/w JavaScript and v86 on serial port 1 (input) and 2 (output) + uart1: true, + uart2: true, + // Remove unneeded security features since running in a sandbox (spectre_v2=off, pti=off). + // Make state file half the size by setting "init_on_free=on", i.e. Linux overwrites freed memory with zeroes. + // Set "rootflags=trans=virtio,cache=none --> doesn't help with FS cache" + cmdline: "rw init=/bin/systemd root=/dev/sda console=ttyS0 spectre_v2=off pti=off init_on_free=on", + screen_dummy: true, +}); + +console.log("Now booting, please stand by ..."); + +var boot_start = Date.now(); +var serial_text = ""; +let booted = false; +let saving = false; + +emulator.add_listener("serial0-output-byte", function(byte) +{ + var c = String.fromCharCode(byte); + process.stdout.write(c); + + serial_text += c; + + if(!booted && serial_text.includes("GUI_READY")) + { + console.error("\nBooted in %d", (Date.now() - boot_start) / 1000); + booted = true; + + // Sync and drop caches: Makes it safer to change the filesystem as fewer files are rendered + // Disable kernel logging with dmesg (drop_caches outputs things to ttyS0 even if run it on another port) + emulator.serial0_send("sudo dmesg -n 1; sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\n"); + + } + + if(booted && !saving && serial_text.includes("GUI_READY")) + { + saving = true; + console.error("\n\n'GUI_READY' signal received!"); + console.error("Waiting 240 seconds for GUI to fully render before saving state..."); + + // Delay the state save by 240 seconds + setTimeout(function() { + console.error("\nTime is up! Saving state now..."); + emulator.save_state().then(function(s) + { + fs.writeFile(OUTPUT_FILE, new Uint8Array(s), function(e) + { + if(e) throw e; + console.error("Saved as " + OUTPUT_FILE); + console.error("Compressing to " + OUTPUT_FILE + ".zst"); + execFileSync("zstd", ["-19", "-f", OUTPUT_FILE, "-o", OUTPUT_FILE + ".zst"], { stdio: "inherit" }); + stop(); + }); + }); + }, 240000); + } +}); + +function handle_key(c) +{ + if(c === "\u0003") + { + // ctrl c + stop(); + } + else + { + emulator.serial0_send(c); + } +} + +function stop() +{ + emulator.stop(); + process.stdin.pause(); +}