From 8341e542fb6b357411476c4bad6af15251830235 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:46:36 +0200 Subject: [PATCH] feat(sabnzbd): enable Home Assistant ingress (#3019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(sabnzbd): enable Home Assistant ingress The add-on already carried a complete but disabled nginx ingress scaffold: `etc/nginx/` with its includes and a `servers/ingress.conf`, a `cont-init.d/32-nginx_ingress.sh` short-circuited by `exit 0`, and `ENV PACKAGES="nginx"` in a Dockerfile byte-identical to nzbget's. Only `ingress: true` and the s6 service that starts nginx were missing. What SABnzbd 5.1.1 actually needs from the proxy, measured against the running add-on rather than assumed: - Its interface emits only relative links (`href="../../config/general/"`, `href="../../staticcfg/css/Auto.css"`, `action="./one"`), and grepping the 5.1.1 source for `(href|src|action)="/` across `interfaces/{Glitter,Config, wizard}` and for absolute `url:` literals in the Glitter JavaScript returns nothing. A plain pass-through proxy preserves path depth, so no `sub_filter` is warranted. The previous config's `sub_filter /sabnzbd ...` would also have mangled the `https://sabnzbd.org/wiki/...` help links present on every config page. - Redirects are the one exception: `Raiser()` prefixes `cfg.url_base()`, so `GET /` answers `303 Location: /sabnzbd/wizard/`. One `proxy_redirect` handles every observed case; all of them were path-absolute, never a full URL. Login redirects and logout go through the same `Raiser()`, and the session cookie's path is hardcoded to `/` (`interface.py:316`), so it is still sent under the ingress path. - SABnzbd rejects a Host header that is not an IP literal: `Host: homeassistant` answers 403 "Hostname verification failed", while `Host: 192.168.1.5:8123` answers 200. nginx therefore sends `$proxy_host` instead of including the shared `proxy_params.conf`, which forwards `$http_host`. `ingress_entry: sabnzbd` is dropped rather than kept: Supervisor appends it to the ingress URL, which only resolves while the user's `url_base` is literally `/sabnzbd`, and that is a setting they can change. SABnzbd serves the same interface at `/` as under its `url_base` (verified for `/config/general/`, `/static/`, `/staticcfg/` and `/wizard/`), so entering at the ingress root works for any value, including the empty code default. Ingress traffic reaches SABnzbd as `127.0.0.1:8080` and so is not filtered by a user's host whitelist; direct ip:port access is unchanged and still is. Co-Authored-By: Claude Opus 5 * fix(sabnzbd): keep the ingress Location relative and scope the login cookie Exercising the shipped config against the running add-on caught a bug that reading it did not. With nginx's default `absolute_redirect on`, rewriting `Location: /sabnzbd/wizard/` produced `http://homeassistant.local:18099/api/hassio_ingress//sabnzbd/wizard/` — nginx expands a scheme-less replacement using the browser's Host and its own listen port, which is the add-on's internal ingress port and is not reachable from the browser. `absolute_redirect off` keeps it a path, which the browser resolves against the Home Assistant origin. `proxy_cookie_path` comes from Codex's review of the diff. SABnzbd hardcodes the login cookie to `Path=/` (`interface.py:316`), so on the shared ingress origin the browser would send it to every other add-on's ingress path as well. Verified end to end by running the shipped nginx.conf and ingress.conf against the live add-on, with the browser Host set to a non-IP hostname throughout: all five redirect cases return a path under the ingress entry, the config pages, wizard, API and both static roots return 200, and a stub upstream emitting `Path=/` comes back rewritten to the ingress path. Co-Authored-By: Claude Opus 5 * fix(sabnzbd): drop webui, which the add-on linter forbids alongside ingress frenck/action-addon-linter fails the PR with "'webui' should be removed, Ingress is enabled." No other ingress add-on in this repo keeps the key. The "Open Web UI" button now opens ingress; the ports mapping is untouched, so direct ip:port still works, it just has to be typed. Co-Authored-By: Claude Opus 5 * fix(sabnzbd): make the nginx finish script actually work on s6-overlay v3 CodeRabbit is right that the execline finish script copied from nzbget is inert on this image. s6-portable-utils dropped `s6-test` in favour of execline's `eltest` — s6-overlay 3.2.1.0 ships no `s6-test` at all — so execlineb cannot run the first `if` block and never reaches s6-svscanctl. `/var/run/s6/services` is also the v2 scandir path; v3's legacy services.d compatibility layer uses /run/service. Rather than port it to eltest, use the shell form the scrutiny add-on already ships: `kill -15 1` signals s6-overlay's init directly, so it depends on neither the s6 tool set nor the scandir path, and it is three lines shorter. The 0 and 256 exclusions are kept, so a normal shutdown does not trigger it. Also fix the CHANGELOG date to YYYY-MM-DD per Copilot: that is what this file and 7705 of the repo's 8068 dated headings use. Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- sabnzbd/CHANGELOG.md | 4 ++ sabnzbd/config.yaml | 5 +-- .../etc/cont-init.d/32-nginx_ingress.sh | 11 +----- sabnzbd/rootfs/etc/nginx/servers/ingress.conf | 38 ++++++++++++------- sabnzbd/rootfs/etc/services.d/nginx/finish | 9 +++++ sabnzbd/rootfs/etc/services.d/nginx/run | 11 ++++++ 6 files changed, 53 insertions(+), 25 deletions(-) create mode 100755 sabnzbd/rootfs/etc/services.d/nginx/finish create mode 100755 sabnzbd/rootfs/etc/services.d/nginx/run diff --git a/sabnzbd/CHANGELOG.md b/sabnzbd/CHANGELOG.md index 1a81d761b3..a32fabe83b 100644 --- a/sabnzbd/CHANGELOG.md +++ b/sabnzbd/CHANGELOG.md @@ -1,4 +1,8 @@ +## 5.1.1.2 (2026-08-25) +- Ingress is now enabled: the WebUI opens directly in the Home Assistant sidebar, and the "Open Web UI" button now goes there. Access by ip:port is unchanged, but has to be typed rather than clicked, as Home Assistant does not allow an add-on to offer both. +- Note for users who set a "Host verification" whitelist in SABnzbd: ingress sends `Host: 127.0.0.1:8080` upstream, because SABnzbd rejects any Host that is not an IP literal. That whitelist therefore no longer filters the ingress route, which is gated by Home Assistant authentication instead. Direct ip:port access is unchanged and still filtered. + ## 5.1.1 (2026-08-22) - Update to latest version from linuxserver/docker-sabnzbd (changelog : https://github.com/linuxserver/docker-sabnzbd/releases) diff --git a/sabnzbd/config.yaml b/sabnzbd/config.yaml index 0f946b457b..d0463b4df1 100644 --- a/sabnzbd/config.yaml +++ b/sabnzbd/config.yaml @@ -70,7 +70,7 @@ environment: PGID: "0" PUID: "0" image: ghcr.io/alexbelgium/sabnzbd-{arch} -ingress_entry: sabnzbd +ingress: true init: false map: - addon_config:rw @@ -106,5 +106,4 @@ schema: slug: sabnzbd udev: true url: https://github.com/alexbelgium/hassio-addons -version: "5.1.1" -webui: http://[HOST]:[PORT:8080] +version: "5.1.1.2" diff --git a/sabnzbd/rootfs/etc/cont-init.d/32-nginx_ingress.sh b/sabnzbd/rootfs/etc/cont-init.d/32-nginx_ingress.sh index 7d8665c981..8aa5bedcad 100755 --- a/sabnzbd/rootfs/etc/cont-init.d/32-nginx_ingress.sh +++ b/sabnzbd/rootfs/etc/cont-init.d/32-nginx_ingress.sh @@ -1,21 +1,14 @@ #!/usr/bin/with-contenv bashio # shellcheck shell=bash -# shellcheck disable=SC2317 set -e ################# # NGINX SETTING # ################# -exit 0 - ingress_port=$(bashio::addon.ingress_port) ingress_interface=$(bashio::addon.ip_address) +ingress_entry=$(bashio::addon.ingress_entry) sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf - -# Allows serving js -sed -i 's/// %end% -->/g' /app/sabnzbd/webui/index.html -sed -i 's//g' /app/sabnzbd/webui/index.html -sed -i 's/ %end% -->//g' /app/sabnzbd/webui/index.html +sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf diff --git a/sabnzbd/rootfs/etc/nginx/servers/ingress.conf b/sabnzbd/rootfs/etc/nginx/servers/ingress.conf index 8b4c4ffc20..feaf6848a0 100644 --- a/sabnzbd/rootfs/etc/nginx/servers/ingress.conf +++ b/sabnzbd/rootfs/etc/nginx/servers/ingress.conf @@ -2,22 +2,34 @@ server { listen %%interface%%:%%port%% default_server; include /etc/nginx/includes/server_params.conf; - include /etc/nginx/includes/proxy_params.conf; client_max_body_size 0; - location / { - add_header Access-Control-Allow-Origin *; - proxy_connect_timeout 30m; - proxy_send_timeout 30m; - proxy_read_timeout 30m; - proxy_pass http://127.0.0.1:8080; + location / { + proxy_pass http://127.0.0.1:8080; - proxy_set_header Accept-Encoding ""; - # Correct url without port when using https - sub_filter_once off; - sub_filter_types *; - sub_filter /sabnzbd %%ingress_entry%%/sabnzbd; - } + # SABnzbd refuses any request whose Host is not an IP literal + # ("Access denied - Hostname verification failed"), so send the + # upstream socket rather than the browser's host. X-Forwarded-For is + # the only other header it reads (for its verify_xff_header option). + proxy_set_header Host $proxy_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # The interface itself only emits relative links, so no body + # rewriting is needed. Redirects are the exception: Raiser() emits + # a path under url_base, so prefix them with the ingress entry. + # absolute_redirect must stay off, or nginx expands the rewritten + # Location into http://:/..., a port the + # browser cannot reach. + proxy_redirect / %%ingress_entry%%/; + absolute_redirect off; + + # The login cookie is hardcoded to Path=/, which on the ingress + # origin would send it to every other add-on's ingress path too. + proxy_cookie_path / %%ingress_entry%%/; + + proxy_http_version 1.1; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } } diff --git a/sabnzbd/rootfs/etc/services.d/nginx/finish b/sabnzbd/rootfs/etc/services.d/nginx/finish new file mode 100755 index 0000000000..4aa1013152 --- /dev/null +++ b/sabnzbd/rootfs/etc/services.d/nginx/finish @@ -0,0 +1,9 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +# ============================================================================== +# Stop the container when Nginx fails, so ingress does not silently go dead +# ============================================================================== +if [[ "$1" -ne 0 && "$1" -ne 256 ]]; then + bashio::log.error "Nginx exited with code $1" + kill -15 1 +fi diff --git a/sabnzbd/rootfs/etc/services.d/nginx/run b/sabnzbd/rootfs/etc/services.d/nginx/run new file mode 100755 index 0000000000..73bc49e9da --- /dev/null +++ b/sabnzbd/rootfs/etc/services.d/nginx/run @@ -0,0 +1,11 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e +# ============================================================================== + +# Wait for sabnzbd to become available +bashio::net.wait_for 8080 localhost 900 + +bashio::log.info "Starting NGinx..." + +exec nginx