penta 4 dni temu
rodzic
commit
04068c273d
4 zmienionych plików z 883 dodań i 777 usunięć
  1. 432 578
      Dockerfile
  2. 229 137
      entrypoint.sh
  3. 175 0
      selkies-entrypoint.sh
  4. 47 62
      supervisord.conf

+ 432 - 578
Dockerfile

@@ -1,194 +1,306 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+# syntax=docker/dockerfile:1.7
+
+# Final target:
+#   Ubuntu 24.04 + NVIDIA GLX + XFCE + PipeWire + Firefox + Selkies
+#
+# The build uses intermediate stages, but produces a single final image.
 
-# Supported base images: Ubuntu 24.04, 22.04, 20.04
-ARG DISTRIB_IMAGE=ubuntu
 ARG DISTRIB_RELEASE=24.04
-FROM ${DISTRIB_IMAGE}:${DISTRIB_RELEASE}
-ARG DISTRIB_IMAGE
-ARG DISTRIB_RELEASE
+ARG SELKIES_IMAGE=ghcr.io/selkies-project/selkies/py-build:latest
+ARG SELKIES_JS_IMAGE=ghcr.io/selkies-project/selkies/js-interposer:latest-ubuntu24.04
+ARG NVIDIA_VAAPI_DRIVER_VERSION=latest
+
+
+# -----------------------------------------------------------------------------
+# Current Selkies wheel
+# -----------------------------------------------------------------------------
+
+FROM ${SELKIES_IMAGE} AS selkies-build
+
+
+# -----------------------------------------------------------------------------
+# Selkies joystick interposer
+# -----------------------------------------------------------------------------
+
+FROM ${SELKIES_JS_IMAGE} AS selkies-js-interposer
+
+
+# -----------------------------------------------------------------------------
+# Build the current nvidia-vaapi-driver without retaining build dependencies
+# -----------------------------------------------------------------------------
+
+FROM ubuntu:${DISTRIB_RELEASE} AS nvidia-vaapi-builder
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG NVIDIA_VAAPI_DRIVER_VERSION
+
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+RUN apt-get update \
+    && apt-get install --no-install-recommends -y \
+        ca-certificates \
+        curl \
+        jq \
+        meson \
+        ninja-build \
+        pkg-config \
+        libdrm-dev \
+        libegl-dev \
+        libffmpeg-nvenc-dev \
+        libgstreamer-plugins-bad1.0-dev \
+        libva-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN set -eux; \
+    version="${NVIDIA_VAAPI_DRIVER_VERSION}"; \
+    if [[ "${version}" == "latest" ]]; then \
+        version="$(curl -fsSL https://api.github.com/repos/elFarto/nvidia-vaapi-driver/releases/latest \
+            | jq -r '.tag_name' \
+            | sed 's/^v//')"; \
+    fi; \
+    curl -fsSL \
+        "https://github.com/elFarto/nvidia-vaapi-driver/archive/refs/tags/v${version}.tar.gz" \
+        -o /tmp/nvidia-vaapi-driver.tar.gz; \
+    mkdir -p /tmp/nvidia-vaapi-driver; \
+    tar -xzf /tmp/nvidia-vaapi-driver.tar.gz \
+        --strip-components=1 \
+        -C /tmp/nvidia-vaapi-driver; \
+    cd /tmp/nvidia-vaapi-driver; \
+    meson setup build \
+        --prefix=/usr \
+        --buildtype=release; \
+    meson compile -C build; \
+    DESTDIR=/out meson install -C build
+
+
+# -----------------------------------------------------------------------------
+# Final image
+# -----------------------------------------------------------------------------
 
-LABEL maintainer="https://github.com/ehfd,https://github.com/danisla"
+FROM ubuntu:${DISTRIB_RELEASE}
 
 ARG DEBIAN_FRONTEND=noninteractive
-# Configure rootless user environment for constrained conditions without escalated root privileges inside containers
+ARG DISTRIB_RELEASE
 ARG TZ=UTC
