Compare commits

..

22 Commits

Author SHA1 Message Date
Enrique
1cef061e05 bento new simple version + updater (#3042)
Co-authored-by: ToledoEM <8144940+ToledoEM@users.noreply.github.com>
2026-09-04 08:20:12 +02:00
Alexandre
561ef230a9 feat(baikal): update to Baikal 0.12.1 and track sabre-io releases (#3039)
* feat(baikal): update to Baikal 0.12.1 and track sabre-io releases

ckulka/baikal-docker, and the archived fork the addon was based on, stopped
publishing images at Baikal 0.10.1, so the addon could not follow upstream and
its updater tracked a repository that no longer moves.

The base image is now used for its runtime only (nginx, php-fpm, msmtp) and the
application comes from the release published by sabre-io, which the updater can
follow. The Home Assistant timezone fix the fork carried as a whole patched
Plugin.php is applied as the single hunk it actually is, and the build fails if
sabre/dav ever moves that code.

The addon data folder holds the application as well as the user data, and it was
seeded with no-clobber, so a rebuilt image never replaced the code being served.
Application folders are now refreshed on every start ; Specific and config are
still only seeded when missing.

Closes #3038

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

* docs(baikal): satisfy markdownlint on the new changelog and readme lines

Bare URLs (MD034) and a heading with no blank line before its list (MD022 /
MD032). The blank line matches the older hand-written entries in the same file.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 09:48:26 +02:00
github-actions
e97b290d70 GitHub bot: changelog [nobuild] 2026-09-01 13:10:19 +00:00
Alexandre
3cff4f015f Update config.yaml 2026-09-01 15:04:09 +02:00
alexbelgium
8fe99bf9f7 birdnet-go-dev: rebuild for the reworked live spectrogram in fork PR #61
The previous build shipped the SoX-default render (its own axes, legend
and palette). PR #61 now matches the detection spectrogram style, so
rebuild to re-merge it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 14:08:25 +02:00
alexbelgium
b5002906a7 birdnet-go-dev: rebuild to pick up fork PR #61 (SoX live spectrogram)
merge-prs.sh re-clones the fork and merges every open non-draft PR on
each build, so bumping the version is what pulls in alexbelgium/birdnet-go#61
along with the Dockerfile changes made since the last build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:01:18 +02:00
Alexandre
4a2d04144f Update Dockerfile 2026-09-01 10:31:12 +02:00
GitHub Actions
5ced5d1554 Revert "Update version to 20260901.2"
This reverts commit 67b093681f.
2026-09-01 08:28:02 +00:00
dependabot[bot]
164facc49f build(deps): bump anthropics/claude-code-action from 1.0.199 to 1.0.210 (#3036)
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.199 to 1.0.210.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](dcb57747bf...a874e9ecd7)

---
updated-dependencies:
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.210
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 10:26:31 +02:00
Alexandre
67b093681f Update version to 20260901.2 2026-09-01 10:26:18 +02:00
Alexandre
bb4c74a734 Refactor Dockerfile to improve clarity and efficiency
Removed comments and streamlined the Dockerfile for clarity. Adjusted package installations and added healthcheck configurations.
2026-09-01 10:25:46 +02:00
github-actions
29bc24ce71 GitHub bot: changelog [nobuild] 2026-09-01 08:16:27 +00:00
Alexandre
138e3ca0d3 Update config.yaml 2026-09-01 10:10:14 +02:00
alexbelgium
a85eac8c5f fix(birdnet-go-dev): build with golang:1.27-trixie to match upstream go.mod
Upstream commit 9d166ef2 raised go.mod to 'go 1.27.0' and moved its own
Dockerfile to golang:1.27-trixie. The addon keeps a separate copy of that
Dockerfile, which stayed on golang:1.26-trixie, so the amd64 build failed
with 'go.mod requires go >= 1.27.0 (running go 1.26.7; GOTOOLCHAIN=local)'.

Re-bumps to 20260901 to retrigger the builder after the failed run was
auto-reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 08:56:34 +02:00
GitHub Actions
35f2041142 Revert "birdnet-go-dev: bump to 20260901 (upstream sync + PR #6 conflict resolved)"
This reverts commit 34eebba154.
2026-09-01 06:43:08 +00:00
alexbelgium
34eebba154 birdnet-go-dev: bump to 20260901 (upstream sync + PR #6 conflict resolved)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 08:37:09 +02:00
Alexandre
59690b157d fix(filebrowser_quantum): keep the no-preview Download and Open file links inside the ingress panel (#3033)
* fix(filebrowser_quantum): keep the no-preview Download and Open file links inside the ingress panel

1.5.3.2 fixed the download anchor api/resources.js builds and clicks itself,
but the 'no preview available' screen -- what a .zip or .bin gets -- offers its
own Download and 'Open file' buttons as target="_blank" links, and so does the
share list in settings. The companion app has no navigationAction policy
delegate, so every new-window request reaches createWebViewWith and is handed
to an external browser, which carries no ingress session cookie: 401.

Replaces the wrapper around HTMLAnchorElement.prototype.click with a single
capturing click listener. It reaches the hidden anchor exactly as before -- a
programmatic .click() dispatches through the document like a real one -- and
also the two the user clicks, which the wrapper never saw.

Downloads gain the attribute everywhere; dropping target="_blank" is limited
to the companion app, identified by the Mobile/HomeAssistant marker it appends
to the user agent, because in a real browser a new tab is the better
behaviour.

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

* fix(filebrowser_quantum): leave modified clicks alone, and correct the comments

Review of #3033 found that the listener ran for every click, so a
cmd/ctrl/shift-click on the visible Download link -- an explicit request for a
separate context -- was turned into a download instead. It now only touches
unmodified primary clicks, which is also what a programmatic .click() reports
(button 0, no modifiers), so the hidden anchor is unaffected.

Also corrects three overclaims: the listener reaches connected anchors only
(both shipped download paths append theirs first); absolute sidebar links go
through window.open rather than an anchor and are not covered; and links inside
the pdf, srcdoc-preview and OnlyOffice iframes are a separate document this
listener never sees. Records that an error response is now saved as a file.

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

* docs(filebrowser_quantum): say why the download branch also drops target

Review of #3033 read the unconditional target removal as contradicting the
comment above it. The removal is deliberate: with the download attribute set, a
same-origin link downloads and never opens a tab, so it changes nothing in a
browser (measured), but it stops the companion app from taking its new-window
path before it considers the download -- an ordering not testable from outside
iOS. The comment now says so.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 10:54:41 +02:00
Alexandre
020d0ca897 ci(triage): name the real fault when the Claude credential is rejected (#3034)
The AI pipeline has been down since ~2026-08-25. Every Claude-backed step fails
with:

  "result": "Failed to authenticate. API Error: 401 OAuth access token has been
             revoked."
  "error": "authentication_failed", "api_error_status": 401

The CLAUDE_CODE_OAUTH_TOKEN secret (last updated 2026-07-24) has been revoked.
That is not fixable in code — it needs regenerating — but the six days it went
unnoticed are, because nothing on the way out said so.

What a maintainer actually saw was the action reporting:

  "--json-schema was provided but Claude did not return structured_output.
   Result subtype: success"

which points at the schema, and then Apply verdict's generic "usually a
workflow-level fault ... left untouched for a retry". Neither mentions
credentials, and the failure presents per-issue while the real scope is every
tier at once: tier 1 cannot label, so tier 2's batch is empty and the sweep
reports success daily having done nothing.

GATE 1 now checks the execution file for authentication_failed / HTTP 401
before the max-turns branch and says what is wrong and what to do — regenerate
with `claude setup-token`, update the secret in the CR_PAT environment, and set
AI_DISABLED=true to silence the runs meanwhile. Same array guard and
fail-closed posture as hit_max_turns: an unrecognised shape is simply not an
auth failure and falls through to the generic branch.

Behaviour is otherwise unchanged — this branch already exited 1 without
touching labels, which was correct for a systemic fault.

Verified against the exact execution-file shape captured from the live 08-30
failure (both the issues and catch-up paths report the new error), and
regression-checked that max_turns still escalates on the automated retry, that
generic failures keep the generic message with and without an execution file,
and that the verdict paths are untouched.

Co-authored-by: claude-ai-fix[bot] <claude-ai-fix[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 10:25:59 +02:00
Alexandre
c8ca11604e fix(calibre-web): document the optional features, drop the dead calibre install (#3032)
* feat(calibre-web): document the optional features and drop the dead calibre install

Issue #1143 asks how to add Calibre-Web's optional extras (metadata, kobo,
...) to the add-on. They are already there: the LinuxServer base image pip
installs optional-requirements.txt alongside requirements.txt into its
/lsiopy virtualenv, so every extra ships enabled. Listing the .dist-info
directories of the published image confirms scholarly, rarfile, py7zr,
mutagen, jsonschema, python-ldap, flask-dance, PyDrive2, comicapi and the
rest are present. Running pip install calibreweb[...] in the container just
fetches an unused second copy from PyPI and is thrown away when the
Supervisor recreates the container.

What actually hides the reporter's cover fields is Calibre-Web's own
gating: book_edit.html only renders 'Fetch Cover from URL' and 'Upload
Cover from Local Disk' when current_user.role_upload() and g.allow_upload
are both true, i.e. Enable Uploads plus the user's Upload permission. The
README now says so.

While checking where the Calibre binaries come from, the Dockerfile step
that claimed to install them turned out to be dead. The image has no wget,
only curl, so 'wget ... | sh /dev/stdin install_dir=/opt/calibre' loses
wget to exit 127, hands sh an empty script, and the pipeline still exits 0.
In the published amd64 image that RUN's layer decompresses to an empty tar
and no layer contains anything under /opt/calibre. Repairing it with curl
would not help: calibre's installer then hard-exits on the missing libEGL,
libOpenGL and libxcb-cursor that the universal-calibre mod apt-installs
itself, so the build would start failing and the image would grow by about
a gigabyte for binaries the mod already provides at start. The step is
removed and a comment records where the binaries really come from.

Refs https://github.com/alexbelgium/hassio-addons/issues/1143

* docs(calibre-web): tighten the optional-features wording after review

Say pip install calibreweb[...] is unsupported and can disturb the pinned
dependencies rather than calling the result unused, scope the extra-package
advice to compatible packages, list the calibre binaries as examples rather
than as a set all three operations need, and mark the docker mod as the
default rather than a certainty since DOCKER_MODS can be overridden.
2026-08-31 07:18:40 +02:00
github-actions
0f5b646078 Github bot : image compressed 2026-08-30 23:12:32 +00:00
Alexandre
408eef8a0f fix(lint): make the Unicode-space regex actually match Unicode spaces (#3027)
The `Lint workflows` autofix job has failed on every scheduled run since at
least 2026-08-16, with `shfmt` reporting parse errors ("LitWord cannot be
followed by a word", "${ stmts;} is a mksh feature", ...) in ~70 shell scripts
that parse cleanly on a pristine checkout.

Root cause is the preceding "Fix non-printable Unicode spaces" step. Its regex
was written as `$'[\\u00A0\\u2002...]'`: the doubled backslash makes bash's
ANSI-C quoting emit the literal text ` `, and Perl has no `\u` codepoint
escape — `\u` is the titlecase-next-character operator, so the character class
degrades to the plain characters `0 2 3 5 7 8 9 A B F`. The step therefore
replaced those digits and letters with spaces in every text file in the repo,
which is what left the shell scripts unparseable. `shfmt` then exited 1 and the
job stopped before opening its autofix PR — the only reason the corruption was
never committed.

Switch to Perl's own `\x{...}` escape in a plain single-quoted string, so the
class holds the ten intended code points and nothing else.

Verified locally against the exact step body extracted from the workflow: on a
sample of the repo it now rewrites only the real U+202F occurrences (e.g.
`postgres_15/.../99-run.sh`, `birdnet-pi/DOCS.md`) and leaves all other text
untouched, and `shfmt v3.12.0 -w -i 4 -ci -bn -sr` over the whole repo exits 0
with no parse errors.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 09:50:12 +02:00
Alexandre
0997c302ae fix(filebrowser_quantum): make Download save the file in the iOS companion app (#3030)
* fix(filebrowser_quantum): make Download save the file in the iOS companion app

FileBrowser downloads by clicking an <a> that carries no download attribute
and letting the attachment response do the rest. The Home Assistant iOS
companion app is a WKWebView, where a download only happens when WebKit turns
a navigation action into a WKDownload -- which is what the download attribute
does, and the app hands the result 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 inside the ingress panel a plain attachment
navigation is simply rendered: a text file opens and shows its content with no
way to save it.

The ingress filter now adds the attribute, matched on the two exact download
endpoints so nothing else in the app is touched. Desktop browsers already
downloaded these and are unaffected, and an empty value keeps the filename the
server sends in Content-Disposition.

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

* fix(filebrowser_quantum): keep Open file a navigation, and narrow the claims

Review of #3030 found that the 'no preview available' fallback renders an
'Open file' link on the same download endpoint with inline=true
(views/files/Preview.vue), so a pathname-only match would have turned opening
a file into downloading it. Exclude inline=true.

Also narrows two overstated claims: the app's download manager is gated on
iOS 17, and the public-share sidebar downloads with window.open() rather than
an anchor, so it is not covered.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 09:49:09 +02:00
160 changed files with 291 additions and 93 deletions

View File

@@ -58,60 +58,3 @@ 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 64 KiB

BIN
.github/stats.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -125,7 +125,7 @@ jobs:
- name: Analyse and fix
if: steps.batch.outputs.count != '0'
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange. The scheduled path

View File

@@ -38,7 +38,7 @@ jobs:
run: |
set -euo pipefail
CHANGED_FILES=$(git diff --name-only "$DIFF_RANGE")
UNICODE_SPACES_REGEX=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
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=$'[\\u00A0\\u2002\\u2003\\u2007\\u2008\\u2009\\u202F\\u205F\\u3000\\u200B]'
UNICODE_SPACES_REGEX='[\x{00A0}\x{2002}\x{2003}\x{2007}\x{2008}\x{2009}\x{202F}\x{205F}\x{3000}\x{200B}]'
find . -type f ! -path "./.git/*" | while read -r file; do
MIME_TYPE=$(file --mime-type -b "$file")
if [[ "$MIME_TYPE" == text/* ]]; then

View File

@@ -64,7 +64,7 @@ jobs:
fetch-depth: 1
- name: Run Claude Code
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.

View File

@@ -135,7 +135,7 @@ jobs:
- name: Execute the plan
if: steps.bundle.outputs.has_plan == 'true'
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -166,7 +166,7 @@ jobs:
id: classify
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token
@@ -362,6 +362,25 @@ jobs:
"$EXECUTION_FILE" >/dev/null 2>&1
}
# Did the run die because the Claude credential is bad? The action
# reports this uselessly — a revoked token surfaces as "--json-schema
# was provided but Claude did not return structured_output", which
# points at the schema and not at auth. The execution file carries the
# truth: api_retry / result objects with error "authentication_failed"
# and a 401. Same array guard and fail-closed posture as above; an
# unrecognised shape simply is not an auth failure and falls through
# to the generic branch.
hit_auth_failure() {
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 1
jq -e '(type == "array") and
any(.[]?;
(type == "object") and
(((.error? // "") == "authentication_failed") or
((.error_status? // 0) == 401) or
((.api_error_status? // 0) == 401)))' \
"$EXECUTION_FILE" >/dev/null 2>&1
}
# GATE 1 — did the action itself run? This is checked BEFORE looking
# at the payload, because the action can fail *after* having written
# a valid structured output: the object would sail through the shape
@@ -380,6 +399,16 @@ jobs:
if [ "${CLASSIFY_OUTCOME:-}" = "failure" ]; then
restore_needs_info
# Checked before anything else, because it is the one failure with
# a specific remedy and it takes down every tier at once — tier 1
# cannot label, so tier 2's batch is empty and the whole pipeline
# goes quiet while each run still fails in a way that reads like a
# per-issue problem. Say plainly what is wrong and what to do.
if hit_auth_failure; then
echo "::error::CLAUDE_CODE_OAUTH_TOKEN is rejected (HTTP 401 / authentication_failed). This is NOT a problem with issue #$ISSUE — every AI workflow is down until the credential is replaced. Regenerate it with 'claude setup-token' and update the CLAUDE_CODE_OAUTH_TOKEN secret in the CR_PAT environment. Set the AI_DISABLED repo variable to 'true' to silence these runs meanwhile."
exit 1
fi
# ...with one exception. Exhausting the turn budget is NOT a
# workflow fault: the action ran fine and this particular issue was
# just too tangled to finish inside the turn budget. Treating it as systemic

View File

@@ -79,7 +79,7 @@ jobs:
- name: Address CodeRabbit comments
if: steps.claim.outputs.go == 'true'
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,10 @@
## 0.12.1 (2026-09-03)
- Update to Baikal 0.12.1 from 0.10.1 (changelog : <https://github.com/sabre-io/Baikal/releases>). This includes the 0.12.1 fix for an XSS vulnerability that let an authenticated user take over the admin interface by renaming a calendar
- ⚠ After the update, open the Baikal web admin once : Baikal asks to confirm the upgrade before it serves calendars again
- The application is now taken from the release published by sabre-io instead of from the ckulka/baikal-docker image, which stopped at 0.10.1. The base image still provides nginx, php-fpm and msmtp. Automatic version tracking is enabled again, following sabre-io/Baikal
- The Baikal application files in the addon data folder are now replaced on every start instead of being kept. Calendars, contacts, users and the Baikal configuration are untouched ; any manual edit made inside the application folders themselves is lost
- The Home Assistant project has deprecated support for the armv7, armhf and i386 architectures. Support wil be fully dropped in the upcoming Home Assistant 2025.12 release
## 0.10.1-hafix4 (2025-11-18)

View File

@@ -16,7 +16,19 @@
ARG BUILD_FROM
ARG BUILD_VERSION
ARG BUILD_UPSTREAM="0.10.1+hafix"
ARG BUILD_UPSTREAM="0.12.1"
# ckulka/baikal-docker, which builds the base image, stopped publishing new
# Baikal versions at 0.10.1 : the base image is used for its runtime only
# (nginx, php-fpm, msmtp) and the application comes from the release published
# by sabre-io itself
FROM alpine:3.21 AS baikal
ARG BUILD_UPSTREAM
RUN apk add --no-cache curl unzip \
&& curl -f -s -S -L -o /tmp/baikal.zip "https://github.com/sabre-io/Baikal/releases/download/${BUILD_UPSTREAM}/baikal-${BUILD_UPSTREAM}.zip" \
&& unzip -q /tmp/baikal.zip -d / \
&& rm /tmp/baikal.zip
FROM ${BUILD_FROM}
##################
@@ -28,6 +40,24 @@ ENV S6_CMD_WAIT_FOR_SERVICES=1 \
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
S6_SERVICES_GRACETIME=0
# Ship the Baikal release instead of the one bundled in the base image
RUN rm -rf /var/www/baikal
COPY --from=baikal --chown=nginx:nginx /baikal /var/www/baikal
# Home Assistant asks for an expanded time range, and Baikal stores
# cal:calendar-timezone as a bare timezone name rather than as the VTIMEZONE
# object sabre/dav expects, so sabre/dav raises a ParseException and answers
# 500 (sabre-io/Baikal#1241 and sabre-io/dav#1318, both still open). Read the
# value as a timezone name instead. The two checks turn a release that moved
# this code into a failed build rather than into an addon that Home Assistant
# cannot read.
# hadolint ignore=SC2016
RUN \
DAVPLUGIN="/var/www/baikal/vendor/sabre/dav/lib/CalDAV/Plugin.php" \
&& sed -i '/^ \/\/ This property contains a VCALENDAR with a single$/,/^ \$vtimezoneObj->destroy();$/c\ $calendarTimeZone = new DateTimeZone($tzResult[$tzProp]);' "$DAVPLUGIN" \
&& grep -qxF ' $calendarTimeZone = new DateTimeZone($tzResult[$tzProp]);' "$DAVPLUGIN" \
&& ! grep -qxF ' $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();' "$DAVPLUGIN"
# Image specific modifications
# hadolint ignore=SC2015, SC2013, SC2086
RUN \

View File

@@ -33,7 +33,9 @@ _Thanks to everyone having starred my repo! To star it click on the image below,
---
[Baikal](https://sabre.io/baikal/) is a lightweight CalDAV+CardDAV server. It offers an extensive web interface with easy management of users, address books and calendars. It is fast and simple to install and only needs a basic php capable server. The data can be stored in a MySQL or a SQLite database.
It is based on the docker image : https://github.com/ckulka/baikal-docker
It ships the release published by [sabre-io](https://github.com/sabre-io/Baikal/releases), running on the nginx and php-fpm image built by <https://github.com/ckulka/baikal-docker>.
After an update of Baikal itself, open the web admin once : Baikal asks to confirm the upgrade before it serves calendars again. Calendars, contacts, users and the Baikal configuration are kept, but a manual edit made inside the application folders themselves is replaced on every start.
## Configuration

View File

@@ -1,6 +1,6 @@
{
"build_from": {
"aarch64": "ghcr.io/mralucarddante/baikal-docker-hass:latest",
"amd64": "ghcr.io/mralucarddante/baikal-docker-hass:latest"
"aarch64": "ckulka/baikal:nginx-php8.2",
"amd64": "ckulka/baikal:nginx-php8.2"
}
}

View File

@@ -84,5 +84,5 @@ schema:
slug: baikal
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: 0.10.1-hafix4
version: "0.12.1"
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"

View File

@@ -1,7 +1,21 @@
#!/bin/bash
set -e
# Copy data
cp -rnf /var/www/baikal/* /data/
# Baikal keeps its database in Specific and its configuration in config. The
# release ships both as empty folders, so they are created here rather than
# copied, and are then left alone : they hold the user's data
mkdir -p /data/config /data/Specific/db
# Everything else is application code, and is replaced on every start so that a
# rebuilt image actually replaces the code that is served
for item in /var/www/baikal/*; do
name="$(basename "$item")"
case "$name" in
Specific | config) continue ;;
esac
rm -rf "/data/$name"
cp -rf "$item" /data/
done
# Fix permissions
chown -R nginx:nginx /data

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,9 +1,9 @@
{
"github_exclude": "+",
"last_update": "26-04-2025",
"github_beta": "false",
"last_update": "2026-09-03",
"repository": "alexbelgium/hassio-addons",
"slug": "baikal",
"source": "github",
"upstream_repo": "ckulka/baikal-docker",
"upstream_version": "0.10.1"
"upstream_repo": "sabre-io/Baikal",
"upstream_version": "0.12.1"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,7 +1,20 @@
## 2.8.8 (03-09-2026)
- Now builds upstream's Simple Mode release instead of the standard one. It leaves out the bentopdf.com marketing pages (nav bar, hero, features, FAQ, footer). Both builds carry the same 130 tool pages and the same LibreOffice WebAssembly payload, so the served payload just gets smaller: about 228 MB, down from about 258 MB.
- Breaking: you can no longer reach the marketing UI, and there is no way to bring it back.
- Upstream BentoPDF is now v2.8.8: <https://github.com/alam00000/bentopdf/releases/tag/v2.8.8>
- That includes the v2.8.7 security fixes (GHSA-wh78-rcw2-hhg9, GHSA-5xjf-rr5x-pcfj, GHSA-cx8x-7rrr-r9x8), which cover every version through v2.8.6
- Version mismatch fixed. The image built upstream 2.8.2 while reporting 2.8.4; `ARG BUILD_VERSION` now matches `version`
- Added `updater.json` so `addons_updater` now tracks upstream releases automatically
## 2.8.4 (24-04-2026)
- Minor bugs fixed
## 2.8.2 (07-04-2026)
- Minor bugs fixed
# Changelog
## 2.8.2

View File

@@ -1,13 +1,19 @@
# Global build args — must be declared before the first FROM to be usable in FROM instructions
ARG BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.23
ARG BUILD_VERSION=2.8.2
ARG BUILD_VERSION=2.8.8
# Upstream BentoPDF release to fetch. Tracked separately from BUILD_VERSION so
# the add-on can carry a local patch counter (e.g. 2.8.8.1) without breaking the
# release URLs, which only exist for real upstream tags.
ARG BUILD_UPSTREAM=2.8.8
# Stage 1: Download and extract the BentoPDF dist release (includes bundled WASM)
# Stage 1: Download and extract the BentoPDF Simple Mode dist release (includes
# bundled WASM). Simple Mode drops the bentopdf.com marketing UI and keeps every
# PDF tool, so it is the only build shipped here.
FROM alpine:3.21 AS dist
ARG BUILD_VERSION
ARG BUILD_UPSTREAM
# hadolint ignore=DL3018
RUN apk add --no-cache curl unzip \
&& curl -fsSL "https://github.com/alam00000/bentopdf/releases/download/v${BUILD_VERSION}/dist-${BUILD_VERSION}.zip" \
&& curl -fsSL "https://github.com/alam00000/bentopdf/releases/download/v${BUILD_UPSTREAM}/dist-simple-${BUILD_UPSTREAM}.zip" \
-o /tmp/bentopdf.zip \
&& unzip /tmp/bentopdf.zip -d /tmp/dist

View File

@@ -104,6 +104,12 @@ A privacy-first PDF toolkit running entirely in your browser — no uploads, no
No other configuration is needed. Drop your files in and go.
### Simple Mode build
Upstream publishes two builds per release, and this add-on uses the Simple Mode one. It leaves out the bentopdf.com marketing pages: nav bar, hero, features, FAQ and footer.
You still get all 130 tool pages and the same LibreOffice WebAssembly payload as the standard build. Dropping those pages also cuts the served payload to about 228 MB, down from about 258 MB.
---
## Privacy

View File

@@ -2,7 +2,7 @@ name: "BentoPDF"
slug: bentopdf
image: ghcr.io/alexbelgium/bentopdf-{arch}
description: "Privacy-first PDF toolkit. 50+ tools, all processing client-side in the browser. Files never leave your device."
version: "2.8.4"
version: "2.8.8"
url: "https://github.com/alexbelgium/hassio-addons/tree/master/bentopdf"
arch:
- amd64

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

9
bentopdf/updater.json Normal file
View File

@@ -0,0 +1,9 @@
{
"last_update": "2026-09-03",
"repository": "alexbelgium/hassio-addons",
"slug": "bentopdf",
"source": "github",
"upstream_repo": "alam00000/bentopdf",
"upstream_version": "2.8.8",
"github_beta": false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,3 +1,13 @@
## 20260901.4 (01-09-2026)
- Minor bugs fixed
## 20260901.3 (01-09-2026)
- Rebuild: live spectrogram in Currently Hearing now uses the same SoX recipe, palette and 2:1 ratio as the detection spectrograms, with a kHz axis overlay (fork PR #61)
## 20260901.2 (01-09-2026)
- Rebuild: re-merges the open fork PRs, adding a static SoX spectrogram of the chunk being analysed to the Currently Hearing card (fork PR #61)
## 20260901.1 (01-09-2026)
- Minor bugs fixed
## 20260901 (01-09-2026)
- Synced with upstream birdnet-go (7 commits, incl. Go 1.27 upgrade); build image bumped to golang:1.27-trixie to match; resolved merge conflict in fork PR #6
## 20260829.2 (29-08-2026)
- Minor bugs fixed
## 20260829.1 (29-08-2026)

View File

@@ -89,7 +89,7 @@ RUN apk add --no-cache curl && \
# 1a Build environment (mirrors tphakala/birdnet-go Docker/Dockerfile) #
#########################################################################
FROM --platform=$BUILDPLATFORM golang:1.26-trixie AS buildenv
FROM --platform=$BUILDPLATFORM golang:1.27-trixie AS buildenv
ARG BUILD_VERSION
ENV BUILD_VERSION=${BUILD_VERSION:-unknown}

View File

@@ -127,5 +127,5 @@ slug: birdnet-go-dev
udev: true
url: https://github.com/alexbelgium/hassio-addons
usb: true
version: "20260829.2"
version: "20260901.4"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,4 +1,8 @@
## 0.6.27.3 (2026-08-30)
- Doc: explain in the README that Calibre-Web's optional extras (metadata, kobo, gdrive, gmail, goodreads, ldap, oauth, comics) are already installed by the LinuxServer base image, that `pip install calibreweb[...]` inside the container is useless and not persistent, and that the cover fields on the Edit Metadata page are gated on `Enable Uploads` plus the user's `Upload` permission (https://github.com/alexbelgium/hassio-addons/issues/1143)
- Fix: remove the Dockerfile step that claimed to install the Calibre binaries into `/opt/calibre`. There is no `wget` in the image, so the command failed silently and left the layer empty; the binaries are and were provided at start by the default `linuxserver/mods:universal-calibre` docker mod
## 0.6.27.2 (2026-08-23)
- Fix: trust the whole supervisor network range for the ingress auth header instead of the addon's own address, which changes across restarts. The list is only written when that range is missing, so an entry added in the calibre-web admin page is no longer erased on every start (https://github.com/alexbelgium/hassio-addons/pull/3010)

View File

@@ -35,11 +35,9 @@ RUN \
&& echo '#!/bin/sh' > /usr/bin/xdg-desktop-menu && chmod +x /usr/bin/xdg-desktop-menu \
&& echo '#!/bin/sh' > /usr/bin/xdg-mime && chmod +x /usr/bin/xdg-mime
# Install calibre binaries (provides calibredb required for book downloads)
# hadolint ignore=DL4006
RUN \
wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin install_dir=/opt/calibre
ENV PATH="/opt/calibre:${PATH}"
# By default the calibre binaries (calibredb, ebook-convert, ebook-meta) are installed at start by
# the linuxserver/mods:universal-calibre docker mod, set as the default DOCKER_MODS in config.yaml.
# The xdg-* stubs above keep its calibre_postinstall step quiet.
# Global LSIO modifications
COPY ha_lsio.sh /ha_lsio.sh

View File

@@ -97,6 +97,18 @@ This addon supports mounting both local drives and remote SMB shares:
- **Local drives**: See [Mounting Local Drives in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-Local-Drives-in-Addons)
- **Remote shares**: See [Mounting Remote Shares in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-remote-shares-in-Addons)
### Optional Calibre-Web features
Calibre-Web documents optional extras that a manual installation adds with `pip install calibreweb[metadata]` and similar. **You do not need to install anything here**: the LinuxServer base image this add-on builds on installs Calibre-Web's `requirements.txt` *and* its full `optional-requirements.txt` into the application's virtualenv, so the gdrive, gmail, goodreads, ldap, oauth, metadata, comics and kobo dependencies are all present already. Running `pip install calibreweb[...]` inside the container is not a supported way to enable them: it installs the PyPI distribution of Calibre-Web over an installation that already has those dependencies, and it can disturb the versions the base image pinned. It is also thrown away, because the Supervisor recreates the add-on container on restart.
Optional features are switched on in the Calibre-Web web interface, not in the add-on options, under `Admin` -> `Basic Configuration` -> `Feature Configuration` (for example `Enable Uploads`, `Enable Kobo sync`, `Use Goodreads`).
**Book covers.** The `Fetch Cover from URL` and `Upload Cover from Local Disk` fields only appear on a book's `Edit Metadata` page when `Enable Uploads` is ticked in `Feature Configuration` **and** the logged-in user has the `Upload` permission (`Admin` -> the user -> `Upload`). A missing Python package is not what hides them.
**Conversion, metadata embedding and the other Calibre integrations** use command-line binaries such as `ebook-convert`, `ebook-meta` and `calibredb`. Those are installed at start by the `linuxserver/mods:universal-calibre` docker mod, which is the shipped default of the `DOCKER_MODS` option. If you set `DOCKER_MODS` yourself, keep `linuxserver/mods:universal-calibre` in the list (mods are separated by `|`) or those binaries disappear.
**Other compatible Python packages** can be installed from the add-on's custom script (see the section below); `pip` there points at Calibre-Web's own virtualenv. Such a script runs on every start, and it has to, since the container's writable layer does not persist.
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables:

View File

@@ -116,5 +116,5 @@ schema:
slug: calibre-web
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
version: "0.6.27.2"
version: "0.6.27.3"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,8 +1,28 @@
## 1.5.3.3 (2026-08-31)
- Complete the iOS companion app fix from 1.5.3.2. The "no preview available"
screen -- what you get for a `.zip`, `.bin` or anything else FileBrowser
cannot render -- offers its Download and "Open file" buttons as new-tab
links, and so does the share list in settings. The app hands every new tab
to an external browser, which carries no ingress session, so those answered
401. Those two buttons and the settings share links now stay in the panel
when running in the companion app, and open a new tab as before in a normal
browser -- as does a cmd/ctrl/shift-click anywhere, which is left alone.
Download also saves the file instead of displaying it, which 1.5.3.2 only
fixed for the download button in the file list. Links the app opens without
a link element, such as the public-share sidebar download, are still
affected.
## 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.
- 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.
## 1.5.3.1 (2026-08-29)
- Fix "open parent directory" in Tools -> File Size Analyzer under Home

View File

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

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,6 +12,101 @@ 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. Saving and opening a file. FileBrowser downloads by clicking an <a>
# that carries no download attribute -- a hidden one it builds itself
# (api/resources.js), and two visible ones in the "no preview available"
# fallback (views/files/Preview.vue), which are also target="_blank".
# Neither works in the Home Assistant companion app:
#
# - A download happens in its WKWebView 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:, iOS 17+). Its response policy delegate returns .allow for
# every sub-frame and never returns .download, so a plain attachment
# navigation in the ingress panel is just rendered: the file opens and
# shows its content with no way to save it.
# - target="_blank" is worse. The app has no navigationAction policy
# delegate, so every new-window request reaches createWebViewWith,
# which hands the url to an external browser. That browser carries no
# ingress session cookie, so Home Assistant answers 401.
#
# One capturing click listener covers both, and covers the hidden anchor
# too: a programmatic .click() on an anchor that is in the document
# dispatches through it exactly like a real one -- same event, button 0,
# no modifiers -- and the capture phase runs before the link is followed.
# It replaces an earlier wrapper around HTMLAnchorElement.prototype.click,
# which reached the hidden anchor but not the two the user clicks. The
# wrapper would also have caught a click on a *detached* anchor, which
# this cannot; both of FileBrowser's download paths append theirs to the
# body first (api/resources.js), so nothing is lost today.
#
# Only unmodified primary clicks are touched. A cmd/ctrl/shift-click is
# an explicit request for a separate context and is left alone, which
# matters on desktop and in the Mac Catalyst app.
#
# Downloads gain the attribute everywhere -- desktop browsers already
# saved these on a plain click, and an empty value keeps the filename the
# server sends in Content-Disposition. One behaviour does change there:
# if the endpoint answers with an error the body is saved as a file
# instead of being shown, because the decision is made before the
# response exists. Matched on the two exact download endpoints minus
# inline=true, which is the fallback's "Open file" link and is meant to
# open rather than save. That branch drops target="_blank" in every
# browser too: with the attribute present a same-origin link downloads
# and never opens a tab (measured), so it changes nothing here, but it
# stops the companion app from taking its new-window path before it
# considers the download -- an ordering that cannot be tested from
# outside iOS.
#
# Dropping target="_blank" from links that are *not* downloads is limited
# to the companion app, identified by the Mobile/HomeAssistant marker it
# appends to the user agent
# (HAAPI.swift, applicationNameForUserAgent -- it covers iPhone, iPad and
# Mac Catalyst). In a real browser a new tab is the better behaviour and
# is left alone; in the app it is a 401. It applies to any same-origin
# link below the add-on's own base path, which in practice is the
# "Open file" button and the share links in settings
# (views/settings/Shares.vue). Links to other origins keep their new tab,
# and only an exact target="_blank" is matched, so named windows, _parent
# and _top are untouched.
#
# Not covered, and still 401 in the app: anything the app opens with
# window.open() rather than an anchor, which is the public-share sidebar
# download and absolute sidebar links (components/sidebar/Links.vue); and
# links inside a document FileBrowser renders in its own iframe -- the
# pdf viewer, the srcdoc markdown/html preview, OnlyOffice -- because a
# listener on this document never sees another document's clicks.
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)};document.addEventListener('click',function(e){try{if(e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;var a=e.target&&e.target.closest?e.target.closest('a'):null;if(!a||!a.href)return;var b=(window.globalVars||{}).baseURL;if(!b)return;if(b.slice(-1)!=='/')b+='/';var t=new URL(a.href,location.href);if(t.origin!==location.origin)return;if(!a.hasAttribute('download')&&t.searchParams.get('inline')!=='true'&&(t.pathname===b+'api/resources/download'||t.pathname===b+'public/api/resources/download')){a.download='';a.removeAttribute('target');return}if(a.target==='_blank'&&t.pathname.indexOf(b)===0&&navigator.userAgent.indexOf('Mobile/HomeAssistant')!==-1){a.removeAttribute('target')}}catch(err){}},true)})();";
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More