Compare commits

...

7 Commits

Author SHA1 Message Date
Alexandre
2fc1ea84be fix(immich): URI-encode DB credentials for psql connection strings (#2980)
The addon builds every psql connection as a postgres:// URI with the raw
username and password interpolated in. libpq percent-decodes the userinfo
part of a URI, so a password containing '%' (or '@', '/', '?', '#') is
decoded into different bytes before it reaches the server, and every
connection fails with "password authentication failed for user".

Encode the credentials with jq's @uri once and use the encoded copies in
the URIs only; the raw password is still what gets handed to Immich via
export_db_env and what is written by CREATE/ALTER USER. Those SQL
statements now double single quotes so a password containing a single
quote no longer breaks the statement either.

This is the same approach already used by the postgres_15 and postgres_17
addons in this repo.

Closes #1614

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:29:50 +02:00
Alexandre
48ac78c59d fix(filebrowser_quantum): repair direct access on port 8071 (#2998)
* fix(filebrowser_quantum): repair direct access on port 8071

1.5.1.1 published the port but direct access still did not work, in two ways
measured against a running instance:

1. The root redirect was absolute, so nginx built it from $server_port and
   sent the browser to :8072 — the container-internal port, not the published
   one. `absolute_redirect off` keeps the redirect relative.
2. The page served under /filebrowser_quantum/ referenced its assets under
   the app's own baseURL (the ingress entry path), which that vhost did not
   route: GET /api/hassio_ingress/<hash>/public/static/favicon.svg returned
   404 while the same file under /filebrowser_quantum/ returned 200. The page
   loaded and every asset on it failed.

Rather than translating paths, the vhost now passes requests through
unchanged and redirects only the bare root to the app's baseURL, which is
what its own links already point at. Asset, API and websocket URLs then work
without any response rewriting. Ingress is untouched.

* docs(filebrowser_quantum): describe the legacy redirects accurately

The comments, CHANGELOG and README still said only the bare root was
redirected, which stopped being true when the two /filebrowser_quantum
compatibility redirects were added. Raised by CodeRabbit and Codex.
2026-08-19 10:27:36 +02:00
Alexandre
9302fc9a51 fix(komga): keep the reader inside the ingress panel (#2995)
* fix(komga): keep the reader inside the ingress panel

Komga's ui opens the reader with window.open(url, '_blank'). The Home
Assistant companion apps hand such a popup to an external browser, which
carries no ingress session cookie, so Home Assistant answers 401 before
Komga is reached.

Nginx now injects a small script into the ui shell that turns same origin
popups into a navigation in the current tab. The OAuth2 login popup, which
passes a window name and a feature string, and cross origin links are left
untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(komga): only intercept popups when resourceBaseUrl is known

Review feedback : the '/' fallback meant that if Komga ever stopped
setting window.resourceBaseUrl, every same origin _blank popup would be
captured -- and ingress shares the Home Assistant origin. Require the
base, and give it a trailing slash so a sibling path such as
<entry>/komgaX is not treated as being below <entry>/komga.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:30:47 +02:00
Alexandre
29ce08c162 fix(seerr): reapply the asset cache-bust reverted by the builder (#2975) (#2997)
Restores #2993 verbatim. It was merged, then reverted by the builder's
revert-on-failure job a minute later - not because of anything in it, but
because EndBug/add-and-commit's floating v11 tag had moved to a release whose
action.yml no longer loads, so prebuild-sanitize failed before running a step.
The tag is pinned back to v11.0.0 in #2996, which has to land first for the
builder to get past that job.

The change itself is unchanged and still verified against the real njs module:
the rewritten /_next paths carry the add-on version, njs strips the marker
before proxying, so a browser holding the year-cached rewritten bundle fetches
fresh URLs on the first load after the update.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:30:20 +02:00
Alexandre
bb1d0c6b66 ci: pin EndBug/add-and-commit to v11.0.0, the floating v11 tag is broken (#2996)
Every push to master has failed to build since 2026-08-19 05:15. The
prebuild-sanitize job dies before running a single step:

    EndBug/add-and-commit/v11/action.yml (Line: 25, Col: 18):
    Unrecognized named-value: 'github'. Located at position 1 within
    expression: github.workspace
    Failed to load EndBug/add-and-commit/v11/action.yml

Upstream's v11.1.0, published 2026-08-18 22:44 UTC, put a literal
"${{ github.workspace }}" inside the description of the `cwd` input. Action
metadata descriptions are still parsed as expressions and the `github` context
does not exist there, so the action no longer loads at all. The floating v11
tag was moved to it, which is why nothing changed in this repo and every
workflow using the action broke at once - the builder, the README and stats
refreshers, the CRLF sweep, the image compressor and the issue labeller.

v11.0.0 does not contain that line and loads normally, so pinning to it keeps
the version Dependabot moved us to in #2985 while stepping off the tag. It also
took out an unrelated add-on fix: the builder's revert-on-failure job reverted
the seerr merge (#2993) as collateral, and that is being reapplied separately.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:29:52 +02:00
GitHub Actions
1b1436b61a Revert "fix(seerr): version the rewritten asset paths so cached bundles expire (#2975) (#2993)"
This reverts commit 08fe5e33be.
2026-08-19 05:15:54 +00:00
Alexandre
08fe5e33be fix(seerr): version the rewritten asset paths so cached bundles expire (#2975) (#2993)
* fix(seerr): version the rewritten asset paths so cached bundles expire (#2975)

Seerr serves everything under /_next/static/ with "Cache-Control: public,
max-age=31536000, immutable", and nginx's sub_filter strips ETag,
Last-Modified and Content-Length from every response it rewrites. The HTML
is served "no-store" but keeps naming the same chunk URLs, and all three
3.4.1.x add-on versions ship the same upstream build, so a browser that had
loaded Seerr through ingress once kept replaying the JavaScript it cached
then - for up to a year, with no request to revalidate it.

That is why #2975 outlived two fixes: the reporter's https origin was still
executing the 3.4.1/3.4.1.1 bundle, whose rewritten root link makes Next
hard-navigate to /api/hassio_ingress/<token> without a trailing slash, which
Home Assistant does not route and answers with its own "404: Not Found". An
origin that had never cached it - the same instance over http://<ip>:8123 -
already showed the fixed behaviour.

The asset paths now carry the add-on version ("/ha-3-4-1-3/_next/..."), so
every release has its own URLs, a poisoned cache is bypassed on the first
load after an update, and any future change to a rewrite rule is actually
delivered. njs strips the marker again before proxying, so Seerr still
receives the paths it serves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(seerr): tighten the cache-bust comments after review

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(seerr): take the cache-bust marker straight from BUILD_VERSION

bashio::addon.version is an indirection here: bashio-standalone.sh defines it
as printf '%s' "${BUILD_VERSION:-1.0}", and the builder always passes
BUILD_VERSION from config.yaml, which the Dockerfile bakes in as an ENV. Reading
it directly drops a Supervisor round-trip and the fallback chain around it, for
the same value. The sanitiser stays: it protects the sed replacement and the
regex literal the marker lands in inside Seerr's bundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:15:20 +02:00
23 changed files with 148 additions and 51 deletions

View File

@@ -228,7 +228,7 @@ jobs:
echo "... done"
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "GitHub bot : README updated"
default_author: github_actions

View File

@@ -237,7 +237,7 @@ jobs:
# Get stars evolution
wget -S -O .github/starsevol.svg "https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date" || true
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "GitHub bot : graphs updated"
default_author: github_actions

View File

@@ -59,7 +59,7 @@ jobs:
# Remove issues list
rm issueslist
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "Github bot : issues linked to readme"
default_author: github_actions

View File

@@ -95,7 +95,7 @@ jobs:
- name: Commit sanitize changes
id: sanitize_commit
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
commit: -u
message: "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
@@ -410,7 +410,7 @@ jobs:
done
- name: Commit changelog changes
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
commit: -u
message: "GitHub bot: changelog [nobuild]"

View File

@@ -18,7 +18,7 @@ jobs:
uses: erclu/check-crlf@v1
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "Github bot : CRLF corrected"
default_author: github_actions
@@ -50,7 +50,7 @@ jobs:
dos2unix -k "$f"
done
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "Github bot : CRLF corrected"
default_author: github_actions

View File

@@ -31,7 +31,7 @@ jobs:
- name: Commit if needed
if: steps.calibre.outputs.markdown != ''
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
message: "Github bot : image compressed"
default_author: github_actions

View File

@@ -109,7 +109,7 @@ jobs:
#TOTAL3="$(awk '{SUM+=$2}END{print SUM}' Stats)"
- name: Commit if needed
uses: EndBug/add-and-commit@v11
uses: EndBug/add-and-commit@v11.0.0
with:
default_author: github_actions
message : "Github bot : stats updated"

View File

@@ -1,4 +1,12 @@
## 1.5.1.2 (2026-08-19)
- Fix direct access on port 8071, which was broken in 1.5.1.1: the root
redirect pointed at the container-internal port 8072 instead of the
published one, and the page it led to referenced assets under a path the
add-on did not serve, so every asset returned 404. Requests are now passed
through unchanged, with the bare root and the two previously documented
`/filebrowser_quantum` URLs redirected to the app's configured base path.
## 1.5.1.1 (2026-08-16)
- Expose the web UI on host port 8071, reachable at `<your-ip>:8071`
(redirects to `/filebrowser_quantum/`). Direct access is served by a new,

View File

@@ -42,11 +42,11 @@ comparison to installing any other Home Assistant add-on.
1. Click the `Save` button to store your configuration.
1. Start the add-on.
1. Check the logs of the add-on to see if everything went well.
1. Access the web UI through the sidebar or at `<your-ip>:8071/filebrowser_quantum/`.
1. Access the web UI through the sidebar or at `<your-ip>:8071`.
## Configuration
The web UI can be found at `<your-ip>:8071` (redirects to `/filebrowser_quantum/`) or through the Home Assistant sidebar when using Ingress.
The web UI can be found at `<your-ip>:8071` or through the Home Assistant sidebar when using Ingress. Direct access redirects to the add-on's configured base path, so the address bar will show a longer URL than the one you typed.
**Default credentials:**
- Username: `admin`

View File

@@ -118,4 +118,4 @@ schema:
slug: filebrowser_quantum
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.5.1.1"
version: "1.5.1.2"

View File

@@ -43,13 +43,9 @@ declare ingress_interface
declare ingress_port
#declare keyfile
# The app's own baseURL is always the Supervisor ingress-entry path — this is
# unchanged from before. FileBrowser Quantum has no known "ignore baseURL for
# routing" leniency the way classic filebrowser's app does, so ingress access
# is left completely untouched here. Direct ip:port access is handled below by
# a second, separate nginx vhost (direct.conf) that rewrites a fixed public
# path onto this same ingress-entry baseURL, instead of changing the baseURL
# itself.
# The app's own baseURL is the Supervisor ingress-entry path, unchanged from
# before: FileBrowser emits that prefix as absolute links in its HTML and JS,
# so it is also the path direct ip:port access has to use (see direct.conf).
FB_BASEURL=$(bashio::addon.ingress_entry)
export FB_BASEURL
@@ -67,11 +63,11 @@ sed -i "s|%%port%%|${ingress_port}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%interface%%|${ingress_interface}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/ingress.conf
# --- Direct ip:port access (separate from ingress, see comment above) ---
# Publishes a second nginx vhost on a fixed internal port (published to the
# host as 8071 via config.yaml's `ports:`), at a fixed public path
# (/filebrowser_quantum/), that proxies to the same backend the ingress vhost
# uses. This keeps the app's own baseURL, and therefore ingress, unchanged.
# --- Direct ip:port access (separate vhost, ingress untouched) ---
# Listens on 8072, published to the host as 8071 by config.yaml's `ports:`.
# Requests are passed through unchanged; the bare root and the two legacy
# /filebrowser_quantum paths are redirected to the app's baseURL, which is what
# its own links already point at.
sed -i "s|%%protocol%%|${ADDON_PROTOCOL}|g" /etc/nginx/servers/direct.conf
sed -i "s|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/direct.conf

View File

@@ -6,19 +6,35 @@ server {
client_max_body_size 0;
# nginx listens on 8072 inside the container but is published to the host
# as 8071. An absolute redirect would be built from $server_port and send
# the browser to :8072, which is not published and therefore unreachable.
absolute_redirect off;
# FileBrowser serves under its baseURL (the Supervisor ingress entry) and
# emits that prefix as absolute links in its HTML/JS, so the browser must
# use that same path here. The bare root and the two legacy paths below
# redirect to it; every other request is proxied through untouched, which
# keeps asset, API and websocket URLs working without response rewriting.
location = / {
return 302 /filebrowser_quantum/;
return 302 %%subpath%%;
}
# 1.5.1.1 briefly documented /filebrowser_quantum/ as the direct URL. The
# app never served that path itself, so send those bookmarks on instead of
# letting them fall through to a 404.
location = /filebrowser_quantum {
return 301 /filebrowser_quantum/;
return 302 %%subpath%%;
}
location /filebrowser_quantum/ {
add_header Access-Control-Allow-Origin *;
location = /filebrowser_quantum/ {
return 302 %%subpath%%;
}
location / {
proxy_connect_timeout 30m;
proxy_send_timeout 30m;
proxy_read_timeout 30m;
proxy_pass %%protocol%%://backend%%subpath%%;
proxy_pass %%protocol%%://backend;
}
}

View File

@@ -1,4 +1,7 @@
## 3.1.0.1 (2026-08-17)
- Fix `password authentication failed for user` when `DB_PASSWORD` contains special characters. Passwords are now URI-encoded before being used in the psql connection string, and SQL-escaped before being used in `CREATE`/`ALTER USER` statements
## 3.1.0 (2026-08-01)
- Update to latest version from immich-app/immich (changelog : https://github.com/immich-app/immich/releases)

View File

@@ -141,6 +141,6 @@ slug: immich
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "3.1.0"
version: "3.1.0.1"
video: true
webui: http://[HOST]:[PORT:8080]

View File

@@ -98,10 +98,11 @@ setup_root_user() {
fi
# Check if the root user exists.
if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then
if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" -tAc "SELECT 1 FROM pg_roles WHERE rolname='root'" | grep -q 1; then
bashio::log.info "Root user does not exist. Creating root user with DB_ROOT_PASSWORD..."
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${DB_ROOT_PASSWORD}';
local root_password_sql="${DB_ROOT_PASSWORD//\'/\'\'}"
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE ROLE root WITH LOGIN SUPERUSER CREATEDB CREATEROLE PASSWORD '${root_password_sql}';
EOF
else
bashio::log.info "Root user exists with a non-default password. No migration needed."
@@ -113,10 +114,10 @@ setup_database() {
bashio::log.info "Setting up external PostgreSQL database..."
# Create the database if it does not exist
if ! psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \
if ! psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}/postgres" -tAc \
"SELECT 1 FROM pg_database WHERE datname='${DB_DATABASE_NAME}';" | grep -q 1; then
bashio::log.info "Database does not exist. Creating it now..."
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
CREATE DATABASE ${DB_DATABASE_NAME};
EOF
else
@@ -124,20 +125,21 @@ EOF
fi
# Ensure the user exists and update its password
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
local db_password_sql="${DB_PASSWORD//\'/\'\'}"
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USERNAME}') THEN
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}';
ELSE
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${db_password_sql}';
END IF;
END
\$\$;
EOF
# Ensure the user has full privileges on the database
psql "postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}" << EOF
psql "postgres://${DB_USERNAME_URI}:${DB_PASSWORD_URI}@${DB_HOSTNAME}:${DB_PORT}" << EOF
GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME};
EOF
@@ -147,7 +149,7 @@ EOF
# Function to check if the vectors (pgvecto.rs) extension is available on the server
check_vector_extension() {
echo "Checking if 'vectors' extension is available for database '${DB_DATABASE_NAME}'..."
RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';")
RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vectors';")
if [[ "$RESULT" == "1" ]]; then
echo "✅ 'vectors' extension is available."
return 0
@@ -163,7 +165,7 @@ check_vector_extension() {
# itself on first startup; checking pg_extension would false-warn on every fresh install.
check_vchord_extension() {
echo "Checking if 'vchord' extension is available for database '${DB_DATABASE_NAME}'..."
RESULT=$(psql "postgres://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';")
RESULT=$(psql "postgres://$DB_USERNAME_URI:$DB_PASSWORD_URI@$DB_HOSTNAME:$DB_PORT/${DB_DATABASE_NAME}" -tAc "SELECT 1 FROM pg_available_extensions WHERE name = 'vchord';")
if [[ "$RESULT" == "1" ]]; then
echo "✅ 'vchord' extension is available."
return 0
@@ -187,6 +189,14 @@ export DB_PORT="$(bashio::config 'DB_PORT')"
export JWT_SECRET="$(bashio::config 'JWT_SECRET')"
export DB_HOSTNAME="$(bashio::config 'DB_HOSTNAME')"
# libpq percent-decodes the userinfo part of a postgres:// URI, so credentials
# containing reserved characters (% @ / : ? #) are misread and every psql call
# below fails with "password authentication failed". Encode them once here and
# use the encoded copies in URIs only - the app itself still gets the raw value
# through export_db_env. Same approach as the postgres_15/postgres_17 addons.
export DB_USERNAME_URI="$(jq -rn --arg x "$DB_USERNAME" '$x|@uri')"
export DB_PASSWORD_URI="$(jq -rn --arg x "$DB_PASSWORD" '$x|@uri')"
if bashio::config.true 'VIPS_NOVECTOR'; then
export VIPS_NOVECTOR="1"
fi

View File

@@ -1,4 +1,7 @@
## 1.26.3.1 (19-08-2026)
- Fix : tapping `Read` in the Home Assistant companion app opened the reader in an external browser, which carries no ingress session cookie, so Home Assistant answered `401 Unauthorized` before Komga was reached ([#2994](https://github.com/alexbelgium/hassio-addons/issues/2994)). Komga opens the reader with `window.open(url, '_blank')` ; nginx now injects a script that turns that popup into a navigation of the ingress panel itself. Only http(s) urls below Komga's own base path are affected, so the OAuth2 login popup and links out of Komga keep their own window
## 1.26.3 (2026-08-13)
- Update to latest version from gotson/komga (changelog : https://github.com/gotson/komga/releases)
## 1.26.1.4 (12-08-2026)

View File

@@ -101,4 +101,4 @@ schema:
slug: komga
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/komga
version: "1.26.3"
version: "1.26.3.1"

View File

@@ -60,6 +60,25 @@ server {
# Only the json/xml document types are added here, so book pages are
# never scanned.
sub_filter "http://127.0.0.1:25600/komga" "%%ingress_entry%%/komga";
# Komga opens the reader with window.open(url, '_blank'). In the Home
# Assistant companion apps the ingress panel is a webview, which hands
# such a popup to an external browser : that browser carries no ingress
# session cookie, so Home Assistant answers 401 before Komga is even
# reached. Turn that popup into a navigation of the panel itself, but
# only for the call shape Komga uses (name _blank, no feature string)
# and only for http(s) urls below window.resourceBaseUrl. That leaves
# the OAuth2 login popup (window.open(url, 'oauth2Login', '<features>'),
# which needs its own window), blob urls and links out of Komga alone,
# and if Komga ever stopped setting resourceBaseUrl the popup is left
# untouched rather than widened to the whole Home Assistant origin,
# which ingress shares. Anchored on the single page app mount point :
# both Komga ui shells carry it once, and only a book served as
# text/html rather than the xhtml the epub spec mandates could collide
# with it -- the same exposure the /komga filter above already has, and
# Komga sends script-src 'none' on that endpoint.
sub_filter "<div id=\"app\">" "<script>(function(){var o=window.open;window.open=function(u,n,f){try{var b=window.resourceBaseUrl;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&t.pathname.indexOf(b)===0){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)}})();</script><div id=\"app\">";
sub_filter_types application/json application/webpub+json
application/divina+json application/opds+json
application/atom+xml;

View File

@@ -1,4 +1,7 @@
## 3.4.1.3 (2026-08-18)
- Fixed the `404: Not Found` on **Discover** persisting for browsers that had already opened Seerr through ingress, even after 3.4.1.1 and 3.4.1.2 were installed (#2975). Seerr serves its JavaScript bundle with `Cache-Control: public, max-age=31536000, immutable`, and the add-on's nginx rewrites that bundle to carry the ingress prefix - which strips the `ETag` and `Last-Modified` a browser would revalidate with. Since every add-on version served the same upstream build, the chunk URLs never changed either, so a browser kept replaying the broken 3.4.1/3.4.1.1 JavaScript from its own cache for up to a year and no fix could reach it. That is why the report persisted on the origin the reporter uses daily (`https://<domain>/`) while a browser that had never cached it (`http://<ip>:8123/`) already showed the fixed behaviour. The asset paths now carry the add-on version, so each release has its own URLs and the first page load after an update fetches the current bundle. Only ingress was affected; the directly published port 5055 always worked.
## 3.4.1.2 (2026-08-18)
- Fixed **Discover** in the sidebar still failing through ingress after 3.4.1.1 (#2975). The trailing slash added in 3.4.1.1 was also applied to the copy of the link inside Seerr's JavaScript bundle, and Next.js' client-side router strips a trailing slash before navigating: it then sent the click to a URL Home Assistant does not route, so it either landed on the same `404: Not Found` or threw `Invariant: attempted to hard navigate to the same URL` and did nothing at all. The bundle is no longer rewritten, so **Discover** routes inside the app exactly like **Requests**, **Issues** and **Settings** already did. The server-rendered link keeps its trailing slash. Only ingress was affected; the directly published port 5055 always worked.

View File

@@ -96,4 +96,4 @@ schema:
slug: seerr
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/seerr
version: "3.4.1.2"
version: "3.4.1.3"

View File

@@ -12,10 +12,27 @@ ingress_port=$(bashio::addon.ingress_port)
ingress_interface=$(bashio::addon.ip_address)
ingress_entry=$(bashio::addon.ingress_entry)
# Cache-busting marker for the rewritten JavaScript bundle.
#
# Seerr serves /_next/static/ as "public, max-age=31536000, immutable", and
# nginx's sub_filter strips ETag and Last-Modified off every response it
# rewrites, while the HTML naming those chunks is served "no-store" and keeps
# naming the same URLs. A browser therefore pins the bundle this add-on rewrote
# on its first visit for a year, with no request left that could deliver a
# later change to the sub_filter rules below - which is how #2975 outlived two
# fixes. Folding the version into the asset path gives every release its own
# URLs. njs/ingress.js strips the marker again before proxying.
#
# BUILD_VERSION is the add-on version baked in at build time (it is also what
# bashio::addon.version returns). Only [A-Za-z0-9-] survives: the marker ends up
# inside a regex literal in Seerr's own bundle, where a dot would be a wildcard.
asset_tag="ha-$(printf '%s' "${BUILD_VERSION:-0}" | tr -c 'A-Za-z0-9' '-')"
# Update ingress.conf with actual values
sed -i "s|%%port%%|${ingress_port}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%interface%%|${ingress_interface}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%ingress_entry_escaped%%|${ingress_entry//\//\\\\\/}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%asset_tag%%|${asset_tag}|g" /etc/nginx/servers/ingress.conf
bashio::log.info "Nginx ingress configured on ${ingress_interface}:${ingress_port}"
bashio::log.info "Nginx ingress configured on ${ingress_interface}:${ingress_port} (asset tag ${asset_tag})"

View File

@@ -47,11 +47,27 @@ function encodePart(part) {
}
/*
* Returns the request URI with the path untouched byte-for-byte and only the
* query string repaired. Used as the proxy_pass target.
* The cache-busting marker servers/ingress.conf inserts in front of every
* rewritten "/_next" path, e.g. "/ha-3-4-1-3/_next/static/chunks/x.js". It
* gives each add-on release its own asset URLs - Seerr serves /_next/static/ as
* immutable for a year and sub_filter strips the validators, so identical URLs
* would pin the rewritten bundle in the browser forever. Seerr knows nothing
* about the marker, so it is removed again here, on the way in.
*
* Any marker is accepted, not just the one this container serves: a tab opened
* before an add-on update keeps requesting its dynamic chunks under the marker
* it was handed, and those have to keep working until it is reloaded. The
* lookahead keeps a real Seerr path that merely starts with "ha-" untouched.
*/
var ASSET_TAG = /^\/ha-[0-9A-Za-z-]+(?=\/_next(\/|$))/;
/*
* Returns the request URI with the path untouched byte-for-byte apart from the
* cache-busting marker, and only the query string repaired. Used as the
* proxy_pass target.
*/
function uri(r) {
var raw = r.variables.request_uri;
var raw = r.variables.request_uri.replace(ASSET_TAG, "");
var split = raw.indexOf("?");
if (split < 0) {

View File

@@ -77,8 +77,14 @@ server {
# because the compiled output spells the prop 'href:"/"' and not
# 'href="/"'. These are textual substitutions over someone else's minified
# output: recheck them whenever Seerr or Next.js is upgraded.
sub_filter '\/_next' '%%ingress_entry_escaped%%\/_next';
sub_filter '/_next' '$app/_next';
# "%%asset_tag%%" is a cache-busting marker carrying the add-on version,
# substituted by 32-nginx_ingress.sh - which explains why it is needed.
# In short: without it a browser replays the bundle this file produced at
# the version it first loaded, for a year, and no later change to any
# rule here can reach it. njs/ingress.js strips the marker back off
# before proxying; the two belong together, do not change one alone.
sub_filter '\/_next' '%%ingress_entry_escaped%%\/%%asset_tag%%\/_next';
sub_filter '/_next' '$app/%%asset_tag%%/_next';
sub_filter '/api/v1' '$app/api/v1';
sub_filter '/login/plex/loading' '$app/login/plex/loading';
sub_filter '/images/' '$app/images/';