-ENV PASSWD=mypasswd
-RUN apt-get clean && apt-get update && apt-get dist-upgrade -y && apt-get install --no-install-recommends -y \
-        apt-utils \
-        dbus-user-session \
-        fakeroot \
-        fuse \
-        kmod \
-        locales \
-        ssl-cert \
-        sudo \
-        udev \
-        tzdata && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/* && \
-    locale-gen en_US.UTF-8 && \
-    ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime && echo "${TZ}" > /etc/timezone && \
-    # Only use sudo-root for root-owned directory (/dev, /proc, /sys) or user/group permission operations, not for apt-get installation or file/directory operations
-    mv -f /usr/bin/sudo /usr/bin/sudo-root && \
-    ln -snf /usr/bin/fakeroot /usr/bin/sudo && \
-    groupadd -g 1000 ubuntu || echo 'Failed to add ubuntu group' && \
-    useradd -ms /bin/bash ubuntu -u 1000 -g 1000 || echo 'Failed to add ubuntu user' && \
-    usermod -a -G adm,audio,cdrom,dialout,dip,fax,floppy,games,input,lp,plugdev,render,ssl-cert,sudo,tape,tty,video,voice ubuntu && \
-    echo "ubuntu ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers && \
-    echo "ubuntu:${PASSWD}" | chpasswd && \
-    chown -R -f -h --no-preserve-root ubuntu:ubuntu / || echo 'Failed to set filesystem ownership in some paths to ubuntu user' && \
-    # Preserve setuid/setgid removed by chown
-    chmod -f 4755 /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/bin/chfn /usr/bin/chsh /usr/bin/mount /usr/bin/gpasswd /usr/bin/passwd /usr/bin/newgrp /usr/bin/umount /usr/bin/su /usr/bin/sudo-root /usr/bin/fusermount || echo 'Failed to set chmod setuid for some paths' && \
-    chmod -f 2755 /var/local /var/mail /usr/sbin/unix_chkpwd /usr/sbin/pam_extrausers_chkpwd /usr/bin/expiry /usr/bin/chage || echo 'Failed to set chmod setgid for some paths'
-
-# Set locales
-ENV LANG="en_US.UTF-8"
-ENV LANGUAGE="en_US:en"
-ENV LC_ALL="en_US.UTF-8"
-
-USER 1000
-# Use BUILDAH_FORMAT=docker in buildah
-SHELL ["/usr/bin/fakeroot", "--", "/bin/sh", "-c"]
-
-# Install operating system libraries or packages
-RUN apt-get update && apt-get install --no-install-recommends -y \
-        # Operating system packages
-        software-properties-common \
-        build-essential \
+ARG USER_NAME=ubuntu
+ARG USER_UID=1000
+ARG USER_GID=1000
+
+LABEL org.opencontainers.image.title="Selkies NVIDIA XFCE Desktop" \
+      org.opencontainers.image.description="XFCE remote desktop with Selkies, PipeWire and NVIDIA acceleration" \
+      org.opencontainers.image.source="https://github.com/selkies-project/selkies"
+
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+
+ENV TZ="${TZ}" \
+    LANG="en_US.UTF-8" \
+    LANGUAGE="en_US:en" \
+    LC_ALL="en_US.UTF-8" \
+    PASSWD="mypasswd" \
+    DISPLAY=":20" \
+    DISPLAY_SIZEW="1920" \
+    DISPLAY_SIZEH="1080" \
+    DISPLAY_REFRESH="60" \
+    DISPLAY_DPI="96" \
+    DISPLAY_CDEPTH="24" \
+    VIDEO_PORT="DFP" \
+    DESKTOP_SESSION="xfce" \
+    XDG_SESSION_DESKTOP="xfce" \
+    XDG_CURRENT_DESKTOP="XFCE" \
+    XDG_SESSION_TYPE="x11" \
+    SELKIES_MODE="websockets" \
+    SELKIES_PORT="8080" \
+    SELKIES_ENCODER="h264enc" \
+    SELKIES_ENABLE_RESIZE="false" \
+    SELKIES_ENABLE_BASIC_AUTH="true" \
+    NVIDIA_VISIBLE_DEVICES="all" \
+    NVIDIA_DRIVER_CAPABILITIES="all" \
+    __GL_SYNC_TO_VBLANK="0" \
+    __GLX_VENDOR_LIBRARY_NAME="nvidia" \
+    LIBVA_DRIVER_NAME="nvidia" \
+    NVD_BACKEND="direct" \
+    MOZ_DISABLE_RDD_SANDBOX="1" \
+    MOZ_X11_EGL="1" \
+    PIPEWIRE_LATENCY="128/48000" \
+    XDG_RUNTIME_DIR="/tmp/runtime-ubuntu" \
+    PIPEWIRE_RUNTIME_DIR="/tmp/runtime-ubuntu" \
+    PULSE_RUNTIME_PATH="/tmp/runtime-ubuntu/pulse" \
+    PULSE_SERVER="unix:/tmp/runtime-ubuntu/pulse/native" \
+    DBUS_SYSTEM_BUS_ADDRESS="unix:path=/tmp/runtime-ubuntu/dbus-system-bus" \
+    APPIMAGE_EXTRACT_AND_RUN="1" \
+    SUDO_EDITOR="mousepad"
+
+# Bootstrap packages needed to configure APT repositories.
+RUN apt-get update \
+    && apt-get install --no-install-recommends -y \
         ca-certificates \
-        cups-browsed \
-        cups-bsd \
-        cups-common \
-        cups-filters \
-        printer-driver-cups-pdf \
-        alsa-base \
-        alsa-utils \
-        file \
-        gnupg \
         curl \
-        wget \
+        gnupg \
+        locales \
+        ssl-cert \
+        tzdata \
+    && locale-gen en_US.UTF-8 \
+    && ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime \
+    && echo "${TZ}" > /etc/timezone \
+    && rm -rf /var/lib/apt/lists/*
+
+# Keep the same Mozilla and PipeWire repositories as the original image.
+RUN install -d -m 0755 \
+        /etc/apt/preferences.d \
+        /etc/apt/sources.list.d \
+        /etc/apt/trusted.gpg.d \
+    && printf '%s\n' \
+        'Package: firefox*' \
+        'Pin: version 1:1snap*' \
+        'Pin-Priority: -1' \
+        > /etc/apt/preferences.d/firefox-nosnap \
+    && curl -fsSL \
+        'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x738BEB9321D1AAEC13EA9391AEBDF4819BE21867' \
+        | gpg --dearmor \
+        > /etc/apt/trusted.gpg.d/mozillateam-ubuntu-ppa.gpg \
+    && echo \
+        "deb https://ppa.launchpadcontent.net/mozillateam/ppa/ubuntu noble main" \
+        > /etc/apt/sources.list.d/mozillateam-ubuntu-ppa.list \
+    && curl -fsSL \
+        'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFC43B7352BCC0EC8AF2EEB8B25088A0359807596' \
+        | gpg --dearmor \
+        > /etc/apt/trusted.gpg.d/pipewire-debian-ubuntu.gpg \
+    && echo \
+        "deb https://ppa.launchpadcontent.net/pipewire-debian/pipewire-upstream/ubuntu noble main" \
+        > /etc/apt/sources.list.d/pipewire-upstream.list \
+    && echo \
+        "deb https://ppa.launchpadcontent.net/pipewire-debian/wireplumber-upstream/ubuntu noble main" \
+        > /etc/apt/sources.list.d/wireplumber-upstream.list
+
+# Base system, desktop, Xorg, NVIDIA runtime interfaces and user tools.
+# There are deliberately no Intel/AMD VA-API or Vulkan drivers and no i386
+# architecture.
+RUN apt-get update \
+    && apt-get install --no-install-recommends -y \
+        apt-utils \
+        bash-completion \
+        binutils \
+        btop \
         bzip2 \
-        gzip \
-        xz-utils \
-        unar \
-        rar \
-        unrar \
-        zip \
-        unzip \
-        zstd \
-        gcc \
-        git \
+        clinfo \
+        dbus-user-session \
+        dbus-x11 \
+        desktop-file-utils \
         dnsutils \
-        coturn \
-        jq \
-        python3 \
-        python3-cups \
-        python3-numpy \
-        nano \
-        vim \
-        htop \
+        file \
+        firefox \
         fonts-dejavu \
-        fonts-freefont-ttf \
-        fonts-hack \
         fonts-liberation \
         fonts-noto \
         fonts-noto-cjk \
-        fonts-noto-cjk-extra \
         fonts-noto-color-emoji \
-        fonts-noto-extra \
-        fonts-noto-ui-extra \
-        fonts-noto-hinted \
         fonts-noto-mono \
-        fonts-noto-unhinted \
-        fonts-opensymbol \
-        fonts-symbola \
         fonts-ubuntu \
-        lame \
+        fuse \
+        git \
+        gvfs \
+        jq \
+        kmod \
         less \
-        libavcodec-extra \
+        libdrm2 \
+        libegl1 \
+        libelf-dev \
+        libgcrypt20 \
+        libgl1 \
+        libgles1 \
+        libgles2 \
+        libglu1-mesa \
+        libglvnd-dev \
+        libglvnd0 \
+        libglx0 \
+        libgstreamer-plugins-bad1.0-0 \
+        libopengl0 \
+        libopus0 \
+        libpci3 \
         libpulse0 \
-        supervisor \
-        net-tools \
-        packagekit-tools \
-        pkg-config \
-        mesa-utils \
-        mesa-va-drivers \
+        libsm6 \
+        libva-drm2 \
+        libva-x11-2 \
         libva2 \
+        libvulkan1 \
+        libx11-6 \
+        libx11-xcb1 \
+        libxau6 \
+        libxcb-dri3-0 \
+        libxcb1 \
+        libxdamage1 \
+        libxdmcp6 \
+        libxext6 \
+        libxfixes3 \
+        libxkbcommon0 \
+        libxtst6 \
+        libxv1 \
+        mousepad \
+        nano \
+        neofetch \
+        net-tools \
+        ocl-icd-libopencl1 \
+        pavucontrol \
+        pciutils \
+        procps \
+        psmisc \
+        python3 \
+        python3-pip \
+        python3-venv \
+        ristretto \
+        sudo \
+        supervisor \
+        thunar \
+        tumbler \
+        udev \
+        unzip \
         vainfo \
-        vdpau-driver-all \
-        libvdpau-va-gl1 \
-        vdpauinfo \
-        mesa-vulkan-drivers \
+        vim \
         vulkan-tools \
-        radeontop \
-        libvulkan-dev \
-        ocl-icd-libopencl1 \
-        clinfo \
-        xkb-data \
+        wget \
+        wmctrl \
+        x11-apps \
+        x11-utils \
+        x11-xkb-utils \
+        x11-xserver-utils \
+        x264 \
+        x265 \
         xauth \
         xbitmaps \
+        xclip \
+        xcvt \
         xdg-user-dirs \
         xdg-utils \
+        xfce4 \
+        xfce4-goodies \
+        xfce4-notifyd \
+        xfce4-pulseaudio-plugin \
+        xfce4-terminal \
         xfonts-base \
         xfonts-scalable \
         xinit \
+        xkb-data \
+        xsel \
+        xserver-xorg-core \
+        xserver-xorg-input-libinput \
+        xserver-xorg-legacy \
         xsettingsd \
-        libxrandr-dev \
-        x11-xkb-utils \
-        x11-xserver-utils \
-        x11-utils \
-        x11-apps \
-        xserver-xorg-input-all \
-        xserver-xorg-input-wacom \
-        xserver-xorg-video-all \
-        xserver-xorg-video-intel \
-        xserver-xorg-video-qxl \
-        # NVIDIA driver installer dependencies
-        libc6-dev \
-        libpci3 \
-        libelf-dev \
-        libglvnd-dev \
-        # OpenGL libraries
-        libxau6 \
-        libxdmcp6 \
-        libxcb1 \
-        libxext6 \
-        libx11-6 \
-        libxv1 \
-        libxtst6 \
-        libdrm2 \
-        libegl1 \
-        libgl1 \
-        libopengl0 \
-        libgles1 \
-        libgles2 \
-        libglvnd0 \
-        libglx0 \
-        libglu1 \
-        libsm6 \
-        # NGINX web server
-        nginx \
-        apache2-utils \
-        netcat-openbsd && \
-    # Sanitize NGINX path
-    sed -i -e 's/\/var\/log\/nginx\/access\.log/\/dev\/stdout/g' -e 's/\/var\/log\/nginx\/error\.log/\/dev\/stderr/g' -e 's/\/run\/nginx\.pid/\/tmp\/nginx\.pid/g' /etc/nginx/nginx.conf && \
-    echo "error_log /dev/stderr;" >> /etc/nginx/nginx.conf && \
-    # PipeWire and WirePlumber
-    mkdir -pm755 /etc/apt/trusted.gpg.d && curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFC43B7352BCC0EC8AF2EEB8B25088A0359807596" | gpg --dearmor -o /etc/apt/trusted.gpg.d/pipewire-debian-ubuntu-pipewire-upstream.gpg && \
-    mkdir -pm755 /etc/apt/sources.list.d && echo "deb https://ppa.launchpadcontent.net/pipewire-debian/pipewire-upstream/ubuntu $(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"') main" > "/etc/apt/sources.list.d/pipewire-debian-ubuntu-pipewire-upstream-$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"').list" && \
-    mkdir -pm755 /etc/apt/sources.list.d && echo "deb https://ppa.launchpadcontent.net/pipewire-debian/wireplumber-upstream/ubuntu $(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"') main" > "/etc/apt/sources.list.d/pipewire-debian-ubuntu-wireplumber-upstream-$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"').list" && \
-    apt-get update && apt-get install --no-install-recommends -y \
+        xterm \
+        xdotool \
+        xz-utils \
+        zip \
+        zstd \
         pipewire \
         pipewire-alsa \
         pipewire-audio-client-libraries \
         pipewire-jack \
+        pipewire-libcamera \
         pipewire-locales \
         pipewire-v4l2 \
         pipewire-vulkan \
-        pipewire-libcamera \
         gstreamer1.0-libcamera \
         gstreamer1.0-pipewire \
+        gir1.2-wp-0.5 \
         libpipewire-0.3-modules \
         libpipewire-module-x11-bell \
         libspa-0.2-bluetooth \
@@ -196,432 +308,174 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
         libspa-0.2-modules \
         wireplumber \
         wireplumber-locales \
-        gir1.2-wp-0.5 && \
-    # Packages only meant for x86_64
-    if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
-    dpkg --add-architecture i386 && apt-get update && apt-get install --no-install-recommends -y \
-        intel-gpu-tools \
-        nvtop \
-        va-driver-all \
-        i965-va-driver-shaders \
-        intel-media-va-driver-non-free \
-        va-driver-all:i386 \
-        i965-va-driver-shaders:i386 \
-        intel-media-va-driver-non-free:i386 \
-        libva2:i386 \
-        vdpau-driver-all:i386 \
-        mesa-vulkan-drivers:i386 \
-        libvulkan-dev:i386 \
-        libc6:i386 \
-        libxau6:i386 \
-        libxdmcp6:i386 \
-        libxcb1:i386 \
-        libxext6:i386 \
-        libx11-6:i386 \
-        libxv1:i386 \
-        libxtst6:i386 \
-        libdrm2:i386 \
-        libegl1:i386 \
-        libgl1:i386 \
-        libopengl0:i386 \
-        libgles1:i386 \
-        libgles2:i386 \
-        libglvnd0:i386 \
-        libglx0:i386 \
-        libglu1:i386 \
-        libsm6:i386; fi && \
-    # Install nvidia-vaapi-driver, requires the kernel parameter `nvidia_drm.modeset=1` set to run correctly
-    if [ "$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')" \> "20.04" ]; then \
-    apt-get update && apt-get install --no-install-recommends -y \
-        meson \
-        gstreamer1.0-plugins-bad \
-        libffmpeg-nvenc-dev \
-        libva-dev \
-        libegl-dev \
-        libgstreamer-plugins-bad1.0-dev && \
-    NVIDIA_VAAPI_DRIVER_VERSION="$(curl -fsSL "https://api.github.com/repos/elFarto/nvidia-vaapi-driver/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -fsSL "https://github.com/elFarto/nvidia-vaapi-driver/archive/v${NVIDIA_VAAPI_DRIVER_VERSION}.tar.gz" | tar -xzf - && mv -f nvidia-vaapi-driver* nvidia-vaapi-driver && cd nvidia-vaapi-driver && meson setup build && meson install -C build && rm -rf /tmp/*; fi && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/* && \
-    echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \
-    echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf && \
-    # Configure OpenCL manually
-    mkdir -pm755 /etc/OpenCL/vendors && echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd && \
-    # Configure Vulkan manually
-    VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)') && \
-    mkdir -pm755 /etc/vulkan/icd.d/ && echo "{\n\
-    \"file_format_version\" : \"1.0.0\",\n\
-    \"ICD\": {\n\
-        \"library_path\": \"libGLX_nvidia.so.0\",\n\
-        \"api_version\" : \"${VULKAN_API_VERSION}\"\n\
-    }\n\
-}" > /etc/vulkan/icd.d/nvidia_icd.json && \
-    # Configure EGL manually
-    mkdir -pm755 /usr/share/glvnd/egl_vendor.d/ && echo "{\n\
-    \"file_format_version\" : \"1.0.0\",\n\
-    \"ICD\": {\n\
-        \"library_path\": \"libEGL_nvidia.so.0\"\n\
-    }\n\
-}" > /usr/share/glvnd/egl_vendor.d/10_nvidia.json
-# Expose NVIDIA libraries and paths
-ENV PATH="/usr/local/nvidia/bin${PATH:+:${PATH}}"
-ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/usr/local/nvidia/lib:/usr/local/nvidia/lib64"
-# Make all NVIDIA GPUs visible by default
-ENV NVIDIA_VISIBLE_DEVICES=all
-# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
-ENV NVIDIA_DRIVER_CAPABILITIES=all
-# Disable VSYNC for NVIDIA GPUs
-ENV __GL_SYNC_TO_VBLANK=0
-# Set default DISPLAY environment
-ENV DISPLAY=":20"
-
-# Anything above this line should always be kept the same between docker-selkies-glx-desktop and docker-selkies-egl-desktop
-
-# Default environment variables (default password is "mypasswd")
-ENV DISPLAY_SIZEW=1920
-ENV DISPLAY_SIZEH=1080
-ENV DISPLAY_REFRESH=60
-ENV DISPLAY_DPI=96
-ENV DISPLAY_CDEPTH=24
-ENV VIDEO_PORT=DFP
-ENV KASMVNC_ENABLE=false
-ENV SELKIES_ENCODER=nvh264enc
-ENV SELKIES_ENABLE_RESIZE=false
-ENV SELKIES_ENABLE_BASIC_AUTH=true
-
-# Install Xorg
-RUN apt-get update && apt-get install --no-install-recommends -y \
-        xorg \
-        xterm && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/*
-
-# Anything below this line should always be kept the same between docker-selkies-glx-desktop and docker-selkies-egl-desktop
-
-# Install KDE and other GUI packages
-RUN mkdir -pm755 /etc/apt/preferences.d && echo "Package: firefox*\n\
-Pin: version 1:1snap*\n\
-Pin-Priority: -1" > /etc/apt/preferences.d/firefox-nosnap && \
-    mkdir -pm755 /etc/apt/trusted.gpg.d && curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x738BEB9321D1AAEC13EA9391AEBDF4819BE21867" | gpg --dearmor -o /etc/apt/trusted.gpg.d/mozillateam-ubuntu-ppa.gpg && \
-    mkdir -pm755 /etc/apt/sources.list.d && echo "deb https://ppa.launchpadcontent.net/mozillateam/ppa/ubuntu $(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"') main" > "/etc/apt/sources.list.d/mozillateam-ubuntu-ppa-$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"').list" && \
-    apt-get update && apt-get install --no-install-recommends -y \
-        kde-baseapps \
-        plasma-desktop \
-        plasma-workspace \
-        adwaita-icon-theme-full \
-        appmenu-gtk3-module \
-        ark \
-        aspell \
-        aspell-en \
-        breeze \
-        breeze-cursor-theme \
-        breeze-gtk-theme \
-        breeze-icon-theme \
-        dbus-x11 \
-        debconf-kde-helper \
-        desktop-file-utils \
-        dolphin \
-        dolphin-plugins \
-        enchant-2 \
-        fcitx \
-        fcitx-frontend-gtk2 \
-        fcitx-frontend-gtk3 \
-        fcitx-frontend-qt5 \
-        fcitx-module-dbus \
-        fcitx-module-kimpanel \
-        fcitx-module-lua \
-        fcitx-module-x11 \
-        fcitx-tools \
-        fcitx-hangul \
-        fcitx-libpinyin \
-        fcitx-m17n \
-        fcitx-mozc \
-        fcitx-sayura \
-        fcitx-unikey \
-        filelight \
-        frameworkintegration \
-        gwenview \
-        haveged \
-        hunspell \
-        im-config \
-        kwrite \
-        kcalc \
-        kcharselect \
-        kdeadmin \
-        kde-config-fcitx \
-        kde-config-gtk-style \
-        kde-config-gtk-style-preview \
-        kdeconnect \
-        kdegraphics-thumbnailers \
-        kde-spectacle \
-        kdf \
-        kdialog \
-        kfind \
-        kget \
-        khotkeys \
-        kimageformat-plugins \
-        kinfocenter \
-        kio \
-        kio-extras \
-        kmag \
-        kmenuedit \
-        kmix \
-        kmousetool \
-        kmouth \
-        ksshaskpass \
-        ktimer \
-        kwin-addons \
-        kwin-x11 \
-        libdbusmenu-glib4 \
-        libdbusmenu-gtk3-4 \
-        libgail-common \
-        libgdk-pixbuf2.0-bin \
-        libgtk2.0-bin \
-        libgtk-3-bin \
-        libkf5baloowidgets-bin \
-        libkf5dbusaddons-bin \
-        libkf5iconthemes-bin \
-        libkf5kdelibs4support5-bin \
-        libkf5khtml-bin \
-        libkf5parts-plugins \
-        libqt5multimedia5-plugins \
-        librsvg2-common \
-        media-player-info \
-        okular \
-        okular-extra-backends \
-        plasma-browser-integration \
-        plasma-calendar-addons \
-        plasma-dataengines-addons \
-        plasma-discover \
-        plasma-integration \
-        plasma-runners-addons \
-        plasma-widgets-addons \
-        print-manager \
-        qapt-deb-installer \
-        qml-module-org-kde-runnermodel \
-        qml-module-org-kde-qqc2desktopstyle \
-        qml-module-qtgraphicaleffects \
-        qml-module-qt-labs-platform \
-        qml-module-qtquick-xmllistmodel \
-        qt5-gtk-platformtheme \
-        qt5-image-formats-plugins \
-        qt5-style-plugins \
-        qtspeech5-flite-plugin \
-        qtvirtualkeyboard-plugin \
-        software-properties-qt \
-        sonnet-plugins \
-        sweeper \
-        systemsettings \
-        ubuntu-drivers-common \
-        vlc \
-        vlc-plugin-access-extra \
-        vlc-plugin-notify \
-        vlc-plugin-samba \
-        vlc-plugin-skins2 \
-        vlc-plugin-video-splitter \
-        vlc-plugin-visualization \
-        xdg-user-dirs \
-        xdg-utils \
-        firefox \
-        transmission-qt && \
-    apt-get install --install-recommends -y \
-        libreoffice \
-        libreoffice-kf5 \
-        libreoffice-plasma \
-        libreoffice-style-breeze && \
-    # Ensure Firefox as the default web browser
-    xdg-settings set default-web-browser firefox.desktop && \
-    update-alternatives --set x-www-browser /usr/bin/firefox && \
-    # Install Google Chrome for supported architectures
-    if [ "$(dpkg --print-architecture)" = "amd64" ]; then cd /tmp && curl -o google-chrome-stable.deb -fsSL "https://dl.google.com/linux/direct/google-chrome-stable_current_$(dpkg --print-architecture).deb" && apt-get update && apt-get install --no-install-recommends -y ./google-chrome-stable.deb && rm -f google-chrome-stable.deb && sed -i '/^Exec=/ s/$/ --password-store=basic --in-process-gpu/' /usr/share/applications/google-chrome.desktop; fi && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/* && \
-    # Fix KDE startup permissions issues in containers
-    MULTI_ARCH=$(dpkg --print-architecture | sed -e 's/arm64/aarch64-linux-gnu/' -e 's/armhf/arm-linux-gnueabihf/' -e 's/riscv64/riscv64-linux-gnu/' -e 's/ppc64el/powerpc64le-linux-gnu/' -e 's/s390x/s390x-linux-gnu/' -e 's/i.*86/i386-linux-gnu/' -e 's/amd64/x86_64-linux-gnu/' -e 's/unknown/x86_64-linux-gnu/') && \
-    cp -f /usr/lib/${MULTI_ARCH}/libexec/kf5/start_kdeinit /tmp/ && \
-    rm -f /usr/lib/${MULTI_ARCH}/libexec/kf5/start_kdeinit && \
-    cp -f /tmp/start_kdeinit /usr/lib/${MULTI_ARCH}/libexec/kf5/start_kdeinit && \
-    rm -f /tmp/start_kdeinit && \
-    # KDE disable screen lock, double-click to open instead of single-click
-    echo "[Daemon]\n\
-Autolock=false\n\
-LockOnResume=false" > /etc/xdg/kscreenlockerrc && \
-    echo "[Compositing]\n\
-Enabled=false" > /etc/xdg/kwinrc && \
-    echo "[KDE]\n\
-SingleClick=false\n\
-\n\
-[KDE Action Restrictions]\n\
-action/lock_screen=false\n\
-logout=false\n\
-\n\
-[General]\n\
-BrowserApplication=firefox.desktop" > /etc/xdg/kdeglobals
-# KDE environment variables
-ENV DESKTOP_SESSION=plasma
-ENV XDG_SESSION_DESKTOP=KDE
-ENV XDG_CURRENT_DESKTOP=KDE
-ENV XDG_SESSION_TYPE=x11
-ENV KDE_FULL_SESSION=true
-ENV KDE_SESSION_VERSION=5
-ENV KDE_APPLICATIONS_AS_SCOPE=1
-ENV KWIN_COMPOSE=N
-ENV KWIN_EFFECTS_FORCE_ANIMATIONS=0
-ENV KWIN_EXPLICIT_SYNC=0
-ENV KWIN_X11_NO_SYNC_TO_VBLANK=1
-# Use sudoedit to change protected files instead of using sudo on kwrite
-ENV SUDO_EDITOR=kwrite
-# Enable AppImage execution in containers
-ENV APPIMAGE_EXTRACT_AND_RUN=1
-# Set input to fcitx
-ENV GTK_IM_MODULE=fcitx
-ENV QT_IM_MODULE=fcitx
-ENV XIM=fcitx
-ENV XMODIFIERS="@im=fcitx"
-
-# Wine, Winetricks, and launchers, this process must be consistent with https://wiki.winehq.org/Ubuntu
-ARG WINE_BRANCH=staging
-RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \
-    mkdir -pm755 /etc/apt/keyrings && curl -fsSL -o /etc/apt/keyrings/winehq-archive.key "https://dl.winehq.org/wine-builds/winehq.key" && \
-    curl -fsSL -o "/etc/apt/sources.list.d/winehq-$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"').sources" "https://dl.winehq.org/wine-builds/ubuntu/dists/$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"')/winehq-$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"').sources" && \
-    apt-get update && apt-get install --install-recommends -y \
-        winehq-${WINE_BRANCH} && \
-    apt-get install --no-install-recommends -y \
-        q4wine \
-        playonlinux && \
-    LUTRIS_VERSION="$(curl -fsSL "https://api.github.com/repos/lutris/lutris/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -o lutris.deb -fsSL "https://github.com/lutris/lutris/releases/download/v${LUTRIS_VERSION}/lutris_${LUTRIS_VERSION}_all.deb" && apt-get install --no-install-recommends -y ./lutris.deb && rm -f lutris.deb && \
-    HEROIC_VERSION="$(curl -fsSL "https://api.github.com/repos/Heroic-Games-Launcher/HeroicGamesLauncher/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -o heroic_launcher.deb -fsSL "https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher/releases/download/v${HEROIC_VERSION}/Heroic-${HEROIC_VERSION}-linux-$(dpkg --print-architecture).deb" && apt-get install --no-install-recommends -y ./heroic_launcher.deb && rm -f heroic_launcher.deb && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/* && \
-    curl -o /usr/bin/winetricks -fsSL "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks" && \
-    chmod -f 755 /usr/bin/winetricks && \
-    curl -o /usr/share/bash-completion/completions/winetricks -fsSL "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks.bash-completion"; fi
-
-# Install latest Selkies (https://github.com/selkies-project/selkies) build, Python application, and web application, should be consistent with Selkies documentation
-ARG PIP_BREAK_SYSTEM_PACKAGES=1
-RUN apt-get update && apt-get install --no-install-recommends -y \
-        # GStreamer dependencies
-        python3-pip \
-        python3-dev \
-        python3-gi \
-        python3-setuptools \
-        python3-wheel \
-        libgcrypt20 \
-        libgirepository-1.0-1 \
-        glib-networking \
-        libglib2.0-0 \
-        libgudev-1.0-0 \
-        alsa-utils \
-        jackd2 \
-        libjack-jackd2-0 \
-        libpulse0 \
-        libopus0 \
-        libvpx-dev \
-        x264 \
-        x265 \
-        libdrm2 \
-        libegl1 \
-        libgl1 \
-        libopengl0 \
-        libgles1 \
-        libgles2 \
-        libglvnd0 \
-        libglx0 \
-        wayland-protocols \
-        libwayland-dev \
-        libwayland-egl1 \
-        wmctrl \
-        xsel \
-        xdotool \
-        x11-utils \
-        x11-xkb-utils \
-        x11-xserver-utils \
-        xserver-xorg-core \
-        libx11-xcb1 \
-        libxcb-dri3-0 \
-        libxdamage1 \
-        libxfixes3 \
-        libxv1 \
-        libxtst6 \
-        libxext6 && \
-    if [ "$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')" \> "20.04" ]; then apt-get install --no-install-recommends -y xcvt libopenh264-dev svt-av1 aom-tools; else apt-get install --no-install-recommends -y mesa-utils-extra; fi && \
-    # Automatically fetch the latest Selkies version and install the components
-    SELKIES_VERSION="$(curl -fsSL "https://api.github.com/repos/selkies-project/selkies/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /opt && curl -fsSL "https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}/gstreamer-selkies_gpl_v${SELKIES_VERSION}_ubuntu$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')_$(dpkg --print-architecture).tar.gz" | tar -xzf - && \
-    cd /tmp && curl -O -fsSL "https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}/selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" && pip3 install --no-cache-dir --force-reinstall "selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" "websockets<14.0" && rm -f "selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" && \
-    cd /opt && curl -fsSL "https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}/selkies-gstreamer-web_v${SELKIES_VERSION}.tar.gz" | tar -xzf - && \
-    cd /tmp && curl -o selkies-js-interposer.deb -fsSL "https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}/selkies-js-interposer_v${SELKIES_VERSION}_ubuntu$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '\"')_$(dpkg --print-architecture).deb" && apt-get update && apt-get install --no-install-recommends -y ./selkies-js-interposer.deb && rm -f selkies-js-interposer.deb && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/*
-
-# Install the KasmVNC web interface and RustDesk for fallback
-RUN KASMVNC_VERSION="$(curl -fsSL "https://api.github.com/repos/kasmtech/KasmVNC/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -o kasmvncserver.deb -fsSL "https://github.com/kasmtech/KasmVNC/releases/download/v${KASMVNC_VERSION}/kasmvncserver_$(grep '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2 | tr -d '\"')_${KASMVNC_VERSION}_$(dpkg --print-architecture).deb" && apt-get update && apt-get install --no-install-recommends -y ./kasmvncserver.deb libdatetime-perl && rm -f kasmvncserver.deb && \
-    RUSTDESK_VERSION="$(curl -fsSL "https://api.github.com/repos/rustdesk/rustdesk/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -o rustdesk.deb -fsSL "https://github.com/rustdesk/rustdesk/releases/download/${RUSTDESK_VERSION}/rustdesk-${RUSTDESK_VERSION}-$(uname -m).deb" && apt-get update && apt-get install --no-install-recommends -y ./rustdesk.deb && rm -f rustdesk.deb && \
-    YQ_VERSION="$(curl -fsSL "https://api.github.com/repos/mikefarah/yq/releases/latest" | jq -r '.tag_name' | sed 's/[^0-9\.\-]*//g')" && \
-    cd /tmp && curl -o yq -fsSL "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_$(dpkg --print-architecture)" && install ./yq /usr/bin/ && rm -f yq && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/debconf/* /var/log/* /tmp/* /var/tmp/*
-ENV PATH="${PATH:+${PATH}:}/usr/lib/rustdesk"
-ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/usr/lib/rustdesk/lib"
-
-# Add custom packages right below this comment, or use FROM in a new container and replace entrypoint.sh or supervisord.conf, and set ENTRYPOINT to /usr/bin/supervisord
-
-# Copy scripts and configurations used to start the container with `--chown=1000:1000`
-COPY --chown=1000:1000 entrypoint.sh /etc/entrypoint.sh
-RUN chmod -f 755 /etc/entrypoint.sh
-COPY --chown=1000:1000 selkies-gstreamer-entrypoint.sh /etc/selkies-gstreamer-entrypoint.sh
-RUN chmod -f 755 /etc/selkies-gstreamer-entrypoint.sh
-COPY --chown=1000:1000 kasmvnc-entrypoint.sh /etc/kasmvnc-entrypoint.sh
-RUN chmod -f 755 /etc/kasmvnc-entrypoint.sh
-COPY --chown=1000:1000 supervisord.conf /etc/supervisord.conf
-RUN chmod -f 755 /etc/supervisord.conf
-
-# Configure coTURN script
-RUN echo "#!/bin/bash\n\
-set -e\n\
-turnserver \
-    --verbose \
-    --listening-ip=\"0.0.0.0\" \
-    --listening-ip=\"::\" \
-    --listening-port=\"\${SELKIES_TURN_PORT:-3478}\" \
-    --realm=\"\${TURN_REALM:-example.com}\" \
-    --external-ip=\"\${TURN_EXTERNAL_IP:-\$(dig -4 TXT +short @ns1.google.com o-o.myaddr.l.google.com 2>/dev/null | { read output; if [ -z \"\$output\" ] || echo \"\$output\" | grep -q '^;;'; then exit 1; else echo \"\$(echo \$output | sed 's,\\\",,g')\"; fi } || dig -6 TXT +short @ns1.google.com o-o.myaddr.l.google.com 2>/dev/null | { read output; if [ -z \"\$output\" ] || echo \"\$output\" | grep -q '^;;'; then exit 1; else echo \"[\$(echo \$output | sed 's,\\\",,g')]\"; fi } || hostname -I 2>/dev/null | awk '{print \$1; exit}' || echo '127.0.0.1')}\" \
-    --min-port=\"\${TURN_MIN_PORT:-49152}\" \
-    --max-port=\"\${TURN_MAX_PORT:-65535}\" \
-    --channel-lifetime=\"\${TURN_CHANNEL_LIFETIME:--1}\" \
-    --lt-cred-mech \
-    --user=\"selkies:\${TURN_RANDOM_PASSWORD:-\$(tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 24)}\" \
-    --no-cli \
-    --cli-password=\"\${TURN_RANDOM_PASSWORD:-\$(tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 24)}\" \
-    --userdb=\"\${XDG_RUNTIME_DIR:-/tmp}/turnserver-turndb\" \
-    --pidfile=\"\${XDG_RUNTIME_DIR:-/tmp}/turnserver.pid\" \
-    --log-file=\"stdout\" \
-    --allow-loopback-peers \
-    \${TURN_EXTRA_ARGS} \$@\
-" > /etc/start-turnserver.sh && chmod -f 755 /etc/start-turnserver.sh
-
-SHELL ["/bin/sh", "-c"]
-
-USER 0
-# Enable sudo through sudo-root with uid 0
-RUN if [ -d "/usr/libexec/sudo" ]; then SUDO_LIB="/usr/libexec/sudo"; else SUDO_LIB="/usr/lib/sudo"; fi && \
-    chown -R -f -h --no-preserve-root root:root /usr/bin/sudo-root /etc/sudo.conf /etc/sudoers /etc/sudoers.d /etc/sudo_logsrvd.conf "${SUDO_LIB}" || echo 'Failed to provide root permissions in some paths relevant to sudo' && \
-    chmod -f 4755 /usr/bin/sudo-root || echo 'Failed to set chmod setuid for root'
-USER 1000
-
-ENV PIPEWIRE_LATENCY="128/48000"
-ENV XDG_RUNTIME_DIR=/tmp/runtime-ubuntu
-ENV PIPEWIRE_RUNTIME_DIR="${PIPEWIRE_RUNTIME_DIR:-${XDG_RUNTIME_DIR:-/tmp}}"
-ENV PULSE_RUNTIME_PATH="${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR:-/tmp}/pulse}"
-ENV PULSE_SERVER="${PULSE_SERVER:-unix:${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR:-/tmp}/pulse}/native}"
-
-# dbus-daemon to the below address is required during startup
-ENV DBUS_SYSTEM_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR:-/tmp}/dbus-system-bus"
-
-USER 1000
-ENV SHELL=/bin/bash
-ENV USER=ubuntu
-ENV HOME=/home/ubuntu
-WORKDIR /home/ubuntu
+    && apt-get clean \
+    && rm -rf \
+        /var/lib/apt/lists/* \
+        /var/cache/apt/* \
+        /var/cache/debconf/* \
+        /var/log/* \
+        /tmp/* \
+        /var/tmp/*
+
+# Install only the compiled NVIDIA VA-API runtime from the builder stage.
+COPY --from=nvidia-vaapi-builder /out/usr/ /usr/
+
+# Make NVIDIA libraries injected by NVIDIA Container Toolkit discoverable.
+RUN printf '%s\n' \
+        '/usr/local/nvidia/lib' \
+        '/usr/local/nvidia/lib64' \
+        > /etc/ld.so.conf.d/nvidia.conf \
+    && install -d -m 0755 \
+        /etc/OpenCL/vendors \
+        /etc/vulkan/icd.d \
+        /usr/share/glvnd/egl_vendor.d \
+    && echo 'libnvidia-opencl.so.1' \
+        > /etc/OpenCL/vendors/nvidia.icd \
+    && cat > /etc/vulkan/icd.d/nvidia_icd.json <<'EOF'
+{
+  "file_format_version": "1.0.0",
+  "ICD": {
+    "library_path": "libGLX_nvidia.so.0",
+    "api_version": "1.3.0"
+  }
+}
+EOF
+
+RUN cat > /usr/share/glvnd/egl_vendor.d/10_nvidia.json <<'EOF'
+{
+  "file_format_version": "1.0.0",
+  "ICD": {
+    "library_path": "libEGL_nvidia.so.0"
+  }
+}
+EOF
+
+ENV PATH="/opt/selkies/bin:/usr/local/nvidia/bin:${PATH}" \
+    LD_LIBRARY_PATH="/usr/local/nvidia/lib:/usr/local/nvidia/lib64"
+
+# Install the Selkies wheel from the official stable build image.
+COPY --from=selkies-build /opt/pypi/dist/selkies-*.whl /tmp/
+
+RUN python3 -m venv /opt/selkies \
+    && /opt/selkies/bin/python -m pip install \
+        --no-cache-dir \
+        --upgrade \
+        pip \
+    && /opt/selkies/bin/python -m pip install \
+        --no-cache-dir \
+        /tmp/selkies-*.whl \
+    && rm -f /tmp/selkies-*.whl
+
+# Preserve browser gamepad support from the original image.
+COPY --from=selkies-js-interposer /opt/*.deb /tmp/selkies-js-interposer.deb
+
+RUN apt-get update \
+    && apt-get install --no-install-recommends -y \
+        /tmp/selkies-js-interposer.deb \
+    && rm -f /tmp/selkies-js-interposer.deb \
+    && rm -rf /var/lib/apt/lists/*
+
+# Create the regular desktop user. sudo-root is kept because Xorg and the
+# NVIDIA userspace installer need a few targeted root operations at runtime.
+RUN groupadd --gid "${USER_GID}" "${USER_NAME}" \
+    && useradd \
+        --uid "${USER_UID}" \
+        --gid "${USER_GID}" \
+        --create-home \
+        --shell /bin/bash \
+        "${USER_NAME}" \
+    && for group in \
+        adm audio cdrom dialout dip fax floppy games input lp plugdev render \
+        ssl-cert sudo tape tty video voice; \
+       do \
+        getent group "${group}" >/dev/null \
+            && usermod -aG "${group}" "${USER_NAME}" \
+            || true; \
+       done \
+    && echo "${USER_NAME} ALL=(ALL:ALL) NOPASSWD: ALL" \
+        > "/etc/sudoers.d/${USER_NAME}" \
+    && chmod 0440 "/etc/sudoers.d/${USER_NAME}" \
+    && echo "${USER_NAME}:${PASSWD}" | chpasswd \
+    && cp -a /usr/bin/sudo /usr/bin/sudo-root \
+    && chown root:root /usr/bin/sudo-root \
+    && chmod 4755 /usr/bin/sudo-root \
+    && install -d \
+        -o "${USER_UID}" \
+        -g "${USER_GID}" \
+        -m 0700 \
+        /tmp/runtime-ubuntu \
+    && chown -R \
+        "${USER_UID}:${USER_GID}" \
+        "/home/${USER_NAME}" \
+        /etc/X11 \
+        /opt/selkies
+
+# XFCE defaults suitable for a permanently streamed desktop.
+RUN install -d -m 0755 \
+        /etc/xdg/xfce4/xfconf/xfce-perchannel-xml \
+        /etc/firefox/policies \
+    && if [[ -f /etc/xdg/xfce4/panel/default.xml ]]; then \
+        cp -f \
+            /etc/xdg/xfce4/panel/default.xml \
+            /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml; \
+       fi \
+    && cat > /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<channel name="xfce4-power-manager" version="1.0">
+  <property name="xfce4-power-manager" type="empty">
+    <property name="blank-on-ac" type="int" value="0"/>
+    <property name="dpms-enabled" type="bool" value="false"/>
+    <property name="lock-screen-suspend-hibernate" type="bool" value="false"/>
+  </property>
+</channel>
+EOF
+
+RUN cat > /etc/firefox/policies/policies.json <<'EOF'
+{
+  "policies": {
+    "Preferences": {
+      "gfx.x11-egl.force-enabled": {
+        "Value": true,
+        "Status": "default"
+      },
+      "media.ffmpeg.vaapi.enabled": {
+        "Value": true,
+        "Status": "default"
+      },
+      "media.hardware-video-decoding.force-enabled": {
+        "Value": true,
+        "Status": "default"
+      },
+      "media.rdd-ffmpeg.enabled": {
+        "Value": true,
+        "Status": "default"
+      }
+    }
+  }
+}
+EOF
+
+RUN update-alternatives --set x-www-browser /usr/bin/firefox \
+    || true
+
+# The three files below must be placed beside this Dockerfile.
+COPY --chown=${USER_UID}:${USER_GID} entrypoint.sh /etc/entrypoint.sh
+COPY --chown=${USER_UID}:${USER_GID} selkies-entrypoint.sh /etc/selkies-entrypoint.sh
+COPY --chown=${USER_UID}:${USER_GID} supervisord.conf /etc/supervisord.conf
+
+RUN chmod 0755 \
+        /etc/entrypoint.sh \
+        /etc/selkies-entrypoint.sh \
+        /etc/supervisord.conf
+
+USER ${USER_UID}:${USER_GID}
+
+ENV USER="${USER_NAME}" \
+    HOME="/home/${USER_NAME}" \
+    SHELL="/bin/bash"
+
+WORKDIR /home/${USER_NAME}
 
 EXPOSE 8080
 
-ENTRYPOINT ["/usr/bin/supervisord"]
+ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

+ 229 - 137
entrypoint.sh

@@ -1,154 +1,246 @@
 #!/bin/bash
 
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at https://mozilla.org/MPL/2.0/.
-
-set -e
-
-trap "echo TRAPed signal" HUP INT QUIT TERM
-
-# Wait for XDG_RUNTIME_DIR
-until [ -d "${XDG_RUNTIME_DIR}" ]; do sleep 0.5; done
-# Make user directory owned by the default user
-chown -f "$(id -nu):$(id -ng)" ~ || sudo-root chown -f "$(id -nu):$(id -ng)" ~ || chown -R -f -h --no-preserve-root "$(id -nu):$(id -ng)" ~ || sudo-root chown -R -f -h --no-preserve-root "$(id -nu):$(id -ng)" ~ || echo 'Failed to change user directory permissions, there may be permission issues'
-# Change operating system password to environment variable
-(echo "${PASSWD}"; echo "${PASSWD}";) | sudo passwd "$(id -nu)" || (echo "mypasswd"; echo "${PASSWD}"; echo "${PASSWD}";) | passwd "$(id -nu)" || echo 'Password change failed, using default password'
-# Remove directories to make sure the desktop environment starts
-rm -rf /tmp/.X* ~/.cache || echo 'Failed to clean X11 paths'
-# Change time zone from environment variable
-ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime && echo "${TZ}" | tee /etc/timezone > /dev/null || echo 'Failed to set timezone'
-# Add Lutris directories to path
-export PATH="${PATH:+${PATH}:}/usr/local/games:/usr/games"
-# Add LibreOffice to library path
-export LD_LIBRARY_PATH="/usr/lib/libreoffice/program${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
-
-# Configure joystick interposer
-export SELKIES_INTERPOSER='/usr/$LIB/selkies_joystick_interposer.so'
-export LD_PRELOAD="${SELKIES_INTERPOSER}${LD_PRELOAD:+:${LD_PRELOAD}}"
-export SDL_JOYSTICK_DEVICE=/dev/input/js0
-mkdir -pm1777 /dev/input || sudo-root mkdir -pm1777 /dev/input || echo 'Failed to create joystick interposer directory'
-touch /dev/input/js0 /dev/input/js1 /dev/input/js2 /dev/input/js3 || sudo-root touch /dev/input/js0 /dev/input/js1 /dev/input/js2 /dev/input/js3 || echo 'Failed to create joystick interposer devices'
-chmod 777 /dev/input/js* || sudo-root chmod 777 /dev/input/js* || echo 'Failed to change permission for joystick interposer devices'
-
-# Set default display
+# Adapted from docker-selkies-glx-desktop for an XFCE-only NVIDIA image.
+# This Source Code Form is subject to the Mozilla Public License, v. 2.0.
+
+set -Eeuo pipefail
+
+trap 'echo "Desktop entrypoint received a termination signal"' HUP INT QUIT TERM
+
 export DISPLAY="${DISPLAY:-:20}"
-# PipeWire-Pulse server socket path
-export PIPEWIRE_LATENCY="128/48000"
-export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}"
-export PIPEWIRE_RUNTIME_DIR="${PIPEWIRE_RUNTIME_DIR:-${XDG_RUNTIME_DIR:-/tmp}}"
-export PULSE_RUNTIME_PATH="${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR:-/tmp}/pulse}"
-export PULSE_SERVER="${PULSE_SERVER:-unix:${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR:-/tmp}/pulse}/native}"
-
-if ! command -v nvidia-xconfig >/dev/null 2>&1; then
-  # Install NVIDIA userspace driver components including X graphic libraries, keep contents same between docker-selkies-glx-desktop and docker-selkies-egl-desktop
-  export NVIDIA_DRIVER_ARCH="$(dpkg --print-architecture | sed -e 's/arm64/aarch64/' -e 's/armhf/32bit-ARM/' -e 's/i.*86/x86/' -e 's/amd64/x86_64/' -e 's/unknown/x86_64/')"
-  if [ -z "${NVIDIA_DRIVER_VERSION}" ]; then
-    # Driver version is provided by the kernel through the container toolkit, prioritize kernel driver version if available
-    if [ -f "/proc/driver/nvidia/version" ]; then
-      export NVIDIA_DRIVER_VERSION="$(head -n1 </proc/driver/nvidia/version | awk '{for(i=1;i<=NF;i++) if ($i ~ /^[0-9]+\.[0-9\.]+/) {print $i; exit}}')"
-    elif command -v nvidia-smi >/dev/null 2>&1; then
-      # Use NVIDIA-SMI when not available
-      export NVIDIA_DRIVER_VERSION="$(nvidia-smi --version | grep 'DRIVER version' | cut -d: -f2 | tr -d ' ')"
-    else
-      echo 'Failed to find NVIDIA GPU driver version, container will likely not start because of no NVIDIA container toolkit or NVIDIA GPU driver present'
-    fi
-  fi
-  cd /tmp
-  # If version is different, new installer will overwrite the existing components
-  if [ ! -f "/tmp/NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}.run" ]; then
-    # Check multiple sources in order to probe both consumer and datacenter driver versions
-    curl -fsSL -O "https://international.download.nvidia.com/XFree86/Linux-${NVIDIA_DRIVER_ARCH}/${NVIDIA_DRIVER_VERSION}/NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}.run" || curl -fsSL -O "https://international.download.nvidia.com/tesla/${NVIDIA_DRIVER_VERSION}/NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}.run" || echo 'Failed NVIDIA GPU driver download'
-  fi
-  if [ -f "/tmp/NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}.run" ]; then
-    # Extract installer before installing
-    rm -rf "NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}"
-    sh "NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}.run" -x
-    cd "NVIDIA-Linux-${NVIDIA_DRIVER_ARCH}-${NVIDIA_DRIVER_VERSION}"
-    # Run NVIDIA driver installation without the kernel modules and host components
-    sudo ./nvidia-installer --silent \
-                      --no-kernel-module \
-                      --install-compat32-libs \
-                      --no-nouveau-check \
-                      --no-nvidia-modprobe \
-                      --no-systemd \
-                      --no-rpms \
-                      --no-backup \
-                      --no-check-for-alternate-installs
-    rm -rf /tmp/NVIDIA* && cd ~
-  else
-    echo 'Unless using non-NVIDIA GPUs, container will likely not work correctly'
-  fi
+export DISPLAY_SIZEW="${DISPLAY_SIZEW:-1920}"
+export DISPLAY_SIZEH="${DISPLAY_SIZEH:-1080}"
+export DISPLAY_REFRESH="${DISPLAY_REFRESH:-60}"
+export DISPLAY_DPI="${DISPLAY_DPI:-96}"
+export DISPLAY_CDEPTH="${DISPLAY_CDEPTH:-24}"
+export VIDEO_PORT="${VIDEO_PORT:-DFP}"
+export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp/runtime-${USER:-ubuntu}}"
+export PIPEWIRE_RUNTIME_DIR="${PIPEWIRE_RUNTIME_DIR:-${XDG_RUNTIME_DIR}}"
+export PULSE_RUNTIME_PATH="${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR}/pulse}"
+export PULSE_SERVER="${PULSE_SERVER:-unix:${PULSE_RUNTIME_PATH}/native}"
+
+until [[ -d "${XDG_RUNTIME_DIR}" ]]; do
+    sleep 0.5
+done
+
+sudo-root chown "$(id -u):$(id -g)" "${HOME}" || true
+
+if [[ -n "${PASSWD:-}" ]]; then
+    printf '%s:%s\n' "${USER}" "${PASSWD}" | sudo-root chpasswd || true
 fi
 
-# Remove existing Xorg configuration
-if [ -f "/etc/X11/xorg.conf" ]; then
-  rm -f "/etc/X11/xorg.conf"
-fi
+rm -rf /tmp/.X11-unix "/tmp/.X${DISPLAY#:}-lock" "${HOME}/.cache" || true
 
-# Get first GPU device of specified visible devices when `NVIDIA_VISIBLE_DEVICES` devices are specified
-if [ "${NVIDIA_VISIBLE_DEVICES}" != "all" ] && [ "${NVIDIA_VISIBLE_DEVICES}" != "none" ] && [ "${NVIDIA_VISIBLE_DEVICES}" != "void" ] && [ -n "${NVIDIA_VISIBLE_DEVICES}" ]; then
-  export GPU_SELECT="$(nvidia-smi --id=$(echo ${NVIDIA_VISIBLE_DEVICES} | cut -d ',' -f1) --query-gpu=uuid --format=csv,noheader | head -n1)"
-# Get first GPU device out of all visible devices in other situations
-else
-  export GPU_SELECT="$(nvidia-smi --query-gpu=uuid --format=csv,noheader | head -n1)"
+sudo-root ln -snf "/usr/share/zoneinfo/${TZ:-UTC}" /etc/localtime || true
+printf '%s\n' "${TZ:-UTC}" | sudo-root tee /etc/timezone >/dev/null || true
+
+# Joystick interposer support.
+SELKIES_INTERPOSER_PATH="$(
+    find /usr/lib /usr/lib64 /usr/local/lib \
+        -name 'selkies_joystick_interposer.so' \
+        -print -quit 2>/dev/null || true
+)"
+
+if [[ -n "${SELKIES_INTERPOSER_PATH}" ]]; then
+    export LD_PRELOAD="${SELKIES_INTERPOSER_PATH}${LD_PRELOAD:+:${LD_PRELOAD}}"
+    export SDL_JOYSTICK_DEVICE="${SDL_JOYSTICK_DEVICE:-/dev/input/js0}"
+
+    sudo-root install -d -m 1777 /dev/input || true
+    for joystick in /dev/input/js{0..3}; do
+        sudo-root touch "${joystick}" || true
+        sudo-root chmod 0777 "${joystick}" || true
+    done
 fi
 
-if [ -z "${GPU_SELECT}" ] || [ "${GPU_SELECT#*No devices}" != "${GPU_SELECT}" ]; then
-  export GPU_SELECT="$(nvidia-smi --query-gpu=uuid --format=csv,noheader | head -n1)"
-  if [ -z "${GPU_SELECT}" ]; then
-    echo "No NVIDIA GPUs detected or NVIDIA Container Toolkit not configured. Exiting."
+install_nvidia_userspace() {
+    command -v nvidia-smi >/dev/null 2>&1 || {
+        echo "NVIDIA Container Toolkit has not exposed nvidia-smi." >&2
+        return 1
+    }
+
+    if command -v nvidia-xconfig >/dev/null 2>&1; then
+        return 0
+    fi
+
+    local driver_arch driver_version installer archive_dir
+
+    driver_arch="$(
+        dpkg --print-architecture |
+        sed -e 's/arm64/aarch64/' \
+            -e 's/armhf/32bit-ARM/' \
+            -e 's/i.*86/x86/' \
+            -e 's/amd64/x86_64/' \
+            -e 's/unknown/x86_64/'
+    )"
+
+    driver_version="${NVIDIA_DRIVER_VERSION:-}"
+
+    if [[ -z "${driver_version}" && -f /proc/driver/nvidia/version ]]; then
+        driver_version="$(
+            head -n 1 /proc/driver/nvidia/version |
+            awk '{for (i=1; i<=NF; i++) if ($i ~ /^[0-9]+\.[0-9.]+$/) {print $i; exit}}'
+        )"
+    fi
+
+    if [[ -z "${driver_version}" ]]; then
+        driver_version="$(
+            nvidia-smi --query-gpu=driver_version --format=csv,noheader |
+            head -n 1
+        )"
+    fi
+
+    if [[ -z "${driver_version}" ]]; then
+        echo "Unable to determine the host NVIDIA driver version." >&2
+        return 1
+    fi
+
+    installer="/tmp/NVIDIA-Linux-${driver_arch}-${driver_version}.run"
+    archive_dir="/tmp/NVIDIA-Linux-${driver_arch}-${driver_version}"
+
+    echo "Installing NVIDIA ${driver_version} userspace GLX components"
+
+    curl -fsSL \
+        "https://international.download.nvidia.com/XFree86/Linux-${driver_arch}/${driver_version}/NVIDIA-Linux-${driver_arch}-${driver_version}.run" \
+        -o "${installer}" \
+    || curl -fsSL \
+        "https://international.download.nvidia.com/tesla/${driver_version}/NVIDIA-Linux-${driver_arch}-${driver_version}.run" \
+        -o "${installer}"
+
+    rm -rf "${archive_dir}"
+    (
+        cd /tmp
+        sh "${installer}" -x
+    )
+
+    sudo-root "${archive_dir}/nvidia-installer" \
+        --silent \
+        --no-kernel-module \
+        --no-install-compat32-libs \
+        --no-nouveau-check \
+        --no-nvidia-modprobe \
+        --no-systemd \
+        --no-rpms \
+        --no-backup \
+        --no-check-for-alternate-installs
+
+    rm -rf "${installer}" "${archive_dir}"
+}
+
+install_nvidia_userspace
+
+# Always select the first GPU visible inside the container. Device indexes are
+# renumbered by NVIDIA Container Toolkit, so the host-side index is unreliable.
+GPU_SELECT="$(
+    nvidia-smi --query-gpu=uuid --format=csv,noheader |
+    head -n 1
+)"
+
+if [[ -z "${GPU_SELECT}" ]]; then
+    echo "No visible NVIDIA GPU was detected." >&2
     exit 1
-  fi
 fi
 
-# Setting `VIDEO_PORT` to none disables RANDR/XRANDR, causing potential compatibility issues, set to DFP if using datacenter GPUs
-if [ "$(echo ${VIDEO_PORT} | tr '[:upper:]' '[:lower:]')" = "none" ]; then
-  export CONNECTED_MONITOR="--use-display-device=None"
-# The X server is otherwise deliberately set to a specific video port despite not being plugged to enable RANDR/XRANDR, monitor will display the screen if plugged to the specific port
+HEX_ID="$(
+    nvidia-smi \
+        --id="${GPU_SELECT}" \
+        --query-gpu=pci.bus_id \
+        --format=csv,noheader |
+    head -n 1
+)"
+
+IFS=':.' read -r domain bus device function <<<"${HEX_ID}"
+
+BUS_ID="PCI:$((16#${bus}))@$((16#${domain})):$((16#${device})):$((16#${function}))"
+MODELINE="$(cvt -r "${DISPLAY_SIZEW}" "${DISPLAY_SIZEH}" "${DISPLAY_REFRESH}" | sed -n '2p')"
+MODE_NAME="$(awk '{print $2}' <<<"${MODELINE}" | tr -d '"')"
+
+if [[ "${VIDEO_PORT,,}" == "none" ]]; then
+    CONNECTED_MONITOR=(--use-display-device=None)
 else
-  export CONNECTED_MONITOR="--connected-monitor=${VIDEO_PORT:-DFP}"
+    CONNECTED_MONITOR=(--connected-monitor="${VIDEO_PORT}")
 fi
 
-# Bus ID from nvidia-smi is in hexadecimal format and should be converted to decimal format (including the domain) which Xorg understands, required because nvidia-xconfig doesn't work as intended in a container
-HEX_ID="$(nvidia-smi --query-gpu=pci.bus_id --id=${GPU_SELECT} --format=csv,noheader | head -n1)"
-IFS=":." ARR_ID=(${HEX_ID})
-unset IFS
-BUS_ID="PCI:$(printf '%u' 0x${ARR_ID[1]})@$(printf '%u' 0x${ARR_ID[0]}):$(printf '%u' 0x${ARR_ID[2]}):$(printf '%u' 0x${ARR_ID[3]})"
-# A custom modeline should be generated because there is no monitor to fetch this information normally
-export MODELINE="$(cvt -r ${DISPLAY_SIZEW} ${DISPLAY_SIZEH} ${DISPLAY_REFRESH} | sed -n 2p)"
-# Generate /etc/X11/xorg.conf with nvidia-xconfig
-nvidia-xconfig --virtual="${DISPLAY_SIZEW}x${DISPLAY_SIZEH}" --depth="${DISPLAY_CDEPTH}" --mode="$(echo ${MODELINE} | awk '{print $2; exit}' | tr -d '\"')" --allow-empty-initial-configuration --no-probe-all-gpus --busid="${BUS_ID}" --include-implicit-metamodes --mode-debug --no-sli --no-base-mosaic --only-one-x-screen ${CONNECTED_MONITOR}
-# Guarantee that the X server starts without a monitor by adding more options to the configuration
-sed -i '/Driver\s\+"nvidia"/a\    Option         "ModeValidation" "NoMaxPClkCheck,NoEdidMaxPClkCheck,NoMaxSizeCheck,NoHorizSyncCheck,NoVertRefreshCheck,NoVirtualSizeCheck,NoExtendedGpuCapabilitiesCheck,NoTotalSizeCheck,NoDualLinkDVICheck,NoDisplayPortBandwidthCheck,AllowNon3DVisionModes,AllowNonHDMI3DModes,AllowNonEdidModes,NoEdidHDMI2Check,AllowDpInterlaced"' /etc/X11/xorg.conf
-# Support external GPUs
-sed -i '/Driver\s\+"nvidia"/a\    Option         "AllowExternalGpus" "True"' /etc/X11/xorg.conf
-# Add custom generated modeline to the configuration
-sed -i '/Section\s\+"Monitor"/a\    '"${MODELINE}" /etc/X11/xorg.conf
-# Disable screen blanking in X11
-sed -i '/"DPMS"/d' /etc/X11/xorg.conf
-sed -i '/Section\s\+"Monitor"/a\    Option         "DPMS" "False"' /etc/X11/xorg.conf
-# Prevent interference between GPUs, add this to the host or other containers running Xorg as well
-echo -e "Section \"ServerFlags\"\n    Option         \"DontVTSwitch\" \"True\"\n    Option         \"DontZap\" \"True\"\n    Option         \"AllowMouseOpenFail\" \"True\"\n    Option         \"AutoAddGPU\" \"False\"\nEndSection" | tee -a /etc/X11/xorg.conf > /dev/null
-
-# Real sudo (sudo-root) is required in Ubuntu 20.04 but not in newer Ubuntu, this symbolic link enables running Xorg inside a container with `-sharevts`
-ln -snf /dev/ptmx /dev/tty7 || sudo-root ln -snf /dev/ptmx /dev/tty7 || echo 'Failed to create /dev/tty7 device'
-
-# Run Xorg server with required extensions
-/usr/lib/xorg/Xorg "${DISPLAY}" vt7 -noreset -novtswitch -sharevts -nolisten "tcp" -ac -dpi "${DISPLAY_DPI}" +extension "COMPOSITE" +extension "DAMAGE" +extension "GLX" +extension "RANDR" +extension "RENDER" +extension "MIT-SHM" +extension "XFIXES" +extension "XTEST" &
-
-# Wait for X server to start
-echo 'Waiting for X Socket' && until [ -S "/tmp/.X11-unix/X${DISPLAY#*:}" ]; do sleep 0.5; done && echo 'X Server is ready'
-
-# Start KDE desktop environment
-export XDG_SESSION_ID="${DISPLAY#*:}"
-export QT_LOGGING_RULES="${QT_LOGGING_RULES:-*.debug=false;qt.qpa.*=false}"
-/usr/bin/dbus-launch --exit-with-session /usr/bin/startplasma-x11 &
+sudo-root rm -f /etc/X11/xorg.conf
+
+sudo-root nvidia-xconfig \
+    --virtual="${DISPLAY_SIZEW}x${DISPLAY_SIZEH}" \
+    --depth="${DISPLAY_CDEPTH}" \
+    --mode="${MODE_NAME}" \
+    --allow-empty-initial-configuration \
+    --no-probe-all-gpus \
+    --busid="${BUS_ID}" \
+    --include-implicit-metamodes \
+    --mode-debug \
+    --no-sli \
+    --no-base-mosaic \
+    --only-one-x-screen \
+    "${CONNECTED_MONITOR[@]}"
+
+sudo-root sed -i \
+    '/Driver[[:space:]]*"nvidia"/a\    Option "ModeValidation" "NoMaxPClkCheck,NoEdidMaxPClkCheck,NoMaxSizeCheck,NoHorizSyncCheck,NoVertRefreshCheck,NoVirtualSizeCheck,NoExtendedGpuCapabilitiesCheck,NoTotalSizeCheck,NoDualLinkDVICheck,NoDisplayPortBandwidthCheck,AllowNon3DVisionModes,AllowNonHDMI3DModes,AllowNonEdidModes,NoEdidHDMI2Check,AllowDpInterlaced"' \
+    /etc/X11/xorg.conf
+
+sudo-root sed -i \
+    '/Driver[[:space:]]*"nvidia"/a\    Option "AllowExternalGpus" "True"' \
+    /etc/X11/xorg.conf
+
+sudo-root sed -i \
+    '/Section[[:space:]]*"Monitor"/a\    '"${MODELINE}" \
+    /etc/X11/xorg.conf
+
+sudo-root sed -i '/"DPMS"/d' /etc/X11/xorg.conf
+sudo-root sed -i \
+    '/Section[[:space:]]*"Monitor"/a\    Option "DPMS" "False"' \
+    /etc/X11/xorg.conf
+
+cat <<'EOF' | sudo-root tee -a /etc/X11/xorg.conf >/dev/null
+Section "ServerFlags"
+    Option "DontVTSwitch" "True"
+    Option "DontZap" "True"
+    Option "AllowMouseOpenFail" "True"
+    Option "AutoAddGPU" "False"
+EndSection
+EOF
+
+sudo-root ln -snf /dev/ptmx /dev/tty7
+
+/usr/lib/xorg/Xorg \
+    "${DISPLAY}" \
+    vt7 \
+    -noreset \
+    -novtswitch \
+    -sharevts \
+    -nolisten tcp \
+    -ac \
+    -dpi "${DISPLAY_DPI}" \
+    +extension COMPOSITE \
+    +extension DAMAGE \
+    +extension GLX \
+    +extension RANDR \
+    +extension RENDER \
+    +extension MIT-SHM \
+    +extension XFIXES \
+    +extension XTEST \
+    &
+
+XORG_PID=$!
+
+echo "Waiting for X socket ${DISPLAY}"
+until [[ -S "/tmp/.X11-unix/X${DISPLAY#*:}" ]]; do
+    if ! kill -0 "${XORG_PID}" 2>/dev/null; then
+        echo "Xorg exited before creating its socket." >&2
+        exit 1
+    fi
+    sleep 0.5
+done
+
+xset -dpms || true
+xset s off || true
+xset s noblank || true
 
-# Start Fcitx input method framework
-/usr/bin/fcitx &
+export XDG_SESSION_ID="${DISPLAY#*:}"
 
-# Add custom processes right below this line, or within `supervisord.conf` to perform service management similar to systemd
+xfce4-session &
+XFCE_PID=$!
 
-echo "Session Running. Press [Return] to exit."
-read
+wait "${XFCE_PID}"

+ 175 - 0
selkies-entrypoint.sh

@@ -0,0 +1,175 @@
+#!/bin/bash
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+set -Eeuo pipefail
+
+bool_is_true() {
+    case "${1,,}" in
+        1|true|yes|on) return 0 ;;
+        *) return 1 ;;
+    esac
+}
+
+# Runtime and display configuration.
+export DISPLAY="${DISPLAY:-:20}"
+export PIPEWIRE_LATENCY="${PIPEWIRE_LATENCY:-128/48000}"
+export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp/runtime-${USER:-desktop}}"
+export PIPEWIRE_RUNTIME_DIR="${PIPEWIRE_RUNTIME_DIR:-${XDG_RUNTIME_DIR}}"
+export PULSE_RUNTIME_PATH="${PULSE_RUNTIME_PATH:-${XDG_RUNTIME_DIR}/pulse}"
+export PULSE_SERVER="${PULSE_SERVER:-unix:${PULSE_RUNTIME_PATH}/native}"
+
+install -d -m 0700 "${XDG_RUNTIME_DIR}"
+
+# Enable the optional joystick interposer only when it is actually installed.
+SELKIES_INTERPOSER_PATH="$(
+    find /usr/lib /usr/lib64 /usr/local/lib \
+        -name 'selkies_joystick_interposer.so' \
+        -print -quit 2>/dev/null || true
+)"
+
+if [[ -n "${SELKIES_INTERPOSER_PATH}" ]]; then
+    export LD_PRELOAD="${SELKIES_INTERPOSER_PATH}${LD_PRELOAD:+:${LD_PRELOAD}}"
+    export SDL_JOYSTICK_DEVICE="${SDL_JOYSTICK_DEVICE:-/dev/input/js0}"
+    echo "Joystick interposer enabled: ${SELKIES_INTERPOSER_PATH}"
+fi
+
+# NVIDIA NVRTC is not always mounted by the NVIDIA container runtime.
+# Keep the upstream-compatible runtime download, but place it in a
+# user-writable directory instead of modifying /usr at container startup.
+install_nvrtc() {
+    bool_is_true "${SELKIES_DOWNLOAD_NVRTC:-true}" || return 0
+    command -v nvidia-smi >/dev/null 2>&1 || return 0
+    nvidia-smi >/dev/null 2>&1 || return 0
+
+    if ldconfig -p 2>/dev/null | grep -q 'libnvrtc\.so'; then
+        echo "NVRTC already available"
+        return 0
+    fi
+
+    local cuda_version cuda_major nvrtc_arch multiarch
+    local nvrtc_url nvrtc_archive fallback_version destination
+
+    cuda_version="$(
+        nvidia-smi --version |
+        awk -F: '/CUDA Version/{gsub(/[[:space:]]/, "", $2); print $2; exit}'
+    )"
+
+    if [[ -z "${cuda_version}" ]]; then
+        echo "WARNING: NVIDIA is available, but its CUDA compatibility version could not be detected" >&2
+        return 0
+    fi
+
+    cuda_major="${cuda_version%%.*}"
+    if [[ "${cuda_major}" =~ ^[0-9]+$ ]] && (( cuda_major >= 13 )); then
+        cuda_version="12.9"
+    fi
+
+    nvrtc_arch="$(
+        dpkg --print-architecture |
+        sed -e 's/arm64/sbsa/' \
+            -e 's/ppc64el/ppc64le/' \
+            -e 's/i.*86/x86/' \
+            -e 's/amd64/x86_64/' \
+            -e 's/unknown/x86_64/'
+    )"
+
+    multiarch="$(
+        dpkg --print-architecture |
+        sed -e 's/arm64/aarch64-linux-gnu/' \
+            -e 's/armhf/arm-linux-gnueabihf/' \
+            -e 's/riscv64/riscv64-linux-gnu/' \
+            -e 's/ppc64el/powerpc64le-linux-gnu/' \
+            -e 's/s390x/s390x-linux-gnu/' \
+            -e 's/i.*86/i386-linux-gnu/' \
+            -e 's/amd64/x86_64-linux-gnu/' \
+            -e 's/unknown/x86_64-linux-gnu/'
+    )"
+
+    nvrtc_url="https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvrtc/linux-${nvrtc_arch}/"
+
+    nvrtc_archive="$(
+        curl -fsSL "${nvrtc_url}" |
+        grep -oP "(?<=href=')cuda_nvrtc-linux-${nvrtc_arch}-${cuda_version}\.[0-9]+-archive\.tar\.xz" |
+        sort -V |
+        tail -n 1 ||
+        true
+    )"
+
+    if [[ -z "${nvrtc_archive}" ]]; then
+        fallback_version="${cuda_version}.0"
+        nvrtc_archive="$(
+            {
+                curl -fsSL "${nvrtc_url}" |
+                    grep -oP "(?<=href=')cuda_nvrtc-linux-${nvrtc_arch}-.*?\.tar\.xz" ||
+                    true
+                echo "cuda_nvrtc-linux-${nvrtc_arch}-${fallback_version}-archive.tar.xz"
+            } |
+            sort -V |
+            grep -B 1 --fixed-strings "${fallback_version}" |
+            head -n 1 ||
+            true
+        )"
+    fi
+
+    if [[ -z "${nvrtc_archive}" ]]; then
+        echo "WARNING: no compatible NVRTC archive found for CUDA ${cuda_version}" >&2
+        return 0
+    fi
+
+    destination="${NVRTC_DEST_PREFIX:-${XDG_CACHE_HOME:-${HOME:-/tmp}/.cache}/selkies/nvrtc}/lib/${multiarch}"
+    install -d -m 0755 "${destination}"
+
+    if compgen -G "${destination}/libnvrtc*" >/dev/null; then
+        echo "Using cached NVRTC runtime from ${destination}"
+        export LD_LIBRARY_PATH="${destination}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
+        return 0
+    fi
+
+    echo "Installing NVRTC runtime: ${nvrtc_archive}"
+    rm -rf /tmp/selkies-nvrtc
+    install -d /tmp/selkies-nvrtc
+
+    curl -fsSL "${nvrtc_url}${nvrtc_archive}" |
+        tar -xJf - -C /tmp/selkies-nvrtc
+
+    find /tmp/selkies-nvrtc -type f -name 'libnvrtc*' \
+        -exec install -m 0755 {} "${destination}/" \;
+
+    rm -rf /tmp/selkies-nvrtc
+    export LD_LIBRARY_PATH="${destination}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
+}
+
+install_nvrtc
+
+echo "Waiting for X socket ${DISPLAY}"
+until [[ -S "/tmp/.X11-unix/X${DISPLAY#*:}" ]]; do
+    sleep 0.5
+done
+
+echo "Waiting for PipeWire-Pulse socket ${PULSE_RUNTIME_PATH}/native"
+until [[ -S "${PULSE_RUNTIME_PATH}/native" ]]; do
+    sleep 0.5
+done
+
+# The current Selkies wheel contains the backend and HTML5 client.
+# WebSocket transport needs only this one listening port.
+export SELKIES_MODE="${SELKIES_MODE:-websockets}"
+export SELKIES_ENCODER="${SELKIES_ENCODER:-h264enc}"
+export SELKIES_ENABLE_RESIZE="${SELKIES_ENABLE_RESIZE:-false}"
+export SELKIES_ENABLE_BASIC_AUTH="${SELKIES_ENABLE_BASIC_AUTH:-true}"
+export SELKIES_BASIC_AUTH_USER="${SELKIES_BASIC_AUTH_USER:-${USER:-desktop}}"
+export SELKIES_BASIC_AUTH_PASSWORD="${SELKIES_BASIC_AUTH_PASSWORD:-${PASSWD:-mypasswd}}"
+
+echo "Starting Selkies on ${SELKIES_ADDR:-0.0.0.0}:${SELKIES_PORT:-8080}"
+exec selkies \
+    --addr="${SELKIES_ADDR:-0.0.0.0}" \
+    --port="${SELKIES_PORT:-8080}" \
+    --enable_https="false" \
+    --mode="${SELKIES_MODE}" \
+    --encoder="${SELKIES_ENCODER}" \
+    --enable_resize="${SELKIES_ENABLE_RESIZE}" \
+    --enable_basic_auth="${SELKIES_ENABLE_BASIC_AUTH}" \
+    "$@"

+ 47 - 62
supervisord.conf

@@ -7,120 +7,105 @@ file=/tmp/supervisor.sock
 chmod=0700
 
 [supervisord]
-logfile=/tmp/supervisord.log
-logfile_maxbytes=5MB
-logfile_backups=0
+logfile=/dev/null
 loglevel=info
 pidfile=/tmp/supervisord.pid
 childlogdir=/tmp
 nodaemon=true
 
 [rpcinterface:supervisor]
-supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface
 
 [supervisorctl]
 serverurl=unix:///tmp/supervisor.sock
 
 [include]
-files = /etc/supervisor/conf.d/*.conf
+files=/etc/supervisor/conf.d/*.conf
 
+# Starts Xorg and the XFCE session.
 [program:entrypoint]
-command=bash -c "dbus-run-session -- /etc/entrypoint.sh"
-stdout_logfile=/tmp/entrypoint.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
+command=/usr/bin/dbus-run-session -- /etc/entrypoint.sh
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
 redirect_stderr=true
 stopasgroup=true
+killasgroup=true
 stopsignal=INT
 autostart=true
 autorestart=true
+startretries=20
 priority=1
 
+# A lightweight system bus at a writable, rootless-compatible path.
 [program:dbus]
-command=bash -c "mkdir -pm700 \"${XDG_RUNTIME_DIR}\"; chown -f \"$(id -nu):$(id -ng)\" \"${XDG_RUNTIME_DIR}\"; chmod -f 700 \"${XDG_RUNTIME_DIR}\"; dbus-daemon --system --nofork --nosyslog --nopidfile --address=\"${DBUS_SYSTEM_BUS_ADDRESS}\""
+command=/bin/bash -c 'install -d -m 0700 "${XDG_RUNTIME_DIR}" && dbus-daemon --system --nofork --nosyslog --nopidfile --address="${DBUS_SYSTEM_BUS_ADDRESS}"'
 environment=DISPLAY="%(ENV_DISPLAY)s",XDG_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",DBUS_SYSTEM_BUS_ADDRESS="%(ENV_DBUS_SYSTEM_BUS_ADDRESS)s"
-stdout_logfile=/tmp/dbus.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
 redirect_stderr=true
 stopasgroup=true
+killasgroup=true
 stopsignal=INT
 autostart=true
 autorestart=true
+startretries=20
 priority=1
 
-[program:selkies-gstreamer]
-command=bash -c "if [ \"$(echo ${KASMVNC_ENABLE} | tr '[:upper:]' '[:lower:]')\" != \"true\" ]; then /etc/selkies-gstreamer-entrypoint.sh; else sleep infinity; fi"
-stdout_logfile=/tmp/selkies-gstreamer-entrypoint.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
-redirect_stderr=true
-stopasgroup=true
-stopsignal=INT
-autostart=true
-autorestart=true
-priority=20
-
-[program:kasmvnc]
-command=bash -c "if [ \"$(echo ${KASMVNC_ENABLE} | tr '[:upper:]' '[:lower:]')\" = \"true\" ]; then /etc/kasmvnc-entrypoint.sh; else sleep infinity; fi"
-stdout_logfile=/tmp/kasmvnc-entrypoint.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
-redirect_stderr=true
-stopasgroup=true
-stopsignal=INT
-autostart=true
-autorestart=true
-priority=20
-
-[program:nginx]
-command=bash -c "until nc -z localhost ${SELKIES_PORT:-8081}; do sleep 0.5; done; /usr/sbin/nginx -g \"daemon off;\""
-stdout_logfile=/tmp/nginx.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
-redirect_stderr=true
-stopasgroup=true
-stopsignal=INT
-autostart=true
-autorestart=true
-priority=30
-
 [group:pipewire-group]
-program=pipewire,wireplumber,pipewire-pulse
+programs=pipewire,wireplumber,pipewire-pulse
 priority=10
 
 [program:pipewire]
-command=bash -c "until [ -S \"/tmp/.X11-unix/X${DISPLAY#*:}\" ]; do sleep 0.5; done; dbus-run-session -- /usr/bin/pipewire"
+command=/bin/bash -c 'until [ -S "/tmp/.X11-unix/X${DISPLAY#*:}" ]; do sleep 0.5; done; exec dbus-run-session -- /usr/bin/pipewire'
 environment=PIPEWIRE_LATENCY="128/48000",DISPLAY="%(ENV_DISPLAY)s",DISABLE_RTKIT="y",XDG_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",DBUS_SYSTEM_BUS_ADDRESS="%(ENV_DBUS_SYSTEM_BUS_ADDRESS)s",PIPEWIRE_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",PULSE_RUNTIME_PATH="%(ENV_XDG_RUNTIME_DIR)s/pulse"
-stdout_logfile=/tmp/pipewire.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
 redirect_stderr=true
 stopasgroup=true
+killasgroup=true
 stopsignal=INT
 autostart=true
 autorestart=true
+startretries=20
 
 [program:wireplumber]
-command=bash -c "until [ \"$(echo ${XDG_RUNTIME_DIR}/pipewire-*.lock)\" != \"${XDG_RUNTIME_DIR}/pipewire-*.lock\" ]; do sleep 0.5; done; dbus-run-session -- /usr/bin/wireplumber"
+command=/bin/bash -c 'until compgen -G "${XDG_RUNTIME_DIR}/pipewire-*.lock" >/dev/null; do sleep 0.5; done; exec dbus-run-session -- /usr/bin/wireplumber'
 environment=PIPEWIRE_LATENCY="128/48000",DISPLAY="%(ENV_DISPLAY)s",DISABLE_RTKIT="y",XDG_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",DBUS_SYSTEM_BUS_ADDRESS="%(ENV_DBUS_SYSTEM_BUS_ADDRESS)s",PIPEWIRE_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",PULSE_RUNTIME_PATH="%(ENV_XDG_RUNTIME_DIR)s/pulse"
-stdout_logfile=/tmp/wireplumber.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
 redirect_stderr=true
 stopasgroup=true
+killasgroup=true
 stopsignal=INT
 autostart=true
 autorestart=true
+startretries=20
 
 [program:pipewire-pulse]
-command=bash -c "until [ \"$(echo ${XDG_RUNTIME_DIR}/pipewire-*.lock)\" != \"${XDG_RUNTIME_DIR}/pipewire-*.lock\" ]; do sleep 0.5; done; dbus-run-session -- /usr/bin/pipewire-pulse"
+command=/bin/bash -c 'until compgen -G "${XDG_RUNTIME_DIR}/pipewire-*.lock" >/dev/null; do sleep 0.5; done; exec dbus-run-session -- /usr/bin/pipewire-pulse'
 environment=PIPEWIRE_LATENCY="128/48000",DISPLAY="%(ENV_DISPLAY)s",DISABLE_RTKIT="y",XDG_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",DBUS_SYSTEM_BUS_ADDRESS="%(ENV_DBUS_SYSTEM_BUS_ADDRESS)s",PIPEWIRE_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",PULSE_RUNTIME_PATH="%(ENV_XDG_RUNTIME_DIR)s/pulse"
-stdout_logfile=/tmp/pipewire-pulse.log
-stdout_logfile_maxbytes=5MB
-stdout_logfile_backups=0
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
 redirect_stderr=true
 stopasgroup=true
+killasgroup=true
 stopsignal=INT
 autostart=true
 autorestart=true
+startretries=20
+
+# Selkies now serves its own web client directly; no NGINX or VNC process.
+[program:selkies]
+command=/etc/selkies-entrypoint.sh
+environment=DISPLAY="%(ENV_DISPLAY)s",XDG_RUNTIME_DIR="%(ENV_XDG_RUNTIME_DIR)s",PIPEWIRE_RUNTIME_DIR="%(ENV_PIPEWIRE_RUNTIME_DIR)s",PULSE_RUNTIME_PATH="%(ENV_PULSE_RUNTIME_PATH)s",PULSE_SERVER="%(ENV_PULSE_SERVER)s"
+stdout_logfile=/dev/fd/1
+stdout_logfile_maxbytes=0
+redirect_stderr=true
+stopasgroup=true
+killasgroup=true
+stopsignal=INT
+autostart=true
+autorestart=true
+startsecs=1
+startretries=20
+priority=20