# syntax=docker/dockerfile:1 ########################################### # Stage 1: Alpine rootfs builder ########################################### FROM alpine:3.21 AS alpine-rootfs-stage ARG S6_OVERLAY_VERSION="3.2.1.0" ARG ROOTFS=/root-out ARG REL=v3.21 ARG ALPINE_ARCH=x86_64 ARG S6_OVERLAY_ARCH=x86_64 ARG MIRROR=http://dl-cdn.alpinelinux.org/alpine ARG PACKAGES=alpine-baselayout,alpine-keys,apk-tools,busybox,libc-utils # install packages RUN \ apk add --no-cache bash xz # build rootfs RUN \ mkdir -p "${ROOTFS}/etc/apk" && \ { \ echo "${MIRROR}/${REL}/main"; \ echo "${MIRROR}/${REL}/community"; \ } > "${ROOTFS}/etc/apk/repositories" && \ apk --root "${ROOTFS}" --no-cache --keys-dir /etc/apk/keys add --arch ${ALPINE_ARCH} --initdb ${PACKAGES//,/ } && \ sed -i -e 's/^root::/root:!:/' /root-out/etc/shadow # add s6 overlay ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-noarch.tar.xz ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz # add s6 optional symlinks ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz && unlink /root-out/usr/bin/with-contenv ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-arch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-symlinks-arch.tar.xz ########################################### # Stage 2: Ubuntu rootfs builder ########################################### FROM alpine:3 AS ubuntu-rootfs-stage ARG UBUNTU_ARCH=amd64 ARG UBUNTU_REL ARG UBUNTU_TAG ENV REL=${UBUNTU_REL} ENV ARCH=${UBUNTU_ARCH} ENV TAG=${UBUNTU_TAG} # install packages RUN \ apk add --no-cache bash curl git jq tzdata xz # grab base tarball RUN \ git clone --depth=1 https://git.launchpad.net/cloud-images/+oci/ubuntu-base -b ${TAG} /build && \ cd /build/oci && \ DIGEST=$(jq -r '.manifests[0].digest[7:]' < index.json) && \ cd /build/oci/blobs/sha256 && \ if jq -e '.layers // empty' < "${DIGEST}" >/dev/null 2>&1; then \ TARBALL=$(jq -r '.layers[0].digest[7:]' < ${DIGEST}); \ else \ MULTIDIGEST=$(jq -r ".manifests[] | select(.platform.architecture == \"${ARCH}\") | .digest[7:]" < ${DIGEST}) && \ TARBALL=$(jq -r '.layers[0].digest[7:]' < ${MULTIDIGEST}); \ fi && \ mkdir /root-out && \ tar xf ${TARBALL} -C /root-out && \ rm -rf \ /root-out/var/log/* \ /root-out/home/ubuntu \ /root-out/root/{.ssh,.bashrc,.profile} \ /build # set version for s6 overlay ARG S6_OVERLAY_VERSION="3.2.1.0" ARG S6_OVERLAY_ARCH="x86_64" # add s6 overlay ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-noarch.tar.xz ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz # add s6 optional symlinks ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-symlinks-noarch.tar.xz && unlink /root-out/usr/bin/with-contenv ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-arch.tar.xz /tmp RUN tar -C /root-out -Jxpf /tmp/s6-overlay-symlinks-arch.tar.xz ########################################### # Stage 3: Ubuntu base temp ########################################### FROM scratch AS ubuntu-base-temp COPY --from=ubuntu-rootfs-stage /root-out/ / ARG VERSION ARG MODS_VERSION="v3" ARG PKG_INST_VERSION="v1" ARG LSIOWN_VERSION="v1" ARG WITHCONTENV_VERSION="v1" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/docker-mods.${MODS_VERSION}" "/docker-mods" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/package-install.${PKG_INST_VERSION}" "/etc/s6-overlay/s6-rc.d/init-mods-package-install/run" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/lsiown.${LSIOWN_VERSION}" "/usr/bin/lsiown" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/with-contenv.${WITHCONTENV_VERSION}" "/usr/bin/with-contenv" ARG DEBIAN_FRONTEND="noninteractive" ENV HOME="/root" \ LANGUAGE="en_US.UTF-8" \ LANG="en_US.UTF-8" \ TERM="xterm" \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME="0" \ S6_VERBOSITY=1 \ S6_STAGE2_HOOK=/docker-mods \ VIRTUAL_ENV=/lsiopy \ PATH="/lsiopy/bin:$PATH" # Generate sources.list dynamically based on UBUNTU_REL and arch ARG UBUNTU_REL ARG UBUNTU_ARCH RUN \ echo "**** Generating sources.list for ${UBUNTU_REL} (${UBUNTU_ARCH}) ****" && \ if [ "${UBUNTU_ARCH}" = "amd64" ]; then \ MIRROR="http://archive.ubuntu.com/ubuntu"; \ else \ MIRROR="http://ports.ubuntu.com/ubuntu-ports"; \ fi && \ echo "deb ${MIRROR} ${UBUNTU_REL} main restricted" > /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL} main restricted" >> /etc/apt/sources.list && \ echo "deb ${MIRROR} ${UBUNTU_REL}-updates main restricted" >> /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL}-updates main restricted" >> /etc/apt/sources.list && \ echo "deb ${MIRROR} ${UBUNTU_REL} universe multiverse" >> /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL} universe multiverse" >> /etc/apt/sources.list && \ echo "deb ${MIRROR} ${UBUNTU_REL}-updates universe multiverse" >> /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL}-updates universe multiverse" >> /etc/apt/sources.list && \ echo "deb ${MIRROR} ${UBUNTU_REL}-security main restricted" >> /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL}-security main restricted" >> /etc/apt/sources.list && \ echo "deb ${MIRROR} ${UBUNTU_REL}-security universe multiverse" >> /etc/apt/sources.list && \ echo "deb-src ${MIRROR} ${UBUNTU_REL}-security universe multiverse" >> /etc/apt/sources.list RUN \ echo "**** Ripped from Ubuntu Docker Logic ****" && \ rm -f /etc/apt/sources.list.d/ubuntu.sources && \ set -xe && \ echo '#!/bin/sh' > /usr/sbin/policy-rc.d && \ echo 'exit 101' >> /usr/sbin/policy-rc.d && \ chmod +x /usr/sbin/policy-rc.d && \ dpkg-divert --local --rename --add /sbin/initctl && \ cp -a /usr/sbin/policy-rc.d /sbin/initctl && \ sed -i 's/^exit.*/exit 0/' /sbin/initctl && \ echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup && \ echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean && \ echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean && \ echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean && \ echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages && \ echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes && \ echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests && \ mkdir -p /run/systemd && \ echo 'docker' > /run/systemd/container && \ echo "**** install apt-utils and locales ****" && \ apt-get update && \ apt-get upgrade -y && \ apt-get install -y apt-utils locales && \ echo "**** install packages ****" && \ apt-get install -y \ bc catatonit cron curl gnupg jq netcat-openbsd systemd-standalone-sysusers tzdata && \ echo "**** generate locale ****" && \ locale-gen en_US.UTF-8 && \ echo "**** prepare shared folders ****" && \ mkdir -p /app /config /defaults /lsiopy && \ echo "**** create video and render groups with standard GIDs ****" && \ groupadd -g 44 video 2>/dev/null || groupmod -g 44 video 2>/dev/null || true && \ groupadd -g 106 render 2>/dev/null || groupmod -g 106 render 2>/dev/null || true && \ echo "**** cleanup ****" && \ id ubuntu >/dev/null 2>&1 && userdel ubuntu || echo "ubuntu user does not exist, skipping" && \ apt-get autoremove && \ apt-get clean && \ rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* /var/log/* # add local files for ubuntu base COPY ubuntu-root/ / ########################################### # Stage 4: Xvfb builder ########################################### FROM ubuntu-base-temp AS xvfb-builder COPY /patches /patches ENV PATCH_VERSION=21 \ HOME=/config RUN \ echo "**** build deps ****" && \ apt-get update && \ apt-get install -y devscripts dpkg-dev && \ apt-get build-dep -y xorg-server RUN \ echo "**** get and build xvfb ****" && \ apt-get source xorg-server && \ cd xorg-server-* && \ cp /patches/${PATCH_VERSION}-xvfb-dri3.patch patch.patch && \ patch -p0 < patch.patch && \ awk ' \ { print } \ /include \/usr\/share\/dpkg\/architecture.mk/ { \ print ""; \ print "GLAMOR_DEP_LIBS := $(shell pkg-config --libs gbm epoxy libdrm)"; \ print "GLAMOR_DEP_CFLAGS := $(shell pkg-config --cflags gbm epoxy libdrm)"; \ print "export DEB_LDFLAGS_PREPEND ?= $(GLAMOR_DEP_LIBS)"; \ print "export DEB_CFLAGS_PREPEND ?= $(GLAMOR_DEP_CFLAGS)"; \ } \ ' debian/rules > debian/rules.tmp && mv debian/rules.tmp debian/rules && \ debuild -us -uc -b && \ mkdir -p /build-out/usr/bin && \ mv debian/xvfb/usr/bin/Xvfb /build-out/usr/bin/ ########################################### # Stage 5: Alpine base temp ########################################### FROM alpine-rootfs-stage AS alpine-base-temp COPY --from=alpine-rootfs-stage /root-out/ / ARG BUILD_DATE ARG VERSION ARG MODS_VERSION="v3" ARG PKG_INST_VERSION="v1" ARG LSIOWN_VERSION="v1" ARG WITHCONTENV_VERSION="v1" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/docker-mods.${MODS_VERSION}" "/docker-mods" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/package-install.${PKG_INST_VERSION}" "/etc/s6-overlay/s6-rc.d/init-mods-package-install/run" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/lsiown.${LSIOWN_VERSION}" "/usr/bin/lsiown" ADD --chmod=755 "https://raw.githubusercontent.com/linuxserver/docker-mods/mod-scripts/with-contenv.${WITHCONTENV_VERSION}" "/usr/bin/with-contenv" ENV PS1="$(whoami)@$(hostname):$(pwd)\\$ " \ HOME="/root" \ TERM="xterm" \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME="0" \ S6_VERBOSITY=1 \ S6_STAGE2_HOOK=/docker-mods \ VIRTUAL_ENV=/lsiopy \ PATH="/lsiopy/bin:$PATH" RUN \ echo "**** install runtime packages ****" && \ apk add --no-cache \ alpine-release bash ca-certificates catatonit coreutils curl findutils jq \ netcat-openbsd procps-ng shadow tzdata && \ echo "**** prepare shared folders ****" && \ mkdir -p /app /config /defaults /lsiopy && \ echo "**** cleanup ****" && \ rm -rf /tmp/* # add local files for alpine base COPY alpine-root/ / ########################################### # Stage 6: Selkies frontend builder ########################################### FROM alpine-base-temp AS frontend RUN \ echo "**** install build packages ****" && \ apk add cmake git nodejs npm RUN \ echo "**** ingest code ****" && \ git clone https://github.com/selkies-project/selkies.git /src && \ cd /src && \ git checkout -f f1ade4dd700bf0157bb78a8a58eab42fbb8f02ee RUN \ echo "**** build shared core library ****" && \ cd /src/addons/gst-web-core && \ npm install && \ npm run build && \ echo "**** build multiple dashboards ****" && \ DASHBOARDS="selkies-dashboard selkies-dashboard-zinc selkies-dashboard-wish" && \ mkdir /buildout && \ for DASH in $DASHBOARDS; do \ cd /src/addons/$DASH && \ cp ../gst-web-core/dist/selkies-core.js src/ && \ npm install && \ npm run build && \ mkdir -p dist/src dist/nginx && \ cp ../gst-web-core/dist/selkies-core.js dist/src/ && \ cp ../universal-touch-gamepad/universalTouchGamepad.js dist/src/ && \ cp ../gst-web-core/nginx/* dist/nginx/ && \ cp -r ../gst-web-core/dist/jsdb dist/ && \ mkdir -p /buildout/$DASH && \ cp -ar dist/* /buildout/$DASH/; \ done ########################################### # Stage 7: Selkies base image ########################################### FROM ubuntu-base-temp AS selkies-base # set version label ARG VERSION LABEL build_version="Linuxserver.io version:- ${VERSION}" LABEL maintainer="thelamer" # env ENV DISPLAY=:1 \ PERL5LIB=/usr/local/bin \ HOME=/config \ START_DOCKER=true \ PULSE_RUNTIME_PATH=/defaults \ SELKIES_INTERPOSER=/usr/lib/selkies_joystick_interposer.so \ NVIDIA_DRIVER_CAPABILITIES=all \ DISABLE_ZINK=false \ DISABLE_DRI3=false \ DPI=96 \ TITLE=Selkies ARG APT_EXTRA_PACKAGES="" ARG LIBVA_DEB_URL="https://launchpad.net/ubuntu/+source/libva/2.22.0-3ubuntu2/+build/30591127/+files/libva2_2.22.0-3ubuntu2_amd64.deb" # Optional: jammy-friendly libva deb (built against glibc 2.35). Leave empty to skip on 22.04. ARG LIBVA_DEB_URL_JAMMY="http://launchpadlibrarian.net/587480468/libva2_2.14.0-1_amd64.deb" ARG LIBVA_LIBDIR="/usr/lib/x86_64-linux-gnu" ARG PROOT_ARCH="x86_64" ARG SELKIES_VERSION="1.6.2" ARG VIRTUALGL_VERSION="3.1.4" # Step 1: Install base system packages and Docker RUN \ echo "**** dev deps ****" && \ apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3-dev && \ echo "**** enable locales ****" && \ sed -i '/locale/d' /etc/dpkg/dpkg.cfg.d/excludes && \ echo "**** install docker ****" && \ unset VERSION && \ curl https://get.docker.com | sh && \ echo "**** install deps ****" && \ curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get update && \ echo "**** determine Ubuntu version-specific packages ****" && \ UBUNTU_VERSION=$(lsb_release -rs 2>/dev/null || grep VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') && \ LABWC_PKG="" && \ MESA_GALLIUM_PKG="" && \ if [ "$(echo "${UBUNTU_VERSION}" | cut -d. -f1)" -ge 24 ]; then \ LABWC_PKG="labwc" && \ MESA_GALLIUM_PKG="mesa-libgallium"; \ fi && \ echo "**** Installing packages (Ubuntu ${UBUNTU_VERSION}) ****" && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ breeze-cursor-theme ca-certificates cmake console-data dbus-x11 \ dunst file \ fonts-noto-cjk fonts-noto-color-emoji fonts-noto-core foot fuse-overlayfs \ g++ gcc git ${APT_EXTRA_PACKAGES} kbd ${LABWC_PKG} libatk1.0-0 libatk-bridge2.0-0 \ libev4 libfontenc1 libfreetype6 libgbm1 libgcrypt20 libgirepository-1.0-1 \ libgl1-mesa-dri libglu1-mesa libgnutls30 libgtk-3.0 libjpeg-turbo8 \ libnginx-mod-http-fancyindex libnotify-bin libnss3 libnvidia-egl-wayland1 \ libopus0 libp11-kit0 libpam0g libtasn1-6 libvulkan1 libwayland-client0 \ libwayland-cursor0 libwayland-egl1 libwayland-server0 libx11-6 \ libxau6 libxcb1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 \ libxcursor1 libxdmcp6 libxext6 libxfconf-0-3 libxfixes3 libxfont2 libxinerama1 \ libxkbcommon-dev libxkbcommon-x11-0 libxshmfence1 libxtst6 locales-all make \ ${MESA_GALLIUM_PKG} mesa-va-drivers mesa-vulkan-drivers mesa-utils vainfo vdpauinfo \ libvulkan-dev ocl-icd-libopencl1 clinfo libdrm2 libegl1 libgl1 libopengl0 libgles1 libgles2 \ libglvnd0 libglx0 libglu1 libglvnd-dev \ nginx openbox openssh-client \ openssl pciutils procps psmisc pulseaudio pulseaudio-utils python3 python3-venv \ gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav \ gstreamer1.0-vaapi \ bash-completion software-properties-common ssl-cert stterm sudo tar util-linux vulkan-tools \ wl-clipboard wtype x11-apps x11-common x11-utils x11-xkb-utils x11-xserver-utils \ xauth xclip xcvt xdg-utils xdotool xfconf xfonts-base xkb-data xsel \ xserver-common xserver-xorg-core xserver-xorg-video-amdgpu xserver-xorg-video-ati \ xserver-xorg-video-nouveau xserver-xorg-video-qxl xserver-xorg-video-dummy \ xsettingsd xterm xutils xvfb zlib1g zstd && \ echo "**** install coturn (AMD64 only) ****" && \ ARCH_CUR=$(dpkg --print-architecture) && \ if [ "${ARCH_CUR}" = "amd64" ]; then \ apt-get install -y --no-install-recommends coturn; \ fi && \ echo "**** install Intel VA drivers (AMD64 only) ****" && \ ARCH_CUR=$(dpkg --print-architecture) && \ if [ "${ARCH_CUR}" = "amd64" ]; then \ apt-get install -y --no-install-recommends i965-va-driver-shaders intel-media-va-driver-; \ apt-get install -y --no-install-recommends intel-media-va-driver-non-free; \ fi && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Step 2: Install selkies and related components RUN \ echo "**** install selkies ****" && \ SELKIES_RELEASE=$(curl -sX GET "https://api.github.com/repos/selkies-project/selkies/releases/latest" \ | awk '/tag_name/{print $4;exit}' FS='[""]') && \ curl -o /tmp/selkies.tar.gz -L \ "https://github.com/selkies-project/selkies/archive/f1ade4dd700bf0157bb78a8a58eab42fbb8f02ee.tar.gz" && \ cd /tmp && \ tar xf selkies.tar.gz && \ cd selkies-* && \ sed -i '/cryptography/d' pyproject.toml && \ UBUNTU_VERSION="$(. /etc/os-release && echo ${VERSION_ID})" && \ if [ "$(echo "${UBUNTU_VERSION}" | cut -d. -f1)" -lt 24 ]; then \ echo "**** Ubuntu ${UBUNTU_VERSION}: removing xkbcommon dependency (not compatible) ****" && \ sed -i '/xkbcommon/d' pyproject.toml; \ fi && \ python3 -m venv --system-site-packages /lsiopy && \ pip install . && \ pip install setuptools && \ if [ "${UBUNTU_VERSION}" = "22.04" ]; then \ echo "Installing pixelflux 1.4.7 for Ubuntu 22.04 (GLIBC 2.35)" && \ pip install pixelflux==1.4.7; \ elif [ "${UBUNTU_VERSION}" = "24.04" ]; then \ echo "Installing pixelflux from selkies dependencies for Ubuntu 24.04" && \ echo "pixelflux already installed"; \ else \ echo "Warning: Unknown Ubuntu version ${UBUNTU_VERSION}, using default pixelflux"; \ fi && \ echo "**** install selkies interposer ****" && \ cd addons/js-interposer && \ gcc -shared -fPIC -ldl -o selkies_joystick_interposer.so joystick_interposer.c && \ mv selkies_joystick_interposer.so /usr/lib/selkies_joystick_interposer.so && \ echo "**** install selkies fake udev ****" && \ cd ../fake-udev && \ make && \ mkdir /opt/lib && \ mv libudev.so.1.0.0-fake /opt/lib/ && \ echo "**** add icon ****" && \ mkdir -p /usr/share/selkies/www && \ curl -o /usr/share/selkies/www/icon.png \ https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/selkies-logo.png && \ curl -o /usr/share/selkies/www/favicon.ico \ https://raw.githubusercontent.com/linuxserver/docker-templates/refs/heads/master/linuxserver.io/img/selkies-icon.ico && \ rm -rf /tmp/* # Step 3: System configuration and tools RUN \ echo "**** openbox tweaks ****" && \ sed -i \ -e 's/NLIMC/NLMC/g' \ -e '/debian-menu/d' \ -e 's|| yes\n|' \ -e 's|| \n|' \ -e 's|4|1|' \ /etc/xdg/openbox/rc.xml && \ sed -i 's/--startup/--replace --startup/g' /usr/bin/openbox-session && \ echo "**** user perms ****" && \ sed -e 's/%sudo ALL=(ALL:ALL) ALL/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/g' -i /etc/sudoers && \ echo "**** proot-apps ****" && \ mkdir /proot-apps/ && \ PAPPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/linuxserver/proot-apps/releases/latest" \ | awk '/tag_name/{print $4;exit}' FS='[""]') && \ curl -L https://github.com/linuxserver/proot-apps/releases/download/${PAPPS_RELEASE}/proot-apps-${PROOT_ARCH}.tar.gz \ | tar -xzf - -C /proot-apps/ && \ echo "${PAPPS_RELEASE}" > /proot-apps/pversion && \ echo "**** dind support ****" && \ useradd -U dockremap && \ usermod -G dockremap dockremap && \ echo 'dockremap:165536:65536' >> /etc/subuid && \ echo 'dockremap:165536:65536' >> /etc/subgid && \ curl -o /usr/local/bin/dind -L \ https://raw.githubusercontent.com/moby/moby/master/hack/dind && \ chmod +x /usr/local/bin/dind && \ echo 'hosts: files dns' > /etc/nsswitch.conf && \ groupadd -f docker && \ echo "**** install VirtualGL ****" && \ cd /tmp && VIRTUALGL_VERSION="$(echo ${VIRTUALGL_VERSION} | sed 's/[^0-9\.\-]*//g')" && \ if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ dpkg --add-architecture i386 && \ apt-get update && \ curl -fsSL -O "https://github.com/VirtualGL/virtualgl/releases/download/${VIRTUALGL_VERSION}/virtualgl_${VIRTUALGL_VERSION}_amd64.deb" && \ curl -fsSL -O "https://github.com/VirtualGL/virtualgl/releases/download/${VIRTUALGL_VERSION}/virtualgl32_${VIRTUALGL_VERSION}_amd64.deb" && \ apt-get install -y --no-install-recommends "./virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "./virtualgl32_${VIRTUALGL_VERSION}_amd64.deb" && \ rm -f "virtualgl_${VIRTUALGL_VERSION}_amd64.deb" "virtualgl32_${VIRTUALGL_VERSION}_amd64.deb" && \ chmod -f u+s /usr/lib/libvglfaker.so /usr/lib/libvglfaker-nodl.so /usr/lib/libvglfaker-opencl.so /usr/lib/libdlfaker.so /usr/lib/libgefaker.so 2>/dev/null || true && \ chmod -f u+s /usr/lib32/libvglfaker.so /usr/lib32/libvglfaker-nodl.so /usr/lib32/libvglfaker-opencl.so /usr/lib32/libdlfaker.so /usr/lib32/libgefaker.so 2>/dev/null || true && \ chmod -f u+s /usr/lib/i386-linux-gnu/libvglfaker.so /usr/lib/i386-linux-gnu/libvglfaker-nodl.so /usr/lib/i386-linux-gnu/libvglfaker-opencl.so /usr/lib/i386-linux-gnu/libdlfaker.so /usr/lib/i386-linux-gnu/libgefaker.so 2>/dev/null || true; \ elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ curl -fsSL -O "https://github.com/VirtualGL/virtualgl/releases/download/${VIRTUALGL_VERSION}/virtualgl_${VIRTUALGL_VERSION}_arm64.deb" && \ apt-get update && apt-get install -y --no-install-recommends ./virtualgl_${VIRTUALGL_VERSION}_arm64.deb && \ rm -f "virtualgl_${VIRTUALGL_VERSION}_arm64.deb" && \ chmod -f u+s /usr/lib/libvglfaker.so /usr/lib/libvglfaker-nodl.so /usr/lib/libdlfaker.so /usr/lib/libgefaker.so 2>/dev/null || true; \ fi && \ echo "**** configure OpenCL and EGL for NVIDIA ****" && \ mkdir -pm755 /etc/OpenCL/vendors && echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd && \ 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 && \ echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \ echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* # Step 5: Install libva, locales, and theme ARG LIBVA_DEB_URL ARG LIBVA_DEB_URL_JAMMY ARG LIBVA_LIBDIR RUN \ UBUNTU_VERSION="$(. /etc/os-release && echo ${VERSION_ID})" && \ UBUNTU_MAJOR="$(echo "${UBUNTU_VERSION}" | cut -d. -f1)" && \ if [ "${UBUNTU_MAJOR}" -ge 24 ]; then \ echo "**** libva hack (Ubuntu ${UBUNTU_VERSION}) ****" && \ mkdir /tmp/libva && \ curl -o /tmp/libva/libva.deb -L "${LIBVA_DEB_URL}" && \ cd /tmp/libva && \ ar x libva.deb && \ tar xf data.tar.zst && \ rm -f ${LIBVA_LIBDIR}/libva.so.2* && \ cp -a usr/lib/${LIBVA_LIBDIR#/usr/lib/}/libva.so.2* ${LIBVA_LIBDIR}/; \ elif [ -n "${LIBVA_DEB_URL_JAMMY}" ]; then \ echo "**** libva hack (Ubuntu ${UBUNTU_VERSION}, jammy-compatible override) ****" && \ mkdir /tmp/libva && \ curl -o /tmp/libva/libva.deb -L "${LIBVA_DEB_URL_JAMMY}" && \ cd /tmp/libva && \ ar x libva.deb && \ tar xf data.tar.zst && \ rm -f ${LIBVA_LIBDIR}/libva.so.2* && \ cp -a usr/lib/${LIBVA_LIBDIR#/usr/lib/}/libva.so.2* ${LIBVA_LIBDIR}/; \ else \ echo "**** skip libva hack on Ubuntu ${UBUNTU_VERSION} (use distro libva) ****"; \ fi && \ echo "**** locales ****" && \ for LOCALE in $(curl -sL https://raw.githubusercontent.com/thelamer/lang-stash/master/langs); do \ localedef -i $LOCALE -f UTF-8 $LOCALE.UTF-8; \ done && \ echo "**** theme ****" && \ curl -s https://raw.githubusercontent.com/thelamer/lang-stash/master/theme.tar.gz \ | tar xzvf - -C /usr/share/themes/Clearlooks/openbox-3/ && \ echo "**** enable hardware encoders in selkies settings (arm64 only) ****" && \ ARCH_CUR=$(dpkg --print-architecture) && \ if [ "${ARCH_CUR}" != "amd64" ]; then \ python3 -c "import selkies, pathlib; p = pathlib.Path(selkies.__file__).with_name('settings.py'); t = p.read_text(); old = \"['x264enc', 'x264enc-striped', 'jpeg']\"; new = \"['x264enc', 'x264enc-striped', 'jpeg', 'nvh264enc', 'vah264enc', 'vaapih264enc']\"; \ assert old in t, f'Expected encoder list not found in {p}'; p.write_text(t.replace(old, new, 1)); print('Updated selkies encoder allowlist:', p)"; \ fi && \ rm -rf /tmp/* # Step 6: Install selkies-gstreamer (AMD64 only) ARG SELKIES_VERSION RUN \ echo "**** selkies-gstreamer (amd64 only) ****" && \ ARCH_CUR=$(dpkg --print-architecture) && \ if [ "${ARCH_CUR}" = "amd64" ]; then \ UBUNTU_VERSION="$(. /etc/os-release && echo ${VERSION_ID})"; \ BASE_URL="https://github.com/selkies-project/selkies/releases/download/v${SELKIES_VERSION}"; \ mkdir -p /opt/gstreamer && \ curl -fsSL "${BASE_URL}/gstreamer-selkies_gpl_v${SELKIES_VERSION}_ubuntu${UBUNTU_VERSION}_amd64.tar.gz" | tar -xzf - -C /opt && \ cd /tmp && curl -O -fsSL "${BASE_URL}/selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" && \ pip install --no-cache-dir "selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" && \ rm -f "selkies_gstreamer-${SELKIES_VERSION}-py3-none-any.whl" && \ curl -fsSL "${BASE_URL}/selkies-gstreamer-web_v${SELKIES_VERSION}.tar.gz" | tar -xzf - -C /opt && \ echo "**** upgrade websockets to 15.x for selkies compatibility ****" && \ pip install --force-reinstall 'websockets>=15.0'; \ else \ echo "Skipping selkies-gstreamer install on arch ${ARCH_CUR}"; \ fi && \ rm -rf /tmp/* # Step 7: Install CUDA NVRTC for WSL2 support (AMD64 only) RUN \ echo "**** install CUDA NVRTC for WSL2 cudaconvert support (amd64 only) ****" && \ ARCH_CUR=$(dpkg --print-architecture) && \ if [ "${ARCH_CUR}" = "amd64" ]; then \ UBUNTU_VERSION="$(. /etc/os-release && echo ${VERSION_ID})"; \ UBUNTU_VERSION_NODOT=$(echo "${UBUNTU_VERSION}" | tr -d '.'); \ CUDA_VERSION="12-6"; \ CUDA_KEYRING_VERSION="1.1"; \ curl -fsSL "https://developer.download.nvidia.com/compute/cuda/repos/ubuntu${UBUNTU_VERSION_NODOT}/x86_64/cuda-keyring_${CUDA_KEYRING_VERSION}-1_all.deb" -o /tmp/cuda-keyring.deb && \ dpkg -i /tmp/cuda-keyring.deb && rm /tmp/cuda-keyring.deb && \ apt-get update && \ apt-get install -y --no-install-recommends cuda-nvrtc-${CUDA_VERSION} && \ rm -rf /var/lib/apt/lists/*; \ fi # Step 8: Final cleanup RUN \ echo "**** cleanup ****" && \ apt-get purge -y --autoremove python3-dev && \ apt-get autoclean && \ rm -rf /config/.cache /config/.npm /var/lib/apt/lists/* /var/tmp/* /tmp/* # add local files - this will overwrite ubuntu-root files if conflicts exist COPY ubuntu-root/ / # Apply websockets 15.x compatibility patch for selkies-gstreamer (amd64 only) RUN if [ "$(dpkg --print-architecture)" = "amd64" ] && [ -f /usr/local/bin/patch-selkies-websockets15.py ]; then \ chmod +x /usr/local/bin/patch-selkies-websockets15.py && \ python3 /usr/local/bin/patch-selkies-websockets15.py; \ fi COPY --from=frontend /buildout /usr/share/selkies COPY --from=xvfb-builder /build-out/ / # Make TURN server script executable (AMD64 only, after final COPY) RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then chmod 755 /etc/start-turnserver.sh; fi # ports and volumes EXPOSE 3000 3001 VOLUME /config ########################################### # Stage 8: Final webtop image ########################################### FROM selkies-base # set version label ARG VERSION LABEL build_version="Linuxserver.io version:- ${VERSION}" LABEL maintainer="thelamer" ARG DEBIAN_FRONTEND="noninteractive" # title ENV TITLE="Ubuntu KDE" \ NO_GAMEPAD=true RUN \ echo "**** add icon ****" && \ curl -o /usr/share/selkies/www/icon.png \ https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/webtop-logo.png && \ echo "**** install packages ****" && \ add-apt-repository ppa:xtradeb/apps && \ apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ bc chromium dolphin gwenview kde-config-gtk-style kdialog kfind khotkeys \ kio-extras knewstuff-dialog konsole ksystemstats kubuntu-settings-desktop \ kubuntu-wallpapers kubuntu-web-shortcuts kwin-addons kwin-x11 kwrite \ plasma-desktop plasma-workspace qml-module-qt-labs-platform systemsettings kubuntu-desktop && \ if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ echo "**** install latest google-chrome (amd64) ****" && \ cd /tmp && \ curl -fsSL -o google-chrome-stable.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y ./google-chrome-stable.deb && \ rm -f /tmp/google-chrome-stable.deb; \ fi && \ echo "**** application tweaks ****" && \ sed -i 's#^Exec=.*#Exec=/usr/local/bin/wrapped-chromium#g' \ /usr/share/applications/chromium.desktop && \ echo "**** kde tweaks ****" && \ sed -i \ 's/applications:org.kde.discover.desktop,/applications:org.kde.konsole.desktop,/g' \ /usr/share/plasma/plasmoids/org.kde.plasma.taskmanager/contents/config/main.xml && \ echo "**** cleanup ****" && \ apt-get autoclean && \ rm -rf /config/.cache /config/.launchpadlib /var/lib/apt/lists/* /var/tmp/* /tmp/* # Initialize bash-completion and command-not-found databases # This ensures apt tab completion works properly RUN apt-get update && \ apt-get install -y apt-file command-not-found && \ apt-file update && \ /usr/lib/cnf-update-db && \ # Disable docker-clean that prevents apt cache completion rm -f /etc/apt/apt.conf.d/docker-clean && \ # Configure apt to keep cache files for completion mkdir -p /etc/apt/apt.conf.d && \ echo 'Dir::Cache::pkgcache "/var/cache/apt/pkgcache.bin";' > /etc/apt/apt.conf.d/00-apt-cache-completion && \ echo 'Dir::Cache::srcpkgcache "/var/cache/apt/srcpkgcache.bin";' >> /etc/apt/apt.conf.d/00-apt-cache-completion && \ # Generate apt cache for package name completion apt-cache gencaches && \ chmod 644 /var/cache/apt/*.bin && \ # Verify cache files were created ls -la /var/cache/apt/*.bin # add local files for KDE webtop COPY kde-root/ / # ports and volumes EXPOSE 3000 VOLUME /config ENTRYPOINT ["/init"]