Compare commits

...

30 Commits

Author SHA1 Message Date
github-actions
2eb4f169af GitHub bot : README updated 2026-08-20 17:22:21 +00:00
github-actions
2247172ad1 GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild] 2026-08-20 13:38:07 +00:00
Alexandre
d0ef2fec48 feat(comicarr): new add-on for Comicarr with ingress support (#3001)
* feat(comicarr): new add-on with Home Assistant ingress

Comicarr is a fork of Mylar3 with a React frontend and a FastAPI backend.
The upstream image is a plain python:3.12-slim with no s6-overlay, so
ha_entrypoint.sh runs as pid 1 and supervises both the app and nginx —
the same shape the komga add-on uses.

Ingress needs a reverse proxy because the app has no url-base support of
any kind: vite emits absolute /assets urls, the api client and the cover
img tags build absolute /api and /cache urls, and SecurityHeadersMiddleware
sends X-Frame-Options: DENY together with a CSP carrying
frame-ancestors 'none', which alone would leave the panel blank. The
bundled nginx rewrites those paths onto the ingress entry, replaces the
two framing headers with the same policy narrowed to the Home Assistant
origin, scopes the session cookie to the ingress path and drops upstream's
one-year immutable caching for the rewritten assets.

The app is started directly as root by default rather than through the
upstream /entrypoint.sh, which runs useradd -u "$PUID" under set -e and
would exit on this repo's PUID=0 default; that entrypoint is still used
when the user asks for an unprivileged uid. --port 8090 is forced because
the port is writable from the Settings page and changing it there would
silently break both the proxy and the health check.

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

* docs(comicarr): note that switching PUID leaves existing files root-owned

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

* fix(comicarr): drop ingress_port, the add-on linter rejects the default

8099 is the Supervisor default, and frenck/action-addon-linter fails with
"'ingress_port' should be removed, it uses a default value". komga omits it
for the same reason; nginx still binds whatever bashio::addon.ingress_port
reports.

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

* fix(comicarr): 0755 on the entrypoint instead of 777

The rest of the repo uses 777 here, but this add-on is the one that offers a
non-root mode: with PUID set, the app runs as an unprivileged user that could
otherwise rewrite a file docker executes as root on the next start. Nothing
writes to /ha_entrypoint.sh at runtime, so 0755 costs nothing.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:37:26 +02:00
github-actions
8ddd7957f8 GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild] 2026-08-20 10:25:31 +00:00
Alexandre
d0ec6b2243 Modify 2026-08-20 12:24:37 +02:00
Alexandre
68257f926b Update updater.json 2026-08-20 12:16:41 +02:00
Alexandre
9960a04304 fix(claude_desktop): install the complete Codex package so tool calls work (#3000)
* fix(claude_desktop): install the complete Codex package, not just the executable

Since codex-cli 0.147.0 the CLI delegates every shell and file-read tool call to a
companion codex-code-mode-host binary that it looks up next to its own executable.
81-codex_cli.sh downloaded the codex-<target>.tar.gz release asset, which contains
only the codex executable, so that binary was never installed and every tool call
failed with "failed to spawn code-mode host ...: No such file or directory" while
the run still exited 0.

Download the codex-package-<target>.tar.gz asset instead — the complete package
tree upstream's own installer uses — and install all of it into the existing
/data/codex prefix, which already satisfies Codex's layout contract. Make the
"already installed" test require the code-mode host and the package manifest so
existing incomplete installs repair themselves, and report layout completeness in
claude-tools-doctor.sh.

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

* fix(claude_desktop): advertise Codex only when its package tree is complete

82-claude_tools.sh registers the Codex MCP server whenever the launcher at
/data/codex/bin/codex is executable and re-checks nothing else, while the launcher
and the package tree persist in /data independently of each other. Three paths
therefore reached that launcher next to an install that cannot run a tool call: a
boot that cannot reach the release metadata and keeps a pre-existing install missing
the code-mode host or the manifest, the same boot finding a stamp-less tree left by
an interrupted replacement, and a launcher surviving from an earlier boot after the
install was dropped. All three reproduced against the real script with stubbed
bashio/s6 and an unreachable metadata endpoint.

Define completeness once (executable, code-mode host, package manifest, version
stamp) and gate the launcher on it, removing the launcher and the /usr/local/bin
symlink when it does not hold. Nothing else is deleted, so a later boot completes the
install without another download or another login. The doctor's layout check now
includes the stamp for the same reason.

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

* fix(claude_desktop): install the Codex package tree by position, not by name

The whole reason for downloading the 118 MB codex-package asset instead of the
lone executable is that a helper Codex needs must not be left out — that is the
bug this branch fixes. install_codex_package still enumerated the five paths
release 0.148.0 happens to ship, so a helper added by a future release would be
downloaded, extracted and then discarded, failing exactly the way the missing
code-mode host does today. Verified against the extracted function: with a
staged tree carrying an unknown bin/ helper and an unknown top-level directory,
the previous code installed neither.

Move whatever the archive contains instead: every staged entry beside bin/ into
/data/codex, every staged bin/ entry except the entrypoint into /data/codex/bin,
then the entrypoint to codex-real last, so the ordering guarantee the stamp
relies on is unchanged. Only paths the archive actually contains are touched,
because /data/codex also holds this install's staging directory, and the
existing launcher is skipped by name while the version stamp is a dot file that
no glob matches. Removing each destination before moving onto it also drops
files an older release left behind.

Exercised with a scaffold around the extracted function: fresh install with
unknown helpers present, upgrade over an existing install with a stale helper
and a launcher to preserve, a minimal package with no optional directories, and
an unwritable prefix to confirm failure is reported rather than swallowed.

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

* docs(claude_desktop): scope the two deletion claims to what the code does

Both overstated. install_codex_package() replaces every path the new release
ships, but does not prune a path upstream stops shipping, so "files an older
release left behind are removed with it" was wrong for exactly that case; and
"nothing is deleted beyond the launcher" read as if the /usr/local/bin/codex
symlink named in the previous sentence survived, when it is removed with it.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:18:40 +02:00
Alexandre
4f8c0f2ed1 feat(kapowarr): new add-on with Home Assistant ingress support (#2999)
* feat(kapowarr): new add-on with Home Assistant ingress support

Kapowarr is a comic book library manager in the *arr family. The add-on is
built on the upstream image (mrcas/kapowarr), with the repository's standard
nginx ingress scaffolding on top.

Ingress uses the pattern komga and bazarr already use here: Kapowarr renders
absolute urls from its url base, Home Assistant strips its own ingress prefix
before forwarding, so Kapowarr is started with --UrlBase /kapowarr and nginx
rewrites that fixed prefix back onto the ingress entry.

Database and logs go to the add-on configuration directory. Temporary
downloads are symlinked there rather than passed with --TempDownloadFolder,
which upstream re-applies on its own restarts and would keep overwriting a
folder chosen in Settings > Download.

* fix(kapowarr): review fixes from the codex pass

- repair a /app/temp_downloads symlink pointing at the wrong target instead of
  accepting any symlink
- exclude logs and temporary downloads from Home Assistant backups: the temp
  folder now lives in the add-on config directory and can hold gigabytes
- fix the /dev/nvme2n3p3 typo inherited from the copied device list (the
  partition is nvme2n1p3); the same typo is present in the other add-ons
- document that the url base must not be changed, and that a non-zero PUID
  only reaches folders that user can already access
- drop three dead Dockerfile lines (BASHIO_VERSION is overridden inside
  ha_automatic_packages.sh, USER root is a no-op on this image)

* fix(kapowarr): pin host and port too, not just the url base

Found by a Codex review that could read the upstream source.

Kapowarr stores host, port and url base in its database and reads the stored
value whenever the matching flag is absent. Only --UrlBase was passed, so a
host or port changed in Settings > General survived every restart and upgrade
while nginx and the healthcheck stayed pointed at 127.0.0.1:5656 -- a permanent
502 with no way back except editing the database by hand.

All three flags are startup-only upstream, so passing them re-applies the
add-on's values once per container start without fighting the self-restarts
Kapowarr performs after a settings change.
2026-08-19 21:21:55 +02:00
Alexandre
2fc1ea84be fix(immich): URI-encode DB credentials for psql connection strings (#2980)
The addon builds every psql connection as a postgres:// URI with the raw
username and password interpolated in. libpq percent-decodes the userinfo
part of a URI, so a password containing '%' (or '@', '/', '?', '#') is
decoded into different bytes before it reaches the server, and every
connection fails with "password authentication failed for user".

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

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

Closes #1614

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

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

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

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

* docs(filebrowser_quantum): describe the legacy redirects accurately

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:15:20 +02:00
Alexandre
dac8efba60 fix(scrutiny_original): keep real /init as PID 1 so s6 supervision starts (#2991)
* fix(scrutiny_original): keep real /init as PID 1 so s6 supervision starts

collector-once's s6-svwait -u /run/service/scrutiny fails because
ha_entrypoint.sh (not the image's own /init) was PID 1 and never runs
real s6 supervision, so /run/service/* never gets created. Same root
cause and fix as scrutiny/scrutiny_fa (#2878): keep /init as PID 1 and
let ha_entrypoint.sh act only as the S6_STAGE2_HOOK.

Verified against the actual analogj/scrutiny:latest-omnibus image
(pulled via the GHCR registry API): its collector-once run script is
byte-identical to the one that caused #2877, and it bundles the same
s6-overlay-3.1.6.2.

Closes #2989

* fix(scrutiny_original): symlink /command into /usr/bin at build time

With /init as PID 1, ha_entrypoint.sh's own PID1 branch (which creates
this same symlink at runtime, and rewrites service run-file shebangs)
never runs. nginx/run and finish use #!/usr/bin/with-contenv bashio,
which only resolves if /usr/bin/with-contenv exists — so without this,
nginx (ingress) fails to start. scrutiny/Dockerfile and
scrutiny_fa/Dockerfile already do this in their "Install apps" stage;
this was missed when porting their PID-1 fix over.

Caught by chatgpt-codex-connector's PR review.
2026-08-18 21:36:49 +02:00
Alexandre
87f69ce79b fix(scrutiny_fa_original): keep real /init as PID 1 so s6 supervision starts (#2992)
Same bug and root cause as scrutiny_original (#2991) and scrutiny/scrutiny_fa
(#2878): collector-once's `s6-svwait -u /run/service/scrutiny` fails because
ha_entrypoint.sh (not the image's own /init) was PID 1 and never runs real s6
supervision, so /run/service/* never gets created.

scrutiny_fa_original shares the exact same rootfs (byte-identical cont-init.d
and services.d/nginx scripts) as scrutiny_original and builds from the same
ghcr.io/analogj/scrutiny:latest-omnibus image, so the same fix applies:
keep /init as PID 1, patch ha_entrypoint.sh to hand off to real s6-rc
supervision, and symlink /command into /usr/bin at build time (nginx/run and
finish use #!/usr/bin/with-contenv bashio, which only resolves once that
symlink exists — a P1 finding from scrutiny_original's PR review that
applies here identically).

Keeps the existing bashio::require.unprotected guard in
/etc/cont-init.d/90-run.sh unchanged.
2026-08-18 21:36:35 +02:00
Alexandre
84dfd1b996 fix(seerr): stop rewriting the root link inside the JS bundle (#2975) (#2986)
* fix(seerr): stop rewriting the root link inside the JS bundle (#2975)

3.4.1.1 appended a trailing slash to both the server-rendered "Discover"
link and its counterpart inside Seerr's JavaScript bundle. The slash is
correct in the HTML - Home Assistant routes ingress on
"/api/hassio_ingress/{token}/{path:.*}" and rejects a slash-less entry -
but it cannot survive in the bundle: next/link resolves a pushed href
through normalizePathTrailingSlash(), which drops a trailing slash while
`trailingSlash` is false, and Next.js then hard navigates to the
slash-less URL, recreating the same 404. On the root page it instead
throws "Invariant: attempted to hard navigate to the same URL" and the
click does nothing.

Dropping the bundle rewrite leaves the link as "/", which the client
router matches against its own "/" route and transitions to in-app -
the same path every other sidebar entry already takes, none of which are
rewritten here.

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

* fix(seerr): drop text/html from sub_filter_types

nginx pre-seeds text/html into sub_filter_types, so listing it emits
`[warn] duplicate MIME type "text/html"` on every config load. Verified
against nginx 1.22.1 locally: with the type dropped, `nginx -t` is
warning free and an HTML response is still filtered.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:29:01 +02:00
dependabot[bot]
2b6e07040f Bump anthropics/claude-code-action from 1.0.187 to 1.0.193 (#2984)
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.187 to 1.0.193.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](1623c36729...9d7150bc8a)

---
updated-dependencies:
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.193
  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-08-18 08:43:45 +02:00
dependabot[bot]
680386525f Bump EndBug/add-and-commit from 10 to 11 (#2985)
Bumps [EndBug/add-and-commit](https://github.com/endbug/add-and-commit) from 10 to 11.
- [Release notes](https://github.com/endbug/add-and-commit/releases)
- [Commits](https://github.com/endbug/add-and-commit/compare/v10...v11)

---
updated-dependencies:
- dependency-name: EndBug/add-and-commit
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-18 08:43:31 +02:00
github-actions
20a2f997c9 GitHub bot: changelog [nobuild] 2026-08-17 16:43:17 +00:00
Alexandre
15de5e3a39 fix(seerr): add trailing slash to ingress root-link rewrites (#2976)
Seerr's Discover link is href="/". nginx rewrote it to the bare ingress
entry, but Home Assistant only routes ingress on
"/api/hassio_ingress/{token}/{path:.*}", so a URL without the trailing
slash matches no route and Home Assistant answers its own plain-text
"404: Not Found" before the request reaches the add-on.

Closes #2975

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 18:35:14 +02:00
Pol Montanera
232e697301 Fix FileBrowser Quantum direct web access (#2979)
* Fix FileBrowser Quantum direct web access

* fix(filebrowser_quantum): make direct ip:port access actually work

ports: {8080/tcp: 8071} alone (as originally proposed) publishes the app's
own port, but FileBrowser Quantum's server.baseURL is set at boot to the
Supervisor ingress-entry path (an opaque, per-install hash), so the app only
serves correctly under that exact path -- a bare port publish gives an
unreachable page, per alexbelgium's own analysis on #2978.

Add a second, dedicated nginx vhost (direct.conf) that proxies a fixed public
path (/filebrowser_quantum/) onto the same ingress-entry baseURL the existing
ingress vhost already targets, instead of changing the app's baseURL itself.
This leaves the ingress vhost, and therefore Ingress access, completely
unchanged -- only the new vhost is new surface area. config.yaml now
publishes the new vhost's internal port (8072) to host 8071, not the app's
own 8080 directly.

Co-authored-by: polmonta <polmonta05@gmail.com>

---------

Co-authored-by: polmonta <polmonta05@gmail.com>
Co-authored-by: alexbelgium <alexandre.pary@gmail.com>
2026-08-17 18:32:48 +02:00
Alexandre
53ad396e5b fix(claude_desktop): sign-in persistence broke again — safeStorage patcher didn't handle bundles without a use-strict directive (#2983)
* fix(claude_desktop): patch safeStorage on bundles without a use-strict directive

The v1.37 safeStorage patcher only knew how to inject its plaintext-encryption
opt-in after a leading "use strict" directive in Claude Desktop's main bundle,
and refused to patch anything else. Confirmed live: Claude Desktop 1.30096.1's
main bundle no longer opens with that directive (bare IIFE instead), so the
patch has been silently refusing to run on every boot and sessions stopped
persisting across restarts again, with the same "Encryption not available"
warning documented in SIGN_IN.md before v1.37.

applyPatch() now falls back to prepending the opt-in as the bundle's first
statement when no directive is found, after skipping any leading BOM,
hashbang, or banner comment so a directive hidden behind a comment is still
protected rather than pushed out of position zero.

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

* fix(claude_desktop): scan the full directive prologue, not just line 0

Addresses two Codex review findings on PR #2983:

1. skipPrologue()'s //-comment scan only recognized "\n" as a line
   terminator. A comment ending in CR-only or U+2028/U+2029 (all valid
   ECMAScript LineTerminators) made it swallow the rest of the file as
   "still the comment", landing the patch after the bundle's last
   statement instead of before it. Reproduced with
   `// banner\r"use strict";(function(){})();`.

2. applyPatch() only checked whether the very first statement was
   literally "use strict". A directive prologue can hold more than one
   string-literal statement, and "use strict" only has to appear
   somewhere in it, not first; prepending ahead of an earlier directive
   pushed the whole prologue out of first-statement position and
   silently dropped strict mode. Reproduced with
   `"use custom";"use strict";(...)`.

Replaced the single-directive check with scanDirectivePrologue(), which
walks every leading string-literal-only statement and inserts right
after the full prologue (or at the same position when there is none).
skipPrologue/applyPatch split into skipBomAndHashbang +
skipWhitespaceAndComments + scanDirectivePrologue accordingly.

Verified: both findings reproduced against the pre-fix code and no
longer occur; 13-case regression suite covering the original edge cases
plus both findings all pass; re-run against the live production
app.asar still patches successfully and idempotently.

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

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 18:31:20 +02:00
Alexandre
bcdd972c2f Revert "docs(filebrowser_quantum): stop advertising direct access on port 807…" (#2982)
This reverts commit 65233d1291.
2026-08-17 07:57:24 +02:00
Alexandre
65233d1291 docs(filebrowser_quantum): stop advertising direct access on port 8071 (#2981)
config.yaml declares ingress_port: 8071 but no ports: key, so nothing is
published to the host network. ingress_port is the internal port the
Supervisor ingress proxy connects to on the add-on's private IP, and Home
Assistant only renders the Network card for add-ons that declare ports:.
Direct access at <your-ip>:8071 has therefore never worked; the README was
carried over from the sibling filebrowser add-on, which does declare
ports: 8080/tcp: 8071.

Correct the three README claims rather than publishing a port, since the
app is configured with server.baseURL set to the ingress entry and would
not serve correctly on a plain published port without further work.

Closes #2978

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:55:10 +02:00
github-actions
f63b4f3f92 Github bot : image compressed 2026-08-16 23:05:57 +00:00
github-actions[bot]
4b68ce7efd Update stargazer map & cache 2026-08-16 00:22:00 +00:00
Alexandre
9b9eab47bc fix(birdnet-pi): make ALSA_CARD actually select the microphone (#2972)
* fix(birdnet-pi): turn ALSA_CARD into a valid ALSA PCM name for REC_CARD

99-run.sh copied ALSA_CARD verbatim into REC_CARD, but BirdNET-Pi passes
REC_CARD to "arecord -D" (scripts/birdnet_recording.sh) and "ffmpeg -f alsa
-i" (scripts/livestream.sh), which expect an ALSA PCM name. A card index such
as ALSA_CARD=1 therefore produced "Unknown PCM 1" and no recording at all.

Build "plughw:CARD=<value>,DEV=0" from a card index or card id, and pass
through a value that already is a PCM name. Also use sed --follow-symlinks so
the rewrite no longer replaces the ~/BirdNET-Pi/birdnet.conf symlink with a
detached copy of /config/birdnet.conf.

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

* fix: address CodeRabbit review

* fix(birdnet-pi): bump version so the ALSA_CARD fix actually ships

The PR changed 99-run.sh and added a CHANGELOG entry but left config.yaml
untouched, so `version` still read 2026.08.02. Supervisor only offers a rebuild
when `version` changes: without this the fix would have merged, the add-on would
have kept running the old image, and the issue would have looked closed while
ALSA_CARD stayed broken.

2026.08.15 matches the CHANGELOG heading this PR already adds, which is this
add-on's convention — every past version lines up with a dated heading
(2026.08.02, 2026.07.22, ...). Not a `.N` counter bump: birdnet-pi's `version`
has drifted from updater.json's `upstream_version` (0.11), so the counter rule
does not apply and the add-on's own date scheme governs.

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

* fix(birdnet-pi-zach): turn ALSA_CARD into a valid ALSA PCM name for REC_CARD

birdnet-pi-zach/rootfs/etc/cont-init.d/99-run.sh carried a byte-identical
copy of the same bug fixed in birdnet-pi by this PR: REC_CARD was copied
verbatim from ALSA_CARD, but BirdNET-Pi passes REC_CARD to "arecord -D"
and "ffmpeg -f alsa -i", which expect an ALSA PCM name, not a card index.
sed -i also replaced the $HOME/BirdNET-Pi/birdnet.conf symlink with a
detached copy on first use.

Apply the same fix: build "plughw:CARD=<value>,DEV=0" from a card index
or card id, pass through a value that already is a PCM name, and use
sed --follow-symlinks against /config/birdnet.conf only. Documented in
README_standalone.md, same as birdnet-pi.

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

* fix: resolve ALSA_CARD against real PCM names, not a fixed allowlist

CodeRabbit and Codex both flagged that the passthrough check only recognized
default/null/pulse/pipewire: any other colon-free ALSA PCM name (sysdefault,
front, surround51, a custom .asoundrc alias, ...) was still misread as a card
index/id and rewritten as plughw:CARD=<name>,DEV=0, which then fails to open.

alsa-utils is already installed in both images, so check the value against
"arecord -L" (an exact, whole-line match against its unindented PCM-name
lines) instead of hardcoding the set of names ALSA ships with. Anything that
isn't a real PCM name still falls through to the plughw:CARD= build, so a
numeric index or a card id is handled exactly as before.

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

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:22:18 +02:00
Alexandre
948a2722f3 fix(ai): let the fix step own config.yaml, and require the patch-counter bump (#2973)
* fix(ai): let the fix step own config.yaml, and require the patch-counter bump

The premise that the fix step cannot touch config.yaml turned out to be wrong,
and the real problem was the opposite of what it looked like.

config.yaml was already in scope — issue-fix.md lists it among the files the
sweep reads and owns, and all three merged ai-fix PRs edited it. What they
edited, though, was the one thing hard limit 2 forbade outright:

  PR #2970  qbittorrent  version: "5.2.3.2" -> "5.2.3.3"
  PR #2912  bazarr       version: "1.6.0.1" -> "1.6.0.2"

Both bumped only the LOCAL PATCH COUNTER, leaving the upstream X.Y.Z alone —
i.e. exactly the right thing, in direct violation of the written rule. Nothing
enforces that rule (ai_guard_paths.sh only covers .github/ and .templates/), so
it has been quietly contradicted by practice, and it also contradicts CLAUDE.md's
own PR requirement to bump version.

It matters because Supervisor will not offer a rebuild without the bump: a fix
merged without one ships inert while the issue looks closed. That is the worst
outcome available — worse than not fixing it.

So the carve-out is narrowed to what addons_updater actually owns (the
`upstream` field and the upstream X.Y.Z), and bumping the trailing .N is now
required rather than forbidden, with the dot-not-hyphen trap called out
(X.Y.Z-N reads as a semver pre-release and Supervisor treats it as older).
Exotic version shapes — LSIO tags, dates, nightlies — are explicitly left alone
rather than guessed at.

Applied to all four places the rule is stated so they cannot drift:
issue-fix.md, issue-execute-plan.md, CLAUDE.md, and pr-coderabbit.md — the last
keeps the restriction, since it amends a PR whose single bump already covers it,
but now says why instead of reading as a contradiction.

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

* fix(ai): derive the patch counter from updater.json, not from version's shape

Six review findings, all reproduced against the repo before accepting.

Codex (P1) — the rule "increment the trailing .N" is wrong for most of this
repo, because you cannot tell a local counter from an upstream component by
looking at `version`. Checked all 134 add-ons:

  version == upstream_version (no counter, must APPEND .1):  82
  version == upstream_version + .N (counter, INCREMENT):      8
  version drifted from upstream (LEAVE ALONE):               36
  no usable updater.json (LEAVE ALONE):                       8

So the previous wording would have mutated updater-owned data on 82 add-ons:
sonarr's 4.0.19.3001 IS the upstream version, and incrementing it to
4.0.19.3002 burns the identifier of a future real release; linkwarden's 2.16.0
would have become 2.16.1, indistinguishable from an upstream minor bump.

updater.json's upstream_version is now the authority: append .1 when version
equals it, increment only the digits that follow it, otherwise leave version
alone. Validated by running the rule as written over every add-on — 0
violations of the invariant that a bumped version must still start with
upstream_version.

Copilot — there is no `upstream:` key in any config.yaml (0 of 134); upstream
tracking lives in updater.json as upstream_repo / upstream_version. That was
inherited text naming a field that does not exist, in all four places. Replaced
with the real constraint: never edit updater.json.

Copilot — the "a workflow step enforces them" headers over-claimed. Only limit
1 is machine-enforced (ai_guard_paths.sh); the rest ship silently if broken,
which is worth saying plainly given limit 2 has been quietly contradicted by
practice for months.

Copilot — Outcome B produces a plan and no PR, so "say so in the pull request
body" had no place to land. Now covers both.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:14:29 +02:00
225 changed files with 2278 additions and 125 deletions

View File

@@ -11,10 +11,21 @@ Read:
diff, verification, risk). This is your spec.
- `/tmp/ai-exec/issue.json` — the issue it fixes (for `Closes #<n>` and context).
## Hard limits (identical to the fix sweep — a workflow step enforces them)
## Hard limits (identical to the fix sweep; only limit 1 is machine-enforced)
1. **Never modify `.github/` or `.templates/`.** Repo-wide infrastructure.
2. **Never touch the `version` or `upstream` fields in `config.yaml`.**
2. **`config.yaml` is yours to edit, except the upstream part of `version`;
never edit `updater.json`** — `addons_updater` owns both. (There is no
`upstream:` key in `config.yaml`.) You must still
bump the local patch counter, or Supervisor never offers the rebuild and the
fix ships inert. Read `updater.json` to find the boundary — you cannot tell
it from `version` alone, since upstream versions here run to four or five
components. With `U` = `upstream_version`: if `version` equals `U`, **append**
`.1` (sonarr `4.0.19.3001` -> `4.0.19.3001.1`); if it is `U` + `.` + digits,
**increment** those digits (radarr `6.3.0.10514.1` -> `6.3.0.10514.2`);
anything else — no `updater.json`, drifted version, LSIO tag, date, nightly —
leave `version` alone and say so in the pull request body. A dot, never a
hyphen.
3. **One add-on, one branch:** `ai-fix/<addon>-<issue-number>`.
4. **Never merge, never close the issue, never enable auto-merge.** Open the
pull request **ready for review** — CI (`onpr_check-pr.yaml`) validates it,

View File

@@ -13,14 +13,52 @@ about confidence matters more than the number of pull requests you open.
## Hard limits
These are not guidelines. A workflow step enforces them after you finish, and
anything that violates them gets blocked and flagged.
These are not guidelines. Limit 1 is machine-enforced — a workflow step checks
every pull request you open and blocks and flags anything that violates it. The
rest are on you: nothing checks them, so breaking one ships silently.
1. **Never modify `.github/` or `.templates/`.** Those are inherited by every
add-on in the repo. A change there is a 100-add-on incident, not a fix.
2. **Never touch the `version` or `upstream` fields in `config.yaml`.** The
`addons_updater` job owns those. Editing them causes merge conflicts you
will not be around to resolve.
2. **`config.yaml` is yours to edit, with one carve-out.** Never change the
*upstream part* of `version` — the portion that tracks the upstream release
— and never edit `updater.json` at all. The `addons_updater` job owns both,
and editing them causes merge conflicts you will not be around to resolve.
(There is no `upstream:` key in `config.yaml`; upstream tracking lives in
`updater.json` as `upstream_repo` / `upstream_version`.)
The **local patch counter** is a different thing and you must bump it. When
you change any file in an add-on, `version` must change too — otherwise
Supervisor never offers the rebuild, the add-on keeps running the old image,
and your fix ships inert: merged, doing nothing, with the issue looking
closed. That is worse than not fixing it at all.
**You cannot tell the counter from `version` alone — read `updater.json`.**
Upstream versions in this repo have anywhere from one to five components, so
a trailing `.1234` is just as likely to belong to upstream as to be a local
counter. `updater.json`'s `upstream_version` is the authority. Let `U` be
that value, and compare:
| `version` vs `U` | what to do | example |
|---|---|---|
| identical | **append** `.1` | sonarr `4.0.19.3001` -> `4.0.19.3001.1` |
| `U` + `.` + digits | **increment** those digits | radarr `6.3.0.10514.1` -> `6.3.0.10514.2` |
| anything else | **leave it alone** | plex, readarr, joal |
Getting this backwards corrupts data you do not own: sonarr's `4.0.19.3001`
*is* the upstream version, so "increment the last component" would produce
`4.0.19.3002` and burn the identifier of a future real release. 82 of the
add-ons in this repo are in that first row — appending is the common case,
incrementing the rare one.
Use a dot, never a hyphen: `X.Y.Z-N` parses as a semver pre-release, which
Supervisor treats as *older* than `X.Y.Z` and will not offer.
The third row is not a failure — it is the safe answer whenever the add-on
has no `updater.json`, its `version` has drifted from `upstream_version`, or
the format is exotic (LSIO tag `1.43.1.10611-1e34174b1-ls301`, a date, a
nightly). Do not guess a counter onto those. Leave `version` untouched and
say so — in the pull request body, or in the plan if this issue is going out
as Outcome B — so a human can decide.
3. **One add-on per branch, one branch per pull request.** Branch name
`ai-fix/<addon>-<issue-number>`.
4. **Never merge, never close an issue, never enable auto-merge.** Opening a

View File

@@ -23,7 +23,10 @@ Consider only comments authored by `coderabbitai[bot]`. Ignore its collapsed
## Hard limits (a workflow step enforces the first)
1. **Never modify `.github/` or `.templates/`.** Repo-wide infrastructure.
2. **Never touch the `version` or `upstream` fields in `config.yaml`.**
2. **Leave `version` in `config.yaml` alone, and never edit `updater.json`.** The rest of
`config.yaml` is fair game. Unlike the fix sweep, you are amending a pull
request that has *already* bumped the local patch counter — one bump covers
the whole PR, so incrementing it again here would just churn the diff.
3. **Stay within this PR's scope and branch.** Do not open a new PR, do not
touch other add-ons, do not merge, do not mark ready/draft.

View File

@@ -442,6 +442,7 @@ Lolekpolek,,2026-08-10
LonelySoul7X,,2026-08-10
Lorsel,Italy,
Luca2165801154,,2026-08-10
Lucius-Waverly,,2026-08-16
LuciusEternal,,2026-08-10
LuckyTriple7,Germany,
Luckyfuzz,,2026-08-10
@@ -539,6 +540,7 @@ NoFace3D,,2026-08-10
Noa3129,,2026-08-10
Noob25930,,2026-08-10
NuclearTruck,,2026-08-10
OXERY,,2026-08-16
Oberknecht,Germany,
OdorDecoder,,2026-08-10
Omega7572,,
@@ -1083,6 +1085,7 @@ cecet23,Italy,
cecilchurms,,2026-08-10
celynw,United Kingdom,
ch4d1,Germany,
chapliak,Canada,2026-08-16
charithmadhuranga,Sri Lanka,
charl3y15,United States,
charlestephen,United States,
@@ -1112,6 +1115,7 @@ cicishahita,,2026-08-10
cipector,,2026-08-10
circasurvivor55,,2026-08-10
cirf,,2026-08-10
citizenserious,,2026-08-16
cityeyes,,2026-08-10
classhu,,2026-08-10
clementleroy,France,
@@ -1744,6 +1748,7 @@ lakshanthad,Canada,
lallinger-tech,Germany,
laoto1234,,2026-08-10
larry3,,2026-08-10
lassepi,,2026-08-16
laughedelic,Norway,
lazymule,Türkiye,2026-08-10
lazytarget,Sweden,
@@ -1760,6 +1765,7 @@ letrouf,,2026-08-10
levvvy,,2026-08-10
lexluthors,,2026-08-10
lguerard,,2026-08-10
lheinke,,2026-08-16
lhimo,,2026-08-10
liangshao200,,2026-08-10
lichenophile,,2026-08-10
@@ -1842,6 +1848,7 @@ mareklab,,2026-08-10
marevers,Germany,
marian-paun,Romania,
mariusvslprts,Germany,
mark-219,United States,2026-08-16
markcshaz,,2026-08-10
markist,,2026-08-10
marlonqpa,,2026-08-10
@@ -1885,6 +1892,7 @@ mediaexplorer74,Russian Federation,2026-08-10
mefisto22,,2026-08-10
megavolts,United States,
megawubs,Netherlands,
meremortals70,,2026-08-16
meska,,2026-08-10
methbkts,France,
mevlutdemirbas,,2026-08-10
@@ -1940,6 +1948,7 @@ mmstano,,2026-08-10
mmz06,,2026-08-10
mnex9191,,2026-08-10
mnpeart,United States,
mocodev-io,,2026-08-16
moein805,"Iran, Islamic Republic of",
mohammedmulazada,Netherlands,
mohammednafeel,,2026-08-10
@@ -1979,6 +1988,7 @@ natbrood,,2026-08-10
nate-marshall,,2026-08-10
nduest,Germany,
ne7runner,,2026-08-10
nean,,2026-08-16
nedsined,,2026-08-10
neildotwilliams,,2026-08-10
neilpercy,,2026-08-10
@@ -2167,6 +2177,7 @@ qdominik,,2026-08-10
qezzo,,2026-08-10
qianc123,,2026-08-10
qimingzihaofan233,,2026-08-10
qtoosha,,2026-08-16
quank1968,,2026-08-10
quartzbear,,2026-08-10
quirbiefe,,2026-08-10
@@ -2369,6 +2380,7 @@ stDragon,,2026-08-10
stagietek,Australia,
stanisboiko,Ukraine,
starbuck93,United States,
statuscue,Germany,2026-08-16
steef84,,2026-08-10
stef-th,,2026-08-10
stefangries,,2026-08-10
@@ -2455,6 +2467,7 @@ threnard,,2026-08-10
tianmaozuo,,2026-08-10
tibonou,,2026-08-10
tigroff,,2026-08-10
tijmenvanstraten,,2026-08-16
tillmannschatz,Germany,
tim-frensch,,2026-08-10
timTam97,Australia,
1 username country last_checked
442 LonelySoul7X 2026-08-10
443 Lorsel Italy
444 Luca2165801154 2026-08-10
445 Lucius-Waverly 2026-08-16
446 LuciusEternal 2026-08-10
447 LuckyTriple7 Germany
448 Luckyfuzz 2026-08-10
540 Noa3129 2026-08-10
541 Noob25930 2026-08-10
542 NuclearTruck 2026-08-10
543 OXERY 2026-08-16
544 Oberknecht Germany
545 OdorDecoder 2026-08-10
546 Omega7572
1085 cecilchurms 2026-08-10
1086 celynw United Kingdom
1087 ch4d1 Germany
1088 chapliak Canada 2026-08-16
1089 charithmadhuranga Sri Lanka
1090 charl3y15 United States
1091 charlestephen United States
1115 cipector 2026-08-10
1116 circasurvivor55 2026-08-10
1117 cirf 2026-08-10
1118 citizenserious 2026-08-16
1119 cityeyes 2026-08-10
1120 classhu 2026-08-10
1121 clementleroy France
1748 lallinger-tech Germany
1749 laoto1234 2026-08-10
1750 larry3 2026-08-10
1751 lassepi 2026-08-16
1752 laughedelic Norway
1753 lazymule Türkiye 2026-08-10
1754 lazytarget Sweden
1765 levvvy 2026-08-10
1766 lexluthors 2026-08-10
1767 lguerard 2026-08-10
1768 lheinke 2026-08-16
1769 lhimo 2026-08-10
1770 liangshao200 2026-08-10
1771 lichenophile 2026-08-10
1848 marevers Germany
1849 marian-paun Romania
1850 mariusvslprts Germany
1851 mark-219 United States 2026-08-16
1852 markcshaz 2026-08-10
1853 markist 2026-08-10
1854 marlonqpa 2026-08-10
1892 mefisto22 2026-08-10
1893 megavolts United States
1894 megawubs Netherlands
1895 meremortals70 2026-08-16
1896 meska 2026-08-10
1897 methbkts France
1898 mevlutdemirbas 2026-08-10
1948 mmz06 2026-08-10
1949 mnex9191 2026-08-10
1950 mnpeart United States
1951 mocodev-io 2026-08-16
1952 moein805 Iran, Islamic Republic of
1953 mohammedmulazada Netherlands
1954 mohammednafeel 2026-08-10
1988 nate-marshall 2026-08-10
1989 nduest Germany
1990 ne7runner 2026-08-10
1991 nean 2026-08-16
1992 nedsined 2026-08-10
1993 neildotwilliams 2026-08-10
1994 neilpercy 2026-08-10
2177 qezzo 2026-08-10
2178 qianc123 2026-08-10
2179 qimingzihaofan233 2026-08-10
2180 qtoosha 2026-08-16
2181 quank1968 2026-08-10
2182 quartzbear 2026-08-10
2183 quirbiefe 2026-08-10
2380 stagietek Australia
2381 stanisboiko Ukraine
2382 starbuck93 United States
2383 statuscue Germany 2026-08-16
2384 steef84 2026-08-10
2385 stef-th 2026-08-10
2386 stefangries 2026-08-10
2467 tianmaozuo 2026-08-10
2468 tibonou 2026-08-10
2469 tigroff 2026-08-10
2470 tijmenvanstraten 2026-08-16
2471 tillmannschatz Germany
2472 tim-frensch 2026-08-10
2473 timTam97 Australia

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 68 KiB

BIN
.github/stats.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

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

View File

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

View File

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

View File

@@ -64,7 +64,7 @@ jobs:
fetch-depth: 1
- name: Run Claude Code
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # 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@1623c36729ac1cd5895198cded705a287de7db79 # v1
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

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

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@1623c36729ac1cd5895198cded705a287de7db79 # v1
uses: anthropics/claude-code-action@9d7150bc8a3dae8149739a88019d192b579ad90c # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -161,7 +161,17 @@ the sweep), `ai:plan-pending` (plan posted, awaiting `ai:approved`), `ai:fixed`,
out of the automated tiers but not the manual ones. **Kill switch:** set the
repo variable `AI_DISABLED=true` to pause every AI workflow with no file edits.
AI fixes must never touch `.github/` or `.templates/` (enforced by
`ai_guard_paths.sh`) or the `version`/`upstream` fields in `config.yaml`.
`ai_guard_paths.sh`). They may edit `config.yaml` freely except the upstream
part of `version`, and must never edit `updater.json` — `addons_updater` owns
both. (There is no `upstream:` key in `config.yaml`; upstream tracking lives in
`updater.json`.) They must still bump the local patch counter so Supervisor
offers the rebuild —
without it the fix ships inert. The counter boundary comes from
`updater.json`'s `upstream_version`, never from the shape of `version`: append
`.1` when the two are equal (the common case — upstream versions here run to
four or five components), increment the trailing digits only when `version` is
`upstream_version` + `.N`, and otherwise leave `version` alone. This rule is
prompt-only, not machine-enforced.
## Linting Rules

View File

@@ -56,7 +56,7 @@ If you want to do add the repository manually, please follow the procedure highl
### Number of addons
- In the repository : 141
- In the repository : 143
- Installed : 361103
### Top 3
@@ -309,6 +309,16 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-open-page-variant.svg) [Comicarr](comicarr/) : Automated comic book and manga downloader and library manager with a modern React UI
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomicarr%2Fconfig.yaml)
![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomicarr%2Fupdater.json)
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![ingress][ingress-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-open.svg) [Comixed](comixed/) : managing digital comics
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fcomixed%2Fconfig.yaml)
@@ -560,6 +570,16 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
&#10003; ![image](https://api.iconify.design/mdi/book-multiple.svg) [Kapowarr](zzz_archived_kapowarr/) : Comic book library manager, fitting in the *arr suite of software
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fzzz_archived_kapowarr%2Fconfig.yaml)
![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fzzz_archived_kapowarr%2Fupdater.json)
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![ingress][ingress-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
&#10003; [Kometa](kometa/) : Python script to update metadata information for movies, shows, and collections as well as automatically build collections
&emsp;&emsp;![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2Fkometa%2Fconfig.yaml)

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.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 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.7 KiB

After

Width:  |  Height:  |  Size: 1.7 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: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 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: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,8 @@
## 2026.08.15 (15-08-2026)
- Fix: `ALSA_CARD` now really selects the microphone. Its value was copied as-is into `REC_CARD`, but BirdNET-Pi hands `REC_CARD` to `arecord -D` / `ffmpeg -f alsa -i`, which expect an ALSA PCM name: a card index such as `1` gave `Unknown PCM 1` and no recording at all. It is now converted to `plughw:CARD=<value>,DEV=0`, while a value that already is a PCM name (`dsnoop:CARD=Audio,DEV=0`, `default`, `null`, `pulse`, `pipewire`, ...) is used as provided
- Fix: setting `ALSA_CARD` no longer replaces the `~/BirdNET-Pi/birdnet.conf` symlink with a detached copy of `/config/birdnet.conf` (now only `/config/birdnet.conf`, which the symlink points to, is updated)
## 2026.07.10-2 (10-07-2026)
- Minor bugs fixed
## 2026.07.10 (10-07-2026)

View File

@@ -101,6 +101,17 @@ Ensure you have the following installed on your system:
If rtsp feed doesn't work, perhaps you need to add "-rtsp-transport tcp" to your ffmpeg instruction, or allow udp on your network
### Selecting the microphone
By default the container records through PulseAudio (`REC_CARD=default` in `birdnet.conf`). To record directly from a USB microphone instead, find it on the host with `arecord -l`, then pass its card number (or its card id) as `ALSA_CARD`:
```yaml
environment:
- ALSA_CARD=1 # "card 1: Audio [KT USB Audio]" in the output of "arecord -l"
```
At startup this writes `REC_CARD=plughw:CARD=1,DEV=0` into your `birdnet.conf`. A value that already is a full ALSA PCM name, as listed by `arecord -L`, is used as provided - for example `ALSA_CARD=dsnoop:CARD=Audio,DEV=0`, which allows the recording and the livestream services to read the same microphone at the same time.
## Updating to the Latest Version
To check for new versions of the container and update:

View File

@@ -116,5 +116,5 @@ tmpfs: true
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-pi-zach
usb: true
version: 2026.07.10.2
version: 2026.08.15
video: true

View File

@@ -66,12 +66,26 @@ fi || true
# Use ALSA CARD defined in add-on options if available
if [ -n "${ALSA_CARD:-}" ]; then
bashio::log.warning "ALSA_CARD is defined, the birdnet.conf is adapt to use device $ALSA_CARD"
for file in "$HOME"/BirdNET-Pi/birdnet.conf /config/birdnet.conf; do
if [ -f "$file" ]; then
sed -i "/^REC_CARD/c\REC_CARD=$ALSA_CARD" "$file"
fi
done
# REC_CARD is passed as-is to "arecord -D" (scripts/birdnet_recording.sh) and to
# "ffmpeg -f alsa -i" (scripts/livestream.sh), so it must be an ALSA PCM name.
# ALSA_CARD holds a card index (1) or a card id (Audio), which are not PCM names:
# writing them as-is gives "Unknown PCM 1" and no recording at all. Build a PCM
# name from them, unless the value already is one of ALSA's own PCM names
# (checked against "arecord -L", e.g. default, null, pulse, sysdefault, front...).
if [[ "$ALSA_CARD" == *:* ]] || arecord -L 2> /dev/null | grep -qx "$ALSA_CARD"; then
REC_CARD="$ALSA_CARD"
else
REC_CARD="plughw:CARD=${ALSA_CARD},DEV=0"
fi
bashio::log.warning "ALSA_CARD is defined, the birdnet.conf is adapted to use device $REC_CARD"
# --follow-symlinks : $HOME/BirdNET-Pi/birdnet.conf is a symlink to /config/birdnet.conf
# (01-structure.sh), and sed -i would replace it with a regular file, detaching it from
# the file the WebUI writes to. Only /config/birdnet.conf is updated directly, since the
# home-path symlink is writable by the pi/caddy user and could be repointed before this
# root-run script gets to it.
if [ -f /config/birdnet.conf ]; then
sed -i --follow-symlinks "/^REC_CARD/c\REC_CARD=$REC_CARD" /config/birdnet.conf
fi
fi
# Define permissions for audio

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,3 +1,7 @@
## 2026.08.15 (15-08-2026)
- Fix: `ALSA_CARD` now really selects the microphone. Its value was copied as-is into `REC_CARD`, but BirdNET-Pi hands `REC_CARD` to `arecord -D` / `ffmpeg -f alsa -i`, which expect an ALSA PCM name: a card index such as `1` gave `Unknown PCM 1` and no recording at all. It is now converted to `plughw:CARD=<value>,DEV=0`, while a value that already is a PCM name (`dsnoop:CARD=Audio,DEV=0`, `default`, `null`, `pulse`, `pipewire`, ...) is used as provided
- Fix: setting `ALSA_CARD` no longer replaces the `~/BirdNET-Pi/birdnet.conf` symlink with a detached copy of `/config/birdnet.conf` (now only `/config/birdnet.conf`, which the symlink points to, is updated)
## 2026.08.02 (02-08-2026)
- Fix: ingress returned "502 Bad Gateway" because Caddy never listened on :8082. `91-nginx_ingress.sh` hooked the ingress site into `update_caddyfile.sh` with a sed anchored on `sudo caddy fmt --overwrite`, but 2026.07.10-1 strips `sudo` from every BirdNET-Pi script at build time, so the anchor stopped matching. `update_caddyfile.sh` then rewrote the Caddyfile from scratch just before Caddy started, dropping the ingress site
- Fix: `caddy_ingress.sh` no longer appends a second `:8082` block when it runs twice (a duplicate site address makes Caddy refuse to start)

View File

@@ -101,6 +101,17 @@ Ensure you have the following installed on your system:
If rtsp feed doesn't work, perhaps you need to add "-rtsp-transport tcp" to your ffmpeg instruction, or allow udp on your network
### Selecting the microphone
By default the container records through PulseAudio (`REC_CARD=default` in `birdnet.conf`). To record directly from a USB microphone instead, find it on the host with `arecord -l`, then pass its card number (or its card id) as `ALSA_CARD`:
```yaml
environment:
- ALSA_CARD=1 # "card 1: Audio [KT USB Audio]" in the output of "arecord -l"
```
At startup this writes `REC_CARD=plughw:CARD=1,DEV=0` into your `birdnet.conf`. A value that already is a full ALSA PCM name, as listed by `arecord -L`, is used as provided - for example `ALSA_CARD=dsnoop:CARD=Audio,DEV=0`, which allows the recording and the livestream services to read the same microphone at the same time.
## Updating to the Latest Version
To check for new versions of the container and update:

View File

@@ -116,5 +116,5 @@ tmpfs: true
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-pi
usb: true
version: 2026.08.02
version: 2026.08.15
video: true

View File

@@ -66,12 +66,26 @@ fi || true
# Use ALSA CARD defined in add-on options if available
if [ -n "${ALSA_CARD:-}" ]; then
bashio::log.warning "ALSA_CARD is defined, the birdnet.conf is adapt to use device $ALSA_CARD"
for file in "$HOME"/BirdNET-Pi/birdnet.conf /config/birdnet.conf; do
if [ -f "$file" ]; then
sed -i "/^REC_CARD/c\REC_CARD=$ALSA_CARD" "$file"
fi
done
# REC_CARD is passed as-is to "arecord -D" (scripts/birdnet_recording.sh) and to
# "ffmpeg -f alsa -i" (scripts/livestream.sh), so it must be an ALSA PCM name.
# ALSA_CARD holds a card index (1) or a card id (Audio), which are not PCM names:
# writing them as-is gives "Unknown PCM 1" and no recording at all. Build a PCM
# name from them, unless the value already is one of ALSA's own PCM names
# (checked against "arecord -L", e.g. default, null, pulse, sysdefault, front...).
if [[ "$ALSA_CARD" == *:* ]] || arecord -L 2> /dev/null | grep -qx "$ALSA_CARD"; then
REC_CARD="$ALSA_CARD"
else
REC_CARD="plughw:CARD=${ALSA_CARD},DEV=0"
fi
bashio::log.warning "ALSA_CARD is defined, the birdnet.conf is adapted to use device $REC_CARD"
# --follow-symlinks : $HOME/BirdNET-Pi/birdnet.conf is a symlink to /config/birdnet.conf
# (01-structure.sh), and sed -i would replace it with a regular file, detaching it from
# the file the WebUI writes to. Only /config/birdnet.conf is updated directly, since the
# home-path symlink is writable by the pi/caddy user and could be repointed before this
# root-run script gets to it.
if [ -f /config/birdnet.conf ]; then
sed -i --follow-symlinks "/^REC_CARD/c\REC_CARD=$REC_CARD" /config/birdnet.conf
fi
fi
# Define permissions for audio

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: 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: 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.3 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.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,4 +1,84 @@
## 07308545.3 (19-08-2026)
- Fix an incompletely installed Codex CLI, which silently broke every Codex tool call. Since
codex-cli 0.147.0 the CLI does not execute shell commands or file reads itself; it delegates
them to a companion `codex-code-mode-host` binary that it looks up next to its own executable.
`81-codex_cli.sh` downloaded the `codex-<target>.tar.gz` release asset, which contains only the
`codex` executable, so that companion binary was never installed. Measured on the running add-on
(codex-cli 0.147.0, `/data/codex/bin` holding only `.version`, the launcher and `codex-real`):
`codex exec` starts, authenticates and answers, but every tool call fails with
`failed to spawn code-mode host /data/codex/bin/codex-code-mode-host: No such file or directory`
and the run still exits 0 — so Codex answered from the prompt text alone and the failure looked
like success. `--disable code_mode` does not avoid it.
- The installer now downloads the `codex-package-<target>.tar.gz` release asset, which is the
complete package tree upstream's own installer uses, and installs all of it. Not a list of
known file names: whatever the archive contains is moved into place by position, so a helper
added by a future release arrives beside the entrypoint on its own instead of being extracted
and then dropped — cherry-picking today's two binaries works today, but it is the same mistake
at a smaller scale. For release 0.148.0 that means the entrypoint as
`/data/codex/bin/codex-real`, `codex-code-mode-host` beside it, and `codex-package.json`,
`codex-resources/` (bundled bubblewrap and zsh) and `codex-path/` (bundled ripgrep) in
`/data/codex`. The installed tree grows from ~246 MB to ~300 MB, and `/data/codex` is now
explicitly add-on-owned in its entirety: every path the new release ships replaces the
installed copy of that path outright rather than merging into it, so nothing should be kept
there by hand. A path upstream stops shipping altogether is not pruned — it is left behind as
dead weight that the new entrypoint no longer looks for. Codex's own state stays in
`~/.codex` and is never touched.
- An incomplete install is no longer advertised. `82-claude_tools.sh` registers the Codex MCP
server whenever the launcher at `/data/codex/bin/codex` is executable and re-checks nothing
else, and both the launcher and the package tree persist in `/data` independently of each
other. The launcher is therefore now written only for an install that has its executable, its
code-mode host, its package manifest and its version stamp, and is removed together with the
`/usr/local/bin/codex` symlink otherwise. The stamp is part of that test because it is deleted
before the first file of a replacement is moved and written after the last, so a stamp-less
prefix is exactly the tree that may mix two releases. This covers the cases that reach the
launcher without a fresh install: a boot that cannot reach the release metadata and finds a
pre-existing incomplete install, and a launcher left behind by an interrupted replacement.
Nothing under `/data/codex` is deleted beyond that launcher — the executable, the package
tree and the ChatGPT sign-in stay, so a later boot completes the install without another
download or another login.
- That layout is load-bearing, so the install prefix was chosen to satisfy it rather than
changed. Codex canonicalises its own executable path, requires the parent directory to be
named `bin`, and reads the manifest and helper directories from that directory's parent — the
existing `/data/codex/bin` prefix already matches, and the executable's file name is not part
of the contract, so `codex-real` and the subscription-only `codex` launcher wrapping it are
both unchanged, as is the `/usr/local/bin/codex` symlink and the MCP registration.
- Existing installs repair themselves. The "already installed, skip the download" test now also
requires the code-mode host and the package manifest to be present, so an add-on that already
has a working `codex-real` and no helpers reinstalls on the next start instead of staying
quietly broken.
- `claude-tools-doctor.sh` now reports whether the package layout is complete, because the
failure mode this fixes is invisible in `codex --version`, in the version stamp and in the
exit code.
- Known limitation, unchanged by this release and not caused by it: Codex's own Linux sandbox
cannot start in this container. Running both the system `bwrap` 0.8.0 and the bundled one
directly with `--dev-bind / / --unshare-net /bin/true` fails identically with
`bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted`, so it is a container capability
limitation rather than a packaging one. With the shipped `codex_sandbox_mode: workspace-write`
default, tool calls therefore still fail with that bwrap error; `danger-full-access` is the only
mode that currently executes commands, and the container is already the security boundary.
## 07308545.1 (17-08-2026)
- Minor bugs fixed
## 07308543.1 (17-08-2026)
- Fix the "For your security, sign in again" prompt recurring on every restart again. The v1.37
`safeStorage` patch (`86-claude_safestorage.sh` / `claude-safestorage-patch.js`) only knew how
to inject its opt-in after a leading `"use strict"` directive in the app's main bundle, and
refused to patch anything else. Confirmed live on the running add-on (Claude Desktop
1.30096.1): the shipped main bundle (`.vite/build/index.pre.js`) no longer opens with a
`"use strict"` directive at all — it now opens directly with a bare IIFE — so the patcher has
been silently refusing to patch on every boot, `safeStorage.isEncryptionAvailable()` stayed
`false`, and the app's own log kept showing `Encryption not available, returning empty env
vars` exactly as before v1.37. `applyPatch()` now falls back to inserting the opt-in as the
bundle's first real statement when no directive is present, skipping past any leading BOM,
hashbang, or banner comment first so a directive hidden behind a comment is still found and
protected rather than pushed out of position zero. Verified by copying the live production
`app.asar` and running the patcher against it directly: the previously-refused bundle now
patches successfully, the marker lands correctly, a second run reports "Already patched", and
targeted unit tests cover the bare-IIFE, comment-hidden-directive, hashbang, and
unterminated-comment cases.
- One-time step after upgrading, same as v1.37: the previously-stored session is already stale,
so complete one sign-in from a computer; it then persists across restarts.
## 07308545 (2026-08-15)
- Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases)
- Upstream tag : v3.2.2+claude1.30096.1

View File

@@ -209,14 +209,25 @@ registers `codex mcp-server` in both Claude Code and Claude Desktop. A Claude
session can therefore delegate a task to ChatGPT Codex and read its result back
through MCP.
Codex is not baked into the image because its Linux binary is large and the
feature is off by default. At each startup, the add-on resolves the latest
stable upstream release. It downloads the architecture-specific binary into
persistent `/data/codex/bin` only when the installed release is missing or
outdated, verifies the GitHub-published SHA-256 digest before extraction or
execution, validates the staged binary with `--version`, and replaces the
existing binary atomically. If release metadata or the download is unavailable,
startup continues and a previously working installation is retained.
Codex is not baked into the image because its Linux distribution is large and
the feature is off by default. At each startup, the add-on resolves the latest
stable upstream release. It downloads the architecture-specific package into
persistent `/data/codex` only when the installed release is missing, incomplete
or outdated, verifies the GitHub-published SHA-256 digest before extraction or
execution, and validates the staged package with `--version` before it replaces
the installed one. The complete upstream package is installed, not just the
`codex` executable: Codex delegates every shell and file-read tool call to a
companion `codex-code-mode-host` binary that it looks up next to itself, so an
executable installed on its own can answer but can never run anything. If
release metadata or the download is unavailable, startup continues and a
previously working installation is retained.
`/data/codex` belongs to the add-on: everything below it — `bin/`,
`codex-package.json`, `codex-resources/` and `codex-path/` — is replaced as a
unit whenever a new release is installed, so it is not a place to keep files by
hand. Codex's own state (`auth.json`, `config.toml`) lives in `~/.codex` and is
never touched by an install. The installed package is roughly 300 MB, and an
upgrade briefly needs room for the archive and both releases at once.
### Signing in with a ChatGPT subscription

View File

@@ -13,8 +13,10 @@ streamed desktop.
offline until a fresh sign-in was done from a computer). v1.35 switched to
`--password-store=basic` plus a cont-init script that re-syncs the persistent openbox
`autostart` from the image on every boot — **but that flag alone does nothing**, and the bug
survived it untouched. Actually fixed in v1.37, which adds the application-side opt-in the
`basic` backend requires; see "Why v1.35 did not work" below.
survived it untouched. v1.37 added the application-side opt-in the `basic` backend requires;
see "Why v1.35 did not work" below. That patcher then regressed silently when upstream's
bundle output changed shape — fixed again in 07308543.1; see "Why v1.37 stopped working"
below.
- **Planned only:** Problem A (in-desktop browser for OAuth) is intentionally not implemented.
The image ships no browser; complete the login with the user-side workaround below.
@@ -170,10 +172,40 @@ The third row is the one that matters: it is the restart survival this add-on ne
reaches upgrades, not just fresh installs.
4. `gnome-keyring` stays out of the Dockerfile.
### Why v1.37 stopped working
`claude-safestorage-patch.js` only knew how to inject its opt-in *after* a leading
`"use strict"` directive in the app's main bundle, and refused to patch (leaving the app
unpatched and the session un-persisted) if that directive wasn't there. Confirmed live on the
running add-on: Claude Desktop 1.30096.1's main bundle (`.vite/build/index.pre.js`) no longer
opens with a `"use strict"` directive — it now opens directly with a bare IIFE
(`(function(){try{var e=typeof window...`). Upstream's build output changed shape at some point
after v1.37 shipped, the patcher's one injection point stopped existing, and it had been
silently refusing to patch on every boot since — the app's `main.log` kept showing exactly the
same `Encryption not available, returning empty env vars` warning documented above, and the
session went back to not surviving restarts.
`applyPatch()` now falls back to inserting the opt-in as the bundle's first real statement when
no `"use strict"` directive is found, rather than refusing outright. It skips past any leading
BOM, hashbang, or banner comment first (`skipPrologue()`), so a directive hidden behind a
comment is still found and protected instead of being pushed out of the first-statement
position by a naive prepend — Vite/esbuild banners commonly put a license comment ahead of the
directive. A bundle with no directive at all has nothing to protect, so prepending the opt-in
there is unconditionally safe: the injected code is a complete `try{}catch(e){}` statement, and
a statement can never merge with what follows it via ASI the way a bare expression could.
Verified by copying the live production `app.asar` and running the patcher against it directly
(outside the container's boot sequence): the previously-refused bundle now patches
successfully, the marker lands at the front of the main entry, a second run correctly reports
"Already patched" (idempotent), and unit tests cover the bare-IIFE, comment-hidden-directive,
hashbang, and unterminated-comment cases.
### One-time step after upgrading
The previously-stored session is already stale. Complete **one** sign-in from a computer
(mobile still can't finish the OAuth flow itself, per Problem A) — the session then persists
normally and dispatch stays online regardless of which device connects first afterward.
normally and dispatch stays online regardless of which device connects first afterward. This
applies again after the 07308543.1 fix above, since the affected sessions were never persisted
in the first place.
---
@@ -185,7 +217,10 @@ normally and dispatch stays online regardless of which device connects first aft
- `claude_desktop/rootfs/etc/cont-init.d/86-claude_safestorage.sh` and
`claude_desktop/rootfs/usr/local/bin/claude-safestorage-patch.js` — new in v1.37; the
app-side `safeStorage` opt-in that makes `--password-store=basic` actually take effect.
`claude-safestorage-patch.js` updated again in 07308543.1 to also patch bundles with no
leading `"use strict"` directive, and to look past leading comments/hashbang when deciding
whether one is present.
- `claude_desktop/Dockerfile` — corrected stale comment (gnome-keyring is not installed).
- `claude_desktop/CHANGELOG.md` / `config.yaml` — v1.35, then v1.37.
- `claude_desktop/CHANGELOG.md` / `config.yaml` — v1.35, then v1.37, then 07308543.1.
Problem A (in-desktop browser for OAuth) remains planned-only; not touched by this change.

View File

@@ -136,5 +136,5 @@ schema:
slug: claude_desktop
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "07308545"
version: "07308545.3"
video: true

View File

@@ -10,10 +10,20 @@ set -o pipefail
# The install prefix is /data/codex, NOT $HOME/.codex/bin: /data is persistent regardless of the
# configurable data_location, and the managed MCP merge treats commands under $HOME as
# user-installed. Codex state (auth.json, config.toml) remains in the runtime user's home.
#
# Codex is distributed as a package tree, not as a lone executable: since 0.147.0 every shell and
# file-read tool call is executed by a companion binary, codex-code-mode-host, that Codex looks up
# next to itself. Upstream publishes that tree as the codex-package-<target> release asset, and the
# whole tree is installed here. Its layout is load-bearing and must not be flattened: Codex
# canonicalises its own executable path, requires the parent directory to be named `bin`, and then
# reads codex-package.json, codex-resources/ and codex-path/ from that directory's parent. The
# executable's file name is not part of that contract, which is why codex-real keeps its name.
CODEX_ROOT="/data/codex"
CODEX_PREFIX="${CODEX_ROOT}/bin"
CODEX_BIN="${CODEX_PREFIX}/codex"
CODEX_REAL="${CODEX_PREFIX}/codex-real"
CODEX_HOST="${CODEX_PREFIX}/codex-code-mode-host"
CODEX_MANIFEST="${CODEX_ROOT}/codex-package.json"
CODEX_STAMP="${CODEX_PREFIX}/.version"
CODEX_LINK="/usr/local/bin/codex"
CODEX_RELEASE_API="https://api.github.com/repos/openai/codex/releases/latest"
@@ -28,6 +38,60 @@ run_as_runtime_user() {
s6-setuidgid abc env HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" "$@"
}
# What "installed" means, in one place. A Codex that is missing its code-mode host, its package
# manifest or its version stamp still starts, authenticates and answers — it simply cannot run a
# single tool call — so presence of the executable alone is not a usable install. The stamp counts
# because it is removed before the first file of a replacement is moved and written after the last,
# so its absence next to an executable means the tree may mix two releases.
codex_install_is_complete() {
[ -x "$CODEX_REAL" ] \
&& [ -x "$CODEX_HOST" ] \
&& [ -f "$CODEX_MANIFEST" ] \
&& [ -f "$CODEX_STAMP" ]
}
# Move a verified package tree from staging into the install prefix. Called only from an `if`
# condition, where `set -e` does not apply, so every step reports failure explicitly.
#
# Whatever the package ships is installed, rather than the file names this add-on happens to know
# about today: a helper added by a future release has to arrive beside codex-real on its own, or it
# fails exactly the way the missing code-mode host did. Only paths the archive actually contains are
# touched — /data/codex also holds this install's staging directory, so the tree below it is never
# cleared wholesale.
#
# The long, failure-prone part of an install — the download and its digest check — is already done
# by the time this runs; what is left is same-filesystem renames of an already validated tree. They
# are not one atomic operation, so the version stamp is removed first: any interruption leaves a
# stamp-less prefix, which the next boot treats as "not installed" and replaces wholesale. The
# entrypoint is moved last, so a prefix whose codex-real is the new release is a prefix whose
# helper binaries are the new release too.
install_codex_package() {
local staged="$1"
local entry name
rm -f -- "$CODEX_STAMP" || return 1
# Everything beside bin/ first — the manifest and the helper directories (codex-resources/ and
# codex-path/ today, holding bubblewrap, zsh and ripgrep) — then everything the package puts in
# bin/ except the entrypoint, then the entrypoint. The existing launcher and version stamp are
# never matched: the launcher is skipped by name and the stamp is a dot file.
for entry in "${staged}"/*; do
name="${entry##*/}"
if [ ! -e "$entry" ] || [ "$name" = "bin" ]; then
continue
fi
rm -rf -- "${CODEX_ROOT:?}/${name}" || return 1
mv -f -- "$entry" "${CODEX_ROOT}/${name}" || return 1
done
for entry in "${staged}"/bin/*; do
name="${entry##*/}"
if [ ! -e "$entry" ] || [ "$name" = "codex" ]; then
continue
fi
rm -rf -- "${CODEX_PREFIX:?}/${name}" || return 1
mv -f -- "$entry" "${CODEX_PREFIX}/${name}" || return 1
done
mv -f -- "${staged}/bin/codex" "$CODEX_REAL" || return 1
}
if ! bashio::config.true 'install_codex_cli'; then
# Non-destructive: preserve the binary and completed ChatGPT sign-in for a later re-enable.
# 82-claude_tools.sh removes only the MCP registration and managed guidance.
@@ -44,7 +108,7 @@ case "$(uname -m)" in
;;
esac
CODEX_ASSET="codex-${CODEX_TARGET}.tar.gz"
CODEX_ASSET="codex-package-${CODEX_TARGET}.tar.gz"
mkdir -p "$CODEX_PREFIX"
# Migrate the PR's earlier direct-binary layout to the enforced wrapper layout without another
@@ -116,38 +180,47 @@ PY
fi
if [ -z "$release_info" ]; then
if [ -x "$CODEX_REAL" ] && run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
if codex_install_is_complete && run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
bashio::log.warning "Unable to resolve the latest verified Codex release; keeping the existing install"
else
bashio::log.warning "Unable to resolve the latest verified Codex release; Codex is unavailable this boot"
exit 0
bashio::log.warning "Unable to resolve the latest verified Codex release; the installed Codex is missing or incomplete and stays unavailable until a boot can reach the release metadata"
fi
else
IFS=$'\t' read -r CODEX_WANTED CODEX_SHA256 CODEX_URL <<< "$release_info"
if [ -x "$CODEX_REAL" ] \
# An install is complete only if the code-mode host and the package manifest are there too:
# every install made before this add-on switched to the package asset has a working codex-real
# and no helpers, and repairs itself here rather than needing a fresh /data. Running the binary
# also rejects one built for another architecture, which a restored backup could leave behind.
if codex_install_is_complete \
&& [ "$(cat "$CODEX_STAMP" 2> /dev/null || true)" = "$CODEX_WANTED" ] \
&& run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
bashio::log.info "Codex CLI ${CODEX_WANTED} already installed (latest stable)"
else
bashio::log.info "Installing latest stable Codex CLI ${CODEX_WANTED} (${CODEX_TARGET}); this is a large one-time download"
archive="${codex_tmp}/${CODEX_ASSET}"
extracted="${codex_tmp}/codex-${CODEX_TARGET}"
staged="${codex_tmp}/package"
# Fail open for add-on startup but fail closed for the candidate binary: its official
# Fail open for add-on startup but fail closed for the candidate release: its official
# release digest must match before extraction or execution, and replacement happens only
# after the staged binary successfully runs.
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 600 \
-o "$archive" "$CODEX_URL" \
# after the staged tree is complete and its entrypoint successfully runs. The candidate is
# exercised in staging with its own codex-package.json and helper directories in place, so
# the layout Codex will resolve at runtime is the layout that was validated.
if mkdir -p "$staged" \
&& chmod 0755 "$staged" \
&& curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 600 \
-o "$archive" "$CODEX_URL" \
&& printf '%s %s\n' "$CODEX_SHA256" "$archive" | sha256sum -c - > /dev/null \
&& tar -xzf "$archive" -C "$codex_tmp" \
&& [ -f "$extracted" ] \
&& chmod 0755 "$extracted" \
&& run_as_runtime_user "$extracted" --version > /dev/null 2>&1 \
&& mv -f "$extracted" "$CODEX_REAL"; then
&& tar -xzf "$archive" -C "$staged" \
&& [ -f "${staged}/codex-package.json" ] \
&& [ -f "${staged}/bin/codex" ] \
&& [ -f "${staged}/bin/codex-code-mode-host" ] \
&& chmod 0755 "${staged}/bin/codex" "${staged}/bin/codex-code-mode-host" \
&& run_as_runtime_user "${staged}/bin/codex" --version > /dev/null 2>&1 \
&& install_codex_package "$staged"; then
printf '%s' "$CODEX_WANTED" > "$CODEX_STAMP"
bashio::log.info "Codex CLI installed: $("$CODEX_REAL" --version 2> /dev/null || echo unknown)"
elif [ -x "$CODEX_REAL" ]; then
elif codex_install_is_complete; then
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; keeping the existing install"
else
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; Codex is unavailable this boot"
@@ -155,7 +228,16 @@ else
fi
fi
if [ ! -x "$CODEX_REAL" ]; then
# The launcher is the add-on's single "Codex is usable" signal: 82-claude_tools.sh registers the
# Codex MCP server when it is executable and re-checks nothing else. Write it only for a complete
# install, and remove it — together with the PATH symlink — for an incomplete one. Both the launcher
# and the package tree live in /data and survive restarts independently, so a launcher left from an
# earlier boot would otherwise outlive the install it was written for and advertise a Codex whose
# every tool call fails. The executable, the package tree and the ChatGPT sign-in are all left in
# place: a later boot completes the install without another download or another login.
if ! codex_install_is_complete; then
rm -f -- "$CODEX_BIN" "$CODEX_LINK"
bashio::log.warning "Codex is not completely installed; not registering it this boot"
exit 0
fi

View File

@@ -128,24 +128,98 @@ function integrityOf(buf, blockSize) {
return { algorithm: 'SHA256', hash: sha256(buf), blockSize, blocks };
}
/* Insert the opt-in after the bundle's leading "use strict" directive. It must go *after* it: a
* directive prologue only takes effect as the very first statement, so prepending would silently
* drop the whole main process out of strict mode.
// Any of the four ECMAScript LineTerminator code points — not just "\n". A //-comment or an ASI
// boundary ends at the first of these, and using a bare "\n" search for that would let a CR- or
// U+2028/U+2029-terminated line swallow real code as "still the comment/still on this line" and
// misplace the insertion point deep inside the bundle instead of before it.
const LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
/* Skip a leading BOM and hashbang line. Only meaningful at byte 0 — called once, before any
* directive scanning. */
function skipBomAndHashbang(source) {
let i = source.charCodeAt(0) === 0xfeff ? 1 : 0; // BOM
if (source.startsWith('#!', i)) {
const m = LINE_TERMINATOR.exec(source.slice(i));
i += m ? m.index + 1 : source.length - i;
}
return i;
}
/* Skip whitespace and comments starting at i. Returns the next index, or -1 for an unterminated
* block comment (caller refuses rather than guesses). */
function skipWhitespaceAndComments(source, i) {
for (;;) {
const rest = source.slice(i);
const ws = /^\s+/.exec(rest);
if (ws) {
i += ws[0].length;
continue;
}
if (rest.startsWith('//')) {
const m = LINE_TERMINATOR.exec(rest);
i += m ? m.index + 1 : rest.length;
continue;
}
if (rest.startsWith('/*')) {
const end = rest.indexOf('*/');
if (end === -1) return -1;
i += end + 2;
continue;
}
return i;
}
}
// A single-line string literal: no raw line terminator in its content (a real one would need an
// escaped line continuation, which this deliberately doesn't special-case — failing to match
// just means the prologue scan below stops there, which is always safe, see applyPatch).
const STRING_LITERAL = /^(['"])(?:\\.|(?!\1)[^\\\n\r\u2028\u2029])*\1/;
/* Scan the bundle's full leading directive prologue: every consecutive ExpressionStatement made
* of nothing but a string literal, per how ECMAScript directives actually work. A directive
* prologue can hold more than one entry, and "use strict" only has to appear *somewhere* in it,
* not first — so this treats every leading directive as needing protection, not just one
* specifically named "use strict". Returns the index right after the full prologue (which is
* also correct as "no prologue, insert here" when there wasn't one), or -1 when a leading string
* literal isn't cleanly terminated as its own statement — ambiguous whether it's a directive at
* all, refused rather than guessed at. */
function scanDirectivePrologue(source, start) {
let i = start;
for (;;) {
const next = skipWhitespaceAndComments(source, i);
if (next === -1) return -1;
const rest = source.slice(next);
const m = STRING_LITERAL.exec(rest);
if (!m) return next; // not a directive; prologue (possibly empty) ends here
const after = rest.slice(m[0].length);
if (after[0] === ';') {
i = next + m[0].length + 1;
} else if (after === '' || LINE_TERMINATOR.test(after[0])) {
i = next + m[0].length;
} else {
return -1; // "use strict" + x and friends: not unambiguously a directive
}
}
}
/* Insert the opt-in right after the bundle's full leading directive prologue (BOM/hashbang, then
* any run of string-literal-only statements — "use strict" among them if present). It must go
* *after* the whole prologue, not just after the first entry: a directive prologue only takes
* effect when its members are the very first statements, so inserting between two of them, or
* ahead of all of them, would silently drop the file out of strict mode just as surely as
* inserting ahead of a lone "use strict" would.
*
* Returns null — meaning "refuse to patch" — for anything that is not unambiguously a directive.
* `"use strict" + x` is an expression, not a directive, and injecting into it would produce a
* syntax error, so the directive is only accepted when it is terminated by its own semicolon, a
* line break, or end of input. */
* When there is no prologue at all (observed from Claude Desktop 1.30096.1 onward, whose main
* entry opens with a bare IIFE instead), there is nothing to preserve: PATCH lands at the same
* position anyway, as the file's first real statement. A `try{}catch(e){}` statement can never
* merge with whatever follows via ASI — unlike a bare expression, a statement is not a valid
* left-hand side for anything a following token could continue — so this is unconditionally
* safe once placed after any banner comment / hashbang / directive prologue. */
function applyPatch(source) {
const m = /^\s*(['"])use strict\1(;?)/.exec(source);
if (!m) return null;
const rest = source.slice(m[0].length);
const terminated = m[2] === ';' || rest === '' || /^[\r\n]/.test(rest);
if (!terminated) return null;
// Supply the terminator when the directive relied on ASI; without it the injected code would
// continue the string-literal expression instead of following it.
const sep = m[2] === ';' ? '' : ';';
return source.slice(0, m[0].length) + sep + PATCH + rest;
const start = skipBomAndHashbang(source);
const end = scanDirectivePrologue(source, start);
if (end === -1) return null;
return source.slice(0, end) + PATCH + source.slice(end);
}
function writeAll(fd, buf) {
@@ -203,7 +277,7 @@ function main() {
const patchedSource = applyPatch(original);
if (patchedSource === null) {
fail(`${mainRel} does not begin with a recognized "use strict" directive; refusing to patch`);
fail(`${mainRel} opens with an ambiguous "use strict"-like string literal; refusing to patch`);
}
const patched = Buffer.from(patchedSource, 'utf8');

View File

@@ -183,6 +183,16 @@ if bashio::config.true 'install_codex_cli'; then
if [ -x "$codex_bin" ]; then
printf '%-30s %s\n' "installed" "$("$codex_bin" --version 2> /dev/null || echo 'FAILED TO RUN')"
printf '%-30s %s\n' "installed version stamp" "$(cat /data/codex/bin/.version 2> /dev/null || echo 'MISSING')"
# Codex runs every shell and file-read tool call through this companion binary. When it is
# absent the CLI still starts, authenticates and answers, but each tool call fails and the
# run still exits 0 — so report it explicitly rather than leaving it to be inferred.
if [ -x /data/codex/bin/codex-code-mode-host ] \
&& [ -f /data/codex/codex-package.json ] \
&& [ -f /data/codex/bin/.version ]; then
printf '%-30s %s\n' "package layout" "complete"
else
printf '%-30s %s\n' "package layout" "INCOMPLETE - tool calls will fail; restart the add-on to reinstall"
fi
printf '%-30s %s\n' "release policy" "latest stable, SHA-256 verified"
printf '%-30s %s\n' "authentication policy" "ChatGPT subscription only"

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: 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: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

3
comicarr/CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
## 0.34.0 (20-08-2026)
- Initial release, based on upstream frankieramirez/comicarr 0.34.0 (changelog : https://github.com/frankieramirez/comicarr/releases)
- Home Assistant ingress support through a bundled nginx reverse proxy

124
comicarr/Dockerfile Normal file
View File

@@ -0,0 +1,124 @@
#============================#
# ALEXBELGIUM'S DOCKERFILE #
#============================#
# _.------.
# _.-` ('>.-`"""-.
# '.--'` _'` _ .--.)
# -' '-.-';` `
# ' - _.' ``'--.
# '---` .-'""`
# /`
#=== Home Assistant Addon ===#
#################
# 1 Build Image #
#################
ARG BUILD_FROM
ARG BUILD_VERSION
ARG BUILD_UPSTREAM="0.34.0"
FROM ghcr.io/frankieramirez/comicarr:${BUILD_UPSTREAM}
##################
# 2 Modify Image #
##################
USER root
# No S6_* tuning here : the upstream image is a plain python:3.12-slim with no
# s6-overlay, so the vars the other addons set would be read by nobody
##################
# 3 Install apps #
##################
# Add rootfs
# Absolute paths on purpose : the upstream image sets WORKDIR /opt/comicarr, so
# the relative "find ." used by the other addons would miss /etc entirely
COPY rootfs/ /
RUN find /etc/cont-init.d /etc/services.d -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \;
# Uses /bin for compatibility purposes
# hadolint ignore=DL4005
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
# Modules
ARG MODULES="00-banner.sh 00-global_var.sh 01-custom_script.sh 00-local_mounts.sh 00-smb_mounts.sh"
# Automatic modules download
# Runs before the apps installer on purpose (the repo-wide order) : this script
# bootstraps bash, curl and ca-certificates itself, which the slim base lacks,
# and the apps installer below decides what to install by grepping the modules
# it downloads here
COPY ha_automodules.sh /ha_automodules.sh
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
# Manual apps
ENV PACKAGES="nginx"
# Automatic apps & bashio
COPY ha_autoapps.sh /ha_autoapps.sh
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
################
# 4 Entrypoint #
################
# The upstream image ships no s6-overlay, so ha_entrypoint runs as pid 1 : it
# executes /etc/cont-init.d, then supervises /etc/services.d. This replaces the
# upstream /entrypoint.sh, which services.d/comicarr/run still calls when the
# user asks for an unprivileged uid.
COPY ha_entrypoint.sh /ha_entrypoint.sh
RUN chmod 0755 /ha_entrypoint.sh
ENTRYPOINT ["/ha_entrypoint.sh"]
# Install bashio
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
############
# 5 Labels #
############
ARG BUILD_ARCH
ARG BUILD_DATE
ARG BUILD_DESCRIPTION
ARG BUILD_NAME
ARG BUILD_REF
ARG BUILD_REPOSITORY
ARG BUILD_VERSION
ENV BUILD_VERSION="${BUILD_VERSION}"
LABEL \
io.hass.name="${BUILD_NAME}" \
io.hass.description="${BUILD_DESCRIPTION}" \
io.hass.arch="${BUILD_ARCH}" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION} \
maintainer="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.title="${BUILD_NAME}" \
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
org.opencontainers.image.vendor="Home Assistant Add-ons" \
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.url="https://github.com/alexbelgium" \
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
org.opencontainers.image.created=${BUILD_DATE} \
org.opencontainers.image.revision=${BUILD_REF} \
org.opencontainers.image.version=${BUILD_VERSION}
#################
# 6 Healthcheck #
#################
# First boot runs the alembic migrations against a cold sqlite database, which
# is slow on a low-end arm board : leave it time to settle before failing
ENV HEALTH_PORT="8090" \
HEALTH_URL="/api/health"
HEALTHCHECK \
--interval=30s \
--retries=5 \
--start-period=180s \
--timeout=25s \
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" >/dev/null 2>&1 || exit 1

95
comicarr/README.md Normal file
View File

@@ -0,0 +1,95 @@
# Home Assistant Add-on: Comicarr
Automated comic book and manga downloader and library manager with a modern React UI.
[Comicarr](https://comicarr.com) is a fork of Mylar3 rebuilt around a React frontend and a
FastAPI backend. You add series, and it watches for new issues, sends them to your download
client, tags them and files them into your library.
## About
- Track comic series and manga, and grab new issues as they are released
- Works with SABnzbd, NZBGet, blackhole and torrent clients
- Metadata from ComicVine and Metron, with automatic tagging
- One-command migration from an existing Mylar3 installation
- OPDS feed for third-party readers
## Installation
1. Add this repository to Home Assistant.
2. Install the **Comicarr** add-on.
3. Start the add-on and open it from the sidebar (ingress), or on port `8090` at
`http://homeassistant:8090`.
4. Complete the first-run setup when the web interface asks for it.
5. Point Comicarr's library and download folders at a persistent location such as
`/media/comics` and `/share/downloads`.
The first start takes longer than usual: the database migrations run against a cold SQLite
database.
## Configuration
| Option | Description |
|--------|-------------|
| `PUID` / `PGID` | Ownership applied to the add-on configuration directory. Defaults to `0` (root). See the note below before changing it. |
| `TZ` | Timezone, e.g. `Europe/Paris`. |
| `localdisks` | Local disks to mount, e.g. `sda1` or a disk label. |
| `networkdisks` | SMB shares to mount, e.g. `//192.168.1.2/comics`. Mounted under `/mnt`. |
| `cifsusername` / `cifspassword` / `cifsdomain` | Credentials for the SMB shares. |
| `smbv1` | Allow the legacy SMBv1 protocol. |
| `env_vars` | Extra environment variables passed to Comicarr. See the [wiki](https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2). |
`COMICARR_LOG_LEVEL` (`0`, `1` or `2`) is a useful `env_vars` entry: it overrides the log
verbosity chosen in Settings on every restart.
With the default `PUID`/`PGID` of `0`, Comicarr runs as root, which is what lets it write to
Home Assistant's root-owned `/media` and `/share`. Setting `PUID` to any other value hands
startup to the upstream entrypoint, which creates a matching user and drops privileges — the
library and download folders then have to be writable by that user. Switching an existing
installation from `0` to an unprivileged uid also leaves the files already written under
`/config/comicarr` owned by root; chown them yourself, or Comicarr will fail the first time it
writes its configuration or database.
The web interface port is fixed at `8090`. Changing **Settings → Interface → port** has no
effect: the add-on forces `8090` on startup, because ingress and the health check are built
around it.
## Ingress and URLs
Comicarr has no url-base setting, so the add-on bundles an nginx reverse proxy that rewrites the
absolute `/assets`, `/api` and `/cache` urls in the served HTML, JavaScript and CSS onto the
ingress path, and replaces the upstream `X-Frame-Options: DENY` and `frame-ancestors 'none'`
headers, which would otherwise leave the panel blank.
Two consequences worth knowing:
- The app's client-side router does not know about the ingress prefix. It rewrites the panel's
address to `/` shortly after loading. Everything keeps working, because every request url is
rewritten to an absolute ingress path — but reloading the panel frame itself (rather than
reopening it from the sidebar) shows Home Assistant instead of Comicarr.
- Two places in the app navigate with `window.location` rather than the router: finishing the
first-run setup, and a session expiring while the dashboard is open. Both leave the panel;
reopening Comicarr from the sidebar recovers.
External clients — OPDS readers in particular — must use the direct `http://homeassistant:8090`
url. Ingress is browser-session based, so those clients cannot authenticate through it.
Do not enable HTTPS inside Comicarr's own settings: the add-on's proxy talks plain HTTP to it on
`127.0.0.1`, and ingress would stop working.
## Data
Comicarr's `config.ini`, database, logs and cover cache live in `/config/comicarr` inside the
add-on, which Home Assistant maps to this add-on's own configuration directory —
`/addon_configs/<repository_id>_comicarr`, browsable with the Filebrowser add-on. They survive
add-on updates. That is the same layout as the upstream `./config:/config` compose volume, so an
existing installation can be copied in as is.
Comic and download folders are **not** stored there. Point them at `/media`, `/share` or a
mounted disk. The `/comics`, `/manga` and `/downloads` paths used by the upstream docker image
are not persistent in Home Assistant — do not use them.
## Support
- [Comicarr upstream project](https://github.com/frankieramirez/comicarr)
- [Add-on repository issues](https://github.com/alexbelgium/hassio-addons/issues)

68
comicarr/apparmor.txt Normal file
View File

@@ -0,0 +1,68 @@
#include <tunables/global>
profile comicarr_addon flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
capability chown,
capability dac_override,
capability dac_read_search,
capability fowner,
capability setgid,
capability setuid,
capability sys_chroot,
capability sys_admin,
file,
signal,
mount,
umount,
remount,
network udp,
network tcp,
network dgram,
network stream,
network inet,
network inet6,
network netlink raw,
network unix dgram,
# Entrypoint stack
/init ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/run/{,**} rwk,
/dev/tty rw,
/bin/** ix,
/usr/bin/** ix,
/usr/lib/bashio/** ix,
/etc/s6/** rix,
/run/s6/** rix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/init rix,
/var/run/** mrwkl,
/var/run/ mrwkl,
/dev/i2c-1 mrwkl,
# Files required
/dev/fuse mrwkl,
/dev/sda1 mrwkl,
/dev/sdb1 mrwkl,
/dev/nvme0 mrwkl,
/dev/nvme1 mrwkl,
/dev/mmcblk0p1 mrwkl,
/dev/* mrwkl,
/tmp/** mrkwl,
# Data access
/data/** rw,
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read) peer=docker-default,
# docker daemon confinement requires explicit allow rule for signal
signal (receive) set=(kill,term) peer=/usr/bin/docker,
}

6
comicarr/build.json Normal file
View File

@@ -0,0 +1,6 @@
{
"build_from": {
"aarch64": "ghcr.io/frankieramirez/comicarr:latest",
"amd64": "ghcr.io/frankieramirez/comicarr:latest"
}
}

105
comicarr/config.yaml Normal file
View File

@@ -0,0 +1,105 @@
arch:
- aarch64
- amd64
description:
Automated comic book and manga downloader and library manager with a modern
React UI
devices:
- /dev/dri
- /dev/dri/card0
- /dev/dri/card1
- /dev/dri/renderD128
- /dev/vchiq
- /dev/video10
- /dev/video11
- /dev/video12
- /dev/video13
- /dev/video14
- /dev/video15
- /dev/video16
- /dev/ttyUSB0
- /dev/sda
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
- /dev/sdg
- /dev/nvme
- /dev/nvme0
- /dev/nvme0n1
- /dev/nvme0n1p1
- /dev/nvme0n1p2
- /dev/nvme0n1p3
- /dev/nvme1n1
- /dev/nvme1n1p1
- /dev/nvme1n1p2
- /dev/nvme1n1p3
- /dev/nvme2n1
- /dev/nvme2n1p1
- /dev/nvme2n1p2
- /dev/nvme2n3p3
- /dev/mmcblk
- /dev/fuse
- /dev/sda1
- /dev/sdb1
- /dev/sdc1
- /dev/sdd1
- /dev/sde1
- /dev/sdf1
- /dev/sdg1
- /dev/sda2
- /dev/sdb2
- /dev/sdc2
- /dev/sdd2
- /dev/sde2
- /dev/sdf2
- /dev/sdg2
- /dev/sda3
- /dev/sdb3
- /dev/sda4
- /dev/sdb4
- /dev/sda5
- /dev/sda6
- /dev/sda7
- /dev/sda8
- /dev/nvme0
- /dev/nvme1
- /dev/nvme2
image: ghcr.io/alexbelgium/comicarr-{arch}
ingress: true
init: false
map:
- addon_config:rw
- media:rw
- share:rw
name: Comicarr
options:
env_vars: []
PGID: 0
PUID: 0
panel_icon: mdi:book-open-page-variant
ports:
8090/tcp: 8090
ports_description:
8090/tcp: Web interface and OPDS feed
privileged:
- SYS_ADMIN
- DAC_READ_SEARCH
schema:
env_vars:
- name: match(^[A-Za-z0-9_]+$)
value: str?
PGID: int
PUID: int
TZ: str?
cifsdomain: str?
cifspassword: str?
cifsusername: str?
localdisks: str?
networkdisks: str?
smbv1: bool?
slug: comicarr
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/comicarr
version: "0.34.0"

BIN
comicarr/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
comicarr/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,28 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# Comicarr keeps its config.ini, sqlite database, logs and cover cache in the
# datadir it is started with. /config is the addon_config mount, so using
# /config/comicarr reproduces the layout of the upstream compose file's
# "./config:/config" volume : an existing installation can be copied in as is.
CONFIG_LOCATION="/config/comicarr"
bashio::log.info "Config stored in $CONFIG_LOCATION"
mkdir -p "$CONFIG_LOCATION"
# Numbered 20- on purpose : it must sort after 00-global_var.sh, which is what
# exports PUID/PGID from the addon options. The upstream image sets neither, so
# the fallbacks only apply when the module is absent.
# Not recursive : the cover cache under $CONFIG_LOCATION grows to thousands of
# files, and walking it on every boot would delay startup for no gain. This is
# what the upstream entrypoint does too.
chown "${PUID:-0}:${PGID:-0}" /config "$CONFIG_LOCATION"
# The upstream entrypoint installs the timezone when it runs, and the default
# path in services.d/comicarr/run bypasses it, so do it here for both paths.
if [ -n "${TZ:-}" ] && [ -f "/usr/share/zoneinfo/${TZ}" ]; then
ln -sf "/usr/share/zoneinfo/${TZ}" /etc/localtime
echo "${TZ}" > /etc/timezone
fi

View File

@@ -0,0 +1,17 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
#################
# NGINX SETTING #
#################
declare ingress_interface
declare ingress_port
declare ingress_entry
ingress_port=$(bashio::addon.ingress_port)
ingress_interface=$(bashio::addon.ip_address)
ingress_entry=$(bashio::addon.ingress_entry)
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf

View File

@@ -0,0 +1,96 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@@ -0,0 +1 @@
resolver 127.0.0.11 ipv6=off;

View File

@@ -0,0 +1,56 @@
# Run nginx in foreground.
daemon off;
# This is run inside Docker.
user root;
# Pid storage location.
pid /var/run/nginx.pid;
# Set number of worker processes.
worker_processes 1;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Write error log to Hass.io add-on log.
error_log /proc/1/fd/1 error;
# Load allowed environment vars
env HASSIO_TOKEN;
# Load dynamic modules.
include /etc/nginx/modules-enabled/*.conf;
# Max num of simultaneous connections by a worker process.
events {
worker_connections 512;
}
http {
include /etc/nginx/includes/mime.types;
log_format hassio '[$time_local] $status '
'$http_x_forwarded_for($remote_addr) '
'$request ($http_user_agent)';
access_log /proc/1/fd/1 hassio;
client_max_body_size 4G;
default_type application/octet-stream;
gzip on;
keepalive_timeout 65;
sendfile on;
server_tokens off;
tcp_nodelay on;
tcp_nopush on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
include /etc/nginx/includes/resolver.conf;
include /etc/nginx/servers/*.conf;
}

View File

@@ -0,0 +1,82 @@
server {
listen %%interface%%:%%port%% default_server;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# The dashboard subscribes to /api/events/stream over SSE ; buffering
# would hold every event back until the buffer fills.
proxy_buffering off;
proxy_connect_timeout 30m;
proxy_send_timeout 30m;
proxy_read_timeout 30m;
# Comicarr refuses to be framed : SecurityHeadersMiddleware sends
# X-Frame-Options: DENY and a CSP carrying frame-ancestors 'none', which
# on their own leave the ingress panel blank. Replace both with the same
# policy narrowed to the Home Assistant origin that serves the panel.
# The CSP below is upstream's list verbatim except for two directives :
# frame-ancestors becomes 'self', and img-src takes any https origin
# instead of the metadata-provider allowlist upstream compiles into the
# header -- that allowlist grows with upstream releases, and a stale copy
# kept here would silently stop covers from loading.
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'" always;
# FastAPI's redirect-slash Location headers are built against the address
# nginx talks to and carry no ingress prefix ; the second rule covers an
# already relative Location.
absolute_redirect off;
proxy_redirect http://127.0.0.1:8090/ %%ingress_entry%%/;
proxy_redirect / %%ingress_entry%%/;
# Keep the session cookie on the ingress path rather than the Home
# Assistant root, so it is not sent to Home Assistant itself nor to any
# other add-on's ingress panel. Cookies are matched against the request
# path, and every request the app makes is rewritten below to sit under
# the ingress entry, so this does not cost the session.
proxy_cookie_path / %%ingress_entry%%/;
# Comicarr has no url-base setting of any kind : vite emits /assets/...
# with no base, and the api client, the SSE hook and the cover <img>
# tags all build absolute /api/... and /cache/... urls. Ingress strips
# its own prefix before forwarding, so the prefix has to be put back
# into what the browser sees. Only html (implicit), javascript and css
# are scanned -- json responses, cover images and archive bodies stream
# through untouched.
proxy_set_header Accept-Encoding "";
sub_filter_once off;
sub_filter_types application/javascript text/javascript text/css;
sub_filter '"/assets/' '"%%ingress_entry%%/assets/';
sub_filter "'/assets/" "'%%ingress_entry%%/assets/";
sub_filter 'url(/assets/' 'url(%%ingress_entry%%/assets/';
sub_filter '"/api/' '"%%ingress_entry%%/api/';
sub_filter "'/api/" "'%%ingress_entry%%/api/";
sub_filter '`/api/' '`%%ingress_entry%%/api/';
sub_filter '"/cache/' '"%%ingress_entry%%/cache/';
sub_filter "'/cache/" "'%%ingress_entry%%/cache/";
sub_filter '`/cache/' '`%%ingress_entry%%/cache/';
sub_filter '"/favicon.ico"' '"%%ingress_entry%%/favicon.ico"';
# Rewritten javascript and css must not be kept under upstream's one
# year immutable policy for /assets : those file names are content
# hashed upstream, so a change to the rules above would otherwise never
# reach a browser that already holds the old transformed bundle. Every
# other response is already sent as no-cache by the app, so this
# overrides nothing else.
proxy_hide_header Cache-Control;
add_header Cache-Control "no-cache" always;
}
}

View File

@@ -0,0 +1,31 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# The upstream /entrypoint.sh creates a "comicarr" user out of PUID/PGID and
# gosu's to it, but it runs "useradd -u $PUID" under set -e : with PUID=0 -- the
# default in this repo, and the only value that can write Home Assistant's
# root-owned /media and /share -- useradd refuses the duplicate uid and takes
# the whole container down. So the default path starts the app directly as root,
# the same choice the komga add-on makes, and the upstream entrypoint is used
# only when the user asks for an unprivileged uid.
#
# --port is forced on both paths. HTTP_PORT is writable from the Settings page,
# and changing it there would silently break nginx's proxy_pass and the
# healthcheck, leaving an add-on that looks healthy and serves nothing.
umask "${UMASK:-002}"
if [ "${PUID:-0}" != "0" ]; then
bashio::log.info "Starting Comicarr as ${PUID}:${PGID:-0} ..."
exec /entrypoint.sh --port 8090
fi
bashio::log.info "Starting Comicarr..."
cd /opt/comicarr
exec python /opt/comicarr/Comicarr.py \
--nolaunch \
--datadir /config/comicarr \
--port 8090

View File

@@ -0,0 +1,35 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
# ==============================================================================
# Wait for Comicarr to answer before nginx starts serving ingress. First boot
# runs the alembic migrations against a cold database, so leave a wide margin,
# but poll rather than call bashio::net.wait_for : bashio takes (port host
# timeout) while the bundled bashio-standalone.sh takes (host port timeout), and
# picking the wrong one would either fail instantly or block for the whole
# timeout.
# The per probe timeouts keep the 15 minute ceiling real : without them a half
# open connection would hang a single probe, and the loop, forever.
# A wall clock deadline, not an attempt count : a failed probe costs up to
# max-time on top of the sleep, so counting attempts would stretch the wait to
# roughly twice the advertised ceiling.
comicarr_ready=false
deadline=$((SECONDS + 900))
while [ "$SECONDS" -lt "$deadline" ]; do
if curl -sf --connect-timeout 2 --max-time 5 -o /dev/null "http://127.0.0.1:8090/api/health"; then
comicarr_ready=true
break
fi
sleep 5
done
# Deliberately not fatal : nginx serving a 502 tells the user something is wrong
# and starts working by itself once Comicarr finally answers, while refusing to
# start would take ingress down for good after ha_entrypoint gives up retrying.
if [ "$comicarr_ready" != true ]; then
bashio::log.warning "Comicarr did not answer within 15 minutes. Starting NGinx anyway : ingress will return 502 until it does."
fi
bashio::log.info "Starting NGinx..."
exec nginx

10
comicarr/updater.json Normal file
View File

@@ -0,0 +1,10 @@
{
"github_beta": "false",
"github_fulltag": false,
"last_update": "2026-08-20",
"repository": "alexbelgium/hassio-addons",
"slug": "comicarr",
"source": "github",
"upstream_repo": "frankieramirez/comicarr",
"upstream_version": "0.34.0"
}

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.3 KiB

After

Width:  |  Height:  |  Size: 1.5 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.4 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: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,4 +1,18 @@
## 1.5.1.2 (2026-08-19)
- Fix direct access on port 8071, which was broken in 1.5.1.1: the root
redirect pointed at the container-internal port 8072 instead of the
published one, and the page it led to referenced assets under a path the
add-on did not serve, so every asset returned 404. Requests are now passed
through unchanged, with the bare root and the two previously documented
`/filebrowser_quantum` URLs redirected to the app's configured base path.
## 1.5.1.1 (2026-08-16)
- Expose the web UI on host port 8071, reachable at `<your-ip>:8071`
(redirects to `/filebrowser_quantum/`). Direct access is served by a new,
separate nginx vhost that proxies to the same backend Ingress already uses;
Ingress itself, and the app's own base URL, are unchanged.
## 1.5.1 (2026-08-08)
- Update to latest version from gtsteffaniak/filebrowser (changelog : https://github.com/gtsteffaniak/filebrowser/releases)

View File

@@ -46,7 +46,7 @@ comparison to installing any other Home Assistant add-on.
## Configuration
The web UI can be found at `<your-ip>:8071` or through the Home Assistant sidebar when using Ingress.
The web UI can be found at `<your-ip>:8071` or through the Home Assistant sidebar when using Ingress. Direct access redirects to the add-on's configured base path, so the address bar will show a longer URL than the one you typed.
**Default credentials:**
- Username: `admin`

View File

@@ -97,6 +97,10 @@ options:
default_user_scope: "/"
panel_admin: false
panel_icon: mdi:file-search
ports:
8072/tcp: 8071
ports_description:
8072/tcp: Web UI port
privileged:
- SYS_ADMIN
- DAC_READ_SEARCH
@@ -114,4 +118,4 @@ schema:
slug: filebrowser_quantum
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.5.1"
version: "1.5.1.2"

View File

@@ -43,6 +43,9 @@ declare ingress_interface
declare ingress_port
#declare keyfile
# The app's own baseURL is the Supervisor ingress-entry path, unchanged from
# before: FileBrowser emits that prefix as absolute links in its HTML and JS,
# so it is also the path direct ip:port access has to use (see direct.conf).
FB_BASEURL=$(bashio::addon.ingress_entry)
export FB_BASEURL
@@ -59,6 +62,15 @@ sed -i "s|%%protocol%%|${ADDON_PROTOCOL}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%port%%|${ingress_port}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%interface%%|${ingress_interface}|g" /etc/nginx/servers/ingress.conf
sed -i "s|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/ingress.conf
# --- Direct ip:port access (separate vhost, ingress untouched) ---
# Listens on 8072, published to the host as 8071 by config.yaml's `ports:`.
# Requests are passed through unchanged; the bare root and the two legacy
# /filebrowser_quantum paths are redirected to the app's baseURL, which is what
# its own links already point at.
sed -i "s|%%protocol%%|${ADDON_PROTOCOL}|g" /etc/nginx/servers/direct.conf
sed -i "s|%%subpath%%|${FB_BASEURL}/|g" /etc/nginx/servers/direct.conf
mkdir -p /var/log/nginx && touch /var/log/nginx/error.log
############################

View File

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

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.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.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: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 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: 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.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

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