Compare commits

...

3 Commits

Author SHA1 Message Date
alexbelgium
bae169871a docs(skill): pin the upstream digest for revert comparisons, and note layer whiteouts
Both raised in review of #3028: with build_from :latest a revert only
discriminates when both builds resolve the same upstream image, and plain tar
does not process OCI whiteouts when reconstructing a rootfs from layers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 08:30:33 +02:00
alexbelgium
979f7dbe5c docs(skill): record that a clean local reproduction does not outrank a production report
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 08:24:40 +02:00
alexbelgium
1d86e79dce revert(filebrowser_quantum): drop the ingress window.open filter pending download report
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 08:18:20 +02:00
4 changed files with 64 additions and 31 deletions

View File

@@ -58,3 +58,60 @@ Once the rebuilt add-on is running, re-run the measurement that motivated the wo
cannot be self-verified — a service that reads its environment only at start makes an env-var fix
unproven until the add-on restarts, which needs the user or `ha-cli` with their agreement. If you
cannot restart, the change is **Assumed**, not Verified, and must be reported that way.
## A local reproduction that passes does not outrank a user's production report
PR #3026 added an nginx `sub_filter` to the `filebrowser_quantum` ingress vhost. After it merged,
the user reported that Download had stopped working — the file opened inline instead. Every check
run against the change came back clean:
- the shipped bundle's Download action builds an `<a href>` and clicks it, and never calls
`window.open`, which is all the injected shim overrides;
- the shim's own predicate, evaluated live in the running app, returned false for the download
URL, the `inline=true` raw URL and the public-share download URL;
- the download response through the vhost was **byte-identical** with and without the `sub_filter`
(`Content-Disposition: attachment` intact, `Content-Length` unchanged), for `text/plain` and for
the `text/html` case the filter actually scans;
- upstream's frontend download code is unchanged from v1.5.0-stable to v1.5.4-stable, and Home
Assistant's ingress iframe carries no `sandbox` attribute (`ha-panel-app.ts`), so downloads are
not sandbox-blocked either.
None of that reproduced the failure, and none of it explained the one fact that mattered: the user
saw it fail under ingress and work on the add-on's direct port, and the `sub_filter` was the only
ingress-side change in that update. **Revert first and keep investigating.**
A revert can also discriminate — if the symptom survives it, the cause was the concurrent upstream
bump rather than the diff — but only if **both builds resolve the same upstream image**. With
`build_from: <image>:latest` they need not: `latest` can move between the two rebuilds, and then
the comparison proves nothing. Resolve the tag to a digest and record it before and after, or pin
`build_from` to that digest for the duration of the experiment:
```bash
T=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:<repo>:pull" | jq -r .token)
curl -s -H "Authorization: Bearer $T" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
"https://registry-1.docker.io/v2/<repo>/manifests/latest" \
| jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest'
```
For the case above both builds resolved `gtstef/filebrowser:latest` amd64 to
`sha256:e549e1a9b5de573c276f7be67444841559e3f89cbc664a2f926968453d069436` (v1.5.4-stable, created
2026-08-29T14:19:56Z), so that comparison was controlled — but it was luck, not design.
Worth building next time, because it took most of the session and is reusable: a reproduction rig
made **out of the published image's own layers**, with no dockerd. Pull the manifest and blobs from
the registry with `curl` + `jq`, untar them in order, then run the extracted binary through the
image's own musl loader (`root/lib/ld-musl-x86_64.so.1 ./filebrowser`) with the real `http/dist`
next to it. Plain `tar` does not interpret whiteouts, so a file a later layer deletes
(`.wh.<name>`) or a directory it marks opaque (`.wh..wh..opq`) survives into the reconstructed
rootfs; check with `tar tzf <layer> | grep '\.wh\.'` and reach for an OCI-aware unpacker if any
turn up. (There were none in `gtstef/filebrowser:latest` — all eleven layers, 0 whiteout entries —
so that rig was faithful.) Put the add-on's rendered `ingress.conf` in front of it, and a second nginx server in
front of that to strip the `/api/hassio_ingress/<token>` prefix the way Supervisor does. That gets
the real frontend, the real backend and the real vhost under a browser — everything except
Supervisor itself.
**`build_from: <image>:latest` means every merge ships an upstream version bump too.** Record which
upstream version each add-on image was built from — the registry config blob's `created` timestamp,
the resolved manifest digest, and the binary's version string — before attributing a regression to
the diff.

View File

@@ -1,4 +1,9 @@
## 1.5.3.2 (2026-08-30)
- Revert the 1.5.3.1 ingress filter while a report of broken downloads is
investigated. "Open parent directory" in the tool views opens a new tab
again, as it did up to 1.5.3.
## 1.5.3.1 (2026-08-29)
- Fix "open parent directory" in Tools -> File Size Analyzer under Home
Assistant ingress. FileBrowser opened the parent folder in a new tab, which

View File

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

View File

@@ -3,7 +3,7 @@ server {
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
client_max_body_size 0;
location / {
@@ -12,35 +12,6 @@ server {
proxy_send_timeout 30m;
proxy_read_timeout 30m;
proxy_pass %%protocol%%://backend%%subpath%%;
# Tools -> File Size Analyzer (and the other tool views) open a result's
# parent folder with window.open(<absolute url>, '_blank'), because
# goToItem() takes its newTab argument from the context menu's
# showLimitedOptions flag, which those views always set. Behind ingress
# that popup lands on the raw /api/hassio_ingress/<token>/ url with no
# Home Assistant frontend around it to keep the ingress session alive,
# so the new tab answers 401 instead of showing the folder. Turn that
# popup into a navigation of the panel itself.
#
# The context menu's "go to item" action, which search results and the
# tool views also offer, hardcodes the same new tab and is fixed too.
#
# Scoped to the two prefixes goToItem() builds, "files/" and
# "public/share/", so the window.open calls that download or preview a
# file (they go to api/resources/download) keep their own tab: sending
# an inline raw file to location.assign would replace the whole app.
# A link out of the add-on keeps its own tab as well, unless a user
# points a sidebar link (or a link inside a file open in the editor) at
# this same instance's files/ or public/share/ route, which then also
# opens in the panel. Same shape as the komga add-on's ingress filter.
#
# Injected into the page's existing nonce-carrying inline script rather
# than next to <div id="app">: FileBrowser sends
# script-src 'self' 'nonce-<random>', so a standalone inline <script>
# would be blocked. If upstream ever drops that
# window.__pwaDeferredPrompt line the filter simply stops matching and
# the popup behaviour returns, which is the pre-fix state.
sub_filter "window.__pwaDeferredPrompt = null;" "window.__pwaDeferredPrompt = null;(function(){var o=window.open;window.open=function(u,n,f){try{var b=(window.globalVars||{}).baseURL;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+'files/')===0||t.pathname.indexOf(b+'public/share/')===0)){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)}})();";
}
}