add debian xfce setup example

This commit is contained in:
Yanshu Wang
2026-08-30 05:34:23 +02:00
committed by Fabian
parent cba06bf1e7
commit 77bd1b9e85
10 changed files with 543 additions and 1 deletions
+1
View File
@@ -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) —
+1 -1
View File
@@ -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": {
+42
View File
@@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>debian example</title>
<script src="../build/libv86.js"></script>
</head>
<body>
<div id="screen_container" tabindex="0" style="outline: none;">
<div style="white-space: pre; font: 14px monospace; line-height: 14px"></div>
<canvas style="display: none"></canvas>
</div>
<script>
"use strict";
window.onload = function () {
const screen = document.getElementById("screen_container");
screen.addEventListener("mousedown", function() {
screen.focus();
});
const emulator = new V86({
wasm_path: "../build/v86.wasm",
memory_size: 1636 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
screen_container: screen,
initial_state: { url: "../images/debian-state-base.bin.zst" },
filesystem: { baseurl: "../images/debian-9p-rootfs-flat/" },
network_relay_url: "wss://relay.widgetry.org/",
autostart: true,
});
const canvas = document.querySelector("canvas");
canvas.addEventListener("click", function() {
canvas.requestPointerLock();
});
};
</script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>debian example</title>
<script src="../build/libv86.js"></script>
</head>
<body>
<div id="screen_container" tabindex="0" style="outline: none;">
<div style="white-space: pre; font: 14px monospace; line-height: 14px"></div>
<canvas style="display: none"></canvas>
</div>
<script>
"use strict";
window.onload = function () {
const screen = document.getElementById("screen_container");
screen.addEventListener("mousedown", function() {
screen.focus();
});
const emulator = new V86({
wasm_path: "../build/v86.wasm",
memory_size: 1636 * 1024 * 1024,
vga_memory_size: 8 * 1024 * 1024,
screen_container: screen,
initial_state: { url: "../images/debian-state-base.bin.zst" },
hda: {
url: "../images/debian-chunks/chunk.zst",
async: true,
use_parts: true,
fixed_chunk_size: 128 * 1024,
size: 5 * 1024 * 1024 * 1024,
},
network_relay_url: "wss://relay.widgetry.org/",
autostart: true,
});
const canvas = document.querySelector("canvas");
canvas.addEventListener("click", function() {
canvas.requestPointerLock();
});
};
</script>
</body>
</html>
+109
View File
@@ -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/[email protected] /etc/systemd/system/[email protected] && \
printf '%s\n' \
'[Service]' \
'TTYVTDisallocate=no' \
> /etc/systemd/system/[email protected]/getty-noclear.conf && \
printf '%s\n' \
'[Service]' \
'ExecStart=' \
'ExecStart=-/sbin/agetty --autologin username --noissue --noclear %I 38400 $TERM' \
> /etc/systemd/system/[email protected]/getty-override.conf && \
printf '%s\n' \
'[Service]' \
'ExecStart=' \
'ExecStart=-/sbin/agetty --autologin username --noissue --noclear -s %I 115200,38400,9600 vt102' \
> /etc/systemd/system/[email protected]/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
+31
View File
@@ -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
+33
View File
@@ -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"
+48
View File
@@ -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"
+115
View File
@@ -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: "<UNUSED>",
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();
}
+115
View File
@@ -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: "<UNUSED>",
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();
}