mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-23 10:04:00 +02:00
Compare commits
3 Commits
408eef8a0f
...
revert/fbq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bae169871a | ||
|
|
979f7dbe5c | ||
|
|
1d86e79dce |
@@ -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.
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CHANGED_FILES=$(git diff --name-only "$DIFF_RANGE")
|
||||
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
|
||||
UNICODE_SPACES_REGEX=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
|
||||
for file in $CHANGED_FILES; do
|
||||
if [ -f "$file" ]; then
|
||||
MIME_TYPE=$(file --mime-type -b "$file")
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- name: Fix non-printable Unicode spaces in all text files
|
||||
run: |
|
||||
set -euo pipefail
|
||||
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
|
||||
UNICODE_SPACES_REGEX=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
|
||||
find . -type f ! -path "./.git/*" | while read -r file; do
|
||||
MIME_TYPE=$(file --mime-type -b "$file")
|
||||
if [[ "$MIME_TYPE" == text/* ]]; then
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
|
||||
## 1.5.3.2 (2026-08-30)
|
||||
- Fix Download in the Home Assistant iOS companion app (iOS 17 and later),
|
||||
where a file opened and showed its content with no way to save it.
|
||||
FileBrowser downloads by clicking a link that carries no `download`
|
||||
attribute, and the app's WKWebView only turns a click into a real download
|
||||
when that attribute is present, so inside the ingress panel the file was
|
||||
simply rendered. The ingress filter now adds the attribute to FileBrowser's
|
||||
own download link. "Open file" still opens, and the public-share sidebar's
|
||||
own download button is not covered. Desktop browsers already downloaded
|
||||
these and are unchanged, as is direct access on port 8071.
|
||||
- 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
|
||||
|
||||
@@ -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,60 +12,6 @@ server {
|
||||
proxy_send_timeout 30m;
|
||||
proxy_read_timeout 30m;
|
||||
proxy_pass %%protocol%%://backend%%subpath%%;
|
||||
|
||||
# Two things the ingress panel needs that a plain browser tab does not.
|
||||
# Both are 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 stops matching and both
|
||||
# behaviours revert, which is the state before either fix.
|
||||
#
|
||||
# 1. Opening a folder. The tool views (Tools -> File Size Analyzer and
|
||||
# the others) always set the context menu's showLimitedOptions flag, and
|
||||
# openParentFolder() hands that same flag to goToItem() as its newTab
|
||||
# argument, so the folder is opened with window.open(<url>, '_blank').
|
||||
# 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 it into a navigation of the panel itself. The context
|
||||
# menu's "go to item" action, offered by search results and the tool
|
||||
# views, hardcodes the same new tab and is fixed with it.
|
||||
#
|
||||
# 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. Same shape as the komga
|
||||
# add-on's ingress filter.
|
||||
#
|
||||
# 2. Download. FileBrowser downloads a file by building an <a> with no
|
||||
# download attribute, clicking it, and letting the attachment response do
|
||||
# the rest. The Home Assistant iOS companion app is a WKWebView, and a
|
||||
# download happens there only when WebKit turns a navigation *action*
|
||||
# into a WKDownload, which is what the download attribute does -- the app
|
||||
# hands that to its own download manager
|
||||
# (WebViewController+WebKitDelegates.swift, navigationAction:didBecome
|
||||
# download:). Its response policy delegate returns .allow for every
|
||||
# sub-frame and never returns .download, so a plain attachment navigation
|
||||
# inside the ingress panel is just rendered: a text file opens and shows
|
||||
# its content with no way to save it. Adding the attribute makes the same
|
||||
# click a real download. Desktop browsers already downloaded these and
|
||||
# are unaffected, and an empty value keeps the filename the server sends
|
||||
# in Content-Disposition. Matched on the two exact download endpoints,
|
||||
# minus inline=true: the "no preview available" fallback renders an
|
||||
# "Open file" link on the same endpoint with that parameter
|
||||
# (views/files/Preview.vue), and it is meant to open, not save. Only
|
||||
# programmatic clicks pass through here -- a person clicking a link never
|
||||
# calls HTMLAnchorElement.prototype.click -- so this reaches
|
||||
# FileBrowser's own hidden download anchor and nothing a user clicks.
|
||||
#
|
||||
# Not covered: the public-share sidebar downloads with window.open()
|
||||
# rather than an anchor, and the app's download manager is gated on
|
||||
# iOS 17 (WebViewController+WebKitDelegates.swift).
|
||||
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)};var c=HTMLAnchorElement.prototype.click;HTMLAnchorElement.prototype.click=function(){try{var b=(window.globalVars||{}).baseURL;if(b&&this.href&&!this.hasAttribute('download')){if(b.slice(-1)!=='/')b+='/';var t=new URL(this.href,location.href);if(t.origin===location.origin&&t.searchParams.get('inline')!=='true'&&(t.pathname===b+'api/resources/download'||t.pathname===b+'public/api/resources/download')){this.download=''}}}catch(e){}return c.apply(this,arguments)}})();";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user