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>
# 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."
@@ -43,15 +43,21 @@ Shared build-time scripts are pulled from `.templates/` at build time:
-`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
@@ -86,11 +92,13 @@ The `env_vars` schema key enables the env-var passthrough mechanism. At runtime
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`)
-`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.
## updater.json Format
@@ -120,8 +128,41 @@ When an upstream version is bumped, update `version` in `config.yaml`. If the ad
**Weekly** (`weekly_addons_updater`): Runs the `addons_updater` container to bump add-on versions to match upstream.
Other automation workflows:
-`daily_README.yaml`– Regenerates the root `README.md` add-on table.
-`weekly_crlftolf.yaml`– Finds and fixes CRLF line endings repo-wide.
-`weekly_reduceimagesize.yml`– Compresses images and opens a PR with savings.
-`weekly_stats.yaml` / `helper_stats_graphs.yaml`– Refresh the `Stats`/`Stats2` files and stat graphs.
-`daily_stale.yml`– Warns and closes stale issues/PRs.
-`on_issues.yml` / `on_issues_ping_submitter.yml`– Link issues to add-on READMEs and ping submitters.
-`generate_stargazer_map.yml`– Regenerates the stargazer map image.
Adding `[nobuild]` anywhere in a commit message skips the builder workflow.
### AI issue triage
A tiered, Claude-powered pipeline triages and fixes add-on issues. It escalates
from cheap classification to a maintainer-approved automated fix, always leaving
manual actions with precedence. Prompts live in `.github/prompts/`, shared shell
in `.github/scripts/`.
| Workflow | Model | Trigger | Role |
|---|---|---|---|
| `on_issues_ai_triage.yaml` | Sonnet-low | issue opened (+ author reply, daily catch-up) | Tier 1: classify, dedupe, answer, ask for info; label `ai-triage` for real add-on bugs |
| `daily_ai_fix.yaml` | Opus 5-xhigh | daily 03:00 | Tier 2: diagnose the `ai-triage` batch; small+confident → ready PR (`ai:fixed`); else write a plan (`ai:plan-pending`) |
| `on_issue_approved.yaml` | Opus 5-high | maintainer adds `ai:approved` | Tier 3: execute the approved plan → ready PR |
| `on_claude_mention.yml` | Sonnet-low | `@claude` by @alexbelgium | Manual interactive override on any issue/PR |
| `on_pr_coderabbit.yml` | Sonnet-low | CodeRabbit reviews an `ai-fix/*` PR | Once: fix or reply to review comments |
Control labels (`ai:*`) are workflow-owned. Key ones: `ai-triage` (queued for
the sweep), `ai:plan-pending` (plan posted, awaiting `ai:approved`), `ai:fixed`,
`ai:upstream`, `ai:needs-info` (a reporter reply re-runs tier 1 once),
`ai:needs-human`, `ai:blocked` (touched protected paths). `no-ai` opts an issue
out of the automated tiers but not the manual ones. **Kill switch:** set the
repo variable `AI_DISABLED=true` to pause every AI workflow with no file edits.
AI fixes must never touch `.github/` or `.templates/` (enforced by
`ai_guard_paths.sh`) or the `version`/`upstream` fields in `config.yaml`.
## Linting Rules
| Tool | Config | Key ignores |
@@ -146,3 +187,7 @@ Scripts in `rootfs/etc/cont-init.d/` run in lexicographic order. Common numberin
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.
@@ -169,6 +187,18 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-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)
@@ -228,6 +258,16 @@ If you want to do add the repository manually, please follow the procedure highl
![amd64][amd64-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
@@ -314,7 +355,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-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
@@ -324,7 +365,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓  [Filebrowser (25659x)](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
@@ -370,7 +411,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-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
@@ -485,7 +522,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓  [Jellyfin NAS](jellyfin/) : A free Software Media System that puts you in control of managing and streaming your media
✓  [Jellyfin (88478x) NAS](jellyfin/) : A free Software Media System that puts you in control of managing and streaming your media
@@ -548,7 +585,7 @@ If you want to do add the repository manually, please follow the procedure highl
![aarch64][aarch64-badge]
![amd64][amd64-badge]
✓  [Maintainerr](maintainerr/) : Rule-based media cleanup tool for Plex, Jellyfin and Emby. Creates collections and optionally deletes unwatched content.
✓  [Maintainerr](maintainerr/) : Rule-based media cleanup tool for Plex, Jellyfin (88478x) and Emby. Creates collections and optionally deletes unwatched content.
@@ -861,7 +899,7 @@ If you want to do add the repository manually, please follow the procedure highl
![smb][smb-badge]
![localdisks][localdisks-badge]
✓  [Seerr](seerr/) : Open-source media request and discovery manager for Jellyfin, Plex, and Emby
✓  [Seerr](seerr/) : Open-source media request and discovery manager for Jellyfin (88478x), Plex, and Emby
✓  [Spotweb](spotweb/) : Spotweb is a decentralized usenet community based on the Spotnet protocol
[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.
- Harden the `output.sqlite.path` rewrite added for the persistence fix: reject paths containing `..` traversal segments (shared `validate_safe_path` check), and create the destination's parent directory under `/config` when the relative path includes a subdirectory (e.g. `db/birdnet.db`), since SQLite does not create missing parent directories itself.
## source-20260705-2 (05-07-2026)
- Fix detections/database not persisting across restarts on a fresh install: upstream's default `config.yaml` ships `output.sqlite.path: birdnet.db` (relative) explicitly, so the missing-only (`//=`) seeding introduced previously never rewrote it to an absolute path. A relative path resolves against the app's ephemeral working directory, so the database was silently recreated empty on every restart. Any relative `output.sqlite.path` is now rewritten to live under the persistent `/config` on startup; values already set to an absolute path are left untouched. (https://github.com/tphakala/birdnet-go/discussions/3774)
## source-20260705 (05-07-2026)
- Minor bugs fixed
## source-2026070 (05-07-2026)
- Minor bugs fixed
## source-20260703 (03-07-2026)
- Minor bugs fixed
## source-20260702-em (02-07-2026)
- Minor bugs fixed
## source-20260702-2 (02-07-2026)
- Minor bugs fixed
## source-20260701 (01-07-2026)
- Minor bugs fixed
## source-20260629-3 (29-06-2026)
- Minor bugs fixed
## source-20260629-2 (29-06-2026)
- Minor bugs fixed
## source-20260629 (29-06-2026)
- Minor bugs fixed
## source-20260627-v3 (28-06-2026)
- Minor bugs fixed
## source-20260627-v2 (27-06-2026)
- Minor bugs fixed
## source-20260627 (27-06-2026)
- Minor bugs fixed
## source-20260626-5 (26-06-2026)
- Minor bugs fixed
## source-20260626-4 (26-06-2026)
- Minor bugs fixed
## source-20260626-3 (26-06-2026)
- Minor bugs fixed
## source-20260626 (26-06-2026)
- Minor bugs fixed
## source-20260625-14 (26-06-2026)
- Minor bugs fixed
## source-20260625-13 (25-06-2026)
- Minor bugs fixed
## source-20260625-12 (25-06-2026)
- Minor bugs fixed
## source-20260625-11 (25-06-2026)
- Minor bugs fixed
## source-20260625-10 (25-06-2026)
- Minor bugs fixed
## source-20260625-5 (25-06-2026)
- Minor bugs fixed
## source-20260625-4 (25-06-2026)
- Minor bugs fixed
## source-20260625-3 (25-06-2026)
- Minor bugs fixed
## source-20260625-2 (25-06-2026)
- Minor bugs fixed
## source-20260625 (25-06-2026)
- Minor bugs fixed
## source-20260624-6 (24-06-2026)
- Minor bugs fixed
## source-20260624-5 (24-06-2026)
- Minor bugs fixed
## source-20260624-4 (24-06-2026)
- Minor bugs fixed
## source-20260624-3 (24-06-2026)
- Minor bugs fixed
## source-20260624-2 (24-06-2026)
- Minor bugs fixed
## source-20260623-3 (23-06-2026)
- Minor bugs fixed
## source-20260623-2 (23-06-2026)
- Minor bugs fixed
## source-20260623 (23-06-2026)
- Minor bugs fixed
## source-20260622 (23-06-2026)
- Minor bugs fixed
## source-20260621-5 (22-06-2026)
- Minor bugs fixed
## source-20260621-4 (22-06-2026)
- Minor bugs fixed
## source-20260621-3 (22-06-2026)
- Minor bugs fixed
## source-20260621-2 (22-06-2026)
- Minor bugs fixed
## source-20260621-1 (21-06-2026)
- Fix OpenVINO load failure: bundle oneTBB (libtbb.so.12) from OpenVINO 3rdparty libs so libopenvino_c.so resolves at runtime
## source-20260620-13 (21-06-2026)
- Minor bugs fixed
## source-20260620-12 (21-06-2026)
- Minor bugs fixed
## source-20260620-11 (21-06-2026)
- Minor bugs fixed
## source-20260620-10 (21-06-2026)
- Minor bugs fixed
## source-20260620-7 (21-06-2026)
- Minor bugs fixed
## source-20260620-6 (21-06-2026)
- Minor bugs fixed
## source-20260620-5 (21-06-2026)
- Minor bugs fixed
## source-20260620-4 (21-06-2026)
- Minor bugs fixed
## source-20260620-3 (21-06-2026)
- Minor bugs fixed
## source-20260620-2 (21-06-2026)
- Minor bugs fixed
## source-20260620 (21-06-2026)
- Minor bugs fixed
## source-20260619-8 (20-06-2026)
- Minor bugs fixed
## source-20260619-7 (20-06-2026)
- Minor bugs fixed
## source-20260619-6 (20-06-2026)
- Minor bugs fixed
## source-20260619-5 (20-06-2026)
- Minor bugs fixed
## source-20260619-4 (20-06-2026)
- Minor bugs fixed
## source-20260619-3 (20-06-2026)
- Minor bugs fixed
## source-20260619-2 (20-06-2026)
- Minor bugs fixed
## source-20260619 (20-06-2026)
- Minor bugs fixed
## source-20260618-3 (18-06-2026)
- Minor bugs fixed
## source-20260618-2 (18-06-2026)
- Minor bugs fixed
## source-20260617-4 (18-06-2026)
- Minor bugs fixed
## source-20260617-3 (17-06-2026)
- Minor bugs fixed
## source-20260617-2 (17-06-2026)
- Minor bugs fixed
## source-20260617 (17-06-2026)
- Minor bugs fixed
## source-20260616-2 (16-06-2026)
- Minor bugs fixed
## source-20260616 (16-06-2026)
- Minor bugs fixed
## source-20260615bats (15-06-2026)
- Minor bugs fixed
## source-20260615 (15-06-2026)
- Minor bugs fixed
## source-20260614-2 (14-06-2026)
- Minor bugs fixed
## source-20260614 (14-06-2026)
- Minor bugs fixed
## source-20260613 (13-06-2026)
- Minor bugs fixed
## source-20260612-4 (13-06-2026)
- Minor bugs fixed
## source-20260612-3 (12-06-2026)
- Minor bugs fixed
## source-20260612-2 (12-06-2026)
- Minor bugs fixed
## source-20260612 (12-06-2026)
- Minor bugs fixed
## source-20260610-9 (11-06-2026)
- Minor bugs fixed
## source-20260610-8 (11-06-2026)
- Minor bugs fixed
## source-20260610-7 (11-06-2026)
- Minor bugs fixed
## source-20260610-6 (11-06-2026)
- Minor bugs fixed
## source-20260610-5 (11-06-2026)
- Minor bugs fixed
## source-20260610-4 (10-06-2026)
- Minor bugs fixed
## source-20260610-3 (10-06-2026)
- Minor bugs fixed
## source-20260610-2 (10-06-2026)
- Minor bugs fixed
## source-20260610 (10-06-2026)
- Minor bugs fixed
## source-20260608-8 (09-06-2026)
- Minor bugs fixed
## source-20260608-7 (09-06-2026)
- Minor bugs fixed
## source-20260608-6 (08-06-2026)
- Minor bugs fixed
## source-20260608-5 (08-06-2026)
- Minor bugs fixed
## source-20260608-4 (08-06-2026)
- Minor bugs fixed
## source-20260608-3 (08-06-2026)
- Minor bugs fixed
## source-20260608-2 (08-06-2026)
- Minor bugs fixed
## source-20260608 (08-06-2026)
- Minor bugs fixed
## source-20260607-4 (07-06-2026)
- Minor bugs fixed
## source-20260607-3 (07-06-2026)
- Minor bugs fixed
## source-20260607-2 (07-06-2026)
- Minor bugs fixed
## source-20260607 (07-06-2026)
- **Test variant** of the birdnet-go add-on that compiles BirdNET-Go from the `alexbelgium/birdnet-go` fork instead of pulling the prebuilt `ghcr.io/tphakala/birdnet-go` image.
- At build time, `merge-prs.sh` syncs the fork's `main` with the `tphakala/birdnet-go` upstream and merges every open non-draft ("in review") pull request on the fly, so the resulting binary is upstream main + all work currently under review.
- Home Assistant integration layers (nginx ingress, modules, init scripts, options handling) are identical to the standard birdnet-go add-on.
- Published as a separate image (`ghcr.io/alexbelgium/birdnet-go-source-{arch}`) so it never overwrites the production add-on image.
- Fix (`01-structure.sh`): create the absolute `BIRDSONGS_FOLDER` target (e.g. the default `/config/clips`) before migrating clips from a legacy `/data/clips`, so upgrades with existing recordings no longer abort startup under `set -e`.
## nightly-20260601-2 (03-06-2026)
- Minor bugs fixed
## nightly-20260601 (2026-06-01)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260524 (2026-05-30)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260429-405 (2026-05-30)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260525-3 (28-05-2026)
- New `mqtt_auto_config` addon option (default `false`). When `true` and the Home Assistant MQTT addon is active, `realtime.mqtt.{enabled,broker,username,password}` are written directly to `config.yaml` on every restart. When `false` but Mosquitto is detected, the addon still logs the broker details and reminds you about the option — nothing is written.
- New `mariadb_auto_config` addon option (default `false`). When `true` and the Home Assistant MariaDB addon is active, `output.mysql.*` is filled in and `output.sqlite.enabled` is set to `false`. When `false` but MariaDB is detected, the addon logs the credentials and reminds you about the option.
- **Breaking**: `output.sqlite.path` and `logging.file_output.*` are now seeded only when missing from `config.yaml` (previously overwritten every restart). Values changed through the BirdNET-Go UI or by hand-editing `config.yaml` now survive container restarts. If you relied on `LOG_MAX_SIZE_MB` / `LOG_MAX_AGE_DAYS` addon options to override an existing setting in `config.yaml`, remove the existing key from `config.yaml` or edit it directly — the option will only be applied on first run.
- **Breaking (UI only)**: The nginx ingress reverse-proxy no longer rewrites HTML `href`/`src`/`action` attributes; upstream BirdNET-Go handles those itself via `X-Ingress-Path`. JavaScript string-literal rewrites are unchanged. Please file an issue if you see broken images, links, or forms in the ingress UI after upgrade.
- Fix database-migration restore: the timestamped backup created during a `BIRDSONGS_FOLDER` change was being written to the script's working directory and looked up under a fresh timestamp on restore, so a SQL failure left the user unable to recover. Backup path is now absolute and reused for restore.
- Harden the `BIRDSONGS_FOLDER` SQL/YAML path substitution: paths containing characters outside `[A-Za-z0-9._/-]` are now rejected up front instead of being interpolated raw into the SQL UPDATE statement.
- Tolerate a missing internet connection on first boot: if the default `config.yaml` cannot be downloaded from GitHub, the init script now seeds an empty YAML document so the addon-defaults block populates a usable config (rather than aborting the script on the next `yq` call under `set -e`).
- Warn (without failing the build) if the upstream `entrypoint.sh` patch target drifts in a new nightly.
- Remove a dead nginx upstream definition that pointed at an unused port.
## nightly-20260525-2 (26-05-2026)
- Suppress noisy startup logs: silence `chmod /dev/snd` errors on the read-only HA mount, and hide unavailable ALSA plugins (JACK, OSS, dsnoop) from device enumeration so libjack and pcm_oss/dsnoop probes no longer print at launch. ALSA overrides are written to `/root/.asoundrc` (since `/etc/asound.conf` is read-only in this environment).
- Allow advanced users to override the ALSA config by dropping a custom `asound.conf` into the addon config folder.
- Add LOG_MAX_SIZE_MB and LOG_MAX_AGE_DAYS addon options to manage log storage size
- Automatically trim log files exceeding configured age on startup
## nightly-20260525 (26-05-2026)
- Minor bugs fixed
## nightly-20260511-414-2 (22-05-2026)
- Minor bugs fixed
## nightly-20260511-414 (2026-05-16)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260429-405 (2026-05-02)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260321-397 (2026-03-26)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260315 (2026-03-21)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260311 (2026-03-14)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260118-2 (17-02-2026)
- Minor bugs fixed
## nightly-20260118 (2026-01-21)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260113 (2026-01-14)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260111 (2026-01-12)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20260110 (2026-01-10)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20251223-2 (2025-12-27)
- Minor bugs fixed
## nightly-20251224 (2025-12-24)
- Minor bugs fixed
## nightly-20251223 (2025-12-23)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20251214 (2025-12-20)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
- 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.
- Preserve the microphone selected in the BirdNET-Go UI unless the `homeassistant_microphone` option explicitly forces the default device.
## "nightly-20251028" (2025-11-01)
- Minor bugs fixed
## nightly-20251028 (2025-11-01)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## "nightly-20251012" (2025-10-18)
- Minor bugs fixed
## nightly-20251012 (2025-10-18)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20251008 (2025-10-11)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250904_6 (2025-09-17)
- New option "homeassistant_microphone". If set to true, will use homeassistant's microphone by setting the audio_card to "default". Please use the addon options to select the device to which "default" is allocated
## nightly-20250904 (2025-09-06)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250826 (2025-08-30)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250813 (2025-08-16)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250805 (2025-08-09)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250731-4 (2025-08-04)
- Minor bugs fixed
## nightly-20250731-3 (2025-08-04)
- Minor bugs fixed
## nightly-20250731-2 (2025-08-02)
- Minor bugs fixed
## nightly-20250731 (2025-08-01)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250730 (2025-07-30)
- Minor bugs fixed
## nightly-20250725-2 (2025-07-28)
- Fix /asset path
- Added 9090 telemetry port
## nightly-20250725 (2025-07-25)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## nightly-20250710 (2025-07-12)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250710 (2025-07-12)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250704 (2025-07-07)
- Minor bugs fixed
## 20250508 (2025-07-05)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250419 (2025-05-17)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250427-7 (2025-05-15)
- Breaking change: COMMAND addon option removed. Please instead use the config.yaml to define the RTSP feeds
- Use entrypoint
## 20250427-2 (2025-04-27)
- Minor bugs fixed
## 20250427 (2025-04-27)
- Minor bugs fixed
## 20250316 (2025-04-26)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 0.6.4-3 (2025-04-07)
- Minor bugs fixed
## 0.6.4-2 (2025-03-30)
- Minor bugs fixed
## 0.6.4 (2025-03-17)
- Minor bugs fixed
## 0.6.3 (2025-03-15)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 0.6.2-2 (2025-02-21)
- Minor bugs fixed
## 0.6.2 (2025-02-21)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250126-2 (2025-02-21)
- Minor bugs fixed
## 20250126 (2025-02-15)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 0.6.0-nightly-20250124 (2025-01-25)
- Minor bugs fixed
## 0.6.0-4 (2025-01-21)
- Fix sounds play
- Correct sqlite for //
## 0.6.0 (2025-01-18)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
## 20250103-10 (2025-01-17)
- BREAKING CHANGE : improve implementation of addon options such as Birdsongs folder. Please check the log at first start if anything is different than you expected
- WARNING : your files will move to the new Birdsongs folder in case of change
- WARNING : your db will be modified in case of Birdsongs folder change to still allow access to files. A backup will always be created
- Fix ingress issues
## 20250103 (2025-01-11)
- Update to latest version from tphakala/birdnet-go (changelog : https://github.com/tphakala/birdnet-go/releases)
BirdNET-Go can be integrated with Home Assistant using a MQTT Broker.
> **💡 Easiest path — automatic discovery.** If you run the Home Assistant
> Mosquitto (MQTT) addon, just set `mqtt_auto_config: true` in this add-on's
> options. The add-on then wires in the broker credentials **and** enables
> BirdNET-Go's native Home Assistant MQTT auto-discovery, so the detection
> sensors appear in Home Assistant automatically with **no manual YAML at
> all**. The manual sensor/template/card configuration below is only needed if
> you want to build your own custom entities instead of (or in addition to) the
> auto-discovered ones.
## MQTT Configuration
Your Home Assistant must be setup with MQTT and BirdNET-Go MQTT integration must be enabled. Modify the BirdNET-Go config.yaml file to enable MQTT. If you are using the Mosquitto Broker addon, you will see a log message during the BirdNET-Go startup showing the internal MQTT server details needed for configuration similar to below.
```text
BirdNET-Go log snipped showing MQTT details:
/etc/cont-init.d/33-mqtt.sh: executing
---
MQTT addon is active on your system! Add the MQTT details below to the Birdnet-go config.yaml :
Add the [MQTT sensor](https://www.home-assistant.io/integrations/sensor.mqtt/) yaml configuration below to your Home Assistant configuration.yaml file. Reload the configuration and once BirdNET-Go publishes a new finding to MQTT the new BirdNET-Go sensors should show that latest finding data.
Then create a new template sensor using the configuration below.
```yaml
- trigger:
- platform:mqtt
topic:"birdnet"
id:birdnet
- platform:time
at:"00:00:00"
id:reset
sensor:
- unique_id:c893533c-3c06-4ebe-a5bb-da833da0a947
name:BirdNET-Go Events
state:>
{% if trigger.id == 'reset' %}
{{ now() }}
{% elif trigger.id == 'birdnet' %}
{{ today_at(trigger.payload_json.Time) }}
{% endif %}
attributes:
bird_events:>
{% if trigger.id == 'reset' %}
{{ [] }}
{% elif trigger.id == 'birdnet' %}
{% set time = trigger.payload_json.Time %}
{% set name = trigger.payload_json.CommonName %}
{% set confidence = trigger.payload_json.Confidence|round(2) * 100 ~ '%' %}
{% set current = this.attributes.get('bird_events', []) %}
{% set new = dict(time=time, name=name, confidence=confidence) %}
{{ current + [new] }}
{% endif %}
```
### BirdNET-Go Dashboard Cards
There are two versions listed below. The first example will link the Bird Name to Wikipedia. The other example will link to All About Birds. You will need to modify the Confidence link to match your Home Assistant setup.
> **⚠️ Test build.** This is a special variant of the [standard birdnet-go add-on](https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-go). Instead of pulling the prebuilt `ghcr.io/tphakala/birdnet-go` image, it **compiles BirdNET-Go from the [`alexbelgium/birdnet-go`](https://github.com/alexbelgium/birdnet-go) fork**. At build time it syncs the fork's `main` with the `tphakala/birdnet-go` upstream and **merges every open non-draft ("in review") pull request on the fly** (see [`merge-prs.sh`](./merge-prs.sh)), so the binary reflects upstream main plus all work currently under review. Everything below is identical to the standard add-on.
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
If this add-on saves you time or makes your setup easier, I would be very grateful for your support!
[![Buy me a coffee][donation-badge]](https://www.buymeacoffee.com/alexbelgium)
[![Donate via PayPal][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA)
_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_
[](https://github.com/alexbelgium/hassio-addons/stargazers)
[BirdNET-Go](https://github.com/tphakala/birdnet-go/tree/main) is an AI solution for continuous avian monitoring and identification developed by @tphakala
This addon is based on their docker image.
## Configuration
Install, then start the addon a first time. Webui can be found at <http://homeassistant:8080>.
You'll need a microphone : either use one connected to HA or the audio stream of a rstp camera.
The audio clips folder can be stored on an external or SMB drive by mounting it from the addon options, then specifying the path instead of "clips/". For example, "/mnt/NAS/Birdnet/"
Options can be configured through three ways :
- Addon options
```yaml
BIRDSONGS_FOLDER:/config/clips# where audio clips are stored (can be on a mounted drive)
LOG_MAX_SIZE_MB:50# max log file size before rotation
LOG_MAX_AGE_DAYS:7# max log retention in days
homeassistant_microphone:false# when true, force audio source to "default" (HA microphone)
env_vars:[]# extra environment variables to pass to the container
TZ:Etc/UTC# timezone, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
mqtt_auto_config:false# set true to auto-wire the Home Assistant MQTT addon into config.yaml
mariadb_auto_config:false# set true to auto-wire the Home Assistant MariaDB addon into config.yaml (also disables SQLite)
```
- Config.yaml
Additional variables can be configured using the config.yaml file found in /config/db21ed7f_birdnet-go/config.yaml using the Filebrowser addon
- Config_env.yaml
Additional environment variables can be configured there
### MQTT and MariaDB auto-configuration (opt-in)
If the Home Assistant **MQTT** addon is installed and running and you set `mqtt_auto_config: true` in the addon options, the addon writes the HA Mosquitto credentials directly into BirdNET-Go's `config.yaml` on every startup: `realtime.mqtt.enabled`, `broker`, `username`, and `password` are populated, and the topic defaults to `birdnet`. In addition, it enables BirdNET-Go's **native Home Assistant MQTT auto-discovery** (`realtime.mqtt.homeassistant.enabled`), so the detection sensors show up in Home Assistant automatically — **no manual MQTT sensor YAML required** (the hand-written sensors in [HAINTEGRATION.md](./HAINTEGRATION.md) remain available if you prefer to build your own). Messages are also retained (`realtime.mqtt.retain: true`) so sensor states survive Home Assistant restarts. When the option is `false` (the default), the addon still logs the broker details and reminds you about the option whenever Mosquitto is detected — nothing is written.
If the Home Assistant **MariaDB** addon is installed and running and you set `mariadb_auto_config: true`, the addon writes the HA credentials into `output.mysql.*` and sets `output.sqlite.enabled` to `false` (database name `birdnet`, created on first connect). When the option is `false` (the default), the addon only logs the credentials so you can configure them manually.
The addon also seeds `output.sqlite.path` and `logging.file_output.*` defaults only when those keys are missing from `config.yaml`, so values you change through the BirdNET-Go UI now survive container restarts.
### Mounting Drives
This addon supports mounting both local drives and remote SMB shares:
- **Local drives**: See [Mounting Local Drives in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-Local-Drives-in-Addons)
- **Remote shares**: See [Mounting Remote Shares in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-remote-shares-in-Addons)
### Custom Scripts and Environment Variables
This addon supports custom scripts and environment variables through the `addon_config` mapping:
- **Custom scripts**: See [Running Custom Scripts in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons)
- **env_vars option**: Use the add-on `env_vars` option to pass extra environment variables (uppercase or lowercase names). See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
## Installation
The installation of this add-on is pretty straightforward and not different in comparison to installing any other add-on.
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. Click the `Save` button to store your configuration.
1. Set the add-on options to your preferences
1. Start the add-on.
1. Check the logs of the add-on to see if everything went well.
1. Open the webUI and adapt the software options
## Integration with HA
Home Assistant Integration instructions are found here, [Birdnet-Go Addon: Home Assistant Integration](./HAINTEGRATION.md)
## Setting up a RTSP Source using VLC
VLC opens a TCP port but the stream is udp. Because of this will need to configure Birdnet-Go to use udp. Adjust the config.yaml file to udp or use the birdnet-go command line option:
description: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)
devices:
- /dev/dri
- /dev/snd
- /dev/snd/controlC0
- /dev/snd/controlC1
- /dev/snd/pcmC1D0c
- /dev/snd/pcmC1D0p
- /dev/dri/card0
- /dev/dri/card1
- /dev/dri/renderD128
- /dev/vchiq
- /dev/video10
- /dev/video11
- /dev/video12
- /dev/video13
- /dev/video14
- /dev/video15
- /dev/video16
- /dev/ttyUSB0
- /dev/sda
- /dev/sdb
- /dev/sdc
- /dev/sdd
- /dev/sde
- /dev/sdf
- /dev/sdg
- /dev/nvme
- /dev/nvme0
- /dev/nvme0n1
- /dev/nvme0n1p1
- /dev/nvme0n1p2
- /dev/nvme0n1p3
- /dev/nvme1n1
- /dev/nvme1n1p1
- /dev/nvme1n1p2
- /dev/nvme1n1p3
- /dev/nvme2n1
- /dev/nvme2n1p1
- /dev/nvme2n1p2
- /dev/nvme2n3p3
- /dev/mmcblk
- /dev/fuse
- /dev/sda1
- /dev/sdb1
- /dev/sdc1
- /dev/sdd1
- /dev/sde1
- /dev/sdf1
- /dev/sdg1
- /dev/sda2
- /dev/sdb2
- /dev/sdc2
- /dev/sdd2
- /dev/sde2
- /dev/sdf2
- /dev/sdg2
- /dev/sda3
- /dev/sdb3
- /dev/sda4
- /dev/sdb4
- /dev/sda5
- /dev/sda6
- /dev/sda7
- /dev/sda8
- /dev/nvme0
- /dev/nvme1
- /dev/nvme2
environment:
BIRDNET_GID:"0"
BIRDNET_UID:"0"
image:ghcr.io/alexbelgium/birdnet-go-dev-{arch}
ingress:true
ingress_entry:"ui/dashboard"
ingress_stream:true
init:false
map:
- addon_config:rw
- media:rw
- share:rw
name:Birdnet-go (customized and built from source)
bashio::log.warning "Modifying database paths from $CURRENT_BIRDSONGS_FOLDER to $BIRDSONGS_FOLDER. A backup will be created at ${BACKUP_FILE}"
# Create backup at the absolute path we'll restore from on failure.
if ! cp /config/birdnet.db "$BACKUP_FILE";then
bashio::log.error "Failed to create a backup of the database. Aborting path modification."
exit1
fi
# Paths were validated above against [A-Za-z0-9._/-]+ so quote
# escaping in the SQL literal is not a concern.
SQL_QUERY="UPDATE notes SET clip_name = '${BIRDSONGS_FOLDER}/' || substr(clip_name, length('${CURRENT_BIRDSONGS_FOLDER}/') + 1) WHERE clip_name LIKE '${CURRENT_BIRDSONGS_FOLDER}/%';"
if ! sqlite3 /config/birdnet.db "$SQL_QUERY";then
bashio::log.warning "An error occurred while updating the paths. The database backup will be restored."
if[ -f "$BACKUP_FILE"];then
mv "$BACKUP_FILE" /config/birdnet.db
bashio::log.info "The database backup has been restored."
else
bashio::log.error "Backup file $BACKUP_FILE not found! Manual intervention required."
fi
else
bashio::log.info "Paths have been successfully updated."
fi
fi
fi
####################
# Correct Defaults
####################
# Seed addon-specific defaults only if the user has not set them in
# config.yaml. The "//=" form leaves any user-edited value alone, so
# changes made via the BirdNET-Go UI or by hand-editing /config/config.yaml
# survive container restarts.
bashio::log.info "Seeding default configuration values (only if missing)"
bashio::log.info "Seeding default log rotation: max ${LOG_MAX_SIZE_MB}MB per file, max ${LOG_MAX_AGE_DAYS} days retention (only applied if not already set)"
# Seed log-rotation defaults; do not clobber user-edited values.
# Create the database — birdnet-go connects to an existing schema and does NOT
# create it automatically. MYSQL_PWD avoids exposing the password via the
# process command line.
if ! MYSQL_PWD="${MYSQL_PASS}" mysql \
--skip-ssl \
--host="${MYSQL_HOST_RESOLVED}"\
--port="${MYSQL_PORT}"\
--user="${MYSQL_USER}"\
--connect-timeout=10\
-e "CREATE DATABASE IF NOT EXISTS \`${MYSQL_DATABASE}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;";then
bashio::log.error "Failed to create MariaDB database '${MYSQL_DATABASE}' — verify the MariaDB addon is running and the user has CREATE DATABASE privileges"
exit1
fi
bashio::log.blue "Database '${MYSQL_DATABASE}' is ready"
# Upstream config.go stores port as a string; pass it as such to match.
# $host / $port / etc. are jq/yq variables, not shell expansions — the
# single quotes around the filter are intentional.
# When the Home Assistant MQTT addon is active, optionally wire its
# credentials directly into BirdNET-Go's config.yaml. Upstream reads MQTT
# settings only from YAML (no env-var overrides exist), so this is the only
# way to auto-configure them. The behaviour is opt-in via the
# mqtt_auto_config addon option. When the option is off but Mosquitto is
# detected, we log a one-shot hint pointing users at the option.
#
# In addition to the broker credentials we enable BirdNET-Go's native Home
# Assistant MQTT auto-discovery (realtime.mqtt.homeassistant.*). This makes
# the detection sensors appear in Home Assistant automatically, so users no
# longer have to hand-write the MQTT sensor YAML from HAINTEGRATION.md.
CONFIG_LOCATION="/config/config.yaml"
if ! bashio::services.available 'mqtt';then
exit0
fi
MQTT_HOST="$(bashio::services 'mqtt''host')"
MQTT_PORT="$(bashio::services 'mqtt''port')"
MQTT_USER="$(bashio::services 'mqtt''username')"
MQTT_PASS="$(bashio::services 'mqtt''password')"
MQTT_BROKER="tcp://${MQTT_HOST}:${MQTT_PORT}"
if ! bashio::config.true 'mqtt_auto_config';then
bashio::log.green "---"
bashio::log.yellow "Home Assistant MQTT addon detected. Set 'mqtt_auto_config: true' in the addon options to wire it into BirdNET-Go automatically AND enable Home Assistant auto-discovery (sensors appear in HA with no manual YAML). Connection details:"
bashio::log.blue "MQTT user : ${MQTT_USER}"
bashio::log.blue "MQTT password: ${MQTT_PASS}"
bashio::log.blue "MQTT broker : ${MQTT_BROKER}"
bashio::log.green "---"
exit0
fi
if[ ! -f "$CONFIG_LOCATION"];then
bashio::log.warning "Skipping MQTT auto-configuration: $CONFIG_LOCATION not found"
exit0
fi
bashio::log.green "---"
bashio::log.blue "mqtt_auto_config enabled; writing Home Assistant MQTT credentials into BirdNET-Go config"
bashio::log.blue "Broker: ${MQTT_BROKER}"
bashio::log.blue "User: ${MQTT_USER}"
bashio::log.blue "Home Assistant auto-discovery: enabled (sensors appear in HA automatically)"
bashio::log.green "---"
# $broker / $user / $pass / "birdnet" are jq/yq variables and literals,
# not shell expansions, so the single quotes are intentional.
#
# Connection fields (enabled/broker/username/password) are force-set on every
# start so they track the HA MQTT addon's rotating credentials. Topic and the
# homeassistant.* discovery knobs use "//=" so they are only seeded when
# missing. retain is seeded via an explicit has() check rather than "//=",
# because jq treats a user-set "retain: false" as falsy and "//=" would wrongly
# flip it back to true; has() seeds the default only when the key is truly
# absent. Any value the user later changes in the BirdNET-Go UI or config.yaml
# therefore survives restarts. homeassistant.enabled is force-set to true
# because turning on discovery is the whole point of the auto-config option.
bashio::log.warning "Failed to copy /config/asound.conf; continuing with bundled /root/.asoundrc defaults"
fi
else
bashio::log.warning "/config/asound.conf exists but is not readable; continuing with bundled /root/.asoundrc defaults"
fi
fi
# Check if alsa_card is provided
CONFIG_LOCATION="/config/config.yaml"
if bashio::config.true "homeassistant_microphone";then
bashio::log.info "homeassistant_microphone option is selected. The audio card config value is set to 'default'. Set in the addon options to which this is set"
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.