mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-26 03:14:01 +02:00
Compare commits
2 Commits
b9761d9dd3
...
fix/ai-aut
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16e2c6aac5 | ||
|
|
3ac46b0cd5 |
2
.github/workflows/daily_ai_fix.yaml
vendored
2
.github/workflows/daily_ai_fix.yaml
vendored
@@ -125,7 +125,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Analyse and fix
|
- name: Analyse and fix
|
||||||
if: steps.batch.outputs.count != '0'
|
if: steps.batch.outputs.count != '0'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||||
|
|||||||
2
.github/workflows/on_claude_mention.yml
vendored
2
.github/workflows/on_claude_mention.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run Claude Code
|
- name: Run Claude Code
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||||
|
|||||||
2
.github/workflows/on_issue_approved.yaml
vendored
2
.github/workflows/on_issue_approved.yaml
vendored
@@ -135,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Execute the plan
|
- name: Execute the plan
|
||||||
if: steps.bundle.outputs.has_plan == 'true'
|
if: steps.bundle.outputs.has_plan == 'true'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
46
.github/workflows/on_issues_ai_triage.yaml
vendored
46
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -166,7 +166,7 @@ jobs:
|
|||||||
id: classify
|
id: classify
|
||||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Without this the action falls back to the OIDC -> Claude App token
|
# Without this the action falls back to the OIDC -> Claude App token
|
||||||
@@ -363,13 +363,22 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Did the run die because the Claude credential is bad? The action
|
# Did the run die because the Claude credential is bad? The action
|
||||||
# reports this uselessly — a revoked token surfaces as "--json-schema
|
# reports this uselessly — the failure surfaces as "--json-schema was
|
||||||
# was provided but Claude did not return structured_output", which
|
# provided but Claude did not return structured_output", which points
|
||||||
# points at the schema and not at auth. The execution file carries the
|
# at the schema and not at auth. The execution file carries the truth.
|
||||||
# truth: api_retry / result objects with error "authentication_failed"
|
#
|
||||||
# and a 401. Same array guard and fail-closed posture as above; an
|
# A bad credential shows up in more than one shape, and both have been
|
||||||
# unrecognised shape simply is not an auth failure and falls through
|
# seen in production within a week:
|
||||||
# to the generic branch.
|
# * revoked token -> error "authentication_failed", HTTP 401
|
||||||
|
# * malformed token -> error "invalid_request", api_error_status
|
||||||
|
# null, and the reason only in the SDK's message text ("Invalid
|
||||||
|
# Authorization header value from CLAUDE_CODE_OAUTH_TOKEN: it
|
||||||
|
# contains a line break at character 62").
|
||||||
|
# Matching only the first shape reported the second as a generic
|
||||||
|
# workflow fault, so the text marker is checked too — but only on an
|
||||||
|
# object the SDK itself flagged as an API error, so an issue body that
|
||||||
|
# merely mentions the secret's name cannot fake one. A false positive
|
||||||
|
# would change only the message: this branch exits 1 either way.
|
||||||
hit_auth_failure() {
|
hit_auth_failure() {
|
||||||
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 1
|
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 1
|
||||||
jq -e '(type == "array") and
|
jq -e '(type == "array") and
|
||||||
@@ -377,10 +386,26 @@ jobs:
|
|||||||
(type == "object") and
|
(type == "object") and
|
||||||
(((.error? // "") == "authentication_failed") or
|
(((.error? // "") == "authentication_failed") or
|
||||||
((.error_status? // 0) == 401) or
|
((.error_status? // 0) == 401) or
|
||||||
((.api_error_status? // 0) == 401)))' \
|
((.api_error_status? // 0) == 401) or
|
||||||
|
(((.is_api_error_message? // false) == true) and
|
||||||
|
(tostring | test("CLAUDE_CODE_OAUTH_TOKEN|Invalid auth token")))))' \
|
||||||
"$EXECUTION_FILE" >/dev/null 2>&1
|
"$EXECUTION_FILE" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The SDK's own words are far more useful than anything this script
|
||||||
|
# can infer — "it contains a line break at character 62" names the
|
||||||
|
# exact defect. Surface it verbatim when present.
|
||||||
|
auth_failure_detail() {
|
||||||
|
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 0
|
||||||
|
jq -r 'if type == "array" then
|
||||||
|
[ .[]? | select(type == "object")
|
||||||
|
| select((.is_api_error_message? // false) == true)
|
||||||
|
| tostring
|
||||||
|
| capture("(?<m>Invalid Authorization header value[^\"]*|Invalid auth token[^\"]*)")
|
||||||
|
| .m ] | first // ""
|
||||||
|
else "" end' "$EXECUTION_FILE" 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
# GATE 1 — did the action itself run? This is checked BEFORE looking
|
# GATE 1 — did the action itself run? This is checked BEFORE looking
|
||||||
# at the payload, because the action can fail *after* having written
|
# at the payload, because the action can fail *after* having written
|
||||||
# a valid structured output: the object would sail through the shape
|
# a valid structured output: the object would sail through the shape
|
||||||
@@ -405,7 +430,8 @@ jobs:
|
|||||||
# goes quiet while each run still fails in a way that reads like a
|
# 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.
|
# per-issue problem. Say plainly what is wrong and what to do.
|
||||||
if hit_auth_failure; then
|
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."
|
DETAIL=$(auth_failure_detail)
|
||||||
|
echo "::error::CLAUDE_CODE_OAUTH_TOKEN is being rejected${DETAIL:+ — $DETAIL}. This is NOT a problem with issue #$ISSUE: every AI workflow is down until the credential is fixed. Regenerate with 'claude setup-token' and re-enter the secret in the CR_PAT environment as a SINGLE line with no line break or trailing newline. Set the AI_DISABLED repo variable to 'true' to silence these runs meanwhile."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/on_pr_coderabbit.yml
vendored
2
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Address CodeRabbit comments
|
- name: Address CodeRabbit comments
|
||||||
if: steps.claim.outputs.go == 'true'
|
if: steps.claim.outputs.go == 'true'
|
||||||
uses: anthropics/claude-code-action@a874e9ecd7bb36efdad65429c6b35815f5a08f10 # v1
|
uses: anthropics/claude-code-action@dcb57747bfceeaa1fa72638cae52295d1d853d4a # v1
|
||||||
with:
|
with:
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
## 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
|
- 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)
|
## 0.10.1-hafix4 (2025-11-18)
|
||||||
|
|||||||
@@ -16,19 +16,7 @@
|
|||||||
|
|
||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
ARG BUILD_VERSION
|
ARG BUILD_VERSION
|
||||||
ARG BUILD_UPSTREAM="0.12.1"
|
ARG BUILD_UPSTREAM="0.10.1+hafix"
|
||||||
|
|
||||||
# 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}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
##################
|
##################
|
||||||
@@ -40,24 +28,6 @@ ENV S6_CMD_WAIT_FOR_SERVICES=1 \
|
|||||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||||
S6_SERVICES_GRACETIME=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
|
# Image specific modifications
|
||||||
# hadolint ignore=SC2015, SC2013, SC2086
|
# hadolint ignore=SC2015, SC2013, SC2086
|
||||||
RUN \
|
RUN \
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ _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.
|
[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 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>.
|
It is based on the docker image : 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
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"build_from": {
|
"build_from": {
|
||||||
"aarch64": "ckulka/baikal:nginx-php8.2",
|
"aarch64": "ghcr.io/mralucarddante/baikal-docker-hass:latest",
|
||||||
"amd64": "ckulka/baikal:nginx-php8.2"
|
"amd64": "ghcr.io/mralucarddante/baikal-docker-hass:latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,5 +84,5 @@ schema:
|
|||||||
slug: baikal
|
slug: baikal
|
||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
version: "0.12.1"
|
version: 0.10.1-hafix4
|
||||||
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"
|
webui: "[PROTO:ssl]://[HOST]:[PORT:80]"
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
|
||||||
|
|
||||||
# Baikal keeps its database in Specific and its configuration in config. The
|
# Copy data
|
||||||
# release ships both as empty folders, so they are created here rather than
|
cp -rnf /var/www/baikal/* /data/
|
||||||
# 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
|
# Fix permissions
|
||||||
chown -R nginx:nginx /data
|
chown -R nginx:nginx /data
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"github_beta": "false",
|
"github_exclude": "+",
|
||||||
"last_update": "2026-09-03",
|
"last_update": "26-04-2025",
|
||||||
"repository": "alexbelgium/hassio-addons",
|
"repository": "alexbelgium/hassio-addons",
|
||||||
"slug": "baikal",
|
"slug": "baikal",
|
||||||
"source": "github",
|
"source": "github",
|
||||||
"upstream_repo": "sabre-io/Baikal",
|
"upstream_repo": "ckulka/baikal-docker",
|
||||||
"upstream_version": "0.12.1"
|
"upstream_version": "0.10.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
## 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)
|
## 20260829.2 (29-08-2026)
|
||||||
- Minor bugs fixed
|
- Minor bugs fixed
|
||||||
## 20260829.1 (29-08-2026)
|
## 20260829.1 (29-08-2026)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ RUN apk add --no-cache curl && \
|
|||||||
# 1a Build environment (mirrors tphakala/birdnet-go Docker/Dockerfile) #
|
# 1a Build environment (mirrors tphakala/birdnet-go Docker/Dockerfile) #
|
||||||
#########################################################################
|
#########################################################################
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.27-trixie AS buildenv
|
FROM --platform=$BUILDPLATFORM golang:1.26-trixie AS buildenv
|
||||||
ARG BUILD_VERSION
|
ARG BUILD_VERSION
|
||||||
ENV BUILD_VERSION=${BUILD_VERSION:-unknown}
|
ENV BUILD_VERSION=${BUILD_VERSION:-unknown}
|
||||||
|
|
||||||
|
|||||||
@@ -127,5 +127,5 @@ slug: birdnet-go-dev
|
|||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
usb: true
|
usb: true
|
||||||
version: "20260901.4"
|
version: "20260829.2"
|
||||||
video: true
|
video: true
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
|
|
||||||
## 0.6.27.4 (2026-09-04)
|
|
||||||
- Fix: Kobo sync could not be enabled, failing with "Kepubify binary not found" even when the path was set by hand. The LinuxServer base image installs the converter as `/usr/bin/kepubify` with `curl -o`, which leaves it mode 0644 and gives it a name calibre-web does not accept : `binary_helper.py` only takes `kepubify-linux-64bit` or `kepubify-linux-32bit`, and only when `os.access(X_OK)` passes. The addon now makes the binary executable and publishes it as `/opt/kepubify/kepubify-linux-64bit`, the directory calibre-web's own autodetection already probes, so the path is filled in without any manual step (https://github.com/alexbelgium/hassio-addons/issues/3040)
|
|
||||||
- Fix: on installs created before that change, calibre-web had already run its autodetection once, found nothing usable and stored an empty path, and it never retries. An empty path is now reset so calibre-web detects the converter itself at the next start. A path set by hand is left alone
|
|
||||||
- Fix: `/usr/bin` also keeps working as a converter path, so the setting stored by anyone who applied the manual `ln -sf /usr/bin/kepubify /usr/bin/kepubify-linux-64bit` workaround keeps resolving after the update instead of silently breaking again
|
|
||||||
|
|
||||||
## 0.6.27.3 (2026-08-30)
|
## 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)
|
- 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
|
- 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
|
||||||
|
|||||||
@@ -45,23 +45,8 @@ ARG CONFIGLOCATION="/config"
|
|||||||
RUN chmod 744 /ha_lsio.sh && if grep -qr "lsio" /etc; then /ha_lsio.sh "$CONFIGLOCATION"; fi && rm /ha_lsio.sh
|
RUN chmod 744 /ha_lsio.sh && if grep -qr "lsio" /etc; then /ha_lsio.sh "$CONFIGLOCATION"; fi && rm /ha_lsio.sh
|
||||||
|
|
||||||
# Specific images modifications
|
# Specific images modifications
|
||||||
# The base image installs kepubify with "curl -o /usr/bin/kepubify", which both leaves it mode
|
|
||||||
# 0644 and gives it a name calibre-web does not accept : binary_helper.py only takes
|
|
||||||
# "kepubify-linux-64bit" or "kepubify-linux-32bit", and only when os.access(X_OK) passes. Both
|
|
||||||
# defects have to be fixed, which is why enabling Kobo sync failed with "Kepubify binary not
|
|
||||||
# found" even when the path was set by hand. Publish it under an accepted name in /opt/kepubify,
|
|
||||||
# the directory calibre-web's own autodetect_kepubify_binary() already probes, so calibre-web
|
|
||||||
# fills the setting in itself. The second link keeps /usr/bin working as well : that is the value
|
|
||||||
# already stored by anyone who applied the "ln -sf /usr/bin/kepubify /usr/bin/kepubify-linux-64bit"
|
|
||||||
# workaround, and their setting is not empty, so it is left alone below and has to keep resolving.
|
|
||||||
# Unguarded on purpose: if a future base image stops shipping the binary, the build must fail here
|
|
||||||
# rather than ship a silently broken add-on.
|
|
||||||
RUN \
|
RUN \
|
||||||
usermod --home /config abc \
|
usermod --home /config abc
|
||||||
&& chmod 0755 /usr/bin/kepubify \
|
|
||||||
&& mkdir -p /opt/kepubify \
|
|
||||||
&& ln -s /usr/bin/kepubify /opt/kepubify/kepubify-linux-64bit \
|
|
||||||
&& ln -s /usr/bin/kepubify /usr/bin/kepubify-linux-64bit
|
|
||||||
|
|
||||||
##################
|
##################
|
||||||
# 3 Install apps #
|
# 3 Install apps #
|
||||||
|
|||||||
@@ -116,5 +116,5 @@ schema:
|
|||||||
slug: calibre-web
|
slug: calibre-web
|
||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
|
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre_web
|
||||||
version: "0.6.27.4"
|
version: "0.6.27.3"
|
||||||
video: true
|
video: true
|
||||||
|
|||||||
@@ -32,14 +32,6 @@ else
|
|||||||
# before calibre-web, so a failure here is not fatal : the next start applies it.
|
# before calibre-web, so a failure here is not fatal : the next start applies it.
|
||||||
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) ||
|
trusted_ips_error=$(sqlite3 /config/app.db "update settings set config_reverse_proxy_trusted_ips='127.0.0.1,::1,::ffff:127.0.0.1,172.30.32.0/23,::ffff:172.30.32.0/119,'||coalesce(config_reverse_proxy_trusted_ips,'') where coalesce(config_reverse_proxy_trusted_ips,'') not like '%::ffff:172.30.32.0/119%'" 2>&1) ||
|
||||||
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
|
bashio::log.warning "Could not set the ingress trusted ip list, it will be applied at next start (${trusted_ips_error})"
|
||||||
|
|
||||||
# Calibre-web autodetects kepubify only while this setting is still NULL. On every install
|
|
||||||
# that predates the Dockerfile fix above, that detection already ran, found nothing usable and
|
|
||||||
# stored an empty string, so it is never retried. Put an empty value back to NULL and
|
|
||||||
# calibre-web detects /opt/kepubify itself when it starts, a few seconds after this runs.
|
|
||||||
# A path the user set by hand is not empty and is left alone.
|
|
||||||
kepubify_error=$(sqlite3 /config/app.db "update settings set config_kepubifypath = NULL where config_kepubifypath = ''" 2>&1) ||
|
|
||||||
bashio::log.warning "Could not reset the kepubify path, it will be applied at next start (${kepubify_error})"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
bashio::log.info "Default username:password is admin:admin123"
|
bashio::log.info "Default username:password is admin:admin123"
|
||||||
|
|||||||
@@ -1,18 +1,4 @@
|
|||||||
|
|
||||||
## 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)
|
## 1.5.3.2 (2026-08-30)
|
||||||
- Fix Download in the Home Assistant iOS companion app (iOS 17 and later),
|
- 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.
|
where a file opened and showed its content with no way to save it.
|
||||||
|
|||||||
@@ -118,4 +118,4 @@ schema:
|
|||||||
slug: filebrowser_quantum
|
slug: filebrowser_quantum
|
||||||
udev: true
|
udev: true
|
||||||
url: https://github.com/alexbelgium/hassio-addons
|
url: https://github.com/alexbelgium/hassio-addons
|
||||||
version: "1.5.3.3"
|
version: "1.5.3.2"
|
||||||
|
|||||||
@@ -41,72 +41,31 @@ server {
|
|||||||
# same instance's files/ or public/share/ route. Same shape as the komga
|
# same instance's files/ or public/share/ route. Same shape as the komga
|
||||||
# add-on's ingress filter.
|
# add-on's ingress filter.
|
||||||
#
|
#
|
||||||
# 2. Saving and opening a file. FileBrowser downloads by clicking an <a>
|
# 2. Download. FileBrowser downloads a file by building an <a> with no
|
||||||
# that carries no download attribute -- a hidden one it builds itself
|
# download attribute, clicking it, and letting the attachment response do
|
||||||
# (api/resources.js), and two visible ones in the "no preview available"
|
# the rest. The Home Assistant iOS companion app is a WKWebView, and a
|
||||||
# fallback (views/files/Preview.vue), which are also target="_blank".
|
# download happens there only when WebKit turns a navigation *action*
|
||||||
# Neither works in the Home Assistant companion app:
|
# 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.
|
||||||
#
|
#
|
||||||
# - A download happens in its WKWebView only when WebKit turns a
|
# Not covered: the public-share sidebar downloads with window.open()
|
||||||
# navigation *action* into a WKDownload, which is what the download
|
# rather than an anchor, and the app's download manager is gated on
|
||||||
# attribute does; the app hands that to its own download manager
|
# iOS 17 (WebViewController+WebKitDelegates.swift).
|
||||||
# (WebViewController+WebKitDelegates.swift, navigationAction:didBecome
|
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)}})();";
|
||||||
# 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)})();";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user