Seventeen addons carried a config.yaml version that awesomeversion, the
library Home Assistant orders versions with, reports as UNKNOWN: text
tags such as version-bf9e0b4f or sts, LinuxServer tags such as
v26.2-ls256, and local counters written as a semver pre-release such as
15.7-47. Home Assistant cannot tell which of two such versions is newer,
so update detection depends on a compare exception rather than on
ordering, and any version that becomes partially comparable silently
stops being offered.
Each version keeps every number it carried, as a section of its own:
v26.2-ls256 becomes v26.2.256, 4.16-r0-ls95-7 becomes 4.16.0.95.7 and
5.0.0b5-3 becomes 5.0.0.5.3, so nothing that ordered the addon is lost
and no previously published version is reused. The two versions holding
no number at all use the date instead. Only config.yaml and CHANGELOG.md
change, so every addon still builds from the upstream tag recorded in
its Dockerfile and updater.json.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(addons_updater): write Home Assistant compliant addon versions
The updater copied the raw upstream tag into config.yaml. Home Assistant
orders addon versions with awesomeversion and hides the update when it can
compare both versions and the new one is not strictly newer, so tags such
as 1.2.3-2, 1.2.3+4 or 1.2.3-2026-08-01 silently stopped the update from
being offered, and tags such as version-bf9e0b4f or ubuntu-2026-06-01
cannot be ordered at all.
The addon version is now derived from the upstream tag by ha_version.py,
using the same library Home Assistant uses: a sortable and newer tag is
kept as it is, 1.2.3-4 and 1.2.3+4 become 1.2.3.4, otherwise the release
number inside the tag, an incremented addon number or the date is used.
updater.json keeps the raw upstream tag, so the same upstream release is
never published twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(addons_updater): address review comments
- calendar versions carrying a counter now advance to the current date
instead of only incrementing the counter
- --selftest runs against a fixed date, so it keeps passing after today
- config.json is written from a validated jq result, as updater.json is
- README states the raw tag is added to the changelog only when it
differs from the addon version
- docstring, comment and changelog formatting
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(addons_updater): turn pre-release markers into a version section
"5.0.0b5" is published as "5.0.0.5" so the beta number keeps ordering
the addon instead of relying on how awesomeversion reads the marker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(addons_updater): keep every number of an unorderable tag
"v26.2-ls256" is published as "v26.2.256", "nightly-2.6.1.5509-ls8" as
"2.6.1.5509.8" and "4.16-r0-ls94" as "4.16.0.94", so the build number
keeps ordering the addon instead of being dropped. Words holding no
number, architectures and commit hashes are left out, and a section
ending on a year is counted up rather than incremented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* style(addons_updater): keep the helper docstrings on one line
Codacy runs pydocstyle with D213, which the multi-line summary added
with the numbers rule trips.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(addons_updater): leave out more arch words and unreal dates
"i686" and friends were read as the number 686, and "2026.02.31" was
taken for a calendar version. Both now fall back to the plain number
rules.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The "mount" auto-detection in ha_automatic_packages.sh appended the
glob "ntfs*" to the apt install list. On Debian trixie-based images
(e.g. ghcr.io/starosdev/scrutiny:latest-omnibus, used by scrutiny and
scrutiny_fa) this glob fails to resolve even though the real package
ntfs-3g exists, breaking the Docker build. Use the exact package name
instead, matching the apk branch just above it.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Exclude the compile-breaking Species Manage PR from dynamic source merging by keeping it in draft, and retrigger the birdnet-go-dev build as source-20260729.1.
* fix(claude_desktop): persist sign-in by opting into Electron safeStorage
Claude Desktop asked the user to sign in again on every start. The v1.35 fix
was inert: --password-store=basic did reach the process (confirmed on a live
install's /proc/<pid>/cmdline), but the app still logged "safeStorage not
available, tokens will not persist" on every launch.
Electron refuses its built-in basic_text backend unless the application calls
safeStorage.setUsePlainTextEncryption(true) before the ready event, and Claude
Desktop never calls it - the symbol is present in the shipped Electron binary
but absent from resources/app.asar. So isEncryptionAvailable() stayed false and
the auth token was never persisted. Verified against a standalone Electron of
the same generation: without the opt-in it is false; with it, true, and a
separate later process decrypts a blob written by an earlier one.
There is no equivalent command-line switch, and NODE_OPTIONS=--require is
ignored by packaged Electron apps (verified against the real binary), so the
opt-in is injected into the app's main bundle inside app.asar. gnome-keyring
stays out of the image: its first-boot password prompt blocks the app from
launching at all.
The patcher fails closed, rebuilds the archive preserving unpacked/symlink
entries, recomputes the changed entry's SHA-256 integrity record, and fully
re-validates the result from disk before renaming it into place. It re-runs on
every boot after 81-claude_update.sh, since an apt upgrade ships a fresh
unpatched app.asar, and is marker-guarded so an unchanged app is a no-op.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(claude_desktop): harden the safeStorage hook against review findings
- Sweep stale .app.asar.addon-tmp.* from the shell hook. `timeout` kills the
patcher outright, so a run that hits the 120s cap never executes its own
cleanup; the live archive stays unpatched, so every later boot would retry
under a new pid and strand another archive-sized file.
- End the hook with an explicit `exit 0`. The logging `while` loop's status
became the script's status, so an empty last line could exit non-zero and
fail cont-init - the opposite of the documented "never block startup".
Both raised in review on #2922.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(webtop,webtop_kde,claude_desktop): give Selkies XDG_RUNTIME_DIR and the right WS port
Selkies panics with `RuntimeDirNotSet` right after its data websocket server
comes up, and binds that server on 8081 while nginx proxies 8082.
Upstream relies on s6-rc ordering: init-selkies-config publishes
XDG_RUNTIME_DIR and CUSTOM_WS_PORT into the s6 envdir, and svc-selkies is
started afterwards. ha_entrypoint.sh replaces s6-overlay and launches every
s6-rc.d run script in parallel with no dependency graph, so a longrun can
snapshot the envdir (with-contenv reads it once, at exec) before the oneshot
has written to it. Port 8081 in the report is the proof: that is selkies' own
default, not the 8082 init-selkies-config writes near the end of its run.
Only the webtop images carry PIXELFLUX_WAYLAND=true, which is why the missing
runtime dir reaches a Wayland socket bind there and not on claude_desktop.
20-folders.sh now exports both variables inside each run script, where no
start ordering can lose them, and 90-ingress.sh derives the nginx CWS
substitution from the same value.
Also correct the base image's $HOME/.XDG override where that write happens
rather than appending a correction to init-selkies-config: the oneshot
tolerance block appends `exit 0`, so on every boot after the first the
appended correction sat past it and never ran.
81-microsoft_edge.sh (webtop only), addressing the open review comments on
PR #2920:
- apt-get/dpkg failures no longer abort cont-init; each is guarded, warns and
exits 0, and apt acquisition is bounded so a stalled mirror cannot hang
start-up
- quote ${EDGE_VERSION+x}
- gate the wrapper swap on /helpers/microsoft-edge-stable still existing, so a
second run cannot move the installed wrapper aside with nothing to replace it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(claude_desktop): inject the run-script exports after the data_location rewrite
Two findings from an independent review of the previous commit.
The `s|$DEFAULT_LOCATION|$LOCATION|g` pass over the s6-rc.d run scripts is a
blind textual substitution, and it ran after the export injection. A
data_location *under* the image default -- /config/data_kde/foo on an image
whose default is /config/data_kde -- therefore rewrote the freshly injected
`export HOME=/config/data_kde/foo` into `.../foo/foo`. Injecting after the
rewrite instead of before removes the double substitution.
Quote the injected values so a location containing whitespace cannot produce a
broken run script. XDG_CACHE_HOME stays unquoted: the loop greps for it as its
idempotence marker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(webtop,webtop_kde,claude_desktop): address review feedback on the Selkies env fix
Refresh the injected run-script exports on every boot (codex, coderabbit).
They were guarded by the XDG_CACHE_HOME sentinel, so they were written once and
then survived in the writable layer. Raising PUID left every service exporting a
/run/user/<old-uid> the remapped abc user cannot use, and clearing a custom
CUSTOM_WS_PORT left Selkies on the old port while 90-ingress.sh moved nginx back
to 8082 -- with the envdir written at the same boot disagreeing with both. The
exports now sit in a marked managed block that is stripped and rewritten each
boot, mirroring how the ~/.bashrc block in the same script already works. The
sweep also removes the bare exports earlier versions wrote, so an upgraded
container cannot end up with two sets. No upstream run script in these images
sets any of the five, so it only ever removes our own.
Validate CUSTOM_WS_PORT once, where it enters (coderabbit). It is interpolated
into generated shell and into a sed replacement, so a non-numeric or
out-of-range value could corrupt a run script or the nginx config. 90-ingress.sh
repeats the check rather than trusting the envdir, so a malformed value cannot
reach the nginx config if 20-folders.sh did not get that far.
Download Edge to an mktemp path instead of a fixed /tmp/edge.deb (coderabbit).
This runs as root against a world-writable tmpfs, where a predictable name can
be pre-created as a symlink to redirect the download or swap what is installed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* refactor(webtop,webtop_kde,claude_desktop): share the Selkies startup scripts
All three add-ons are built on the LinuxServer Selkies base image and had
independently drifted copies of the same startup scripts. claude_desktop's
copies carry a set of fixes the two webtops never received, so make
claude_desktop the single source and symlink the shared scripts from
webtop_kde/rootfs (which webtop/rootfs already symlinks in full).
Shared by symlink: 20-folders.sh, 21-gpu_permissions.sh, 80-configuration.sh,
90-ingress.sh and the six etc/nginx/includes files.
Kept add-on specific: everything Claude-only stays in claude_desktop
(81/82/83/84 tool installs, 85-openbox_autostart.sh, defaults/, usr/local/bin,
svc-headroom), and everything webtop-only stays in webtop_kde (90-ssl.sh,
helpers/microsoft-edge-stable, and the new 81-microsoft_edge.sh).
To make the shared scripts add-on agnostic:
- 20-folders.sh derives its default data location from the home directory the
Dockerfile baked into the abc user instead of hardcoding /data/data. That
yields /data/data on claude_desktop and /config/data_kde on both webtops,
matching each add-on's previous behaviour exactly.
- The permission_mode: bypass root guard is skipped on add-ons that do not
declare that option.
- 80-configuration.sh falls back to pip when the image does not ship uv.
- The Microsoft Edge install moves out of 80-configuration.sh into a
webtop-only 81-microsoft_edge.sh, which also absorbs the ownership fixup
that used to run in 20-folders.sh before Edge was installed and so never
matched anything.
CI: the builder's symlink-resolution step made a single pass over a
pre-computed file list, so resolving webtop/rootfs (a directory symlink)
could copy the symlinks inside it verbatim, leaving links that escape the
webtop build context. Verified on this tree: the old loop leaves 10 dangling
symlinks under webtop/. Extract it to .github/scripts/resolve_symlinks.sh,
repeat until a pass finds nothing, and run it in the PR check too, which had
no resolution step at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: guard two startup aborts found in review
Both are crash paths in code added by this PR, not hardening:
- 20-folders.sh: getent exits 2 when the user does not exist, and under
bashio's `set -o pipefail` plus the script's `set -e` that aborts at the
assignment, so the "could not read abc's home" fallback below it was
unreachable. Same trap already documented in 21-gpu_permissions.sh.
Verified: without the guard the shell exits 2; with it the fallback runs.
- 81-microsoft_edge.sh: the ownership fixup lost the `-f` guard the original
had in 20-folders.sh. Without nullglob an unmatched /usr/bin/microsoft-edge*
reaches chown as a literal and `set -e` kills container startup. Now a
nullglob array with a warning when empty.
Also make resolve_symlinks.sh fail on a broken symlink instead of deleting it.
Dropping it silently yields an image that builds clean and misbehaves at
runtime; a red build is easier to diagnose. Verified both paths: the repo as-is
resolves to 0 symlinks and exit 0, and an injected broken link exits 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: address review comments on data-location default and Edge downloads
Two findings from CodeRabbit, both correctness rather than hardening:
- 20-folders.sh rewrites abc's home in /etc/passwd further down, so re-reading
it on the next boot returned the *previously selected* location as the image
default. On a restart that reuses the container's writable layer, clearing
data_location would strand the user on their old custom path instead of
restoring the built-in one. Cache the value in /etc/.addon_image_home, which
shares the writable layer's lifetime with the edit it compensates for: a
rebuilt or recreated container starts from a pristine /etc/passwd and
regenerates it. Simulated all three cases (first boot, reused container with
a rewritten passwd, recreated container) under `set -e` + `set -o pipefail`.
- 81-microsoft_edge.sh: both curl calls were unbounded, so a stalled
packages.microsoft.com would hang cont-init.d and with it the whole add-on.
Add --fail/--connect-timeout/--max-time and skip the install with a logged
error when version discovery or the download fails. The desktop is useful
without Edge; an add-on wedged before Selkies starts is not.
Not addressed, deliberately: escaping $LOCATION/$DEFAULT_LOCATION for sed, and
validating symlink targets in resolve_symlinks.sh. Both are hardening against
inputs that are not reachable in normal use, both predate this PR, and the
maintainer has asked to prioritise usability over that class of change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(seerr): re-encode ingress query strings for the OpenAPI validator
Searches through ingress fail with a 400 from the Seerr API, which the UI
reports as "500 Internal Server Error". The same searches succeed on the
directly published port 5055.
Supervisor proxies ingress traffic with `params=request.query`
(supervisor/api/ingress.py) - an already-decoded MultiDict - so aiohttp/yarl
re-encodes the query string on the way to the add-on. yarl's safe set is far
wider than the one express-openapi-validator accepts: it emits a space as "+"
and forwards ":", "/", "@", "!", "$", "'", "(", ")", "*" and "," bare, while
the validator tests the raw, still-encoded value against
RESERVED_CHARS = /[\:\/\?#\[\]@!\$&\'()\*\+,;=]/
and rejects the request. That breaks most real titles - "Monsters, Inc.",
"Ocean's Eleven", "Mission: Impossible", "Mamma Mia!".
An njs handler now re-encodes exactly those characters before proxying. This
is lossless: yarl only emits them bare when they were literal characters of
the value, since anything ambiguous arrives already encoded ("+" as %2B, "&"
as %26, "=" as %3D). "&" and "=" are left alone as the query string's own
separators, and the path is forwarded byte-for-byte.
Verified against a real express-openapi-validator over all 19 characters in
RESERVED_CHARS, and diffed byte-for-byte against the previous config across
representative traffic: only query-string encoding changes.
Fixes#2906Fixes#2646
* fix(seerr): ship the njs load_module snippet and encode "?" and ";"
Addresses two review findings.
1. The njs module never loaded. .templates/ha_automatic_packages.sh moves the
rootfs /etc/nginx aside to /etc/nginx2 before installing nginx, then does
`rm -r /etc/nginx` and restores the saved tree. That deletes the
load_module snippet nginx-mod-http-js installs, so nginx aborted with
`unknown directive "js_import"` and the service's finish hook would have
shut the add-on down - all ingress dead, not just search. The image build
still passed CI because it never starts nginx. The snippet now ships in the
rootfs so it survives the swap, under the package's own filename so the two
can never both be present and double-load the module.
2. "?" arrives bare from yarl and was not encoded. It slipped through testing
because the validator strips one occurrence with `qs.replace('?', '')`
before checking, so a single "?" passes by accident and only a second one
("Who? What?") returned 400.
NEEDS_ENCODING is now derived from the validator's RESERVED_CHARS minus the
"&" and "=" separators, rather than from the characters yarl happens to emit
bare today, so it stays correct if either side changes its safe set. The added
characters are a no-op for current traffic: yarl already percent-encodes
"# [ ] ;", so Seerr receives them encoded regardless.
Verified by replaying the build-time /etc/nginx2 swap and starting nginx, which
fails without the snippet and serves correctly with it; by sending every
RESERVED_CHAR bare; and by diffing forwarded bytes against master, unchanged at
2/20 with all query-parser edge cases identical.
* style(seerr): satisfy Codacy - double quotes in njs, changelog blank lines
Clears the 11 new Info-level Codacy findings: 9x ESLint 'quotes' in
njs/ingress.js and 2x markdownlint MD022/MD032 on the changelog entry.
No behaviour change; re-verified through the build-time /etc/nginx2 swap.
install_codex_cli was non-functional: every boot logged "Verified Codex
<version> installation failed; Codex is unavailable this boot" and no binary
was ever installed.
The download, its SHA-256 verification against the GitHub-published digest, and
the extraction all succeeded. The chain broke at the final step, which validates
the candidate binary by running --version as the abc runtime user: mktemp -d
creates its directory 0700 root:root, and abc cannot traverse a root-only
directory, so exec failed with "unable to exec: Permission denied" (exit 126)
before the binary could be moved into place. Because the whole chain is a single
&&-list, that surfaced only as the generic failure warning.
Fixed by making the staging directory traversable immediately after mktemp.
Nothing secret is staged there -- the public release archive and the extracted
binary, both world-readable upstream artifacts -- and the existing cleanup()
trap still removes the directory on exit. The validation deliberately keeps
running as abc rather than root, so the binary is exercised as the identity that
will actually run it.
Reproduced and verified on a live add-on container: the same probe goes from
exit 126 to success once the mode is widened, and the fixed script now completes
the install (codex-real 0.145.0 in place, wrapper on PATH, managed config
written, no staging leftovers).
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(portainer_be): add Portainer Business Edition add-on
Adds a new `portainer_be` add-on based on the existing Portainer (CE)
add-on, requested in #873.
Business Edition has no public GitHub release tarball like CE, so the
binary and web assets are pulled from the official multi-arch
`portainer/portainer-ee` image via a multi-stage build and placed under
/opt/portainer, mirroring CE's layout exactly. All runtime scripts,
nginx/ingress config, options schema, SSL and password handling are
unchanged from CE, so behaviour is identical apart from the edition.
Users obtain a free (up to 3 nodes) Business Edition license key by
registering with Portainer and enter it in the web UI on first launch.
- config.yaml: slug portainer_be, BE image name, BE description/name
- Dockerfile: multi-stage COPY from portainer/portainer-ee (no CE tarball)
- updater.json: dockerhub source tracking portainer/portainer-ee
- apparmor.txt: unique profile name (portainer_be_addon)
- CHANGELOG/README/DOCS: BE-specific, documents the license-key step
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(portainer_be): address PR review feedback
- nginx finish: move shebang to byte 0 (leading blank line prevented S6 from
recognising the interpreter, so the finish hook could fail to tear down the
supervision tree) [Codex P2]
- ingress: tighten CSP to `frame-ancestors 'self'` to match the adjacent
X-Frame-Options SAMEORIGIN; HA ingress embeds same-origin so the panel keeps
working [CodeRabbit]
- README: correct login note (password is the configured option value, never
printed to logs); drop MD012 consecutive blank lines [CodeRabbit]
- DOCS: fix "environement" -> "environment" typo [CodeRabbit]
Skipped: nginx SSL "idempotency" finding — /etc/nginx lives in the read-only
image layer and cont-init re-renders from the pristine template on every
container start, so in-place sed edits never accumulate or need restoring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* revert(portainer_be): keep CSP frame-ancestors * for ingress compatibility
Reverts the frame-ancestors 'self' change from the previous commit. The
wildcard is required for the Home Assistant ingress iframe to embed the
Portainer UI; tightening it breaks the ingress panel. Matches the CE add-on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* claude_desktop: add optional Codex CLI with device-code login and MCP bridge
Adds OpenAI's Codex CLI to the add-on as an opt-in fourth tool, so a Claude
session can delegate work to ChatGPT Codex as an independent second agent.
Install (install_codex_cli, default off): Codex is deliberately not baked into
the image -- its Linux binary is ~310 MB extracted, which is not worth carrying
in every installation for an off-by-default option, and updating it would then
need an add-on rebuild. A new 81-codex_cli.sh downloads the pinned static-musl
release (ENV CODEX_VERSION) into /data/codex/bin instead. That prefix is outside
$HOME on purpose: the managed-MCP merge treats any command under $HOME as
user-installed and refuses to manage it. Staging happens under /data rather than
the default /tmp, which here is a RAM-backed tmpfs mounted noexec -- holding
420 MB there during boot is a risk on a small host, and the binary could not be
verified there at all. The download fails open like the Claude Desktop update
check and validates the new binary by running it before replacing the old one.
Login (codex-login): Codex's default sign-in serves an OAuth callback on
localhost:1455 and expects a local browser, which cannot work in this add-on.
The helper runs `codex login --device-auth` instead -- the flow OpenAI documents
for headless machines -- printing a URL and one-time code to approve elsewhere.
It drops to the abc runtime user first so auth.json is not created root-owned.
MCP (codex mcp-server): registered through the existing managed-MCP merge rather
than a second copy of it, so it inherits that code's idempotence, no-clobber and
removal-when-disabled behaviour. A managed CLAUDE.md block explains when a second
agent is worth the round-trip.
New codex_sandbox_mode (default danger-full-access) is applied both as -c
overrides on the MCP command and as a managed block at the top of
~/.codex/config.toml; Codex's own Landlock/bubblewrap sandbox is unreliable
inside the container, which is already the security boundary.
Verified against the real 0.145.0 binary: tools/list returns `codex` and
`codex-reply` (hyphen, not the underscore upstream docs report), an invalid
-c sandbox_mode is rejected by name, the installer lifecycle behaves correctly
on re-run and on a bad pin, and the device code is flushed within seconds while
still polling, which is the non-TTY case that matters.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* claude_desktop: harden Codex subscription MCP setup
* claude_desktop: use runtime home for Codex login
* claude_desktop: reconcile runtime user home ownership
* claude_desktop: report verified Codex subscription setup
* claude_desktop: track latest Codex at runtime
* claude_desktop: document subscription-only Codex MCP
* claude_desktop: enforce Codex runtime identity
* claude_desktop: persist Codex in runtime home
* claude_desktop: prevent Codex auth override bypass
* claude_desktop: default Codex to workspace write
* claude_desktop: redact Codex authentication diagnostics
* claude_desktop: document safer Codex MCP defaults
* claude_desktop: validate Codex candidate as runtime user
* claude_desktop: align Codex sandbox fallback
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Bazarr's config.yaml carries a base_url key under general: (Bazarr's own
ingress path) AND a separate base_url under each configured *arr integration
-- radarr.base_url, sonarr.base_url, etc. -- which is how Bazarr reaches
those services at their own ingress-prefixed URL.
Every base_url sed in this addon was unscoped:
sed -i "s| base_url:.*| base_url: /$slug|" "$CONFIG_LOCATION"
sed applies s/// to every matching line in the file, not just the first, and
" base_url:.*" matches any 2-space-indented base_url line regardless of
which top-level section it's under. Since general.base_url, radarr.base_url,
sonarr.base_url etc. all sit at that same indent, this rewrote all of them to
Bazarr's own value on every container start (32-nginx_ingress.sh) and again
in the run script's fallback -- silently breaking Bazarr's configured
connections to Radarr and Sonarr.
Scope each sed to the general: block only, reusing the range idiom this file
already uses to scope the auth: block's type: substitution:
sed -i "/^general:/,/^[^ ]/{ s| base_url:.*| base_url: /$slug|; }" ...
Verified against a representative config.yaml (general/radarr/sonarr/subsarr
sections, including general:'s list-style provider entries) for all three
connection_mode branches plus the run script's fallback: general.base_url is
the only line touched in every case; radarr.base_url and sonarr.base_url
survive with their original values.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix Bazarr ingress: keep redirects relative so they aren't blocked as mixed content
Opening the Bazarr panel over HTTPS failed with:
Mixed Content: ... requested an insecure frame
'http://<ha_host>:8099/bazarr/'. This request has been blocked
Bazarr is Flask-based and answers /bazarr (the ingress entry, which has no
trailing slash) with a redirect to /bazarr/, made absolute against the Host
nginx sends upstream -- http://127.0.0.1:6767/bazarr/. proxy_redirect's
implicit "default" rule strips that prefix, which makes nginx treat the
Location as its own; the header filter then re-absolutises it as
$scheme://$host:$server_port/... Since $host is the browser's host forwarded
by the Supervisor and $server_port is the ingress port (8099, the Supervisor
default as no ingress_port is declared), the result is a plain-http URL on a
port the browser refuses to frame from an https page.
absolute_redirect off keeps the Location relative, and the proxy_redirect
rules re-prefix it with the ingress entry so it resolves under
/api/hassio_ingress/<token>/. The second rule also covers backends that emit
an already-relative Location; external absolute redirects match neither rule
and pass through untouched.
Verified against a local nginx with a stand-in backend: the pre-fix config
reproduces http://<host>:<ingress_port>/bazarr/ exactly, and the fixed config
returns /api/hassio_ingress/<token>/bazarr/ for both absolute and relative
upstream Locations while leaving an external redirect alone.
Also fixes the fallback base_url in services.d/nginx/run, which wrote it
without the leading / and so reintroduced the startup crash fixed in 1.5.6-4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Tighten base_url guard in nginx run script to require the leading slash
CodeRabbit review on #2910: the guard `grep -q "base_url.*$slug"` matches
both "base_url: bazarr" and "base_url: /bazarr" -- the .* swallows the slash
-- so it treated the malformed no-slash form as already correct and never
triggered the repair. Require the literal "base_url: /$slug" instead, so a
config missing the slash is actually detected and fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
The AI fix sweep has never completed a non-empty batch. claude-code-action
rejects track_progress unless the triggering event is pull_request, issues,
issue_comment, pull_request_review_comment or pull_request_review, and
daily_ai_fix.yaml only ever runs on schedule or workflow_dispatch. The step
died in input validation after 0.3s:
Action failed with error: track_progress is only supported for events:
pull_request, issues, issue_comment, pull_request_review_comment,
pull_request_review. Current event: workflow_dispatch
This went unnoticed because the step is gated on `count != '0'`, so every
run with an empty batch skipped it and reported green. Every run that
actually had issues to work through failed identically (runs 30265277395,
30011599875).
daily_ai_fix.yaml: remove it — no trigger of this workflow can ever satisfy
the constraint. Claude still comments per issue via gh, as issue-fix.md
instructs; only the run-level sticky comment is lost.
on_issue_approved.yaml: same latent failure on its workflow_dispatch path,
but the `issues` path is valid, so gate it on the event instead of dropping
it. The action defaults this input to the string "false", so the expression
result is a shape it already handles.
Co-authored-by: Claude <noreply@anthropic.com>
Every claude-code-action step except on_claude_mention.yml left the github_token
input unset, so the action fell back to the OIDC -> Claude App token exchange.
That exchange requires github.actor to have write access on the repo; on an
issues.opened event the actor is the outside reporter, so it always 401'd.
Classify is continue-on-error, so the job went green while doing nothing.
Setting the input short-circuits the exchange (action.yml maps it to
OVERRIDE_GITHUB_TOKEN; token.ts returns it before requesting OIDC).
GITHUB_TOKEN for the read-only classifier; AI_PR_TOKEN for the three that push
branches or open PRs, so the resulting PR triggers CI.
Also dropped the workflow-level id-token: write grant, which is unreachable once
github_token is set (CodeRabbit).
Keep stderr from the dotenv env-file validation so a failure shows which line
broke instead of just "not valid shell".
Co-Authored-By: Claude <noreply@anthropic.com>
build.json named the multi-arch collabora/code:latest for both architectures.
The builder never passes --platform -- it runs each architecture on its own
native runner -- so BUILD_FROM is the only thing selecting which binaries end
up in the add-on. That resolves correctly today only because the runner
architecture happens to match the target. Name collabora/code:latest-amd64 and
collabora/code:latest-arm64, which are published in lockstep with latest.
The official image sets file capabilities on two binaries, and COPY --from does
not carry extended attributes, so they arrived stripped:
coolforkit-caps cap_chown,cap_fowner,cap_sys_chroot=ep
coolmount cap_sys_admin=ep
Without them coolwsd starts and serves the admin console, but cannot chroot a
kit process, so no document ever opens. Reapply and verify them.
Replace the smoke test, which is why the build is currently red: coolwsd
refuses to run as root (exit 78), and --version does not exit anyway, since the
official entrypoint passes it to the long-running server. Check instead that
every binary resolves its libraries against the Debian base.
For ssl: true, hand Collabora the certificate copies in /etc/coolwsd rather
than /ssl. coolwsd runs as uid 1001 and a private key in /ssl is commonly
root-only, so it could not be read; the copies were already being made and
chowned, but nothing pointed at them.
Co-Authored-By: Claude <noreply@anthropic.com>
The base image already sets it, but hadolint cannot see an inherited SHELL
(DL4006), and the ldd linkage check relies on pipefail to notice a failing ldd.
Co-Authored-By: Claude <noreply@anthropic.com>
"coolwsd --version" does not short-circuit: it runs a full initialisation and
tries to create a jail, which fails in a build layer because the --o: paths the
launcher passes are absent, so it looked for /usr/bin/jails. It did prove the
binaries link against the Debian base, but booting Collabora is the wrong check
for a build step.
ldd asserts the same thing directly: every NEEDED library of coolwsd,
coolforkit-ns and coolmount resolves on this base. The loop uses an if rather
than "grep && exit 1" so that a clean result does not leave the loop with
grep's non-zero status and fail the good case.
Co-Authored-By: Claude <noreply@anthropic.com>
coolwsd refuses to start as root, so "coolwsd --version" failed the build even
though it proved what it was there to prove: the payload copied out of the
distroless image links and executes on the Debian base. Run it through su as
uid 1001, which is also exactly how 99-run.sh launches it.
Also drop --system from the useradd/groupadd, which only produced a
"uid 1001 is greater than SYS_UID_MAX 999" warning.
Co-Authored-By: Claude <noreply@anthropic.com>
build.json is where every add-on in this repo records the upstream image it
tracks, and it is what the updater bot rewrites. Hardcoding the Collabora tag in
the Dockerfile and putting the Debian base in build.json inverted that.
BUILD_FROM is now collabora/code:latest again and feeds the build stage the
payload is copied from; the Debian runtime base is named in the Dockerfile,
where it is an implementation detail of the add-on rather than the upstream
being tracked.
Co-Authored-By: Claude <noreply@anthropic.com>
dotenv_quote emits a double-quoted value, and both files are read back by
sourcing them from a shell: browserless_chrome does "set -a; . /.env" from its
Dockerfile, wger copies /.env into /data/env.sh as export lines, and
fireflyiii_data_importer relies on /etc/environment for cron. Inside double
quotes $ and ` are still special, and neither was escaped, so the value was
expanded rather than read literally:
pa$$w0rd came back as pa904869w0rd (the shell PID)
${HOME} came back as /root
back`tick` ran tick as a command and kept only "back"
Escape both, after the existing backslash doubling so the added backslashes are
not doubled in turn.
Extend --self-test to cover this path as well: it now writes an env file, checks
it parses, sources it and compares. An unescaped backtick makes the file
unparseable, which would take the sourcing shell down with it, so that case is
reported rather than left to abort the run.
Values containing a real newline remain out of scope: dotenv_quote writes them
as a literal \n, which a dotenv parser unescapes but a shell does not.
Co-Authored-By: Claude <noreply@anthropic.com>
Upstream rebuilt collabora/code as a Nix distroless image between 26.04.2.1.1
(2026-07-01) and 26.04.2.2.1 (2026-07-18): /bin and /sbin are empty and the
entrypoint is coolwsd itself. It can no longer serve as BUILD_FROM, since every
RUN, s6-overlay and bashio need a shell. The addon build has been failing since,
which is independent of the option fixes in this branch.
Collabora is now taken from the official image as a build stage and copied onto
ghcr.io/hassio-addons/debian-base:
- Only the payload is copied: /usr/bin/cool*, /usr/share/coolwsd, /etc/coolwsd,
/opt/collaboraoffice and /opt/cool. /etc and /nix are deliberately left out:
in the distroless image /etc/resolv.conf, /etc/hosts, /etc/passwd, /etc/group
and /etc/nsswitch.conf are symlinks into /nix/store, so importing them would
break DNS and wipe the base image users.
- coolwsd links only against glibc, libstdc++, libgcc and libm, and needs at
most GLIBCXX_3.4.22, so the Debian base satisfies it; the office engine
bundles its own cairo, fontconfig, curl, icu and fonts. Only openssl,
fontconfig, libcap2-bin, cpio, findutils and ca-certificates are installed.
- The uid/gid 1001 cool user is recreated, matching the official image.
- /start-collabora-online.sh is gone, so the addon ships an equivalent launcher
which also regenerates the self-signed certificate when ssl is off.
- The build now runs "coolwsd --version" so a payload that cannot link fails the
build instead of shipping an image that will not start.
Co-Authored-By: Claude <noreply@anthropic.com>
00-global_var.sh turns every addon option into an "export KEY='value'" block
that is injected at the top of cont-init scripts, service run scripts and the
shells, so the quoting has to survive an eval byte for byte.
Two defects sat on that path and hid each other:
- shell_quote replaced ' with '"'"' followed by a stray space, so a value like
"O'Brien pass" reached the application as "O' Brien pass". Passwords and any
option holding an apostrophe were silently wrong.
- shell_quote also doubled every backslash, and append_export then passed the
result through "awk -v", which runs its own escape processing and halved it
again. Backslash values (regexes, Windows and UNC paths) therefore survived
by accident, and fixing either half alone breaks them: dropping the doubling
leaves awk eating \t, \b and \\, while keeping it doubles the value for real
once awk is gone.
shell_quote now applies the POSIX rule (only ' needs escaping, as '\'') and
append_export appends the line directly instead of going through awk, which
also drops a full rewrite of the block per option.
Add a --self-test that builds a real export block and sources it, so the check
covers the whole path rather than either helper in isolation -- testing them
separately is exactly what let this pair stay wrong:
bash .templates/00-global_var.sh --self-test
Reported in #2768. dotenv_quote is left alone: its output is double-quoted, so
the doubling it does is correct there.
Co-Authored-By: Claude <noreply@anthropic.com>
Reported in #2768: several users could not get Collabora to talk to
Nextcloud, and the two options meant to configure it had no effect.
- 99-run.sh read a `domain` option that does not exist in the schema (the
option is `domain1`), and recent Collabora releases dropped the `domain`
environment variable entirely, so `domain1` was inert. It now maps to
`server_name` with a deprecation warning.
- `server_name` and `cert_domain` were in the schema but never passed to
Collabora. `server_name` is what fixes "Your browser has been unable to
connect to the Collabora server" behind a reverse proxy.
- `aliasgroup*` entries are matched by Collabora as regular expressions, so
a dot needs a single backslash. The README asked for two, which can never
match a real hostname. Values are now normalised (unescaped, escaped and
double-escaped all give the same correct pattern) and logged at startup.
Values containing other regex metacharacters are left untouched.
- Added `ssl_termination`, needed when `ssl` is false but Collabora is
reached over https through a reverse proxy, and `aliasgroup2`/`aliasgroup3`.
- `cert_domain` is a certificate common name, so it is a string, not a bool.
- Releases on CollaboraOnline/online are now Helm charts only, which had
renumbered the addon from 25.4.9.2 down to 1.3.0 and hid updates from the
Supervisor. Version tracking moves back to the collabora/code Docker Hub
tags.
Co-Authored-By: Claude <noreply@anthropic.com>
The HEALTHCHECK branched on "$ssl", but that variable is never present
in the container environment: Supervisor only injects the environment:
block from config.yaml (FB_BASEURL, PGID, PUID) plus TZ/SUPERVISOR_TOKEN.
The ssl option lives in /data/options.json and is read via bashio inside
cont-init, and HEALTHCHECK CMD is spawned by dockerd, so no export from
that shell can ever reach it.
The test was therefore always false and the healthcheck kept probing
http:// against the TLS listener, producing the
http: TLS handshake error ... client sent an HTTP request to an HTTPS server
spam reported in #2881.
Write the resolved protocol to /run/health_protocol from 99-run.sh and
read it back in the healthcheck, matching the pattern already used by
the gitea addon. Also corrects the 127.0.01 typo (missing octet).
Co-Authored-By: Claude <noreply@anthropic.com>
Opus 5 released 2026-07-24: same price as 4.8, both effort levels already
used here (xhigh in the tier-2 sweep, high in the tier-3 executor) remain
supported. Swap --model claude-opus-4-8 -> claude-opus-5 in the two Opus
steps; Sonnet-low tiers (classify, @claude, CodeRabbit follow-up) untouched.
Auto-merge for AI PRs was considered and declined -- keeping the existing
ready-PR-requires-manual-merge behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ingress responses were re-gzipped by nginx (default gzip_types includes
text/html), dropping Content-Length and forcing a chunked/streamed
response. That pushes both Supervisor and Core's ingress proxy out of
their buffered relay path into the streaming path, where an
aiohttp-side error surfaces to the browser as a 502 Bad Gateway even
though the addon's own nginx logs a 200. Disabling gzip on the ingress
server block keeps responses identity-encoded with an intact
Content-Length so the relay uses the simpler, more robust buffered
path.
Fixes#2766
- A (security): authenticate the ai-plan comment selector — only accept a
plan from a trusted author (OWNER/MEMBER/COLLABORATOR), so a reporter can't
inject a plan that executes on approval. Fail-safe to no-plan otherwise.
- B: clear ai:approved in the no-plan branch so a later real plan can be
re-approved (re-adding a present label fires no labeled event).
- C: claim ai:needs-info via a live re-check inside the serialized job so
queued reporter replies can't each run a classification; restore the flag if
no verdict was produced so the issue doesn't drop out of the retry path.
- D: gate workflow_dispatch of the tier-3 executor to github.actor == alexbelgium.
- E: exempt ai:approved from the stale bot.
- F: filter catch-up candidates server-side (search) instead of capping at the
100 newest issues, so older untriaged issues aren't silently missed.
- G: run ai_guard_paths.sh from the trusted default-branch copy, not the
in-tree copy a job could have modified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Redesign the AI issue-triage pipeline so it can fix confidently on its own,
ask for approval only when unsure, and always yield to manual actions — while
staying cheap and fast.
- Tier 2 (daily_ai_fix) becomes graded: high-confidence small fixes open a
READY-for-review PR; anything medium/large gets a full Opus-written plan
comment (ai:plan-pending) instead of a PR.
- New Tier 3 (on_issue_approved): maintainer adds ai:approved and the posted
plan is executed on Opus into a ready PR — immediate, zero cost until asked.
- New @claude interactive workflow (on_claude_mention): maintainer-only,
Sonnet-low, full precedence over the automated tiers.
- New CodeRabbit follow-up (on_pr_coderabbit): one-shot Sonnet pass that fixes
or replies to CodeRabbit's review of an ai-fix/* PR.
- Tier 1 self-healing: a reporter's reply to a needs-info request re-runs
classification exactly once (ai:needs-info); a daily catch-up re-dispatches
any issue that never got triaged.
- Extras: global kill switch (repo var AI_DISABLED), stale-bot exemption for
AI labels, shared ai_guard_paths.sh, track_progress on the Opus tiers,
CLAUDE.md documentation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified: gh issue edit --add-label=... is purely additive, and the
"owned" branch exited without touching labels at all — so a manual
workflow_dispatch re-triage that changes the verdict (e.g. a prior
addon-bug run now comes back needs-info, upstream-bug, or owned) left
the old ai-triage label in place, and daily_ai_fix.yaml would still pick
the issue up for the unattended fix pass despite the fresh verdict.
Both label-applying paths now also remove whichever of
ai-triage/ai:classified/ai:needs-human this run did NOT re-apply, as a
separate best-effort call that can't block the add. Simulated every
verdict/confidence transition, including the reported case (addon-bug ->
needs-info): ai-triage is now correctly removed instead of left stale.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two findings from Codex review of #2899, both live now that DRY_RUN is gone:
- A low-confidence `addon-bug` had ai:needs-human set by the low branch and
then ai-triage appended right back unconditionally, so it would enter the
unattended tier-2 fix pass despite Rule 2 saying an uncertain call should
only flag a human. Guard the ai-triage add on CONF != low.
- The label-create loop used `--force`, which updates existing labels; with
a model-supplied cosmetic label like `bug` that already exists, triage
recolored it to ededed as a side effect. Drop `--force` so existing labels
are left untouched (create fails harmlessly via || true) while missing
ones are still created.
tier 2's own label step keeps --force intentionally: its list is a fixed
set of workflow-owned ai:* labels meant to be gray, not model input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a workflow_dispatch trigger with a required `issue` input to
on_issues_ai_triage.yaml, so a specific (existing) issue can be triaged
on demand instead of only on issues.opened.
- Every issue-number reference now reads
`github.event.issue.number || inputs.issue`, so it resolves from the
event on the auto path and from the input on manual dispatch.
- The job's auto-trigger guards (skip the maintainer's own issues, honour
no-ai) are bypassed on workflow_dispatch — a manual run is a deliberate
override.
- The 60s ping_submitter wait is skipped on manual dispatch; there's no
race to lose against an issue whose ping already landed.
The input flows only through env vars and expression contexts, never
inline into a run: block, so there's no shell-injection surface; a bad
number just fails `gh issue view` cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A manual AI fix sweep failed with "Could not fetch an OIDC token. Did you
remember to add id-token: write to your workflow permissions?". The action
mints a GitHub OIDC token to authenticate the claude_code_oauth_token flow,
which needs id-token: write — absent from both jobs' permissions. Tier 2
failed on it now; tier 1 would have failed identically the first time it
ran live. Added to both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- on_issues_ai_triage.yaml: remove DRY_RUN entirely. AI_PR_TOKEN is now
configured, tier 1 has been watched in dry-run, and the toggle was
meant to be temporary scaffolding, not a permanent code path — verdicts
now apply labels/comments unconditionally.
- daily_ai_fix.yaml: fold ai:blocked into the existing "ensure labels
exist up front" step (renamed to reflect that). It was the one control
label neither workflow ever created: the forbidden-paths guard applies
it directly, and under set -euo pipefail a missing label there aborts
that step's loop entirely, silently skipping every remaining PR behind
the one that failed. No repo had hit this yet only because no label in
the ai:*/ai-* namespace existed at all before now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified each against current code before fixing; verification details are
in the PR description update.
Fixed:
- issue-classify.md: Rule 0 now requires the addon-submitter-ping marker to
appear in a comment headed "### @github-actions[bot]", not just anywhere
in a comment or issue body, so it can't be spoofed to suppress triage.
- ai_triage_context.sh: separator-insensitive addon-slug matching (fixes
"Calibre-web" -> calibre_web, and the earlier ImmichFrame -> immich_frame
miss) before falling back to substring matching; sparse-checkout failure
now surfaces "UNRESOLVED" into the bundle instead of silently proceeding
addon-less; duplicate-issue search excludes the issue being triaged from
its own candidate list.
- on_issues_ai_triage.yaml: persist-credentials: false on the read-only
tooling checkout (nothing in that job pushes); both actions pinned to
commit SHAs (Dependabot already covers github-actions repo-wide, and
on_issues_ai.yml already sets this precedent for another AI action);
model-supplied labels are now filtered to drop anything in the ai-*/ai:*
control namespace before merging with the deterministic ai-triage/
ai:classified additions, closing a path where a verdict could
self-trigger tier 2 regardless of its actual classification.
- daily_ai_fix.yaml: both actions pinned to the same commit SHAs;
workflow_dispatch inputs.issue/inputs.limit moved out of direct
${{ }} interpolation in the run: script and into env vars with numeric
validation (template-injection); Guard forbidden paths' PR listing
limit raised 50 -> 300 so it can't silently drop ai-fix/ PRs behind
unrelated open PRs before the branch-name filter applies.
Skipped (reasons in PR description):
- persist-credentials on daily_ai_fix.yaml's checkout: disabling it
breaks the only auth path git push currently uses, and the same
AI_PR_TOKEN is already directly readable via GH_TOKEN env by that job's
unrestricted Bash(git:*)/Bash(gh:*) tools regardless.
- Splitting untrusted AI analysis into a separate job from PR-creation/
write access: legitimate defense in depth, but a full architecture
redesign, not a minimal fix.
- Full hard-limit enforcement (config.yaml immutability, diff caps,
draft-only status) replicated at the workflow level: heavy lift: the
prompt already covers these as Claude-followed instructions; only the
protected-paths check is duplicated as deterministic enforcement,
which is the single highest-severity one to enforce outside the model.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that tier 2 runs daily instead of weekly, an issue left carrying
ai-triage after a sweep would be re-selected and fully re-analysed the very
next morning, before there's been a working day to review the first PR.
Nothing previously dropped issues out of the ai-triage backlog once handled.
- issue-fix.md: new hard limit 6 — relabel every issue as the last action
before moving to the next one. ai:fixed / ai:upstream / ai:needs-human
replace ai-triage depending on outcome.
- daily_ai_fix.yaml: pre-create the three replacement labels once, up front
(Claude never has to improvise a color or retry a missing-label error —
wasted turns multiplied by batch size). Add a "Guard against repeat
processing" step, same belt-and-braces pattern as the existing forbidden-
paths guard: force-relabel to ai:needs-human anything the batch still
finds carrying ai-triage after the run, independent of whether Claude's
own relabeling succeeded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Swap anthropic_api_key -> claude_code_oauth_token in both Claude steps,
reading CLAUDE_CODE_OAUTH_TOKEN from the CR_PAT GitHub Environment. Both
jobs now declare `environment: CR_PAT` so the environment-scoped secret
is reachable.
- Rename weekly_ai_fix.yaml -> daily_ai_fix.yaml (matches this repo's
daily_/weekly_ filename convention) and change its cron from
"0 3 * * SUN" to "0 3 * * *".
- on_issues_ai_triage.yaml already triggered on issues.opened; no schedule
change was needed there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Require repo-owner authorship or the `ai: fix-approved` label before Codex
runs on existing-add-on improvements, mirroring the bug path. Closes the
cost/abuse vector where any external user could auto-trigger expensive Codex
runs and draft PRs.
- Pin openai/codex-action to a commit SHA (was the mutable @v1 tag) since it
receives OPENAI_API_KEY.
- Add a catch-all failure reporter to publish_fix so apply/push/PR-create
failures notify the issue and swap labels instead of failing silently.
- Reject creation of new top-level files in the patch validator (previously
only new directories were blocked).
- Update triage comment wording and README to match the new gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Install tier-1 classifier (on_issues_ai_triage.yaml) and tier-2 fix sweep
(weekly_ai_fix.yaml) plus their helper script and prompts.
Pre-merge fixes from verification:
- Wait-for-ping sleep 150s -> 60s. on_issues_ping_submitter completes in
6-11s of job time across the last 10 runs; 60s covers runner-queue skew
with margin.
- Rule 0 rewritten to match the real ownership signal: ping_submitter posts
a github-actions[bot] comment with a stable marker
`<!-- addon-submitter-ping:<addon> -->`. Rule 0 now keys off that literal
marker instead of fuzzy prose, and guards against @<user> == alexbelgium.
- Silence one intentional shellcheck SC2016 (literal Markdown backticks) so
actionlint runs clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 21-gpu_permissions.sh: distinguish the expected getent exit 2
(unnamed GID) from other getent failures, warning instead of
silently masking unrelated NSS/database errors.
- SIGN_IN.md: state explicitly that --password-store=basic trades
away OS-backed at-rest protection.
Addresses CodeRabbit nitpicks from the PR review. The symlink-following
concern on 85-openbox_autostart.sh raised by CodeRabbit and Codex is left
open for maintainer review (see PR comment).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Session logs on a live install showed safeStorage unavailable (no keyring daemon
behind the forced gnome-libsecret store) causing recurring "sign in again" prompts,
and the resulting stale session parking the cowork/dispatch bridge — surfacing as
the desktop showing offline in the Claude app when opened from mobile first.
Switch to --password-store=basic (no daemon, no first-boot prompt) and sync the
persistent openbox autostart from the image on every boot so the fix reaches
existing installs, not just fresh ones.
Also fix 21-gpu_permissions.sh exiting 2 at boot: getent's expected exit-2 for an
as-yet-unnamed DRI group, combined with bashio's pipefail + set -e, skipped the
script's own unnamed-group fallback before it could run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the obsolete Yarn startup path with direct Linkwarden runtime commands, run the worker directly, and release add-on version 2.15.1.2 with backup guidance.
Codacy flagged 2 new markdownlint issues against the repo's 0-max gate:
the new heading and its following bullet list need a blank line between
them, matching the spacing every other CHANGELOG entry already uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI failed the build: apt refused to install docker.io because it pulls
in Debian's containerd/runc, which Conflicts with the containerd.io
already installed by the base image's own Docker-in-Docker support
(docker-ce + containerd.io from Docker's apt repo, toggled by the
pre-existing START_DOCKER env var — the reason that option existed
before this PR). qemu-system-x86 and ovmf are unaffected and stay.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds qemu-system-x86, ovmf, and docker.io (Bookworm main) plus virtiofsd
for sharing the workspace into the sandbox microVM. virtiofsd has no
Bookworm/backports package and its trixie .deb would GLIBC-mismatch the
runtime, so it's built from the pinned crates.io release in a dedicated
builder stage, mirroring the existing rtk/tokensave pattern.
Also updates the repo versioning convention in CLAUDE.md: local patch
counters should use a dot (X.Y.Z.N) instead of a hyphen (X.Y.Z-N), since
the hyphen form parses as a semver pre-release and Supervisor won't offer
the update.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The healthcheck CMD hardcoded http://, but 92-ssl.sh switches Caddy to
https://:8081 when ssl=true, so the probe would fail with the wrong scheme.
Select the scheme from the ssl env var and pass -k (the probe hits
127.0.0.1, not the certificate's real name).
Also replace `&>/dev/null` with `>/dev/null 2>&1`. HEALTHCHECK's shell form
runs under /bin/sh, which in this image is dash, not bash. Dash parses
`cmd &>/dev/null` as `cmd &` (backgrounded) followed by a separate no-op
`>/dev/null`, discarding curl's exit status entirely -- so the healthcheck
always reported healthy regardless of whether the WebUI actually responded.
Verified under dash directly: no listener -> exit 1, http server -> exit 0,
forced scheme mismatch -> exit 1, https with self-signed cert + -k -> exit 0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Standalone Docker (no Supervisor) disables nginx and serves the WebUI via
Caddy on 8081; nothing listens on port 80 when ssl=false, so the Docker
HEALTHCHECK failed and the container reported "unhealthy" although it worked.
Point HEALTH_PORT at 8081. HA mode is unaffected (Supervisor ignores Docker
health-checks).
Also drop rootfs/etc/sudoers.d/birdnet-abc-systemctl. A chroot boot harness
against the published image confirmed the standalone container already boots
and serves the WebUI (HTTP 200), and that nothing ever runs as the `abc`
user: pi has `NOPASSWD:ALL` and caddy has NOPASSWD:ALL via the pre-existing
010_caddy-nopasswd drop-in, so the abc rule was valid but inert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review feedback on #2888 without widening the revert scope
back to a moving target:
- prebuild-sanitize's own [nobuild] commit lands on top of HEAD_SHA
within the same run, so before..HEAD_SHA doesn't include it. It's
still this push's own fallout, not a neighboring push's, so capture
its SHA via job output and revert it explicitly (reverted first,
since it's on top).
- git rebase in the push-retry loop could conflict and get killed
silently by set -e, burning the remaining retry attempts. Abort the
rebase and fail loudly instead.
revert-on-failure re-fetched master and reverted before..HEAD, but HEAD
was the live tip of master, not the head of the failing push. When the
updater bot pushes one addon per commit in quick succession, a single
addon's build failure would sweep in every successful commit pushed
while the revert job was still running and revert them too.
Revert before..github.sha instead, and retry the push with a rebase in
case master moves again before we push.
Confirmed on the live run: a depth-1 (default) shallow checkout of the
merge commit truncates parent refs at that boundary commit entirely, so
git rev-parse HEAD^1 fails with "unknown revision" even though the merge
commit itself is checked out fine. Bumping this job's checkout to
fetch-depth: 2 pulls in both the merge commit and its two parents, making
HEAD^1 resolvable with a real tree to diff against.
github.event.pull_request.base.sha is fixed at the time the triggering
event fired. In a repo with frequent direct-to-master pushes, master can
advance between that event and job checkout, while the actions/checkout
merge commit (github.sha) is always built against the *current* master
tip. Diffing the stale event SHA against the live merge commit picked up
unrelated upstream commits — observed live on this PR: scrutiny and
scrutiny_fa showed up as "changed" and failed their changelog check, even
though this PR only touches the workflow file.
HEAD^1 is the actual base the checked-out merge commit was built from
(verified: parents are [live master tip, PR head]), so it can't go stale.
- Stop masking git fetch/diff failures with a blanket `|| true`. That
swallowed real errors (bad ref, network failure) into an empty
changed_addons result, the same silent-skip failure mode this PR
exists to fix. Capture the diff separately from the grep filter so
`|| true` only covers grep's expected "no match" exit code, while
fetch/diff failures now abort the job via the runner's default
`set -eo pipefail`.
- Write changelogs_files using the GITHUB_OUTPUT multiline delimiter
syntax instead of a plain `key=value` echo. A PR touching more than
one addon's CHANGELOG.md produced a value with embedded newlines,
which corrupts the output file under the single-line format.
Addresses review comments from coderabbitai and chatgpt-codex-connector
on PR #2887.
github.event.before is only populated on push events, but this workflow
triggers on pull_request, where it's empty. This made every git diff call
fail (fatal: ambiguous argument) and changed-addons resolve to [], so
addon linting, build testing, and changelog checks were silently skipped
on every PR regardless of what changed.
Use github.event.pull_request.base.sha instead, which is always populated
for pull_request-triggered runs.
The updater tracks elastic/elasticsearch GitHub tags and bumps as soon as a
tag appears, but the build pulled `FROM elasticsearch:<v>` (the Docker Hub
`library/elasticsearch` mirror), whose arm64/aarch64 tag lags hours behind
the release. This broke the aarch64 build right after "updated to 8.19.19"
(and would recur on every release): docker.io/library/elasticsearch:8.19.19
had only linux/amd64 at build time, no arm64.
Switch to Elastic's own registry, which publishes the multi-arch image
atomically with the GitHub tag the updater watches. The image is otherwise
identical (same User 1000:0, tini entrypoint, eswrapper cmd), so the
entrypoint patch and uid handling are unchanged, and the updater's blanket
version sed over the Dockerfile is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TokenSave repository indexing was silently processing zero paths on every
boot: bashio::config's read -d '' always returns non-zero, and process
substitution inherits the errexit that the bashio wrapper enables, so
`done < <(bashio::config 'tokensave_project_paths')` died before printing
anything. Fixed by capturing with command substitution first.
Also a simplification pass over the startup scripts — three duplicated
settings.json hook mutators collapse into one helper, two duplicated
CLAUDE.md guidance managers collapse into another, 81-tokensave_repositories.sh
merges into 82's loop, and several dead code paths (apk/pacman installers,
pip3 fallback, the /tmp/claude-desktop-command indirection, a stale
auto_update option, a redundant chown pass) are removed. No change to what
gets configured — Headroom/RTK/TokenSave still auto-apply to every session
type. tokensave bumped 7.2.0 -> 7.4.0 (rtk and headroom-ai were already at
latest). See CHANGELOG.md for full detail.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
build.json used the rolling ghcr.io/linuxserver/baseimage-selkies
:*-debianbookworm tag, which LinuxServer rebuilds continuously (and
which itself installs selkies "latest" at base-build time). The
desktop/stream runtime could therefore change under the add-on with no
change to its own files.
Pin both architectures to the current version (45960cc3-ls113). The
versioned tags resolve to exactly the image the rolling tag points at
today (amd64 sha256:6a4d5154..., aarch64 sha256:90914dfd...), so this is
a no-op for the current build but makes future builds reproducible; the
base now only moves when this value is bumped deliberately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iAzC9599AaN45Ko6RXtSW
The Selkies web client stayed on "waiting for stream" and Claude Desktop
never appeared, with "libEGL warning: failed to open /dev/dri/card0:
Permission denied" in the log.
The LinuxServer base image adds the desktop user (abc) to the /dev/dri
render group in its init-video s6 oneshot, but that oneshot is not a
dependency of svc-xorg/svc-selkies/svc-de. On Home Assistant those
long-running services start (via s6-setuidgid abc) before init-video has
added abc to the render group, so Xorg/Selkies/pixelflux open the render
device without permission and the video pipeline never produces frames.
Prepare the exposed DRI nodes in a new 21-gpu_permissions.sh cont-init
script: cont-init.d completes before any s6-rc service starts, so abc is
added to each node's owning group (and the node is made world read/write
as a timing-independent fallback) in time for the graphical services.
Best-effort and a no-op when no GPU is exposed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iAzC9599AaN45Ko6RXtSW
Two review findings on PR #2871:
- coderabbitai: MIN_CHARS/MIN_SAVED_TOKENS parsed with a bare int() at module
import time, before any try/except could catch a bad value — a malformed
env_vars passthrough would crash the hook on every matched tool call instead
of failing open as documented. Wrapped in _int_env() with a safe fallback.
- chatgpt-codex-connector: Glob and Grep (files_with_matches mode) return a
`filenames: string[]` field per the CLI's own output schema, which the
hook's string-only candidate scan never touched — large file listings, the
exact case named in the CLAUDE.md guidance this add-on installs, passed
through uncompressed. Verified empirically that routing such an array
through compress()/SmartCrusher (as done for JSON-blob string fields)
silently subsamples it — 600 paths collapsed to ~15 with no visible marker,
unsafe for paths the model needs to act on individually. Added a separate
deterministic path: arrays over ARRAY_KEEP (40) entries are truncated in
order with one labeled marker entry appended, full array recoverable from
the CCR store by hash. Verified round-trip on Glob- and Grep-shaped
payloads (600 and 200 entries); confirmed order preservation and that
small arrays still pass through untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Desktop-spawned Claude Code sessions (cowork/dispatch) pin ANTHROPIC_BASE_URL
to the production endpoint (headroom #869), so the transparent proxy never
sees their traffic and compression depended on the model voluntarily calling
the headroom MCP tools. A managed PostToolUse hook now compresses
Bash/Grep/Glob/WebFetch outputs over ~4000 chars in every session type with
Headroom's rule-based pipeline, swapping them in via
hookSpecificOutput.updatedToolOutput with a retrieval marker. Originals live
in the shared CCR SQLite store, so mcp__headroom__headroom_retrieve recovers
them; savings land in the durable ledger (client "posttooluse-hook").
The hook fails open, never compresses stderr, skips sub-50-token savings, and
registers idempotently in ~/.claude/settings.json only after a --self-test
gate; new headroom_auto_compress option (default true) removes the managed
entry cleanly when disabled. Measured: 10781->2964 tokens (73%) on a
representative HA states dump, ~1.7 s hook overhead, <100 ms pass-through.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three add-on runtime-environment bugs, all found while investigating a Headroom
dashboard stuck at 0 gain.
Headroom MCP server had no HF_HOME. 1.27 fixed the Kompress model cache for the
svc-headroom proxy longrun by exporting HF_HOME there, but the MCP server is a
different process: Claude Desktop and Claude Code spawn it from the registered
mcpServers entry, so it never saw that export and kept resolving the HuggingFace
cache to ~/.cache, which this add-on symlinks to tmpfs. Its Kompress ML path
therefore never found the model, re-downloaded ~270 MB into tmpfs on every boot,
and lost it on the next one -- headroom_compress returned router:noop (output
unchanged) for prose and other unstructured content. Rule-based compression
(SmartCrusher, structured tool output) was unaffected and worked throughout,
which is why the failure only showed on some payloads. Carry env.HF_HOME on the
managed headroom entry in both claude_desktop_config.json and ~/.claude.json.
~/.gitconfig was written as root and left unreadable by abc. `git config --global`
ran as root during init and rewrites the file on every start, so 20-folders.sh's
earlier recursive chown never stuck to it; .config/gh survived abc-owned only
because the "already authenticated" branch skips rewriting it. The user that
actually runs git, gh and Claude could not read its own committer identity or the
gh credential helper: every commit failed with "Author identity unknown" and
authenticated pushes fell back to prompting. Run the git/gh setup as abc via
s6-setuidgid, matching 81-tokensave_repositories.sh, and reclaim root-owned
copies left by earlier versions before writing.
~/.bashrc accumulated stale HOME/FM_HOME exports across data_location changes.
The idempotency guard only tested for the current $LOCATION, so changing the
option and later changing it back appended a second block while leaving the first,
and the last one written won for every interactive shell. $HOME then pointed at a
directory the add-on no longer manages, so anything resolving config through it
read the wrong path -- `headroom doctor` reported "claude: not routed (no
~/.claude/settings.json)" against a correctly routed install, and bare `headroom`
invocations created a stray .headroom tree under the old location. Make the block
marker-delimited and rewrite it from scratch each boot.
Verified on a running add-on: headroom_compress now reports 1909 -> 1122 tokens
(41.2%, router:mixed) through the live MCP server; `headroom doctor` reports
"claude: routed via /data/data/.claude/settings.json"; and git commits work as abc
without a repo-local identity override.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex flagged that the synchronous pre-warm (up to 300s) blocked the proxy port bind, defeating the terminal wrapper's health-check fallback and, combined with the new settings-managed ANTHROPIC_BASE_URL, could send terminal Claude Code launches to a proxy that was not listening yet.
The proxy already has a non-blocking answer to a cold cache: content_router.py calls compressor.ensure_background_load() on first use and passes the request through uncompressed until the model lands, so the port always binds immediately. Persisting HF_HOME alone is enough -- Kompress self-heals within the first couple of requests on a cold boot and loads instantly (eager preload) on every boot after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
flock -n silently skipped TokenSave prep on lock contention with no retry until next restart; wait up to 60s instead (kernel releases flock the instant its owner exits, so only a truly stuck lock can't clear within that window).
Quarantine fired on any sync failure after 3 retries, including transient causes (permissions, disk full, missing binary) unrelated to corruption. Now only quarantines when stderr names actual database corruption (SQLite malformed/not-a-database/disk-image wording); other failures leave the index untouched and retry next start.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Headroom kept reporting zero savings for two independent reasons:
1. Desktop cowork/local-agent-mode sessions never reached the proxy.
Desktop spawns its bundled Claude Code binary at an absolute path
(bypassing the PATH wrapper) with ANTHROPIC_BASE_URL pinned to the
production endpoint (headroom #869). Manage env.ANTHROPIC_BASE_URL
in ~/.claude/settings.json instead — Claude Code writes settings
`env` entries over the inherited environment at startup, and cowork
sessions load user settings. Managed-value semantics: only set or
remove the variable when absent or equal to the add-on-managed proxy
URL, so a user-customized endpoint is never clobbered.
2. Even proxied traffic compressed nothing (175 requests, 0 saved).
The proxy's startup preload is cache-only, but the HF model cache
defaulted to ~/.cache -> tmpfs, wiped every restart, so the Kompress
ONNX model and its separately fetched ModernBERT tokenizer were
never cached and the engine idled in "deferred" mode forever
(misleadingly logged as "Kompress: not installed"). svc-headroom now
sets HF_HOME to persistent ~/.headroom/hf and pre-warms the cache
once at startup, bounded at 300s so an offline install still starts
the proxy in pass-through mode and retries next boot. The proxy
extra's ONNX runtime suffices — the multi-GB PyTorch [ml] extra is
deliberately not installed.
Verified live: proxy logs "Kompress: ENABLED (ModernBERT token
compressor)" after restart, and a terminal `claude -p` round-trip
increments the proxy's api_requests counter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bashio::config prints its result via printf without a trailing newline, so
a plain while-read loop drops the last (often only) configured project path
and no TokenSave repository would be initialized. Use the
read || [ -n ... ] idiom in the three path loops so the final unterminated
record is still processed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KN8i26JrKSaBdvTrpVEyQ6
Codacy flagged the `A && B || continue` short-circuit pattern in the three
tokensave path loops; rewrite it as an explicit if so the fallback can never
run when both tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KN8i26JrKSaBdvTrpVEyQ6
Version 1.25 chowned the data location to a hardcoded 1000:1000 but never
mapped the shared abc desktop user to that UID: during cont-init abc was
still the image default (911), so TokenSave, RTK, nginx, PulseAudio, the
Mesa shader cache, and Claude Desktop itself failed with Permission denied.
The base image's init-adduser then remapped abc to root mid-startup because
it reads PUID/PGID from add-on options (fallback 0) where they were never
defined, which additionally made Claude Code reject bypass mode.
- Add PUID/PGID add-on options (default 1000:1000) and remap abc to that
identity at the top of 20-folders.sh, before any ownership pass and
before any service resolves the user; pin init-adduser to the same
effective identity so it cannot diverge mid-startup.
- In permission_mode bypass, fall back from a configured PUID 0 to UID
1000, since Claude Code refuses bypass permissions as root.
- Replace the nonexistent bashio::config.array (only present in the repo's
standalone bashio) with bashio::config in the TokenSave repository setup,
tools configuration, and claude-tools-doctor.sh.
- Chown managed Claude configuration files to the effective abc identity
instead of the raw configured PUID/PGID, which fell back to root.
- Pre-create /tmp/.X11-unix (sticky 1777) so Xorg running as non-root abc
on the tmpfs /tmp can create its socket.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KN8i26JrKSaBdvTrpVEyQ6
The startup indexer chose init vs sync purely on whether
.tokensave/tokensave.db existed, so an interrupted init or a hard
add-on stop mid-write could leave a partial or malformed SQLite graph
that every subsequent boot then ran `sync` against, failing (and
staying broken) forever.
Prepare each configured repo defensively instead:
- serialize the operation under a startup-scoped flock so an
overlapping restart or a mid-boot git post-commit/checkout sync hook
can't write the same DB concurrently;
- refresh an existing index with a retried incremental sync, since
SQLITE_BUSY from lock contention is transient, not corruption;
- quarantine a genuinely unreadable index (sync still failing after
retries) or a half-written one (an interrupted init, detected via a
sentinel file) to .tokensave/corrupt-<timestamp>/ and rebuild from
scratch, so the graph self-heals rather than propagating corruption.
All file operations run as the abc runtime user because the repo
.tokensave directory is outside this script's final ownership pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The /usr/local/bin/claude wrapper hardcoded HEADROOM_BIN as
/usr/local/bin/headroom, but the binary is installed at
/usr/bin/headroom (symlink to /lsiopy/bin/headroom). The -x check
therefore always failed and terminal Claude Code sessions never
routed through the Headroom proxy at 127.0.0.1:8787.
Resolve the binary with "command -v headroom" instead; an empty
result still fails the -x check safely and falls back to launching
Claude Code directly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Check ha_mcp_token before SUPERVISOR_TOKEN: this add-on always sets
homeassistant_api, so the admin-equivalent Supervisor token was always
present and silently shadowed a user's deliberately scoped-down
ha_mcp_token, defeating the documented scoping path (Codex P1).
- Make ha-cli itself refuse to run when enable_ha_api_helper is false,
instead of only removing the CLAUDE.md guidance text — disabling the
option now actually disables the helper (Codex P2).
- Normalize HA_BASE_URL to include /api when the user omits it, so REST
calls don't 404 (CodeRabbit).
- Read/write CLAUDE.md with explicit UTF-8 in the ha-api-helper removal
block, matching the emoji/special characters Claude tends to write
there (CodeRabbit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ship a `ha-cli` command that lets Claude configure Home Assistant
(automations, scripts, scenes, helpers, dashboards, registries, service
calls) through the Home Assistant Core API instead of a /config filesystem
mount, so secrets.yaml and other add-ons' credentials stay out of reach.
It authenticates automatically with the add-on's SUPERVISOR_TOKEN via the
Supervisor Core-API proxy (no token setup), with optional HA_TOKEN /
ha_mcp_token overrides for a scoped Home Assistant user. A managed guidance
block in ~/.claude/CLAUDE.md tells Claude Code to use the helper and to
confirm before writes. Gated by the new enable_ha_api_helper option
(default on). Adds the websockets dependency for the WebSocket subcommand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Home Assistant's MCP Server integration serves stateless Streamable HTTP at
/api/mcp; mcp-proxy defaults to SSE, so the previous registration (SSE at
/mcp_server/sse) could never attach. Pass --transport=streamablehttp
--stateless and default ha_mcp_url to /api/mcp.
Match managed MCP entries by binary basename outside $HOME so a base-image
path change still updates them, while user-installed binaries under $HOME
remain untouched. Resolve tokensave via command -v like the others.
Write Claude config files 0600 (they hold the HA long-lived token in clear
text) and scope the build-time chmod +x pass to the shipped script dirs.
Docs: dashboard reachability wording, stale /config/data HOME, and the
custom-script filename (claude_desktop.sh, per the $slug.sh template).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Legacy monolithic builder was removed upstream in 2026.06.0; this repo
already uses the modular build-image action, so only the pin moves
(2026.03.2 -> 2026.06.0). Action inputs/outputs unchanged upstream —
drop-in compatible. Also strips trailing whitespace at EOF (yamllint).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove standalone web terminal (ttyd/tmux service, port 7681, terminal_*
options, claude-direct/claude-headroom wrappers). Claude Code stays and
powers Desktop cowork/dispatch sessions.
Fix Headroom dashboard: proxy bound 127.0.0.1 only, mapped port 8787
refused external connections; bind 0.0.0.0.
Fix dispatch/sign-in persistence: gnome-keyring package was never
installed, so the autostart keyring bootstrap no-oped and Electron
safeStorage was unavailable (allowlist cache + auth grants lost).
Add tokensave MCP (pinned 7.2.0, source-built like RTK), real HA MCP
bridge via mcp-proxy (enable_ha_mcp + ha_mcp_url/ha_mcp_token), uv for
additional_pip. Register managed MCP servers in Desktop and Claude Code
configs without clobbering user entries. Drop orphan options
ha_smart_context/dangerously_skip_permissions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The published 8.19.18 images are correct (verified: real ES 8.19.18,
run as root, migration + privilege-drop in place). But some upgrades
were left running a stale cached Elasticsearch 7.17.9 image that starts
as uid 1000, producing the reported "mv: cannot move '/data/config' ...
Permission denied" and "AccessDeniedException[.../data/nodes/0]".
- Bump version to 8.19.18-3 to force Home Assistant / Docker to pull a
fresh image tag instead of reusing the cached one.
- Add an explicit root check on the first init pass (before any move or
chown) so a non-root start fails with a clear, actionable message
instead of the cryptic permission error, and wrap the config-archive
mv with the same clear failure. The re-exec'd uid-1000 pass returns
before this check, so the privilege drop still works.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Elasticsearch refuses to bootstrap as root ("can not run elasticsearch
as root"). The previous fix in this PR kept the container root at
runtime to fix the /data permission failure, but never dropped
privileges again afterward — unlike 7.17.9, whose own entrypoint used
`chroot --userspec=1000:0` before launching Elasticsearch, the upstream
8.x entrypoint no longer does that. So every start, fresh or upgrade,
would fail once addon-init.sh's setup finished.
Fix: after addon-init.sh completes its root-only work (migration guard,
data/config relocation, chown), it re-execs the entrypoint itself as
uid 1000 via `chroot --userspec=1000:0 / ...` — the same mechanism
7.17.9 used, and exactly what the add-on's AppArmor profile already
grants (sys_chroot, setuid, setgid). On the re-exec'd pass the script
returns immediately (guarded by an exported sentinel) so none of the
setup work repeats; exported env vars (env_vars, the security default)
survive the exec normally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reported: on upgrade from an existing 7.17.9 install, the add-on failed
to start with "mv: cannot move '/data/config' to
'/data/config.bak-7.17.9': Permission denied".
Root cause: a previous fix in this same release restored `USER 1000:0`
at the end of the Dockerfile to match the upstream base image's own
final USER directive. But the upstream 8.19 entrypoint no longer drops
privileges itself (confirmed: it execs elasticsearch directly, no
gosu/chroot dance), and existing installs have /data owned by root
(7.17.9's default image variant runs fully as root). A non-root
container can never chown or move that data.
Revert to root at runtime, matching how this add-on always ran and
matching its own AppArmor profile (chown, setuid, setgid, sys_chroot,
mount capabilities — all meaningless for a non-root process anyway).
Root stays required for the build-time entrypoint patch too, unchanged
from the prior fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
curl -f treated any 4xx as failure, including 401. Users who enable
xpack.security (a supported override via ES_SETTING_XPACK_SECURITY_ENABLED)
got 401 on the unauthenticated healthcheck request, so the version marker
was never written and every restart re-logged the one-time migration
notice. Read the HTTP status directly and accept 200 or 401.
Reviewed and skipped: the cp -rn merge-into-existing-directory concern —
verified empirically (both locally and against the image's Debian/GNU
coreutils base) that GNU cp merges correctly into a pre-existing
same-named destination without nesting; the existing test suite already
exercises this exact path (legacy 7.x data preserved during migration).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- The 8.19.18 base image ends the build as USER 1000:0 with a
root-owned, read-only (0555) entrypoint, so the sed patch and later
chmod/package-install steps failed. Switch to root for the build and
restore the Elasticsearch user before runtime.
- Tighten the env_vars name check to require a leading letter/underscore
(shell identifier rules) instead of allowing a leading digit, which
made `export "$name"=...` fail and abort startup under `set -e`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The add-on reported version 8.14.3 but the shipped image was still
Elasticsearch 7.17.9 — the Dockerfile BUILD_UPSTREAM was never bumped,
and the builder uses that ARG. The homeassistant-elasticsearch
integration requires 8.14+, so configuration failed (#2849).
- Upgrade to Elasticsearch 8.19.18 (latest 8.x; 9.x cannot read indices
created in 7.x)
- Add automatic 7.x -> 8.x data migration with a guard that aborts on
unsupported paths (downgrade, or data more than one major behind).
The version marker is written only after ES answers on 9200, so a
failed upgrade never masks the true on-disk data lineage
- Default xpack.security.enabled=false to preserve plain-HTTP behavior
the HA component expects; override via ES_SETTING_XPACK_SECURITY_ENABLED
- Fix the env_vars option, which never worked (the image has no
s6-overlay, so the cont-init stack never ran)
- Remove the ingest-attachment plugin install (bundled since ES 8.0,
which broke the 8.x build)
- Replace line-number-based entrypoint patching with a proper init
script sourced via a pattern-anchored injection
- Add updater.json pinned to the 8.19 line to prevent version drift and
accidental 9.x jumps
Fixes#2849
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The build-time hook that removes `sudo` from the BirdNET-Pi scripts was
injected at line 2 of newinstaller.sh, i.e. before the repo is cloned, so it
was a no-op and `sudo` remained in 25 scripts. Outside Home Assistant this
breaks every script invoked by a non-sudoers user (php-fpm's `caddy` user for
the WebUI System Controls, or `abc`) with "X is not in the sudoers file",
which is what prevented standalone (no-Supervisor) operation. Move the strip
to run after the installer clones the repo. Also drop the unused (and, in this
fork, incorrect) `$my_dir` -> `/config` rewrite.
Additionally create /run/php before starting PHP-FPM: it is normally created
by systemd-tmpfiles, which does not run in this container, so on a fresh/tmpfs
/run the socket cannot be bound and the WebUI never comes up.
Verified against the published 2026.07.10 image: the WebUI serves HTTP 200 and
restart_services.sh runs cleanly as the non-sudoers `caddy` user.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Creates a new Home Assistant add-on that installs from zach7036's BirdNET-Pi-Enhanced-Version fork instead of the Nachtzuster/alexbelgium fork. Leaves the existing birdnet-pi addon untouched.
Key changes from birdnet-pi:
- Installer repointed to zach7036/BirdNET-Pi-Enhanced-Version/main/newinstaller.sh
- Removed alexbelgium repo rename sed (zach7036 fork already clones itself)
- Removed merge_open_prs PR-merging step (install stable main)
- Updated slug to birdnet-pi-zach, image to birdnet-pi-zach-{arch}
- Updated README/docs upstream description
- Unique apparmor profile name
The generic Dockerfile patches (strip sudo, remap my_dir, systemctl shims) apply unchanged to the new fork since it follows standard BirdNET-Pi layout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Gate the headroom section on install_headroom (bashio::config.true), same
as svc-headroom, instead of only checking whether the binary is on PATH
(it's pip-installed unconditionally at build time, so it's always
present). have_headroom is now just a secondary availability guard.
Fixes noisy/stale headroom output when the option is disabled.
- Switch the shebang to with-contenv bashio so HOME comes from the s6
envdir instead of a hardcoded /data/data. 20-folders.sh only rewrites
/data/data references under /defaults, /etc/cont-init.d,
/etc/services.d and /etc/s6-overlay/s6-rc.d — not /usr/local/bin — so
a custom data_location previously left this script reading/writing the
wrong home directory.
Verified live: real cron firing confirms with-contenv correctly resolves
HOME and bashio::config from the s6 envdir when invoked by cron; isolated
gating-logic test covers all four enabled/binary-present combinations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add claude-gains-report.sh + /defaults/crontabs/root: an hourly rtk gain +
headroom savings snapshot to the add-on log (heartbeat + accumulated gains).
- Add svc-headroom longrun: run the headroom proxy as a local MCP backend
(127.0.0.1:8787, no client routing) so headroom_compress/headroom_retrieve
actually store/retrieve content and record savings. Backend only, so the
Claude Desktop app's traffic is untouched (headroom #869).
- Nudge headroom tool usage via a managed, idempotent CLAUDE.md block.
- Bump version 1.6 -> 1.7 and update CHANGELOG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The removal block's json.loads() was wrapped in a bare try/except that set
data = None on any parse error, then silently no-op'd (isinstance(data, dict)
is False) with exit 0 -- so a malformed claude_desktop_config.json meant the
headroom entry was never removed and bashio::log.warning never fired.
Drop the try/except so parse errors propagate naturally and the script exits
non-zero, matching the sibling rtk removal block's existing convention and
triggering the bashio::log.warning fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw
headroom's proxy/wrap routing relies on ANTHROPIC_BASE_URL, which Claude
Desktop force-overrides (headroom #869), so it cannot transparently compress
the desktop app. Register headroom's MCP server (headroom mcp serve) in Claude
Desktop's claude_desktop_config.json instead -- the supported integration --
exposing the headroom_compress/headroom_retrieve/headroom_stats tools inside
the app. The plain desktop launch is left untouched.
The JSON merge is idempotent, preserves any other MCP servers and top-level
keys, backs up malformed config, and removes the entry again when
install_headroom is disabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw
headroom "wrap" only accepts coding-agent CLIs (claude, codex, cursor, ...)
with agent args after "--", so it cannot wrap the claude-desktop Electron app.
Route the launch through headroom's standalone compression proxy instead: the
command file now starts "headroom proxy" and points Claude Desktop at it via
ANTHROPIC_BASE_URL, while the autostart still falls back to a plain launch if
that fails.
Claude Desktop currently force-overrides ANTHROPIC_BASE_URL (headroom #869), so
transparent compression only engages once upstream adds Desktop support; until
then the app launches normally. Update the README/CHANGELOG accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw
install_headroom defaults to true and headroom is baked into the image, so
82-claude_tools.sh rewrote the desktop launch to "headroom wrap claude-desktop
--no-sandbox ...". headroom "wrap" only accepts coding-agent CLIs (claude,
codex, cursor, ...) and expects agent arguments after a "--" separator, so
"claude-desktop" plus the Electron flags is an invalid wrap target/options.
The autostart ran only that command with no fallback, leaving the desktop app
unlaunched for default users.
- Keep the plain claude-desktop launch; just expose headroom with a usage hint
(matches the documented "make headroom available and log a usage hint").
- Harden autostart to fall back to the default launch if a custom/wrapped
command fails to start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw
Startup crash loop ("All subprocesses terminated. Exiting."):
- Drop the misplaced shm_size env var (Home Assistant ignores it) and run Claude
Desktop with --disable-dev-shm-usage so the Electron renderer survives the default
64 MB /dev/shm.
- Make the Selkies desktop init oneshots (init-video, init-selkies-config) tolerant so
a partially-permitted device/permission op in the HA sandbox no longer fails add-on
bringup and crash-loops the container.
- Pre-create /tmp/selkies_js.log so the base image's "chmod 777 /tmp/selkies*" calls
never fail on an empty glob; reconcile XDG_RUNTIME_DIR to the tmpfs runtime dir.
Sign-in persistence ("Your sign-in won't be saved on this device"):
- Bundle gnome-keyring/libsecret-1-0/dbus-x11 and start an unlocked Secret Service in
the desktop session, launching with --password-store=gnome-libsecret. The keyring DB
lives on persistent storage (/config/data), so the session survives restarts.
Docs: add SIGN_IN.md documenting both problems and the (deferred) in-desktop browser
option needed to complete the initial OAuth login.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XycVEAj8oZgmQszn9gdQ2E
- Exit with a clear error if the /app/config migration fails, rather than
continuing to run against the non-persistent source directory (user
changes would otherwise silently vanish on the next restart).
- Exit with a clear error if /app is missing instead of swallowing a
broken upstream image layout with `|| true`.
Addresses further review feedback from PR #2816.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZTfSk3GQRU7oD85TnjsmW
- Generate ENCRYPTION_KEY as 64 hex chars instead of base64: upstream reads
it via Buffer.from(ENCRYPTION_KEY, 'hex') for aes-256-cbc, so a base64
value silently broke Spotify token encryption for anyone leaving the
option blank (the default path).
- Store the key without a trailing newline and chmod 600 it.
- Only delete /app/config after a successful copy into /config, so a
failed migration (permissions, disk full) can't silently wipe the
upstream default config.
Addresses review feedback from PR #2816.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZTfSk3GQRU7oD85TnjsmW
New add-on wrapping the upstream jjdenhertog/spotify-to-plex Docker image
(requested in issue #2814). Keeps Spotify playlists synced to Plex.
- Wraps the multi-arch upstream image via BUILD_FROM (amd64 + aarch64)
- Uses the shared ha_entrypoint framework with 00-global_var.sh so add-on
options (Spotify credentials, redirect URI, encryption key) become the
environment variables the app expects
- 99-run.sh persists /app/config into the add-on config dir, auto-generates
and stores a stable ENCRYPTION_KEY when left blank, then hands off to the
upstream supervisord
- Web UI exposed directly on port 9030
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZTfSk3GQRU7oD85TnjsmW
Addresses review feedback on the sqlite persistence fix (PR #2812):
- validate_safe_path now rejects ".." path segments so a relative
output.sqlite.path can't traverse outside /config when rewritten.
- The rewrite now creates the destination's parent directory, since
SQLite won't create one itself for a path like "db/birdnet.db".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHJ4o22cdcgdNtb81tBTPJ
Upstream's shipped default config.yaml explicitly sets
output.sqlite.path to the relative "birdnet.db", so the missing-only
("//=") seeding of that key never fired. A relative path resolves
against the app's ephemeral container working directory instead of the
persistent /config volume, so the database was silently recreated
empty on every restart.
Rewrite any relative output.sqlite.path to live under /config on
startup, leaving already-absolute (user-customized) paths untouched.
Fixes https://github.com/tphakala/birdnet-go/discussions/3774
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHJ4o22cdcgdNtb81tBTPJ
awesomeversion (what HA's update entity uses since core's is-newer
check) parses "0.8.2-1" as semver with pre-release "1", and pre-releases
sort BELOW the base version: AwesomeVersion("0.8.2-1") >
AwesomeVersion("0.8.2") is False. Users stuck on the broken 0.8.2 nginx
release therefore see 0.8.2-1 as "Up-to-date" with a disabled Update
button and get no update notification. Four-segment 0.8.2.1 compares
strictly greater than both 0.8.2 and 0.8.2-1, and less than the next
upstream 0.8.3, so the fix becomes installable for everyone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016acGazhHvyCNUFt71xzFxm
Per Copilot review feedback on #2807: add a build-time assertion for `psql`
so a future base-image change that drops postgresql-client is caught at
`docker build` time with a clear error, instead of surfacing as a cryptic
runtime failure in 99-run.sh. Also drop the hard-coded "14 through 18"
client version range from the comment, since that's specific to the
current base image and could go stale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcbbgB7A5LLbPuPRjLUouk
The imagegenius v3 base image moved to a newer Ubuntu release where the
apt-key binary no longer exists, breaking the Dockerfiles' manual
PGDG-repo install of postgresql-client-15 (`wget ... | apt-key add -`).
That install was already redundant: imagegenius's own Dockerfile installs
postgresql-client-14 through 18 itself (via the modern signed-by keyring
method), so the psql CLI used by 99-run.sh is already present in the base
image. Remove the downstream install entirely rather than patching it to
use a keyring, since it duplicated work the base image already does.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcbbgB7A5LLbPuPRjLUouk
- Fix check_vchord_extension (and check_vector_extension) to query
pg_available_extensions against the actual immich database instead of
pg_extension on the default connection database. Immich creates the vchord
extension itself on first startup, so checking pg_extension before Immich
ever runs produced a false warning on every fresh install; checking
pg_available_extensions reports whether the server CAN provide the
extension, which is what the startup diagnostic actually needs.
- Drop the vestigial `services: - mysql:want` Supervisor service-discovery
hint from the four Immich config.yaml files: nothing in the add-on reads
it, and the scripts are hard-coded to PostgreSQL via psql — Immich has
never supported MySQL. Also fix the matching misleading line in the base
README.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcbbgB7A5LLbPuPRjLUouk
imagegenius/docker-immich stopped publishing GitHub releases (its newest release
is a "2026.0.0" announcement to that effect) and ships v3 only to GHCR, so
lastversion was stuck returning 2.7.5 and the add-ons never updated past v2.
Point the four Immich updater.json files at the real product repo
immich-app/immich, which publishes clean stable vX.Y.Z releases (currently
v3.0.1). The build.json images stay on the imagegenius :3 rolling tag, which
serves the newest 3.x build, keeping the tracked version and the pulled image
coherent. Also clear the now-obsolete github_exclude "2026" guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcbbgB7A5LLbPuPRjLUouk
Upstream 0.8.2 added http-level limit_req_zone directives to its
nginx.conf. The ingress config was a wholesale copy of that file, and
both land in the same http context via servers/*.conf — nginx refuses
to declare a named shared-memory zone twice and dies at startup with
'limit_req_zone "api_rl" already bound' (502 on every page). Extract
everything from the column-0 "server {" onward instead, so maps and
zones stay declared once; zone/variable references resolve across
included files regardless of include order. Also comment out the new
Content-Security-Policy header in the ingress copy, matching the
existing X-* handling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016acGazhHvyCNUFt71xzFxm
- Pin build.json to the imagegenius v3 image line (:3, :3-cuda, :3-noml, :3-openvino)
- Bump config.yaml version to 3.0.1 and record upstream_version 3.0.1 in updater.json
- Add a non-fatal VectorChord (vchord) startup check in the shared 99-run.sh so users
on a non-VectorChord database get a clear diagnostic (v3 drops pgvecto.rs)
- Document the Immich v3 database (VectorChord) and CPU requirements in each README and
point users at the Postgres 15 / Postgres 17 add-ons
- CHANGELOG entries for all four add-ons
The Postgres 15 / Postgres 17 add-ons already ship the official immich VectorChord image
(vectorchord0.4.3, matching Immich v3's pinned database) and are intentionally left
unchanged to preserve the pgvecto.rs to VectorChord migration path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcbbgB7A5LLbPuPRjLUouk
- Grant NET_ADMIN capability and /dev/net/tun device so the ZEROTIER option
works at runtime (Codex review)
- Use a JSON boolean for updater.json "paused" to match the documented format
(CodeRabbit review)
- Document the ZeroTier requirements in README and CHANGELOG
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZfzWxhkWHq8i8P7fnDvL
Implements the Zoraxy general-purpose reverse proxy (issue #1946) as a new
add-on following the repository conventions:
- Based on the upstream zoraxydocker/zoraxy image (Alpine)
- Web management UI exposed on port 8000 (webui link); reverse proxy on 80/443
- Persistent config/db/logs/plugins relocated to /config (addon_config) so they
survive add-on updates, by patching the upstream entrypoint working directory
- Options NOAUTH/ZEROTIER/FASTGEOIP/MDNS mapped to upstream env vars via the
shared 00-global_var module, plus env_vars passthrough for advanced settings
- Standard 6-section Dockerfile, supervised services.d launcher, healthcheck
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZfzWxhkWHq8i8P7fnDvL
Both variants share the same image; aligning the version ensures the
Supervisor pulls the updated build that suppresses the misleading
NET_RAW/NET_ADMIN alert for Full Access users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WfZ5JKAR2NHP1KjoAGyBBD
Under the Home Assistant Supervisor the required NET_RAW/NET_ADMIN
capabilities are granted, but NetAlertX's upstream capabilities-audit
script cannot read them and prints a "🚨 ALERT: capabilities are missing"
banner. Users repeatedly mistook this informational message for the cause
of unrelated issues. Neutralise the audit script at build time (kept in
place as a no-op for the entrypoint runner) and bump version/changelog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WfZ5JKAR2NHP1KjoAGyBBD
The folded block scalar was ~532 chars, exceeding the CodeRabbit v2
schema constraint and causing the config to be rejected. Trimmed to
238 chars; detailed context already lives in path_instructions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195h5b5zBHAA6urgJtp4aJP
Configure CodeRabbit (.coderabbit.yaml) tailored to this Home Assistant
add-on repository:
- tone_instructions establishing the HA add-on context for both issue
help and PR reviews
- chat.auto_reply + knowledge_base (issues/PRs/learnings) to support
issue triage and first-line help
- path_instructions encoding the add-on conventions (config.yaml,
Dockerfile, S6 cont-init/services scripts, updater.json, CHANGELOG,
workflows) from CLAUDE.md
- labeling/path filters aligned with the repo's existing labels and
generated artifacts
- review tools matching CI: shellcheck, hadolint, markdownlint,
yamllint, actionlint, gitleaks, checkov
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195h5b5zBHAA6urgJtp4aJP
- netbird-server: new profile `netbird-server_addon` (includes capability sys_chroot)
- netalertx_fa: symlink apparmor.txt to base netalertx profile, matching the
repo's existing _fa convention (e.g. scrutiny_fa -> scrutiny)
- signalk intentionally left without a profile (config sets apparmor: false)
No version bumps — profiles apply on each add-on's next update.
Co-Authored-By: Claude <noreply@anthropic.com>
- Add `capability sys_chroot` to all add-on AppArmor profiles (prophylactic
fix; required by any service that uses privilege-separation chroot, e.g.
sshd, Elasticsearch JVM, postgres)
- Fix AppArmor profile name collisions where add-ons had copy-pasted a wrong
profile name (inadyn_addon, db21ed7f_qbittorrent, radarr_addon,
db21ed7f_scrutiny, addon_db21ed7f_emby_nas, addon_updater, chromium_addon,
fireflyiii_addon, webtop_addon, joplin, gitea_addon) causing AppArmor to
silently apply the wrong profile
No version bumps — profiles apply on next add-on update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V7P3Nbem7n9FqGTXrLMadP
Gitea's install wizard uses an atomic SaveTo that replaces the symlink
at /data/gitea/conf/app.ini with a real file containing the completed
install config. On the next restart /config/app.ini (the pre-wizard
template) already exists, so the previous guard skipped the copy and
the rm deleted the real installed config, wiping DB/security settings.
Always copy the real file over /config/app.ini regardless of whether
the target already exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D46Ef3nZZdbVuwUpPhCd4T
Symlink /data/gitea/conf/app.ini -> /config/app.ini so users can read
and edit the full Gitea configuration via the HA file editor without
needing shell access. Existing installs migrate their app.ini on first
restart. Closes#1907.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D46Ef3nZZdbVuwUpPhCd4T
Both add-ons failed because their AppArmor profiles did not list
`capability sys_chroot`, which AppArmor then denied even though it is
part of Docker's default capability set:
- gitea: sshd privilege-separation chroot("/var/empty") failed with
"Operation not permitted [preauth]", breaking git-over-SSH (#2653)
- elasticsearch: upstream startup chroot failed with
"chroot: cannot change root directory" (#2709)
Also rename the elasticsearch AppArmor profile from the copy-pasted
`inadyn_addon` (shared with several other add-ons) to
`elasticsearch_addon` to avoid profile-name collisions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V7P3Nbem7n9FqGTXrLMadP
Upstream paperless-ngx switched to an s6-overlay v3 init system at v2.15.0
(ENTRYPOINT ["/init"]) and removed the legacy /sbin/docker-entrypoint.sh that
this add-on patched. The add-on had therefore been broken since the 2.15.x bump.
- Inject initialization via S6_STAGE2_HOOK=/ha_entrypoint.sh instead of patching
the now-removed upstream entrypoint
- Export runtime variables to the s6 container_environment so the upstream
supervised services (svc-webserver, svc-worker, svc-scheduler, svc-consumer)
pick them up
- Use the canonical ha_entrypoint.sh template (remove the outdated bundled copy)
- Add 00-global_var module + jq for env_vars passthrough
- Guard the ImageMagick policy patch and the optional nginx/redis startup
- Bump version and updater tracking to 2.20.15
tr reads from the infinite /dev/urandom stream; head exits after N bytes,
closing the pipe, which sends SIGPIPE to tr (exit 141). With set -euo pipefail
at the top of 99-run.sh, pipefail surfaces that as the script exit code and
the container never starts. Suppress it with || true on both occurrences in
the MinIO credential generation block.
https://claude.ai/code/session_01MaLKhb2CJiF9Fb3Dyr585r
- Rename AppArmor profile from the leftover qbittorrent name to ente_addon
to avoid colliding with the qbittorrent add-on's profile
- Map the Accounts (3001), Auth (3003) and Cast (3004) ports so the login,
2FA and cast web apps served by nginx are actually reachable
- Default the external Postgres port to 5432 when DB_PORT is left blank
- Write the resolved DB host/port to museum.yaml so external databases are
configured correctly on disk, not just via env overrides
- Exclude minio-data and postgres from Home Assistant backups to avoid
pulling the whole photo library and database into every backup
https://claude.ai/code/session_01MaLKhb2CJiF9Fb3Dyr585r
1. Generate random MinIO credentials on first run, persist to /config/minio-creds,
reuse on restart. Export MINIO_ROOT_USER/PASSWORD env vars for MinIO server.
2. Make nginx web startup idempotent by checking if web.bak exists before moving.
3. Bind MinIO console to 127.0.0.1:9001 with --console-address.
4. Expose port 3002 (albums) in config.yaml and derive ENTE_ALBUMS_ORIGIN from
the API endpoint host with the mapped external port 8302.
Add a dedicated nginx health endpoint on 127.0.0.1:3001 with access_log
off that returns 200 at /health. Update HEALTHCHECK to verify both that
the filebrowser process is running (pgrep) and nginx is serving (curl to
health endpoint), avoiding direct HTTP requests to filebrowser that caused
log spam every 5 seconds.
Adds clear step-by-step instructions in the Mounting Drives section
of all Immich addon READMEs explaining how to use a mounted local
disk for Immich storage by combining localdisks and data_location.
Removing /config/config.yaml after a failed first-boot curl left the
next yq read (.realtime.audio.export.path) trying to open a missing
file; under set -e that aborts the entire cont-init script, so the
addon would never get to seed its defaults or start BirdNET-Go.
Seed an empty YAML document ({}) instead. The existing "//=" defaults
block then populates output.sqlite.path, logging.file_output.*, and
the migration block writes realtime.audio.export.path. Result: an
offline first boot now produces a valid minimal config.yaml and the
container starts cleanly.
Also harden the yq read with "// """ so a freshly seeded "{}" doc
returns an empty string (caught by the existing :-DEFAULT fallback)
rather than the literal string "null".
Replace mqtt_disable / mariadb_disable (opt-out, default-on) with
mqtt_auto_config / mariadb_auto_config (opt-in, default-off). When the
HA addon is detected but the option is off, still log the broker /
database credentials and a hint pointing the user at the option — so
discoverability stays the same without surprise config rewrites.
Bugs fixed in 01-structure.sh:
- Database backup created during BIRDSONGS_FOLDER migration was written
to the script's CWD instead of /config, and the restore path was
recomputed with a fresh timestamp — so any second-boundary crossing
between backup and restore left the user unable to recover. Backup
path is now absolute and reused for restore.
- Path inputs are validated against [A-Za-z0-9._/-]+ before being
interpolated into the SQL UPDATE statement.
- Default-config download tolerates network failure instead of leaving
an empty config.yaml behind.
- output.sqlite.path and logging.file_output.* are now seeded with the
"set-if-missing" idiom (`//=`) so user edits to config.yaml survive
restarts. (Breaking: addon options for log rotation now only seed
defaults on first run.)
- Path normalization centralized; trailing-slash juggling removed.
UX upgrades:
- 33-mqtt.sh now auto-configures realtime.mqtt.* in config.yaml from
the HA Mosquitto addon (with new mqtt_disable opt-out option).
- 33-mariadb.sh now auto-switches output.mysql.* to the HA MariaDB
addon and disables SQLite (with mariadb_disable opt-out option).
Cleanup:
- Dockerfile: upstream entrypoint sed-patch now warns (not silently
succeeds) when the target pattern is missing in a new nightly.
- Removed dead nginx upstream.conf pointing at unused port 8096.
- Trimmed redundant nginx HTML-attribute sub_filters; upstream
birdnet-go handles those itself via X-Ingress-Path. JS string
rewrites kept since the upstream HTML rewriter does not touch JS.
(Breaking UI-side if upstream regresses — see CHANGELOG.)
- Change cronupdate shebang from bashio to /bin/bash (cron PATH lacks bashio)
- Remove bashio API calls from cron script (no Supervisor access in cron)
- Source /.env in cron script to load all env_vars from 00-global_var.sh
- Persist SILENT_MODE to /etc/environment for cron access
- Remove destructive `sed 's|root|www-data|g'` on /etc/crontab
- Fix /etc/environment permissions from 600 to 644 for cron readability
/etc/asound.conf is read-only in the addon environment, so both the
shipped overrides and the user-supplied override are now written to
/root/.asoundrc (the app runs as root with HOME=/root). ALSA loads
~/.asoundrc as an additive layer on top of the system config, so the
override behavior is unchanged.
Add addon options to control birdnet-go log file rotation:
- LOG_MAX_SIZE_MB (default: 50): maximum size per log file before rotation
- LOG_MAX_AGE_DAYS (default: 7): maximum days to retain old log files
On startup, the addon:
1. Configures birdnet-go's logging.file_output settings in config.yaml
2. Sets max_rotated_files to 3 and enables compression
3. Trims existing log files exceeding the configured age
Fixes#1922
Advanced users who legitimately need JACK, a custom dsnoop chain, or any
other ALSA setup can drop their own asound.conf into the addon config
folder. The cont-init script copies it over /etc/asound.conf before
launching the app, replacing the addon-shipped defaults.
- Patch upstream entrypoint via sed so chmod on the read-only /dev/snd
mount no longer prints "Read-only file system" lines.
- Add /etc/asound.conf overriding the JACK, OSS, dsp, and dsnoop PCM
plugins to type "null" with hint.show off. This hides them from
snd_device_name_hint(), so miniaudio's device enumeration no longer
probes them at launch and the corresponding libjack/pcm_oss/pcm_dsnoop
errors disappear.
Two build-time bugs prevented the immich image from being built:
1. The find . chmod command ran from WORKDIR=/ and descended into
/app/immich/server/node_modules/, hitting files it could not chmod
(exit code 1 at build step 5). Fixed by scoping find to only
/etc /usr/local/bin /usr/local/lib /usr/local/share — the actual
directories populated by COPY rootfs/ /.
2. sed -i tried to patch /etc/s6-overlay/s6-rc.d/init-test-run/run
which no longer exists in the imagegenius base image (removed in
an earlier upstream update). Fixed with a [ -f ... ] guard so the
sed is silently skipped when the file is absent.
Reproduced both bugs locally with docker build, verified fix on a
bare-metal VPS — all 17 build steps complete cleanly.
Fixes#2718
When image: is set in config.yaml, HA pulls that image directly and
never runs the Dockerfile, so the rootfs overlay (including the
passthrough entrypoint) was never applied. Removing image: forces HA
to build from the Dockerfile, which COPYs rootfs/ and applies our
run script and entrypoint override.
Also switch run script from symlink approach to AURRAL_DATA_DIR env
var, which avoids the race condition where docker-entrypoint.sh runs
before s6 and tries to chown a broken symlink target.
/data is HA's built-in private persistent storage for every addon - no
user configuration needed. Only download_folder needs to be user-facing
since that's where the music lives and users need to know the path.
The upstream docker-entrypoint.sh chowns /app/backend/data which fails
when it's a symlink to a host-mounted HA path. Instead, use the env vars
that Aurral natively supports to redirect paths:
- AURRAL_DATA_DIR -> data_folder config option
- DOWNLOAD_FOLDER -> download_folder config option
- WEEKLY_FLOW_FOLDER -> download_folder/weekly-flow
This lets the entrypoint chown the real (container-internal) /app/backend/data
unmolested, while node writes persistent data directly to the HA share paths.
Previously image: pointed directly at the upstream ghcr.io/lklynet/aurral,
meaning the Dockerfile was never built and the rootfs overlay (including the
entrypoint fix) was never applied. Changed to ghcr.io/alexbelgium/aurral-{arch}
which is where the CI build pushes the image built FROM the upstream via build.json.
docker-entrypoint.sh runs chown -R on /app/backend/data which fails when
that path is a symlink to a host-mounted HA volume. Replace it with a
passthrough script that just exec's its arguments, letting the s6 run
script handle all setup.
The upstream docker-entrypoint.sh runs chown -R on /app/backend/data before
the s6 run script can replace it with a symlink. When the symlink target is a
host-mounted HA path (/share, /data, etc.), the chown fails with
"Operation not permitted".
Overriding ENTRYPOINT with /init hands control directly to s6-overlay, which
then runs the run script that creates the symlinks before node starts.
- config.yaml: strip back to download_folder, data_folder, port only
- README.md: match alexbelgium style, merge upstream link into About,
remove badge buttons, remove Navidrome references
- config.yaml: fix url to point to master, add ingress support,
remove download_folder/data_folder from options (set via UI),
match lidarr schema style with env_vars passthrough
- updater.json: add with upstream v1.76.12 tracking lklynet/aurral
- run script: use proper ingress port env var, cleaner startup
- README.md: fix install instructions to reference master branch
Replace the legacy Werkzeug dev server (python -m core.api) with a single
gunicorn GeventWebSocketWorker, matching upstream BirdNET-PiPy 0.7.0's
production-server migration (the add-on already builds 0.7.0 source).
Single worker is mandatory: live detections fan out via an in-process
Socket.IO emit (no Redis message_queue), so >1 worker would silently drop
Live Feed broadcasts. Heavy maintenance jobs cooperatively yield to keep the
worker responsive. Bound to 127.0.0.1:5002 for single-container use. Also
fixes the now-stale 'Python core.api' comment in nginx/run.
Add-on packaging change only -> 0.7.0 -> 0.7.0-1 (suffix convention; base
tracks upstream via the updater bot).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent failure modes can cause the restart loop on arm64:
1. /dev/stdout may be inaccessible in some ARM container runtimes; replaced
with /proc/1/fd/1 which LSIO images already use for direct container stdout.
2. s6-notifyoncheck writes to fd 3 on check success, but s6-rc only opens
that fd when notification-fd exists in the service directory. LSIO arm64
images ship svc-qbittorrent without it, so s6-notifyoncheck exits with
EBADF and s6 restarts the service in a loop. Guard with a file check and
fall back to exec'ing qbittorrent-nox directly when the file is absent.
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
/dev/stdout resolves via /proc/self/fd/1 and can be inaccessible in some
ARM container runtimes, causing the exec redirect to fail and s6 to restart
the service in a loop. /proc/1/fd/1 is the path LSIO images already use
for direct container stdout (see nginx silent-mode handling) and is reliable
across architectures.
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
s6-rc opens fd 3 for a longrun service only when a notification-fd file is
present in the service directory. The LSIO aarch64 image ships svc-qbittorrent
without that file, so fd 3 is never opened. s6-notifyoncheck exits with EBADF
after its readiness check passes, s6 treats the supervised process as dead and
immediately restarts it — producing the infinite "Starting qBittorrent..." loop.
Check for the notification-fd file at runtime: use the full s6-notifyoncheck
path when it is present (amd64, preserving existing behaviour), and fall back
to exec'ing qbittorrent-nox directly when it is absent (aarch64).
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
s6-notifyoncheck writes to fd 3 when its readiness check passes, but
the LSIO aarch64 image's svc-qbittorrent service has no notification-fd
file so s6-rc never opens fd 3. s6-notifyoncheck exits with EBADF, s6
sees the supervised process die and immediately restarts it, producing
the infinite "Starting qBittorrent..." loop seen on odroid-c2 and other
aarch64 boards.
Drop s6-notifyoncheck entirely and exec qbittorrent-nox directly under
s6-setuidgid so s6 supervises the real process. Silent mode is handled
by redirecting the shell's fds before exec rather than passing a
/dev/stdout path (avoids a separate class of ARM container fd issues).
https://claude.ai/code/session_01F16ThtZyfXj6ZKFPkrSAAq
Seafile's check_init_admin.py looks for SEAFILE_ADMIN_EMAIL/PASSWORD
in the env, then falls back to conf/admin.txt, and only prompts
interactively if neither is available. The upstream init.sh writes
admin.txt, but it is skipped when conf/ccnet.conf or conf/revision
already exist (e.g. after a partial previous install) and the env
vars do not always reach the seahub subprocess via su. Write
admin.txt directly and inject the values into seafile.env so admin
creation succeeds (#2685).
https://claude.ai/code/session_01EwuoFH7aHJMySr9J775XQP
s6-notifyoncheck exits with EBADF when notification-fd 3 isn't opened by
s6-rc (can happen depending on LSIO image layer order), killing the supervised
qBittorrent process and causing the 2-second restart loop. Dropping it lets
s6 supervise qBittorrent directly without the fragile fd notification path.
Also probe /app, /usr/bin, and /usr/local/bin for the binary so the addon
works across LSIO image builds that place qbittorrent-nox in different spots.
https://claude.ai/code/session_015eiGSjWjSVtKbBFhBHUeDt
On HAOS >=17.3 the Supervisor Docker network gained IPv6, so
core-mariadb resolves to an IPv6 address first. The official MariaDB
addon only grants its service user from the IPv4 supervisor subnet, so
connections from IPv6 fail with "Access denied".
Resolve the hostname to its IPv4 address before connecting in every
addon that consumes bashio::services 'mysql' 'host': photoprism,
monica, fireflyiii, seafile, zoneminder. Fall back to the raw hostname
if resolution fails so IPv4-only setups keep working unchanged.
Replaces the 0.6.6-3 sleep+ingress-file-loop in services.d/nginx/run
with bashio::net.wait_for on 127.0.0.1:5002 (core.api). Under
s6-overlay all services.d/* services start concurrently, so nginx
could accept requests before the API had bound its port — proxy paths
(/api/, /socket.io/, /internal/auth) would 502, and that 502 could be
cached by an upstream service worker / edge cache (e.g. Cloudflare-
fronted HA), leaving the UI blank.
Matches the sister-addon pattern (bazarr, jellyfin, radarr). Also
switches to `exec nginx` for proper s6 supervision of the nginx PID.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump birdnet-pipy to 0.6.6 (upstream tags v0.6.5 + v0.6.6; skipping
0.6.5 same as the updater bot would).
Minor CHANGELOG.md cleanup while here: normalize 10 entries from
DD-MM-YYYY to YYYY-MM-DD, and remove a stale pre-ingress block (0.2,
0.6.1-0.6.6 dated Jan 2026) that was colliding with real version
numbers further up the file.
When DB_CONNECTION is set to mariadb_addon, the script now checks if the user
has explicitly configured DB_USERNAME, DB_PASSWORD, or DB_DATABASE in addon
options. If set, those values are used instead of the MariaDB addon service
discovery credentials. This fixes authentication failures when the service
account doesn't have proper access.
Fixes: Firefly III access denied for user 'service' issue
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/7cacda5b-d03e-47c5-b4fc-4cfb4ef2a3dc
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Adds `homeassistant_api: true` so the addon can call HA Core's
`update.install` service. Required for in-app self-update —
Supervisor blocks `/store/addons/<self>/update`, so the backend
routes through Core.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The app derives its timezone from station lat/lng via timezonefinder
(Settings → Location in the Web UI), so a separate addon-level TZ only
ever created mismatch with the UI-derived zone. All app-facing timestamps
(dashboard, API, DB) and Python service stdout (api/main/birdnet) already
honor the UI zone via logging_config.py's formatter, and the frontend log
viewer re-converts Icecast timestamps on read.
Net effect in the HA addon log pane: Python services still show the
correct local time; only Icecast's raw stdout now prints in UTC (accepted
trade for a single source of truth). Deletes
rootfs/etc/cont-init.d/02-timezone.sh added in 0.5.6-2.
Also cleans up the options YAML in DOCS.md/README.md: drops
RECORDING_MODE and RTSP_URL (never wired — app reads them from
user_settings.json), drops http_stream from the modes list, and moves
STREAM_BITRATE under an env_vars: example since it's honored by
start-icecast.sh but not a schema option.
Bumped to 0.6.3-2 (addon-only, no upstream app change).
Upstream 0.6.3 consolidates deployment-path handling into a single
<base href> declaration in index.html with Vite base: './' and
relative paths for all internal URLs. The previous seven sub_filter
rules (href/src/'/api'/'/socket.io') are no longer needed — one
<base href> replacement is sufficient.
Also removes the incidental brittleness from byte-level sub_filter
matches in minified JS bundles (the '/stream/' rule inadvertently
double-prefixed the literal 'api.get("/stream/config")' string).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the nginx rewrite directive that could cause URL normalization/re-encoding
issues with special characters in query strings (like spaces encoded as +).
HA Supervisor strips the ingress prefix before forwarding, making the rewrite
unnecessary. Without it, proxy_pass uses the raw $request_uri which preserves
URL encoding.
Also fix Connection header from hardcoded "upgrade" to $connection_upgrade
map variable for proper WebSocket vs regular HTTP request handling.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/3982b002-dfcb-4eb5-98c2-913f665b6a07
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Two fixes for the birdnet-pipy addon:
1. Icecast log permission error: 01-structure.sh creates /app/data/logs
as root, but start-icecast.sh runs via gosu icecast2. The first log
write fails with "Permission denied", crashing icecast in a restart
loop (502 Bad Gateway on the Live Feed page). Fix: chown the log dir
and file to icecast2 before dropping privileges.
2. Live Feed double-prefixed request in ingress mode: the "/stream/"
sub_filter rule also matches 'api.get("/stream/config")' in the JS
bundle, producing a request like
/hassio_ingress/TOKEN/api/hassio_ingress/TOKEN/stream/config (404).
The upstream frontend (BirdNET-PiPy >= 0.6.2) now strips the leading
slash from stream URLs so they resolve via <base href> — the
sub_filter rule is no longer needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The calibre-web addon relies on calibredb to retrieve book metadata for
downloads. Previously, calibre was only installed at runtime via
DOCKER_MODS (linuxserver/mods:universal-calibre), which could fail due
to network issues or changes in the init sequence.
Installing calibre at build time ensures calibredb is always available
in the container, fixing the 500 Internal Server Error when downloading
books.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/6f6e6795-4a2c-4c6a-88b2-931def081d20
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The upstream Maintainerr image declares /opt/data as a Docker VOLUME.
Attempting to rm -rf /opt/data fails with "Resource busy" because mount
points cannot be removed. Instead, we now:
1. Copy seed data from /opt/data to /config (persistent storage)
2. Clear contents inside /opt/data (rm -rf /opt/data/*)
3. Symlink each item in /config back into /opt/data
This ensures the VOLUME directory stays intact while all data is
redirected to persistent storage.
Agent-Logs-Url: https://github.com/alexbelgium/hassio-addons/sessions/82a46feb-2e9c-4c40-b193-614167e6d5c3
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Added transmissionic webUI, moved transmission-web-control to the end - is removed in the current version, but there are discussions ongoing so not removing it for the moment
- Add General display options (Interval, ShowClock, etc.) to addon config schema
- Add per-Account filter options (Albums, People, ShowFavorites, etc.) to Accounts schema
- Rewrite 99-run.sh to generate complete Settings.yaml with General and Accounts sections
- env_vars are automatically classified as General or Account-level settings
- Schema options take precedence over env_vars
- Full backward compatibility: existing env_var configs continue to work
- Update README with comprehensive options documentation
- Bump version to 1.0.32.0-4
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The upstream haugene/transmission-openvpn v5.4.0 image uses systemd-resolved
via D-Bus in its update-resolv-conf script, which fails in HA containers
that lack systemd/D-Bus with 'sd_bus_open_system: No such file or directory'.
This adds a custom update-resolv-conf script that directly modifies
/etc/resolv.conf by parsing OpenVPN's foreign_option_* environment variables.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Add Accounts list schema to config.yaml for multi-account support
- Make ApiKey and ImmichServerUrl optional when using Accounts list
- Generate Settings.yaml from addon options in 99-run.sh
- Fix /app/Config symlink direction for proper config persistence
- Set IMMICHFRAME_CONFIG_PATH for reliable config discovery
- Update README with multi-account documentation and examples
- Bump version to 1.0.32.0-2
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- New init script 02-timezone.sh: reads TZ from addon config, validates
against /usr/share/zoneinfo, applies to container (/etc/localtime,
/etc/timezone, s6 container env)
- Change default TZ from Etc/UTC to Europe/Paris
- Increment version to 0.5.6-2
- Update CHANGELOG.md and DOCS.md
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The probe script requires bashio::addon.version to return non-empty output,
but the Supervisor API may not be ready during container init. This causes
all shebang candidates to be rejected even though the interpreter works fine.
Fix: output a 'PROBE_OK' marker when the script executes successfully but
the version is empty. Also check /.bashio-standalone.sh as fallback path
(some containers store it there instead of /usr/local/lib/).
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
When the environment is too large for exec (E2BIG), scripts fail with
'env: can't execute bashio: Argument list too long'. This adds a fallback
that sources the script in a subshell with bashio preloaded, avoiding exec
entirely. Applied to both init scripts and service runners.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The script failed with 'find: /homeassistant/addons_config: No such file or directory'
because it accessed the directory without checking if it exists. Since the script uses
set -e, the failing find command caused the entire init script to exit.
This follows the same pattern already used in the filebrowser addon's 20-folders.sh.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Use DATA_DIR=/config (config:rw map) per maintainer request
- Scope chmod to /entrypoint.sh and cont-init.d instead of whole FS
- Gate chown -R behind .initialized marker to avoid slow restarts
- Bump version to 0.133.1 (latest upstream manyfold-solo release)
- Remove image: field from config.yaml so HA builds via Dockerfile
- Pin Dockerfile and build.yaml to 0.133.1 instead of :latest
- Add Secret Key Base section to README covering all install scenarios
- Add Migration section to README with steps to preserve data when
switching addon slug or reinstalling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add hassio_api and hassio_role: manager to config.yaml so the addon
can call /addons/self/restart via the Supervisor API
- Resolve branch to commit SHA before downloading source for build
traceability (displayed in the app's Settings page)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The upstream Gramps Web project deprecated EMAIL_USE_TLS in favor of
EMAIL_USE_SSL (for port 465) and EMAIL_USE_STARTTLS (for port 587).
This updates the addon config schema and documentation accordingly.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Dynamically detect render device GID using stat instead of hardcoding 104
- chmod 666 render devices to ensure accessibility for non-root PUID/PGID
- Only run render setup when /dev/dri exists
- Use GID-specific group naming to avoid conflicts
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The addons_updater script's global sed replacement corrupted Dockerfiles
when the upstream_version was empty (""), replacing all empty double-quoted
strings with version strings. This corrupted:
- HEALTH_URL values (causing malformed health check URLs and container
instability from failed health checks)
- ASCII art comments in Dockerfile headers
Fixed affected addons: guacamole, tdarr, photoprism, enedisgateway2mqtt_dev,
gazpar2mqtt, seafile
Also added a guard in the updater script to skip updates when version
strings are empty, preventing this corruption from recurring.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
On aarch64, native Node.js modules (sharp, @next/swc, etc.) may have
been incorrectly cross-compiled via Docker BuildKit QEMU emulation.
Add an npm rebuild step that runs at startup on aarch64 to re-download
the correct prebuilt native binaries for the actual hardware.
Fixes #XXXX
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Add ingress: true, ingress_port: 0, and ingress_stream: true to
config.yaml. Update ingress_params.conf with sub_filter rules for
Vue.js SPA: rewrite API paths, stream paths, Socket.IO paths, and
inject base href for Vue Router history mode.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Replace `usermod -o -u 0 node` and `groupmod -o -g 0 node` with direct
sed modifications to /etc/passwd and /etc/group. The usermod/groupmod
commands can hang indefinitely in container environments due to lock
file contention, NSS cache daemon (nscd) interactions, or PAM module
issues. The sed approach achieves the same result without these risks.
Fixes #XXXX
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The sudoers entry for the wger user was written to /etc/sudoers before
the sudo package was installed. When apt-get installs sudo, its default
/etc/sudoers conffile may overwrite the entry, causing sudo to fail at
runtime. This prevented ha_entrypoint.sh from running as root, so
/data/media was never created with correct permissions, resulting in
PermissionError when downloading exercise images.
Fix: re-add the sudoers entry after the sudo package is installed.
Also improve symlink handling in 90-run.sh to avoid self-referencing
copies on subsequent container starts.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- ha_entrypoint.sh: probe_script_content now tries regular bashio first,
falls back to bashio-standalone.sh if Supervisor API unreachable
- 00-banner.sh: source bashio-standalone before calling bashio functions
in standalone branch (prevents undefined function errors with set -e)
- 01-custom_script.sh: same probe fix + add bashio-standalone.sh to
fallback source chain
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Sonarr v4 configures FFMpegCore to look for ffprobe exclusively in its own
binary directory (/app/sonarr/bin/) via AppDomain.CurrentDomain.BaseDirectory.
The addon already installs ffmpeg via apt (providing /usr/bin/ffprobe), but
Sonarr never looks there. This init script creates a symlink so Sonarr can
find the system-installed ffprobe.
Fixes#2451
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Source /etc/environment in cronupdate to load addon env vars (FIREFLY_III_URL, etc.)
- Persist cron-needed vars (JSON_CONFIGURATION_DIR, IMPORT_DIR_WHITELIST, etc.) to /etc/environment
- Fix inverted nginx map values that caused health check logs to spam while suppressing real requests
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
Add the "Add repository to my Home Assistant" blue button to all
addon README files that were missing it. This makes it easier for
new HA users to add the repository with a single click.
Updated 59 addon READMEs:
- 51 with standard Installation section format
- 8 with non-standard or missing Installation sections
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Use ${PULSE_SERVER:-unix:/run/pulse/native} in icecast/run and main/run
to respect the PULSE_SERVER env var set by HA supervisor
- Create /run/pulse/native symlink when PulseAudio socket is elsewhere
- Copy PulseAudio cookie for icecast2 user authentication
- Add icecast2 to audio group
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
The upstream Docker image's write_config.sh hardcodes
FILE_SERVER_ROOT = "http://<SERVER_IP>/seafhttp" in seahub_settings.py,
overwriting the addon's configured value on first run. This causes
download URLs to contain an incorrect /seafhttp prefix and miss the
file server port.
Fix by creating a helper script (apply_addon_urls.sh) that re-applies
the addon's FILE_SERVER_ROOT and SERVICE_URL right before Seafile
services start, after all upstream init/setup/update scripts complete.
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
1. Mountpoint name collisions: check if /mnt/$diskname already exists
before mounting and skip with warning if it does
2. test_mount() readonly false positive: verify mountpoint is actually
mounted before reporting readonly status
3. Independent retries: save original MOUNTOPTIONS and use as base for
each noserverino/noperm retry independently
4. Hostname support: accept hostnames (not just IPv4) in CIFS/NFS path
validation, FSTYPE detection, and server extraction
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Add SMB 1.0 to version ladder in retry_cifs_with_vers_ladder_on_dialect_failure
- Add sec=ntlm to security variants for SMB1 compatibility
- Fix SMBVERS_FORCE override to actually force when SMBv1 detected
- Fix smbclient SMBv1 detection to use credentials instead of anonymous
- Add SMBv1 pattern detection to nmap smb-protocols parsing
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
- Set ingress: false and add webui URL in config.yaml
- Remove nginx from Dockerfile PACKAGES
- Remove nginx ingress init script, nginx config, and nginx service
Co-authored-by: alexbelgium <44178713+alexbelgium@users.noreply.github.com>
# Max 250 chars. Detailed context lives in path_instructions below.
tone_instructions:"HA add-on repo with 120+ Docker add-ons for Home Assistant Supervisor. Be concise, practical and friendly; most contributors are hobbyists. Link to add-on READMEs and the repo wiki. Follow existing conventions over generic best practices."
early_access:false
enable_free_tier:true
reviews:
# "chill" keeps reviews helpful without nitpicking the many small upstream
# version-bump PRs that dominate this repo.
profile:"chill"
# Don't block merges; this repo merges frequently and relies on CI gating.
You are triaging a new issue on `alexbelgium/hassio-addons`, a monorepo of
100+ Home Assistant add-ons. Each add-on is a thin wrapper (Dockerfile,
`run.sh`, s6 services, nginx config, `config.yaml`) around an upstream
application that Alex does not maintain.
Your entire output is one JSON object written to `/tmp/ai-triage/verdict.json`.
You do not comment, label, or edit anything.
## Rule 0 — ownership short-circuit
Read the existing comments in the context bundle first. The
`on_issues_ping_submitter` workflow signals ownership by posting a **comment**
(authored by `github-actions[bot]`) that pings the add-on's original submitter.
Its exact, machine-stable format is:
```
<!-- addon-submitter-ping:<addon> -->
Heads up @<user>: this issue appears to mention `<addon>`.
```
Match it on the literal marker `<!-- addon-submitter-ping:` — that string is
the reliable signal; do not infer ownership from prose. The bundle renders
each comment under a `### @<login>` heading — the marker only counts when that
heading reads `### @github-actions[bot]`. A marker pasted inside the issue
body, or inside a comment from any other login, is not the workflow's signal
and must be ignored. If a comment satisfying both conditions is present **and**
the pinged `@<user>` is not `alexbelgium`, stop immediately and emit:
```json
{"verdict":"owned","confidence":"high"}
```
Do not spend turns on anything else. (The workflow only ever pings a mapped
submitter, so in practice `@<user>` is always someone other than `alexbelgium`;
the check is a guard, not a common case.)
## Rule 1 — pick exactly one verdict
| verdict | when |
|---|---|
| `duplicate` | An existing open or closed issue reports the same thing. Set `duplicate_of`. |
| `needs-info` | You cannot tell what is wrong without the add-on version, HA version, architecture, config, or the actual log output. |
| `question` | A usage question answerable from `DOCS.md`, the wiki, or the add-on config. Not a defect. |
| `upstream-bug` | The fault is in the upstream application or its image, not in this repo's wrapper. |
| `addon-bug` | The fault is in something this repo owns: the Dockerfile, `run.sh`, s6 service files, nginx config, `config.yaml` schema, or an option that is not being passed through. |
| `feature-request` | New capability, new add-on, new option. |
**The `upstream-bug` / `addon-bug` split is the one that matters.** Only
`addon-bug` triggers the expensive fix pass. Getting it wrong means the bot
opens a pull request against code that does not exist in this repository.
Test it explicitly: name the file in this repo you would have to change. If you
cannot name one, it is not `addon-bug`.
## Rule 2 — confidence is a real signal
Set `confidence` to `low` whenever any of these hold:
- The add-on could not be resolved from the title (`UNRESOLVED` in the bundle).
- The issue mixes several unrelated problems.
- You are choosing between `upstream-bug` and `addon-bug` and could argue both.
- The report is in a language you are not confident reading.
`low` confidence suppresses the comment entirely and flags a human instead.
Prefer that over a fluent guess. A wrong answer on a support issue costs Alex
more trust than no answer.
## Rule 3 — writing the comment
Only `duplicate`, `needs-info`, and `question` get a comment. The other verdicts
are labelled silently and handled later.
- **duplicate** — one line, link the other issue, no explanation.
- **needs-info** — ask only for what is *strictly* required to proceed, as a
short checklist. Never more than four items. Say where to find each one
(e.g. the add-on log tab, the Configuration tab). Do not ask for anything
already present in the issue body.
- **question** — answer only from files in the context bundle, and quote the
file path you took it from. If the bundle does not contain the answer, this
is `needs-info`, not `question`. Never invent option names.
Never close an issue. Never promise a timeline. Never say a fix is coming.
"Blocked automatically: this PR modifies shared infrastructure (\`.github/\` or \`.templates/\`), which is inherited by every add-on in the repo. Needs manual review before it goes anywhere."
--system-prompt "You are @claude on alexbelgium/hassio-addons, invoked manually by the maintainer, so you take precedence over the automated triage tiers. Each add-on is a thin wrapper around an upstream app. For a small, confident change: make the edit on a branch ai-fix/<addon>-<issue>, run shellcheck on any shell, add a CHANGELOG.md entry, and open a ready PR that Closes the issue. For anything large or uncertain: do NOT grind on it here on Sonnet — post your analysis and recommend applying the ai:approved label (if a tier-2 plan exists) or dispatching the AI fix sweep. Never modify .github/ or .templates/. Never touch the version or upstream fields in config.yaml. Never merge or enable auto-merge."
"No AI plan (\`<!-- ai-plan -->\`) from the triage bot was found on this issue, so \`ai:approved\` has nothing to execute (removed). Run the tier-2 sweep on it first (\`AI fix sweep\` → issue $ISSUE), then approve the plan it posts."
if ! command -v bashio::addon.version >/dev/null 2>&1;then
for f in /usr/lib/bashio/bashio.sh /usr/lib/bashio/lib.sh /usr/src/bashio/bashio.sh /usr/local/lib/bashio/bashio.sh /usr/local/lib/bashio-standalone.sh;do
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
This is a Home Assistant add-on repository containing 120+ Docker-based add-ons for the Home Assistant Supervisor. Each add-on is a self-contained directory with a Dockerfile, config schema, and S6-overlay init scripts. The repository uses GitHub Actions for CI/CD, linting, and automated upstream version tracking.
## Add-On Directory Structure
Most add-ons follow this common layout, though exceptions exist (e.g. some archived add-ons use `config.json` instead of `config.yaml`, some add-ons have `build.yaml` instead of `build.json` or no build file at all, and not every add-on includes a `rootfs/` tree):
```
addon_name/
├── config.yaml # HA add-on metadata, schema, ports, maps
├── build.json # Base Docker images per architecture (may be build.yaml, or absent)
├── CHANGELOG.md # Required; must be updated on every PR
└── rootfs/ # Optional; absent in some add-ons
└── etc/
├── cont-init.d/ # S6-overlay init scripts (numbered, run in order)
└── services.d/ # S6-overlay supervised services (some add-ons use
# s6-overlay v3 layout at etc/s6-overlay/s6-rc.d/ instead)
```
## Dockerfile Convention
Most Dockerfiles follow this 6-section pattern (some add-ons deviate slightly, e.g. using a pinned upstream image directly instead of `ARG BUILD_FROM`):
4.**Entrypoint**– Set `S6_STAGE2_HOOK=/ha_entrypoint.sh`
5.**Labels**– Standard OCI + HA labels from build args
6.**Healthcheck**– curl-based check suppressed from nginx/apache logs
Shared build-time scripts are pulled from `.templates/` at build time:
-`ha_automodules.sh`– Downloads module scripts listed in `ARG MODULES=`
-`ha_autoapps.sh`– Installs packages listed in `ENV PACKAGES=`
-`ha_entrypoint.sh`– S6 stage-2 hook; launches the cont-init stack at container start
-`ha_lsio.sh`– Patches LinuxServer.io base images for HA compatibility
-`bashio-standalone.sh`– Bashio library for scripts outside Supervisor context
The `ARG MODULES=` line lists template scripts to download at build time (e.g., `00-banner.sh 01-custom_script.sh 00-smb_mounts.sh`). Commonly-used modules in `.templates/` (not exhaustive):
-`00-banner.sh`– Print the add-on startup banner
-`00-global_var.sh`– Initialize global env vars from HA options
-`00-local_mounts.sh`– Mount local disks (localdisks option)
-`00-smb_mounts.sh`– SMB/CIFS network mount support
-`00-deprecated.sh`– Print a deprecation warning for add-ons superseded by official ones
-`01-config_yaml.sh`– Map HA options → app's `config.yaml`
-`01-custom_script.sh`– Run user-provided custom scripts
-`99-custom_script.sh`– Run a user `script.sh` from the add-on config dir at startup
Other helper scripts in `.templates/` used at build/run time: `ha_automatic_packages.sh` (resolve package names across distros), `ha_entrypoint_modif.sh`, `00-aaa_dockerfile_backup.sh`, plus `config.template`/`script.template`/`show_text_color` (templates/assets copied into add-ons).
## config.yaml Schema
Key fields in every add-on's `config.yaml`:
```yaml
arch:[aarch64, amd64]
image:ghcr.io/alexbelgium/{slug}-{arch}
version:"X.Y.Z"# upstream version (format varies; see Versioning section)
ingress:true/false
ingress_port:8000
map:
- addon_config:rw # /addon_configs/<hostname>/
- share:rw
- media:rw
- ssl
schema:
env_vars:# Allows arbitrary env var passthrough
- name:match(^[A-Za-z0-9_]+$)
value:str?
PUID:int
PGID:int
TZ:str?
networkdisks:str? # SMB mounts
localdisks:str? # Local disk mounts
```
The `env_vars` schema key enables the env-var passthrough mechanism. At runtime the `00-global_var.sh` cont-init module reads `/data/options.json` and exports each key as an environment variable (writing to `/.env` and `/etc/environment`). `ha_entrypoint.sh` is the S6 stage-2 hook that launches the cont-init stack but does not itself perform the JSON-to-env conversion.
## Versioning
Add-on versions in `config.yaml` closely follow the upstream release tag and do not conform to a single fixed format. Common patterns include:
-`X.Y.Z`– plain upstream semver (e.g. `0.137.0`)
-`X.Y.Z.N`– upstream version with a local patch counter (e.g. `0.6.26.2`)
For the local patch counter, use a dot (`X.Y.Z.N`), not a hyphen. `X.Y.Z-N` parses as a semver pre-release tag, which Home Assistant Supervisor treats as *older* than plain `X.Y.Z` — it will not offer the update. New and updated add-ons should use `.N`; existing `-N` versions should be migrated to `.N` opportunistically (e.g. when that add-on is next touched), not as a standalone repo-wide sweep.
When an upstream version is bumped, update `version` in `config.yaml`. If the add-on's `Dockerfile` contains an `ARG BUILD_UPSTREAM` line, update that value too — it is the canonical place that records the upstream version at build time (it is **not** stored in `build.json`/`build.yaml`). Some add-ons do not use `BUILD_UPSTREAM` at all. The `updater.json` file tracks which upstream source/repo to monitor and records the last seen version.
Add-ons support end-user customization without rebuilding the image (see the repo wiki). At startup, `99-custom_script.sh` looks in the add-on's config directory for a user-provided `script.sh` (seeded from `.templates/script.template`) and executes it. Combined with the `env_vars` passthrough and the custom-script modules, this lets users inject commands and environment without forking the add-on.
✓ [Aurral](aurral/) : Self-hosted music discovery, request management, flows, and playlist importing for Lidarr with library-aware recommendations.
@@ -113,15 +119,22 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓ [Bazarr NAS](bazarr/) : Companion application to Sonarr and Radarr to download subtitles
✓ [Bazarr NAS](bazarr/) : Companion application to Sonarr and Radarr to download subtitles
✓  [BentoPDF](bentopdf/) : Privacy-first PDF toolkit. 50+ tools, all processing client-side in the browser. Files never leave your device.
@@ -162,6 +187,18 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓  [Birdnet-go (customized and built from source)](birdnet-go-dev/) : Realtime BirdNET soundscape analyzer, compiled from the alexbelgium/birdnet-go fork with all open PRs merged, with OpenVINO enabled for Intel CPU/iGPU acceleration (amd64-only test build)
@@ -221,6 +258,24 @@ If you want to do add the repository manually, please follow the procedure highl
![amd64][amd64-badge]
![amd64][amd64-badge]
![ingress][ingress-badge]
![ingress][ingress-badge]
✓  [Claude Desktop](claude_desktop/) : Claude Desktop with Headroom, RTK, and TokenSave optimization
✓  [Cleanuparr](cleanuparr/) : Automatically removes stuck and unwanted downloads from your *arr and download clients
✓  [Cloudcommander](cloudcommander/) : Cloud Commander a file manager for the web with console and editor
✓  [Cloudcommander](cloudcommander/) : Cloud Commander a file manager for the web with console and editor
@@ -299,7 +355,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![amd64][amd64-badge]
✓  [FileBrowser Quantum](filebrowser_quantum/) : FileBrowser Quantum provides a modern, responsive file manager with multi-source support, advanced authentication options, and realtime indexing for your Home Assistant files.
✓  [FileBrowser Quantum](filebrowser_quantum/) : FileBrowser Quantum provides a modern, responsive file manager with multi-source support, advanced authentication options, and realtime indexing for your Home Assistant files
@@ -309,7 +365,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓  [Filebrowser (12032x)](filebrowser/) : filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files
✓  [Filebrowser](filebrowser/) : filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files
@@ -355,7 +411,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![amd64][amd64-badge]
✓ [Free Games Claimer (NoVNC not working)](free_games_claimer/) : automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG
✓ [Free Games Claimer](free_games_claimer/) : Automatically claims free games from Epic Games Store, Amazon Prime Gaming, GOG, Steam, and optional GamerPower-supported stores
@@ -470,7 +522,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓  [Jellyfin NAS](jellyfin/) : A free Software Media System that puts you in control of managing and streaming your media
✓  [Jellyfin (88956x) NAS](jellyfin/) : A free Software Media System that puts you in control of managing and streaming your media
✓  [Mealie](mealie/) : Mealie is a self hosted recipe manager and meal planner built in Vue
✓  [Mealie](mealie/) : Mealie is a self hosted recipe manager and meal planner built in Vue
@@ -594,7 +660,7 @@ If you want to do add the repository manually, please follow the procedure highl
![ingress][ingress-badge]
![ingress][ingress-badge]
![mqtt][mqtt-badge]
![mqtt][mqtt-badge]
✓  [NetAlertX Full Access](netalertx_fa/) : 🖧🔍 WIFI / LAN scanner, intruder, and presence detector
✓  [NetAlertX Full Access](netalertx_fa/) : 🖧🔍 Centralized network visibility and continuous asset discovery.
@@ -620,6 +686,12 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓ [Nginx Proxy Manager + Static Web Server](nginx_webserver_proxy/) : Nginx Proxy Manager with a built-in configurable static file server. Manage reverse proxies via NPM UI on port 81 while serving files from HA storage on port 80.
@@ -672,10 +744,10 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![amd64][amd64-badge]
✓ [Overseerr](overseerr/) : Request management and media discovery tool built to work with your existing Plex ecosystem
✓ [Overseerr (deprecated)](zzz_archived_overseerr/) : Request management and media discovery tool built to work with your existing Plex ecosystem (deprecated, migrate to Seer add-on)
@@ -724,6 +796,14 @@ If you want to do add the repository manually, please follow the procedure highl
![amd64][amd64-badge]
![amd64][amd64-badge]
![full_access][full_access-badge]
![full_access][full_access-badge]
✓  [Portainer Business Edition](portainer_be/) : Manage your Docker environment with ease (Business Edition)
@@ -738,7 +818,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![aarch64][aarch64-badge]
![amd64][amd64-badge]
![amd64][amd64-badge]
✓ [Prowlarr NAS](prowlarr/) : Torrent Trackers and Usenet indexers offering complete management ofSonarr, Radarr, Lidarr, and Readarr indexers with no per app setup required
✓ [Prowlarr NAS](prowlarr/) : Torrent Trackers and Usenet indexers offering complete management ofSonarr, Radarr, Lidarr, and Readarr indexers with no per app setup required
@@ -827,6 +907,14 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![smb][smb-badge]
![localdisks][localdisks-badge]
![localdisks][localdisks-badge]
✓  [Seerr](seerr/) : Open-source media request and discovery manager for Jellyfin (88956x), Plex, and Emby
✓  [Spotweb](spotweb/) : Spotweb is a decentralized usenet community based on the Spotnet protocol
✓  [Spotweb](spotweb/) : Spotweb is a decentralized usenet community based on the Spotnet protocol
✓ [zzz_archived : Code-server (VScode)](zzz_archived_code-server/) : Deprecated : Code-server is VS Code running on a remote server, accessible through the browser
✓ [zzz_archived : Code-server (VScode)](zzz_archived_code-server/) : Deprecated : Code-server is VS Code running on a remote server, accessible through the browser
## description: Automatic addons update by aligning version tag with upstream releases 3.19.16 (2026-01-10)
## 2026.08 (2026-08-01)
- Addon versions written in config.yaml now always comply with Home Assistant versioning: an upstream tag Home Assistant cannot order (`version-bf9e0b4f`, `ubuntu-2026-06-01`, ...) or would sort as older (`1.2.3-2`, `1.2.3+4`) no longer lands in config.yaml. The addon number is incremented instead, while the raw upstream tag stays in updater.json so the same release is never published twice
- Pre-release markers become a version section, `5.0.0b5` is published as `5.0.0.5`
- A tag Home Assistant cannot order keeps every number it carries, `v26.2-ls256` is published as `v26.2.256`
- Upstream tags are escaped before being replaced in Dockerfile/build files
## 2026.06 (05-06-2026)
- Minor bugs fixed
- Minor bugs fixed
## 2026.05 (30-05-2026)
- Update lastversion to 3.6.12
## 3.19.16 (2026-01-10)
## 3.19.16 (2026-01-10)
- Add config option to choose ISO8601 (YYYY-MM-DD) or DD-MM-YYYY dates for last_update/changelog entries
- Add config option to choose ISO8601 (YYYY-MM-DD) or DD-MM-YYYY dates for last_update/changelog entries
@@ -37,7 +37,8 @@ This script allows to automatically update addons based on upstream new releases
The installation of this add-on is pretty straightforward and not different in
The installation of this add-on is pretty straightforward and not different in
comparison to installing any other Hass.io add-on.
comparison to installing any other Hass.io add-on.
1.[Add my Hass.io add-ons repository][repository] to your Hass.io instance.
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
1. Install this add-on.
1. Install this add-on.
1. Configure the add-on to your preferences, see below
1. Configure the add-on to your preferences, see below
1. Click the `Save` button to store your configuration.
1. Click the `Save` button to store your configuration.
@@ -72,6 +73,22 @@ You can add the following tags in the file :
- dockerhub_by_date: in dockerhub, uses the last_update date instead of the version
- dockerhub_by_date: in dockerhub, uses the last_update date instead of the version
- dockerhub_list_size: in dockerhub, how many containers to consider for latest version
- dockerhub_list_size: in dockerhub, how many containers to consider for latest version
### Addon version numbering
The `version` written in the addon `config.yaml` is the one Home Assistant compares to decide whether an update is available. Home Assistant hides the update when it can order both versions and the new one is not strictly newer (`1.2.3` -> `1.2.3-2` is a semver pre-release, so it is *older*), and it cannot order tags such as `version-bf9e0b4f` or `ubuntu-2026-06-01` at all.
The addon version is therefore derived from the upstream tag:
- a tag Home Assistant can order and that is newer is used as it is
-`1.2.3-4` and `1.2.3+4` become `1.2.3.4`
- a pre-release marker becomes a section of its own, so the number it carries keeps ordering the addon: `5.0.0b5` -> `5.0.0.5`
- a tag it cannot order keeps every number it carries, in order: `v26.2-ls256` -> `v26.2.256`, `nightly-2.6.1.5509-ls8` -> `2.6.1.5509.8`, `4.16-r0-ls94` -> `4.16.0.94`, `ubuntu-2026-07-28` -> `2026.07.28`. Words holding no number, an architecture, and anything else such as a commit hash are left out
- a tag holding no number at all (`version-bf9e0b4f`, `sts`) increments the current addon version (`1.37` -> `1.38`), or uses the date when there is nothing to increment (`2026.08.01`, then `2026.08.01.1` for a second update the same day)
`updater.json` always keeps the raw upstream tag, so the next run still compares upstream with upstream and a single upstream release never triggers two addon updates. The raw tag is also kept in the Dockerfile and the build files, and is added to the changelog entry when it differs from the addon version.
These rules are checked by `python3 /usr/bin/ha_version.py --selftest`, which can be run from a terminal in the addon container.
### Addon configuration
### Addon configuration
Here you define the values that will allow the addon to connect to your repository.
Here you define the values that will allow the addon to connect to your repository.
bashio::log.error "... $SLUG : version $ADDONVERSION could not be written in the addon config, reverting"
git checkout -- "$ADDONFOLDER"
continue
fi
# Replace upstream tag and date, keeping the file intact if jq
# fails as a truncated updater.json would lose the addon source
if ! UPDATERJSON="$(jq --arg version "$LASTVERSION" --arg date "$DATE"'.upstream_version = $version | .last_update = $date'"$ADDONFOLDER/updater.json")";then
bashio::log.error "... $SLUG : updater.json could not be updated, reverting"
[Aurral](https://github.com/lklynet/aurral) is a self-hosted music discovery, request management, flows, and playlist importing app for Lidarr with library-aware recommendations.
This addon is based on the docker image <https://github.com/lklynet/aurral>
## Configuration
| Option | Default | Description |
|---|---|---|
| `download_folder` | `/share/aurral/downloads` | Path where Aurral writes flow downloads. Must be under `/share`. |
| `weekly_flow_folder` | `weekly-flow` | Subfolder name appended to `download_folder` for weekly flow files. The full path will be `download_folder/weekly_flow_folder`. |
## Installation
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
2. Install this add-on.
3. Click the `Save` button to store your configuration.
4. Set the `download_folder` option to your preferred path.
5. Optionally set `weekly_flow_folder` to customise the weekly flow subfolder name.
6. Start the add-on.
7. Check the logs of the add-on to see if everything went well.
- Added support for configuring extra environment variables via the `env_vars` add-on option alongside config.yaml. See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
- Added support for configuring extra environment variables via the `env_vars` add-on option alongside config.yaml. See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
- Fix base_url sed patterns rewriting *every*`base_url` key in Bazarr's config.yaml (radarr.base_url, sonarr.base_url, and any other configured integration), instead of only Bazarr's own under `general:`. This silently broke the Radarr/Sonarr connections inside Bazarr on every addon restart when ingress was enabled
## 1.6.0.1 (2026-07-27)
- Fix ingress: nginx rewrote Bazarr's redirects into an absolute `http://<host>:8099/...` URL, which the browser blocked as mixed content when Home Assistant is served over HTTPS. Redirects now stay relative and point at the ingress path
- Fix fallback base_url in the nginx service script missing its leading `/`, which crashed Bazarr on startup
## 1.6.0 (2026-07-08)
- Update to latest version from linuxserver/docker-bazarr (changelog : https://github.com/linuxserver/docker-bazarr/releases)
## 1.5.6-4 (2026-04-22)
- Fix Bazarr crash on startup: base_url must start with '/' for Flask blueprint registration
## 1.5.6-3
- Add Ingress support with nginx reverse proxy for sidebar integration
if ! bashio::config.has_value "connection_mode" || [ "$(bashio::config 'connection_mode')" != "noingress_auth" ]; then
if ! sed -n "/^general:/,/^[^ ]/ { /^ base_url: \/$slug$/p; }" "$CONFIG_LOCATION" | grep -q .; then
bashio::log.warning "BaseUrl not set properly, restarting"
# Must start with / for Flask blueprint registration. Scoped to
# the general: block only -- config.yaml also carries a base_url
# under each configured *arr integration (radarr.base_url,
# sonarr.base_url, ...) and those must not be touched.
sed -i "/^general:/,/^[^ ]/{ s| base_url:.*| base_url: /$slug|; }" "$CONFIG_LOCATION"
bashio::addon.restart
fi
fi
fi
fi
bashio::log.info "Starting NGinx..."
exec nginx
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.