From 020def0305cfb9fabd36371d2a112f6ee52c6533 Mon Sep 17 00:00:00 2001 From: Wai Cheng <58031942+kwaich@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:01:59 +0800 Subject: [PATCH] intel: fix VA-API encoders not registering (no element "vah264enc") (#721) * intel: fix VA-API encoders not registering (no element "vah264enc") The intel images fail to stream with a hardware encoder on stock config: every client connect ends with '(pipeline error) no element "vah264enc"'. There are three problems, all of which are needed to fix it: 1. The render group never reaches the neko process. supervisord resolves the supplementary groups of each program from /etc/group when it spawns it, but 'rendergroup-init' (which calls usermod) is spawned in the same pass, so the neko process usually starts before the group has been assigned and cannot access /dev/dri/render*. Assign the group in an entrypoint, before supervisord starts, instead. 2. libva does not initialize the X11 display without an explicitly selected driver in this image, it fails with 'vaGetDriverNames() failed'. Set LIBVA_DRIVER_NAME=iHD, which is the only driver installed by the intel image. 3. GStreamer only registers the VA-API elements if a usable VA display is found while the plugins are scanned for the first time, and the outcome is cached in the user's registry. neko performs that scan at startup, before the X server is ready, so the cache ends up without vah264enc/vah264dec and stays that way. Wait for VA to be usable and drop the stale registry before starting neko. * intel: keep the wrapper and the supervisord override in the intel image Per the review on #721: no wrapper in every flavour with a runtime 'vainfo' check, it should only be in the intel image. - neko-serve moved to runtime/intel/neko-serve and is copied only by Dockerfile.intel. The 'command -v vainfo' check is dropped, only the intel image ships it. - The [program:neko] block is overridden with runtime/intel/supervisord.neko.conf, copied to /etc/neko/supervisord/neko.conf. [include] is parsed after the main config and the last definition of a program wins, so supervisord.conf keeps 'neko serve' and it does not have to be touched. Same mechanism as supervisord.nvidia.conf in the app images. - runtime/supervisord.conf, runtime/Dockerfile and runtime/Dockerfile.nvidia are back to their previous content. --- runtime/Dockerfile.intel | 9 +++++++- runtime/intel/entrypoint.sh | 15 +++++++++++++ runtime/intel/neko-serve | 26 ++++++++++++++++++++++ runtime/intel/supervisord.neko.conf | 16 +++++++++++++ runtime/intel/supervisord.rendergroup.conf | 12 ---------- 5 files changed, 65 insertions(+), 13 deletions(-) create mode 100755 runtime/intel/entrypoint.sh create mode 100755 runtime/intel/neko-serve create mode 100644 runtime/intel/supervisord.neko.conf delete mode 100644 runtime/intel/supervisord.rendergroup.conf diff --git a/runtime/Dockerfile.intel b/runtime/Dockerfile.intel index 3f0ee230..bf49e444 100644 --- a/runtime/Dockerfile.intel +++ b/runtime/Dockerfile.intel @@ -82,12 +82,14 @@ RUN set -eux; \ # copy runtime configs COPY --chown=neko:neko .Xresources /home/$USERNAME/.Xresources COPY dbus /usr/bin/dbus +COPY intel/neko-serve /usr/bin/neko-serve COPY default.pa /etc/pulse/default.pa COPY supervisord.conf /etc/neko/supervisord.conf COPY supervisord.dbus.conf /etc/neko/supervisord.dbus.conf +COPY intel/supervisord.neko.conf /etc/neko/supervisord/neko.conf COPY xorg.conf /etc/neko/xorg.conf COPY intel/add-render-group.sh /usr/bin/add-render-group.sh -COPY intel/supervisord.rendergroup.conf /etc/neko/supervisord/supervisord.rendergroup.conf +COPY intel/entrypoint.sh /usr/bin/entrypoint.sh # # copy runtime folders @@ -106,6 +108,10 @@ ENV NEKO_PLUGINS_ENABLED=true ENV NEKO_PLUGINS_DIR=/etc/neko/plugins/ ENV NEKO_HWENC=VAAPI ENV RENDER_GID= +# libva does not initialize the X11 display without an explicitly selected +# driver here ('vaGetDriverNames() failed'), and the GStreamer VA plugin scan +# uses X11. This image only ships the iHD driver (intel-media-va-driver-non-free). +ENV LIBVA_DRIVER_NAME=iHD # # add healthcheck @@ -116,4 +122,5 @@ HEALTHCHECK --interval=10s --timeout=5s --retries=8 \ # # run neko +ENTRYPOINT ["/usr/bin/entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/neko/supervisord.conf"] diff --git a/runtime/intel/entrypoint.sh b/runtime/intel/entrypoint.sh new file mode 100755 index 00000000..a00b4767 --- /dev/null +++ b/runtime/intel/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# supervisord reads /etc/group to determine the supplementary groups of every +# program it spawns, so the render group has to be assigned before supervisord +# starts. Doing it from a program spawned by supervisord (as the previous +# 'rendergroup-init' did) races with the neko program: neko is usually spawned +# before 'usermod' has finished and is left without access to /dev/dri/render*, +# which in turn makes the VA-API encoders fail to register. +# +# Assign the render group to the neko user first, then hand over to supervisord. +if ! /usr/bin/add-render-group.sh; then + echo "entrypoint: could not assign the render group, VA-API may not work (is /dev/dri passed to the container?)" >&2 +fi + +exec "$@" diff --git a/runtime/intel/neko-serve b/runtime/intel/neko-serve new file mode 100755 index 00000000..8f8bab19 --- /dev/null +++ b/runtime/intel/neko-serve @@ -0,0 +1,26 @@ +#!/bin/sh + +# GStreamer only registers the VA-API elements (e.g. vah264enc) if a usable VA +# display is found while the plugins are scanned for the first time, and the +# result of that scan is cached in the user's registry. supervisord starts the +# X server and neko in the same pass, so without waiting here neko can scan the +# plugins before X (and therefore VA-API) is usable. The cache then lacks the +# encoder elements and every client connect fails with +# '(pipeline error) no element "vah264enc"'. +# +# Wait until VA is usable through X, then drop any stale registry, so that the +# first scan of this process happens with a working VA display. +i=0 +while [ "$i" -lt 30 ]; do + if vainfo >/dev/null 2>&1; then break; fi + sleep 1 + i=$((i + 1)) +done + +if [ "$i" -ge 30 ]; then + echo "neko-serve: VA-API was not usable after 30s, starting neko anyway" >&2 +fi + +rm -f "$HOME/.cache/gstreamer-1.0/registry."* + +exec /usr/bin/neko serve "$@" diff --git a/runtime/intel/supervisord.neko.conf b/runtime/intel/supervisord.neko.conf new file mode 100644 index 00000000..37d5e47d --- /dev/null +++ b/runtime/intel/supervisord.neko.conf @@ -0,0 +1,16 @@ +# supervisord reads the files matched by [include] after the main config, and +# the last definition of a program wins, so this overrides the [program:neko] +# block from /etc/neko/supervisord.conf. Only the intel image ships +# /usr/bin/neko-serve. Keep the two blocks in sync. +[program:neko] +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" +command=/usr/bin/neko-serve --server.static "/var/www" +stopsignal=INT +stopwaitsecs=3 +autorestart=true +priority=800 +user=%(ENV_USER)s +stdout_logfile=/var/log/neko/neko.log +stdout_logfile_maxbytes=100MB +stdout_logfile_backups=10 +redirect_stderr=true diff --git a/runtime/intel/supervisord.rendergroup.conf b/runtime/intel/supervisord.rendergroup.conf deleted file mode 100644 index 8a187080..00000000 --- a/runtime/intel/supervisord.rendergroup.conf +++ /dev/null @@ -1,12 +0,0 @@ -[program:rendergroup-init] -environment=RENDER_GID="%(ENV_RENDER_GID)s",USER="%(ENV_USER)s" -command=/usr/bin/add-render-group.sh -startsecs=0 -startretries=0 -autorestart=false -priority=10 -user=root -stdout_logfile=/var/log/neko/rendergroup.log -stdout_logfile_maxbytes=1MB -stdout_logfile_backups=10 -redirect_stderr=true