diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile
index 4be552d4..7319c8c0 100644
--- a/images/chromium-headful/Dockerfile
+++ b/images/chromium-headful/Dockerfile
@@ -1,96 +1,114 @@
-# webrtc client
+###############################################################################
+# Stage 1 ─────────────────────────────────────────────────────────────────────
+# Build WebRTC client frontend
+###############################################################################
FROM node:22-bullseye-slim AS client
+
WORKDIR /src
COPY client/package*.json ./
RUN npm install
COPY client/ .
RUN npm run build
-# xorg dependencies
-FROM docker.io/ubuntu:22.04 AS xorg-deps
+
+###############################################################################
+# Stage 2 ─────────────────────────────────────────────────────────────────────
+# Build custom Xorg dummy video driver (xf86-video-dummy v0.3.8 + RandR patch)
+# and the custom input driver (xf86-input-neko)
+###############################################################################
+FROM ubuntu:22.04 AS xorg-deps
WORKDIR /xorg
ENV DEBIAN_FRONTEND=noninteractive
+
+
+# Build-time dependencies for Xorg modules
RUN set -eux; \
apt-get update; \
apt-get install -y \
git gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;
+
COPY xorg-deps/ /xorg/
-# build xf86-video-dummy v0.3.8 with RandR support
-RUN set -eux; \
- cd xf86-video-dummy/v0.3.8; \
- patch -p1 < ../01_v0.3.8_xdummy-randr.patch; \
- autoreconf -v --install; \
- ./configure; \
- make -j$(nproc); \
- make install;
-# build custom input driver
-RUN set -eux; \
- cd xf86-input-neko; \
- ./autogen.sh --prefix=/usr; \
- ./configure; \
- make -j$(nproc); \
- make install;
+# Build xf86-video-dummy v0.3.8 with RandR support
+RUN cd xf86-video-dummy/v0.3.8 && \
+ patch -p1 < ../01_v0.3.8_xdummy-randr.patch && \
+ autoreconf -v --install && \
+ ./configure && \
+ make -j"$(nproc)" && \
+ make install
+
+# Build custom input driver
+RUN cd xf86-input-neko && \
+ ./autogen.sh --prefix=/usr && \
+ ./configure && \
+ make -j"$(nproc)" && \
+ make install
+
+
+###############################################################################
+# Stage 3 ─────────────────────────────────────────────────────────────────────
+# Extract neko executable from upstream image
+###############################################################################
FROM ghcr.io/onkernel/neko/base:3.0.6-v1.0.1 AS neko
-# ^--- now has event.SYSTEM_PONG with legacy support to keepalive
+
+
+###############################################################################
+# Stage 4 ─────────────────────────────────────────────────────────────────────
+# Final runtime image
+###############################################################################
FROM docker.io/ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV DEBIAN_PRIORITY=high
-RUN apt-get update && \
- apt-get -y upgrade && \
+###############################################################################
+# Base OS & core toolchain & apps
+###############################################################################
+RUN set -eux; \
+ apt-get update; \
+ apt-get -y upgrade; \
apt-get -y install \
- # UI Requirements
- xvfb \
- xterm \
- xdotool \
- scrot \
- imagemagick \
- sudo \
- mutter \
- x11vnc \
- # Python/pyenv reqs
- build-essential \
- libssl-dev \
- zlib1g-dev \
- libbz2-dev \
- libreadline-dev \
- libsqlite3-dev \
- curl \
- git \
- libncursesw5-dev \
- xz-utils \
- tk-dev \
- libxml2-dev \
- libxmlsec1-dev \
- libffi-dev \
- liblzma-dev \
- # Network tools
- net-tools \
- netcat \
- # PPA req
- software-properties-common && \
- # Userland apps
- sudo add-apt-repository ppa:mozillateam/ppa && \
+ xvfb xterm xdotool scrot imagemagick sudo mutter x11vnc upower \
+ build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
+ curl git libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev \
+ net-tools netcat software-properties-common; \
+ \
+ sudo add-apt-repository ppa:mozillateam/ppa; \
sudo apt-get install -y --no-install-recommends \
- chromium-browser \
- libreoffice \
- x11-apps \
- xpdf \
- gedit \
- xpaint \
- tint2 \
- galculator \
- pcmanfm \
- wget \
- xdg-utils \
- libvulkan1 \
- fonts-liberation \
- unzip && \
+ chromium-browser libreoffice x11-apps xpdf gedit xpaint tint2 galculator pcmanfm \
+ wget xdg-utils libvulkan1 fonts-liberation unzip; \
apt-get clean
+
+###############################################################################
+# Locales & fonts
+###############################################################################
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends locales && \
+ printf "%s\n" \
+ "en_US.UTF-8 UTF-8" "en_GB.UTF-8 UTF-8" "es_ES.UTF-8 UTF-8" \
+ "es_MX.UTF-8 UTF-8" "fr_FR.UTF-8 UTF-8" "de_DE.UTF-8 UTF-8" \
+ "it_IT.UTF-8 UTF-8" "pt_PT.UTF-8 UTF-8" "pt_BR.UTF-8 UTF-8" \
+ "nl_NL.UTF-8 UTF-8" "sv_SE.UTF-8 UTF-8" "no_NO.UTF-8 UTF-8" \
+ "da_DK.UTF-8 UTF-8" "fi_FI.UTF-8 UTF-8" "tr_TR.UTF-8 UTF-8" \
+ "vi_VN.UTF-8 UTF-8" "id_ID.UTF-8 UTF-8" "bn_IN.UTF-8 UTF-8" \
+ "pa_IN.UTF-8 UTF-8" "zh_CN.UTF-8 UTF-8" "zh_TW.UTF-8 UTF-8" \
+ "ja_JP.UTF-8 UTF-8" "ko_KR.UTF-8 UTF-8" "ar_SA.UTF-8 UTF-8" \
+ "hi_IN.UTF-8 UTF-8" "ru_RU.UTF-8 UTF-8" "th_TH.UTF-8 UTF-8" \
+ "el_GR.UTF-8 UTF-8" "he_IL.UTF-8 UTF-8" \
+ > /etc/locale.gen && \
+ locale-gen && \
+ update-locale LANG=en_US.UTF-8 && \
+ apt-get install -y --no-install-recommends \
+ fonts-noto-core fonts-noto-ui-core fonts-noto-cjk fonts-noto-cjk-extra && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+
+###############################################################################
+# FFmpeg (static build, latest 7.x)
+###############################################################################
# install ffmpeg manually since the version available in apt is from the 4.x branch due to #drama.
# as of writing these static builds will be the latest 7.0.x release.
RUN set -eux; \
@@ -102,72 +120,160 @@ RUN set -eux; \
install -m755 /tmp/ffmpeg-*/ffprobe /usr/local/bin/ffprobe; \
rm -rf /tmp/ffmpeg*
-# runtime
-ENV USERNAME=root
-RUN set -eux; \
- apt-get update; \
+
+###############################################################################
+# Runtime dependencies, libxcvt, user creation, directory setup
+###############################################################################
+ARG KERNEL_USER=kernel
+ARG KERNEL_UID=1000
+ARG KERNEL_GID=$KERNEL_UID
+
+RUN apt-get update && \
apt-get install -y --no-install-recommends \
- wget ca-certificates python2 supervisor xclip xdotool \
- pulseaudio dbus-x11 xserver-xorg-video-dummy \
- libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \
- gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
- gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
- gstreamer1.0-pulseaudio gstreamer1.0-omx; \
- #
- # install libxcvt0 (not available in debian:bullseye)
- ARCH=$(dpkg --print-architecture); \
- wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_${ARCH}.deb; \
- apt-get install --no-install-recommends ./libxcvt0_0.1.2-1_${ARCH}.deb; \
- rm ./libxcvt0_0.1.2-1_${ARCH}.deb; \
- #
+ wget ca-certificates python2 supervisor xclip xdotool \
+ pulseaudio dbus-x11 xserver-xorg-video-dummy rtkit \
+ libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \
+ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
+ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
+ gstreamer1.0-pulseaudio gstreamer1.0-omx && \
+ \
+ # libxcvt0 (Debian package, not in Ubuntu repo)
+ ARCH=$(dpkg --print-architecture) && \
+ curl -fsSL http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_${ARCH}.deb \
+ -o /tmp/libxcvt.deb && \
+ apt-get install -y --no-install-recommends /tmp/libxcvt.deb && \
+ rm /tmp/libxcvt.deb && \
+ \
+ # Non-root user with audio/video privileges
+ groupadd --gid "$KERNEL_GID" "$KERNEL_USER" && \
+ useradd --uid "$KERNEL_UID" --gid "$KERNEL_GID" \
+ --shell /bin/bash --create-home "$KERNEL_USER" && \
+ for g in audio video pulse pulse-access; do adduser "$KERNEL_USER" "$g"; done && \
+ \
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
- mkdir /tmp/.X11-unix; \
- chmod 1777 /tmp/.X11-unix; \
- chown $USERNAME /tmp/.X11-unix/; \
- #
- # make directories for neko
+ mkdir -p /tmp/.X11-unix && \
+ chmod 1777 /tmp/.X11-unix && \
+ chown "$KERNEL_USER:$KERNEL_USER" /tmp/.X11-unix && \
+ \
+ # Make directories for neko
mkdir -p /etc/neko /var/www /var/log/neko \
- /tmp/runtime-$USERNAME \
- /home/$USERNAME/.config/pulse \
- /home/$USERNAME/.local/share/xorg; \
- chmod 1777 /var/log/neko; \
- chown $USERNAME /var/log/neko/ /tmp/runtime-$USERNAME; \
- chown -R $USERNAME:$USERNAME /home/$USERNAME; \
- # clean up
- apt-get clean -y; \
+ /tmp/runtime-"$KERNEL_USER" \
+ /home/"$KERNEL_USER"/.config \
+ /home/"$KERNEL_USER"/.local/share/xorg && \
+ chmod 1777 /var/log/neko && \
+ chown -R "$KERNEL_USER:$KERNEL_USER" \
+ /var/log/neko /tmp/runtime-"$KERNEL_USER" /home/"$KERNEL_USER" && \
+ chmod 777 /etc/pulse || true && \
+ apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/
-# install chromium & ncat for proxying the remote debugging port
-RUN add-apt-repository -y ppa:xtradeb/apps
-RUN apt update -y && apt install -y chromium ncat
-# Install noVNC
+###############################################################################
+# Chromium (via xtradeb) & ncat
+###############################################################################
+RUN add-apt-repository -y ppa:xtradeb/apps && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends chromium ncat && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+
+###############################################################################
+# noVNC (v1.5.0) + Websockify (v0.12.0)
+###############################################################################
RUN git clone --branch v1.5.0 https://github.com/novnc/noVNC.git /opt/noVNC && \
git clone --branch v0.12.0 https://github.com/novnc/websockify /opt/noVNC/utils/websockify && \
ln -s /opt/noVNC/vnc.html /opt/noVNC/index.html
-# setup desktop env & app
-ENV DISPLAY_NUM=1
-ENV HEIGHT=768
-ENV WIDTH=1024
-ENV WITHDOCKER=true
-COPY xorg.conf /etc/neko/xorg.conf
-COPY neko.yaml /etc/neko/neko.yaml
-COPY --from=neko /usr/bin/neko /usr/bin/neko
-COPY --from=client /src/dist/ /var/www
-COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so
-COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so /usr/lib/xorg/modules/input/neko_drv.so
+###############################################################################
+# Environment
+###############################################################################
+ENV DISPLAY_NUM=1 \
+ HEIGHT=768 \
+ WIDTH=1024 \
+ WITHDOCKER=true
+
+
+###############################################################################
+# Copy build artefacts & configurations
+###############################################################################
+COPY xorg.conf /etc/neko/xorg.conf
+COPY default.pa /etc/pulse/default.pa
+COPY daemon.conf /etc/pulse/daemon.conf
+COPY dbus-pulseaudio.conf /etc/dbus-1/system.d/pulseaudio.conf
+COPY dbus-mpris.conf /etc/dbus-1/system.d/mpris.conf
+COPY neko.yaml /etc/neko/neko.yaml
+
+# Executables & drivers from previous stages
+COPY --from=neko /usr/bin/neko /usr/bin/neko
+COPY --from=client /src/dist/ /var/www
+COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so \
+ /usr/lib/xorg/modules/drivers/dummy_drv.so
+COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so \
+ /usr/lib/xorg/modules/input/neko_drv.so
+
+RUN chown -R "$KERNEL_USER:$KERNEL_USER" /var/www /etc/neko
+
+###############################################################################
+# Additional assets & wrapper
+###############################################################################
COPY image-chromium/ /
-COPY ./wrapper.sh /wrapper.sh
+COPY wrapper.sh /wrapper.sh
+
-# copy the kernel-images API binary built externally
+###############################################################################
+# Copy the kernel-images API binary built externally
+###############################################################################
COPY bin/kernel-images-api /usr/local/bin/kernel-images-api
ENV WITH_KERNEL_IMAGES_API=false
-RUN useradd -m -s /bin/bash kernel
+###############################################################################
+# Copy the kernel-operator artifacts built externally
+###############################################################################
+RUN mkdir -p /tmp/kernel-operator
+
+COPY bin/kernel-operator-api /usr/local/bin/kernel-operator-api
+COPY bin/kernel-operator-test /usr/local/bin/kernel-operator-test
+COPY bin/.kernel-operator.env /tmp/kernel-operator/.kernel-operator.env
+
+RUN chmod +x /usr/local/bin/kernel-images-api \
+ /usr/local/bin/kernel-operator-api \
+ /usr/local/bin/kernel-operator-test
+
+ENV WITH_KERNEL_OPERATOR_API=false
+ENV DEBUG_OPERATOR_TEST=false
+
+###############################################################################
+# Grant kernel user + kernel operator api a lot of freedom
+###############################################################################
+# Passwordless sudo for "kernel" to execute arbitrary root commands when needed
+RUN echo 'kernel ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/010-kernel-nopw && chmod 0440 /etc/sudoers.d/010-kernel-nopw
+# Grant broad Linux capabilities to the operator API and tests while running as "kernel"
+# This preserves the "kernel" user identity but lifts FS/NET/NS limits typical for root tasks
+# To be adjusted for required capabilities range.
+RUN setcap 'cap_chown,cap_fowner,cap_fsetid,cap_dac_override,cap_dac_read_search,cap_mknod,cap_sys_admin,cap_sys_resource,cap_sys_ptrace,cap_sys_time,cap_sys_tty_config,cap_net_admin,cap_net_raw,cap_setuid,cap_setgid=ep' /usr/local/bin/kernel-operator-api || true && \
+ setcap 'cap_chown,cap_fowner,cap_fsetid,cap_dac_override,cap_dac_read_search,cap_mknod,cap_sys_admin,cap_sys_resource,cap_sys_ptrace,cap_net_admin,cap_net_raw=ep' /usr/local/bin/kernel-operator-test || true
+
+
+###############################################################################
+# Set user data
+###############################################################################
RUN cp -r ./user-data /home/kernel/user-data
-ENTRYPOINT [ "/wrapper.sh" ]
+###############################################################################
+# Runtime environment variables
+###############################################################################
+ENV USER=kernel \
+ XDG_RUNTIME_DIR=/tmp/runtime-kernel \
+ PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native \
+ XDG_CONFIG_HOME=/tmp/.chromium \
+ XDG_CACHE_HOME=/tmp/.chromium
+
+
+###############################################################################
+# Entrypoint
+###############################################################################
+ENTRYPOINT ["/wrapper.sh"]
\ No newline at end of file
diff --git a/images/chromium-headful/Kraftfile.erofs b/images/chromium-headful/Kraftfile.erofs
new file mode 100644
index 00000000..18af1a0b
--- /dev/null
+++ b/images/chromium-headful/Kraftfile.erofs
@@ -0,0 +1,12 @@
+spec: v0.6
+
+runtime: index.unikraft.io/official/base-compat:latest
+
+labels:
+ cloud.unikraft.v1.instances/scale_to_zero.policy: "idle"
+ cloud.unikraft.v1.instances/scale_to_zero.stateful: "true"
+ cloud.unikraft.v1.instances/scale_to_zero.cooldown_time_ms: 5000
+
+rootfs: ./initrd
+
+cmd: ["/wrapper.sh"]
diff --git a/images/chromium-headful/Kraftfile.no-erofs b/images/chromium-headful/Kraftfile.no-erofs
new file mode 100644
index 00000000..9364e423
--- /dev/null
+++ b/images/chromium-headful/Kraftfile.no-erofs
@@ -0,0 +1,12 @@
+spec: v0.6
+
+runtime: index.unikraft.io/official/base-compat:latest
+
+labels:
+ cloud.unikraft.v1.instances/scale_to_zero.policy: "on"
+ cloud.unikraft.v1.instances/scale_to_zero.stateful: "true"
+ cloud.unikraft.v1.instances/scale_to_zero.cooldown_time_ms: 4000
+
+rootfs: ./Dockerfile
+
+cmd: ["/wrapper.sh"]
\ No newline at end of file
diff --git a/images/chromium-headful/build-docker.sh b/images/chromium-headful/build-docker.sh
index 5f1db257..04693845 100755
--- a/images/chromium-headful/build-docker.sh
+++ b/images/chromium-headful/build-docker.sh
@@ -11,5 +11,8 @@ source ../../shared/start-buildkit.sh
# Build the kernel-images API binary and place it into ./bin for Docker build context
source ../../shared/build-server.sh "$(pwd)/bin"
+# Build operator api + test + .env → ./bin
+source ../../shared/build-operator-api.sh "$(pwd)/bin"
+
# Build (and optionally push) the Docker image.
docker build -t "$IMAGE" .
diff --git a/images/chromium-headful/build-unikernel.sh b/images/chromium-headful/build-unikernel.sh
index 0b16bf99..c01ad14a 100755
--- a/images/chromium-headful/build-unikernel.sh
+++ b/images/chromium-headful/build-unikernel.sh
@@ -1,37 +1,75 @@
#!/usr/bin/env bash
+# Flag to control whether to use EROFS or not
+EROFS_DISABLE=${EROFS_DISABLE:-false}
+
# Move to the script's directory so relative paths work regardless of the caller CWD
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
cd "$SCRIPT_DIR"
source "$SCRIPT_DIR/../../shared/ensure-common-build-run-vars.sh" chromium-headful
-source "$SCRIPT_DIR/../../shared/erofs-utils.sh"
-# Ensure the mkfs.erofs tool is available
-if ! check_mkfs_erofs; then
- echo "mkfs.erofs is not installed. Installing erofs-utils..."
- install_erofs_utils
-fi
+# Copy the appropriate Kraftfile based on EROFS_DISABLE flag
+if [ "$EROFS_DISABLE" = "false" ]; then
+ echo "Using EROFS configuration (default)..."
+ cp Kraftfile.erofs Kraftfile
+ source "$SCRIPT_DIR/../../shared/erofs-utils.sh"
+
+ # Ensure the mkfs.erofs tool is available
+ if ! check_mkfs_erofs; then
+ echo "mkfs.erofs is not installed. Installing erofs-utils..."
+ install_erofs_utils
+ fi
+
+ set -euo pipefail
+
+ # Build the root file system
+ source ../../shared/start-buildkit.sh
+
+ rm -rf ./.rootfs || true
+
+ # Build the API binary
+ source ../../shared/build-server.sh "$(pwd)/bin"
-set -euo pipefail
-
-# Build the root file system
-source ../../shared/start-buildkit.sh
-rm -rf ./.rootfs || true
-# Build the API binary
-source ../../shared/build-server.sh "$(pwd)/bin"
-app_name=chromium-headful-build
-docker build --platform linux/amd64 -t "$IMAGE" .
-docker rm cnt-"$app_name" || true
-docker create --platform linux/amd64 --name cnt-"$app_name" "$IMAGE" /bin/sh
-docker cp cnt-"$app_name":/ ./.rootfs
-rm -f initrd || true
-sudo mkfs.erofs --all-root -d2 -E noinline_data -b 4096 initrd ./.rootfs
-
-# Package the unikernel (and the new initrd) to KraftCloud
-kraft pkg \
- --name $UKC_INDEX/$IMAGE \
- --plat kraftcloud \
- --arch x86_64 \
- --strategy overwrite \
- --push \
- .
+ # Build operator api + test + .env → ./bin
+ source ../../shared/build-operator-api.sh "$(pwd)/bin"
+
+ app_name=chromium-headful-build
+ docker build --platform linux/amd64 -t "$IMAGE" .
+ docker rm cnt-"$app_name" || true
+ docker create --platform linux/amd64 --name cnt-"$app_name" "$IMAGE" /bin/sh
+ docker cp cnt-"$app_name":/ ./.rootfs
+ rm -f initrd || true
+ # sudo mkfs.erofs --all-root -d2 -E noinline_data -b 4096 initrd ./.rootfs
+ # default block size is 4096 and -b fails for some reason, removed it
+ sudo mkfs.erofs --all-root -d2 -E noinline_data initrd ./.rootfs
+
+ # Package the unikernel (and the new initrd) to KraftCloud
+ kraft pkg \
+ --name $UKC_INDEX/$IMAGE \
+ --plat kraftcloud \
+ --arch x86_64 \
+ --strategy overwrite \
+ --push \
+ .
+else
+ echo "Using non-EROFS configuration..."
+ cp Kraftfile.no-erofs Kraftfile
+
+ set -euo pipefail
+
+ source ../../shared/start-buildkit.sh
+
+ # Build the API binary
+ source ../../shared/build-server.sh "$(pwd)/bin"
+
+ # Build operator api + test + .env → ./bin
+ source ../../shared/build-operator-api.sh "$(pwd)/bin"
+
+ # Package the unikernel to KraftCloud
+ kraft pkg \
+ --name $UKC_INDEX/$IMAGE \
+ --plat kraftcloud --arch x86_64 \
+ --strategy overwrite \
+ --push \
+ .
+fi
diff --git a/images/chromium-headful/client/public/browserconfig.xml b/images/chromium-headful/client/public/browserconfig.xml
index ededce1f..0fd3ece4 100644
--- a/images/chromium-headful/client/public/browserconfig.xml
+++ b/images/chromium-headful/client/public/browserconfig.xml
@@ -3,7 +3,7 @@