diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index c1778d5f7b..377fbd839e 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.36.4 (28-07-2026) + +- Fix Selkies dying with a Rust `RuntimeDirNotSet` unwrap panic just after `Data WebSocket Server listening on port 8081`, and the data websocket then being proxied to the wrong port. Upstream relies on s6-rc ordering: `init-selkies-config` publishes `XDG_RUNTIME_DIR` and `CUSTOM_WS_PORT` into the s6 envdir and `svc-selkies` starts afterwards. The add-on entrypoint replaces s6-overlay and starts every `s6-rc.d` run script in parallel with no dependency graph, so Selkies can snapshot the envdir before that oneshot has written to it -- which is why it bound port 8081 (its own default) instead of the 8082 nginx proxies to, and why its Wayland compositor found no runtime directory to bind a socket in. `20-folders.sh` now exports both variables inside each run script, where no start ordering can lose them, and corrects the base image's `$HOME/.XDG` override where that write happens instead of appending a correction after the `exit 0` that the oneshot-tolerance block adds -- which meant the correction never ran on any boot after the first. + ## 1.36.3 (28-07-2026) - Make the Selkies startup scripts add-on agnostic so `webtop` and `webtop_kde` can share them by symlink instead of carrying their own drifted copies. `20-folders.sh` now derives its default data location from the home directory the Dockerfile baked into the `abc` user (`getent passwd abc`) rather than hardcoding `/data/data`, and the `permission_mode: bypass` root guard is skipped on add-ons that do not declare that option. `80-configuration.sh` falls back to `pip` when the image does not ship `uv`. No behaviour change for Claude Desktop: `getent passwd abc` returns `/data/data`, which is exactly the value that was hardcoded before. diff --git a/claude_desktop/config.yaml b/claude_desktop/config.yaml index 59e4a0ac0d..1a5c9c0fed 100644 --- a/claude_desktop/config.yaml +++ b/claude_desktop/config.yaml @@ -122,5 +122,5 @@ slug: claude_desktop tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: "1.36.3" +version: "1.36.4" video: true diff --git a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh index f2ab418d0a..e05d41380c 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh @@ -92,16 +92,35 @@ XDG_RUNTIME_DIR="/run/user/$PUID" mkdir -p "$XDG_RUNTIME_DIR" chmod 700 "$XDG_RUNTIME_DIR" -for file in /etc/s6-overlay/s6-rc.d/*/run; do - if [ "$(sed -n '1{/bash/p};q' "$file")" ] && ! grep -q '^export XDG_CACHE_HOME=/tmp/cache$' "$file"; then - sed -i "1a export HOME=$LOCATION" "$file" - sed -i "1a export FM_HOME=$LOCATION" "$file" - sed -i "1a export XDG_CACHE_HOME=/tmp/cache" "$file" - fi -done +# Must agree with the CWS substitution in 90-ingress.sh: nginx proxies the Selkies data +# websocket to this port, and Selkies only listens on it if CUSTOM_WS_PORT reaches its process. +# Validated here, once, because the value goes on to be interpolated into generated shell and +# into a sed replacement in 90-ingress.sh, both of which take the normalised value back out of +# the envdir written below. +SELKIES_WS_PORT="${CUSTOM_WS_PORT:-8082}" +if ! [[ "$SELKIES_WS_PORT" =~ ^[0-9]+$ ]] || [ "$SELKIES_WS_PORT" -lt 1 ] || [ "$SELKIES_WS_PORT" -gt 65535 ]; then + bashio::log.warning "CUSTOM_WS_PORT '${CUSTOM_WS_PORT:-}' is not a valid port number; using 8082" + SELKIES_WS_PORT=8082 +fi + +# Upstream relies on s6-rc ordering: init-selkies-config publishes XDG_RUNTIME_DIR and +# CUSTOM_WS_PORT into the s6 envdir, and svc-selkies is started afterwards. The add-on +# entrypoint replaces s6-overlay and launches every s6-rc.d run script in parallel, with no +# dependency graph, so a longrun can snapshot the envdir (with-contenv reads it once, at exec) +# before the oneshot has written to it. Selkies is where that shows: it comes up with +# CUSTOM_WS_PORT unset and binds its data websocket on the 8081 default while nginx proxies +# 8082, and on the PIXELFLUX_WAYLAND images it reaches the compositor with no XDG_RUNTIME_DIR +# and panics with `RuntimeDirNotSet` binding the Wayland socket. +# +# Exporting both inside the run scripts puts them in each process's own environment, where no +# start ordering can lose them, and keeps every service agreeing on one runtime dir -- svc-de +# otherwise waits forever on a Wayland socket under a directory Selkies never used. # Rewrite the home path baked into the image to the user-chosen one. No-op when data_location -# is left at its default. +# is left at its default. Runs before the exports below are injected, not after: this is a +# blind textual substitution, so a location *under* the image default (data_location +# /config/data_kde/foo against a /config/data_kde image) would otherwise rewrite the freshly +# injected "export HOME=/config/data_kde/foo" into ".../foo/foo". if [ "$LOCATION" != "$DEFAULT_LOCATION" ]; then for folders in /defaults /etc/cont-init.d /etc/services.d /etc/s6-overlay/s6-rc.d; do if [ -d "$folders" ]; then @@ -110,12 +129,38 @@ if [ "$LOCATION" != "$DEFAULT_LOCATION" ]; then done fi +# Re-derived on every boot rather than injected once behind a marker, for the same reason the +# ~/.bashrc block below is: the run scripts live in the writable layer and survive a restart, so +# a write-once injection pins whatever PUID and CUSTOM_WS_PORT were in force the first time. +# Raising PUID would leave every service exporting a /run/user/ the remapped abc user +# cannot use, and clearing a custom CUSTOM_WS_PORT would leave Selkies on the old port while +# 90-ingress.sh moved nginx back to 8082. Strip whatever a previous boot left -- the marked +# block, or the bare exports earlier versions wrote -- then write the current values. No +# upstream run script in these images sets any of these five, so the bare-line sweep only ever +# removes our own. +ENV_BLOCK_BEGIN="# --- BEGIN ADDON ENV (managed) ---" +ENV_BLOCK_END="# --- END ADDON ENV (managed) ---" +for file in /etc/s6-overlay/s6-rc.d/*/run; do + [ -n "$(sed -n '1{/bash/p};q' "$file")" ] || continue + sed -i "/^${ENV_BLOCK_BEGIN}\$/,/^${ENV_BLOCK_END}\$/d" "$file" + sed -i -E '/^export (HOME|FM_HOME|XDG_CACHE_HOME|XDG_RUNTIME_DIR|CUSTOM_WS_PORT)=/d' "$file" + # Each "1a" lands at line 2 and pushes the previous one down, so this reads bottom-up. + sed -i "1a $ENV_BLOCK_END" "$file" + sed -i "1a export HOME=\"$LOCATION\"" "$file" + sed -i "1a export FM_HOME=\"$LOCATION\"" "$file" + sed -i "1a export XDG_CACHE_HOME=\"/tmp/cache\"" "$file" + sed -i "1a export XDG_RUNTIME_DIR=\"$XDG_RUNTIME_DIR\"" "$file" + sed -i "1a export CUSTOM_WS_PORT=\"$SELKIES_WS_PORT\"" "$file" + sed -i "1a $ENV_BLOCK_BEGIN" "$file" +done + sed -i "s|^\(abc:[^:]*:[^:]*:[^:]*:[^:]*:\)[^:]*|\1$LOCATION|" /etc/passwd printf "%s" "$LOCATION" > "$S6_ENVDIR/HOME" printf "%s" "$LOCATION" > "$S6_ENVDIR/FM_HOME" printf "%s" "/tmp/cache" > "$S6_ENVDIR/XDG_CACHE_HOME" printf "%s" "$XDG_RUNTIME_DIR" > "$S6_ENVDIR/XDG_RUNTIME_DIR" +printf "%s" "$SELKIES_WS_PORT" > "$S6_ENVDIR/CUSTOM_WS_PORT" # Re-derived on every boot rather than gated on a "does it already say $LOCATION" grep: that # guard only ever recognized the CURRENT $LOCATION, so a user who changed data_location and # later changed it back left two stale HOME/FM_HOME exports in ~/.bashrc, with the last one @@ -159,13 +204,16 @@ bashio::log.info "Setting ownership to $PUID:$PGID" chown -R "${PUID}:${PGID}" "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR" /data chmod -R 700 "$LOCATION" -# The base init-selkies-config script overrides XDG_RUNTIME_DIR to $HOME/.XDG, which lands -# on persistent storage and conflicts with the tmpfs runtime dir set above. Re-assert the -# tmpfs value at the end of that oneshot so the app and desktop agree on one valid dir. +# The base init-selkies-config script overrides XDG_RUNTIME_DIR to $HOME/.XDG, which lands on +# persistent storage and conflicts with the tmpfs runtime dir set above. Correct that write +# where it happens rather than re-asserting the value at the end of the oneshot: the tolerance +# block below appends `exit 0`, so on every boot after the first an appended correction sits +# past it and never runs. SELKIES_CONFIG_RUN="/etc/s6-overlay/s6-rc.d/init-selkies-config/run" if [ -f "$SELKIES_CONFIG_RUN" ]; then + # Drop the trailing correction earlier versions appended, now applied at the source. sed -i '/^# XDG_RUNTIME_DIR override reconciled$/,+1d' "$SELKIES_CONFIG_RUN" - printf '\n# XDG_RUNTIME_DIR override reconciled\nprintf "%%s" "%s" > /run/s6/container_environment/XDG_RUNTIME_DIR\n' "$XDG_RUNTIME_DIR" >> "$SELKIES_CONFIG_RUN" + sed -i "s|^.*> */run/s6/container_environment/XDG_RUNTIME_DIR *\$|printf '%s' '$XDG_RUNTIME_DIR' > /run/s6/container_environment/XDG_RUNTIME_DIR|" "$SELKIES_CONFIG_RUN" fi # The Selkies desktop init oneshots do best-effort device/permission setup (mknod diff --git a/claude_desktop/rootfs/etc/cont-init.d/90-ingress.sh b/claude_desktop/rootfs/etc/cont-init.d/90-ingress.sh index 6d3074e901..e396fa8136 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/90-ingress.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/90-ingress.sh @@ -24,7 +24,15 @@ sed -i '/listen \[::\]/d' "${NGINX_CONFIG}" # Adapt ports and upstream paths for Home Assistant ingress sed -i "s|3000|$(bashio::addon.ingress_port)|g" "${NGINX_CONFIG}" sed -i "s|SUBFOLDER|/|g" "${NGINX_CONFIG}" -sed -i "s|CWS|8082|g" "${NGINX_CONFIG}" +# Same value 20-folders.sh exports to the Selkies services; both must move together or nginx +# proxies the data websocket to a port nothing listens on. That script also normalises it into +# the s6 envdir, which this one picks up through with-contenv; the check is repeated so a +# malformed value cannot reach the nginx config if 20-folders.sh did not get that far. +CWS="${CUSTOM_WS_PORT:-8082}" +if ! [[ "$CWS" =~ ^[0-9]+$ ]] || [ "$CWS" -lt 1 ] || [ "$CWS" -gt 65535 ]; then + CWS=8082 +fi +sed -i "s|CWS|${CWS}|g" "${NGINX_CONFIG}" sed -i "s|REPLACE_HOME|${HOME:-/root}|g" "${NGINX_CONFIG}" sed -i "s|REPLACE_DOWNLOADS_PATH|${HOME:-/config}|g" "${NGINX_CONFIG}" sed -i '/proxy_buffering/a proxy_set_header Accept-Encoding "";' "${NGINX_CONFIG}" diff --git a/webtop/CHANGELOG.md b/webtop/CHANGELOG.md index 6c1271393a..f4b37114be 100644 --- a/webtop/CHANGELOG.md +++ b/webtop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.16-r0-ls95-7 (28-07-2026) + +- Fix Selkies dying with a Rust `RuntimeDirNotSet` unwrap panic just after `Data WebSocket Server listening on port 8081`, and the data websocket then being proxied to the wrong port. Upstream relies on s6-rc ordering: `init-selkies-config` publishes `XDG_RUNTIME_DIR` and `CUSTOM_WS_PORT` into the s6 envdir and `svc-selkies` starts afterwards. The add-on entrypoint replaces s6-overlay and starts every `s6-rc.d` run script in parallel with no dependency graph, so Selkies can snapshot the envdir before that oneshot has written to it -- which is why it bound port 8081 (its own default) instead of the 8082 nginx proxies to, and why its Wayland compositor found no runtime directory to bind a socket in. `20-folders.sh` now exports both variables inside each run script, where no start ordering can lose them, and corrects the base image's `$HOME/.XDG` override where that write happens instead of appending a correction after the `exit 0` that the oneshot-tolerance block adds -- which meant the correction never ran on any boot after the first. + +- Microsoft Edge install: `apt-get` and `dpkg` failures no longer abort container startup -- a transient mirror failure or a bad download now logs a warning and leaves the desktop running without Edge, and apt acquisition is bounded so a stalled mirror cannot hang start-up. The post-install wrapper swap is now gated on the helper still being present, so a second run cannot move the installed wrapper aside with nothing left to replace it. + ## 4.16-r0-ls95-6 (28-07-2026) - Share the Selkies startup scripts with the `claude_desktop` add-on by symlink (`20-folders.sh`, `21-gpu_permissions.sh`, `80-configuration.sh`, `90-ingress.sh` and the nginx includes), so the fixes made there now apply here too. This brings in: GPU render-node permissions granted before the graphical services start (fixes `libEGL warning: failed to open /dev/dri/card0: Permission denied` and the resulting "waiting for stream" hang); the s6 envdir and `XDG_RUNTIME_DIR` created up front; the cache redirected to tmpfs; `/tmp/.X11-unix` pre-created so Xorg can bind its socket as a non-root user; the `init-video` and `init-selkies-config` oneshots made non-fatal so a partially permitted device setup no longer crash-loops the add-on; and an ingress config that keeps the correct (non-SSL) nginx server block. The Microsoft Edge install moves to its own webtop-only `81-microsoft_edge.sh`, which also picks up the ownership fixup that previously ran in `20-folders.sh` before Edge was installed and so never matched anything. diff --git a/webtop/config.yaml b/webtop/config.yaml index fc4c23eca7..92f085570e 100644 --- a/webtop/config.yaml +++ b/webtop/config.yaml @@ -138,5 +138,5 @@ slug: webtop-kde tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: 4.16-r0-ls95-6 +version: 4.16-r0-ls95-7 video: true diff --git a/webtop_kde/CHANGELOG.md b/webtop_kde/CHANGELOG.md index 99cca37427..c64e2317e9 100644 --- a/webtop_kde/CHANGELOG.md +++ b/webtop_kde/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.16-r0-ls93.2 (28-07-2026) + +- Fix Selkies dying with a Rust `RuntimeDirNotSet` unwrap panic just after `Data WebSocket Server listening on port 8081`, and the data websocket then being proxied to the wrong port. Upstream relies on s6-rc ordering: `init-selkies-config` publishes `XDG_RUNTIME_DIR` and `CUSTOM_WS_PORT` into the s6 envdir and `svc-selkies` starts afterwards. The add-on entrypoint replaces s6-overlay and starts every `s6-rc.d` run script in parallel with no dependency graph, so Selkies can snapshot the envdir before that oneshot has written to it -- which is why it bound port 8081 (its own default) instead of the 8082 nginx proxies to, and why its Wayland compositor found no runtime directory to bind a socket in. `20-folders.sh` now exports both variables inside each run script, where no start ordering can lose them, and corrects the base image's `$HOME/.XDG` override where that write happens instead of appending a correction after the `exit 0` that the oneshot-tolerance block adds -- which meant the correction never ran on any boot after the first. + +- Microsoft Edge install: `apt-get` and `dpkg` failures no longer abort container startup -- a transient mirror failure or a bad download now logs a warning and leaves the desktop running without Edge, and apt acquisition is bounded so a stalled mirror cannot hang start-up. The post-install wrapper swap is now gated on the helper still being present, so a second run cannot move the installed wrapper aside with nothing left to replace it. + ## 4.16-r0-ls93.1 (28-07-2026) - Share the Selkies startup scripts with the `claude_desktop` add-on by symlink (`20-folders.sh`, `21-gpu_permissions.sh`, `80-configuration.sh`, `90-ingress.sh` and the nginx includes), so the fixes made there now apply here too. This brings in: GPU render-node permissions granted before the graphical services start (fixes `libEGL warning: failed to open /dev/dri/card0: Permission denied` and the resulting "waiting for stream" hang); the s6 envdir and `XDG_RUNTIME_DIR` created up front; the cache redirected to tmpfs; `/tmp/.X11-unix` pre-created so Xorg can bind its socket as a non-root user; the `init-video` and `init-selkies-config` oneshots made non-fatal so a partially permitted device setup no longer crash-loops the add-on; and an ingress config that keeps the correct (non-SSL) nginx server block. The Microsoft Edge install moves to its own webtop-only `81-microsoft_edge.sh`, which also picks up the ownership fixup that previously ran in `20-folders.sh` before Edge was installed and so never matched anything. diff --git a/webtop_kde/config.yaml b/webtop_kde/config.yaml index 073e773310..33557c6e56 100644 --- a/webtop_kde/config.yaml +++ b/webtop_kde/config.yaml @@ -143,5 +143,5 @@ slug: webtop tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: "4.16-r0-ls93.1" +version: "4.16-r0-ls93.2" video: true diff --git a/webtop_kde/rootfs/etc/cont-init.d/81-microsoft_edge.sh b/webtop_kde/rootfs/etc/cont-init.d/81-microsoft_edge.sh index d8c830fb91..903e48ba4a 100755 --- a/webtop_kde/rootfs/etc/cont-init.d/81-microsoft_edge.sh +++ b/webtop_kde/rootfs/etc/cont-init.d/81-microsoft_edge.sh @@ -11,41 +11,63 @@ if ! bashio::config.true 'install_ms_edge'; then exit 0 fi -bashio::log.info "Adding microsoft edge" -apt-get update -apt-get install --no-install-recommends -y ca-certificates +# Every step below is bounded and non-fatal. cont-init.d blocks the whole add-on, so an +# unreachable or stalled packages.microsoft.com -- or a Debian mirror having a bad day -- must +# not hang or kill startup: the desktop is useful without Edge, an add-on stuck before Selkies +# starts is not. `set -e` would turn any apt or dpkg hiccup into exactly that, so each command +# is guarded and every failure path warns and exits 0. +EDGE_DEB="" +edge_giveup() { + bashio::log.warning "$1; skipping the Microsoft Edge install" + if [ -n "$EDGE_DEB" ]; then + rm -f "$EDGE_DEB" + fi + exit 0 +} + +bashio::log.info "Adding microsoft edge" +# -o Acquire::*Timeout bounds the mirror handshake/transfer the same way --max-time bounds curl. +APT_TIMEOUTS=(-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 -o Acquire::Retries=1) +apt-get "${APT_TIMEOUTS[@]}" update || edge_giveup "apt-get update failed" +apt-get "${APT_TIMEOUTS[@]}" install --no-install-recommends -y ca-certificates \ + || edge_giveup "Installing ca-certificates failed" -# Both requests are bounded and non-fatal. cont-init.d blocks the whole add-on, so an -# unreachable or stalled packages.microsoft.com must not hang or kill startup: the desktop is -# useful without Edge, an add-on stuck before Selkies starts is not. EDGE_REPO="https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable" -if [ -z ${EDGE_VERSION+x} ]; then +if [ -z "${EDGE_VERSION+x}" ]; then EDGE_VERSION=$(curl -sL --fail --connect-timeout 15 --max-time 120 "$EDGE_REPO/" \ | awk -F'(